Created
May 30, 2024 20:44
-
-
Save akien-mga/0b2832c553e63fded43fade442656650 to your computer and use it in GitHub Desktop.
PCX Loader class in GDScript
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
@tool | |
class_name ImageLoaderPCX | |
extends ImageFormatLoaderExtension | |
## Implementation of an ImageFormatLoader for PCX files with the | |
## [code].pcx[/code] extension. | |
## | |
## PCX files are an obsolete image format used in the DOS era, developed in 1985 | |
## by ZSoft Corporation. | |
## | |
## @tutorial(ZSoft PCX file format technical reference manual): https://bespin.org/~qz/pc-gpe/pcx.txt | |
## @tutorial(PCX graphics files explained): http://www.fysnet.net/pcxfile.htm | |
## @experimental: Only supports a few versions/palette types for now. | |
const PCX_HEADER_SIZE: int = 128 | |
const PCX_PALETTE256_SIZE: int = 768 | |
class PCXHeader: | |
var manufacturer: int # u8 | |
var version: int # u8 | |
var encoding: int # u8 | |
var bits_per_pixel: int # u8 | |
var xmin: int # u16 | |
var ymin: int # u16 | |
var xmax: int # u16 | |
var ymax: int # u16 | |
var hdpi: int # u16 | |
var vdpi: int # u16 | |
var palette16: PackedByteArray # 48 bytes | |
var reserved: int # u8 | |
var num_planes: int # u8 | |
var bytes_per_line: int # u16, for each plane | |
var palette_type: int # u16 | |
var h_screen_size: int # u16 | |
var v_screen_size: int # u16 | |
var filler: PackedByteArray # 54 bytes | |
func _get_recognized_extensions() -> PackedStringArray: | |
return ["pcx"] | |
func _load_image(image: Image, file: FileAccess, flags: int, scale: float) -> Error: | |
var len := file.get_length() | |
var header := PCXHeader.new() | |
if len < PCX_HEADER_SIZE: | |
printerr("Invalid PCX file size %d, must be at least %d bytes." \ | |
% [file.get_length(), PCX_HEADER_SIZE]) | |
return ERR_FILE_CORRUPT | |
header.manufacturer = file.get_8() | |
header.version = file.get_8() | |
header.encoding = file.get_8() | |
header.bits_per_pixel = file.get_8() | |
header.xmin = file.get_16() | |
header.ymin = file.get_16() | |
header.xmax = file.get_16() | |
header.ymax = file.get_16() | |
header.hdpi = file.get_16() | |
header.vdpi = file.get_16() | |
header.palette16 = file.get_buffer(48) | |
header.reserved = file.get_8() | |
header.num_planes = file.get_8() | |
header.bytes_per_line = file.get_16() | |
header.palette_type = file.get_16() | |
header.h_screen_size = file.get_16() | |
header.v_screen_size = file.get_16() | |
header.filler = file.get_buffer(54) | |
if header.version != 5: | |
printerr("Only PCX files in version 5 with 256 color palette are supported for now.") | |
return ERR_UNAVAILABLE | |
# Read and decompress image data. | |
if header.encoding != 1: | |
printerr("Unencoded PCX image data isn't supported yet.") | |
return ERR_UNAVAILABLE | |
# TODO: See if this can be optimized, takes 19 ms on my laptop on a 400x256 image. | |
var pre_time := Time.get_ticks_msec() | |
var size_px := Vector2i(header.xmax - header.xmin + 1, header.ymax - header.ymin + 1) | |
var pcx_data_len := header.num_planes * header.bytes_per_line * size_px.y | |
var pcx_data: PackedByteArray | |
var l := 0 | |
while l < pcx_data_len: | |
var byte := file.get_8() | |
if (byte & 0b1100_0000) == 0b1100_0000: # RLE pair. | |
var run_len := (byte & 0b0011_1111) | |
var arr: PackedByteArray | |
arr.resize(run_len) | |
arr.fill(file.get_8()) | |
pcx_data.append_array(arr) | |
l += run_len | |
else: # Single byte. | |
pcx_data.append(byte) | |
l += 1 | |
print('Ticks elapsed decoding RLE: %d ms' % (Time.get_ticks_msec() - pre_time)) | |
# Decode and apply color palette. | |
if header.version == 5 and len <= PCX_HEADER_SIZE + PCX_PALETTE256_SIZE + 1: | |
printerr("Missing 256 color palette in PCX file, other types not supported.") | |
return ERR_UNAVAILABLE | |
pre_time = Time.get_ticks_msec() | |
var palette256: PackedByteArray | |
file.seek_end(-PCX_PALETTE256_SIZE - 1) | |
if file.get_8() == 12: | |
palette256 = file.get_buffer(PCX_PALETTE256_SIZE) | |
var pal_file := FileAccess.open(file.get_path().get_basename() + ".palette", FileAccess.WRITE) | |
pal_file.store_buffer(palette256) | |
print('Ticks elapsed decoding palette: %d ms' % (Time.get_ticks_msec() - pre_time)) | |
pre_time = Time.get_ticks_msec() | |
var img_data: PackedByteArray | |
img_data.resize(pcx_data_len * 3) | |
for i: int in pcx_data_len: | |
var j := i * 3 | |
var idx := pcx_data[i] * 3 | |
img_data[j] = palette256[idx] | |
img_data[j + 1] = palette256[idx + 1] | |
img_data[j + 2] = palette256[idx + 2] | |
print('Ticks elapsed applying palette: %d ms' % (Time.get_ticks_msec() - pre_time)) | |
image.set_data(size_px.x, size_px.y, false, Image.FORMAT_RGB8, img_data) | |
return OK |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment