merge to master

This commit is contained in:
wiredfool 2014-03-26 18:46:35 -07:00
commit 2b81156619
20 changed files with 2828 additions and 4 deletions

View File

@ -4,6 +4,9 @@ Changelog (Pillow)
2.4.0 (unreleased)
------------------
- Added support for JPEG 2000
[al45tair]
- Add more detailed error messages to Image.py
[larsmans]

View File

@ -205,7 +205,7 @@ class ImageFile(Image.Image):
else:
raise IndexError(ie)
if not s: # truncated jpeg
if not s and not d.handles_eof: # truncated jpeg
self.tile = []
# JpegDecode needs to clean things up here either way

249
PIL/Jpeg2KImagePlugin.py Normal file
View File

@ -0,0 +1,249 @@
#
# The Python Imaging Library
# $Id$
#
# JPEG2000 file handling
#
# History:
# 2014-03-12 ajh Created
#
# Copyright (c) 2014 Coriolis Systems Limited
# Copyright (c) 2014 Alastair Houghton
#
# See the README file for information on usage and redistribution.
#
__version__ = "0.1"
from PIL import Image, ImageFile, _binary
import struct
import os
import io
def _parse_codestream(fp):
"""Parse the JPEG 2000 codestream to extract the size and component
count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
hdr = fp.read(2)
lsiz = struct.unpack('>H', hdr)[0]
siz = hdr + fp.read(lsiz - 2)
lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, \
xtosiz, ytosiz, csiz \
= struct.unpack('>HHIIIIIIIIH', siz[:38])
ssiz = [None]*csiz
xrsiz = [None]*csiz
yrsiz = [None]*csiz
for i in range(csiz):
ssiz[i], xrsiz[i], yrsiz[i] \
= struct.unpack('>BBB', siz[36 + 3 * i:39 + 3 * i])
size = (xsiz - xosiz, ysiz - yosiz)
if csiz == 1:
mode = 'L'
elif csiz == 2:
mode = 'LA'
elif csiz == 3:
mode = 'RGB'
elif csiz == 4:
mode == 'RGBA'
else:
mode = None
return (size, mode)
def _parse_jp2_header(fp):
"""Parse the JP2 header box to extract size, component count and
color space information, returning a PIL (size, mode) tuple."""
# Find the JP2 header box
header = None
while True:
lbox, tbox = struct.unpack('>I4s', fp.read(8))
if lbox == 1:
lbox = struct.unpack('>Q', fp.read(8))[0]
hlen = 16
else:
hlen = 8
if tbox == b'jp2h':
header = fp.read(lbox - hlen)
break
else:
fp.seek(lbox - hlen, os.SEEK_CUR)
if header is None:
raise SyntaxError('could not find JP2 header')
size = None
mode = None
hio = io.BytesIO(header)
while True:
lbox, tbox = struct.unpack('>I4s', hio.read(8))
if lbox == 1:
lbox = struct.unpack('>Q', hio.read(8))[0]
hlen = 16
else:
hlen = 8
content = hio.read(lbox - hlen)
if tbox == b'ihdr':
height, width, nc, bpc, c, unkc, ipr \
= struct.unpack('>IIHBBBB', content)
size = (width, height)
if unkc:
if nc == 1:
mode = 'L'
elif nc == 2:
mode = 'LA'
elif nc == 3:
mode = 'RGB'
elif nc == 4:
mode = 'RGBA'
break
elif tbox == b'colr':
meth, prec, approx = struct.unpack('>BBB', content[:3])
if meth == 1:
cs = struct.unpack('>I', content[3:7])[0]
if cs == 16: # sRGB
if nc == 3:
mode = 'RGB'
elif nc == 4:
mode = 'RGBA'
break
elif cs == 17: # grayscale
if nc == 1:
mode = 'L'
elif nc == 2:
mode = 'LA'
break
elif cs == 18: # sYCC
if nc == 3:
mode = 'RGB'
elif nc == 4:
mode == 'RGBA'
break
return (size, mode)
##
# Image plugin for JPEG2000 images.
class Jpeg2KImageFile(ImageFile.ImageFile):
format = "JPEG2000"
format_description = "JPEG 2000 (ISO 15444)"
def _open(self):
sig = self.fp.read(4)
if sig == b'\xff\x4f\xff\x51':
self.codec = "j2k"
self.size, self.mode = _parse_codestream(self.fp)
else:
sig = sig + self.fp.read(8)
if sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a':
self.codec = "jp2"
self.size, self.mode = _parse_jp2_header(self.fp)
else:
raise SyntaxError('not a JPEG 2000 file')
if self.size is None or self.mode is None:
raise SyntaxError('unable to determine size/mode')
self.reduce = 0
self.layers = 0
fd = -1
if hasattr(self.fp, "fileno"):
try:
fd = self.fp.fileno()
except:
fd = -1
self.tile = [('jpeg2k', (0, 0) + self.size, 0,
(self.codec, self.reduce, self.layers, fd))]
def load(self):
if self.reduce:
power = 1 << self.reduce
adjust = power >> 1
self.size = ((self.size[0] + adjust) / power,
(self.size[1] + adjust) / power)
if self.tile:
# Update the reduce and layers settings
t = self.tile[0]
t3 = (t[3][0], self.reduce, self.layers, t[3][3])
self.tile = [(t[0], t[1], t[2], t3)]
ImageFile.ImageFile.load(self)
def _accept(prefix):
return (prefix[:4] == b'\xff\x4f\xff\x51'
or prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
# ------------------------------------------------------------
# Save support
def _save(im, fp, filename):
if filename.endswith('.j2k'):
kind = 'j2k'
else:
kind = 'jp2'
# Get the keyword arguments
info = im.encoderinfo
offset = info.get('offset', None)
tile_offset = info.get('tile_offset', None)
tile_size = info.get('tile_size', None)
quality_mode = info.get('quality_mode', 'rates')
quality_layers = info.get('quality_layers', None)
num_resolutions = info.get('num_resolutions', 0)
cblk_size = info.get('codeblock_size', None)
precinct_size = info.get('precinct_size', None)
irreversible = info.get('irreversible', False)
progression = info.get('progression', 'LRCP')
cinema_mode = info.get('cinema_mode', 'no')
fd = -1
if hasattr(fp, "fileno"):
try:
fd = fp.fileno()
except:
fd = -1
im.encoderconfig = (
offset,
tile_offset,
tile_size,
quality_mode,
quality_layers,
num_resolutions,
cblk_size,
precinct_size,
irreversible,
progression,
cinema_mode,
fd
)
ImageFile._save(im, fp, [('jpeg2k', (0, 0)+im.size, 0, kind)])
# ------------------------------------------------------------
# Registry stuff
Image.register_open('JPEG2000', Jpeg2KImageFile, _accept)
Image.register_save('JPEG2000', _save)
Image.register_extension('JPEG2000', '.jp2')
Image.register_extension('JPEG2000', '.j2k')
Image.register_extension('JPEG2000', '.jpc')
Image.register_extension('JPEG2000', '.jpf')
Image.register_extension('JPEG2000', '.jpx')
Image.register_extension('JPEG2000', '.j2c')
Image.register_mime('JPEG2000', 'image/jp2')
Image.register_mime('JPEG2000', 'image/jpx')

View File

@ -33,6 +33,7 @@ _plugins = ['ArgImagePlugin',
'ImtImagePlugin',
'IptcImagePlugin',
'JpegImagePlugin',
'Jpeg2KImagePlugin',
'McIdasImagePlugin',
'MicImagePlugin',
'MpegImagePlugin',

Binary file not shown.

Binary file not shown.

BIN
Tests/images/test-card.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

106
Tests/test_file_jpeg2k.py Normal file
View File

@ -0,0 +1,106 @@
from tester import *
from PIL import Image
from PIL import ImageFile
codecs = dir(Image.core)
if "jpeg2k_encoder" not in codecs or "jpeg2k_decoder" not in codecs:
skip('JPEG 2000 support not available')
test_card = Image.open('Tests/images/test-card.png')
test_card.load()
def roundtrip(im, **options):
out = BytesIO()
im.save(out, "JPEG2000", **options)
bytes = out.tell()
out.seek(0)
im = Image.open(out)
im.bytes = bytes # for testing only
im.load()
return im
# ----------------------------------------------------------------------
def test_sanity():
# Internal version number
assert_match(Image.core.jp2klib_version, '\d+\.\d+\.\d+$')
im = Image.open('Tests/images/test-card-lossless.jp2')
im.load()
assert_equal(im.mode, 'RGB')
assert_equal(im.size, (640, 480))
assert_equal(im.format, 'JPEG2000')
# ----------------------------------------------------------------------
# These two test pre-written JPEG 2000 files that were not written with
# PIL (they were made using Adobe Photoshop)
def test_lossless():
im = Image.open('Tests/images/test-card-lossless.jp2')
im.load()
im.save('/tmp/test-card.png')
assert_image_similar(im, test_card, 1.0e-3)
def test_lossy_tiled():
im = Image.open('Tests/images/test-card-lossy-tiled.jp2')
im.load()
assert_image_similar(im, test_card, 2.0)
# ----------------------------------------------------------------------
def test_lossless_rt():
im = roundtrip(test_card)
assert_image_equal(im, test_card)
def test_lossy_rt():
im = roundtrip(test_card, quality_layers=[20])
assert_image_similar(im, test_card, 2.0)
def test_tiled_rt():
im = roundtrip(test_card, tile_size=(128, 128))
assert_image_equal(im, test_card)
def test_tiled_offset_rt():
im = roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0),
offset=(32, 32))
assert_image_equal(im, test_card)
def test_irreversible_rt():
im = roundtrip(test_card, irreversible=True, quality_layers=[20])
assert_image_similar(im, test_card, 2.0)
def test_prog_qual_rt():
im = roundtrip(test_card, quality_layers=[60, 40, 20], progression='LRCP')
assert_image_similar(im, test_card, 2.0)
def test_prog_res_rt():
im = roundtrip(test_card, num_resolutions=8, progression='RLCP')
assert_image_equal(im, test_card)
# ----------------------------------------------------------------------
def test_reduce():
im = Image.open('Tests/images/test-card-lossless.jp2')
im.reduce = 2
im.load()
assert_equal(im.size, (160, 120))
def test_layers():
out = BytesIO()
test_card.save(out, 'JPEG2000', quality_layers=[100, 50, 10],
progression='LRCP')
out.seek(0)
im = Image.open(out)
im.layers = 1
im.load()
assert_image_similar(im, test_card, 13)
out.seek(0)
im = Image.open(out)
im.layers = 3
im.load()
assert_image_similar(im, test_card, 0.4)

View File

@ -3325,6 +3325,7 @@ extern PyObject* PyImaging_FliDecoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_GifDecoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_HexDecoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_JpegDecoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_TiffLzwDecoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_MspDecoderNew(PyObject* self, PyObject* args);
@ -3341,6 +3342,7 @@ extern PyObject* PyImaging_ZipDecoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_EpsEncoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_GifEncoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_JpegEncoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_Jpeg2KEncoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_PcxEncoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_RawEncoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_XbmEncoderNew(PyObject* self, PyObject* args);
@ -3393,6 +3395,10 @@ static PyMethodDef functions[] = {
#ifdef HAVE_LIBJPEG
{"jpeg_decoder", (PyCFunction)PyImaging_JpegDecoderNew, 1},
{"jpeg_encoder", (PyCFunction)PyImaging_JpegEncoderNew, 1},
#endif
#ifdef HAVE_OPENJPEG
{"jpeg2k_decoder", (PyCFunction)PyImaging_Jpeg2KDecoderNew, 1},
{"jpeg2k_encoder", (PyCFunction)PyImaging_Jpeg2KEncoderNew, 1},
#endif
{"tiff_lzw_decoder", (PyCFunction)PyImaging_TiffLzwDecoderNew, 1},
#ifdef HAVE_LIBTIFF
@ -3497,6 +3503,13 @@ setup_module(PyObject* m) {
}
#endif
#ifdef HAVE_OPENJPEG
{
extern const char *ImagingJpeg2KVersion(void);
PyDict_SetItemString(d, "jp2klib_version", PyUnicode_FromString(ImagingJpeg2KVersion()));
}
#endif
#ifdef HAVE_LIBZ
/* zip encoding strategies */
PyModule_AddIntConstant(m, "DEFAULT_STRATEGY", Z_DEFAULT_STRATEGY);

View File

@ -52,6 +52,7 @@ typedef struct {
struct ImagingCodecStateInstance state;
Imaging im;
PyObject* lock;
int handles_eof;
} ImagingDecoderObject;
static PyTypeObject ImagingDecoderType;
@ -93,6 +94,9 @@ PyImaging_DecoderNew(int contextsize)
/* Initialize the cleanup function pointer */
decoder->cleanup = NULL;
/* Most decoders don't want to handle EOF themselves */
decoder->handles_eof = 0;
return decoder;
}
@ -194,6 +198,12 @@ _setimage(ImagingDecoderObject* decoder, PyObject* args)
return Py_None;
}
static PyObject *
_get_handles_eof(ImagingDecoderObject *decoder)
{
return PyBool_FromLong(decoder->handles_eof);
}
static struct PyMethodDef methods[] = {
{"decode", (PyCFunction)_decode, 1},
{"cleanup", (PyCFunction)_decode_cleanup, 1},
@ -201,6 +211,13 @@ static struct PyMethodDef methods[] = {
{NULL, NULL} /* sentinel */
};
static struct PyGetSetDef getseters[] = {
{"handles_eof", (getter)_get_handles_eof, NULL,
"True if this decoder expects to handle EOF itself.",
NULL},
{NULL, NULL, NULL, NULL, NULL} /* sentinel */
};
static PyTypeObject ImagingDecoderType = {
PyVarObject_HEAD_INIT(NULL, 0)
"ImagingDecoder", /*tp_name*/
@ -232,7 +249,7 @@ static PyTypeObject ImagingDecoderType = {
0, /*tp_iternext*/
methods, /*tp_methods*/
0, /*tp_members*/
0, /*tp_getset*/
getseters, /*tp_getset*/
};
/* -------------------------------------------------------------------- */
@ -762,3 +779,55 @@ PyImaging_JpegDecoderNew(PyObject* self, PyObject* args)
return (PyObject*) decoder;
}
#endif
/* -------------------------------------------------------------------- */
/* JPEG 2000 */
/* -------------------------------------------------------------------- */
#ifdef HAVE_OPENJPEG
#include "Jpeg2K.h"
PyObject*
PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args)
{
ImagingDecoderObject* decoder;
JPEG2KDECODESTATE *context;
char* mode;
char* format;
OPJ_CODEC_FORMAT codec_format;
int reduce = 0;
int layers = 0;
int fd = -1;
if (!PyArg_ParseTuple(args, "ss|iii", &mode, &format,
&reduce, &layers, &fd))
return NULL;
if (strcmp(format, "j2k") == 0)
codec_format = OPJ_CODEC_J2K;
else if (strcmp(format, "jpt") == 0)
codec_format = OPJ_CODEC_JPT;
else if (strcmp(format, "jp2") == 0)
codec_format = OPJ_CODEC_JP2;
else
return NULL;
decoder = PyImaging_DecoderNew(sizeof(JPEG2KDECODESTATE));
if (decoder == NULL)
return NULL;
decoder->handles_eof = 1;
decoder->decode = ImagingJpeg2KDecode;
decoder->cleanup = ImagingJpeg2KDecodeCleanup;
context = (JPEG2KDECODESTATE *)decoder->state.context;
context->fd = fd;
context->format = codec_format;
context->reduce = reduce;
context->layers = layers;
return (PyObject*) decoder;
}
#endif /* HAVE_OPENJPEG */

View File

@ -153,6 +153,92 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
before building the Python Imaging Library. See the distribution README for
details.
JPEG 2000
^^^^^^^^^
PIL reads and writes JPEG 2000 files containing ``L``, ``LA``, ``RGB`` or
``RGBA`` data. It can also read files containing ``YCbCr`` data, which it
converts on read into ``RGB`` or ``RGBA`` depending on whether or not there is
an alpha channel. PIL supports JPEG 2000 raw codestreams (``.j2k`` files), as
well as boxed JPEG 2000 files (``.j2p`` or ``.jpx`` files). PIL does *not*
support files whose components have different sampling frequencies.
When loading, if you set the ``mode`` on the image prior to the
:py:meth:`~PIL.Image.Image.load` method being invoked, you can ask PIL to
convert the image to either ``RGB`` or ``RGBA`` rather than choosing for
itself. It is also possible to set ``reduce`` to the number of resolutions to
discard (each one reduces the size of the resulting image by a factor of 2),
and ``layers`` to specify the number of quality layers to load.
The :py:meth:`~PIL.Image.Image.save` method supports the following options:
**offset**
The image offset, as a tuple of integers, e.g. (16, 16)
**tile_offset**
The tile offset, again as a 2-tuple of integers.
**tile_size**
The tile size as a 2-tuple. If not specified, or if set to None, the
image will be saved without tiling.
**quality_mode**
Either `"rates"` or `"dB"` depending on the units you want to use to
specify image quality.
**quality_layers**
A sequence of numbers, each of which represents either an approximate size
reduction (if quality mode is `"rates"`) or a signal to noise ratio value
in decibels. If not specified, defaults to a single layer of full quality.
**num_resolutions**
The number of different image resolutions to be stored (which corresponds
to the number of Discrete Wavelet Transform decompositions plus one).
**codeblock_size**
The code-block size as a 2-tuple. Minimum size is 4 x 4, maximum is 1024 x
1024, with the additional restriction that no code-block may have more
than 4096 coefficients (i.e. the product of the two numbers must be no
greater than 4096).
**precinct_size**
The precinct size as a 2-tuple. Must be a power of two along both axes,
and must be greater than the code-block size.
**irreversible**
If ``True``, use the lossy Irreversible Color Transformation
followed by DWT 9-7. Defaults to ``False``, which means to use the
Reversible Color Transformation with DWT 5-3.
**progression**
Controls the progression order; must be one of ``"LRCP"``, ``"RLCP"``,
``"RPCL"``, ``"PCRL"``, ``"CPRL"``. The letters stand for Component,
Position, Resolution and Layer respectively and control the order of
encoding, the idea being that e.g. an image encoded using LRCP mode can
have its quality layers decoded as they arrive at the decoder, while one
encoded using RLCP mode will have increasing resolutions decoded as they
arrive, and so on.
**cinema_mode**
Set the encoder to produce output compliant with the digital cinema
specifications. The options here are ``"no"`` (the default),
``"cinema2k-24"`` for 24fps 2K, ``"cinema2k-48"`` for 48fps 2K, and
``"cinema4k-24"`` for 24fps 4K. Note that for compliant 2K files,
*at least one* of your image dimensions must match 2048 x 1080, while
for compliant 4K files, *at least one* of the dimensions must match
4096 x 2160.
.. note::
To enable JPEG 2000 support, you need to build and install the OpenJPEG
library, version 2.0.0 or higher, before building the Python Imaging
Library.
Windows users can install the OpenJPEG binaries available on the
OpenJPEG website, but must add them to their PATH in order to use PIL (if
you fail to do this, you will get errors about not being able to load the
``_imaging`` DLL).
MSP
^^^

View File

@ -153,6 +153,14 @@ Plugin reference
:undoc-members:
:show-inheritance:
:mod:`Jpeg2KImagePlugin` Module
-----------------------------
.. automodule:: PIL.Jpeg2KImagePlugin
:members:
:undoc-members:
:show-inheritance:
:mod:`McIdasImagePlugin` Module
-------------------------------

160
encode.c
View File

@ -40,6 +40,7 @@ typedef struct {
PyObject_HEAD
int (*encode)(Imaging im, ImagingCodecState state,
UINT8* buffer, int bytes);
int (*cleanup)(ImagingCodecState state);
struct ImagingCodecStateInstance state;
Imaging im;
PyObject* lock;
@ -77,6 +78,9 @@ PyImaging_EncoderNew(int contextsize)
/* Initialize encoder context */
encoder->state.context = context;
/* Most encoders don't need this */
encoder->cleanup = NULL;
/* Target image */
encoder->lock = NULL;
encoder->im = NULL;
@ -87,6 +91,8 @@ PyImaging_EncoderNew(int contextsize)
static void
_dealloc(ImagingEncoderObject* encoder)
{
if (encoder->cleanup)
encoder->cleanup(&encoder->state);
free(encoder->state.buffer);
free(encoder->state.context);
Py_XDECREF(encoder->lock);
@ -797,4 +803,158 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
#endif
/* -------------------------------------------------------------------- */
/* JPEG 2000 */
/* -------------------------------------------------------------------- */
#ifdef HAVE_OPENJPEG
#include "Jpeg2K.h"
static void
j2k_decode_coord_tuple(PyObject *tuple, int *x, int *y)
{
*x = *y = 0;
if (tuple && PyTuple_Check(tuple) && PyTuple_GET_SIZE(tuple) == 2) {
*x = (int)PyInt_AsLong(PyTuple_GET_ITEM(tuple, 0));
*y = (int)PyInt_AsLong(PyTuple_GET_ITEM(tuple, 1));
if (*x < 0)
*x = 0;
if (*y < 0)
*y = 0;
}
}
PyObject*
PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args)
{
ImagingEncoderObject *encoder;
JPEG2KENCODESTATE *context;
char *mode;
char *format;
OPJ_CODEC_FORMAT codec_format;
PyObject *offset = NULL, *tile_offset = NULL, *tile_size = NULL;
char *quality_mode = "rates";
PyObject *quality_layers = NULL;
int num_resolutions = 0;
PyObject *cblk_size = NULL, *precinct_size = NULL;
PyObject *irreversible = NULL;
char *progression = "LRCP";
OPJ_PROG_ORDER prog_order;
char *cinema_mode = "no";
OPJ_CINEMA_MODE cine_mode;
int fd = -1;
if (!PyArg_ParseTuple(args, "ss|OOOsOIOOOssi", &mode, &format,
&offset, &tile_offset, &tile_size,
&quality_mode, &quality_layers, &num_resolutions,
&cblk_size, &precinct_size,
&irreversible, &progression, &cinema_mode,
&fd))
return NULL;
if (strcmp (format, "j2k") == 0)
codec_format = OPJ_CODEC_J2K;
else if (strcmp (format, "jpt") == 0)
codec_format = OPJ_CODEC_JPT;
else if (strcmp (format, "jp2") == 0)
codec_format = OPJ_CODEC_JP2;
else
return NULL;
if (strcmp(progression, "LRCP") == 0)
prog_order = OPJ_LRCP;
else if (strcmp(progression, "RLCP") == 0)
prog_order = OPJ_RLCP;
else if (strcmp(progression, "RPCL") == 0)
prog_order = OPJ_RPCL;
else if (strcmp(progression, "PCRL") == 0)
prog_order = OPJ_PCRL;
else if (strcmp(progression, "CPRL") == 0)
prog_order = OPJ_CPRL;
else
return NULL;
if (strcmp(cinema_mode, "no") == 0)
cine_mode = OPJ_OFF;
else if (strcmp(cinema_mode, "cinema2k-24") == 0)
cine_mode = OPJ_CINEMA2K_24;
else if (strcmp(cinema_mode, "cinema2k-48") == 0)
cine_mode = OPJ_CINEMA2K_48;
else if (strcmp(cinema_mode, "cinema4k-24") == 0)
cine_mode = OPJ_CINEMA4K_24;
else
return NULL;
encoder = PyImaging_EncoderNew(sizeof(JPEG2KENCODESTATE));
if (!encoder)
return NULL;
encoder->encode = ImagingJpeg2KEncode;
encoder->cleanup = ImagingJpeg2KEncodeCleanup;
context = (JPEG2KENCODESTATE *)encoder->state.context;
context->fd = fd;
context->format = codec_format;
context->offset_x = context->offset_y = 0;
j2k_decode_coord_tuple(offset, &context->offset_x, &context->offset_y);
j2k_decode_coord_tuple(tile_offset,
&context->tile_offset_x,
&context->tile_offset_y);
j2k_decode_coord_tuple(tile_size,
&context->tile_size_x,
&context->tile_size_y);
/* Error on illegal tile offsets */
if (context->tile_size_x && context->tile_size_y) {
if (context->tile_offset_x <= context->offset_x - context->tile_size_x
|| context->tile_offset_y <= context->offset_y - context->tile_size_y) {
PyErr_SetString(PyExc_ValueError,
"JPEG 2000 tile offset too small; top left tile must "
"intersect image area");
}
if (context->tile_offset_x > context->offset_x
|| context->tile_offset_y > context->offset_y) {
PyErr_SetString(PyExc_ValueError,
"JPEG 2000 tile offset too large to cover image area");
Py_DECREF(encoder);
return NULL;
}
}
if (quality_layers && PySequence_Check(quality_layers)) {
context->quality_is_in_db = strcmp (quality_mode, "dB") == 0;
context->quality_layers = quality_layers;
Py_INCREF(quality_layers);
}
context->num_resolutions = num_resolutions;
j2k_decode_coord_tuple(cblk_size,
&context->cblk_width,
&context->cblk_height);
j2k_decode_coord_tuple(precinct_size,
&context->precinct_width,
&context->precinct_height);
context->irreversible = PyObject_IsTrue(irreversible);
context->progression = prog_order;
context->cinema_mode = cine_mode;
return (PyObject *)encoder;
}
#endif
/*
* Local Variables:
* c-basic-offset: 4
* End:
*
*/

View File

@ -424,6 +424,14 @@ extern int ImagingJpegDecodeCleanup(ImagingCodecState state);
extern int ImagingJpegEncode(Imaging im, ImagingCodecState state,
UINT8* buffer, int bytes);
#endif
#ifdef HAVE_OPENJPEG
extern int ImagingJpeg2KDecode(Imaging im, ImagingCodecState state,
UINT8* buffer, int bytes);
extern int ImagingJpeg2KDecodeCleanup(ImagingCodecState state);
extern int ImagingJpeg2KEncode(Imaging im, ImagingCodecState state,
UINT8* buffer, int bytes);
extern int ImagingJpeg2KEncodeCleanup(ImagingCodecState state);
#endif
extern int ImagingLzwDecode(Imaging im, ImagingCodecState state,
UINT8* buffer, int bytes);
#ifdef HAVE_LIBTIFF
@ -497,6 +505,32 @@ struct ImagingCodecStateInstance {
void *context;
};
/* Incremental encoding/decoding support */
typedef struct ImagingIncrementalCodecStruct *ImagingIncrementalCodec;
typedef int (*ImagingIncrementalCodecEntry)(Imaging im,
ImagingCodecState state,
ImagingIncrementalCodec codec);
enum {
INCREMENTAL_CODEC_READ = 1,
INCREMENTAL_CODEC_WRITE = 2
};
enum {
INCREMENTAL_CODEC_NOT_SEEKABLE = 0,
INCREMENTAL_CODEC_SEEKABLE = 1
};
extern ImagingIncrementalCodec ImagingIncrementalCodecCreate(ImagingIncrementalCodecEntry codec_entry, Imaging im, ImagingCodecState state, int read_or_write, int seekable, int fd);
extern void ImagingIncrementalCodecDestroy(ImagingIncrementalCodec codec);
extern int ImagingIncrementalCodecPushBuffer(ImagingIncrementalCodec codec, UINT8 *buf, int bytes);
extern ssize_t ImagingIncrementalCodecRead(ImagingIncrementalCodec codec, void *buffer, size_t bytes);
extern off_t ImagingIncrementalCodecSkip(ImagingIncrementalCodec codec, off_t bytes);
extern ssize_t ImagingIncrementalCodecWrite(ImagingIncrementalCodec codec, const void *buffer, size_t bytes);
extern off_t ImagingIncrementalCodecSeek(ImagingIncrementalCodec codec, off_t bytes);
extern size_t ImagingIncrementalCodecBytesInBuffer(ImagingIncrementalCodec codec);
/* Errcodes */
#define IMAGING_CODEC_END 1
#define IMAGING_CODEC_OVERRUN -1

672
libImaging/Incremental.c Normal file
View File

@ -0,0 +1,672 @@
/*
* The Python Imaging Library
* $Id$
*
* incremental decoding adaptor.
*
* Copyright (c) 2014 Coriolis Systems Limited
* Copyright (c) 2014 Alastair Houghton
*
*/
#include "Imaging.h"
/* The idea behind this interface is simple: the actual decoding proceeds in
a thread, which is run in lock step with the main thread. Whenever the
ImagingIncrementalCodecRead() call runs short on data, it suspends the
decoding thread and wakes the main thread. Conversely, the
ImagingIncrementalCodecPushBuffer() call suspends the main thread and wakes
the decoding thread, providing a buffer of data.
The two threads are never running simultaneously, so there is no need for
any addition synchronisation measures outside of this file.
Note also that we start the thread suspended (on Windows), or make it
immediately wait (other platforms), so that it's possible to initialise
things before the thread starts running.
This interface is useful to allow PIL to interact efficiently with any
third-party imaging library that does not support suspendable reads;
one example is OpenJPEG (which is used for J2K support). The TIFF library
might also benefit from using this code.
Note that if using this module, you want to set handles_eof on your
decoder to true. Why? Because otherwise ImageFile.load() will abort,
thinking that the image is truncated, whereas generally you want it to
pass the EOF condition (0 bytes to read) through to your code. */
/* Additional complication: *Some* codecs need to seek; this is fine if
there is a file descriptor, but if we're buffering data it becomes
awkward. The incremental adaptor now contains code to handle these
two cases. */
#ifdef _WIN32
#include <windows.h>
#include <process.h>
#else
#include <pthread.h>
#endif
#define DEBUG_INCREMENTAL 0
#if DEBUG_INCREMENTAL
#define DEBUG(...) printf(__VA_ARGS__)
#else
#define DEBUG(...)
#endif
struct ImagingIncrementalCodecStruct {
#ifdef _WIN32
HANDLE hCodecEvent;
HANDLE hDataEvent;
HANDLE hThread;
#else
pthread_mutex_t start_mutex;
pthread_cond_t start_cond;
pthread_mutex_t codec_mutex;
pthread_cond_t codec_cond;
pthread_mutex_t data_mutex;
pthread_cond_t data_cond;
pthread_t thread;
#endif
ImagingIncrementalCodecEntry entry;
Imaging im;
ImagingCodecState state;
struct {
int fd;
UINT8 *buffer; /* Base of buffer */
UINT8 *ptr; /* Current pointer in buffer */
UINT8 *top; /* Highest point in buffer we've used */
UINT8 *end; /* End of buffer */
} stream;
int read_or_write;
int seekable;
int started;
int result;
};
static void flush_stream(ImagingIncrementalCodec codec);
#if _WIN32
static unsigned int __stdcall
codec_thread(void *ptr)
{
ImagingIncrementalCodec codec = (ImagingIncrementalCodec)ptr;
DEBUG("Entering thread\n");
codec->result = codec->entry(codec->im, codec->state, codec);
DEBUG("Leaving thread (%d)\n", codec->result);
flush_stream(codec);
SetEvent(codec->hCodecEvent);
return 0;
}
#else
static void *
codec_thread(void *ptr)
{
ImagingIncrementalCodec codec = (ImagingIncrementalCodec)ptr;
DEBUG("Entering thread\n");
codec->result = codec->entry(codec->im, codec->state, codec);
DEBUG("Leaving thread (%d)\n", codec->result);
flush_stream(codec);
pthread_mutex_lock(&codec->codec_mutex);
pthread_cond_signal(&codec->codec_cond);
pthread_mutex_unlock(&codec->codec_mutex);
return NULL;
}
#endif
static void
flush_stream(ImagingIncrementalCodec codec)
{
/* This is to flush data from the write buffer for a seekable write
codec. */
if (codec->read_or_write != INCREMENTAL_CODEC_WRITE
|| codec->state->errcode != IMAGING_CODEC_END
|| !codec->seekable
|| codec->stream.fd >= 0)
return;
DEBUG("flushing data\n");
UINT8 *buffer = codec->stream.buffer;
size_t bytes = codec->stream.ptr - codec->stream.buffer;
codec->state->errcode = 0;
codec->seekable = INCREMENTAL_CODEC_NOT_SEEKABLE;
codec->stream.buffer = codec->stream.ptr = codec->stream.end
= codec->stream.top = NULL;
ImagingIncrementalCodecWrite(codec, buffer, bytes);
codec->state->errcode = IMAGING_CODEC_END;
codec->result = (int)ImagingIncrementalCodecBytesInBuffer(codec);
free(buffer);
}
/**
* Create a new incremental codec */
ImagingIncrementalCodec
ImagingIncrementalCodecCreate(ImagingIncrementalCodecEntry codec_entry,
Imaging im,
ImagingCodecState state,
int read_or_write,
int seekable,
int fd)
{
ImagingIncrementalCodec codec = (ImagingIncrementalCodec)malloc(sizeof(struct ImagingIncrementalCodecStruct));
codec->entry = codec_entry;
codec->im = im;
codec->state = state;
codec->result = 0;
codec->stream.fd = fd;
codec->stream.buffer = codec->stream.ptr = codec->stream.end
= codec->stream.top = NULL;
codec->started = 0;
codec->seekable = seekable;
codec->read_or_write = read_or_write;
if (fd >= 0)
lseek(fd, 0, SEEK_SET);
/* System specific set-up */
#if _WIN32
codec->hCodecEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (!codec->hCodecEvent) {
free(codec);
return NULL;
}
codec->hDataEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (!codec->hDataEvent) {
CloseHandle(codec->hCodecEvent);
free(codec);
return NULL;
}
codec->hThread = _beginthreadex(NULL, 0, codec_thread, codec,
CREATE_SUSPENDED, NULL);
if (!codec->hThread) {
CloseHandle(codec->hCodecEvent);
CloseHandle(codec->hDataEvent);
free(codec);
return NULL;
}
#else
if (pthread_mutex_init(&codec->start_mutex, NULL)) {
free (codec);
return NULL;
}
if (pthread_mutex_init(&codec->codec_mutex, NULL)) {
pthread_mutex_destroy(&codec->start_mutex);
free(codec);
return NULL;
}
if (pthread_mutex_init(&codec->data_mutex, NULL)) {
pthread_mutex_destroy(&codec->start_mutex);
pthread_mutex_destroy(&codec->codec_mutex);
free(codec);
return NULL;
}
if (pthread_cond_init(&codec->start_cond, NULL)) {
pthread_mutex_destroy(&codec->start_mutex);
pthread_mutex_destroy(&codec->codec_mutex);
pthread_mutex_destroy(&codec->data_mutex);
free(codec);
return NULL;
}
if (pthread_cond_init(&codec->codec_cond, NULL)) {
pthread_mutex_destroy(&codec->start_mutex);
pthread_mutex_destroy(&codec->codec_mutex);
pthread_mutex_destroy(&codec->data_mutex);
pthread_cond_destroy(&codec->start_cond);
free(codec);
return NULL;
}
if (pthread_cond_init(&codec->data_cond, NULL)) {
pthread_mutex_destroy(&codec->start_mutex);
pthread_mutex_destroy(&codec->codec_mutex);
pthread_mutex_destroy(&codec->data_mutex);
pthread_cond_destroy(&codec->start_cond);
pthread_cond_destroy(&codec->codec_cond);
free(codec);
return NULL;
}
if (pthread_create(&codec->thread, NULL, codec_thread, codec)) {
pthread_mutex_destroy(&codec->start_mutex);
pthread_mutex_destroy(&codec->codec_mutex);
pthread_mutex_destroy(&codec->data_mutex);
pthread_cond_destroy(&codec->start_cond);
pthread_cond_destroy(&codec->codec_cond);
pthread_cond_destroy(&codec->data_cond);
free(codec);
return NULL;
}
#endif
return codec;
}
/**
* Destroy an incremental codec */
void
ImagingIncrementalCodecDestroy(ImagingIncrementalCodec codec)
{
DEBUG("destroying\n");
if (!codec->started) {
#ifdef _WIN32
ResumeThread(codec->hThread);
#else
pthread_cond_signal(&codec->start_cond);
#endif
codec->started = 1;
}
#ifndef _WIN32
pthread_mutex_lock(&codec->data_mutex);
#endif
if (codec->seekable && codec->stream.fd < 0)
free (codec->stream.buffer);
codec->stream.buffer = codec->stream.ptr = codec->stream.end
= codec->stream.top = NULL;
#ifdef _WIN32
SetEvent(codec->hDataEvent);
WaitForSingleObject(codec->hThread, INFINITE);
CloseHandle(codec->hThread);
CloseHandle(codec->hCodecEvent);
CloseHandle(codec->hDataEvent);
#else
pthread_cond_signal(&codec->data_cond);
pthread_mutex_unlock(&codec->data_mutex);
pthread_join(codec->thread, NULL);
pthread_mutex_destroy(&codec->start_mutex);
pthread_mutex_destroy(&codec->codec_mutex);
pthread_mutex_destroy(&codec->data_mutex);
pthread_cond_destroy(&codec->start_cond);
pthread_cond_destroy(&codec->codec_cond);
pthread_cond_destroy(&codec->data_cond);
#endif
free (codec);
}
/**
* Push a data buffer for an incremental codec */
int
ImagingIncrementalCodecPushBuffer(ImagingIncrementalCodec codec,
UINT8 *buf, int bytes)
{
if (!codec->started) {
DEBUG("starting\n");
#ifdef _WIN32
ResumeThread(codec->hThread);
#else
pthread_cond_signal(&codec->start_cond);
#endif
codec->started = 1;
/* Wait for the thread to ask for data */
#ifdef _WIN32
WaitForSingleObject(codec->hCodecEvent, INFINITE);
#else
pthread_mutex_lock(&codec->codec_mutex);
pthread_cond_wait(&codec->codec_cond, &codec->codec_mutex);
pthread_mutex_unlock(&codec->codec_mutex);
#endif
if (codec->result < 0) {
DEBUG("got result %d\n", codec->result);
return codec->result;
}
}
/* Codecs using an fd don't need data, so when we get here, we're done */
if (codec->stream.fd >= 0) {
DEBUG("got result %d\n", codec->result);
return codec->result;
}
DEBUG("providing %p, %d\n", buf, bytes);
#ifndef _WIN32
pthread_mutex_lock(&codec->data_mutex);
#endif
if (codec->read_or_write == INCREMENTAL_CODEC_READ
&& codec->seekable && codec->stream.fd < 0) {
/* In this specific case, we append to a buffer we allocate ourselves */
size_t old_size = codec->stream.end - codec->stream.buffer;
size_t new_size = codec->stream.end - codec->stream.buffer + bytes;
UINT8 *new = (UINT8 *)realloc (codec->stream.buffer, new_size);
if (!new) {
codec->state->errcode = IMAGING_CODEC_MEMORY;
#ifndef _WIN32
pthread_mutex_unlock(&codec->data_mutex);
#endif
return -1;
}
codec->stream.ptr = codec->stream.ptr - codec->stream.buffer + new;
codec->stream.end = new + new_size;
codec->stream.buffer = new;
memcpy(new + old_size, buf, bytes);
} else {
codec->stream.buffer = codec->stream.ptr = buf;
codec->stream.end = buf + bytes;
}
#ifdef _WIN32
SetEvent(codec->hDataEvent);
WaitForSingleObject(codec->hCodecEvent, INFINITE);
#else
pthread_cond_signal(&codec->data_cond);
pthread_mutex_unlock(&codec->data_mutex);
pthread_mutex_lock(&codec->codec_mutex);
pthread_cond_wait(&codec->codec_cond, &codec->codec_mutex);
pthread_mutex_unlock(&codec->codec_mutex);
#endif
DEBUG("got result %d\n", codec->result);
return codec->result;
}
size_t
ImagingIncrementalCodecBytesInBuffer(ImagingIncrementalCodec codec)
{
return codec->stream.ptr - codec->stream.buffer;
}
ssize_t
ImagingIncrementalCodecRead(ImagingIncrementalCodec codec,
void *buffer, size_t bytes)
{
UINT8 *ptr = (UINT8 *)buffer;
size_t done = 0;
if (codec->read_or_write == INCREMENTAL_CODEC_WRITE) {
DEBUG("attempt to read from write codec\n");
return -1;
}
DEBUG("reading (want %llu bytes)\n", (unsigned long long)bytes);
if (codec->stream.fd >= 0) {
ssize_t ret = read(codec->stream.fd, buffer, bytes);
DEBUG("read %lld bytes from fd\n", (long long)ret);
return ret;
}
#ifndef _WIN32
pthread_mutex_lock(&codec->data_mutex);
#endif
while (bytes) {
size_t todo = bytes;
size_t remaining = codec->stream.end - codec->stream.ptr;
if (!remaining) {
DEBUG("waiting for data\n");
#ifndef _WIN32
pthread_mutex_lock(&codec->codec_mutex);
#endif
codec->result = (int)(codec->stream.ptr - codec->stream.buffer);
#if _WIN32
SetEvent(codec->hCodecEvent);
WaitForSingleObject(codec->hDataEvent, INFINITE);
#else
pthread_cond_signal(&codec->codec_cond);
pthread_mutex_unlock(&codec->codec_mutex);
pthread_cond_wait(&codec->data_cond, &codec->data_mutex);
#endif
remaining = codec->stream.end - codec->stream.ptr;
codec->stream.top = codec->stream.end;
DEBUG("got %llu bytes\n", (unsigned long long)remaining);
}
if (todo > remaining)
todo = remaining;
if (!todo)
break;
memcpy (ptr, codec->stream.ptr, todo);
codec->stream.ptr += todo;
bytes -= todo;
done += todo;
ptr += todo;
}
#ifndef _WIN32
pthread_mutex_unlock(&codec->data_mutex);
#endif
DEBUG("read total %llu bytes\n", (unsigned long long)done);
return done;
}
off_t
ImagingIncrementalCodecSkip(ImagingIncrementalCodec codec,
off_t bytes)
{
off_t done = 0;
DEBUG("skipping (want %llu bytes)\n", (unsigned long long)bytes);
/* In write mode, explicitly fill with zeroes */
if (codec->read_or_write == INCREMENTAL_CODEC_WRITE) {
static const UINT8 zeroes[256] = { 0 };
off_t done = 0;
while (bytes) {
size_t todo = (size_t)(bytes > 256 ? 256 : bytes);
ssize_t written = ImagingIncrementalCodecWrite(codec, zeroes, todo);
if (written <= 0)
break;
done += written;
bytes -= written;
}
return done;
}
if (codec->stream.fd >= 0)
return lseek(codec->stream.fd, bytes, SEEK_CUR);
#ifndef _WIN32
pthread_mutex_lock(&codec->data_mutex);
#endif
while (bytes) {
off_t todo = bytes;
off_t remaining = codec->stream.end - codec->stream.ptr;
if (!remaining) {
DEBUG("waiting for data\n");
#ifndef _WIN32
pthread_mutex_lock(&codec->codec_mutex);
#endif
codec->result = (int)(codec->stream.ptr - codec->stream.buffer);
#if _WIN32
SetEvent(codec->hCodecEvent);
WaitForSingleObject(codec->hDataEvent, INFINITE);
#else
pthread_cond_signal(&codec->codec_cond);
pthread_mutex_unlock(&codec->codec_mutex);
pthread_cond_wait(&codec->data_cond, &codec->data_mutex);
#endif
remaining = codec->stream.end - codec->stream.ptr;
}
if (todo > remaining)
todo = remaining;
if (!todo)
break;
codec->stream.ptr += todo;
bytes -= todo;
done += todo;
}
#ifndef _WIN32
pthread_mutex_unlock(&codec->data_mutex);
#endif
DEBUG("skipped total %llu bytes\n", (unsigned long long)done);
return done;
}
ssize_t
ImagingIncrementalCodecWrite(ImagingIncrementalCodec codec,
const void *buffer, size_t bytes)
{
const UINT8 *ptr = (const UINT8 *)buffer;
size_t done = 0;
if (codec->read_or_write == INCREMENTAL_CODEC_READ) {
DEBUG("attempt to write from read codec\n");
return -1;
}
DEBUG("write (have %llu bytes)\n", (unsigned long long)bytes);
if (codec->stream.fd >= 0)
return write(codec->stream.fd, buffer, bytes);
#ifndef _WIN32
pthread_mutex_lock(&codec->data_mutex);
#endif
while (bytes) {
size_t todo = bytes;
size_t remaining = codec->stream.end - codec->stream.ptr;
if (!remaining) {
if (codec->seekable && codec->stream.fd < 0) {
/* In this case, we maintain the stream buffer ourselves */
size_t old_size = codec->stream.top - codec->stream.buffer;
size_t new_size = (old_size + bytes + 65535) & ~65535;
UINT8 *new = (UINT8 *)realloc(codec->stream.buffer, new_size);
if (!new) {
codec->state->errcode = IMAGING_CODEC_MEMORY;
#ifndef _WIN32
pthread_mutex_unlock(&codec->data_mutex);
#endif
return done == 0 ? -1 : done;
}
codec->stream.ptr = codec->stream.ptr - codec->stream.buffer + new;
codec->stream.buffer = new;
codec->stream.end = new + new_size;
codec->stream.top = new + old_size;
} else {
DEBUG("waiting for space\n");
#ifndef _WIN32
pthread_mutex_lock(&codec->codec_mutex);
#endif
codec->result = (int)(codec->stream.ptr - codec->stream.buffer);
#if _WIN32
SetEvent(codec->hCodecEvent);
WaitForSingleObject(codec->hDataEvent, INFINITE);
#else
pthread_cond_signal(&codec->codec_cond);
pthread_mutex_unlock(&codec->codec_mutex);
pthread_cond_wait(&codec->data_cond, &codec->data_mutex);
#endif
}
remaining = codec->stream.end - codec->stream.ptr;
DEBUG("got %llu bytes\n", (unsigned long long)remaining);
}
if (todo > remaining)
todo = remaining;
if (!todo)
break;
memcpy (codec->stream.ptr, ptr, todo);
codec->stream.ptr += todo;
bytes -= todo;
done += todo;
ptr += todo;
}
if (codec->stream.ptr > codec->stream.top)
codec->stream.top = codec->stream.ptr;
#ifndef _WIN32
pthread_mutex_unlock(&codec->data_mutex);
#endif
DEBUG("wrote total %llu bytes\n", (unsigned long long)done);
return done;
}
off_t
ImagingIncrementalCodecSeek(ImagingIncrementalCodec codec,
off_t bytes)
{
DEBUG("seeking (going to %llu bytes)\n", (unsigned long long)bytes);
if (codec->stream.fd >= 0)
return lseek(codec->stream.fd, bytes, SEEK_SET);
if (!codec->seekable) {
DEBUG("attempt to seek non-seekable stream\n");
return -1;
}
if (bytes < 0) {
DEBUG("attempt to seek before stream start\n");
return -1;
}
off_t buffered = codec->stream.top - codec->stream.buffer;
if (bytes <= buffered) {
DEBUG("seek within buffer\n");
codec->stream.ptr = codec->stream.buffer + bytes;
return bytes;
}
return buffered + ImagingIncrementalCodecSkip(codec, bytes - buffered);
}

91
libImaging/Jpeg2K.h Normal file
View File

@ -0,0 +1,91 @@
/*
* The Python Imaging Library
* $Id$
*
* declarations for the OpenJPEG codec interface.
*
* Copyright (c) 2014 by Coriolis Systems Limited
* Copyright (c) 2014 by Alastair Houghton
*/
#include <openjpeg-2.0/openjpeg.h>
/* -------------------------------------------------------------------- */
/* Decoder */
/* -------------------------------------------------------------------- */
typedef struct {
/* CONFIGURATION */
/* File descriptor, if available; otherwise, -1 */
int fd;
/* Specify the desired format */
OPJ_CODEC_FORMAT format;
/* Set to divide image resolution by 2**reduce. */
int reduce;
/* Set to limit the number of quality layers to decode (0 = all layers) */
int layers;
/* PRIVATE CONTEXT (set by decoder) */
const char *error_msg;
ImagingIncrementalCodec decoder;
} JPEG2KDECODESTATE;
/* -------------------------------------------------------------------- */
/* Encoder */
/* -------------------------------------------------------------------- */
typedef struct {
/* CONFIGURATION */
/* File descriptor, if available; otherwise, -1 */
int fd;
/* Specify the desired format */
OPJ_CODEC_FORMAT format;
/* Image offset */
int offset_x, offset_y;
/* Tile information */
int tile_offset_x, tile_offset_y;
int tile_size_x, tile_size_y;
/* Quality layers (a sequence of numbers giving *either* rates or dB) */
int quality_is_in_db;
PyObject *quality_layers;
/* Number of resolutions (DWT decompositions + 1 */
int num_resolutions;
/* Code block size */
int cblk_width, cblk_height;
/* Precinct size */
int precinct_width, precinct_height;
/* Compression style */
int irreversible;
/* Progression order (LRCP/RLCP/RPCL/PCRL/CPRL) */
OPJ_PROG_ORDER progression;
/* Cinema mode */
OPJ_CINEMA_MODE cinema_mode;
/* PRIVATE CONTEXT (set by decoder) */
const char *error_msg;
ImagingIncrementalCodec encoder;
} JPEG2KENCODESTATE;
/*
* Local Variables:
* c-basic-offset: 4
* End:
*
*/

747
libImaging/Jpeg2KDecode.c Normal file
View File

@ -0,0 +1,747 @@
/*
* The Python Imaging Library.
* $Id$
*
* decoder for JPEG2000 image data.
*
* history:
* 2014-03-12 ajh Created
*
* Copyright (c) 2014 Coriolis Systems Limited
* Copyright (c) 2014 Alastair Houghton
*
* See the README file for details on usage and redistribution.
*/
#include "Imaging.h"
#ifdef HAVE_OPENJPEG
#include <stdlib.h>
#include "Jpeg2K.h"
typedef struct {
OPJ_UINT32 tile_index;
OPJ_UINT32 data_size;
OPJ_INT32 x0, y0, x1, y1;
OPJ_UINT32 nb_comps;
} JPEG2KTILEINFO;
/* -------------------------------------------------------------------- */
/* Error handler */
/* -------------------------------------------------------------------- */
static void
j2k_error(const char *msg, void *client_data)
{
JPEG2KDECODESTATE *state = (JPEG2KDECODESTATE *) client_data;
free((void *)state->error_msg);
state->error_msg = strdup(msg);
}
/* -------------------------------------------------------------------- */
/* Buffer input stream */
/* -------------------------------------------------------------------- */
static OPJ_SIZE_T
j2k_read(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data)
{
ImagingIncrementalCodec decoder = (ImagingIncrementalCodec)p_user_data;
size_t len = ImagingIncrementalCodecRead(decoder, p_buffer, p_nb_bytes);
return len ? len : (OPJ_SIZE_T)-1;
}
static OPJ_OFF_T
j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data)
{
ImagingIncrementalCodec decoder = (ImagingIncrementalCodec)p_user_data;
off_t pos = ImagingIncrementalCodecSkip(decoder, p_nb_bytes);
return pos ? pos : (OPJ_OFF_T)-1;
}
/* -------------------------------------------------------------------- */
/* Unpackers */
/* -------------------------------------------------------------------- */
typedef void (*j2k_unpacker_t)(opj_image_t *in,
const JPEG2KTILEINFO *tileInfo,
const UINT8 *data,
Imaging im);
struct j2k_decode_unpacker {
const char *mode;
OPJ_COLOR_SPACE color_space;
unsigned components;
j2k_unpacker_t unpacker;
};
static inline
unsigned j2ku_shift(unsigned x, int n)
{
if (n < 0)
return x >> -n;
else
return x << n;
}
static void
j2ku_gray_l(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
const UINT8 *tiledata, Imaging im)
{
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
unsigned w = tileinfo->x1 - tileinfo->x0;
unsigned h = tileinfo->y1 - tileinfo->y0;
int shift = 8 - in->comps[0].prec;
int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0;
int csiz = (in->comps[0].prec + 7) >> 3;
unsigned x, y;
if (csiz == 3)
csiz = 4;
if (shift < 0)
offset += 1 << (-shift - 1);
switch (csiz) {
case 1:
for (y = 0; y < h; ++y) {
const UINT8 *data = &tiledata[y * w];
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
for (x = 0; x < w; ++x)
*row++ = j2ku_shift(offset + *data++, shift);
}
break;
case 2:
for (y = 0; y < h; ++y) {
const UINT16 *data = (const UINT16 *)&tiledata[2 * y * w];
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
for (x = 0; x < w; ++x)
*row++ = j2ku_shift(offset + *data++, shift);
}
break;
case 4:
for (y = 0; y < h; ++y) {
const UINT32 *data = (const UINT32 *)&tiledata[4 * y * w];
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
for (x = 0; x < w; ++x)
*row++ = j2ku_shift(offset + *data++, shift);
}
break;
}
}
static void
j2ku_gray_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
const UINT8 *tiledata, Imaging im)
{
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
unsigned w = tileinfo->x1 - tileinfo->x0;
unsigned h = tileinfo->y1 - tileinfo->y0;
int shift = 8 - in->comps[0].prec;
int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0;
int csiz = (in->comps[0].prec + 7) >> 3;
unsigned x, y;
if (shift < 0)
offset += 1 << (-shift - 1);
if (csiz == 3)
csiz = 4;
switch (csiz) {
case 1:
for (y = 0; y < h; ++y) {
const UINT8 *data = &tiledata[y * w];
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
for (x = 0; x < w; ++x) {
UINT8 byte = j2ku_shift(offset + *data++, shift);
row[0] = row[1] = row[2] = byte;
row[3] = 0xff;
row += 4;
}
}
break;
case 2:
for (y = 0; y < h; ++y) {
const UINT16 *data = (UINT16 *)&tiledata[2 * y * w];
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
for (x = 0; x < w; ++x) {
UINT8 byte = j2ku_shift(offset + *data++, shift);
row[0] = row[1] = row[2] = byte;
row[3] = 0xff;
row += 4;
}
}
break;
case 4:
for (y = 0; y < h; ++y) {
const UINT32 *data = (UINT32 *)&tiledata[4 * y * w];
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
for (x = 0; x < w; ++x) {
UINT8 byte = j2ku_shift(offset + *data++, shift);
row[0] = row[1] = row[2] = byte;
row[3] = 0xff;
row += 4;
}
}
break;
}
}
static void
j2ku_graya_la(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
const UINT8 *tiledata, Imaging im)
{
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
unsigned w = tileinfo->x1 - tileinfo->x0;
unsigned h = tileinfo->y1 - tileinfo->y0;
int shift = 8 - in->comps[0].prec;
int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0;
int csiz = (in->comps[0].prec + 7) >> 3;
int ashift = 8 - in->comps[1].prec;
int aoffset = in->comps[1].sgnd ? 1 << (in->comps[1].prec - 1) : 0;
int acsiz = (in->comps[1].prec + 7) >> 3;
const UINT8 *atiledata;
unsigned x, y;
if (csiz == 3)
csiz = 4;
if (acsiz == 3)
acsiz = 4;
if (shift < 0)
offset += 1 << (-shift - 1);
if (ashift < 0)
aoffset += 1 << (-ashift - 1);
atiledata = tiledata + csiz * w * h;
for (y = 0; y < h; ++y) {
const UINT8 *data = &tiledata[csiz * y * w];
const UINT8 *adata = &atiledata[acsiz * y * w];
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
for (x = 0; x < w; ++x) {
UINT32 word = 0, aword = 0;
switch (csiz) {
case 1: word = *data++; break;
case 2: word = *(const UINT16 *)data; data += 2; break;
case 4: word = *(const UINT32 *)data; data += 4; break;
}
switch (acsiz) {
case 1: aword = *adata++; break;
case 2: aword = *(const UINT16 *)adata; adata += 2; break;
case 4: aword = *(const UINT32 *)adata; adata += 4; break;
}
UINT8 byte = j2ku_shift(offset + word, shift);
row[0] = row[1] = row[2] = byte;
row[3] = j2ku_shift(aoffset + aword, ashift);
row += 4;
}
}
}
static void
j2ku_srgb_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
const UINT8 *tiledata, Imaging im)
{
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
unsigned w = tileinfo->x1 - tileinfo->x0;
unsigned h = tileinfo->y1 - tileinfo->y0;
int shifts[3], offsets[3], csiz[3];
const UINT8 *cdata[3];
const UINT8 *cptr = tiledata;
unsigned n, x, y;
for (n = 0; n < 3; ++n) {
cdata[n] = cptr;
shifts[n] = 8 - in->comps[n].prec;
offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0;
csiz[n] = (in->comps[n].prec + 7) >> 3;
if (csiz[n] == 3)
csiz[n] = 4;
if (shifts[n] < 0)
offsets[n] += 1 << (-shifts[n] - 1);
cptr += csiz[n] * w * h;
}
for (y = 0; y < h; ++y) {
const UINT8 *data[3];
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
for (n = 0; n < 3; ++n)
data[n] = &cdata[n][csiz[n] * y * w];
for (x = 0; x < w; ++x) {
for (n = 0; n < 3; ++n) {
UINT32 word = 0;
switch (csiz[n]) {
case 1: word = *data[n]++; break;
case 2: word = *(const UINT16 *)data[n]; data[n] += 2; break;
case 4: word = *(const UINT32 *)data[n]; data[n] += 4; break;
}
row[n] = j2ku_shift(offsets[n] + word, shifts[n]);
}
row[3] = 0xff;
row += 4;
}
}
}
static void
j2ku_sycc_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
const UINT8 *tiledata, Imaging im)
{
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
unsigned w = tileinfo->x1 - tileinfo->x0;
unsigned h = tileinfo->y1 - tileinfo->y0;
int shifts[3], offsets[3], csiz[3];
const UINT8 *cdata[3];
const UINT8 *cptr = tiledata;
unsigned n, x, y;
for (n = 0; n < 3; ++n) {
cdata[n] = cptr;
shifts[n] = 8 - in->comps[n].prec;
offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0;
csiz[n] = (in->comps[n].prec + 7) >> 3;
if (csiz[n] == 3)
csiz[n] = 4;
if (shifts[n] < 0)
offsets[n] += 1 << (-shifts[n] - 1);
cptr += csiz[n] * w * h;
}
for (y = 0; y < h; ++y) {
const UINT8 *data[3];
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
UINT8 *row_start = row;
for (n = 0; n < 3; ++n)
data[n] = &cdata[n][csiz[n] * y * w];
for (x = 0; x < w; ++x) {
for (n = 0; n < 3; ++n) {
UINT32 word = 0;
switch (csiz[n]) {
case 1: word = *data[n]++; break;
case 2: word = *(const UINT16 *)data[n]; data[n] += 2; break;
case 4: word = *(const UINT32 *)data[n]; data[n] += 4; break;
}
row[n] = j2ku_shift(offsets[n] + word, shifts[n]);
}
row[3] = 0xff;
row += 4;
}
ImagingConvertYCbCr2RGB(row_start, row_start, w);
}
}
static void
j2ku_srgba_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
const UINT8 *tiledata, Imaging im)
{
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
unsigned w = tileinfo->x1 - tileinfo->x0;
unsigned h = tileinfo->y1 - tileinfo->y0;
int shifts[4], offsets[4], csiz[4];
const UINT8 *cdata[4];
const UINT8 *cptr = tiledata;
unsigned n, x, y;
for (n = 0; n < 4; ++n) {
cdata[n] = cptr;
shifts[n] = 8 - in->comps[n].prec;
offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0;
csiz[n] = (in->comps[n].prec + 7) >> 3;
if (csiz[n] == 3)
csiz[n] = 4;
if (shifts[n] < 0)
offsets[n] += 1 << (-shifts[n] - 1);
cptr += csiz[n] * w * h;
}
for (y = 0; y < h; ++y) {
const UINT8 *data[4];
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
for (n = 0; n < 4; ++n)
data[n] = &cdata[n][csiz[n] * y * w];
for (x = 0; x < w; ++x) {
for (n = 0; n < 4; ++n) {
UINT32 word = 0;
switch (csiz[n]) {
case 1: word = *data[n]++; break;
case 2: word = *(const UINT16 *)data[n]; data[n] += 2; break;
case 4: word = *(const UINT32 *)data[n]; data[n] += 4; break;
}
row[n] = j2ku_shift(offsets[n] + word, shifts[n]);
}
row += 4;
}
}
}
static void
j2ku_sycca_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
const UINT8 *tiledata, Imaging im)
{
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
unsigned w = tileinfo->x1 - tileinfo->x0;
unsigned h = tileinfo->y1 - tileinfo->y0;
int shifts[4], offsets[4], csiz[4];
const UINT8 *cdata[4];
const UINT8 *cptr = tiledata;
unsigned n, x, y;
for (n = 0; n < 4; ++n) {
cdata[n] = cptr;
shifts[n] = 8 - in->comps[n].prec;
offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0;
csiz[n] = (in->comps[n].prec + 7) >> 3;
if (csiz[n] == 3)
csiz[n] = 4;
if (shifts[n] < 0)
offsets[n] += 1 << (-shifts[n] - 1);
cptr += csiz[n] * w * h;
}
for (y = 0; y < h; ++y) {
const UINT8 *data[4];
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
UINT8 *row_start = row;
for (n = 0; n < 4; ++n)
data[n] = &cdata[n][csiz[n] * y * w];
for (x = 0; x < w; ++x) {
for (n = 0; n < 4; ++n) {
UINT32 word = 0;
switch (csiz[n]) {
case 1: word = *data[n]++; break;
case 2: word = *(const UINT16 *)data[n]; data[n] += 2; break;
case 4: word = *(const UINT32 *)data[n]; data[n] += 4; break;
}
row[n] = j2ku_shift(offsets[n] + word, shifts[n]);
}
row += 4;
}
ImagingConvertYCbCr2RGB(row_start, row_start, w);
}
}
static const struct j2k_decode_unpacker j2k_unpackers[] = {
{ "L", OPJ_CLRSPC_GRAY, 1, j2ku_gray_l },
{ "LA", OPJ_CLRSPC_GRAY, 2, j2ku_graya_la },
{ "RGB", OPJ_CLRSPC_GRAY, 1, j2ku_gray_rgb },
{ "RGB", OPJ_CLRSPC_GRAY, 2, j2ku_gray_rgb },
{ "RGB", OPJ_CLRSPC_SRGB, 3, j2ku_srgb_rgb },
{ "RGB", OPJ_CLRSPC_SYCC, 3, j2ku_sycc_rgb },
{ "RGB", OPJ_CLRSPC_SRGB, 4, j2ku_srgb_rgb },
{ "RGB", OPJ_CLRSPC_SYCC, 4, j2ku_sycc_rgb },
{ "RGBA", OPJ_CLRSPC_GRAY, 1, j2ku_gray_rgb },
{ "RGBA", OPJ_CLRSPC_GRAY, 2, j2ku_graya_la },
{ "RGBA", OPJ_CLRSPC_SRGB, 3, j2ku_srgb_rgb },
{ "RGBA", OPJ_CLRSPC_SYCC, 3, j2ku_sycc_rgb },
{ "RGBA", OPJ_CLRSPC_SRGB, 4, j2ku_srgba_rgba },
{ "RGBA", OPJ_CLRSPC_SYCC, 4, j2ku_sycca_rgba },
};
/* -------------------------------------------------------------------- */
/* Decoder */
/* -------------------------------------------------------------------- */
enum {
J2K_STATE_START = 0,
J2K_STATE_DECODING = 1,
J2K_STATE_DONE = 2,
J2K_STATE_FAILED = 3,
};
static int
j2k_decode_entry(Imaging im, ImagingCodecState state,
ImagingIncrementalCodec decoder)
{
JPEG2KDECODESTATE *context = (JPEG2KDECODESTATE *) state->context;
opj_stream_t *stream = NULL;
opj_image_t *image = NULL;
opj_codec_t *codec = NULL;
opj_dparameters_t params;
OPJ_COLOR_SPACE color_space;
j2k_unpacker_t unpack = NULL;
size_t buffer_size = 0;
unsigned n;
stream = opj_stream_default_create(OPJ_TRUE);
if (!stream) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
}
opj_stream_set_read_function(stream, j2k_read);
opj_stream_set_skip_function(stream, j2k_skip);
opj_stream_set_user_data(stream, decoder);
/* Setup decompression context */
context->error_msg = NULL;
opj_set_default_decoder_parameters(&params);
params.cp_reduce = context->reduce;
params.cp_layer = context->layers;
codec = opj_create_decompress(context->format);
if (!codec) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
}
opj_set_error_handler(codec, j2k_error, context);
opj_setup_decoder(codec, &params);
if (!opj_read_header(stream, codec, &image)) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
}
/* Check that this image is something we can handle */
if (image->numcomps < 1 || image->numcomps > 4
|| image->color_space == OPJ_CLRSPC_UNKNOWN) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
}
for (n = 1; n < image->numcomps; ++n) {
/* Check that the sample frequency is uniform */
if (image->comps[0].dx != image->comps[n].dx
|| image->comps[0].dy != image->comps[n].dy) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
}
}
/*
Colorspace Number of components PIL mode
------------------------------------------------------
sRGB 3 RGB
sRGB 4 RGBA
gray 1 L or I
gray 2 LA
YCC 3 YCbCr
If colorspace is unspecified, we assume:
Number of components Colorspace
-----------------------------------------
1 gray
2 gray (+ alpha)
3 sRGB
4 sRGB (+ alpha)
*/
/* Find the correct unpacker */
color_space = image->color_space;
if (color_space == OPJ_CLRSPC_UNSPECIFIED) {
switch (image->numcomps) {
case 1: case 2: color_space = OPJ_CLRSPC_GRAY; break;
case 3: case 4: color_space = OPJ_CLRSPC_SRGB; break;
}
}
for (n = 0; n < sizeof(j2k_unpackers) / sizeof (j2k_unpackers[0]); ++n) {
if (color_space == j2k_unpackers[n].color_space
&& image->numcomps == j2k_unpackers[n].components
&& strcmp (im->mode, j2k_unpackers[n].mode) == 0) {
unpack = j2k_unpackers[n].unpacker;
break;
}
}
if (!unpack) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
}
/* Decode the image tile-by-tile; this means we only need use as much
memory as is required for one tile's worth of components. */
for (;;) {
JPEG2KTILEINFO tile_info;
OPJ_BOOL should_continue;
if (!opj_read_tile_header(codec,
stream,
&tile_info.tile_index,
&tile_info.data_size,
&tile_info.x0, &tile_info.y0,
&tile_info.x1, &tile_info.y1,
&tile_info.nb_comps,
&should_continue)) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
}
if (!should_continue)
break;
if (buffer_size < tile_info.data_size) {
UINT8 *new = realloc (state->buffer, tile_info.data_size);
if (!new) {
state->errcode = IMAGING_CODEC_MEMORY;
state->state = J2K_STATE_FAILED;
goto quick_exit;
}
state->buffer = new;
buffer_size = tile_info.data_size;
}
if (!opj_decode_tile_data(codec,
tile_info.tile_index,
(OPJ_BYTE *)state->buffer,
tile_info.data_size,
stream)) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
}
/* Check the tile bounds; if the tile is outside the image area,
or if it has a negative width or height (i.e. the coordinates are
swapped), bail. */
if (tile_info.x0 >= tile_info.x1
|| tile_info.y0 >= tile_info.y1
|| tile_info.x0 < image->x0
|| tile_info.y0 < image->y0
|| tile_info.x1 - image->x0 > im->xsize
|| tile_info.y1 - image->y0 > im->ysize) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
}
unpack(image, &tile_info, state->buffer, im);
}
if (!opj_end_decompress(codec, stream)) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
}
state->state = J2K_STATE_DONE;
state->errcode = IMAGING_CODEC_END;
quick_exit:
if (codec)
opj_destroy_codec(codec);
if (image)
opj_image_destroy(image);
if (stream)
opj_stream_destroy(stream);
return -1;
}
int
ImagingJpeg2KDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes)
{
JPEG2KDECODESTATE *context = (JPEG2KDECODESTATE *) state->context;
if (state->state == J2K_STATE_DONE || state->state == J2K_STATE_FAILED)
return -1;
if (state->state == J2K_STATE_START) {
context->decoder = ImagingIncrementalCodecCreate(j2k_decode_entry,
im, state,
INCREMENTAL_CODEC_READ,
INCREMENTAL_CODEC_NOT_SEEKABLE,
context->fd);
if (!context->decoder) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
return -1;
}
state->state = J2K_STATE_DECODING;
}
return ImagingIncrementalCodecPushBuffer(context->decoder, buf, bytes);
}
/* -------------------------------------------------------------------- */
/* Cleanup */
/* -------------------------------------------------------------------- */
int
ImagingJpeg2KDecodeCleanup(ImagingCodecState state) {
JPEG2KDECODESTATE *context = (JPEG2KDECODESTATE *)state->context;
if (context->error_msg)
free ((void *)context->error_msg);
if (context->decoder)
ImagingIncrementalCodecDestroy(context->decoder);
return -1;
}
const char *
ImagingJpeg2KVersion(void)
{
return opj_version();
}
#endif /* HAVE_OPENJPEG */
/*
* Local Variables:
* c-basic-offset: 4
* End:
*
*/

562
libImaging/Jpeg2KEncode.c Normal file
View File

@ -0,0 +1,562 @@
/*
* The Python Imaging Library.
* $Id$
*
* decoder for JPEG2000 image data.
*
* history:
* 2014-03-12 ajh Created
*
* Copyright (c) 2014 Coriolis Systems Limited
* Copyright (c) 2014 Alastair Houghton
*
* See the README file for details on usage and redistribution.
*/
#include "Imaging.h"
#ifdef HAVE_OPENJPEG
#include "Jpeg2K.h"
#define CINEMA_24_CS_LENGTH 1302083
#define CINEMA_48_CS_LENGTH 651041
#define COMP_24_CS_MAX_LENGTH 1041666
#define COMP_48_CS_MAX_LENGTH 520833
/* -------------------------------------------------------------------- */
/* Error handler */
/* -------------------------------------------------------------------- */
static void
j2k_error(const char *msg, void *client_data)
{
JPEG2KENCODESTATE *state = (JPEG2KENCODESTATE *) client_data;
free((void *)state->error_msg);
state->error_msg = strdup(msg);
}
/* -------------------------------------------------------------------- */
/* Buffer output stream */
/* -------------------------------------------------------------------- */
static OPJ_SIZE_T
j2k_write(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data)
{
ImagingIncrementalCodec encoder = (ImagingIncrementalCodec)p_user_data;
size_t len = ImagingIncrementalCodecWrite(encoder, p_buffer, p_nb_bytes);
return len ? len : (OPJ_SIZE_T)-1;
}
static OPJ_OFF_T
j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data)
{
ImagingIncrementalCodec encoder = (ImagingIncrementalCodec)p_user_data;
off_t pos = ImagingIncrementalCodecSkip(encoder, p_nb_bytes);
return pos ? pos : (OPJ_OFF_T)-1;
}
static OPJ_BOOL
j2k_seek(OPJ_OFF_T p_nb_bytes, void *p_user_data)
{
ImagingIncrementalCodec encoder = (ImagingIncrementalCodec)p_user_data;
off_t pos = ImagingIncrementalCodecSeek(encoder, p_nb_bytes);
return pos == p_nb_bytes;
}
/* -------------------------------------------------------------------- */
/* Encoder */
/* -------------------------------------------------------------------- */
typedef void (*j2k_pack_tile_t)(Imaging im, UINT8 *buf,
unsigned x0, unsigned y0,
unsigned w, unsigned h);
static void
j2k_pack_l(Imaging im, UINT8 *buf,
unsigned x0, unsigned y0, unsigned w, unsigned h)
{
UINT8 *ptr = buf;
unsigned x,y;
for (y = 0; y < h; ++y) {
UINT8 *data = (UINT8 *)(im->image[y + y0] + x0);
for (x = 0; x < w; ++x)
*ptr++ = *data++;
}
}
static void
j2k_pack_la(Imaging im, UINT8 *buf,
unsigned x0, unsigned y0, unsigned w, unsigned h)
{
UINT8 *ptr = buf;
UINT8 *ptra = buf + w * h;
unsigned x,y;
for (y = 0; y < h; ++y) {
UINT8 *data = (UINT8 *)(im->image[y + y0] + 4 * x0);
for (x = 0; x < w; ++x) {
*ptr++ = data[0];
*ptra++ = data[3];
data += 4;
}
}
}
static void
j2k_pack_rgb(Imaging im, UINT8 *buf,
unsigned x0, unsigned y0, unsigned w, unsigned h)
{
UINT8 *pr = buf;
UINT8 *pg = pr + w * h;
UINT8 *pb = pg + w * h;
unsigned x,y;
for (y = 0; y < h; ++y) {
UINT8 *data = (UINT8 *)(im->image[y + y0] + 4 * x0);
for (x = 0; x < w; ++x) {
*pr++ = data[0];
*pg++ = data[1];
*pb++ = data[2];
data += 4;
}
}
}
static void
j2k_pack_rgba(Imaging im, UINT8 *buf,
unsigned x0, unsigned y0, unsigned w, unsigned h)
{
UINT8 *pr = buf;
UINT8 *pg = pr + w * h;
UINT8 *pb = pg + w * h;
UINT8 *pa = pb + w * h;
unsigned x,y;
for (y = 0; y < h; ++y) {
UINT8 *data = (UINT8 *)(im->image[y + y0] + 4 * x0);
for (x = 0; x < w; ++x) {
*pr++ = *data++;
*pg++ = *data++;
*pb++ = *data++;
*pa++ = *data++;
}
}
}
enum {
J2K_STATE_START = 0,
J2K_STATE_ENCODING = 1,
J2K_STATE_DONE = 2,
J2K_STATE_FAILED = 3,
};
static void
j2k_set_cinema_params(Imaging im, int components, opj_cparameters_t *params)
{
float rate;
unsigned n;
/* These settings have been copied from opj_compress in the OpenJPEG
sources. */
params->tile_size_on = OPJ_FALSE;
params->cp_tdx = params->cp_tdy = 1;
params->tp_flag = 'C';
params->tp_on = 1;
params->cp_tx0 = params->cp_ty0 = 0;
params->image_offset_x0 = params->image_offset_y0 = 0;
params->cblockw_init = 32;
params->cblockh_init = 32;
params->csty |= 0x01;
params->prog_order = OPJ_CPRL;
params->roi_compno = -1;
params->subsampling_dx = params->subsampling_dy = 1;
params->irreversible = 1;
if (params->cp_cinema == OPJ_CINEMA4K_24) {
float max_rate = ((float)(components * im->xsize * im->ysize * 8)
/ (CINEMA_24_CS_LENGTH * 8));
params->POC[0].tile = 1;
params->POC[0].resno0 = 0;
params->POC[0].compno0 = 0;
params->POC[0].layno1 = 1;
params->POC[0].resno1 = params->numresolution - 1;
params->POC[0].compno1 = 3;
params->POC[0].prg1 = OPJ_CPRL;
params->POC[1].tile = 1;
params->POC[1].resno0 = 0;
params->POC[1].compno0 = 0;
params->POC[1].layno1 = 1;
params->POC[1].resno1 = params->numresolution - 1;
params->POC[1].compno1 = 3;
params->POC[1].prg1 = OPJ_CPRL;
params->numpocs = 2;
for (n = 0; n < params->tcp_numlayers; ++n) {
rate = 0;
if (params->tcp_rates[0] == 0) {
params->tcp_rates[n] = max_rate;
} else {
rate = ((float)(components * im->xsize * im->ysize * 8)
/ (params->tcp_rates[n] * 8));
if (rate > CINEMA_24_CS_LENGTH)
params->tcp_rates[n] = max_rate;
}
}
params->max_comp_size = COMP_24_CS_MAX_LENGTH;
} else {
float max_rate = ((float)(components * im->xsize * im->ysize * 8)
/ (CINEMA_48_CS_LENGTH * 8));
for (n = 0; n < params->tcp_numlayers; ++n) {
rate = 0;
if (params->tcp_rates[0] == 0) {
params->tcp_rates[n] = max_rate;
} else {
rate = ((float)(components * im->xsize * im->ysize * 8)
/ (params->tcp_rates[n] * 8));
if (rate > CINEMA_48_CS_LENGTH)
params->tcp_rates[n] = max_rate;
}
}
params->max_comp_size = COMP_48_CS_MAX_LENGTH;
}
}
static int
j2k_encode_entry(Imaging im, ImagingCodecState state,
ImagingIncrementalCodec encoder)
{
JPEG2KENCODESTATE *context = (JPEG2KENCODESTATE *)state->context;
opj_stream_t *stream = NULL;
opj_image_t *image = NULL;
opj_codec_t *codec = NULL;
opj_cparameters_t params;
unsigned components;
OPJ_COLOR_SPACE color_space;
opj_image_cmptparm_t image_params[4];
unsigned xsiz, ysiz;
unsigned tile_width, tile_height;
unsigned tiles_x, tiles_y, num_tiles;
unsigned x, y, tile_ndx;
unsigned n;
j2k_pack_tile_t pack;
int ret = -1;
stream = opj_stream_default_create(OPJ_FALSE);
if (!stream) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
}
opj_stream_set_write_function(stream, j2k_write);
opj_stream_set_skip_function(stream, j2k_skip);
opj_stream_set_seek_function(stream, j2k_seek);
opj_stream_set_user_data(stream, encoder);
/* Setup an opj_image */
if (strcmp (im->mode, "L") == 0) {
components = 1;
color_space = OPJ_CLRSPC_GRAY;
pack = j2k_pack_l;
} else if (strcmp (im->mode, "LA") == 0) {
components = 2;
color_space = OPJ_CLRSPC_GRAY;
pack = j2k_pack_la;
} else if (strcmp (im->mode, "RGB") == 0) {
components = 3;
color_space = OPJ_CLRSPC_SRGB;
pack = j2k_pack_rgb;
} else if (strcmp (im->mode, "YCbCr") == 0) {
components = 3;
color_space = OPJ_CLRSPC_SYCC;
pack = j2k_pack_rgb;
} else if (strcmp (im->mode, "RGBA") == 0) {
components = 4;
color_space = OPJ_CLRSPC_SRGB;
pack = j2k_pack_rgba;
} else {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
}
for (n = 0; n < components; ++n) {
image_params[n].dx = image_params[n].dy = 1;
image_params[n].w = im->xsize;
image_params[n].h = im->ysize;
image_params[n].x0 = image_params[n].y0 = 0;
image_params[n].prec = 8;
image_params[n].bpp = 8;
image_params[n].sgnd = 0;
}
image = opj_image_create(components, image_params, color_space);
/* Setup compression context */
context->error_msg = NULL;
opj_set_default_encoder_parameters(&params);
params.image_offset_x0 = context->offset_x;
params.image_offset_y0 = context->offset_y;
if (context->tile_size_x && context->tile_size_y) {
params.tile_size_on = OPJ_TRUE;
params.cp_tx0 = context->tile_offset_x;
params.cp_ty0 = context->tile_offset_y;
params.cp_tdx = context->tile_size_x;
params.cp_tdy = context->tile_size_y;
tile_width = params.cp_tdx;
tile_height = params.cp_tdy;
} else {
params.cp_tx0 = 0;
params.cp_ty0 = 0;
params.cp_tdx = 1;
params.cp_tdy = 1;
tile_width = im->xsize;
tile_height = im->ysize;
}
if (context->quality_layers && PySequence_Check(context->quality_layers)) {
Py_ssize_t len = PySequence_Length(context->quality_layers);
Py_ssize_t n;
float *pq;
if (len) {
if (len > sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0]))
len = sizeof(params.tcp_rates)/sizeof(params.tcp_rates[0]);
params.tcp_numlayers = (int)len;
if (context->quality_is_in_db) {
params.cp_disto_alloc = params.cp_fixed_alloc = 0;
params.cp_fixed_quality = 1;
pq = params.tcp_distoratio;
} else {
params.cp_disto_alloc = 1;
params.cp_fixed_alloc = params.cp_fixed_quality = 0;
pq = params.tcp_rates;
}
for (n = 0; n < len; ++n) {
PyObject *obj = PySequence_ITEM(context->quality_layers, n);
pq[n] = PyFloat_AsDouble(obj);
}
}
} else {
params.tcp_numlayers = 1;
params.tcp_rates[0] = 0;
params.cp_disto_alloc = 1;
}
if (context->num_resolutions)
params.numresolution = context->num_resolutions;
if (context->cblk_width >= 4 && context->cblk_width <= 1024
&& context->cblk_height >= 4 && context->cblk_height <= 1024
&& context->cblk_width * context->cblk_height <= 4096) {
params.cblockw_init = context->cblk_width;
params.cblockh_init = context->cblk_height;
}
if (context->precinct_width >= 4 && context->precinct_height >= 4
&& context->precinct_width >= context->cblk_width
&& context->precinct_height > context->cblk_height) {
params.prcw_init[0] = context->precinct_width;
params.prch_init[0] = context->precinct_height;
params.res_spec = 1;
params.csty |= 0x01;
}
params.irreversible = context->irreversible;
params.prog_order = context->progression;
params.cp_cinema = context->cinema_mode;
switch (params.cp_cinema) {
case OPJ_OFF:
params.cp_rsiz = OPJ_STD_RSIZ;
break;
case OPJ_CINEMA2K_24:
case OPJ_CINEMA2K_48:
params.cp_rsiz = OPJ_CINEMA2K;
if (params.numresolution > 6)
params.numresolution = 6;
break;
case OPJ_CINEMA4K_24:
params.cp_rsiz = OPJ_CINEMA4K;
if (params.numresolution > 7)
params.numresolution = 7;
break;
}
if (context->cinema_mode != OPJ_OFF)
j2k_set_cinema_params(im, components, &params);
/* Set up the reference grid in the image */
image->x0 = params.image_offset_x0;
image->y0 = params.image_offset_y0;
image->x1 = xsiz = im->xsize + params.image_offset_x0;
image->y1 = ysiz = im->ysize + params.image_offset_y0;
/* Create the compressor */
codec = opj_create_compress(context->format);
if (!codec) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
}
opj_set_error_handler(codec, j2k_error, context);
opj_setup_encoder(codec, &params, image);
/* Start encoding */
if (!opj_start_compress(codec, image, stream)) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
}
/* Write each tile */
tiles_x = (im->xsize + (params.image_offset_x0 - params.cp_tx0)
+ tile_width - 1) / tile_width;
tiles_y = (im->ysize + (params.image_offset_y0 - params.cp_ty0)
+ tile_height - 1) / tile_height;
num_tiles = tiles_x * tiles_y;
state->buffer = malloc (tile_width * tile_height * components);
tile_ndx = 0;
for (y = 0; y < tiles_y; ++y) {
unsigned ty0 = params.cp_ty0 + y * tile_height;
unsigned ty1 = ty0 + tile_height;
unsigned pixy, pixh;
if (ty0 < params.image_offset_y0)
ty0 = params.image_offset_y0;
if (ty1 > ysiz)
ty1 = ysiz;
pixy = ty0 - params.image_offset_y0;
pixh = ty1 - ty0;
for (x = 0; x < tiles_x; ++x) {
unsigned tx0 = params.cp_tx0 + x * tile_width;
unsigned tx1 = tx0 + tile_width;
unsigned pixx, pixw;
unsigned data_size;
if (tx0 < params.image_offset_x0)
tx0 = params.image_offset_x0;
if (tx1 > xsiz)
tx1 = xsiz;
pixx = tx0 - params.image_offset_x0;
pixw = tx1 - tx0;
pack(im, state->buffer, pixx, pixy, pixw, pixh);
data_size = pixw * pixh * components;
if (!opj_write_tile(codec, tile_ndx++, state->buffer,
data_size, stream)) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
}
}
}
if (!opj_end_compress(codec, stream)) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
}
state->errcode = IMAGING_CODEC_END;
state->state = J2K_STATE_DONE;
ret = (int)ImagingIncrementalCodecBytesInBuffer(encoder);
quick_exit:
if (codec)
opj_destroy_codec(codec);
if (image)
opj_image_destroy(image);
if (stream)
opj_stream_destroy(stream);
return ret;
}
int
ImagingJpeg2KEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes)
{
JPEG2KENCODESTATE *context = (JPEG2KENCODESTATE *)state->context;
if (state->state == J2K_STATE_FAILED)
return -1;
if (state->state == J2K_STATE_START) {
int seekable = (context->format != OPJ_CODEC_J2K
? INCREMENTAL_CODEC_SEEKABLE
: INCREMENTAL_CODEC_NOT_SEEKABLE);
context->encoder = ImagingIncrementalCodecCreate(j2k_encode_entry,
im, state,
INCREMENTAL_CODEC_WRITE,
seekable,
context->fd);
if (!context->encoder) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
return -1;
}
state->state = J2K_STATE_ENCODING;
}
return ImagingIncrementalCodecPushBuffer(context->encoder, buf, bytes);
}
/* -------------------------------------------------------------------- */
/* Cleanup */
/* -------------------------------------------------------------------- */
int
ImagingJpeg2KEncodeCleanup(ImagingCodecState state) {
JPEG2KENCODESTATE *context = (JPEG2KENCODESTATE *)state->context;
if (context->quality_layers)
Py_DECREF(context->quality_layers);
if (context->error_msg)
free ((void *)context->error_msg);
if (context->encoder)
ImagingIncrementalCodecDestroy(context->encoder);
return -1;
}
#endif /* HAVE_OPENJPEG */
/*
* Local Variables:
* c-basic-offset: 4
* End:
*
*/

View File

@ -192,6 +192,7 @@ if __name__ == "__main__":
check_module("PIL CORE", "PIL._imaging")
check_module("TKINTER", "PIL._imagingtk")
check_codec("JPEG", "jpeg")
check_codec("JPEG 2000", "jpeg2k")
check_codec("ZLIB (PNG/ZIP)", "zip")
check_codec("LIBTIFF", "libtiff")
check_module("FREETYPE2", "PIL._imagingft")

View File

@ -33,7 +33,8 @@ _LIB_IMAGING = (
"QuantHeap", "PcdDecode", "PcxDecode", "PcxEncode", "Point",
"RankFilter", "RawDecode", "RawEncode", "Storage", "SunRleDecode",
"TgaRleDecode", "Unpack", "UnpackYCC", "UnsharpMask", "XbmDecode",
"XbmEncode", "ZipDecode", "ZipEncode", "TiffDecode")
"XbmEncode", "ZipDecode", "ZipEncode", "TiffDecode", "Incremental",
"Jpeg2KDecode", "Jpeg2KEncode")
def _add_directory(path, dir, where=None):
@ -88,6 +89,7 @@ NAME = 'Pillow'
VERSION = '2.3.0'
TCL_ROOT = None
JPEG_ROOT = None
JPEG2K_ROOT = None
ZLIB_ROOT = None
TIFF_ROOT = None
FREETYPE_ROOT = None
@ -98,6 +100,7 @@ class pil_build_ext(build_ext):
class feature:
zlib = jpeg = tiff = freetype = tcl = tk = lcms = webp = webpmux = None
jpeg2000 = None
required = []
def require(self, feat):
@ -150,7 +153,7 @@ class pil_build_ext(build_ext):
#
# add configured kits
for root in (TCL_ROOT, JPEG_ROOT, TIFF_ROOT, ZLIB_ROOT,
for root in (TCL_ROOT, JPEG_ROOT, JPEG2K_ROOT, TIFF_ROOT, ZLIB_ROOT,
FREETYPE_ROOT, LCMS_ROOT):
if isinstance(root, type(())):
lib_root, include_root = root
@ -321,6 +324,16 @@ class pil_build_ext(build_ext):
_add_directory(library_dirs, "/usr/lib")
_add_directory(include_dirs, "/usr/include")
# on Windows, look for the OpenJPEG libraries in the location that
# the official installed puts them
if sys.platform == "win32":
_add_directory(library_dirs,
os.path.join(os.environ.get("ProgramFiles", ""),
"OpenJPEG 2.0", "lib"))
_add_directory(include_dirs,
os.path.join(os.environ.get("ProgramFiles", ""),
"OpenJPEG 2.0", "include"))
#
# insert new dirs *before* default libs, to avoid conflicts
# between Python PYD stub libs and real libraries
@ -349,6 +362,11 @@ class pil_build_ext(build_ext):
_find_library_file(self, "libjpeg")):
feature.jpeg = "libjpeg" # alternative name
if feature.want('jpeg2000'):
if _find_include_file(self, "openjpeg-2.0/openjpeg.h"):
if _find_library_file(self, "openjp2"):
feature.jpeg2000 = "openjp2"
if feature.want('tiff'):
if _find_library_file(self, "tiff"):
feature.tiff = "tiff"
@ -430,6 +448,9 @@ class pil_build_ext(build_ext):
if feature.jpeg:
libs.append(feature.jpeg)
defs.append(("HAVE_LIBJPEG", None))
if feature.jpeg2000:
libs.append(feature.jpeg2000)
defs.append(("HAVE_OPENJPEG", None))
if feature.zlib:
libs.append(feature.zlib)
defs.append(("HAVE_LIBZ", None))
@ -537,6 +558,7 @@ class pil_build_ext(build_ext):
options = [
(feature.tcl and feature.tk, "TKINTER"),
(feature.jpeg, "JPEG"),
(feature.jpeg2000, "OPENJPEG (JPEG2000)"),
(feature.zlib, "ZLIB (PNG/ZIP)"),
(feature.tiff, "LIBTIFF"),
(feature.freetype, "FREETYPE2"),