Last active
November 28, 2017 02:00
-
-
Save Talon1024/835b34585f4db200434c26da1c40820a to your computer and use it in GitHub Desktop.
Convert CEL files to PNG (Python version)
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
#!/usr/bin/env python3 | |
# -*- coding: utf8 -*- | |
import struct | |
from sys import argv | |
try: | |
import bpy | |
from bpy.types import Operator | |
from bpy_extras.io_utils import ImportHelper | |
blender = True | |
except ImportError: | |
blender = False | |
class CELImage: | |
def __init__(self, name, width, height, palette, data, dlen): | |
self.name = name | |
if width * height != dlen: | |
raise ValueError("Invalid CEL file (width * height != length!)") | |
self.width = width | |
self.height = height | |
self.palette = palette | |
self.data = data | |
@staticmethod | |
def read_cel(celf): | |
celname = getattr(celf, "name", "CEL") | |
celinfo = struct.unpack("<6HI", celf.read(16)) | |
# celinfo[0] - CEL magic number = 37145 | |
# celinfo[1] - width | |
# celinfo[2] - height | |
# celinfo[5] - bit depth? | |
# celinfo[6] - length = width * height | |
if (celinfo[0] != 37145): | |
raise TypeError("Wrong type of CEL file! Try using ffmpeg to " | |
"convert this CEL.") | |
celf.seek(32) | |
celpal = bytes(map(lambda x: x << 2, celf.read(256 * 3))) | |
celdata = celf.read(celinfo[6]) | |
return CELImage(celname, celinfo[1], celinfo[2], celpal, celdata, | |
celinfo[6]) | |
def write_png(self, pngf): | |
import zlib | |
def make_chunk(chtype, chdata=b""): | |
"Make a PNG chunk with the specified type and data" | |
length = struct.pack("!I", len(chdata)) | |
crc = struct.pack("!I", zlib.crc32(chtype + chdata) & 0xffffffff) | |
return length + chtype + chdata + crc | |
pngbuf = bytearray([137, 80, 78, 71, 13, 10, 26, 10]) | |
png_ihdr = struct.pack("!2I5B", self.width, self.height, | |
8, 3, 0, 0, 0) | |
pngbuf += make_chunk(b"IHDR", png_ihdr) | |
png_plte = self.palette | |
pngbuf += make_chunk(b"PLTE", png_plte) | |
# Filter type byte needs to go before each scanline (row) | |
scanlines = [] | |
for row in range(self.height): | |
colmin = row * self.width | |
colmax = (row + 1) * self.width | |
filtertype = 0 | |
scanline = bytearray([filtertype, *self.data[colmin:colmax]]) | |
scanlines.append(scanline) | |
png_idat = zlib.compress(b"".join(scanlines), 9) | |
pngbuf += make_chunk(b"IDAT", png_idat) | |
pngbuf += make_chunk(b"IEND") | |
with open(pngf, "wb") as pngfile: | |
pngfile.write(pngbuf) | |
def to_bl_img(self): | |
palcolours = {} | |
def get_colour(colidx): | |
if colidx in palcolours: return palcolours[colidx] | |
palidx = colidx * 3 # Palette is 768 bytes, not 256 | |
colour_rgba = (*self.palette[palidx:palidx + 3], 255) | |
palcolours[colidx] = tuple(map(lambda x: x / 255, colour_rgba)) | |
return palcolours[colidx] | |
blimg = bpy.data.images.new( | |
self.name, self.width, self.height, False, False) | |
for pixidx, pixel in enumerate(self.data): | |
blimg.pixels[pixidx * 4:pixidx * 4 + 4] = ( | |
palcolours.get(pixel, get_colour(pixel))) | |
del palcolours | |
# Invert vertically | |
def getrowminmax(rowidx): | |
rowmin = rowidx * 4 * self.width | |
rowmax = rowmin + self.width * 4 | |
return rowmin, rowmax | |
pixrows = [] | |
for pixrowidx in range(self.height): | |
pixrowmin, pixrowmax = getrowminmax(pixrowidx) | |
pixrows.append(blimg.pixels[pixrowmin:pixrowmax]) | |
for rowidx, pixrow in enumerate(reversed(pixrows)): | |
rowmin, rowmax = getrowminmax(rowidx) | |
blimg.pixels[rowmin:rowmax] = pixrow | |
del pixrows | |
blimg.pack(as_png=True) | |
return blimg | |
bl_info = { | |
"name": "WC2 CEL importer", | |
"category": "Import-Export" | |
} | |
if blender: | |
class BlenderWrapper(Operator, ImportHelper): | |
"""Import WC2 CEL files - Blender wrapper""" | |
bl_idname = "image.import_cel" | |
bl_label = "Import CEL" | |
filename_ext = "*.cel" | |
def execute(self, context): | |
with open(self.filepath, "rb") as celfile: | |
celimg = CELImage.read_cel(celfile) | |
celimg.to_bl_img() | |
return {'FINISHED'} | |
def menu_func_import_cel(self, context): | |
self.layout.operator(BlenderWrapper.bl_idname, text="WC2 CEL (.cel)") | |
def register(): | |
bpy.utils.register_class(BlenderWrapper) | |
bpy.types.INFO_MT_file_import.append(menu_func_import_cel) | |
def unregister(): | |
bpy.utils.unregister_class(BlenderWrapper) | |
bpy.types.INFO_MT_file_import.remove(menu_func_import_cel) | |
if __name__ == '__main__': | |
if blender: | |
register() | |
else: | |
for celf in argv[1:]: | |
pngf = celf[:celf.rfind(".")] + ".png" | |
with open(celf, "rb") as celfile: | |
celimg = CELImage.read_cel(celfile) | |
celimg.write_png(pngf) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment