图像被打包,数据在重要的地方是小端的。
一般格式:
struct CGFHeader {
uint32_t magic;
uint32_t flags;
uint32_t frame_count;
uint32_t frame_metadata_size;
uint32_t frame_payload_size;
uint32_t unk1;
uint32_t unk2;
};
然后重复frame_count多次,从+0x1c:
struct FrameMeta {
uint32_t unk1;
uint32_t unk2;
uint32_t width;
uint32_t height;
uint32_t unk3;
uint32_t payload_offset;
};
对于帧 N,有效载荷数据开始于sizeof(CGFHeader) + cgf_header.frame_count*sizeof(FrameMeta) + frame_meta[N].payload_offset(即在相应的 处payload_offset,紧接在元数据结构之后)。
打包图像数据的每一行/行都是独立打包的。每行都以uint32_t该行长度的 a 为前缀(包括长度字段)。对打包后的数据进行如下处理:
读取一个方法uint8_t和一个长度uint8_t(见n下文),按照下表进行处理。
| 方法 |
如何处理 |
0x00 |
附加n透明像素。如果n是 0,用透明像素填充线条到预期宽度。 |
0x01 |
n从打包数据中读取(palette_index, alpha) 值对,附加到行。 |
0x02 |
从打包数据中读取单个 (palette_index, alpha) 对。将其附加到行n时间。 |
0x03 |
n从打包数据中读取palette_index 值,附加到该行。 |
0x04 |
从打包数据中读取单个 Palette_index。将其附加到行n时间。 |
从打包数据中保留处理方法uint8_t和长度uint8_t字节,直到您拥有完整的行宽。
为了解释实际的颜色值,您需要相应的调色板。您提到的两种格式:
CPAL254X3STD 只是(在这种情况下)在该标题值之后附加了 254 个 RGB 三元组。
CPAL254X3ALPHA是相同的,但附加了一个 0x100000 字节的结构,它被称为“alpha 映射”。我根本没有费心去看它。
粗略的python3处理示例,将帧转储到子目录中的0.png、1.png等:
import errno
import os
import struct
import sys
from PIL import Image
class HugoPalette(object):
def __init__(self, rawdat):
assert(len(rawdat) >= 12)
assert(rawdat[0:4] == b'CPAL') # palette
self.alpha_map = None
self.entries = None
num_entries = int(rawdat[4:7])
assert(rawdat[7:9] == b'X3') # rgb triples
if rawdat[9:12] == b'STD': # palette
assert(len(rawdat) >= 12 + num_entries*3)
self.entries = []
offset = 12
for n in range(num_entries):
self.entries.append(struct.unpack_from("BBB" ,rawdat, offset))
offset = offset + 3
elif rawdat[9:14] == b'ALPHA': # palette and alphamap
assert(len(rawdat) >= 14 + num_entries*3 + 0x100000)
self.entries = []
offset = 14
for n in range(num_entries):
self.entries.append(struct.unpack_from("BBB" ,rawdat, offset))
offset = offset + 3
self.alphamap = rawdat[14 + num_entries*3:14 + num_entries*3 + 0x100000] # not sure how to interpret
else:
raise NotImplementedError("unknown palette type")
class HugoImage(object):
def __init__(self, width, height, rawdat, offset):
self.width = width
self.height = height
rows = []
for n in range(height):
(packed_line_length,) = struct.unpack_from("<L", rawdat, offset)
assert(len(rawdat) >= offset + packed_line_length)
packed_line = rawdat[offset+4:offset+packed_line_length]
line = []
index = 0
# unpacking:
# 00 nn = skip nn pixels [nn=00: skip to end of line]
# 01 nn pp aa [pp aa ...] = insert nn entries from trailing pp, replacing alpha with aa
# 02 nn pp aa = repeat pp for nn pixels, replacing alpha with aa
# 03 nn pp [pp ...] = insert nn entries from trailing pp
# 04 nn pp = repeat pp for nn pixels
while True:
method = packed_line[index]
pixel_count = packed_line[index+1]
index = index + 2
if method == 0:
if pixel_count == 0:
while(len(line) < self.width):
line.append((0, 0))
break
line.extend([(0,0)]*pixel_count)
elif method == 1:
for p in range(pixel_count):
line.append((packed_line[index], packed_line[index+1]))
index = index + 2
elif method == 2:
line.extend([(packed_line[index], packed_line[index+1])]*pixel_count)
index = index + 2
elif method == 3:
for p in range(pixel_count):
line.append((packed_line[index],0xff))
index = index + 1
elif packed_line[index] == 4:
line.extend([(packed_line[index], 0xff)]*pixel_count)
index = index + 1
assert(len(line) == self.width)
rows.append(line)
offset = offset + 4 + index
self.rows = rows
def load_images(rawdat):
HEADER_STRUCT_SIZE=0x1c
METADATA_STRUCT_SIZE=0x18
offset = 0
assert(len(rawdat) >= offset + HEADER_STRUCT_SIZE)
(magic, _, count, metadata_size, payload_size, _, _) = struct.unpack_from("<LLLLLLL", rawdat, offset)
offset = offset + HEADER_STRUCT_SIZE
assert(magic == 0x46464743)
assert(metadata_size == count * METADATA_STRUCT_SIZE)
metadata = []
for n in range(count):
assert(len(rawdat) >= offset + METADATA_STRUCT_SIZE)
metadata.append(struct.unpack_from("<LLLLLL", rawdat, offset))
offset = offset + METADATA_STRUCT_SIZE
images = []
for im in metadata:
images.append(HugoImage(im[2], im[3], rawdat, offset + im[5]))
return images
def main(args):
if len(args) != 3:
print(f"usage: python3 {args[0]} palette.pal image.cgf")
sys.exit(1)
with open(args[1], "rb") as infile:
dat = infile.read()
pal = HugoPalette(dat)
with open(args[2], "rb") as infile:
dat = infile.read()
images = load_images(dat)
output_dir = args[2] + ".extracted"
output_index = 0
try:
os.makedirs(output_dir)
except OSError as e:
if e.errno != errno.EEXIST:
raise
for i in images:
img = Image.new('RGBA', (i.width, i.height))
for y in range(i.height):
for x in range(i.width):
(index, alpha) = i.rows[y][x]
pal_entry = pal.entries[index]
col = (pal_entry[0], pal_entry[1],pal_entry[2], alpha)
img.putpixel((x,y), col)
img.save(os.path.join(output_dir, str(output_index) + ".png"), "PNG")
output_index = output_index + 1
if __name__ == '__main__':
main(sys.argv)