From d6b8f0f66655c01baa2176aeb38d2907099ceb99 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Wed, 12 Mar 2014 16:25:59 +0000 Subject: [PATCH 01/15] Added a JPEG 2000 decoder based on OpenJPEG. --- PIL/ImageFile.py | 2 +- PIL/Jpeg2KImagePlugin.py | 189 +++++++++++++++ PIL/__init__.py | 1 + _imaging.c | 4 + decode.c | 76 +++++- libImaging/Imaging.h | 18 ++ libImaging/Incremental.c | 366 +++++++++++++++++++++++++++++ libImaging/Jpeg2K.h | 44 ++++ libImaging/Jpeg2KDecode.c | 475 ++++++++++++++++++++++++++++++++++++++ setup.py | 13 +- 10 files changed, 1185 insertions(+), 3 deletions(-) create mode 100644 PIL/Jpeg2KImagePlugin.py create mode 100644 libImaging/Incremental.c create mode 100644 libImaging/Jpeg2K.h create mode 100644 libImaging/Jpeg2KDecode.c diff --git a/PIL/ImageFile.py b/PIL/ImageFile.py index a63fe757e..501e16b00 100644 --- a/PIL/ImageFile.py +++ b/PIL/ImageFile.py @@ -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 diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py new file mode 100644 index 000000000..9acc71c4e --- /dev/null +++ b/PIL/Jpeg2KImagePlugin.py @@ -0,0 +1,189 @@ +# +# 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" + print 'Reading size/mode' + try: + self.size, self.mode = _parse_codestream(self.fp) + except Exception as e: + print e + print '%r, %r' % (self.size, self.mode) + 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 + + self.tile = [('jpeg2k', (0, 0) + self.size, 0, + (self.codec, self.reduce, self.layers))] + + def load(self): + if self.reduce: + power = 1 << self.reduce + self.size = (self.size[0] / power, self.size[1] / power) + + 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') + +# ------------------------------------------------------------ +# Registry stuff + +Image.register_open("JPEG2000", Jpeg2KImageFile, _accept) + +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") diff --git a/PIL/__init__.py b/PIL/__init__.py index 18bd42a5f..b83220097 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -33,6 +33,7 @@ _plugins = ['ArgImagePlugin', 'ImtImagePlugin', 'IptcImagePlugin', 'JpegImagePlugin', + 'Jpeg2KImagePlugin', 'McIdasImagePlugin', 'MicImagePlugin', 'MpegImagePlugin', diff --git a/_imaging.c b/_imaging.c index 078961da4..84b25219b 100644 --- a/_imaging.c +++ b/_imaging.c @@ -3283,6 +3283,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); @@ -3351,6 +3352,9 @@ 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}, #endif {"tiff_lzw_decoder", (PyCFunction)PyImaging_TiffLzwDecoderNew, 1}, #ifdef HAVE_LIBTIFF diff --git a/decode.c b/decode.c index f3ac60e51..359b99695 100644 --- a/decode.c +++ b/decode.c @@ -52,6 +52,7 @@ typedef struct { struct ImagingCodecStateInstance state; Imaging im; PyObject* lock; + int handles_eof; } ImagingDecoderObject; static PyTypeObject ImagingDecoderType; @@ -194,6 +195,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 +208,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 +246,7 @@ static PyTypeObject ImagingDecoderType = { 0, /*tp_iternext*/ methods, /*tp_methods*/ 0, /*tp_members*/ - 0, /*tp_getset*/ + getseters, /*tp_getset*/ }; /* -------------------------------------------------------------------- */ @@ -762,3 +776,63 @@ PyImaging_JpegDecoderNew(PyObject* self, PyObject* args) return (PyObject*) decoder; } #endif + +/* -------------------------------------------------------------------- */ +/* JPEG2000 */ +/* -------------------------------------------------------------------- */ + +#ifdef HAVE_OPENJPEG + +/* We better define this decoder last in this file, so the following + undef's won't mess things up for the Imaging library proper. */ +#undef UINT8 +#undef UINT16 +#undef UINT32 +#undef INT8 +#undef INT16 +#undef INT32 + +#include "Jpeg2K.h" + +PyObject* +PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args) +{ + ImagingDecoderObject* decoder; + JPEG2KSTATE *context; + + char* mode; + char* format; + OPJ_CODEC_FORMAT codec_format; + int reduce = 0; + int layers = 0; + if (!PyArg_ParseTuple(args, "ss|ii", &mode, &format, + &reduce, &layers)) + 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(JPEG2KSTATE)); + if (decoder == NULL) + return NULL; + + decoder->handles_eof = 1; + decoder->decode = ImagingJpeg2KDecode; + decoder->cleanup = ImagingJpeg2KDecodeCleanup; + + context = (JPEG2KSTATE *)decoder->state.context; + + strncpy(context->mode, mode, 8); + context->format = codec_format; + context->reduce = reduce; + context->layers = layers; + + return (PyObject*) decoder; +} +#endif /* HAVE_OPENJPEG */ diff --git a/libImaging/Imaging.h b/libImaging/Imaging.h index b45dcbe23..7a7eb9c66 100644 --- a/libImaging/Imaging.h +++ b/libImaging/Imaging.h @@ -424,6 +424,11 @@ 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); +#endif extern int ImagingLzwDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes); #ifdef HAVE_LIBTIFF @@ -497,6 +502,19 @@ struct ImagingCodecStateInstance { void *context; }; +/* Incremental decoding support */ +typedef struct ImagingIncrementalDecoderStruct *ImagingIncrementalDecoder; + +typedef int (*ImagingIncrementalDecoderEntry)(Imaging im, + ImagingCodecState state, + ImagingIncrementalDecoder decoder); + +extern ImagingIncrementalDecoder ImagingIncrementalDecoderCreate(ImagingIncrementalDecoderEntry decoder_entry, Imaging im, ImagingCodecState state); +extern void ImagingIncrementalDecoderDestroy(ImagingIncrementalDecoder decoder); +extern int ImagingIncrementalDecodeData(ImagingIncrementalDecoder decoder, UINT8 *buf, int bytes); +size_t ImagingIncrementalDecoderRead(ImagingIncrementalDecoder decoder, void *buffer, size_t bytes); +off_t ImagingIncrementalDecoderSkip(ImagingIncrementalDecoder decoder, off_t bytes); + /* Errcodes */ #define IMAGING_CODEC_END 1 #define IMAGING_CODEC_OVERRUN -1 diff --git a/libImaging/Incremental.c b/libImaging/Incremental.c new file mode 100644 index 000000000..0e81af7e8 --- /dev/null +++ b/libImaging/Incremental.c @@ -0,0 +1,366 @@ +/* + * 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 + ImagingIncrementalDecoderRead() call runs short on data, it suspends the + decoding thread and wakes the main thread. Conversely, the + ImagingIncrementalDecodeData() 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. */ + +#ifdef _WIN32 +#include +#include +#else +#include +#endif + +struct ImagingIncrementalStreamStruct { + UINT8 *buffer; + UINT8 *ptr; + UINT8 *end; +}; + +struct ImagingIncrementalDecoderStruct { +#ifdef _WIN32 + HANDLE hDecodeEvent; + HANDLE hDataEvent; + HANDLE hThread; +#else + pthread_mutex_t start_mutex; + pthread_cond_t start_cond; + pthread_mutex_t decode_mutex; + pthread_cond_t decode_cond; + pthread_mutex_t data_mutex; + pthread_cond_t data_cond; + pthread_t thread; +#endif + ImagingIncrementalDecoderEntry entry; + Imaging im; + ImagingCodecState state; + struct { + UINT8 *buffer; + UINT8 *ptr; + UINT8 *end; + } stream; + int started; + int result; +}; + +#if _WIN32 +static void __stdcall +incremental_thread(void *ptr) +{ + ImagingIncrementalDecoder decoder = (ImagingIncrementalDecoder)ptr; + + decoder->result = decoder->entry(decoder->im, decoder->state, decoder); + + SetEvent(decoder->hDecodeEvent); +} +#else +static void * +incremental_thread(void *ptr) +{ + ImagingIncrementalDecoder decoder = (ImagingIncrementalDecoder)ptr; + + decoder->result = decoder->entry(decoder->im, decoder->state, decoder); + + pthread_cond_signal(&decoder->decode_cond); + + return NULL; +} +#endif + +/** + * Create a new incremental decoder */ +ImagingIncrementalDecoder +ImagingIncrementalDecoderCreate(ImagingIncrementalDecoderEntry decoder_entry, + Imaging im, + ImagingCodecState state) +{ + ImagingIncrementalDecoder decoder = (ImagingIncrementalDecoder)malloc(sizeof(struct ImagingIncrementalDecoderStruct)); + + decoder->entry = decoder_entry; + decoder->im = im; + decoder->state = state; + decoder->result = 0; + decoder->stream.buffer = decoder->stream.ptr = decoder->stream.end = NULL; + decoder->started = 0; + + /* System specific set-up */ +#if _WIN32 + decoder->hDecodeEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + + if (!decoder->hDecodeEvent) { + free(decoder); + return NULL; + } + + decoder->hDataEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + + if (!decoder->hDataEvent) { + CloseHandle(decoder->hDecodeEvent); + free(decoder); + return NULL; + } + + decoder->hThread = _beginthreadex(NULL, 0, incremental_thread, decoder, + CREATE_SUSPENDED, NULL); + + if (!decoder->hThread) { + CloseHandle(decoder->hDecodeEvent); + CloseHandle(decoder->hDataEvent); + free(decoder); + return NULL; + } +#else + if (pthread_mutex_init(&decoder->start_mutex, NULL)) { + free (decoder); + return NULL; + } + + if (pthread_mutex_init(&decoder->decode_mutex, NULL)) { + pthread_mutex_destroy(&decoder->start_mutex); + free(decoder); + return NULL; + } + + if (pthread_mutex_init(&decoder->data_mutex, NULL)) { + pthread_mutex_destroy(&decoder->start_mutex); + pthread_mutex_destroy(&decoder->decode_mutex); + free(decoder); + return NULL; + } + + if (pthread_cond_init(&decoder->start_cond, NULL)) { + pthread_mutex_destroy(&decoder->start_mutex); + pthread_mutex_destroy(&decoder->decode_mutex); + pthread_mutex_destroy(&decoder->data_mutex); + free(decoder); + return NULL; + } + + if (pthread_cond_init(&decoder->decode_cond, NULL)) { + pthread_mutex_destroy(&decoder->start_mutex); + pthread_mutex_destroy(&decoder->decode_mutex); + pthread_mutex_destroy(&decoder->data_mutex); + pthread_cond_destroy(&decoder->start_cond); + free(decoder); + return NULL; + } + + if (pthread_cond_init(&decoder->data_cond, NULL)) { + pthread_mutex_destroy(&decoder->start_mutex); + pthread_mutex_destroy(&decoder->decode_mutex); + pthread_mutex_destroy(&decoder->data_mutex); + pthread_cond_destroy(&decoder->start_cond); + pthread_cond_destroy(&decoder->decode_cond); + free(decoder); + return NULL; + } + + if (pthread_create(&decoder->thread, NULL, incremental_thread, decoder)) { + pthread_mutex_destroy(&decoder->start_mutex); + pthread_mutex_destroy(&decoder->decode_mutex); + pthread_mutex_destroy(&decoder->data_mutex); + pthread_cond_destroy(&decoder->start_cond); + pthread_cond_destroy(&decoder->decode_cond); + pthread_cond_destroy(&decoder->data_cond); + free(decoder); + return NULL; + } +#endif + + return decoder; +} + +/** + * Destroy an incremental decoder */ +void +ImagingIncrementalDecoderDestroy(ImagingIncrementalDecoder decoder) +{ + if (!decoder->started) { +#ifdef _WIN32 + ResumeThread(decoder->hThread); +#else + pthread_cond_signal(&decoder->start_cond); +#endif + decoder->started = 1; + } + +#ifndef _WIN32 + pthread_mutex_lock(&decoder->data_mutex); +#endif + + decoder->stream.buffer = decoder->stream.ptr = decoder->stream.end = NULL; + +#ifdef _WIN32 + SetEvent(decoder->hDataEvent); + + WaitForSingleObject(decoder->hThread, INFINITE); + + CloseHandle(decoder->hThread); + CloseHandle(decoder->hDecodeEvent); + CloseHandle(decoder->hDataEvent); +#else + pthread_cond_signal(&decoder->data_cond); + pthread_mutex_unlock(&decoder->data_mutex); + + pthread_join(decoder->thread, NULL); + + pthread_mutex_destroy(&decoder->start_mutex); + pthread_mutex_destroy(&decoder->decode_mutex); + pthread_mutex_destroy(&decoder->data_mutex); + pthread_cond_destroy(&decoder->start_cond); + pthread_cond_destroy(&decoder->decode_cond); + pthread_cond_destroy(&decoder->data_cond); +#endif + free (decoder); +} + +/** + * Decode data using an incremental decoder */ +int +ImagingIncrementalDecodeData(ImagingIncrementalDecoder decoder, + UINT8 *buf, int bytes) +{ + if (!decoder->started) { +#ifdef _WIN32 + ResumeThread(decoder->hThread); +#else + pthread_cond_signal(&decoder->start_cond); +#endif + decoder->started = 1; + } + +#ifndef _WIN32 + pthread_mutex_lock(&decoder->data_mutex); +#endif + + decoder->stream.buffer = decoder->stream.ptr = buf; + decoder->stream.end = buf + bytes; + +#ifdef _WIN32 + SetEvent(decoder->hDataEvent); + WaitForSingleObject(decoder->hDecodeEvent); +#else + pthread_cond_signal(&decoder->data_cond); + pthread_mutex_unlock(&decoder->data_mutex); + + pthread_mutex_lock(&decoder->decode_mutex); + pthread_cond_wait(&decoder->decode_cond, &decoder->decode_mutex); + pthread_mutex_unlock(&decoder->decode_mutex); +#endif + + return decoder->result; +} + +size_t +ImagingIncrementalDecoderRead(ImagingIncrementalDecoder decoder, + void *buffer, size_t bytes) +{ + UINT8 *ptr = (UINT8 *)buffer; + size_t done = 0; + + pthread_mutex_lock(&decoder->data_mutex); + while (bytes) { + size_t todo = bytes; + size_t remaining = decoder->stream.end - decoder->stream.ptr; + + if (!remaining) { +#ifndef _WIN32 + pthread_mutex_lock(&decoder->decode_mutex); +#endif + decoder->result = (int)(decoder->stream.ptr - decoder->stream.buffer); +#if _WIN32 + SetEvent(decoder->hDecodeEvent); + WaitForSingleObject(decoder->hDataEvent); +#else + pthread_cond_signal(&decoder->decode_cond); + pthread_mutex_unlock(&decoder->decode_mutex); + pthread_cond_wait(&decoder->data_cond, &decoder->data_mutex); +#endif + + remaining = decoder->stream.end - decoder->stream.ptr; + } + if (todo > remaining) + todo = remaining; + + if (!todo) + break; + + memcpy (ptr, decoder->stream.ptr, todo); + decoder->stream.ptr += todo; + bytes -= todo; + done += todo; + ptr += todo; + } + pthread_mutex_unlock(&decoder->data_mutex); + + return done; +} + +off_t +ImagingIncrementalDecoderSkip(ImagingIncrementalDecoder decoder, + off_t bytes) +{ + off_t done = 0; + + while (bytes) { + off_t todo = bytes; + off_t remaining = decoder->stream.end - decoder->stream.ptr; + + if (!remaining) { + decoder->result = (int)(decoder->stream.ptr - decoder->stream.buffer); +#if _WIN32 + SetEvent(decoder->hDecodeEvent); + WaitForSingleObject(decoder->hDataEvent); +#else + pthread_cond_signal(&decoder->decode_cond); + pthread_mutex_lock(&decoder->data_mutex); + pthread_cond_wait(&decoder->data_cond, &decoder->data_mutex); +#endif + + remaining = decoder->stream.end - decoder->stream.ptr; + } + if (todo > remaining) + todo = remaining; + + if (!todo) + break; + + decoder->stream.ptr += todo; + bytes -= todo; + done += todo; + } + + return done; +} + diff --git a/libImaging/Jpeg2K.h b/libImaging/Jpeg2K.h new file mode 100644 index 000000000..269357296 --- /dev/null +++ b/libImaging/Jpeg2K.h @@ -0,0 +1,44 @@ +/* + * 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 + +/* -------------------------------------------------------------------- */ +/* Decoder */ + +typedef struct { + /* CONFIGURATION */ + + /* Output mode */ + char mode[8]; + + /* 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; + + ImagingIncrementalDecoder decoder; + + opj_stream_t *stream; +} JPEG2KSTATE; + +/* + * Local Variables: + * c-basic-offset: 4 + * End: + * + */ diff --git a/libImaging/Jpeg2KDecode.c b/libImaging/Jpeg2KDecode.c new file mode 100644 index 000000000..e050c5216 --- /dev/null +++ b/libImaging/Jpeg2KDecode.c @@ -0,0 +1,475 @@ +/* + * 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 +#include "Jpeg2K.h" + +/* -------------------------------------------------------------------- */ +/* Error handler */ +/* -------------------------------------------------------------------- */ + +static void +j2k_error(const char *msg, void *client_data) +{ + JPEG2KSTATE *state = (JPEG2KSTATE *) 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) +{ + ImagingIncrementalDecoder decoder = (ImagingIncrementalDecoder)p_user_data; + + return ImagingIncrementalDecoderRead(decoder, p_buffer, p_nb_bytes); +} + +static OPJ_SIZE_T +j2k_write(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) +{ + return OPJ_FALSE; +} + +static OPJ_OFF_T +j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data) +{ + ImagingIncrementalDecoder decoder = (ImagingIncrementalDecoder)p_user_data; + + return ImagingIncrementalDecoderSkip(decoder, p_nb_bytes); +} + +static OPJ_BOOL +j2k_seek(OPJ_OFF_T p_nb_bytes, void *p_user_data) +{ + // We *deliberately* don't implement this + return OPJ_FALSE; +} + +/* -------------------------------------------------------------------- */ +/* Unpackers */ +/* -------------------------------------------------------------------- */ + +typedef void (*j2k_unpacker_t)(opj_image_t *in, 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, Imaging im) +{ + unsigned x0 = in->comps[0].x0, y0 = in->comps[0].y0; + unsigned w = in->comps[0].w, h = in->comps[0].h; + int shift = 8 - in->comps[0].prec; + int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0; + unsigned x, y; + + if (shift < 0) + offset += 1 << (-shift - 1); + + for (y = 0; y < h; ++y) { + OPJ_INT32 *data = &in->comps[0].data[y * w]; + UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; + for (x = 0; x < w; ++x) + *row++ = j2ku_shift(offset + *data++, shift); + } +} + +static void +j2ku_gray_rgb(opj_image_t *in, Imaging im) +{ + unsigned x0 = in->comps[0].x0, y0 = in->comps[0].y0; + unsigned w = in->comps[0].w, h = in->comps[0].h; + int shift = 8 - in->comps[0].prec; + int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0; + unsigned x, y; + + if (shift < 0) + offset += 1 << (-shift - 1); + + for (y = 0; y < h; ++y) { + OPJ_INT32 *data = &in->comps[0].data[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; + } + } +} + +static void +j2ku_graya_la(opj_image_t *in, Imaging im) +{ + unsigned x0 = in->comps[0].x0, y0 = in->comps[0].y0; + unsigned w = in->comps[0].w, h = in->comps[0].h; + int shift = 8 - in->comps[0].prec; + int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0; + int ashift = 8 - in->comps[1].prec; + int aoffset = in->comps[1].sgnd ? 1 << (in->comps[1].prec - 1) : 0; + unsigned x, y; + + if (shift < 0) + offset += 1 << (-shift - 1); + if (ashift < 0) + aoffset += 1 << (-ashift - 1); + + for (y = 0; y < h; ++y) { + OPJ_INT32 *data = &in->comps[0].data[y * w]; + OPJ_INT32 *adata = &in->comps[1].data[y * w]; + UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4; + for (x = 0; x < w; ++x) { + UINT8 byte = j2ku_shift(offset + *data++, shift); + row[0] = row[1] = row[2] = byte; + row[3] = (unsigned)(offset + *adata++) >> shift; + row += 4; + } + } +} + +static void +j2ku_srgb_rgb(opj_image_t *in, Imaging im) +{ + unsigned x0 = in->comps[0].x0, y0 = in->comps[0].y0; + unsigned w = in->comps[0].w, h = in->comps[0].h; + int shifts[3], offsets[3]; + unsigned n, x, y; + + for (n = 0; n < 3; ++n) { + shifts[n] = 8 - in->comps[n].prec; + offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0; + if (shifts[n] < 0) + offsets[n] += 1 << (-shifts[n] - 1); + } + + for (y = 0; y < h; ++y) { + OPJ_INT32 *data[3]; + UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4; + for (n = 0; n < 3; ++n) + data[n] = &in->comps[n].data[y * w]; + + for (x = 0; x < w; ++x) { + for (n = 0; n < 3; ++n) + row[n] = j2ku_shift(offsets[n] + *data[n]++, shifts[n]); + row[3] = 0xff; + row += 4; + } + } +} + +static void +j2ku_sycc_rgb(opj_image_t *in, Imaging im) +{ + unsigned x0 = in->comps[0].x0, y0 = in->comps[0].y0; + unsigned w = in->comps[0].w, h = in->comps[0].h; + int shifts[3], offsets[3]; + unsigned n, x, y; + + for (n = 0; n < 3; ++n) { + shifts[n] = 8 - in->comps[n].prec; + offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0; + if (shifts[n] < 0) + offsets[n] += 1 << (-shifts[n] - 1); + } + + for (y = 0; y < h; ++y) { + OPJ_INT32 *data[3]; + UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4; + UINT8 *row_start = row; + for (n = 0; n < 3; ++n) + data[n] = &in->comps[n].data[y * w]; + + for (x = 0; x < w; ++x) { + for (n = 0; n < 3; ++n) + row[n] = j2ku_shift(offsets[n] + *data[n]++, shifts[n]); + row[3] = 0xff; + row += 4; + } + ImagingConvertYCbCr2RGB(row_start, row_start, w); + } +} + +static void +j2ku_srgba_rgba(opj_image_t *in, Imaging im) +{ + unsigned x0 = in->comps[0].x0, y0 = in->comps[0].y0; + unsigned w = in->comps[0].w, h = in->comps[0].h; + int shifts[4], offsets[4]; + unsigned n, x, y; + + for (n = 0; n < 4; ++n) { + shifts[n] = 8 - in->comps[n].prec; + offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0; + if (shifts[n] < 0) + offsets[n] += 1 << (-shifts[n] - 1); + } + + for (y = 0; y < h; ++y) { + OPJ_INT32 *data[4]; + UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4; + for (n = 0; n < 4; ++n) + data[n] = &in->comps[n].data[y * w]; + + for (x = 0; x < w; ++x) { + for (n = 0; n < 4; ++n) + row[n] = j2ku_shift(offsets[n] + *data[n]++, shifts[n]); + row += 4; + } + } +} + +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 }, + { "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 }, +}; + +/* -------------------------------------------------------------------- */ +/* 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, + ImagingIncrementalDecoder decoder) +{ + JPEG2KSTATE *context = (JPEG2KSTATE *) state->context; + opj_stream_t *stream; + opj_image_t *image; + opj_codec_t *codec; + opj_dparameters_t params; + OPJ_COLOR_SPACE color_space; + j2k_unpacker_t unpack; + 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_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, context->decoder); + + + /* Setup decompression context */ + context->error_msg = NULL; + + opj_set_default_decoder_parameters(¶ms); + 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, ¶ms); + + 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; + } + + /* Check that the bit depth is uniform */ + if (image->comps[0].prec != image->comps[n].prec) { + 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 (context->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 and unpack the image */ + if (!opj_decode(codec, stream, image) + || !opj_end_decompress(codec, stream)) { + state->errcode = IMAGING_CODEC_BROKEN; + state->state = J2K_STATE_FAILED; + goto quick_exit; + } + + unpack(image, im); + + 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) +{ + JPEG2KSTATE *context = (JPEG2KSTATE *) state->context; + + if (state->state == J2K_STATE_DONE || state->state == J2K_STATE_FAILED) + return -1; + + if (state->state == J2K_STATE_START) { + context->decoder = ImagingIncrementalDecoderCreate(j2k_decode_entry, + im, state); + + if (!context->decoder) { + state->errcode = IMAGING_CODEC_BROKEN; + state->state = J2K_STATE_FAILED; + return -1; + } + + state->state = J2K_STATE_DECODING; + } + + return ImagingIncrementalDecodeData(context->decoder, buf, bytes); +} + +/* -------------------------------------------------------------------- */ +/* Cleanup */ +/* -------------------------------------------------------------------- */ + +int +ImagingJpeg2KDecodeCleanup(ImagingCodecState state) { + JPEG2KSTATE *context = (JPEG2KSTATE *)state->context; + + if (context->decoder) + ImagingIncrementalDecoderDestroy(context->decoder); + + return -1; +} + +const char * +ImagingJpeg2KVersion(void) +{ + return opj_version(); +} + +#endif /* HAVE_OPENJPEG */ + +/* + * Local Variables: + * c-basic-offset: 4 + * End: + * + */ diff --git a/setup.py b/setup.py index 0d791c444..e948e1050 100644 --- a/setup.py +++ b/setup.py @@ -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") def _add_directory(path, dir, where=None): @@ -98,6 +99,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): @@ -349,6 +351,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 +437,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 +547,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"), From 7dba77364a74cf12c71409f56af3f089d11dcce6 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Thu, 13 Mar 2014 11:57:47 +0000 Subject: [PATCH 02/15] Fixed rounding. --- PIL/Jpeg2KImagePlugin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index 9acc71c4e..95d206a99 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -165,7 +165,9 @@ class Jpeg2KImageFile(ImageFile.ImageFile): def load(self): if self.reduce: power = 1 << self.reduce - self.size = (self.size[0] / power, self.size[1] / power) + adjust = power >> 1 + self.size = ((self.size[0] + adjust) / power, + (self.size[1] + adjust) / power) ImageFile.ImageFile.load(self) From 5b22b715cea0a2e3320ed6afa7d573b451ee2bae Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Thu, 13 Mar 2014 12:29:03 +0000 Subject: [PATCH 03/15] Fixed some bugs. --- PIL/Jpeg2KImagePlugin.py | 2 - libImaging/Incremental.c | 117 ++++++++++++++++++++++++++------------ libImaging/Jpeg2KDecode.c | 26 ++++++--- 3 files changed, 97 insertions(+), 48 deletions(-) diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index 95d206a99..62aced03e 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -138,12 +138,10 @@ class Jpeg2KImageFile(ImageFile.ImageFile): sig = self.fp.read(4) if sig == b'\xff\x4f\xff\x51': self.codec = "j2k" - print 'Reading size/mode' try: self.size, self.mode = _parse_codestream(self.fp) except Exception as e: print e - print '%r, %r' % (self.size, self.mode) else: sig = sig + self.fp.read(8) diff --git a/libImaging/Incremental.c b/libImaging/Incremental.c index 0e81af7e8..bb51d91c7 100644 --- a/libImaging/Incremental.c +++ b/libImaging/Incremental.c @@ -42,6 +42,14 @@ #include #endif +#define DEBUG_INCREMENTAL 0 + +#if DEBUG_INCREMENTAL +#define DEBUG(...) printf(__VA_ARGS__) +#else +#define DEBUG(...) +#endif + struct ImagingIncrementalStreamStruct { UINT8 *buffer; UINT8 *ptr; @@ -206,6 +214,8 @@ ImagingIncrementalDecoderCreate(ImagingIncrementalDecoderEntry decoder_entry, void ImagingIncrementalDecoderDestroy(ImagingIncrementalDecoder decoder) { + DEBUG("destroying\n"); + if (!decoder->started) { #ifdef _WIN32 ResumeThread(decoder->hThread); @@ -252,6 +262,8 @@ ImagingIncrementalDecodeData(ImagingIncrementalDecoder decoder, UINT8 *buf, int bytes) { if (!decoder->started) { + DEBUG("starting\n"); + #ifdef _WIN32 ResumeThread(decoder->hThread); #else @@ -260,6 +272,8 @@ ImagingIncrementalDecodeData(ImagingIncrementalDecoder decoder, decoder->started = 1; } + DEBUG("providing %p, %d\n", buf, bytes); + #ifndef _WIN32 pthread_mutex_lock(&decoder->data_mutex); #endif @@ -279,6 +293,8 @@ ImagingIncrementalDecodeData(ImagingIncrementalDecoder decoder, pthread_mutex_unlock(&decoder->decode_mutex); #endif + DEBUG("got result %d\n", decoder->result); + return decoder->result; } @@ -289,12 +305,74 @@ ImagingIncrementalDecoderRead(ImagingIncrementalDecoder decoder, UINT8 *ptr = (UINT8 *)buffer; size_t done = 0; + DEBUG("reading (want %llu bytes)\n", (unsigned long long)bytes); + +#ifndef _WIN32 pthread_mutex_lock(&decoder->data_mutex); +#endif while (bytes) { size_t todo = bytes; size_t remaining = decoder->stream.end - decoder->stream.ptr; if (!remaining) { + DEBUG("waiting for data\n"); + +#ifndef _WIN32 + pthread_mutex_lock(&decoder->decode_mutex); +#endif + decoder->result = (int)(decoder->stream.ptr - decoder->stream.buffer); +#if _WIN32 + SetEvent(decoder->hDecodeEvent); + WaitForSingleObject(decoder->hDataEvent); +#else + pthread_cond_signal(&decoder->decode_cond); + pthread_mutex_unlock(&decoder->decode_mutex); + pthread_cond_wait(&decoder->data_cond, &decoder->data_mutex); +#endif + + remaining = decoder->stream.end - decoder->stream.ptr; + + DEBUG("got %llu bytes\n", (unsigned long long)remaining); + } + if (todo > remaining) + todo = remaining; + + if (!todo) + break; + + memcpy (ptr, decoder->stream.ptr, todo); + decoder->stream.ptr += todo; + bytes -= todo; + done += todo; + ptr += todo; + } +#ifndef _WIN32 + pthread_mutex_unlock(&decoder->data_mutex); +#endif + + DEBUG("read total %llu bytes\n", (unsigned long long)done); + + return done; +} + +off_t +ImagingIncrementalDecoderSkip(ImagingIncrementalDecoder decoder, + off_t bytes) +{ + off_t done = 0; + + DEBUG("skipping (want %llu bytes)\n", (unsigned long long)bytes); + +#ifndef _WIN32 + pthread_mutex_lock(&decoder->data_mutex); +#endif + while (bytes) { + off_t todo = bytes; + off_t remaining = decoder->stream.end - decoder->stream.ptr; + + if (!remaining) { + DEBUG("waiting for data\n"); + #ifndef _WIN32 pthread_mutex_lock(&decoder->decode_mutex); #endif @@ -316,50 +394,15 @@ ImagingIncrementalDecoderRead(ImagingIncrementalDecoder decoder, if (!todo) break; - memcpy (ptr, decoder->stream.ptr, todo); decoder->stream.ptr += todo; bytes -= todo; done += todo; - ptr += todo; } +#ifndef _WIN32 pthread_mutex_unlock(&decoder->data_mutex); - - return done; -} - -off_t -ImagingIncrementalDecoderSkip(ImagingIncrementalDecoder decoder, - off_t bytes) -{ - off_t done = 0; - - while (bytes) { - off_t todo = bytes; - off_t remaining = decoder->stream.end - decoder->stream.ptr; - - if (!remaining) { - decoder->result = (int)(decoder->stream.ptr - decoder->stream.buffer); -#if _WIN32 - SetEvent(decoder->hDecodeEvent); - WaitForSingleObject(decoder->hDataEvent); -#else - pthread_cond_signal(&decoder->decode_cond); - pthread_mutex_lock(&decoder->data_mutex); - pthread_cond_wait(&decoder->data_cond, &decoder->data_mutex); #endif - remaining = decoder->stream.end - decoder->stream.ptr; - } - if (todo > remaining) - todo = remaining; - - if (!todo) - break; - - decoder->stream.ptr += todo; - bytes -= todo; - done += todo; - } + DEBUG("skipped total %llu bytes\n", (unsigned long long)done); return done; } diff --git a/libImaging/Jpeg2KDecode.c b/libImaging/Jpeg2KDecode.c index e050c5216..5c1d4cd42 100644 --- a/libImaging/Jpeg2KDecode.c +++ b/libImaging/Jpeg2KDecode.c @@ -17,7 +17,7 @@ #ifdef HAVE_OPENJPEG -#include +#include #include "Jpeg2K.h" /* -------------------------------------------------------------------- */ @@ -41,27 +41,35 @@ j2k_read(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) { ImagingIncrementalDecoder decoder = (ImagingIncrementalDecoder)p_user_data; - return ImagingIncrementalDecoderRead(decoder, p_buffer, p_nb_bytes); + size_t len = ImagingIncrementalDecoderRead(decoder, p_buffer, p_nb_bytes); + + return len ? len : (OPJ_SIZE_T)-1; } static OPJ_SIZE_T j2k_write(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) { - return OPJ_FALSE; + /* This should never happen */ + fprintf(stderr, "OpenJPEG has written to our read stream(!)"); + abort(); + return (OPJ_SIZE_T)-1; } static OPJ_OFF_T j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data) { ImagingIncrementalDecoder decoder = (ImagingIncrementalDecoder)p_user_data; + off_t pos = ImagingIncrementalDecoderSkip(decoder, p_nb_bytes); - return ImagingIncrementalDecoderSkip(decoder, 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) { - // We *deliberately* don't implement this + /* This should never happen */ + fprintf(stderr, "OpenJPEG tried to seek our read stream(!)"); + abort(); return OPJ_FALSE; } @@ -281,12 +289,12 @@ j2k_decode_entry(Imaging im, ImagingCodecState state, ImagingIncrementalDecoder decoder) { JPEG2KSTATE *context = (JPEG2KSTATE *) state->context; - opj_stream_t *stream; - opj_image_t *image; - opj_codec_t *codec; + 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; + j2k_unpacker_t unpack = NULL; unsigned n; stream = opj_stream_default_create(OPJ_TRUE); From 9a4bff722f9f4dc9320b6ea0478ff8102ec503b3 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Thu, 13 Mar 2014 13:44:26 +0000 Subject: [PATCH 04/15] Decode tile-by-tile; saves memory and means we don't need to buffer the entire image in the OpenJPEG opj_image. --- libImaging/Jpeg2KDecode.c | 355 +++++++++++++++++++++++++++++--------- 1 file changed, 277 insertions(+), 78 deletions(-) diff --git a/libImaging/Jpeg2KDecode.c b/libImaging/Jpeg2KDecode.c index 5c1d4cd42..b503ffa44 100644 --- a/libImaging/Jpeg2KDecode.c +++ b/libImaging/Jpeg2KDecode.c @@ -20,6 +20,13 @@ #include #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 */ /* -------------------------------------------------------------------- */ @@ -77,7 +84,10 @@ j2k_seek(OPJ_OFF_T p_nb_bytes, void *p_user_data) /* Unpackers */ /* -------------------------------------------------------------------- */ -typedef void (*j2k_unpacker_t)(opj_image_t *in, Imaging im); +typedef void (*j2k_unpacker_t)(opj_image_t *in, + const JPEG2KTILEINFO *tileInfo, + const UINT8 *data, + Imaging im); struct j2k_decode_unpacker { const char *mode; @@ -96,102 +106,216 @@ unsigned j2ku_shift(unsigned x, int n) } static void -j2ku_gray_l(opj_image_t *in, Imaging im) +j2ku_gray_l(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, + const UINT8 *tiledata, Imaging im) { - unsigned x0 = in->comps[0].x0, y0 = in->comps[0].y0; - unsigned w = in->comps[0].w, h = in->comps[0].h; + unsigned x0 = tileinfo->x0, y0 = tileinfo->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); - for (y = 0; y < h; ++y) { - OPJ_INT32 *data = &in->comps[0].data[y * w]; - UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; - for (x = 0; x < w; ++x) - *row++ = j2ku_shift(offset + *data++, shift); - } -} - -static void -j2ku_gray_rgb(opj_image_t *in, Imaging im) -{ - unsigned x0 = in->comps[0].x0, y0 = in->comps[0].y0; - unsigned w = in->comps[0].w, h = in->comps[0].h; - int shift = 8 - in->comps[0].prec; - int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0; - unsigned x, y; - - if (shift < 0) - offset += 1 << (-shift - 1); - - for (y = 0; y < h; ++y) { - OPJ_INT32 *data = &in->comps[0].data[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; + 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_graya_la(opj_image_t *in, Imaging im) +j2ku_gray_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, + const UINT8 *tiledata, Imaging im) { - unsigned x0 = in->comps[0].x0, y0 = in->comps[0].y0; - unsigned w = in->comps[0].w, h = in->comps[0].h; + unsigned x0 = tileinfo->x0, y0 = tileinfo->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, y0 = tileinfo->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) { - OPJ_INT32 *data = &in->comps[0].data[y * w]; - OPJ_INT32 *adata = &in->comps[1].data[y * w]; + 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) { - UINT8 byte = j2ku_shift(offset + *data++, shift); + UINT32 word, aword; + + 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] = (unsigned)(offset + *adata++) >> shift; + row[3] = (unsigned)(aoffset + aword) >> ashift; row += 4; } } } static void -j2ku_srgb_rgb(opj_image_t *in, Imaging im) +j2ku_srgb_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, + const UINT8 *tiledata, Imaging im) { - unsigned x0 = in->comps[0].x0, y0 = in->comps[0].y0; - unsigned w = in->comps[0].w, h = in->comps[0].h; - int shifts[3], offsets[3]; + unsigned x0 = tileinfo->x0, y0 = tileinfo->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) { - OPJ_INT32 *data[3]; + const UINT8 *data[3]; UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4; for (n = 0; n < 3; ++n) - data[n] = &in->comps[n].data[y * w]; + data[n] = &cdata[n][csiz[n] * y * w]; for (x = 0; x < w; ++x) { - for (n = 0; n < 3; ++n) - row[n] = j2ku_shift(offsets[n] + *data[n]++, shifts[n]); + for (n = 0; n < 3; ++n) { + UINT32 word; + + 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; } @@ -199,61 +323,106 @@ j2ku_srgb_rgb(opj_image_t *in, Imaging im) } static void -j2ku_sycc_rgb(opj_image_t *in, Imaging im) +j2ku_sycc_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, + const UINT8 *tiledata, Imaging im) { - unsigned x0 = in->comps[0].x0, y0 = in->comps[0].y0; - unsigned w = in->comps[0].w, h = in->comps[0].h; - int shifts[3], offsets[3]; + unsigned x0 = tileinfo->x0, y0 = tileinfo->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) { - OPJ_INT32 *data[3]; + 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] = &in->comps[n].data[y * w]; + data[n] = &cdata[n][csiz[n] * y * w]; for (x = 0; x < w; ++x) { - for (n = 0; n < 3; ++n) - row[n] = j2ku_shift(offsets[n] + *data[n]++, shifts[n]); + for (n = 0; n < 3; ++n) { + UINT32 word; + + 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, Imaging im) +j2ku_srgba_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, + const UINT8 *tiledata, Imaging im) { - unsigned x0 = in->comps[0].x0, y0 = in->comps[0].y0; - unsigned w = in->comps[0].w, h = in->comps[0].h; - int shifts[4], offsets[4]; + unsigned x0 = tileinfo->x0, y0 = tileinfo->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) { - OPJ_INT32 *data[4]; + const UINT8 *data[4]; UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4; for (n = 0; n < 4; ++n) - data[n] = &in->comps[n].data[y * w]; + data[n] = &cdata[n][csiz[n] * y * w]; for (x = 0; x < w; ++x) { - for (n = 0; n < 4; ++n) - row[n] = j2ku_shift(offsets[n] + *data[n]++, shifts[n]); + for (n = 0; n < 4; ++n) { + UINT32 word; + + 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; } } @@ -295,6 +464,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state, 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); @@ -353,13 +523,6 @@ j2k_decode_entry(Imaging im, ImagingCodecState state, state->state = J2K_STATE_FAILED; goto quick_exit; } - - /* Check that the bit depth is uniform */ - if (image->comps[0].prec != image->comps[n].prec) { - state->errcode = IMAGING_CODEC_BROKEN; - state->state = J2K_STATE_FAILED; - goto quick_exit; - } } /* @@ -408,15 +571,51 @@ j2k_decode_entry(Imaging im, ImagingCodecState state, goto quick_exit; } - /* Decode and unpack the image */ - if (!opj_decode(codec, stream, image) - || !opj_end_decompress(codec, stream)) { - 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; - unpack(image, im); + 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; + } + + unpack(image, &tile_info, state->buffer, im); + } quick_exit: if (codec) From aea0ec56b2b6caaa3690b9bf951703d2c74fd524 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Thu, 13 Mar 2014 13:48:15 +0000 Subject: [PATCH 05/15] Fixed a small bug. --- libImaging/Jpeg2KDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libImaging/Jpeg2KDecode.c b/libImaging/Jpeg2KDecode.c index b503ffa44..77615cce5 100644 --- a/libImaging/Jpeg2KDecode.c +++ b/libImaging/Jpeg2KDecode.c @@ -264,7 +264,7 @@ j2ku_graya_la(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, UINT8 byte = j2ku_shift(offset + word, shift); row[0] = row[1] = row[2] = byte; - row[3] = (unsigned)(aoffset + aword) >> ashift; + row[3] = j2ku_shift(aoffset + aword, ashift); row += 4; } } From 61fb89ec54818f4e2a4d35e9c4bc97ec7716e6b3 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Thu, 13 Mar 2014 18:27:16 +0000 Subject: [PATCH 06/15] Added a JPEG 2000 encoder. --- PIL/Jpeg2KImagePlugin.py | 30 ++- _imaging.c | 2 + decode.c | 24 +- encode.c | 135 ++++++++++ libImaging/Imaging.h | 25 +- libImaging/Incremental.c | 356 ++++++++++++++----------- libImaging/Jpeg2K.h | 50 +++- libImaging/Jpeg2KDecode.c | 40 +-- libImaging/Jpeg2KEncode.c | 548 ++++++++++++++++++++++++++++++++++++++ setup.py | 2 +- 10 files changed, 1004 insertions(+), 208 deletions(-) create mode 100644 libImaging/Jpeg2KEncode.c diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index 62aced03e..94d49f705 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -173,17 +173,29 @@ 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' + + ImageFile._save(im, fp, [('jpeg2k', (0, 0)+im.size, 0, kind)]) + # ------------------------------------------------------------ # Registry stuff -Image.register_open("JPEG2000", Jpeg2KImageFile, _accept) +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_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") +Image.register_mime('JPEG2000', 'image/jp2') +Image.register_mime('JPEG2000', 'image/jpx') diff --git a/_imaging.c b/_imaging.c index 84b25219b..fe623e780 100644 --- a/_imaging.c +++ b/_imaging.c @@ -3300,6 +3300,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); @@ -3355,6 +3356,7 @@ static PyMethodDef functions[] = { #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 diff --git a/decode.c b/decode.c index 359b99695..0bc64ec77 100644 --- a/decode.c +++ b/decode.c @@ -778,27 +778,18 @@ PyImaging_JpegDecoderNew(PyObject* self, PyObject* args) #endif /* -------------------------------------------------------------------- */ -/* JPEG2000 */ +/* JPEG 2000 */ /* -------------------------------------------------------------------- */ #ifdef HAVE_OPENJPEG -/* We better define this decoder last in this file, so the following - undef's won't mess things up for the Imaging library proper. */ -#undef UINT8 -#undef UINT16 -#undef UINT32 -#undef INT8 -#undef INT16 -#undef INT32 - #include "Jpeg2K.h" PyObject* PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args) { ImagingDecoderObject* decoder; - JPEG2KSTATE *context; + JPEG2KDECODESTATE *context; char* mode; char* format; @@ -809,16 +800,16 @@ PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args) &reduce, &layers)) return NULL; - if (strcmp (format, "j2k") == 0) + if (strcmp(format, "j2k") == 0) codec_format = OPJ_CODEC_J2K; - else if (strcmp (format, "jpt") == 0) + else if (strcmp(format, "jpt") == 0) codec_format = OPJ_CODEC_JPT; - else if (strcmp (format, "jp2") == 0) + else if (strcmp(format, "jp2") == 0) codec_format = OPJ_CODEC_JP2; else return NULL; - decoder = PyImaging_DecoderNew(sizeof(JPEG2KSTATE)); + decoder = PyImaging_DecoderNew(sizeof(JPEG2KDECODESTATE)); if (decoder == NULL) return NULL; @@ -826,9 +817,8 @@ PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args) decoder->decode = ImagingJpeg2KDecode; decoder->cleanup = ImagingJpeg2KDecodeCleanup; - context = (JPEG2KSTATE *)decoder->state.context; + context = (JPEG2KDECODESTATE *)decoder->state.context; - strncpy(context->mode, mode, 8); context->format = codec_format; context->reduce = reduce; context->layers = layers; diff --git a/encode.c b/encode.c index 8be44d8ec..979742a84 100644 --- a/encode.c +++ b/encode.c @@ -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); @@ -793,3 +799,132 @@ 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; + int irreversible = 0; + char *progression = "LRCP"; + OPJ_PROG_ORDER prog_order; + char *cinema_mode = "no"; + OPJ_CINEMA_MODE cine_mode; + + if (!PyArg_ParseTuple(args, "ss|OOOsOiOpss", &mode, &format, + &offset, &tile_offset, &tile_size, + &quality_mode, &quality_layers, &num_resolutions, + &cblk_size, &irreversible, &progression, &cinema_mode)) + 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->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); + + if (quality_layers && PySequence_Check(quality_layers)) { + context->quality_is_in_db = strcmp (quality_mode, "dB") == 0; + context->quality_layers = quality_layers; + } + + context->num_resolutions = num_resolutions; + + j2k_decode_coord_tuple(cblk_size, + &context->cblk_width, + &context->cblk_height); + + context->irreversible = irreversible; + context->progression = prog_order; + context->cinema_mode = cine_mode; + + return (PyObject *)encoder; +} + +#endif + +/* + * Local Variables: + * c-basic-offset: 4 + * End: + * + */ diff --git a/libImaging/Imaging.h b/libImaging/Imaging.h index 7a7eb9c66..1602622a2 100644 --- a/libImaging/Imaging.h +++ b/libImaging/Imaging.h @@ -428,6 +428,9 @@ extern int ImagingJpegEncode(Imaging im, ImagingCodecState state, 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); @@ -502,18 +505,20 @@ struct ImagingCodecStateInstance { void *context; }; -/* Incremental decoding support */ -typedef struct ImagingIncrementalDecoderStruct *ImagingIncrementalDecoder; +/* Incremental encoding/decoding support */ +typedef struct ImagingIncrementalCodecStruct *ImagingIncrementalCodec; -typedef int (*ImagingIncrementalDecoderEntry)(Imaging im, - ImagingCodecState state, - ImagingIncrementalDecoder decoder); +typedef int (*ImagingIncrementalCodecEntry)(Imaging im, + ImagingCodecState state, + ImagingIncrementalCodec codec); -extern ImagingIncrementalDecoder ImagingIncrementalDecoderCreate(ImagingIncrementalDecoderEntry decoder_entry, Imaging im, ImagingCodecState state); -extern void ImagingIncrementalDecoderDestroy(ImagingIncrementalDecoder decoder); -extern int ImagingIncrementalDecodeData(ImagingIncrementalDecoder decoder, UINT8 *buf, int bytes); -size_t ImagingIncrementalDecoderRead(ImagingIncrementalDecoder decoder, void *buffer, size_t bytes); -off_t ImagingIncrementalDecoderSkip(ImagingIncrementalDecoder decoder, off_t bytes); +extern ImagingIncrementalCodec ImagingIncrementalCodecCreate(ImagingIncrementalCodecEntry codec_entry, Imaging im, ImagingCodecState state); +extern void ImagingIncrementalCodecDestroy(ImagingIncrementalCodec codec); +extern int ImagingIncrementalCodecPushBuffer(ImagingIncrementalCodec codec, UINT8 *buf, int bytes); +extern size_t ImagingIncrementalCodecRead(ImagingIncrementalCodec codec, void *buffer, size_t bytes); +extern off_t ImagingIncrementalCodecSkip(ImagingIncrementalCodec codec, off_t bytes); +extern size_t ImagingIncrementalCodecWrite(ImagingIncrementalCodec codec, const void *buffer, size_t bytes); +extern size_t ImagingIncrementalCodecBytesInBuffer(ImagingIncrementalCodec codec); /* Errcodes */ #define IMAGING_CODEC_END 1 diff --git a/libImaging/Incremental.c b/libImaging/Incremental.c index bb51d91c7..45f064570 100644 --- a/libImaging/Incremental.c +++ b/libImaging/Incremental.c @@ -13,9 +13,9 @@ /* 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 - ImagingIncrementalDecoderRead() call runs short on data, it suspends the + ImagingIncrementalCodecRead() call runs short on data, it suspends the decoding thread and wakes the main thread. Conversely, the - ImagingIncrementalDecodeData() call suspends the main thread and wakes + 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 @@ -50,27 +50,21 @@ #define DEBUG(...) #endif -struct ImagingIncrementalStreamStruct { - UINT8 *buffer; - UINT8 *ptr; - UINT8 *end; -}; - -struct ImagingIncrementalDecoderStruct { +struct ImagingIncrementalCodecStruct { #ifdef _WIN32 - HANDLE hDecodeEvent; + HANDLE hCodecEvent; HANDLE hDataEvent; HANDLE hThread; #else pthread_mutex_t start_mutex; pthread_cond_t start_cond; - pthread_mutex_t decode_mutex; - pthread_cond_t decode_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 - ImagingIncrementalDecoderEntry entry; + ImagingIncrementalCodecEntry entry; Imaging im; ImagingCodecState state; struct { @@ -84,222 +78,228 @@ struct ImagingIncrementalDecoderStruct { #if _WIN32 static void __stdcall -incremental_thread(void *ptr) +codec_thread(void *ptr) { - ImagingIncrementalDecoder decoder = (ImagingIncrementalDecoder)ptr; + ImagingIncrementalCodec codec = (ImagingIncrementalCodec)ptr; - decoder->result = decoder->entry(decoder->im, decoder->state, decoder); + codec->result = codec->entry(codec->im, codec->state, codec); - SetEvent(decoder->hDecodeEvent); + SetEvent(codec->hCodecEvent); } #else static void * -incremental_thread(void *ptr) +codec_thread(void *ptr) { - ImagingIncrementalDecoder decoder = (ImagingIncrementalDecoder)ptr; + ImagingIncrementalCodec codec = (ImagingIncrementalCodec)ptr; - decoder->result = decoder->entry(decoder->im, decoder->state, decoder); + codec->result = codec->entry(codec->im, codec->state, codec); - pthread_cond_signal(&decoder->decode_cond); + pthread_cond_signal(&codec->codec_cond); return NULL; } #endif /** - * Create a new incremental decoder */ -ImagingIncrementalDecoder -ImagingIncrementalDecoderCreate(ImagingIncrementalDecoderEntry decoder_entry, - Imaging im, - ImagingCodecState state) + * Create a new incremental codec */ +ImagingIncrementalCodec +ImagingIncrementalCodecCreate(ImagingIncrementalCodecEntry codec_entry, + Imaging im, + ImagingCodecState state) { - ImagingIncrementalDecoder decoder = (ImagingIncrementalDecoder)malloc(sizeof(struct ImagingIncrementalDecoderStruct)); + ImagingIncrementalCodec codec = (ImagingIncrementalCodec)malloc(sizeof(struct ImagingIncrementalCodecStruct)); - decoder->entry = decoder_entry; - decoder->im = im; - decoder->state = state; - decoder->result = 0; - decoder->stream.buffer = decoder->stream.ptr = decoder->stream.end = NULL; - decoder->started = 0; + codec->entry = codec_entry; + codec->im = im; + codec->state = state; + codec->result = 0; + codec->stream.buffer = codec->stream.ptr = codec->stream.end = NULL; + codec->started = 0; /* System specific set-up */ #if _WIN32 - decoder->hDecodeEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + codec->hCodecEvent = CreateEvent(NULL, FALSE, FALSE, NULL); - if (!decoder->hDecodeEvent) { - free(decoder); + if (!codec->hCodecEvent) { + free(codec); return NULL; } - decoder->hDataEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + codec->hDataEvent = CreateEvent(NULL, FALSE, FALSE, NULL); - if (!decoder->hDataEvent) { - CloseHandle(decoder->hDecodeEvent); - free(decoder); + if (!codec->hDataEvent) { + CloseHandle(codec->hCodecEvent); + free(codec); return NULL; } - decoder->hThread = _beginthreadex(NULL, 0, incremental_thread, decoder, + codec->hThread = _beginthreadex(NULL, 0, codec_thread, codec, CREATE_SUSPENDED, NULL); - if (!decoder->hThread) { - CloseHandle(decoder->hDecodeEvent); - CloseHandle(decoder->hDataEvent); - free(decoder); + if (!codec->hThread) { + CloseHandle(codec->hCodecEvent); + CloseHandle(codec->hDataEvent); + free(codec); return NULL; } #else - if (pthread_mutex_init(&decoder->start_mutex, NULL)) { - free (decoder); + if (pthread_mutex_init(&codec->start_mutex, NULL)) { + free (codec); return NULL; } - if (pthread_mutex_init(&decoder->decode_mutex, NULL)) { - pthread_mutex_destroy(&decoder->start_mutex); - free(decoder); + if (pthread_mutex_init(&codec->codec_mutex, NULL)) { + pthread_mutex_destroy(&codec->start_mutex); + free(codec); return NULL; } - if (pthread_mutex_init(&decoder->data_mutex, NULL)) { - pthread_mutex_destroy(&decoder->start_mutex); - pthread_mutex_destroy(&decoder->decode_mutex); - free(decoder); + 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(&decoder->start_cond, NULL)) { - pthread_mutex_destroy(&decoder->start_mutex); - pthread_mutex_destroy(&decoder->decode_mutex); - pthread_mutex_destroy(&decoder->data_mutex); - free(decoder); + 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(&decoder->decode_cond, NULL)) { - pthread_mutex_destroy(&decoder->start_mutex); - pthread_mutex_destroy(&decoder->decode_mutex); - pthread_mutex_destroy(&decoder->data_mutex); - pthread_cond_destroy(&decoder->start_cond); - free(decoder); + 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(&decoder->data_cond, NULL)) { - pthread_mutex_destroy(&decoder->start_mutex); - pthread_mutex_destroy(&decoder->decode_mutex); - pthread_mutex_destroy(&decoder->data_mutex); - pthread_cond_destroy(&decoder->start_cond); - pthread_cond_destroy(&decoder->decode_cond); - free(decoder); + 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(&decoder->thread, NULL, incremental_thread, decoder)) { - pthread_mutex_destroy(&decoder->start_mutex); - pthread_mutex_destroy(&decoder->decode_mutex); - pthread_mutex_destroy(&decoder->data_mutex); - pthread_cond_destroy(&decoder->start_cond); - pthread_cond_destroy(&decoder->decode_cond); - pthread_cond_destroy(&decoder->data_cond); - free(decoder); + 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 decoder; + return codec; } /** - * Destroy an incremental decoder */ + * Destroy an incremental codec */ void -ImagingIncrementalDecoderDestroy(ImagingIncrementalDecoder decoder) +ImagingIncrementalCodecDestroy(ImagingIncrementalCodec codec) { DEBUG("destroying\n"); - if (!decoder->started) { + if (!codec->started) { #ifdef _WIN32 - ResumeThread(decoder->hThread); + ResumeThread(codec->hThread); #else - pthread_cond_signal(&decoder->start_cond); + pthread_cond_signal(&codec->start_cond); #endif - decoder->started = 1; + codec->started = 1; } #ifndef _WIN32 - pthread_mutex_lock(&decoder->data_mutex); + pthread_mutex_lock(&codec->data_mutex); #endif - decoder->stream.buffer = decoder->stream.ptr = decoder->stream.end = NULL; + codec->stream.buffer = codec->stream.ptr = codec->stream.end = NULL; #ifdef _WIN32 - SetEvent(decoder->hDataEvent); + SetEvent(codec->hDataEvent); - WaitForSingleObject(decoder->hThread, INFINITE); + WaitForSingleObject(codec->hThread, INFINITE); - CloseHandle(decoder->hThread); - CloseHandle(decoder->hDecodeEvent); - CloseHandle(decoder->hDataEvent); + CloseHandle(codec->hThread); + CloseHandle(codec->hCodecEvent); + CloseHandle(codec->hDataEvent); #else - pthread_cond_signal(&decoder->data_cond); - pthread_mutex_unlock(&decoder->data_mutex); + pthread_cond_signal(&codec->data_cond); + pthread_mutex_unlock(&codec->data_mutex); - pthread_join(decoder->thread, NULL); + pthread_join(codec->thread, NULL); - pthread_mutex_destroy(&decoder->start_mutex); - pthread_mutex_destroy(&decoder->decode_mutex); - pthread_mutex_destroy(&decoder->data_mutex); - pthread_cond_destroy(&decoder->start_cond); - pthread_cond_destroy(&decoder->decode_cond); - pthread_cond_destroy(&decoder->data_cond); + 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 (decoder); + free (codec); } /** - * Decode data using an incremental decoder */ + * Push a data buffer for an incremental codec */ int -ImagingIncrementalDecodeData(ImagingIncrementalDecoder decoder, - UINT8 *buf, int bytes) +ImagingIncrementalCodecPushBuffer(ImagingIncrementalCodec codec, + UINT8 *buf, int bytes) { - if (!decoder->started) { + if (!codec->started) { DEBUG("starting\n"); #ifdef _WIN32 - ResumeThread(decoder->hThread); + ResumeThread(codec->hThread); #else - pthread_cond_signal(&decoder->start_cond); + pthread_cond_signal(&codec->start_cond); #endif - decoder->started = 1; + codec->started = 1; } DEBUG("providing %p, %d\n", buf, bytes); #ifndef _WIN32 - pthread_mutex_lock(&decoder->data_mutex); + pthread_mutex_lock(&codec->data_mutex); #endif - decoder->stream.buffer = decoder->stream.ptr = buf; - decoder->stream.end = buf + bytes; + codec->stream.buffer = codec->stream.ptr = buf; + codec->stream.end = buf + bytes; #ifdef _WIN32 - SetEvent(decoder->hDataEvent); - WaitForSingleObject(decoder->hDecodeEvent); + SetEvent(codec->hDataEvent); + WaitForSingleObject(codec->hCodecEvent); #else - pthread_cond_signal(&decoder->data_cond); - pthread_mutex_unlock(&decoder->data_mutex); + pthread_cond_signal(&codec->data_cond); + pthread_mutex_unlock(&codec->data_mutex); - pthread_mutex_lock(&decoder->decode_mutex); - pthread_cond_wait(&decoder->decode_cond, &decoder->decode_mutex); - pthread_mutex_unlock(&decoder->decode_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", decoder->result); + DEBUG("got result %d\n", codec->result); - return decoder->result; + return codec->result; } size_t -ImagingIncrementalDecoderRead(ImagingIncrementalDecoder decoder, +ImagingIncrementalCodecBytesInBuffer(ImagingIncrementalCodec codec) +{ + return codec->stream.ptr - codec->stream.buffer; +} + +size_t +ImagingIncrementalCodecRead(ImagingIncrementalCodec codec, void *buffer, size_t bytes) { UINT8 *ptr = (UINT8 *)buffer; @@ -308,29 +308,29 @@ ImagingIncrementalDecoderRead(ImagingIncrementalDecoder decoder, DEBUG("reading (want %llu bytes)\n", (unsigned long long)bytes); #ifndef _WIN32 - pthread_mutex_lock(&decoder->data_mutex); + pthread_mutex_lock(&codec->data_mutex); #endif while (bytes) { size_t todo = bytes; - size_t remaining = decoder->stream.end - decoder->stream.ptr; + size_t remaining = codec->stream.end - codec->stream.ptr; if (!remaining) { DEBUG("waiting for data\n"); #ifndef _WIN32 - pthread_mutex_lock(&decoder->decode_mutex); + pthread_mutex_lock(&codec->codec_mutex); #endif - decoder->result = (int)(decoder->stream.ptr - decoder->stream.buffer); + codec->result = (int)(codec->stream.ptr - codec->stream.buffer); #if _WIN32 - SetEvent(decoder->hDecodeEvent); - WaitForSingleObject(decoder->hDataEvent); + SetEvent(codec->hCodecEvent); + WaitForSingleObject(codec->hDataEvent); #else - pthread_cond_signal(&decoder->decode_cond); - pthread_mutex_unlock(&decoder->decode_mutex); - pthread_cond_wait(&decoder->data_cond, &decoder->data_mutex); + pthread_cond_signal(&codec->codec_cond); + pthread_mutex_unlock(&codec->codec_mutex); + pthread_cond_wait(&codec->data_cond, &codec->data_mutex); #endif - remaining = decoder->stream.end - decoder->stream.ptr; + remaining = codec->stream.end - codec->stream.ptr; DEBUG("got %llu bytes\n", (unsigned long long)remaining); } @@ -340,14 +340,14 @@ ImagingIncrementalDecoderRead(ImagingIncrementalDecoder decoder, if (!todo) break; - memcpy (ptr, decoder->stream.ptr, todo); - decoder->stream.ptr += todo; + memcpy (ptr, codec->stream.ptr, todo); + codec->stream.ptr += todo; bytes -= todo; done += todo; ptr += todo; } #ifndef _WIN32 - pthread_mutex_unlock(&decoder->data_mutex); + pthread_mutex_unlock(&codec->data_mutex); #endif DEBUG("read total %llu bytes\n", (unsigned long long)done); @@ -356,7 +356,7 @@ ImagingIncrementalDecoderRead(ImagingIncrementalDecoder decoder, } off_t -ImagingIncrementalDecoderSkip(ImagingIncrementalDecoder decoder, +ImagingIncrementalCodecSkip(ImagingIncrementalCodec codec, off_t bytes) { off_t done = 0; @@ -364,29 +364,29 @@ ImagingIncrementalDecoderSkip(ImagingIncrementalDecoder decoder, DEBUG("skipping (want %llu bytes)\n", (unsigned long long)bytes); #ifndef _WIN32 - pthread_mutex_lock(&decoder->data_mutex); + pthread_mutex_lock(&codec->data_mutex); #endif while (bytes) { off_t todo = bytes; - off_t remaining = decoder->stream.end - decoder->stream.ptr; + off_t remaining = codec->stream.end - codec->stream.ptr; if (!remaining) { DEBUG("waiting for data\n"); #ifndef _WIN32 - pthread_mutex_lock(&decoder->decode_mutex); + pthread_mutex_lock(&codec->codec_mutex); #endif - decoder->result = (int)(decoder->stream.ptr - decoder->stream.buffer); + codec->result = (int)(codec->stream.ptr - codec->stream.buffer); #if _WIN32 - SetEvent(decoder->hDecodeEvent); - WaitForSingleObject(decoder->hDataEvent); + SetEvent(codec->hCodecEvent); + WaitForSingleObject(codec->hDataEvent); #else - pthread_cond_signal(&decoder->decode_cond); - pthread_mutex_unlock(&decoder->decode_mutex); - pthread_cond_wait(&decoder->data_cond, &decoder->data_mutex); + pthread_cond_signal(&codec->codec_cond); + pthread_mutex_unlock(&codec->codec_mutex); + pthread_cond_wait(&codec->data_cond, &codec->data_mutex); #endif - remaining = decoder->stream.end - decoder->stream.ptr; + remaining = codec->stream.end - codec->stream.ptr; } if (todo > remaining) todo = remaining; @@ -394,12 +394,12 @@ ImagingIncrementalDecoderSkip(ImagingIncrementalDecoder decoder, if (!todo) break; - decoder->stream.ptr += todo; + codec->stream.ptr += todo; bytes -= todo; done += todo; } #ifndef _WIN32 - pthread_mutex_unlock(&decoder->data_mutex); + pthread_mutex_unlock(&codec->data_mutex); #endif DEBUG("skipped total %llu bytes\n", (unsigned long long)done); @@ -407,3 +407,59 @@ ImagingIncrementalDecoderSkip(ImagingIncrementalDecoder decoder, return done; } +size_t +ImagingIncrementalCodecWrite(ImagingIncrementalCodec codec, + const void *buffer, size_t bytes) +{ + const UINT8 *ptr = (const UINT8 *)buffer; + size_t done = 0; + + DEBUG("write (have %llu bytes)\n", (unsigned long long)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) { + 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); +#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; + } +#ifndef _WIN32 + pthread_mutex_unlock(&codec->data_mutex); +#endif + + DEBUG("wrote total %llu bytes\n", (unsigned long long)done); + + return done; +} diff --git a/libImaging/Jpeg2K.h b/libImaging/Jpeg2K.h index 269357296..128302f19 100644 --- a/libImaging/Jpeg2K.h +++ b/libImaging/Jpeg2K.h @@ -12,13 +12,11 @@ /* -------------------------------------------------------------------- */ /* Decoder */ +/* -------------------------------------------------------------------- */ typedef struct { /* CONFIGURATION */ - /* Output mode */ - char mode[8]; - /* Specify the desired format */ OPJ_CODEC_FORMAT format; @@ -31,10 +29,50 @@ typedef struct { /* PRIVATE CONTEXT (set by decoder) */ const char *error_msg; - ImagingIncrementalDecoder decoder; + ImagingIncrementalCodec decoder; +} JPEG2KDECODESTATE; - opj_stream_t *stream; -} JPEG2KSTATE; +/* -------------------------------------------------------------------- */ +/* Encoder */ +/* -------------------------------------------------------------------- */ + +typedef struct { + /* CONFIGURATION */ + + /* 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; + + /* 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: diff --git a/libImaging/Jpeg2KDecode.c b/libImaging/Jpeg2KDecode.c index 77615cce5..c3254d889 100644 --- a/libImaging/Jpeg2KDecode.c +++ b/libImaging/Jpeg2KDecode.c @@ -34,7 +34,7 @@ typedef struct { static void j2k_error(const char *msg, void *client_data) { - JPEG2KSTATE *state = (JPEG2KSTATE *) client_data; + JPEG2KDECODESTATE *state = (JPEG2KDECODESTATE *) client_data; free((void *)state->error_msg); state->error_msg = strdup(msg); } @@ -46,9 +46,9 @@ j2k_error(const char *msg, void *client_data) static OPJ_SIZE_T j2k_read(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) { - ImagingIncrementalDecoder decoder = (ImagingIncrementalDecoder)p_user_data; + ImagingIncrementalCodec decoder = (ImagingIncrementalCodec)p_user_data; - size_t len = ImagingIncrementalDecoderRead(decoder, p_buffer, p_nb_bytes); + size_t len = ImagingIncrementalCodecRead(decoder, p_buffer, p_nb_bytes); return len ? len : (OPJ_SIZE_T)-1; } @@ -65,8 +65,8 @@ j2k_write(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) static OPJ_OFF_T j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data) { - ImagingIncrementalDecoder decoder = (ImagingIncrementalDecoder)p_user_data; - off_t pos = ImagingIncrementalDecoderSkip(decoder, p_nb_bytes); + ImagingIncrementalCodec decoder = (ImagingIncrementalCodec)p_user_data; + off_t pos = ImagingIncrementalCodecSkip(decoder, p_nb_bytes); return pos ? pos : (OPJ_OFF_T)-1; } @@ -455,9 +455,9 @@ enum { static int j2k_decode_entry(Imaging im, ImagingCodecState state, - ImagingIncrementalDecoder decoder) + ImagingIncrementalCodec decoder) { - JPEG2KSTATE *context = (JPEG2KSTATE *) state->context; + JPEG2KDECODESTATE *context = (JPEG2KDECODESTATE *) state->context; opj_stream_t *stream = NULL; opj_image_t *image = NULL; opj_codec_t *codec = NULL; @@ -482,7 +482,6 @@ j2k_decode_entry(Imaging im, ImagingCodecState state, opj_stream_set_user_data(stream, context->decoder); - /* Setup decompression context */ context->error_msg = NULL; @@ -559,7 +558,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state, 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 (context->mode, j2k_unpackers[n].mode) == 0) { + && strcmp (im->mode, j2k_unpackers[n].mode) == 0) { unpack = j2k_unpackers[n].unpacker; break; } @@ -617,6 +616,14 @@ j2k_decode_entry(Imaging im, ImagingCodecState state, 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; + quick_exit: if (codec) opj_destroy_codec(codec); @@ -631,14 +638,14 @@ j2k_decode_entry(Imaging im, ImagingCodecState state, int ImagingJpeg2KDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) { - JPEG2KSTATE *context = (JPEG2KSTATE *) state->context; + 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 = ImagingIncrementalDecoderCreate(j2k_decode_entry, - im, state); + context->decoder = ImagingIncrementalCodecCreate(j2k_decode_entry, + im, state); if (!context->decoder) { state->errcode = IMAGING_CODEC_BROKEN; @@ -649,7 +656,7 @@ ImagingJpeg2KDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) state->state = J2K_STATE_DECODING; } - return ImagingIncrementalDecodeData(context->decoder, buf, bytes); + return ImagingIncrementalCodecPushBuffer(context->decoder, buf, bytes); } /* -------------------------------------------------------------------- */ @@ -658,10 +665,13 @@ ImagingJpeg2KDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) int ImagingJpeg2KDecodeCleanup(ImagingCodecState state) { - JPEG2KSTATE *context = (JPEG2KSTATE *)state->context; + JPEG2KDECODESTATE *context = (JPEG2KDECODESTATE *)state->context; + + if (context->error_msg) + free ((void *)context->error_msg); if (context->decoder) - ImagingIncrementalDecoderDestroy(context->decoder); + ImagingIncrementalCodecDestroy(context->decoder); return -1; } diff --git a/libImaging/Jpeg2KEncode.c b/libImaging/Jpeg2KEncode.c new file mode 100644 index 000000000..75f40d58f --- /dev/null +++ b/libImaging/Jpeg2KEncode.c @@ -0,0 +1,548 @@ +/* + * 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_read(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) +{ + /* This should never happen */ + fprintf (stderr, "OpenJPEG has read from our write stream(!)"); + abort(); + return (OPJ_SIZE_T)-1; +} + +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 decoder = (ImagingIncrementalCodec)p_user_data; + off_t pos = ImagingIncrementalCodecSkip(decoder, 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) +{ + /* This should never happen */ + fprintf(stderr, "OpenJPEG tried to seek our write stream(!)"); + abort(); + return OPJ_FALSE; +} + +/* -------------------------------------------------------------------- */ +/* 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; + for (unsigned y = 0; y < h; ++y) { + UINT8 *data = (UINT8 *)(im->image[y + y0] + x0); + for (unsigned 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; + for (unsigned y = 0; y < h; ++y) { + UINT8 *data = (UINT8 *)(im->image[y + y0] + 4 * x0); + for (unsigned 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; + for (unsigned y = 0; y < h; ++y) { + UINT8 *data = (UINT8 *)(im->image[y + y0] + 4 * x0); + for (unsigned 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; + for (unsigned y = 0; y < h; ++y) { + UINT8 *data = (UINT8 *)(im->image[y + y0] + 4 * x0); + for (unsigned 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; + 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_read_function(stream, j2k_read); + 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, context->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 (unsigned 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(¶ms); + + 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 (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; + } + + 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, ¶ms); + + /* 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, ¶ms, 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 + tile_width - 1) / tile_width; + tiles_y = (im->ysize + 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_DONE || state->state == J2K_STATE_FAILED) + return -1; + + if (state->state == J2K_STATE_START) { + context->encoder = ImagingIncrementalCodecCreate(j2k_encode_entry, + im, state); + + 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: + * + */ diff --git a/setup.py b/setup.py index e948e1050..8d3511e0c 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ _LIB_IMAGING = ( "RankFilter", "RawDecode", "RawEncode", "Storage", "SunRleDecode", "TgaRleDecode", "Unpack", "UnpackYCC", "UnsharpMask", "XbmDecode", "XbmEncode", "ZipDecode", "ZipEncode", "TiffDecode", "Incremental", - "Jpeg2KDecode") + "Jpeg2KDecode", "Jpeg2KEncode") def _add_directory(path, dir, where=None): From cb1f990a925c5a5e4cb38a4fdba631896474da1a Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Fri, 14 Mar 2014 11:21:08 +0000 Subject: [PATCH 07/15] Added seek support to make writing jp2 files work. Also added support for directly using an fd rather than relying on the Python loop, if we have a real fd. --- PIL/Jpeg2KImagePlugin.py | 38 +++++- decode.c | 6 +- encode.c | 12 +- libImaging/Imaging.h | 17 ++- libImaging/Incremental.c | 241 ++++++++++++++++++++++++++++++++++---- libImaging/Jpeg2K.h | 6 + libImaging/Jpeg2KDecode.c | 26 +--- libImaging/Jpeg2KEncode.c | 33 +++--- 8 files changed, 306 insertions(+), 73 deletions(-) diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index 94d49f705..6b1a36f3f 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -156,9 +156,13 @@ class Jpeg2KImageFile(ImageFile.ImageFile): self.reduce = 0 self.layers = 0 + fd = -1 + if hasattr(self.fp, "fileno"): + fd = self.fp.fileno() + self.tile = [('jpeg2k', (0, 0) + self.size, 0, - (self.codec, self.reduce, self.layers))] + (self.codec, self.reduce, self.layers, fd))] def load(self): if self.reduce: @@ -181,6 +185,38 @@ def _save(im, fp, filename): 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) + irreversible = info.get('irreversible', False) + progression = info.get('progression', 'LRCP') + cinema_mode = info.get('cinema_mode', 'no') + fd = -1 + + if hasattr(fp, "fileno"): + fd = fp.fileno() + + im.encoderconfig = ( + offset, + tile_offset, + tile_size, + quality_mode, + quality_layers, + num_resolutions, + cblk_size, + irreversible, + progression, + cinema_mode, + fd + ) ImageFile._save(im, fp, [('jpeg2k', (0, 0)+im.size, 0, kind)]) diff --git a/decode.c b/decode.c index 0bc64ec77..f3eaa9a50 100644 --- a/decode.c +++ b/decode.c @@ -796,8 +796,9 @@ PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args) OPJ_CODEC_FORMAT codec_format; int reduce = 0; int layers = 0; - if (!PyArg_ParseTuple(args, "ss|ii", &mode, &format, - &reduce, &layers)) + int fd = -1; + if (!PyArg_ParseTuple(args, "ss|iii", &mode, &format, + &reduce, &layers, &fd)) return NULL; if (strcmp(format, "j2k") == 0) @@ -819,6 +820,7 @@ PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args) context = (JPEG2KDECODESTATE *)decoder->state.context; + context->fd = fd; context->format = codec_format; context->reduce = reduce; context->layers = layers; diff --git a/encode.c b/encode.c index 979742a84..52777cc0c 100644 --- a/encode.c +++ b/encode.c @@ -837,16 +837,18 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) PyObject *quality_layers = NULL; int num_resolutions = 0; PyObject *cblk_size = NULL; - int irreversible = 0; + 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|OOOsOiOpss", &mode, &format, + if (!PyArg_ParseTuple(args, "ss|OOOsOIOOssi", &mode, &format, &offset, &tile_offset, &tile_size, &quality_mode, &quality_layers, &num_resolutions, - &cblk_size, &irreversible, &progression, &cinema_mode)) + &cblk_size, &irreversible, &progression, &cinema_mode, + &fd)) return NULL; if (strcmp (format, "j2k") == 0) @@ -891,6 +893,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) context = (JPEG2KENCODESTATE *)encoder->state.context; + context->fd = fd; context->format = codec_format; context->offset_x = context->offset_y = 0; @@ -905,6 +908,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) 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; @@ -913,7 +917,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) &context->cblk_width, &context->cblk_height); - context->irreversible = irreversible; + context->irreversible = PyObject_IsTrue(irreversible); context->progression = prog_order; context->cinema_mode = cine_mode; diff --git a/libImaging/Imaging.h b/libImaging/Imaging.h index 1602622a2..26207d121 100644 --- a/libImaging/Imaging.h +++ b/libImaging/Imaging.h @@ -512,12 +512,23 @@ typedef int (*ImagingIncrementalCodecEntry)(Imaging im, ImagingCodecState state, ImagingIncrementalCodec codec); -extern ImagingIncrementalCodec ImagingIncrementalCodecCreate(ImagingIncrementalCodecEntry codec_entry, Imaging im, ImagingCodecState state); +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 size_t ImagingIncrementalCodecRead(ImagingIncrementalCodec codec, void *buffer, size_t bytes); +extern ssize_t ImagingIncrementalCodecRead(ImagingIncrementalCodec codec, void *buffer, size_t bytes); extern off_t ImagingIncrementalCodecSkip(ImagingIncrementalCodec codec, off_t bytes); -extern size_t ImagingIncrementalCodecWrite(ImagingIncrementalCodec codec, const void *buffer, size_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 */ diff --git a/libImaging/Incremental.c b/libImaging/Incremental.c index 45f064570..bc6a36b3e 100644 --- a/libImaging/Incremental.c +++ b/libImaging/Incremental.c @@ -35,6 +35,11 @@ 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 #include @@ -64,18 +69,24 @@ struct ImagingIncrementalCodecStruct { pthread_cond_t data_cond; pthread_t thread; #endif - ImagingIncrementalCodecEntry entry; - Imaging im; - ImagingCodecState state; + ImagingIncrementalCodecEntry entry; + Imaging im; + ImagingCodecState state; struct { - UINT8 *buffer; - UINT8 *ptr; - UINT8 *end; + 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 started; - int result; + int read_or_write; + int seekable; + int started; + int result; }; +static void flush_stream(ImagingIncrementalCodec codec); + #if _WIN32 static void __stdcall codec_thread(void *ptr) @@ -84,6 +95,8 @@ codec_thread(void *ptr) codec->result = codec->entry(codec->im, codec->state, codec); + flush_stream(codec); + SetEvent(codec->hCodecEvent); } #else @@ -94,18 +107,54 @@ codec_thread(void *ptr) codec->result = codec->entry(codec->im, codec->state, codec); + 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) + ImagingCodecState state, + int read_or_write, + int seekable, + int fd) { ImagingIncrementalCodec codec = (ImagingIncrementalCodec)malloc(sizeof(struct ImagingIncrementalCodecStruct)); @@ -113,8 +162,15 @@ ImagingIncrementalCodecCreate(ImagingIncrementalCodecEntry codec_entry, codec->im = im; codec->state = state; codec->result = 0; - codec->stream.buffer = codec->stream.ptr = codec->stream.end = NULL; + 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 @@ -223,7 +279,11 @@ ImagingIncrementalCodecDestroy(ImagingIncrementalCodec codec) pthread_mutex_lock(&codec->data_mutex); #endif - codec->stream.buffer = codec->stream.ptr = codec->stream.end = NULL; + 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); @@ -264,6 +324,22 @@ ImagingIncrementalCodecPushBuffer(ImagingIncrementalCodec codec, pthread_cond_signal(&codec->start_cond); #endif codec->started = 1; + + /* Wait for the thread to ask for data */ +#ifdef _WIN32 + WaitForSingleObject(codec->hCodecEvent); +#else + pthread_mutex_lock(&codec->codec_mutex); + pthread_cond_wait(&codec->codec_cond, &codec->codec_mutex); + pthread_mutex_unlock(&codec->codec_mutex); +#endif + } + + /* 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); @@ -272,8 +348,30 @@ ImagingIncrementalCodecPushBuffer(ImagingIncrementalCodec codec, pthread_mutex_lock(&codec->data_mutex); #endif - codec->stream.buffer = codec->stream.ptr = buf; - codec->stream.end = buf + bytes; + 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); @@ -298,15 +396,27 @@ ImagingIncrementalCodecBytesInBuffer(ImagingIncrementalCodec codec) return codec->stream.ptr - codec->stream.buffer; } -size_t +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) { + off_t offset = lseek(codec->stream.fd, 0, SEEK_CUR); + ssize_t ret = read(codec->stream.fd, buffer, bytes); + DEBUG("read %lld bytes from fd at %lld\n", (long long)ret, (long long)offset); + return ret; + } + #ifndef _WIN32 pthread_mutex_lock(&codec->data_mutex); #endif @@ -331,9 +441,11 @@ ImagingIncrementalCodecRead(ImagingIncrementalCodec codec, #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; @@ -357,12 +469,30 @@ ImagingIncrementalCodecRead(ImagingIncrementalCodec codec, off_t ImagingIncrementalCodecSkip(ImagingIncrementalCodec codec, - off_t bytes) + 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 = 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 @@ -388,6 +518,7 @@ ImagingIncrementalCodecSkip(ImagingIncrementalCodec codec, remaining = codec->stream.end - codec->stream.ptr; } + if (todo > remaining) todo = remaining; @@ -407,15 +538,23 @@ ImagingIncrementalCodecSkip(ImagingIncrementalCodec codec, return done; } -size_t +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 @@ -424,20 +563,40 @@ ImagingIncrementalCodecWrite(ImagingIncrementalCodec codec, size_t remaining = codec->stream.end - codec->stream.ptr; if (!remaining) { - DEBUG("waiting for space\n"); + 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); + pthread_mutex_lock(&codec->codec_mutex); #endif - codec->result = (int)(codec->stream.ptr - codec->stream.buffer); + codec->result = (int)(codec->stream.ptr - codec->stream.buffer); #if _WIN32 - SetEvent(codec->hCodecEvent); - WaitForSingleObject(codec->hDataEvent); + SetEvent(codec->hCodecEvent); + WaitForSingleObject(codec->hDataEvent); #else - pthread_cond_signal(&codec->codec_cond); - pthread_mutex_unlock(&codec->codec_mutex); - pthread_cond_wait(&codec->data_cond, &codec->data_mutex); + 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; @@ -455,6 +614,10 @@ ImagingIncrementalCodecWrite(ImagingIncrementalCodec codec, 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 @@ -463,3 +626,33 @@ ImagingIncrementalCodecWrite(ImagingIncrementalCodec codec, 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); +} diff --git a/libImaging/Jpeg2K.h b/libImaging/Jpeg2K.h index 128302f19..ba4b53a7f 100644 --- a/libImaging/Jpeg2K.h +++ b/libImaging/Jpeg2K.h @@ -17,6 +17,9 @@ typedef struct { /* CONFIGURATION */ + /* File descriptor, if available; otherwise, -1 */ + int fd; + /* Specify the desired format */ OPJ_CODEC_FORMAT format; @@ -39,6 +42,9 @@ typedef struct { typedef struct { /* CONFIGURATION */ + /* File descriptor, if available; otherwise, -1 */ + int fd; + /* Specify the desired format */ OPJ_CODEC_FORMAT format; diff --git a/libImaging/Jpeg2KDecode.c b/libImaging/Jpeg2KDecode.c index c3254d889..9c4e16b1f 100644 --- a/libImaging/Jpeg2KDecode.c +++ b/libImaging/Jpeg2KDecode.c @@ -53,15 +53,6 @@ j2k_read(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) return len ? len : (OPJ_SIZE_T)-1; } -static OPJ_SIZE_T -j2k_write(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) -{ - /* This should never happen */ - fprintf(stderr, "OpenJPEG has written to our read stream(!)"); - abort(); - return (OPJ_SIZE_T)-1; -} - static OPJ_OFF_T j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data) { @@ -71,15 +62,6 @@ j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data) return pos ? pos : (OPJ_OFF_T)-1; } -static OPJ_BOOL -j2k_seek(OPJ_OFF_T p_nb_bytes, void *p_user_data) -{ - /* This should never happen */ - fprintf(stderr, "OpenJPEG tried to seek our read stream(!)"); - abort(); - return OPJ_FALSE; -} - /* -------------------------------------------------------------------- */ /* Unpackers */ /* -------------------------------------------------------------------- */ @@ -476,9 +458,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state, } opj_stream_set_read_function(stream, j2k_read); - 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, context->decoder); @@ -623,6 +603,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state, } state->state = J2K_STATE_DONE; + state->errcode = IMAGING_CODEC_END; quick_exit: if (codec) @@ -645,7 +626,10 @@ ImagingJpeg2KDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) if (state->state == J2K_STATE_START) { context->decoder = ImagingIncrementalCodecCreate(j2k_decode_entry, - im, state); + im, state, + INCREMENTAL_CODEC_READ, + INCREMENTAL_CODEC_NOT_SEEKABLE, + context->fd); if (!context->decoder) { state->errcode = IMAGING_CODEC_BROKEN; diff --git a/libImaging/Jpeg2KEncode.c b/libImaging/Jpeg2KEncode.c index 75f40d58f..f5feaf72e 100644 --- a/libImaging/Jpeg2KEncode.c +++ b/libImaging/Jpeg2KEncode.c @@ -40,15 +40,6 @@ j2k_error(const char *msg, void *client_data) /* Buffer output stream */ /* -------------------------------------------------------------------- */ -static OPJ_SIZE_T -j2k_read(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) -{ - /* This should never happen */ - fprintf (stderr, "OpenJPEG has read from our write stream(!)"); - abort(); - return (OPJ_SIZE_T)-1; -} - static OPJ_SIZE_T j2k_write(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) { @@ -61,8 +52,8 @@ j2k_write(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) 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); + ImagingIncrementalCodec encoder = (ImagingIncrementalCodec)p_user_data; + off_t pos = ImagingIncrementalCodecSkip(encoder, p_nb_bytes); return pos ? pos : (OPJ_OFF_T)-1; } @@ -70,10 +61,10 @@ j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data) static OPJ_BOOL j2k_seek(OPJ_OFF_T p_nb_bytes, void *p_user_data) { - /* This should never happen */ - fprintf(stderr, "OpenJPEG tried to seek our write stream(!)"); - abort(); - return OPJ_FALSE; + ImagingIncrementalCodec encoder = (ImagingIncrementalCodec)p_user_data; + off_t pos = ImagingIncrementalCodecSeek(encoder, p_nb_bytes); + + return pos == p_nb_bytes; } /* -------------------------------------------------------------------- */ @@ -259,7 +250,6 @@ j2k_encode_entry(Imaging im, ImagingCodecState state, goto quick_exit; } - opj_stream_set_read_function(stream, j2k_read); opj_stream_set_write_function(stream, j2k_write); opj_stream_set_skip_function(stream, j2k_skip); opj_stream_set_seek_function(stream, j2k_seek); @@ -499,12 +489,19 @@ ImagingJpeg2KEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { JPEG2KENCODESTATE *context = (JPEG2KENCODESTATE *)state->context; - if (state->state == J2K_STATE_DONE || state->state == J2K_STATE_FAILED) + 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); + im, state, + INCREMENTAL_CODEC_WRITE, + seekable, + context->fd); if (!context->encoder) { state->errcode = IMAGING_CODEC_BROKEN; From 5cb73c94e947f8567159618b609227a2900f1082 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Fri, 14 Mar 2014 14:35:09 +0000 Subject: [PATCH 08/15] Fixed some Windows issues. --- libImaging/Incremental.c | 14 ++++++++------ setup.py | 13 ++++++++++++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/libImaging/Incremental.c b/libImaging/Incremental.c index bc6a36b3e..b3a1cb00a 100644 --- a/libImaging/Incremental.c +++ b/libImaging/Incremental.c @@ -88,7 +88,7 @@ struct ImagingIncrementalCodecStruct { static void flush_stream(ImagingIncrementalCodec codec); #if _WIN32 -static void __stdcall +static unsigned int __stdcall codec_thread(void *ptr) { ImagingIncrementalCodec codec = (ImagingIncrementalCodec)ptr; @@ -98,6 +98,8 @@ codec_thread(void *ptr) flush_stream(codec); SetEvent(codec->hCodecEvent); + + return 0; } #else static void * @@ -327,7 +329,7 @@ ImagingIncrementalCodecPushBuffer(ImagingIncrementalCodec codec, /* Wait for the thread to ask for data */ #ifdef _WIN32 - WaitForSingleObject(codec->hCodecEvent); + WaitForSingleObject(codec->hCodecEvent, INFINITE); #else pthread_mutex_lock(&codec->codec_mutex); pthread_cond_wait(&codec->codec_cond, &codec->codec_mutex); @@ -375,7 +377,7 @@ ImagingIncrementalCodecPushBuffer(ImagingIncrementalCodec codec, #ifdef _WIN32 SetEvent(codec->hDataEvent); - WaitForSingleObject(codec->hCodecEvent); + WaitForSingleObject(codec->hCodecEvent, INFINITE); #else pthread_cond_signal(&codec->data_cond); pthread_mutex_unlock(&codec->data_mutex); @@ -433,7 +435,7 @@ ImagingIncrementalCodecRead(ImagingIncrementalCodec codec, codec->result = (int)(codec->stream.ptr - codec->stream.buffer); #if _WIN32 SetEvent(codec->hCodecEvent); - WaitForSingleObject(codec->hDataEvent); + WaitForSingleObject(codec->hDataEvent, INFINITE); #else pthread_cond_signal(&codec->codec_cond); pthread_mutex_unlock(&codec->codec_mutex); @@ -509,7 +511,7 @@ ImagingIncrementalCodecSkip(ImagingIncrementalCodec codec, codec->result = (int)(codec->stream.ptr - codec->stream.buffer); #if _WIN32 SetEvent(codec->hCodecEvent); - WaitForSingleObject(codec->hDataEvent); + WaitForSingleObject(codec->hDataEvent, INFINITE); #else pthread_cond_signal(&codec->codec_cond); pthread_mutex_unlock(&codec->codec_mutex); @@ -590,7 +592,7 @@ ImagingIncrementalCodecWrite(ImagingIncrementalCodec codec, codec->result = (int)(codec->stream.ptr - codec->stream.buffer); #if _WIN32 SetEvent(codec->hCodecEvent); - WaitForSingleObject(codec->hDataEvent); + WaitForSingleObject(codec->hDataEvent, INFINITE); #else pthread_cond_signal(&codec->codec_cond); pthread_mutex_unlock(&codec->codec_mutex); diff --git a/setup.py b/setup.py index 8d3511e0c..7d387b822 100644 --- a/setup.py +++ b/setup.py @@ -89,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 @@ -152,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 @@ -323,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 From 9a1b6966b51dc5b2e07d95faa27c0c433b66a088 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Fri, 14 Mar 2014 15:40:30 +0000 Subject: [PATCH 09/15] Added precinct size option. Also added the jp2klib_version symbol on the _imaging module. --- PIL/Jpeg2KImagePlugin.py | 2 ++ _imaging.c | 7 +++++++ encode.c | 10 +++++++--- libImaging/Jpeg2K.h | 3 +++ libImaging/Jpeg2KEncode.c | 9 +++++++++ 5 files changed, 28 insertions(+), 3 deletions(-) diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index 6b1a36f3f..26eec237d 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -196,6 +196,7 @@ def _save(im, fp, filename): 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') @@ -212,6 +213,7 @@ def _save(im, fp, filename): quality_layers, num_resolutions, cblk_size, + precinct_size, irreversible, progression, cinema_mode, diff --git a/_imaging.c b/_imaging.c index fe623e780..987bd0383 100644 --- a/_imaging.c +++ b/_imaging.c @@ -3461,6 +3461,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); diff --git a/encode.c b/encode.c index 52777cc0c..0c444bfc3 100644 --- a/encode.c +++ b/encode.c @@ -836,7 +836,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) char *quality_mode = "rates"; PyObject *quality_layers = NULL; int num_resolutions = 0; - PyObject *cblk_size = NULL; + PyObject *cblk_size = NULL, *precinct_size = NULL; PyObject *irreversible = NULL; char *progression = "LRCP"; OPJ_PROG_ORDER prog_order; @@ -844,10 +844,11 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) OPJ_CINEMA_MODE cine_mode; int fd = -1; - if (!PyArg_ParseTuple(args, "ss|OOOsOIOOssi", &mode, &format, + if (!PyArg_ParseTuple(args, "ss|OOOsOIOOOssi", &mode, &format, &offset, &tile_offset, &tile_size, &quality_mode, &quality_layers, &num_resolutions, - &cblk_size, &irreversible, &progression, &cinema_mode, + &cblk_size, &precinct_size, + &irreversible, &progression, &cinema_mode, &fd)) return NULL; @@ -916,6 +917,9 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) 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; diff --git a/libImaging/Jpeg2K.h b/libImaging/Jpeg2K.h index ba4b53a7f..be6e0770b 100644 --- a/libImaging/Jpeg2K.h +++ b/libImaging/Jpeg2K.h @@ -65,6 +65,9 @@ typedef struct { /* Code block size */ int cblk_width, cblk_height; + /* Precinct size */ + int precinct_width, precinct_height; + /* Compression style */ int irreversible; diff --git a/libImaging/Jpeg2KEncode.c b/libImaging/Jpeg2KEncode.c index f5feaf72e..6bf25def8 100644 --- a/libImaging/Jpeg2KEncode.c +++ b/libImaging/Jpeg2KEncode.c @@ -364,6 +364,15 @@ j2k_encode_entry(Imaging im, ImagingCodecState state, 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; From 168acabcb09c0d30d98874fa1b76a337cd5f5d0c Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Fri, 14 Mar 2014 16:38:07 +0000 Subject: [PATCH 10/15] Fixed some warnings. --- libImaging/Incremental.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libImaging/Incremental.c b/libImaging/Incremental.c index b3a1cb00a..8e42b2e64 100644 --- a/libImaging/Incremental.c +++ b/libImaging/Incremental.c @@ -413,9 +413,8 @@ ImagingIncrementalCodecRead(ImagingIncrementalCodec codec, DEBUG("reading (want %llu bytes)\n", (unsigned long long)bytes); if (codec->stream.fd >= 0) { - off_t offset = lseek(codec->stream.fd, 0, SEEK_CUR); ssize_t ret = read(codec->stream.fd, buffer, bytes); - DEBUG("read %lld bytes from fd at %lld\n", (long long)ret, (long long)offset); + DEBUG("read %lld bytes from fd\n", (long long)ret); return ret; } @@ -482,7 +481,7 @@ ImagingIncrementalCodecSkip(ImagingIncrementalCodec codec, static const UINT8 zeroes[256] = { 0 }; off_t done = 0; while (bytes) { - size_t todo = bytes > 256 ? 256 : bytes; + size_t todo = (size_t)(bytes > 256 ? 256 : bytes); ssize_t written = ImagingIncrementalCodecWrite(codec, zeroes, todo); if (written <= 0) break; From 5853d2aed02abbc225323d15e1a838e2d12b2fca Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Fri, 14 Mar 2014 16:38:30 +0000 Subject: [PATCH 11/15] Add a couple of unpackers. --- libImaging/Jpeg2KDecode.c | 57 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/libImaging/Jpeg2KDecode.c b/libImaging/Jpeg2KDecode.c index 9c4e16b1f..a39c700ed 100644 --- a/libImaging/Jpeg2KDecode.c +++ b/libImaging/Jpeg2KDecode.c @@ -410,6 +410,60 @@ j2ku_srgba_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, } } +static void +j2ku_sycca_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, + const UINT8 *tiledata, Imaging im) +{ + unsigned x0 = tileinfo->x0, y0 = tileinfo->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; + + 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 }, @@ -417,11 +471,14 @@ static const struct j2k_decode_unpacker j2k_unpackers[] = { { "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 }, }; /* -------------------------------------------------------------------- */ From 646f0c21e5b5fff3edca6029d05dd30df7a7bc3f Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Fri, 14 Mar 2014 16:38:50 +0000 Subject: [PATCH 12/15] Updated CHANGES file. --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index abdf73b33..ac5247f63 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.4.0 (unreleased) ------------------ +- Added support for JPEG 2000 + [al45tair] + - Fixed saving mode P image as a PNG with transparency = palette color 0 [d-schmidt] From efd47ce63ba8cebc86eddf076e1fee00ed3a2811 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Fri, 14 Mar 2014 16:39:09 +0000 Subject: [PATCH 13/15] Added documentation for JPEG 2000. --- docs/handbook/image-file-formats.rst | 86 ++++++++++++++++++++++++++++ docs/plugins.rst | 8 +++ 2 files changed, 94 insertions(+) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 21cd615fc..913c47c76 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -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 ^^^ diff --git a/docs/plugins.rst b/docs/plugins.rst index b92b500c1..001cee949 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -153,6 +153,14 @@ Plugin reference :undoc-members: :show-inheritance: +:mod:`Jpeg2KImagePlugin` Module +----------------------------- + +.. automodule:: PIL.Jpeg2KImagePlugin + :members: + :undoc-members: + :show-inheritance: + :mod:`McIdasImagePlugin` Module ------------------------------- From 6840278b7d3781d2fe4159e66c58c27d20588184 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Fri, 14 Mar 2014 16:49:29 +0000 Subject: [PATCH 14/15] Removed print statement. --- PIL/Jpeg2KImagePlugin.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index 26eec237d..36e15b762 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -138,10 +138,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile): sig = self.fp.read(4) if sig == b'\xff\x4f\xff\x51': self.codec = "j2k" - try: - self.size, self.mode = _parse_codestream(self.fp) - except Exception as e: - print e + self.size, self.mode = _parse_codestream(self.fp) else: sig = sig + self.fp.read(8) From 68fd58a7e292c61357e0cae8f9783f91e437d52a Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Fri, 14 Mar 2014 17:25:38 +0000 Subject: [PATCH 15/15] Initialize handles_eof (oops). --- decode.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/decode.c b/decode.c index f3eaa9a50..f1e4aeaba 100644 --- a/decode.c +++ b/decode.c @@ -94,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; }