def _galx_frames(self, info): frames = [] offsets = [] self.fp.seek(info["offset"]) for frame in info["frames"]: if info["bpp"] not in _GAL_MODE: raise GalImageError("Unsupported GAL pixel format") mode, rawmode = _GAL_MODE[frame["bpp"]] layermode = mode if len(frame["layers"]) > 1: print("Warning multi-layer GAL/X image") for layer in frame["layers"]: offsets.append(self.fp.tell()) layer_size = si32le(self.fp.read(4)) self.fp.seek(layer_size, 1) if layer["alpha_on"]: alpha_size = si32le(self.fp.read(4)) self.fp.seek(alpha_size, 1) if mode == "RGB": mode = "RGBA" elif mode == "P": mode = "PA" else: raise GalImageError("unsupported GAL alpha mode") break break frames.append((frame["name"], len(frame["layers"]), mode, layermode, rawmode, frame["box"], frame["palette"])) return frames, offsets
def _decode_alpha(self, frame, info): alpha_size = si32le(self.fd.read(4)) packed_data = zlib.decompress(self.fd.read(alpha_size)) unpacked = self._unpack_layer( BytesIO(packed_data), frame, info["block_width"], info["block_height"], info["randomized"], info["frames"], True, ) size = self.state.xsize, self.state.ysize mask = Image.frombytes("L", size, unpacked, "raw", "L", frame["alpha_stride"]) if Image.getmodebase(self.mode) == "RGB": band = 3 else: band = 1 self.im.putband(mask.im, band)
def decode(self, buffer): info, layermode, rawmode, frame_index = self.args compression = _GAL_COMPRESSION.get(info["compression"]) frame = info["frames"][frame_index] for layer in frame["layers"]: layer_size = si32le(self.fd.read(4)) if compression == "zip": packed_data = zlib.decompress(self.fd.read(layer_size)) layer["data"] = self._unpack_layer( BytesIO(packed_data), frame, info["block_width"], info["block_height"], info["randomized"], info["frames"], ) self._set_as_raw(layer["data"], layermode, rawmode, frame["stride"]) if layer["alpha_on"]: self._decode_alpha(frame, info) elif compression == "jpeg": jpeg_data = self.fd.read(layer_size) self._set_as_jpeg(jpeg_data, layermode) if layer["alpha_on"]: self._decode_alpha(frame, info) else: packed_data = self.fd.read(layer_size) layer["data"] = self._unpack_layer( BytesIO(packed_data), frame, info["block_width"], info["block_height"], info["randomized"], info["frames"], ) self._set_as_raw(layer["data"], layermode, rawmode, frame["stride"]) # TODO: handle multi-layer frames break return 0, 0
def _gal_info(self, header): """LiveMaker GAL image.""" read, seek = self.fp.read, self.fp.seek header += read(2) info = {} info["version"] = header[4:] try: version = int(info["version"]) except ValueError: raise GalImageError("Unsupported GAL version {}".format(header)) if version > 102: header_size = si32le(read(4)) header = read(header_size) info["width"] = i32le(header, 4) info["height"] = i32le(header, 8) info["bpp"] = si32le(header, 0xC) info["frame_count"] = si32le(header, 0x10) if info["frame_count"] > 1: print( "Warning: multi-frame GAL images are not fully supported") info["randomized"] = header[0x15] info["compression"] = header[0x16] info["bg_color"] = i32le(header, 0x18) info["block_width"] = si32le(header, 0x1C) info["block_height"] = si32le(header, 0x20) info["offset"] = header_size + 11 elif version >= 100: # fixed header size header = read(0x10) name_length = i32le(header) seek(name_length + 17, 1) info["width"] = i32le(header, 4) info["height"] = i32le(header, 8) info["bpp"] = si32le(header, 0xC) info["offset"] = name_length + 45 info["block_width"] = 0 info["block_height"] = 0 info["randomized"] = 0 info["compression"] = None info["frame_count"] = 1 else: raise GalImageError("Unsupported GAL version {}".format(header)) return info
def _unpack_layer(self, packed, frame_info, block_width, block_height, randomized, frames, is_alpha=False): # Based on GARbro ImageGAL.cs:ReadBlocks() implementation if block_width <= 0 or block_height <= 0: return packed.read() width = frame_info["width"] height = frame_info["height"] if is_alpha: bpp = 8 stride = frame_info["alpha_stride"] else: bpp = frame_info["bpp"] stride = frame_info["stride"] blocks_w = (width + block_width - 1) // block_width blocks_h = (height + block_height - 1) // block_height blocks_count = blocks_w * blocks_h block_refs = [] for i in range(blocks_count): frame_ref = si32le(packed.read(4)) layer_ref = si32le(packed.read(4)) block_refs.append((frame_ref, layer_ref)) if randomized: raise GalImageError( "LivemakerPro encrypted images are unsupported") i = 0 data = bytearray(stride * height) for y in range(0, height, block_height): # account for block size alignment padding run_height = min(block_height, height - y) for x in range(0, width, block_width): frame_ref, layer_ref = block_refs[i] run_width = min(block_width, width - x) dst = y * stride + (x * bpp + 7) // 8 chunk_size = (run_width * bpp + 7) // 8 if frame_ref == -1: # read block as raw data for j in range(run_height): chunk = packed.read(chunk_size) for k in range(chunk_size): data[dst + k] = chunk[k] dst += stride elif frame_ref == -2: # copy block from this layer src_x = block_width * (layer_ref % blocks_w) src_y = block_height * (layer_ref // blocks_w) src = src_y * stride + (src_x * bpp + 7) // 8 for j in range(run_height): for k in range(chunk_size): data[dst + k] = data[src + k] src += stride dst += stride else: # copy block from another frame/layer if frame_ref >= len(frames) or layer_ref >= len( frames[frame_ref]["layers"]): raise GalImageError("Invalid GaleFrame reference") if is_alpha: ref_data = frames[frame_ref]["layers"][layer_ref][ "alpha_data"] else: ref_data = frames[frame_ref]["layers"][layer_ref][ "data"] for j in range(run_height): for k in range(chunk_size): data[dst + k] = ref_data[dst + k] i += 1 return bytes(data)
def _gal_frames(self, info): read = self.fp.read seek = self.fp.seek frames = [] offsets = [] seek(info["offset"]) info["frames"] = [] for i in range(info["frame_count"]): frame_info = {} name_len = i32le(read(4)) frame_info["name"] = read(name_len).decode("cp932") mask = i32le(read(4)) seek(9, 1) layer_count = i32le(read(4)) if layer_count < 1: raise GalImageError("Invalid GAL frame") frame_info["width"] = si32le(read(4)) frame_info["height"] = si32le(read(4)) bpp = i32le(read(4)) if bpp not in _GAL_MODE or bpp > 32: print(layer_count) print(frame_info, mask) print(bpp) raise GalImageError("Unsupported GAL pixel format") frame_info["bpp"] = bpp if bpp <= 8: palette_size = 1 << bpp frame_info["palette"] = ImagePalette.raw( "BGRX", read(palette_size * 4)) else: frame_info["palette"] = None mode, rawmode = _GAL_MODE[bpp] layermode = mode stride = (frame_info["width"] * bpp + 7) // 8 if bpp >= 8: # align to 4 byte boundary stride = (stride + 3) & ~3 frame_info["stride"] = stride frame_info["alpha_stride"] = (frame_info["width"] + 3) & ~3 frame_info["layers"] = [] for j in range(layer_count): layer_info = {} left = si32le(read(4)) top = si32le(read(4)) layer_info["origin"] = (left, top) layer_info["visible"] = read(1)[0] layer_info["trans_color"] = si32le(read(4)) layer_info["alpha"] = si32le(read(4)) layer_info["alpha_on"] = read(1)[0] name_len = i32le(read(4)) seek(name_len, 1) if int(info["version"]) >= 107: layer_info["lock"] = read(1)[0] if j == 0: offsets.append(self.fp.tell()) else: print( "Warning: multilayer Gale images not fully supported") layer_size = si32le(read(4)) seek(layer_size, 1) alpha_size = si32le(read(4)) if layer_info["alpha_on"] and alpha_size > 0: if mode == "RGB": mode = "RGBA" elif mode == "P": mode = "PA" else: raise GalImageError("unsupported GAL alpha mode") seek(alpha_size, 1) frame_info["layers"].append(layer_info) info["frames"].append(frame_info) box = (0, 0, frame_info["width"], frame_info["height"]) frames.append((frame_info["name"], layer_count, mode, layermode, rawmode, box, frame_info["palette"])) # TODO: handle multi-frame images break return frames, offsets