Last active
July 8, 2024 16:24
-
-
Save vmedea/fce1d6d7fb6a0a4d9f1f89c188cb6197 to your computer and use it in GitHub Desktop.
Rexpaint .xp loader, updated for Godot 4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Rexpaint .xp loader, updated for Godot 4. | |
# Part of "Xenomusa" (C) Mara Huldra 2022 | |
# Based on rex-is-godot by RisingThumb. | |
# SPDX-License-Identifier: MIT | |
extends Object # DO NOT INSTANTIATE | |
class_name RexPaint | |
## Offset into image pixel for glyph. | |
const OFS_GLYPH := 0 | |
## Offset into image pixel for foreground color. | |
const OFS_FG := 1 | |
## Offset into image pixel for background color. | |
const OFS_BG := 2 | |
# cp437 to unicode mapping from rexpaint, for use with to_richtext. | |
const cp437: Array[String] = [ | |
"\u00a0", "☺", "☻", "♥", "♦", "♣", "♠", "•", "◛", "○", "◙", "♂", "♀", "♪", "♫", "☼", | |
"►", "◄", "↕", "‼", "¶", "§", "▬", "↨", "↑", "↓", "→", "←", "∟", "↔", "▲", "▼", | |
"\u00a0", "!", '"', "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", | |
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", | |
"@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", | |
"P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", | |
"`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", | |
"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~", "⌂", | |
"Ç", "ü", "é", "â", "ä", "à", "å", "ç", "ê", "ë", "è", "ï", "î", "ì", "Ä", "Å", | |
"É", "æ", "Æ", "ô", "ö", "ò", "û", "ù", "ÿ", "Ö", "Ü", "¢", "£", "¥", "₧", "ƒ", | |
"á", "í", "ó", "ú", "ñ", "Ñ", "ª", "º", "¿", "⌐", "¬", "½", "¼", "¡", "«", "»", | |
"░", "▒", "▓", "│", "┤", "╡", "╢", "╖", "╕", "╣", "║", "╗", "╝", "╜", "╛", "┐", | |
"└", "┴", "┬", "├", "─", "┼", "╞", "╟", "╚", "╔", "╩", "╦", "╠", "═", "╬", "╧", | |
"╨", "╤", "╥", "╙", "╘", "╒", "╓", "╫", "╪", "┘", "┌", "█", "▄", "▌", "▐", "▀", | |
"α", "ß", "Γ", "π", "Σ", "σ", "µ", "τ", "Φ", "Θ", "Ω", "δ", "∞", "φ", "ε", "∩", | |
"≡", "±", "≥", "≤", "⌠", "⌡", "÷", "≈", "°", "∙", "·", "√", "ⁿ", "²", "■", "□", | |
] | |
# Missing in unscii: | |
# 0x0f U+263C White sun with rays (supposed to be in font-pc8.txt) | |
# 0x7f U+2302 House (assigned to U+007f in font-pc8.txt, which is a Dt control character...) | |
# 0xb9 U+2310 Reversed not sign (supposed to be in font-pc8.txt) | |
# see https://github.com/viznut/unscii/issues/4 | |
const cp437_unscii: Array[String] = [ | |
"\u00a0", "☺", "☻", "♥", "♦", "♣", "♠", "•", "◛", "○", "◙", "♂", "♀", "♪", "♫", " ", | |
"►", "◄", "↕", "‼", "¶", "§", "▬", "↨", "↑", "↓", "→", "←", "∟", "↔", "▲", "▼", | |
"\u00a0", "!", '"', "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", | |
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", | |
"@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", | |
"P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", | |
"`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", | |
"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~", " ", | |
"Ç", "ü", "é", "â", "ä", "à", "å", "ç", "ê", "ë", "è", "ï", "î", "ì", "Ä", "Å", | |
"É", "æ", "Æ", "ô", "ö", "ò", "û", "ù", "ÿ", "Ö", "Ü", "¢", "£", "¥", "₧", "ƒ", | |
"á", "í", "ó", "ú", "ñ", "Ñ", "ª", "º", "¿", " ", "¬", "½", "¼", "¡", "«", "»", | |
"░", "▒", "▓", "│", "┤", "╡", "╢", "╖", "╕", "╣", "║", "╗", "╝", "╜", "╛", "┐", | |
"└", "┴", "┬", "├", "─", "┼", "╞", "╟", "╚", "╔", "╩", "╦", "╠", "═", "╬", "╧", | |
"╨", "╤", "╥", "╙", "╘", "╒", "╓", "╫", "╪", "┘", "┌", "█", "▄", "▌", "▐", "▀", | |
"α", "ß", "Γ", "π", "Σ", "σ", "µ", "τ", "Φ", "Θ", "Ω", "δ", "∞", "φ", "ε", "∩", | |
"≡", "±", "≥", "≤", "⌠", "⌡", "÷", "≈", "°", "∙", "·", "√", "ⁿ", "²", "■", "□", | |
] | |
## Read a Rexpaint .xp file into memory. | |
## | |
## Returns a dictionary with "versionInfo", "layerCount" and "layers" fields. "layers" is an array | |
## of dictionaries with "width", "height" and "image". "image" in turn is an array of | |
## three-element arrays per pixel. Use OFS_GLYPH, OFS_FG, OFS_BG to index. The order is | |
## top-to-bottom, left-to-right as in the file. | |
static func read(path: String) -> Dictionary: | |
# Read the compressed xp file for its buffer. | |
# This is a workaround as open_compressed doesn't work with .xp files | |
# (it expects a custom header, see https://github.com/godotengine/godot/issues/28999). | |
var buffer: PackedByteArray = FileAccess.get_file_as_bytes(path) | |
buffer = buffer.decompress_dynamic(-1, FileAccess.COMPRESSION_GZIP) | |
var ofs := 0 | |
var versionInfo := buffer.decode_u32(ofs) | |
var numberOfLayers := buffer.decode_u32(ofs + 4) | |
var layers := [] | |
const TRANSPARENT_BG := Color8(255, 0, 255, 255) # Background of 255, 0, 255 means transparent. | |
ofs += 8 | |
while len(layers) < numberOfLayers: | |
var width := buffer.decode_u32(ofs) | |
var height := buffer.decode_u32(ofs + 4) | |
var image := [] | |
ofs += 8 | |
for x in width: # It's encoded vertical line by line: top to bottom then left to right | |
for y in height: | |
var glyph := buffer.decode_u32(ofs) | |
var fgC := Color8(buffer.decode_u8(ofs + 4), buffer.decode_u8(ofs + 5), buffer.decode_u8(ofs + 6), 255) | |
var bgC := Color8(buffer.decode_u8(ofs + 7), buffer.decode_u8(ofs + 8), buffer.decode_u8(ofs + 9), 255) | |
if bgC == TRANSPARENT_BG: # Undrawn cell. | |
bgC = Color.TRANSPARENT | |
image.append([glyph, fgC, bgC]) | |
ofs += 10 | |
var layerData := { "height": height, | |
"width": width, | |
"image": image} | |
layers.append(layerData) | |
var rexImage := { "versionInfo": versionInfo, | |
"layerCount": numberOfLayers, | |
"layers": layers} | |
return rexImage | |
## Output (part of) a line from a rexpaint layer to a RichTextLabel. | |
static func to_richtext_line(w: RichTextLabel, layer_data: Dictionary, x0: int, x1: int, y: int, mapping: Array, transparent_bg=null) -> void: | |
for x in range(x0, x1): | |
if x >= 0 and x < layer_data.width and y >= 0 and y < layer_data.height: | |
var idx: int = x * layer_data.height + y | |
var pixel: Array = layer_data.image[idx] | |
w.push_color(pixel[OFS_FG]) | |
if pixel[OFS_BG] != Color.TRANSPARENT and pixel[OFS_BG] != transparent_bg: | |
w.push_bgcolor(pixel[OFS_BG]) | |
w.add_text(mapping[pixel[OFS_GLYPH]]) | |
w.pop() | |
else: | |
w.add_text(mapping[pixel[OFS_GLYPH]]) | |
w.pop() | |
## Output (part of) a rexpaint grid to a RichTextLabel. | |
static func to_richtext(w: RichTextLabel, layer_data: Dictionary, area: Rect2i, mapping: Array, transparent_bg=null) -> void: | |
for y in range(area.position.y, area.end.y): | |
to_richtext_line(w, layer_data, area.position.x, area.end.x, y, mapping, transparent_bg) | |
w.push_color(Color.TRANSPARENT) | |
w.add_text("_\n") # Having a transparent character here works around RichTextLabel's aggressive space-trimming. | |
w.pop() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment