commit
ece3b0db47
7 changed files with 515 additions and 0 deletions
@ -0,0 +1,5 @@ |
|||||||
|
*.c |
||||||
|
*.o |
||||||
|
*.so |
||||||
|
__pycache__ |
||||||
|
|
@ -0,0 +1,45 @@ |
|||||||
|
pyimglibs |
||||||
|
========= |
||||||
|
|
||||||
|
Bindings for image format libraries based on cffi. |
||||||
|
|
||||||
|
Package contents |
||||||
|
---------------- |
||||||
|
Module | Description |
||||||
|
------------------ | ----------------------------------- |
||||||
|
`imglibs._libflif` | Free Lossless Image Format bindings |
||||||
|
`imglibs._libwebp` | WebP bindings |
||||||
|
`imglibs.flif` | PILLOW format for flif |
||||||
|
`imglibs.webp` | (alternative) PILLOW format for webp |
||||||
|
`imglibs.pillows` | Meta-module for importing all PILLOW formats |
||||||
|
|
||||||
|
|
||||||
|
License |
||||||
|
======= |
||||||
|
You can use this code according to the MIT license, but beware that the |
||||||
|
wrapped image format libraries may impose additional restrictions in practice |
||||||
|
|
||||||
|
MIT License |
||||||
|
----------- |
||||||
|
``` |
||||||
|
Copyright (c) 2017 Taeyeon Mori |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all |
||||||
|
copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||||
|
SOFTWARE. |
||||||
|
``` |
||||||
|
|
@ -0,0 +1,47 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
# Helper for getting buffers from file objects |
||||||
|
|
||||||
|
from __future__ import unicode_literals, division |
||||||
|
|
||||||
|
import mmap |
||||||
|
|
||||||
|
|
||||||
|
class FileBuffer(object): |
||||||
|
def __init__(self, fileobj): |
||||||
|
self.file = fileobj |
||||||
|
|
||||||
|
try: |
||||||
|
self.fileno = self.file.fileno() |
||||||
|
except OSError: |
||||||
|
self.fileno = -1 |
||||||
|
|
||||||
|
if self.fileno != -1:# and self.fd.seekable(): # Python 2.x doesn't have seekable() |
||||||
|
# size |
||||||
|
self.file.seek(0, 2) |
||||||
|
self.size = self.file.tell() |
||||||
|
self.buffer = mmap.mmap(self.fileno, self.size, access=mmap.ACCESS_READ) |
||||||
|
self.type = "mmap" |
||||||
|
elif hasattr(self.file, "getbuffer"): # BytesIO |
||||||
|
self.buffer = self.file.getbuffer() |
||||||
|
self.size = len(self.buffer) |
||||||
|
self.type = "buffer" |
||||||
|
else: |
||||||
|
self.buffer = self.file.read() |
||||||
|
self.size = len(self.buffer) |
||||||
|
self.type = "bytes" |
||||||
|
|
||||||
|
def close(self): |
||||||
|
if self.type == "mmap": |
||||||
|
self.file.close() |
||||||
|
elif self.type == "bytes": |
||||||
|
del self.buffer |
||||||
|
elif self.type == "buffer": |
||||||
|
self.buffer = None |
||||||
|
else: |
||||||
|
raise RuntimeError("Unknown FileBuffer type %s" % self.type) |
||||||
|
|
||||||
|
def __enter__(self): |
||||||
|
return self |
||||||
|
|
||||||
|
def __exit__(self, t, e, tb): |
||||||
|
self.close() |
@ -0,0 +1,139 @@ |
|||||||
|
#!/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) |
@ -0,0 +1,119 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
# libflif cffi bindings |
||||||
|
# (c) 2017 Taeyeon Mori |
||||||
|
|
||||||
|
import os |
||||||
|
import re |
||||||
|
import cffi |
||||||
|
|
||||||
|
from simplecpp import simple_cpp |
||||||
|
|
||||||
|
flif_headers = "/usr/include/FLIF" |
||||||
|
|
||||||
|
# -------------------------------------------------------------- |
||||||
|
# helper source code |
||||||
|
source = """ |
||||||
|
|
||||||
|
#include <FLIF/flif.h> |
||||||
|
#include <Python.h> |
||||||
|
|
||||||
|
/* |
||||||
|
uint32_t flif_python_callback(uint32_t quality, int64_t bytes_read, uint8_t decode_over, void *user_data, void *context) |
||||||
|
{ |
||||||
|
PyObject *py_callback = user_data; |
||||||
|
|
||||||
|
if (!user_data) |
||||||
|
{ |
||||||
|
PyErr_SetString(PyExc_ValueError, "user_data cannot be NULL for python callbacks"); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
// TODO |
||||||
|
} */ |
||||||
|
|
||||||
|
void flif_image_read_into_GRAY8(FLIF_IMAGE *image, void *buffer, size_t buffer_size) |
||||||
|
{ |
||||||
|
uint32_t rows = flif_image_get_height(image); |
||||||
|
uint32_t columns = flif_image_get_width(image); |
||||||
|
|
||||||
|
for (uint32_t row = 0; row < rows && (row + 1) * columns <= buffer_size; ++row) |
||||||
|
{ |
||||||
|
flif_image_read_row_GRAY8(image, row, buffer + row * columns, columns); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void flif_image_read_into_RGBA8(FLIF_IMAGE *image, void *buffer, size_t buffer_size) |
||||||
|
{ |
||||||
|
uint32_t rows = flif_image_get_height(image); |
||||||
|
uint32_t columns = flif_image_get_width(image); |
||||||
|
|
||||||
|
for (uint32_t row = 0; row < rows && (row + 1) * columns * 4 <= buffer_size; ++row) |
||||||
|
{ |
||||||
|
flif_image_read_row_RGBA8(image, row, buffer + row * columns * 4, columns * 4); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void flif_image_read_into_RGB8(FLIF_IMAGE *image, void *buffer, size_t buffer_size) |
||||||
|
{ |
||||||
|
uint32_t rows = flif_image_get_height(image); |
||||||
|
uint32_t columns = flif_image_get_width(image); |
||||||
|
uint32_t row; |
||||||
|
|
||||||
|
for (row = 0; row < rows && row * columns * 3 + columns * 4 <= buffer_size; ++row) |
||||||
|
{ |
||||||
|
// Read RGBA8 |
||||||
|
uint8_t *row_buf = (uint8_t *)buffer + row * columns * 3; |
||||||
|
flif_image_read_row_RGBA8(image, row, row_buf, columns * 4); |
||||||
|
// Remove Alpha channel |
||||||
|
for (uint32_t col = 1; col < columns; ++col) |
||||||
|
{ |
||||||
|
row_buf[col * 3] = row_buf[col * 4]; |
||||||
|
row_buf[col * 3 + 1] = row_buf[col * 4 + 1]; |
||||||
|
row_buf[col * 3 + 2] = row_buf[col * 4 + 2]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Last row |
||||||
|
if (row < rows && buffer_size >= (row + 1) * columns * 3) |
||||||
|
{ |
||||||
|
uint8_t lastrow_buf[columns * 4]; |
||||||
|
uint8_t *row_buf = (uint8_t *)buffer + row * columns * 3; |
||||||
|
flif_image_read_row_RGBA8(image, row, lastrow_buf, columns * 4); |
||||||
|
|
||||||
|
for (uint32_t col = 0; col < columns; ++col) |
||||||
|
{ |
||||||
|
row_buf[col * 3] = lastrow_buf[col * 4]; |
||||||
|
row_buf[col * 3 + 1] = lastrow_buf[col * 4 + 1]; |
||||||
|
row_buf[col * 3 + 2] = lastrow_buf[col * 4 + 2]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
""" |
||||||
|
|
||||||
|
# -------------------------------------------------------------- |
||||||
|
# Initialize CFFI |
||||||
|
libflif = cffi.FFI() |
||||||
|
libflif.set_source("imglibs._libflif", source, libraries=["flif"]) |
||||||
|
|
||||||
|
|
||||||
|
# -------------------------------------------------------------- |
||||||
|
# Import libflif symbols |
||||||
|
defs = {} |
||||||
|
|
||||||
|
for header in ("flif_common.h", "flif_dec.h", "flif_enc.h"): |
||||||
|
with open(os.path.join(flif_headers, header), "r") as f: |
||||||
|
libflif.cdef("".join(simple_cpp(f, defs))) |
||||||
|
|
||||||
|
|
||||||
|
# -------------------------------------------------------------- |
||||||
|
# Define above helper funcs |
||||||
|
libflif.cdef(""" |
||||||
|
void flif_image_read_into_GRAY8(FLIF_IMAGE *image, void *buffer, size_t buffer_size); |
||||||
|
void flif_image_read_into_RGBA8(FLIF_IMAGE *image, void *buffer, size_t buffer_size); |
||||||
|
void flif_image_read_into_RGB8(FLIF_IMAGE *image, void *buffer, size_t buffer_size); |
||||||
|
""") |
||||||
|
|
||||||
|
# -------------------------------------------------------------- |
||||||
|
# Build |
||||||
|
if __name__ == "__main__": |
||||||
|
libflif.compile(verbose=True) |
@ -0,0 +1,160 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
# Simple C PreProcessor for cffi |
||||||
|
# (c) 2017 Taeyeon Mori |
||||||
|
|
||||||
|
import re |
||||||
|
import functools |
||||||
|
|
||||||
|
def parse_macro(mac): |
||||||
|
name = [] |
||||||
|
arglist = None |
||||||
|
body = [] |
||||||
|
|
||||||
|
it = iter(mac) |
||||||
|
|
||||||
|
for c in it: |
||||||
|
if c == "(": |
||||||
|
arglist = [] |
||||||
|
break |
||||||
|
elif c.isspace(): |
||||||
|
break |
||||||
|
else: |
||||||
|
name.append(c) |
||||||
|
|
||||||
|
name = "".join(name).strip() |
||||||
|
|
||||||
|
if arglist is not None: |
||||||
|
thisarg = [] |
||||||
|
for c in it: |
||||||
|
if c == ")": |
||||||
|
if thisarg: |
||||||
|
arglist.append("".join(thisarg).strip()) |
||||||
|
break |
||||||
|
elif c == ",": |
||||||
|
if thisarg: |
||||||
|
arglist.append("".join(thisarg).strip()) |
||||||
|
thisarg.clear() |
||||||
|
else: |
||||||
|
thisarg.append(c) |
||||||
|
|
||||||
|
body = "".join(it) |
||||||
|
|
||||||
|
if arglist: |
||||||
|
argrep = re.compile(r"\b(%s)\b" % "|".join(arglist)) |
||||||
|
fn = lambda args: argrep.sub(lambda m: args[arglist.index(m.group(1))], body) |
||||||
|
|
||||||
|
return name + "()", fn |
||||||
|
|
||||||
|
return name, body.strip() |
||||||
|
|
||||||
|
|
||||||
|
def_re = re.compile(r"\b\w+\b") |
||||||
|
|
||||||
|
|
||||||
|
def def_sub(defs, m): |
||||||
|
token = m.group(0) |
||||||
|
if token in defs: |
||||||
|
return " " + defs[token] + " " |
||||||
|
else: |
||||||
|
return token |
||||||
|
|
||||||
|
def make_fnmacrocall_pattern(definitions): |
||||||
|
fns = [x[:-2] for x in definitions.keys() if x[-2:] == "()"] |
||||||
|
if fns: |
||||||
|
return re.compile(r"\b(%s)\(" % "|".join(fns)) |
||||||
|
return None |
||||||
|
|
||||||
|
|
||||||
|
def sub_macros(definitions, fnpattern, line): |
||||||
|
if fnpattern: |
||||||
|
m = fnpattern.search(line) |
||||||
|
while m: |
||||||
|
args = [] |
||||||
|
bracket_level = 0 |
||||||
|
current = [] |
||||||
|
argslen = 1 |
||||||
|
for c in line[m.end(1)+1:]: |
||||||
|
argslen += 1 |
||||||
|
if c == "," and bracket_level == 0: |
||||||
|
args.append("".join(current)) |
||||||
|
current.clear() |
||||||
|
else: |
||||||
|
if c in "([{": |
||||||
|
bracket_level += 1 |
||||||
|
elif c in ")]}": |
||||||
|
bracket_level -= 1 |
||||||
|
if bracket_level < 0: |
||||||
|
if current: |
||||||
|
args.append("".join(current)) |
||||||
|
break |
||||||
|
current.append(c) |
||||||
|
|
||||||
|
line = line[:m.start(1)] + definitions[m.group(1) + "()"](args) + line[m.end(1) + argslen:] |
||||||
|
|
||||||
|
m = fnpattern.search(line) |
||||||
|
|
||||||
|
return def_re.sub(functools.partial(def_sub, definitions), line) |
||||||
|
|
||||||
|
|
||||||
|
def simple_cpp(lines, definitions=None): |
||||||
|
""" Very simple C preprocessor """ |
||||||
|
ifs = [] |
||||||
|
definitions = definitions if definitions is not None else {} |
||||||
|
fnpattern = make_fnmacrocall_pattern(definitions) |
||||||
|
|
||||||
|
prevline = None |
||||||
|
|
||||||
|
for line in lines: |
||||||
|
# Continuation |
||||||
|
if prevline: |
||||||
|
line = prevline + line.strip() |
||||||
|
prevline = None |
||||||
|
if line.lstrip("\r\n").endswith("\\"): |
||||||
|
prevline = line.lstrip("\r\n")[:-1] |
||||||
|
continue |
||||||
|
# comments |
||||||
|
while "/*" in line and "*/" in line: |
||||||
|
line = line[:line.index("/*")] + line[line.index("*/") + 2:] |
||||||
|
if "//" in line: |
||||||
|
line = line[:line.index("//")] |
||||||
|
if line.strip(): |
||||||
|
line += "\n" |
||||||
|
if "/*" in line: |
||||||
|
prevline = line.lstrip("\r\n") |
||||||
|
continue |
||||||
|
# Preprocessor directives |
||||||
|
if line.rstrip().startswith("#"): |
||||||
|
# Process |
||||||
|
cpp_line = line.rstrip()[1:].strip().split(maxsplit=1) |
||||||
|
directive = cpp_line[0] |
||||||
|
args = cpp_line[1] if len(cpp_line) > 1 else None |
||||||
|
|
||||||
|
if directive == "ifdef": |
||||||
|
ifs.append(args in definitions or args + "()" in definitions) |
||||||
|
elif directive == "ifndef": |
||||||
|
ifs.append(args not in definitions and args + "()" not in definitions) |
||||||
|
elif directive == "if": |
||||||
|
print("Simple CPP Warning: #if is unsupported: %s" % args) |
||||||
|
ifs.append(False) |
||||||
|
elif directive == "elif": |
||||||
|
print("Simple CPP Warning: #elif is unsupported: %s" % args) |
||||||
|
ifs[-1] = False |
||||||
|
elif directive == "else": |
||||||
|
ifs[-1] = not ifs[-1] |
||||||
|
elif directive == "endif": |
||||||
|
ifs.pop() |
||||||
|
elif all(ifs): |
||||||
|
if directive == "define": |
||||||
|
name, value = parse_macro(args) |
||||||
|
definitions[name] = value |
||||||
|
if name[-2:] == "()": |
||||||
|
fnpattern = make_fnmacrocall_pattern(definitions) |
||||||
|
elif value: |
||||||
|
yield "#define %s ...\n" % name |
||||||
|
else: |
||||||
|
print("Simple CPP Warning: Ignoring unknown directive %s" % ((directive, args),)) |
||||||
|
elif all(ifs): |
||||||
|
yield sub_macros(definitions, fnpattern, line) |
||||||
|
|
||||||
|
if prevline: |
||||||
|
raise ValueError("Line continuation on last line!") |
Loading…
Reference in new issue