Skip to content

Instantly share code, notes, and snippets.

@vmedea
Last active July 8, 2024 16:24
Show Gist options
  • Save vmedea/fce1d6d7fb6a0a4d9f1f89c188cb6197 to your computer and use it in GitHub Desktop.
Save vmedea/fce1d6d7fb6a0a4d9f1f89c188cb6197 to your computer and use it in GitHub Desktop.
Rexpaint .xp loader, updated for Godot 4
# 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