Python CFFI bindings for image format libraries
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

139 lines
4.3 KiB

#!/usr/bin/env python3
import mmap
from PIL import Image, ImageFile
from .filebuf import FileBuffer
from ._libflif import lib as libflif, ffi
# ---------------------------------------------------------
# Image file
def _accept(prefix):
return prefix[:4] == b"FLIF"
class FLImageFile(ImageFile.ImageFile):
format = "FLIF"
format_description = "Free Lossless Image Format"
def _open(self):
header = self.fp.read(30)
info = libflif.flif_read_info_from_memory(header, 30)
try:
# Mode/Channels
channels = libflif.flif_info_get_nb_channels(info)
if channels == 1:
if libflif.flif_info_get_depth(info) == 1:
self.mode = "1"
else:
self.mode = "L"
elif channels == 3:
self.mode = "RGB"
elif channels == 4:
self.mode = "RGBA"
else:
raise ValueError("Cannot open FLIF image with %i channels" % channels)
# Size
self.size = (
libflif.flif_info_get_width(info),
libflif.flif_info_get_height(info)
)
finally:
libflif.flif_destroy_info(info)
self.tile = [("libflif", (0, 0) + self.size, 0, ())]
Image.register_open(FLImageFile.format, FLImageFile, _accept)
Image.register_extension(FLImageFile.format, ".flif")
Image.register_mime(FLImageFile.format, "image/flif")
Image.register_mime(FLImageFile.format, "application/x-flif")
# ---------------------------------------------------------
# Decoder
class LibFLIFDecoder(ImageFile.PyDecoder):
_pulls_fd = True
def init(self, args):
self.context = libflif.flif_create_decoder()
def _import_image(self, image, bufsize, mode, readinto_fn):
# FIXME: Don't copy shit around TWICE; Probably want to do it in C
buf = bytearray(bufsize)
readinto_fn(image, ffi.from_buffer(buf), bufsize)
self.set_as_raw(bytes(buf), mode)
def decode(self, _):
# Figure out best way to get pointer to data in memory
with FileBuffer(self.fd) as fb:
# Decode
libflif.flif_decoder_decode_memory(self.context, ffi.from_buffer(fb.buffer), fb.size)
# Get image
image = libflif.flif_decoder_get_image(self.context, 0)
# Convert to PIL representation
# flif_image_read_into_* defined in libflif_build.py
if self.mode in ("L", "1"):
self._import_image(image, self.state.xsize * self.state.ysize, "L", libflif.flif_image_read_into_GRAY8)
elif self.mode in ("RGB",):
self._import_image(image, self.state.xsize * self.state.ysize * 3 + self.state.xsize, "RGB", libflif.flif_image_read_into_RGB8)
elif self.mode in ("RGBA",):
self._import_image(image, self.state.xsize + self.state.ysize * 4, "RGBA", libflif.flif_image_read_into_RGBA8)
return 0, 0
def cleanup(self):
libflif.flif_destroy_decoder(self.context)
Image.register_decoder("libflif", LibFLIFDecoder)
# ---------------------------------------------------------
# Writing
def _save(image, fp, filename):
lossy = image.encoderinfo.get("lossy", 0)
# Create FLIF_IMAGE
# FIXME: Probably should do it in C
mode = image.mode
if mode == "L":
fimage = libflif.flif_import_image_GRAY(image.size[0], image.size[1], image.tobytes(), image.size[0])
elif mode == "RGB":
fimage = libflif.flif_import_image_RGB(image.size[0], image.size[1], image.tobytes(), image.size[0] * 3)
elif mode == "RGBA":
fimage = libflif.flif_import_image_RGBA(image.size[0], image.size[1], image.tobytes(), image.size[0] * 4)
else:
raise IOError("cannot write mode %s as FLIF (yet)" % mode)
if fimage == ffi.NULL:
raise RuntimeError("Could not create FLIF_IMAGE")
# Set up encoder
context = libflif.flif_create_encoder()
libflif.flif_encoder_set_lossy(context, lossy)
libflif.flif_encoder_add_image_move(context, fimage)
# Encode
out_buf = ffi.new("void **")
out_size = ffi.new("size_t *")
res = libflif.flif_encoder_encode_memory(context, out_buf, out_size)
fp.write(ffi.buffer(out_buf[0], out_size[0]))
libflif.flif_destroy_encoder(context)
libflif.flif_free_memory(out_buf[0])
Image.register_save(FLImageFile.format, _save)