Skip to content

Instantly share code, notes, and snippets.

@Talon1024
Last active November 28, 2017 02:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Talon1024/835b34585f4db200434c26da1c40820a to your computer and use it in GitHub Desktop.
Save Talon1024/835b34585f4db200434c26da1c40820a to your computer and use it in GitHub Desktop.
Convert CEL files to PNG (Python version)
#!/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