mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-24 17:06:16 +03:00
Merging JPEG 2000 support with .icns plugin.
This commit is contained in:
commit
8eec69d39e
|
@ -4,6 +4,9 @@ Changelog (Pillow)
|
||||||
2.4.0 (unreleased)
|
2.4.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Added support for JPEG 2000
|
||||||
|
[al45tair]
|
||||||
|
|
||||||
- Fixed saving mode P image as a PNG with transparency = palette color 0
|
- Fixed saving mode P image as a PNG with transparency = palette color 0
|
||||||
[d-schmidt]
|
[d-schmidt]
|
||||||
|
|
||||||
|
|
|
@ -205,7 +205,7 @@ class ImageFile(Image.Image):
|
||||||
else:
|
else:
|
||||||
raise IndexError(ie)
|
raise IndexError(ie)
|
||||||
|
|
||||||
if not s: # truncated jpeg
|
if not s and not d.handles_eof: # truncated jpeg
|
||||||
self.tile = []
|
self.tile = []
|
||||||
|
|
||||||
# JpegDecode needs to clean things up here either way
|
# JpegDecode needs to clean things up here either way
|
||||||
|
|
249
PIL/Jpeg2KImagePlugin.py
Normal file
249
PIL/Jpeg2KImagePlugin.py
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
#
|
||||||
|
# The Python Imaging Library
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
# JPEG2000 file handling
|
||||||
|
#
|
||||||
|
# History:
|
||||||
|
# 2014-03-12 ajh Created
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014 Coriolis Systems Limited
|
||||||
|
# Copyright (c) 2014 Alastair Houghton
|
||||||
|
#
|
||||||
|
# See the README file for information on usage and redistribution.
|
||||||
|
#
|
||||||
|
|
||||||
|
__version__ = "0.1"
|
||||||
|
|
||||||
|
from PIL import Image, ImageFile, _binary
|
||||||
|
import struct
|
||||||
|
import os
|
||||||
|
import io
|
||||||
|
|
||||||
|
def _parse_codestream(fp):
|
||||||
|
"""Parse the JPEG 2000 codestream to extract the size and component
|
||||||
|
count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
|
||||||
|
|
||||||
|
hdr = fp.read(2)
|
||||||
|
lsiz = struct.unpack('>H', hdr)[0]
|
||||||
|
siz = hdr + fp.read(lsiz - 2)
|
||||||
|
lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, \
|
||||||
|
xtosiz, ytosiz, csiz \
|
||||||
|
= struct.unpack('>HHIIIIIIIIH', siz[:38])
|
||||||
|
ssiz = [None]*csiz
|
||||||
|
xrsiz = [None]*csiz
|
||||||
|
yrsiz = [None]*csiz
|
||||||
|
for i in range(csiz):
|
||||||
|
ssiz[i], xrsiz[i], yrsiz[i] \
|
||||||
|
= struct.unpack('>BBB', siz[36 + 3 * i:39 + 3 * i])
|
||||||
|
|
||||||
|
size = (xsiz - xosiz, ysiz - yosiz)
|
||||||
|
if csiz == 1:
|
||||||
|
mode = 'L'
|
||||||
|
elif csiz == 2:
|
||||||
|
mode = 'LA'
|
||||||
|
elif csiz == 3:
|
||||||
|
mode = 'RGB'
|
||||||
|
elif csiz == 4:
|
||||||
|
mode == 'RGBA'
|
||||||
|
else:
|
||||||
|
mode = None
|
||||||
|
|
||||||
|
return (size, mode)
|
||||||
|
|
||||||
|
def _parse_jp2_header(fp):
|
||||||
|
"""Parse the JP2 header box to extract size, component count and
|
||||||
|
color space information, returning a PIL (size, mode) tuple."""
|
||||||
|
|
||||||
|
# Find the JP2 header box
|
||||||
|
header = None
|
||||||
|
while True:
|
||||||
|
lbox, tbox = struct.unpack('>I4s', fp.read(8))
|
||||||
|
if lbox == 1:
|
||||||
|
lbox = struct.unpack('>Q', fp.read(8))[0]
|
||||||
|
hlen = 16
|
||||||
|
else:
|
||||||
|
hlen = 8
|
||||||
|
|
||||||
|
if tbox == b'jp2h':
|
||||||
|
header = fp.read(lbox - hlen)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
fp.seek(lbox - hlen, os.SEEK_CUR)
|
||||||
|
|
||||||
|
if header is None:
|
||||||
|
raise SyntaxError('could not find JP2 header')
|
||||||
|
|
||||||
|
size = None
|
||||||
|
mode = None
|
||||||
|
|
||||||
|
hio = io.BytesIO(header)
|
||||||
|
while True:
|
||||||
|
lbox, tbox = struct.unpack('>I4s', hio.read(8))
|
||||||
|
if lbox == 1:
|
||||||
|
lbox = struct.unpack('>Q', hio.read(8))[0]
|
||||||
|
hlen = 16
|
||||||
|
else:
|
||||||
|
hlen = 8
|
||||||
|
|
||||||
|
content = hio.read(lbox - hlen)
|
||||||
|
|
||||||
|
if tbox == b'ihdr':
|
||||||
|
height, width, nc, bpc, c, unkc, ipr \
|
||||||
|
= struct.unpack('>IIHBBBB', content)
|
||||||
|
size = (width, height)
|
||||||
|
if unkc:
|
||||||
|
if nc == 1:
|
||||||
|
mode = 'L'
|
||||||
|
elif nc == 2:
|
||||||
|
mode = 'LA'
|
||||||
|
elif nc == 3:
|
||||||
|
mode = 'RGB'
|
||||||
|
elif nc == 4:
|
||||||
|
mode = 'RGBA'
|
||||||
|
break
|
||||||
|
elif tbox == b'colr':
|
||||||
|
meth, prec, approx = struct.unpack('>BBB', content[:3])
|
||||||
|
if meth == 1:
|
||||||
|
cs = struct.unpack('>I', content[3:7])[0]
|
||||||
|
if cs == 16: # sRGB
|
||||||
|
if nc == 3:
|
||||||
|
mode = 'RGB'
|
||||||
|
elif nc == 4:
|
||||||
|
mode = 'RGBA'
|
||||||
|
break
|
||||||
|
elif cs == 17: # grayscale
|
||||||
|
if nc == 1:
|
||||||
|
mode = 'L'
|
||||||
|
elif nc == 2:
|
||||||
|
mode = 'LA'
|
||||||
|
break
|
||||||
|
elif cs == 18: # sYCC
|
||||||
|
if nc == 3:
|
||||||
|
mode = 'RGB'
|
||||||
|
elif nc == 4:
|
||||||
|
mode == 'RGBA'
|
||||||
|
break
|
||||||
|
|
||||||
|
return (size, mode)
|
||||||
|
|
||||||
|
##
|
||||||
|
# Image plugin for JPEG2000 images.
|
||||||
|
|
||||||
|
class Jpeg2KImageFile(ImageFile.ImageFile):
|
||||||
|
format = "JPEG2000"
|
||||||
|
format_description = "JPEG 2000 (ISO 15444)"
|
||||||
|
|
||||||
|
def _open(self):
|
||||||
|
sig = self.fp.read(4)
|
||||||
|
if sig == b'\xff\x4f\xff\x51':
|
||||||
|
self.codec = "j2k"
|
||||||
|
self.size, self.mode = _parse_codestream(self.fp)
|
||||||
|
else:
|
||||||
|
sig = sig + self.fp.read(8)
|
||||||
|
|
||||||
|
if sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a':
|
||||||
|
self.codec = "jp2"
|
||||||
|
self.size, self.mode = _parse_jp2_header(self.fp)
|
||||||
|
else:
|
||||||
|
raise SyntaxError('not a JPEG 2000 file')
|
||||||
|
|
||||||
|
if self.size is None or self.mode is None:
|
||||||
|
raise SyntaxError('unable to determine size/mode')
|
||||||
|
|
||||||
|
self.reduce = 0
|
||||||
|
self.layers = 0
|
||||||
|
|
||||||
|
fd = -1
|
||||||
|
|
||||||
|
if hasattr(self.fp, "fileno"):
|
||||||
|
try:
|
||||||
|
fd = self.fp.fileno()
|
||||||
|
except:
|
||||||
|
fd = -1
|
||||||
|
|
||||||
|
self.tile = [('jpeg2k', (0, 0) + self.size, 0,
|
||||||
|
(self.codec, self.reduce, self.layers, fd))]
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
if self.reduce:
|
||||||
|
power = 1 << self.reduce
|
||||||
|
adjust = power >> 1
|
||||||
|
self.size = ((self.size[0] + adjust) / power,
|
||||||
|
(self.size[1] + adjust) / power)
|
||||||
|
|
||||||
|
if self.tile:
|
||||||
|
# Update the reduce and layers settings
|
||||||
|
t = self.tile[0]
|
||||||
|
t3 = (t[3][0], self.reduce, self.layers, t[3][3])
|
||||||
|
self.tile = [(t[0], t[1], t[2], t3)]
|
||||||
|
|
||||||
|
ImageFile.ImageFile.load(self)
|
||||||
|
|
||||||
|
def _accept(prefix):
|
||||||
|
return (prefix[:4] == b'\xff\x4f\xff\x51'
|
||||||
|
or prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# Save support
|
||||||
|
|
||||||
|
def _save(im, fp, filename):
|
||||||
|
if filename.endswith('.j2k'):
|
||||||
|
kind = 'j2k'
|
||||||
|
else:
|
||||||
|
kind = 'jp2'
|
||||||
|
|
||||||
|
# Get the keyword arguments
|
||||||
|
info = im.encoderinfo
|
||||||
|
|
||||||
|
offset = info.get('offset', None)
|
||||||
|
tile_offset = info.get('tile_offset', None)
|
||||||
|
tile_size = info.get('tile_size', None)
|
||||||
|
quality_mode = info.get('quality_mode', 'rates')
|
||||||
|
quality_layers = info.get('quality_layers', None)
|
||||||
|
num_resolutions = info.get('num_resolutions', 0)
|
||||||
|
cblk_size = info.get('codeblock_size', None)
|
||||||
|
precinct_size = info.get('precinct_size', None)
|
||||||
|
irreversible = info.get('irreversible', False)
|
||||||
|
progression = info.get('progression', 'LRCP')
|
||||||
|
cinema_mode = info.get('cinema_mode', 'no')
|
||||||
|
fd = -1
|
||||||
|
|
||||||
|
if hasattr(fp, "fileno"):
|
||||||
|
try:
|
||||||
|
fd = fp.fileno()
|
||||||
|
except:
|
||||||
|
fd = -1
|
||||||
|
|
||||||
|
im.encoderconfig = (
|
||||||
|
offset,
|
||||||
|
tile_offset,
|
||||||
|
tile_size,
|
||||||
|
quality_mode,
|
||||||
|
quality_layers,
|
||||||
|
num_resolutions,
|
||||||
|
cblk_size,
|
||||||
|
precinct_size,
|
||||||
|
irreversible,
|
||||||
|
progression,
|
||||||
|
cinema_mode,
|
||||||
|
fd
|
||||||
|
)
|
||||||
|
|
||||||
|
ImageFile._save(im, fp, [('jpeg2k', (0, 0)+im.size, 0, kind)])
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# Registry stuff
|
||||||
|
|
||||||
|
Image.register_open('JPEG2000', Jpeg2KImageFile, _accept)
|
||||||
|
Image.register_save('JPEG2000', _save)
|
||||||
|
|
||||||
|
Image.register_extension('JPEG2000', '.jp2')
|
||||||
|
Image.register_extension('JPEG2000', '.j2k')
|
||||||
|
Image.register_extension('JPEG2000', '.jpc')
|
||||||
|
Image.register_extension('JPEG2000', '.jpf')
|
||||||
|
Image.register_extension('JPEG2000', '.jpx')
|
||||||
|
Image.register_extension('JPEG2000', '.j2c')
|
||||||
|
|
||||||
|
Image.register_mime('JPEG2000', 'image/jp2')
|
||||||
|
Image.register_mime('JPEG2000', 'image/jpx')
|
|
@ -33,6 +33,7 @@ _plugins = ['ArgImagePlugin',
|
||||||
'ImtImagePlugin',
|
'ImtImagePlugin',
|
||||||
'IptcImagePlugin',
|
'IptcImagePlugin',
|
||||||
'JpegImagePlugin',
|
'JpegImagePlugin',
|
||||||
|
'Jpeg2KImagePlugin',
|
||||||
'McIdasImagePlugin',
|
'McIdasImagePlugin',
|
||||||
'MicImagePlugin',
|
'MicImagePlugin',
|
||||||
'MpegImagePlugin',
|
'MpegImagePlugin',
|
||||||
|
|
BIN
Tests/images/test-card-lossless.jp2
Normal file
BIN
Tests/images/test-card-lossless.jp2
Normal file
Binary file not shown.
BIN
Tests/images/test-card-lossy-tiled.jp2
Normal file
BIN
Tests/images/test-card-lossy-tiled.jp2
Normal file
Binary file not shown.
BIN
Tests/images/test-card.png
Normal file
BIN
Tests/images/test-card.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
106
Tests/test_file_jpeg2k.py
Normal file
106
Tests/test_file_jpeg2k.py
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
from tester import *
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from PIL import ImageFile
|
||||||
|
|
||||||
|
codecs = dir(Image.core)
|
||||||
|
|
||||||
|
if "jpeg2k_encoder" not in codecs or "jpeg2k_decoder" not in codecs:
|
||||||
|
skip('JPEG 2000 support not available')
|
||||||
|
|
||||||
|
test_card = Image.open('Tests/images/test-card.png')
|
||||||
|
test_card.load()
|
||||||
|
|
||||||
|
def roundtrip(im, **options):
|
||||||
|
out = BytesIO()
|
||||||
|
im.save(out, "JPEG2000", **options)
|
||||||
|
bytes = out.tell()
|
||||||
|
out.seek(0)
|
||||||
|
im = Image.open(out)
|
||||||
|
im.bytes = bytes # for testing only
|
||||||
|
im.load()
|
||||||
|
return im
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_sanity():
|
||||||
|
# Internal version number
|
||||||
|
assert_match(Image.core.jp2klib_version, '\d+\.\d+\.\d+$')
|
||||||
|
|
||||||
|
im = Image.open('Tests/images/test-card-lossless.jp2')
|
||||||
|
im.load()
|
||||||
|
assert_equal(im.mode, 'RGB')
|
||||||
|
assert_equal(im.size, (640, 480))
|
||||||
|
assert_equal(im.format, 'JPEG2000')
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
# These two test pre-written JPEG 2000 files that were not written with
|
||||||
|
# PIL (they were made using Adobe Photoshop)
|
||||||
|
|
||||||
|
def test_lossless():
|
||||||
|
im = Image.open('Tests/images/test-card-lossless.jp2')
|
||||||
|
im.load()
|
||||||
|
im.save('/tmp/test-card.png')
|
||||||
|
assert_image_similar(im, test_card, 1.0e-3)
|
||||||
|
|
||||||
|
def test_lossy_tiled():
|
||||||
|
im = Image.open('Tests/images/test-card-lossy-tiled.jp2')
|
||||||
|
im.load()
|
||||||
|
assert_image_similar(im, test_card, 2.0)
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_lossless_rt():
|
||||||
|
im = roundtrip(test_card)
|
||||||
|
assert_image_equal(im, test_card)
|
||||||
|
|
||||||
|
def test_lossy_rt():
|
||||||
|
im = roundtrip(test_card, quality_layers=[20])
|
||||||
|
assert_image_similar(im, test_card, 2.0)
|
||||||
|
|
||||||
|
def test_tiled_rt():
|
||||||
|
im = roundtrip(test_card, tile_size=(128, 128))
|
||||||
|
assert_image_equal(im, test_card)
|
||||||
|
|
||||||
|
def test_tiled_offset_rt():
|
||||||
|
im = roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0),
|
||||||
|
offset=(32, 32))
|
||||||
|
assert_image_equal(im, test_card)
|
||||||
|
|
||||||
|
def test_irreversible_rt():
|
||||||
|
im = roundtrip(test_card, irreversible=True, quality_layers=[20])
|
||||||
|
assert_image_similar(im, test_card, 2.0)
|
||||||
|
|
||||||
|
def test_prog_qual_rt():
|
||||||
|
im = roundtrip(test_card, quality_layers=[60, 40, 20], progression='LRCP')
|
||||||
|
assert_image_similar(im, test_card, 2.0)
|
||||||
|
|
||||||
|
def test_prog_res_rt():
|
||||||
|
im = roundtrip(test_card, num_resolutions=8, progression='RLCP')
|
||||||
|
assert_image_equal(im, test_card)
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_reduce():
|
||||||
|
im = Image.open('Tests/images/test-card-lossless.jp2')
|
||||||
|
im.reduce = 2
|
||||||
|
im.load()
|
||||||
|
assert_equal(im.size, (160, 120))
|
||||||
|
|
||||||
|
def test_layers():
|
||||||
|
out = BytesIO()
|
||||||
|
test_card.save(out, 'JPEG2000', quality_layers=[100, 50, 10],
|
||||||
|
progression='LRCP')
|
||||||
|
out.seek(0)
|
||||||
|
|
||||||
|
im = Image.open(out)
|
||||||
|
im.layers = 1
|
||||||
|
im.load()
|
||||||
|
assert_image_similar(im, test_card, 13)
|
||||||
|
|
||||||
|
out.seek(0)
|
||||||
|
im = Image.open(out)
|
||||||
|
im.layers = 3
|
||||||
|
im.load()
|
||||||
|
assert_image_similar(im, test_card, 0.4)
|
13
_imaging.c
13
_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_GifDecoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_HexDecoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_HexDecoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_JpegDecoderNew(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_TiffLzwDecoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_MspDecoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_MspDecoderNew(PyObject* self, PyObject* args);
|
||||||
|
@ -3299,6 +3300,7 @@ extern PyObject* PyImaging_ZipDecoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_EpsEncoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_EpsEncoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_GifEncoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_GifEncoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_JpegEncoderNew(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_PcxEncoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_RawEncoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_RawEncoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_XbmEncoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_XbmEncoderNew(PyObject* self, PyObject* args);
|
||||||
|
@ -3351,6 +3353,10 @@ static PyMethodDef functions[] = {
|
||||||
#ifdef HAVE_LIBJPEG
|
#ifdef HAVE_LIBJPEG
|
||||||
{"jpeg_decoder", (PyCFunction)PyImaging_JpegDecoderNew, 1},
|
{"jpeg_decoder", (PyCFunction)PyImaging_JpegDecoderNew, 1},
|
||||||
{"jpeg_encoder", (PyCFunction)PyImaging_JpegEncoderNew, 1},
|
{"jpeg_encoder", (PyCFunction)PyImaging_JpegEncoderNew, 1},
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_OPENJPEG
|
||||||
|
{"jpeg2k_decoder", (PyCFunction)PyImaging_Jpeg2KDecoderNew, 1},
|
||||||
|
{"jpeg2k_encoder", (PyCFunction)PyImaging_Jpeg2KEncoderNew, 1},
|
||||||
#endif
|
#endif
|
||||||
{"tiff_lzw_decoder", (PyCFunction)PyImaging_TiffLzwDecoderNew, 1},
|
{"tiff_lzw_decoder", (PyCFunction)PyImaging_TiffLzwDecoderNew, 1},
|
||||||
#ifdef HAVE_LIBTIFF
|
#ifdef HAVE_LIBTIFF
|
||||||
|
@ -3455,6 +3461,13 @@ setup_module(PyObject* m) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_OPENJPEG
|
||||||
|
{
|
||||||
|
extern const char *ImagingJpeg2KVersion(void);
|
||||||
|
PyDict_SetItemString(d, "jp2klib_version", PyUnicode_FromString(ImagingJpeg2KVersion()));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_LIBZ
|
#ifdef HAVE_LIBZ
|
||||||
/* zip encoding strategies */
|
/* zip encoding strategies */
|
||||||
PyModule_AddIntConstant(m, "DEFAULT_STRATEGY", Z_DEFAULT_STRATEGY);
|
PyModule_AddIntConstant(m, "DEFAULT_STRATEGY", Z_DEFAULT_STRATEGY);
|
||||||
|
|
71
decode.c
71
decode.c
|
@ -52,6 +52,7 @@ typedef struct {
|
||||||
struct ImagingCodecStateInstance state;
|
struct ImagingCodecStateInstance state;
|
||||||
Imaging im;
|
Imaging im;
|
||||||
PyObject* lock;
|
PyObject* lock;
|
||||||
|
int handles_eof;
|
||||||
} ImagingDecoderObject;
|
} ImagingDecoderObject;
|
||||||
|
|
||||||
static PyTypeObject ImagingDecoderType;
|
static PyTypeObject ImagingDecoderType;
|
||||||
|
@ -93,6 +94,9 @@ PyImaging_DecoderNew(int contextsize)
|
||||||
/* Initialize the cleanup function pointer */
|
/* Initialize the cleanup function pointer */
|
||||||
decoder->cleanup = NULL;
|
decoder->cleanup = NULL;
|
||||||
|
|
||||||
|
/* Most decoders don't want to handle EOF themselves */
|
||||||
|
decoder->handles_eof = 0;
|
||||||
|
|
||||||
return decoder;
|
return decoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,6 +198,12 @@ _setimage(ImagingDecoderObject* decoder, PyObject* args)
|
||||||
return Py_None;
|
return Py_None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_get_handles_eof(ImagingDecoderObject *decoder)
|
||||||
|
{
|
||||||
|
return PyBool_FromLong(decoder->handles_eof);
|
||||||
|
}
|
||||||
|
|
||||||
static struct PyMethodDef methods[] = {
|
static struct PyMethodDef methods[] = {
|
||||||
{"decode", (PyCFunction)_decode, 1},
|
{"decode", (PyCFunction)_decode, 1},
|
||||||
{"cleanup", (PyCFunction)_decode_cleanup, 1},
|
{"cleanup", (PyCFunction)_decode_cleanup, 1},
|
||||||
|
@ -201,6 +211,13 @@ static struct PyMethodDef methods[] = {
|
||||||
{NULL, NULL} /* sentinel */
|
{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 = {
|
static PyTypeObject ImagingDecoderType = {
|
||||||
PyVarObject_HEAD_INIT(NULL, 0)
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
"ImagingDecoder", /*tp_name*/
|
"ImagingDecoder", /*tp_name*/
|
||||||
|
@ -232,7 +249,7 @@ static PyTypeObject ImagingDecoderType = {
|
||||||
0, /*tp_iternext*/
|
0, /*tp_iternext*/
|
||||||
methods, /*tp_methods*/
|
methods, /*tp_methods*/
|
||||||
0, /*tp_members*/
|
0, /*tp_members*/
|
||||||
0, /*tp_getset*/
|
getseters, /*tp_getset*/
|
||||||
};
|
};
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
|
@ -762,3 +779,55 @@ PyImaging_JpegDecoderNew(PyObject* self, PyObject* args)
|
||||||
return (PyObject*) decoder;
|
return (PyObject*) decoder;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* JPEG 2000 */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
#ifdef HAVE_OPENJPEG
|
||||||
|
|
||||||
|
#include "Jpeg2K.h"
|
||||||
|
|
||||||
|
PyObject*
|
||||||
|
PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args)
|
||||||
|
{
|
||||||
|
ImagingDecoderObject* decoder;
|
||||||
|
JPEG2KDECODESTATE *context;
|
||||||
|
|
||||||
|
char* mode;
|
||||||
|
char* format;
|
||||||
|
OPJ_CODEC_FORMAT codec_format;
|
||||||
|
int reduce = 0;
|
||||||
|
int layers = 0;
|
||||||
|
int fd = -1;
|
||||||
|
if (!PyArg_ParseTuple(args, "ss|iii", &mode, &format,
|
||||||
|
&reduce, &layers, &fd))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (strcmp(format, "j2k") == 0)
|
||||||
|
codec_format = OPJ_CODEC_J2K;
|
||||||
|
else if (strcmp(format, "jpt") == 0)
|
||||||
|
codec_format = OPJ_CODEC_JPT;
|
||||||
|
else if (strcmp(format, "jp2") == 0)
|
||||||
|
codec_format = OPJ_CODEC_JP2;
|
||||||
|
else
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
decoder = PyImaging_DecoderNew(sizeof(JPEG2KDECODESTATE));
|
||||||
|
if (decoder == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
decoder->handles_eof = 1;
|
||||||
|
decoder->decode = ImagingJpeg2KDecode;
|
||||||
|
decoder->cleanup = ImagingJpeg2KDecodeCleanup;
|
||||||
|
|
||||||
|
context = (JPEG2KDECODESTATE *)decoder->state.context;
|
||||||
|
|
||||||
|
context->fd = fd;
|
||||||
|
context->format = codec_format;
|
||||||
|
context->reduce = reduce;
|
||||||
|
context->layers = layers;
|
||||||
|
|
||||||
|
return (PyObject*) decoder;
|
||||||
|
}
|
||||||
|
#endif /* HAVE_OPENJPEG */
|
||||||
|
|
|
@ -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
|
before building the Python Imaging Library. See the distribution README for
|
||||||
details.
|
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
|
MSP
|
||||||
^^^
|
^^^
|
||||||
|
|
||||||
|
|
|
@ -153,6 +153,14 @@ Plugin reference
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
:mod:`Jpeg2KImagePlugin` Module
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
.. automodule:: PIL.Jpeg2KImagePlugin
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
:mod:`McIdasImagePlugin` Module
|
:mod:`McIdasImagePlugin` Module
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
|
|
161
encode.c
161
encode.c
|
@ -40,6 +40,7 @@ typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
int (*encode)(Imaging im, ImagingCodecState state,
|
int (*encode)(Imaging im, ImagingCodecState state,
|
||||||
UINT8* buffer, int bytes);
|
UINT8* buffer, int bytes);
|
||||||
|
int (*cleanup)(ImagingCodecState state);
|
||||||
struct ImagingCodecStateInstance state;
|
struct ImagingCodecStateInstance state;
|
||||||
Imaging im;
|
Imaging im;
|
||||||
PyObject* lock;
|
PyObject* lock;
|
||||||
|
@ -77,6 +78,9 @@ PyImaging_EncoderNew(int contextsize)
|
||||||
/* Initialize encoder context */
|
/* Initialize encoder context */
|
||||||
encoder->state.context = context;
|
encoder->state.context = context;
|
||||||
|
|
||||||
|
/* Most encoders don't need this */
|
||||||
|
encoder->cleanup = NULL;
|
||||||
|
|
||||||
/* Target image */
|
/* Target image */
|
||||||
encoder->lock = NULL;
|
encoder->lock = NULL;
|
||||||
encoder->im = NULL;
|
encoder->im = NULL;
|
||||||
|
@ -87,6 +91,8 @@ PyImaging_EncoderNew(int contextsize)
|
||||||
static void
|
static void
|
||||||
_dealloc(ImagingEncoderObject* encoder)
|
_dealloc(ImagingEncoderObject* encoder)
|
||||||
{
|
{
|
||||||
|
if (encoder->cleanup)
|
||||||
|
encoder->cleanup(&encoder->state);
|
||||||
free(encoder->state.buffer);
|
free(encoder->state.buffer);
|
||||||
free(encoder->state.context);
|
free(encoder->state.context);
|
||||||
Py_XDECREF(encoder->lock);
|
Py_XDECREF(encoder->lock);
|
||||||
|
@ -793,3 +799,158 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* JPEG 2000 */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
#ifdef HAVE_OPENJPEG
|
||||||
|
|
||||||
|
#include "Jpeg2K.h"
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2k_decode_coord_tuple(PyObject *tuple, int *x, int *y)
|
||||||
|
{
|
||||||
|
*x = *y = 0;
|
||||||
|
|
||||||
|
if (tuple && PyTuple_Check(tuple) && PyTuple_GET_SIZE(tuple) == 2) {
|
||||||
|
*x = (int)PyInt_AsLong(PyTuple_GET_ITEM(tuple, 0));
|
||||||
|
*y = (int)PyInt_AsLong(PyTuple_GET_ITEM(tuple, 1));
|
||||||
|
|
||||||
|
if (*x < 0)
|
||||||
|
*x = 0;
|
||||||
|
if (*y < 0)
|
||||||
|
*y = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject*
|
||||||
|
PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args)
|
||||||
|
{
|
||||||
|
ImagingEncoderObject *encoder;
|
||||||
|
JPEG2KENCODESTATE *context;
|
||||||
|
|
||||||
|
char *mode;
|
||||||
|
char *format;
|
||||||
|
OPJ_CODEC_FORMAT codec_format;
|
||||||
|
PyObject *offset = NULL, *tile_offset = NULL, *tile_size = NULL;
|
||||||
|
char *quality_mode = "rates";
|
||||||
|
PyObject *quality_layers = NULL;
|
||||||
|
int num_resolutions = 0;
|
||||||
|
PyObject *cblk_size = NULL, *precinct_size = NULL;
|
||||||
|
PyObject *irreversible = NULL;
|
||||||
|
char *progression = "LRCP";
|
||||||
|
OPJ_PROG_ORDER prog_order;
|
||||||
|
char *cinema_mode = "no";
|
||||||
|
OPJ_CINEMA_MODE cine_mode;
|
||||||
|
int fd = -1;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "ss|OOOsOIOOOssi", &mode, &format,
|
||||||
|
&offset, &tile_offset, &tile_size,
|
||||||
|
&quality_mode, &quality_layers, &num_resolutions,
|
||||||
|
&cblk_size, &precinct_size,
|
||||||
|
&irreversible, &progression, &cinema_mode,
|
||||||
|
&fd))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (strcmp (format, "j2k") == 0)
|
||||||
|
codec_format = OPJ_CODEC_J2K;
|
||||||
|
else if (strcmp (format, "jpt") == 0)
|
||||||
|
codec_format = OPJ_CODEC_JPT;
|
||||||
|
else if (strcmp (format, "jp2") == 0)
|
||||||
|
codec_format = OPJ_CODEC_JP2;
|
||||||
|
else
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (strcmp(progression, "LRCP") == 0)
|
||||||
|
prog_order = OPJ_LRCP;
|
||||||
|
else if (strcmp(progression, "RLCP") == 0)
|
||||||
|
prog_order = OPJ_RLCP;
|
||||||
|
else if (strcmp(progression, "RPCL") == 0)
|
||||||
|
prog_order = OPJ_RPCL;
|
||||||
|
else if (strcmp(progression, "PCRL") == 0)
|
||||||
|
prog_order = OPJ_PCRL;
|
||||||
|
else if (strcmp(progression, "CPRL") == 0)
|
||||||
|
prog_order = OPJ_CPRL;
|
||||||
|
else
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (strcmp(cinema_mode, "no") == 0)
|
||||||
|
cine_mode = OPJ_OFF;
|
||||||
|
else if (strcmp(cinema_mode, "cinema2k-24") == 0)
|
||||||
|
cine_mode = OPJ_CINEMA2K_24;
|
||||||
|
else if (strcmp(cinema_mode, "cinema2k-48") == 0)
|
||||||
|
cine_mode = OPJ_CINEMA2K_48;
|
||||||
|
else if (strcmp(cinema_mode, "cinema4k-24") == 0)
|
||||||
|
cine_mode = OPJ_CINEMA4K_24;
|
||||||
|
else
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
encoder = PyImaging_EncoderNew(sizeof(JPEG2KENCODESTATE));
|
||||||
|
if (!encoder)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
encoder->encode = ImagingJpeg2KEncode;
|
||||||
|
encoder->cleanup = ImagingJpeg2KEncodeCleanup;
|
||||||
|
|
||||||
|
context = (JPEG2KENCODESTATE *)encoder->state.context;
|
||||||
|
|
||||||
|
context->fd = fd;
|
||||||
|
context->format = codec_format;
|
||||||
|
context->offset_x = context->offset_y = 0;
|
||||||
|
|
||||||
|
j2k_decode_coord_tuple(offset, &context->offset_x, &context->offset_y);
|
||||||
|
j2k_decode_coord_tuple(tile_offset,
|
||||||
|
&context->tile_offset_x,
|
||||||
|
&context->tile_offset_y);
|
||||||
|
j2k_decode_coord_tuple(tile_size,
|
||||||
|
&context->tile_size_x,
|
||||||
|
&context->tile_size_y);
|
||||||
|
|
||||||
|
/* Error on illegal tile offsets */
|
||||||
|
if (context->tile_size_x && context->tile_size_y) {
|
||||||
|
if (context->tile_offset_x <= context->offset_x - context->tile_size_x
|
||||||
|
|| context->tile_offset_y <= context->offset_y - context->tile_size_y) {
|
||||||
|
PyErr_SetString(PyExc_ValueError,
|
||||||
|
"JPEG 2000 tile offset too small; top left tile must "
|
||||||
|
"intersect image area");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context->tile_offset_x > context->offset_x
|
||||||
|
|| context->tile_offset_y > context->offset_y) {
|
||||||
|
PyErr_SetString(PyExc_ValueError,
|
||||||
|
"JPEG 2000 tile offset too large to cover image area");
|
||||||
|
Py_DECREF(encoder);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quality_layers && PySequence_Check(quality_layers)) {
|
||||||
|
context->quality_is_in_db = strcmp (quality_mode, "dB") == 0;
|
||||||
|
context->quality_layers = quality_layers;
|
||||||
|
Py_INCREF(quality_layers);
|
||||||
|
}
|
||||||
|
|
||||||
|
context->num_resolutions = num_resolutions;
|
||||||
|
|
||||||
|
j2k_decode_coord_tuple(cblk_size,
|
||||||
|
&context->cblk_width,
|
||||||
|
&context->cblk_height);
|
||||||
|
j2k_decode_coord_tuple(precinct_size,
|
||||||
|
&context->precinct_width,
|
||||||
|
&context->precinct_height);
|
||||||
|
|
||||||
|
context->irreversible = PyObject_IsTrue(irreversible);
|
||||||
|
context->progression = prog_order;
|
||||||
|
context->cinema_mode = cine_mode;
|
||||||
|
|
||||||
|
return (PyObject *)encoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Local Variables:
|
||||||
|
* c-basic-offset: 4
|
||||||
|
* End:
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
|
@ -424,6 +424,14 @@ extern int ImagingJpegDecodeCleanup(ImagingCodecState state);
|
||||||
extern int ImagingJpegEncode(Imaging im, ImagingCodecState state,
|
extern int ImagingJpegEncode(Imaging im, ImagingCodecState state,
|
||||||
UINT8* buffer, int bytes);
|
UINT8* buffer, int bytes);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_OPENJPEG
|
||||||
|
extern int ImagingJpeg2KDecode(Imaging im, ImagingCodecState state,
|
||||||
|
UINT8* buffer, int bytes);
|
||||||
|
extern int ImagingJpeg2KDecodeCleanup(ImagingCodecState state);
|
||||||
|
extern int ImagingJpeg2KEncode(Imaging im, ImagingCodecState state,
|
||||||
|
UINT8* buffer, int bytes);
|
||||||
|
extern int ImagingJpeg2KEncodeCleanup(ImagingCodecState state);
|
||||||
|
#endif
|
||||||
extern int ImagingLzwDecode(Imaging im, ImagingCodecState state,
|
extern int ImagingLzwDecode(Imaging im, ImagingCodecState state,
|
||||||
UINT8* buffer, int bytes);
|
UINT8* buffer, int bytes);
|
||||||
#ifdef HAVE_LIBTIFF
|
#ifdef HAVE_LIBTIFF
|
||||||
|
@ -497,6 +505,32 @@ struct ImagingCodecStateInstance {
|
||||||
void *context;
|
void *context;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Incremental encoding/decoding support */
|
||||||
|
typedef struct ImagingIncrementalCodecStruct *ImagingIncrementalCodec;
|
||||||
|
|
||||||
|
typedef int (*ImagingIncrementalCodecEntry)(Imaging im,
|
||||||
|
ImagingCodecState state,
|
||||||
|
ImagingIncrementalCodec codec);
|
||||||
|
|
||||||
|
enum {
|
||||||
|
INCREMENTAL_CODEC_READ = 1,
|
||||||
|
INCREMENTAL_CODEC_WRITE = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
INCREMENTAL_CODEC_NOT_SEEKABLE = 0,
|
||||||
|
INCREMENTAL_CODEC_SEEKABLE = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
extern ImagingIncrementalCodec ImagingIncrementalCodecCreate(ImagingIncrementalCodecEntry codec_entry, Imaging im, ImagingCodecState state, int read_or_write, int seekable, int fd);
|
||||||
|
extern void ImagingIncrementalCodecDestroy(ImagingIncrementalCodec codec);
|
||||||
|
extern int ImagingIncrementalCodecPushBuffer(ImagingIncrementalCodec codec, UINT8 *buf, int bytes);
|
||||||
|
extern ssize_t ImagingIncrementalCodecRead(ImagingIncrementalCodec codec, void *buffer, size_t bytes);
|
||||||
|
extern off_t ImagingIncrementalCodecSkip(ImagingIncrementalCodec codec, off_t bytes);
|
||||||
|
extern ssize_t ImagingIncrementalCodecWrite(ImagingIncrementalCodec codec, const void *buffer, size_t bytes);
|
||||||
|
extern off_t ImagingIncrementalCodecSeek(ImagingIncrementalCodec codec, off_t bytes);
|
||||||
|
extern size_t ImagingIncrementalCodecBytesInBuffer(ImagingIncrementalCodec codec);
|
||||||
|
|
||||||
/* Errcodes */
|
/* Errcodes */
|
||||||
#define IMAGING_CODEC_END 1
|
#define IMAGING_CODEC_END 1
|
||||||
#define IMAGING_CODEC_OVERRUN -1
|
#define IMAGING_CODEC_OVERRUN -1
|
||||||
|
|
672
libImaging/Incremental.c
Normal file
672
libImaging/Incremental.c
Normal file
|
@ -0,0 +1,672 @@
|
||||||
|
/*
|
||||||
|
* The Python Imaging Library
|
||||||
|
* $Id$
|
||||||
|
*
|
||||||
|
* incremental decoding adaptor.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Coriolis Systems Limited
|
||||||
|
* Copyright (c) 2014 Alastair Houghton
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Imaging.h"
|
||||||
|
|
||||||
|
/* The idea behind this interface is simple: the actual decoding proceeds in
|
||||||
|
a thread, which is run in lock step with the main thread. Whenever the
|
||||||
|
ImagingIncrementalCodecRead() call runs short on data, it suspends the
|
||||||
|
decoding thread and wakes the main thread. Conversely, the
|
||||||
|
ImagingIncrementalCodecPushBuffer() call suspends the main thread and wakes
|
||||||
|
the decoding thread, providing a buffer of data.
|
||||||
|
|
||||||
|
The two threads are never running simultaneously, so there is no need for
|
||||||
|
any addition synchronisation measures outside of this file.
|
||||||
|
|
||||||
|
Note also that we start the thread suspended (on Windows), or make it
|
||||||
|
immediately wait (other platforms), so that it's possible to initialise
|
||||||
|
things before the thread starts running.
|
||||||
|
|
||||||
|
This interface is useful to allow PIL to interact efficiently with any
|
||||||
|
third-party imaging library that does not support suspendable reads;
|
||||||
|
one example is OpenJPEG (which is used for J2K support). The TIFF library
|
||||||
|
might also benefit from using this code.
|
||||||
|
|
||||||
|
Note that if using this module, you want to set handles_eof on your
|
||||||
|
decoder to true. Why? Because otherwise ImageFile.load() will abort,
|
||||||
|
thinking that the image is truncated, whereas generally you want it to
|
||||||
|
pass the EOF condition (0 bytes to read) through to your code. */
|
||||||
|
|
||||||
|
/* Additional complication: *Some* codecs need to seek; this is fine if
|
||||||
|
there is a file descriptor, but if we're buffering data it becomes
|
||||||
|
awkward. The incremental adaptor now contains code to handle these
|
||||||
|
two cases. */
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#include <process.h>
|
||||||
|
#else
|
||||||
|
#include <pthread.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define DEBUG_INCREMENTAL 0
|
||||||
|
|
||||||
|
#if DEBUG_INCREMENTAL
|
||||||
|
#define DEBUG(...) printf(__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define DEBUG(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct ImagingIncrementalCodecStruct {
|
||||||
|
#ifdef _WIN32
|
||||||
|
HANDLE hCodecEvent;
|
||||||
|
HANDLE hDataEvent;
|
||||||
|
HANDLE hThread;
|
||||||
|
#else
|
||||||
|
pthread_mutex_t start_mutex;
|
||||||
|
pthread_cond_t start_cond;
|
||||||
|
pthread_mutex_t codec_mutex;
|
||||||
|
pthread_cond_t codec_cond;
|
||||||
|
pthread_mutex_t data_mutex;
|
||||||
|
pthread_cond_t data_cond;
|
||||||
|
pthread_t thread;
|
||||||
|
#endif
|
||||||
|
ImagingIncrementalCodecEntry entry;
|
||||||
|
Imaging im;
|
||||||
|
ImagingCodecState state;
|
||||||
|
struct {
|
||||||
|
int fd;
|
||||||
|
UINT8 *buffer; /* Base of buffer */
|
||||||
|
UINT8 *ptr; /* Current pointer in buffer */
|
||||||
|
UINT8 *top; /* Highest point in buffer we've used */
|
||||||
|
UINT8 *end; /* End of buffer */
|
||||||
|
} stream;
|
||||||
|
int read_or_write;
|
||||||
|
int seekable;
|
||||||
|
int started;
|
||||||
|
int result;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void flush_stream(ImagingIncrementalCodec codec);
|
||||||
|
|
||||||
|
#if _WIN32
|
||||||
|
static unsigned int __stdcall
|
||||||
|
codec_thread(void *ptr)
|
||||||
|
{
|
||||||
|
ImagingIncrementalCodec codec = (ImagingIncrementalCodec)ptr;
|
||||||
|
|
||||||
|
DEBUG("Entering thread\n");
|
||||||
|
|
||||||
|
codec->result = codec->entry(codec->im, codec->state, codec);
|
||||||
|
|
||||||
|
DEBUG("Leaving thread (%d)\n", codec->result);
|
||||||
|
|
||||||
|
flush_stream(codec);
|
||||||
|
|
||||||
|
SetEvent(codec->hCodecEvent);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static void *
|
||||||
|
codec_thread(void *ptr)
|
||||||
|
{
|
||||||
|
ImagingIncrementalCodec codec = (ImagingIncrementalCodec)ptr;
|
||||||
|
|
||||||
|
DEBUG("Entering thread\n");
|
||||||
|
|
||||||
|
codec->result = codec->entry(codec->im, codec->state, codec);
|
||||||
|
|
||||||
|
DEBUG("Leaving thread (%d)\n", codec->result);
|
||||||
|
|
||||||
|
flush_stream(codec);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&codec->codec_mutex);
|
||||||
|
pthread_cond_signal(&codec->codec_cond);
|
||||||
|
pthread_mutex_unlock(&codec->codec_mutex);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void
|
||||||
|
flush_stream(ImagingIncrementalCodec codec)
|
||||||
|
{
|
||||||
|
/* This is to flush data from the write buffer for a seekable write
|
||||||
|
codec. */
|
||||||
|
if (codec->read_or_write != INCREMENTAL_CODEC_WRITE
|
||||||
|
|| codec->state->errcode != IMAGING_CODEC_END
|
||||||
|
|| !codec->seekable
|
||||||
|
|| codec->stream.fd >= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DEBUG("flushing data\n");
|
||||||
|
|
||||||
|
UINT8 *buffer = codec->stream.buffer;
|
||||||
|
size_t bytes = codec->stream.ptr - codec->stream.buffer;
|
||||||
|
|
||||||
|
codec->state->errcode = 0;
|
||||||
|
codec->seekable = INCREMENTAL_CODEC_NOT_SEEKABLE;
|
||||||
|
codec->stream.buffer = codec->stream.ptr = codec->stream.end
|
||||||
|
= codec->stream.top = NULL;
|
||||||
|
|
||||||
|
ImagingIncrementalCodecWrite(codec, buffer, bytes);
|
||||||
|
|
||||||
|
codec->state->errcode = IMAGING_CODEC_END;
|
||||||
|
codec->result = (int)ImagingIncrementalCodecBytesInBuffer(codec);
|
||||||
|
|
||||||
|
free(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new incremental codec */
|
||||||
|
ImagingIncrementalCodec
|
||||||
|
ImagingIncrementalCodecCreate(ImagingIncrementalCodecEntry codec_entry,
|
||||||
|
Imaging im,
|
||||||
|
ImagingCodecState state,
|
||||||
|
int read_or_write,
|
||||||
|
int seekable,
|
||||||
|
int fd)
|
||||||
|
{
|
||||||
|
ImagingIncrementalCodec codec = (ImagingIncrementalCodec)malloc(sizeof(struct ImagingIncrementalCodecStruct));
|
||||||
|
|
||||||
|
codec->entry = codec_entry;
|
||||||
|
codec->im = im;
|
||||||
|
codec->state = state;
|
||||||
|
codec->result = 0;
|
||||||
|
codec->stream.fd = fd;
|
||||||
|
codec->stream.buffer = codec->stream.ptr = codec->stream.end
|
||||||
|
= codec->stream.top = NULL;
|
||||||
|
codec->started = 0;
|
||||||
|
codec->seekable = seekable;
|
||||||
|
codec->read_or_write = read_or_write;
|
||||||
|
|
||||||
|
if (fd >= 0)
|
||||||
|
lseek(fd, 0, SEEK_SET);
|
||||||
|
|
||||||
|
/* System specific set-up */
|
||||||
|
#if _WIN32
|
||||||
|
codec->hCodecEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||||
|
|
||||||
|
if (!codec->hCodecEvent) {
|
||||||
|
free(codec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
codec->hDataEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||||
|
|
||||||
|
if (!codec->hDataEvent) {
|
||||||
|
CloseHandle(codec->hCodecEvent);
|
||||||
|
free(codec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
codec->hThread = _beginthreadex(NULL, 0, codec_thread, codec,
|
||||||
|
CREATE_SUSPENDED, NULL);
|
||||||
|
|
||||||
|
if (!codec->hThread) {
|
||||||
|
CloseHandle(codec->hCodecEvent);
|
||||||
|
CloseHandle(codec->hDataEvent);
|
||||||
|
free(codec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (pthread_mutex_init(&codec->start_mutex, NULL)) {
|
||||||
|
free (codec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pthread_mutex_init(&codec->codec_mutex, NULL)) {
|
||||||
|
pthread_mutex_destroy(&codec->start_mutex);
|
||||||
|
free(codec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pthread_mutex_init(&codec->data_mutex, NULL)) {
|
||||||
|
pthread_mutex_destroy(&codec->start_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->codec_mutex);
|
||||||
|
free(codec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pthread_cond_init(&codec->start_cond, NULL)) {
|
||||||
|
pthread_mutex_destroy(&codec->start_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->codec_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->data_mutex);
|
||||||
|
free(codec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pthread_cond_init(&codec->codec_cond, NULL)) {
|
||||||
|
pthread_mutex_destroy(&codec->start_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->codec_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->data_mutex);
|
||||||
|
pthread_cond_destroy(&codec->start_cond);
|
||||||
|
free(codec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pthread_cond_init(&codec->data_cond, NULL)) {
|
||||||
|
pthread_mutex_destroy(&codec->start_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->codec_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->data_mutex);
|
||||||
|
pthread_cond_destroy(&codec->start_cond);
|
||||||
|
pthread_cond_destroy(&codec->codec_cond);
|
||||||
|
free(codec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pthread_create(&codec->thread, NULL, codec_thread, codec)) {
|
||||||
|
pthread_mutex_destroy(&codec->start_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->codec_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->data_mutex);
|
||||||
|
pthread_cond_destroy(&codec->start_cond);
|
||||||
|
pthread_cond_destroy(&codec->codec_cond);
|
||||||
|
pthread_cond_destroy(&codec->data_cond);
|
||||||
|
free(codec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return codec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy an incremental codec */
|
||||||
|
void
|
||||||
|
ImagingIncrementalCodecDestroy(ImagingIncrementalCodec codec)
|
||||||
|
{
|
||||||
|
DEBUG("destroying\n");
|
||||||
|
|
||||||
|
if (!codec->started) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
ResumeThread(codec->hThread);
|
||||||
|
#else
|
||||||
|
pthread_cond_signal(&codec->start_cond);
|
||||||
|
#endif
|
||||||
|
codec->started = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_lock(&codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (codec->seekable && codec->stream.fd < 0)
|
||||||
|
free (codec->stream.buffer);
|
||||||
|
|
||||||
|
codec->stream.buffer = codec->stream.ptr = codec->stream.end
|
||||||
|
= codec->stream.top = NULL;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
SetEvent(codec->hDataEvent);
|
||||||
|
|
||||||
|
WaitForSingleObject(codec->hThread, INFINITE);
|
||||||
|
|
||||||
|
CloseHandle(codec->hThread);
|
||||||
|
CloseHandle(codec->hCodecEvent);
|
||||||
|
CloseHandle(codec->hDataEvent);
|
||||||
|
#else
|
||||||
|
pthread_cond_signal(&codec->data_cond);
|
||||||
|
pthread_mutex_unlock(&codec->data_mutex);
|
||||||
|
|
||||||
|
pthread_join(codec->thread, NULL);
|
||||||
|
|
||||||
|
pthread_mutex_destroy(&codec->start_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->codec_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->data_mutex);
|
||||||
|
pthread_cond_destroy(&codec->start_cond);
|
||||||
|
pthread_cond_destroy(&codec->codec_cond);
|
||||||
|
pthread_cond_destroy(&codec->data_cond);
|
||||||
|
#endif
|
||||||
|
free (codec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push a data buffer for an incremental codec */
|
||||||
|
int
|
||||||
|
ImagingIncrementalCodecPushBuffer(ImagingIncrementalCodec codec,
|
||||||
|
UINT8 *buf, int bytes)
|
||||||
|
{
|
||||||
|
if (!codec->started) {
|
||||||
|
DEBUG("starting\n");
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
ResumeThread(codec->hThread);
|
||||||
|
#else
|
||||||
|
pthread_cond_signal(&codec->start_cond);
|
||||||
|
#endif
|
||||||
|
codec->started = 1;
|
||||||
|
|
||||||
|
/* Wait for the thread to ask for data */
|
||||||
|
#ifdef _WIN32
|
||||||
|
WaitForSingleObject(codec->hCodecEvent, INFINITE);
|
||||||
|
#else
|
||||||
|
pthread_mutex_lock(&codec->codec_mutex);
|
||||||
|
pthread_cond_wait(&codec->codec_cond, &codec->codec_mutex);
|
||||||
|
pthread_mutex_unlock(&codec->codec_mutex);
|
||||||
|
#endif
|
||||||
|
if (codec->result < 0) {
|
||||||
|
DEBUG("got result %d\n", codec->result);
|
||||||
|
|
||||||
|
return codec->result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Codecs using an fd don't need data, so when we get here, we're done */
|
||||||
|
if (codec->stream.fd >= 0) {
|
||||||
|
DEBUG("got result %d\n", codec->result);
|
||||||
|
|
||||||
|
return codec->result;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG("providing %p, %d\n", buf, bytes);
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_lock(&codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (codec->read_or_write == INCREMENTAL_CODEC_READ
|
||||||
|
&& codec->seekable && codec->stream.fd < 0) {
|
||||||
|
/* In this specific case, we append to a buffer we allocate ourselves */
|
||||||
|
size_t old_size = codec->stream.end - codec->stream.buffer;
|
||||||
|
size_t new_size = codec->stream.end - codec->stream.buffer + bytes;
|
||||||
|
UINT8 *new = (UINT8 *)realloc (codec->stream.buffer, new_size);
|
||||||
|
|
||||||
|
if (!new) {
|
||||||
|
codec->state->errcode = IMAGING_CODEC_MEMORY;
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_unlock(&codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
codec->stream.ptr = codec->stream.ptr - codec->stream.buffer + new;
|
||||||
|
codec->stream.end = new + new_size;
|
||||||
|
codec->stream.buffer = new;
|
||||||
|
|
||||||
|
memcpy(new + old_size, buf, bytes);
|
||||||
|
} else {
|
||||||
|
codec->stream.buffer = codec->stream.ptr = buf;
|
||||||
|
codec->stream.end = buf + bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
SetEvent(codec->hDataEvent);
|
||||||
|
WaitForSingleObject(codec->hCodecEvent, INFINITE);
|
||||||
|
#else
|
||||||
|
pthread_cond_signal(&codec->data_cond);
|
||||||
|
pthread_mutex_unlock(&codec->data_mutex);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&codec->codec_mutex);
|
||||||
|
pthread_cond_wait(&codec->codec_cond, &codec->codec_mutex);
|
||||||
|
pthread_mutex_unlock(&codec->codec_mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DEBUG("got result %d\n", codec->result);
|
||||||
|
|
||||||
|
return codec->result;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
ImagingIncrementalCodecBytesInBuffer(ImagingIncrementalCodec codec)
|
||||||
|
{
|
||||||
|
return codec->stream.ptr - codec->stream.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
ImagingIncrementalCodecRead(ImagingIncrementalCodec codec,
|
||||||
|
void *buffer, size_t bytes)
|
||||||
|
{
|
||||||
|
UINT8 *ptr = (UINT8 *)buffer;
|
||||||
|
size_t done = 0;
|
||||||
|
|
||||||
|
if (codec->read_or_write == INCREMENTAL_CODEC_WRITE) {
|
||||||
|
DEBUG("attempt to read from write codec\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG("reading (want %llu bytes)\n", (unsigned long long)bytes);
|
||||||
|
|
||||||
|
if (codec->stream.fd >= 0) {
|
||||||
|
ssize_t ret = read(codec->stream.fd, buffer, bytes);
|
||||||
|
DEBUG("read %lld bytes from fd\n", (long long)ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_lock(&codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
while (bytes) {
|
||||||
|
size_t todo = bytes;
|
||||||
|
size_t remaining = codec->stream.end - codec->stream.ptr;
|
||||||
|
|
||||||
|
if (!remaining) {
|
||||||
|
DEBUG("waiting for data\n");
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_lock(&codec->codec_mutex);
|
||||||
|
#endif
|
||||||
|
codec->result = (int)(codec->stream.ptr - codec->stream.buffer);
|
||||||
|
#if _WIN32
|
||||||
|
SetEvent(codec->hCodecEvent);
|
||||||
|
WaitForSingleObject(codec->hDataEvent, INFINITE);
|
||||||
|
#else
|
||||||
|
pthread_cond_signal(&codec->codec_cond);
|
||||||
|
pthread_mutex_unlock(&codec->codec_mutex);
|
||||||
|
pthread_cond_wait(&codec->data_cond, &codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
remaining = codec->stream.end - codec->stream.ptr;
|
||||||
|
codec->stream.top = codec->stream.end;
|
||||||
|
|
||||||
|
DEBUG("got %llu bytes\n", (unsigned long long)remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (todo > remaining)
|
||||||
|
todo = remaining;
|
||||||
|
|
||||||
|
if (!todo)
|
||||||
|
break;
|
||||||
|
|
||||||
|
memcpy (ptr, codec->stream.ptr, todo);
|
||||||
|
codec->stream.ptr += todo;
|
||||||
|
bytes -= todo;
|
||||||
|
done += todo;
|
||||||
|
ptr += todo;
|
||||||
|
}
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_unlock(&codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DEBUG("read total %llu bytes\n", (unsigned long long)done);
|
||||||
|
|
||||||
|
return done;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t
|
||||||
|
ImagingIncrementalCodecSkip(ImagingIncrementalCodec codec,
|
||||||
|
off_t bytes)
|
||||||
|
{
|
||||||
|
off_t done = 0;
|
||||||
|
|
||||||
|
DEBUG("skipping (want %llu bytes)\n", (unsigned long long)bytes);
|
||||||
|
|
||||||
|
/* In write mode, explicitly fill with zeroes */
|
||||||
|
if (codec->read_or_write == INCREMENTAL_CODEC_WRITE) {
|
||||||
|
static const UINT8 zeroes[256] = { 0 };
|
||||||
|
off_t done = 0;
|
||||||
|
while (bytes) {
|
||||||
|
size_t todo = (size_t)(bytes > 256 ? 256 : bytes);
|
||||||
|
ssize_t written = ImagingIncrementalCodecWrite(codec, zeroes, todo);
|
||||||
|
if (written <= 0)
|
||||||
|
break;
|
||||||
|
done += written;
|
||||||
|
bytes -= written;
|
||||||
|
}
|
||||||
|
return done;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codec->stream.fd >= 0)
|
||||||
|
return lseek(codec->stream.fd, bytes, SEEK_CUR);
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_lock(&codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
while (bytes) {
|
||||||
|
off_t todo = bytes;
|
||||||
|
off_t remaining = codec->stream.end - codec->stream.ptr;
|
||||||
|
|
||||||
|
if (!remaining) {
|
||||||
|
DEBUG("waiting for data\n");
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_lock(&codec->codec_mutex);
|
||||||
|
#endif
|
||||||
|
codec->result = (int)(codec->stream.ptr - codec->stream.buffer);
|
||||||
|
#if _WIN32
|
||||||
|
SetEvent(codec->hCodecEvent);
|
||||||
|
WaitForSingleObject(codec->hDataEvent, INFINITE);
|
||||||
|
#else
|
||||||
|
pthread_cond_signal(&codec->codec_cond);
|
||||||
|
pthread_mutex_unlock(&codec->codec_mutex);
|
||||||
|
pthread_cond_wait(&codec->data_cond, &codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
remaining = codec->stream.end - codec->stream.ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (todo > remaining)
|
||||||
|
todo = remaining;
|
||||||
|
|
||||||
|
if (!todo)
|
||||||
|
break;
|
||||||
|
|
||||||
|
codec->stream.ptr += todo;
|
||||||
|
bytes -= todo;
|
||||||
|
done += todo;
|
||||||
|
}
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_unlock(&codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DEBUG("skipped total %llu bytes\n", (unsigned long long)done);
|
||||||
|
|
||||||
|
return done;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
ImagingIncrementalCodecWrite(ImagingIncrementalCodec codec,
|
||||||
|
const void *buffer, size_t bytes)
|
||||||
|
{
|
||||||
|
const UINT8 *ptr = (const UINT8 *)buffer;
|
||||||
|
size_t done = 0;
|
||||||
|
|
||||||
|
if (codec->read_or_write == INCREMENTAL_CODEC_READ) {
|
||||||
|
DEBUG("attempt to write from read codec\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG("write (have %llu bytes)\n", (unsigned long long)bytes);
|
||||||
|
|
||||||
|
if (codec->stream.fd >= 0)
|
||||||
|
return write(codec->stream.fd, buffer, bytes);
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_lock(&codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
while (bytes) {
|
||||||
|
size_t todo = bytes;
|
||||||
|
size_t remaining = codec->stream.end - codec->stream.ptr;
|
||||||
|
|
||||||
|
if (!remaining) {
|
||||||
|
if (codec->seekable && codec->stream.fd < 0) {
|
||||||
|
/* In this case, we maintain the stream buffer ourselves */
|
||||||
|
size_t old_size = codec->stream.top - codec->stream.buffer;
|
||||||
|
size_t new_size = (old_size + bytes + 65535) & ~65535;
|
||||||
|
UINT8 *new = (UINT8 *)realloc(codec->stream.buffer, new_size);
|
||||||
|
|
||||||
|
if (!new) {
|
||||||
|
codec->state->errcode = IMAGING_CODEC_MEMORY;
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_unlock(&codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
return done == 0 ? -1 : done;
|
||||||
|
}
|
||||||
|
|
||||||
|
codec->stream.ptr = codec->stream.ptr - codec->stream.buffer + new;
|
||||||
|
codec->stream.buffer = new;
|
||||||
|
codec->stream.end = new + new_size;
|
||||||
|
codec->stream.top = new + old_size;
|
||||||
|
} else {
|
||||||
|
DEBUG("waiting for space\n");
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_lock(&codec->codec_mutex);
|
||||||
|
#endif
|
||||||
|
codec->result = (int)(codec->stream.ptr - codec->stream.buffer);
|
||||||
|
#if _WIN32
|
||||||
|
SetEvent(codec->hCodecEvent);
|
||||||
|
WaitForSingleObject(codec->hDataEvent, INFINITE);
|
||||||
|
#else
|
||||||
|
pthread_cond_signal(&codec->codec_cond);
|
||||||
|
pthread_mutex_unlock(&codec->codec_mutex);
|
||||||
|
pthread_cond_wait(&codec->data_cond, &codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
remaining = codec->stream.end - codec->stream.ptr;
|
||||||
|
|
||||||
|
DEBUG("got %llu bytes\n", (unsigned long long)remaining);
|
||||||
|
}
|
||||||
|
if (todo > remaining)
|
||||||
|
todo = remaining;
|
||||||
|
|
||||||
|
if (!todo)
|
||||||
|
break;
|
||||||
|
|
||||||
|
memcpy (codec->stream.ptr, ptr, todo);
|
||||||
|
codec->stream.ptr += todo;
|
||||||
|
bytes -= todo;
|
||||||
|
done += todo;
|
||||||
|
ptr += todo;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codec->stream.ptr > codec->stream.top)
|
||||||
|
codec->stream.top = codec->stream.ptr;
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_unlock(&codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DEBUG("wrote total %llu bytes\n", (unsigned long long)done);
|
||||||
|
|
||||||
|
return done;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t
|
||||||
|
ImagingIncrementalCodecSeek(ImagingIncrementalCodec codec,
|
||||||
|
off_t bytes)
|
||||||
|
{
|
||||||
|
DEBUG("seeking (going to %llu bytes)\n", (unsigned long long)bytes);
|
||||||
|
|
||||||
|
if (codec->stream.fd >= 0)
|
||||||
|
return lseek(codec->stream.fd, bytes, SEEK_SET);
|
||||||
|
|
||||||
|
if (!codec->seekable) {
|
||||||
|
DEBUG("attempt to seek non-seekable stream\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes < 0) {
|
||||||
|
DEBUG("attempt to seek before stream start\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t buffered = codec->stream.top - codec->stream.buffer;
|
||||||
|
|
||||||
|
if (bytes <= buffered) {
|
||||||
|
DEBUG("seek within buffer\n");
|
||||||
|
codec->stream.ptr = codec->stream.buffer + bytes;
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffered + ImagingIncrementalCodecSkip(codec, bytes - buffered);
|
||||||
|
}
|
91
libImaging/Jpeg2K.h
Normal file
91
libImaging/Jpeg2K.h
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* The Python Imaging Library
|
||||||
|
* $Id$
|
||||||
|
*
|
||||||
|
* declarations for the OpenJPEG codec interface.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 by Coriolis Systems Limited
|
||||||
|
* Copyright (c) 2014 by Alastair Houghton
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <openjpeg-2.0/openjpeg.h>
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Decoder */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
/* CONFIGURATION */
|
||||||
|
|
||||||
|
/* File descriptor, if available; otherwise, -1 */
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
/* Specify the desired format */
|
||||||
|
OPJ_CODEC_FORMAT format;
|
||||||
|
|
||||||
|
/* Set to divide image resolution by 2**reduce. */
|
||||||
|
int reduce;
|
||||||
|
|
||||||
|
/* Set to limit the number of quality layers to decode (0 = all layers) */
|
||||||
|
int layers;
|
||||||
|
|
||||||
|
/* PRIVATE CONTEXT (set by decoder) */
|
||||||
|
const char *error_msg;
|
||||||
|
|
||||||
|
ImagingIncrementalCodec decoder;
|
||||||
|
} JPEG2KDECODESTATE;
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Encoder */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
/* CONFIGURATION */
|
||||||
|
|
||||||
|
/* File descriptor, if available; otherwise, -1 */
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
/* Specify the desired format */
|
||||||
|
OPJ_CODEC_FORMAT format;
|
||||||
|
|
||||||
|
/* Image offset */
|
||||||
|
int offset_x, offset_y;
|
||||||
|
|
||||||
|
/* Tile information */
|
||||||
|
int tile_offset_x, tile_offset_y;
|
||||||
|
int tile_size_x, tile_size_y;
|
||||||
|
|
||||||
|
/* Quality layers (a sequence of numbers giving *either* rates or dB) */
|
||||||
|
int quality_is_in_db;
|
||||||
|
PyObject *quality_layers;
|
||||||
|
|
||||||
|
/* Number of resolutions (DWT decompositions + 1 */
|
||||||
|
int num_resolutions;
|
||||||
|
|
||||||
|
/* Code block size */
|
||||||
|
int cblk_width, cblk_height;
|
||||||
|
|
||||||
|
/* Precinct size */
|
||||||
|
int precinct_width, precinct_height;
|
||||||
|
|
||||||
|
/* Compression style */
|
||||||
|
int irreversible;
|
||||||
|
|
||||||
|
/* Progression order (LRCP/RLCP/RPCL/PCRL/CPRL) */
|
||||||
|
OPJ_PROG_ORDER progression;
|
||||||
|
|
||||||
|
/* Cinema mode */
|
||||||
|
OPJ_CINEMA_MODE cinema_mode;
|
||||||
|
|
||||||
|
/* PRIVATE CONTEXT (set by decoder) */
|
||||||
|
const char *error_msg;
|
||||||
|
|
||||||
|
ImagingIncrementalCodec encoder;
|
||||||
|
} JPEG2KENCODESTATE;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Local Variables:
|
||||||
|
* c-basic-offset: 4
|
||||||
|
* End:
|
||||||
|
*
|
||||||
|
*/
|
747
libImaging/Jpeg2KDecode.c
Normal file
747
libImaging/Jpeg2KDecode.c
Normal file
|
@ -0,0 +1,747 @@
|
||||||
|
/*
|
||||||
|
* The Python Imaging Library.
|
||||||
|
* $Id$
|
||||||
|
*
|
||||||
|
* decoder for JPEG2000 image data.
|
||||||
|
*
|
||||||
|
* history:
|
||||||
|
* 2014-03-12 ajh Created
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Coriolis Systems Limited
|
||||||
|
* Copyright (c) 2014 Alastair Houghton
|
||||||
|
*
|
||||||
|
* See the README file for details on usage and redistribution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Imaging.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_OPENJPEG
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "Jpeg2K.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
OPJ_UINT32 tile_index;
|
||||||
|
OPJ_UINT32 data_size;
|
||||||
|
OPJ_INT32 x0, y0, x1, y1;
|
||||||
|
OPJ_UINT32 nb_comps;
|
||||||
|
} JPEG2KTILEINFO;
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Error handler */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2k_error(const char *msg, void *client_data)
|
||||||
|
{
|
||||||
|
JPEG2KDECODESTATE *state = (JPEG2KDECODESTATE *) client_data;
|
||||||
|
free((void *)state->error_msg);
|
||||||
|
state->error_msg = strdup(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Buffer input stream */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static OPJ_SIZE_T
|
||||||
|
j2k_read(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data)
|
||||||
|
{
|
||||||
|
ImagingIncrementalCodec decoder = (ImagingIncrementalCodec)p_user_data;
|
||||||
|
|
||||||
|
size_t len = ImagingIncrementalCodecRead(decoder, p_buffer, p_nb_bytes);
|
||||||
|
|
||||||
|
return len ? len : (OPJ_SIZE_T)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static OPJ_OFF_T
|
||||||
|
j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data)
|
||||||
|
{
|
||||||
|
ImagingIncrementalCodec decoder = (ImagingIncrementalCodec)p_user_data;
|
||||||
|
off_t pos = ImagingIncrementalCodecSkip(decoder, p_nb_bytes);
|
||||||
|
|
||||||
|
return pos ? pos : (OPJ_OFF_T)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Unpackers */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
typedef void (*j2k_unpacker_t)(opj_image_t *in,
|
||||||
|
const JPEG2KTILEINFO *tileInfo,
|
||||||
|
const UINT8 *data,
|
||||||
|
Imaging im);
|
||||||
|
|
||||||
|
struct j2k_decode_unpacker {
|
||||||
|
const char *mode;
|
||||||
|
OPJ_COLOR_SPACE color_space;
|
||||||
|
unsigned components;
|
||||||
|
j2k_unpacker_t unpacker;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline
|
||||||
|
unsigned j2ku_shift(unsigned x, int n)
|
||||||
|
{
|
||||||
|
if (n < 0)
|
||||||
|
return x >> -n;
|
||||||
|
else
|
||||||
|
return x << n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2ku_gray_l(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
|
||||||
|
const UINT8 *tiledata, Imaging im)
|
||||||
|
{
|
||||||
|
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
|
||||||
|
unsigned w = tileinfo->x1 - tileinfo->x0;
|
||||||
|
unsigned h = tileinfo->y1 - tileinfo->y0;
|
||||||
|
|
||||||
|
int shift = 8 - in->comps[0].prec;
|
||||||
|
int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0;
|
||||||
|
int csiz = (in->comps[0].prec + 7) >> 3;
|
||||||
|
|
||||||
|
unsigned x, y;
|
||||||
|
|
||||||
|
if (csiz == 3)
|
||||||
|
csiz = 4;
|
||||||
|
|
||||||
|
if (shift < 0)
|
||||||
|
offset += 1 << (-shift - 1);
|
||||||
|
|
||||||
|
switch (csiz) {
|
||||||
|
case 1:
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT8 *data = &tiledata[y * w];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
|
||||||
|
for (x = 0; x < w; ++x)
|
||||||
|
*row++ = j2ku_shift(offset + *data++, shift);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT16 *data = (const UINT16 *)&tiledata[2 * y * w];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
|
||||||
|
for (x = 0; x < w; ++x)
|
||||||
|
*row++ = j2ku_shift(offset + *data++, shift);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT32 *data = (const UINT32 *)&tiledata[4 * y * w];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
|
||||||
|
for (x = 0; x < w; ++x)
|
||||||
|
*row++ = j2ku_shift(offset + *data++, shift);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2ku_gray_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
|
||||||
|
const UINT8 *tiledata, Imaging im)
|
||||||
|
{
|
||||||
|
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
|
||||||
|
unsigned w = tileinfo->x1 - tileinfo->x0;
|
||||||
|
unsigned h = tileinfo->y1 - tileinfo->y0;
|
||||||
|
|
||||||
|
int shift = 8 - in->comps[0].prec;
|
||||||
|
int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0;
|
||||||
|
int csiz = (in->comps[0].prec + 7) >> 3;
|
||||||
|
|
||||||
|
unsigned x, y;
|
||||||
|
|
||||||
|
if (shift < 0)
|
||||||
|
offset += 1 << (-shift - 1);
|
||||||
|
|
||||||
|
if (csiz == 3)
|
||||||
|
csiz = 4;
|
||||||
|
|
||||||
|
switch (csiz) {
|
||||||
|
case 1:
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT8 *data = &tiledata[y * w];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
UINT8 byte = j2ku_shift(offset + *data++, shift);
|
||||||
|
row[0] = row[1] = row[2] = byte;
|
||||||
|
row[3] = 0xff;
|
||||||
|
row += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT16 *data = (UINT16 *)&tiledata[2 * y * w];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
UINT8 byte = j2ku_shift(offset + *data++, shift);
|
||||||
|
row[0] = row[1] = row[2] = byte;
|
||||||
|
row[3] = 0xff;
|
||||||
|
row += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT32 *data = (UINT32 *)&tiledata[4 * y * w];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
UINT8 byte = j2ku_shift(offset + *data++, shift);
|
||||||
|
row[0] = row[1] = row[2] = byte;
|
||||||
|
row[3] = 0xff;
|
||||||
|
row += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2ku_graya_la(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
|
||||||
|
const UINT8 *tiledata, Imaging im)
|
||||||
|
{
|
||||||
|
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
|
||||||
|
unsigned w = tileinfo->x1 - tileinfo->x0;
|
||||||
|
unsigned h = tileinfo->y1 - tileinfo->y0;
|
||||||
|
|
||||||
|
int shift = 8 - in->comps[0].prec;
|
||||||
|
int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0;
|
||||||
|
int csiz = (in->comps[0].prec + 7) >> 3;
|
||||||
|
int ashift = 8 - in->comps[1].prec;
|
||||||
|
int aoffset = in->comps[1].sgnd ? 1 << (in->comps[1].prec - 1) : 0;
|
||||||
|
int acsiz = (in->comps[1].prec + 7) >> 3;
|
||||||
|
const UINT8 *atiledata;
|
||||||
|
|
||||||
|
unsigned x, y;
|
||||||
|
|
||||||
|
if (csiz == 3)
|
||||||
|
csiz = 4;
|
||||||
|
if (acsiz == 3)
|
||||||
|
acsiz = 4;
|
||||||
|
|
||||||
|
if (shift < 0)
|
||||||
|
offset += 1 << (-shift - 1);
|
||||||
|
if (ashift < 0)
|
||||||
|
aoffset += 1 << (-ashift - 1);
|
||||||
|
|
||||||
|
atiledata = tiledata + csiz * w * h;
|
||||||
|
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT8 *data = &tiledata[csiz * y * w];
|
||||||
|
const UINT8 *adata = &atiledata[acsiz * y * w];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
UINT32 word = 0, aword = 0;
|
||||||
|
|
||||||
|
switch (csiz) {
|
||||||
|
case 1: word = *data++; break;
|
||||||
|
case 2: word = *(const UINT16 *)data; data += 2; break;
|
||||||
|
case 4: word = *(const UINT32 *)data; data += 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (acsiz) {
|
||||||
|
case 1: aword = *adata++; break;
|
||||||
|
case 2: aword = *(const UINT16 *)adata; adata += 2; break;
|
||||||
|
case 4: aword = *(const UINT32 *)adata; adata += 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT8 byte = j2ku_shift(offset + word, shift);
|
||||||
|
row[0] = row[1] = row[2] = byte;
|
||||||
|
row[3] = j2ku_shift(aoffset + aword, ashift);
|
||||||
|
row += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2ku_srgb_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
|
||||||
|
const UINT8 *tiledata, Imaging im)
|
||||||
|
{
|
||||||
|
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
|
||||||
|
unsigned w = tileinfo->x1 - tileinfo->x0;
|
||||||
|
unsigned h = tileinfo->y1 - tileinfo->y0;
|
||||||
|
|
||||||
|
int shifts[3], offsets[3], csiz[3];
|
||||||
|
const UINT8 *cdata[3];
|
||||||
|
const UINT8 *cptr = tiledata;
|
||||||
|
unsigned n, x, y;
|
||||||
|
|
||||||
|
for (n = 0; n < 3; ++n) {
|
||||||
|
cdata[n] = cptr;
|
||||||
|
shifts[n] = 8 - in->comps[n].prec;
|
||||||
|
offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0;
|
||||||
|
csiz[n] = (in->comps[n].prec + 7) >> 3;
|
||||||
|
|
||||||
|
if (csiz[n] == 3)
|
||||||
|
csiz[n] = 4;
|
||||||
|
|
||||||
|
if (shifts[n] < 0)
|
||||||
|
offsets[n] += 1 << (-shifts[n] - 1);
|
||||||
|
|
||||||
|
cptr += csiz[n] * w * h;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT8 *data[3];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
|
||||||
|
for (n = 0; n < 3; ++n)
|
||||||
|
data[n] = &cdata[n][csiz[n] * y * w];
|
||||||
|
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
for (n = 0; n < 3; ++n) {
|
||||||
|
UINT32 word = 0;
|
||||||
|
|
||||||
|
switch (csiz[n]) {
|
||||||
|
case 1: word = *data[n]++; break;
|
||||||
|
case 2: word = *(const UINT16 *)data[n]; data[n] += 2; break;
|
||||||
|
case 4: word = *(const UINT32 *)data[n]; data[n] += 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
row[n] = j2ku_shift(offsets[n] + word, shifts[n]);
|
||||||
|
}
|
||||||
|
row[3] = 0xff;
|
||||||
|
row += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2ku_sycc_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
|
||||||
|
const UINT8 *tiledata, Imaging im)
|
||||||
|
{
|
||||||
|
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
|
||||||
|
unsigned w = tileinfo->x1 - tileinfo->x0;
|
||||||
|
unsigned h = tileinfo->y1 - tileinfo->y0;
|
||||||
|
|
||||||
|
int shifts[3], offsets[3], csiz[3];
|
||||||
|
const UINT8 *cdata[3];
|
||||||
|
const UINT8 *cptr = tiledata;
|
||||||
|
unsigned n, x, y;
|
||||||
|
|
||||||
|
for (n = 0; n < 3; ++n) {
|
||||||
|
cdata[n] = cptr;
|
||||||
|
shifts[n] = 8 - in->comps[n].prec;
|
||||||
|
offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0;
|
||||||
|
csiz[n] = (in->comps[n].prec + 7) >> 3;
|
||||||
|
|
||||||
|
if (csiz[n] == 3)
|
||||||
|
csiz[n] = 4;
|
||||||
|
|
||||||
|
if (shifts[n] < 0)
|
||||||
|
offsets[n] += 1 << (-shifts[n] - 1);
|
||||||
|
|
||||||
|
cptr += csiz[n] * w * h;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT8 *data[3];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
|
||||||
|
UINT8 *row_start = row;
|
||||||
|
for (n = 0; n < 3; ++n)
|
||||||
|
data[n] = &cdata[n][csiz[n] * y * w];
|
||||||
|
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
for (n = 0; n < 3; ++n) {
|
||||||
|
UINT32 word = 0;
|
||||||
|
|
||||||
|
switch (csiz[n]) {
|
||||||
|
case 1: word = *data[n]++; break;
|
||||||
|
case 2: word = *(const UINT16 *)data[n]; data[n] += 2; break;
|
||||||
|
case 4: word = *(const UINT32 *)data[n]; data[n] += 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
row[n] = j2ku_shift(offsets[n] + word, shifts[n]);
|
||||||
|
}
|
||||||
|
row[3] = 0xff;
|
||||||
|
row += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImagingConvertYCbCr2RGB(row_start, row_start, w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2ku_srgba_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
|
||||||
|
const UINT8 *tiledata, Imaging im)
|
||||||
|
{
|
||||||
|
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
|
||||||
|
unsigned w = tileinfo->x1 - tileinfo->x0;
|
||||||
|
unsigned h = tileinfo->y1 - tileinfo->y0;
|
||||||
|
|
||||||
|
int shifts[4], offsets[4], csiz[4];
|
||||||
|
const UINT8 *cdata[4];
|
||||||
|
const UINT8 *cptr = tiledata;
|
||||||
|
unsigned n, x, y;
|
||||||
|
|
||||||
|
for (n = 0; n < 4; ++n) {
|
||||||
|
cdata[n] = cptr;
|
||||||
|
shifts[n] = 8 - in->comps[n].prec;
|
||||||
|
offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0;
|
||||||
|
csiz[n] = (in->comps[n].prec + 7) >> 3;
|
||||||
|
|
||||||
|
if (csiz[n] == 3)
|
||||||
|
csiz[n] = 4;
|
||||||
|
|
||||||
|
if (shifts[n] < 0)
|
||||||
|
offsets[n] += 1 << (-shifts[n] - 1);
|
||||||
|
|
||||||
|
cptr += csiz[n] * w * h;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT8 *data[4];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
|
||||||
|
for (n = 0; n < 4; ++n)
|
||||||
|
data[n] = &cdata[n][csiz[n] * y * w];
|
||||||
|
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
for (n = 0; n < 4; ++n) {
|
||||||
|
UINT32 word = 0;
|
||||||
|
|
||||||
|
switch (csiz[n]) {
|
||||||
|
case 1: word = *data[n]++; break;
|
||||||
|
case 2: word = *(const UINT16 *)data[n]; data[n] += 2; break;
|
||||||
|
case 4: word = *(const UINT32 *)data[n]; data[n] += 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
row[n] = j2ku_shift(offsets[n] + word, shifts[n]);
|
||||||
|
}
|
||||||
|
row += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2ku_sycca_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
|
||||||
|
const UINT8 *tiledata, Imaging im)
|
||||||
|
{
|
||||||
|
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
|
||||||
|
unsigned w = tileinfo->x1 - tileinfo->x0;
|
||||||
|
unsigned h = tileinfo->y1 - tileinfo->y0;
|
||||||
|
|
||||||
|
int shifts[4], offsets[4], csiz[4];
|
||||||
|
const UINT8 *cdata[4];
|
||||||
|
const UINT8 *cptr = tiledata;
|
||||||
|
unsigned n, x, y;
|
||||||
|
|
||||||
|
for (n = 0; n < 4; ++n) {
|
||||||
|
cdata[n] = cptr;
|
||||||
|
shifts[n] = 8 - in->comps[n].prec;
|
||||||
|
offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0;
|
||||||
|
csiz[n] = (in->comps[n].prec + 7) >> 3;
|
||||||
|
|
||||||
|
if (csiz[n] == 3)
|
||||||
|
csiz[n] = 4;
|
||||||
|
|
||||||
|
if (shifts[n] < 0)
|
||||||
|
offsets[n] += 1 << (-shifts[n] - 1);
|
||||||
|
|
||||||
|
cptr += csiz[n] * w * h;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT8 *data[4];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
|
||||||
|
UINT8 *row_start = row;
|
||||||
|
for (n = 0; n < 4; ++n)
|
||||||
|
data[n] = &cdata[n][csiz[n] * y * w];
|
||||||
|
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
for (n = 0; n < 4; ++n) {
|
||||||
|
UINT32 word = 0;
|
||||||
|
|
||||||
|
switch (csiz[n]) {
|
||||||
|
case 1: word = *data[n]++; break;
|
||||||
|
case 2: word = *(const UINT16 *)data[n]; data[n] += 2; break;
|
||||||
|
case 4: word = *(const UINT32 *)data[n]; data[n] += 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
row[n] = j2ku_shift(offsets[n] + word, shifts[n]);
|
||||||
|
}
|
||||||
|
row += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImagingConvertYCbCr2RGB(row_start, row_start, w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct j2k_decode_unpacker j2k_unpackers[] = {
|
||||||
|
{ "L", OPJ_CLRSPC_GRAY, 1, j2ku_gray_l },
|
||||||
|
{ "LA", OPJ_CLRSPC_GRAY, 2, j2ku_graya_la },
|
||||||
|
{ "RGB", OPJ_CLRSPC_GRAY, 1, j2ku_gray_rgb },
|
||||||
|
{ "RGB", OPJ_CLRSPC_GRAY, 2, j2ku_gray_rgb },
|
||||||
|
{ "RGB", OPJ_CLRSPC_SRGB, 3, j2ku_srgb_rgb },
|
||||||
|
{ "RGB", OPJ_CLRSPC_SYCC, 3, j2ku_sycc_rgb },
|
||||||
|
{ "RGB", OPJ_CLRSPC_SRGB, 4, j2ku_srgb_rgb },
|
||||||
|
{ "RGB", OPJ_CLRSPC_SYCC, 4, j2ku_sycc_rgb },
|
||||||
|
{ "RGBA", OPJ_CLRSPC_GRAY, 1, j2ku_gray_rgb },
|
||||||
|
{ "RGBA", OPJ_CLRSPC_GRAY, 2, j2ku_graya_la },
|
||||||
|
{ "RGBA", OPJ_CLRSPC_SRGB, 3, j2ku_srgb_rgb },
|
||||||
|
{ "RGBA", OPJ_CLRSPC_SYCC, 3, j2ku_sycc_rgb },
|
||||||
|
{ "RGBA", OPJ_CLRSPC_SRGB, 4, j2ku_srgba_rgba },
|
||||||
|
{ "RGBA", OPJ_CLRSPC_SYCC, 4, j2ku_sycca_rgba },
|
||||||
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Decoder */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
enum {
|
||||||
|
J2K_STATE_START = 0,
|
||||||
|
J2K_STATE_DECODING = 1,
|
||||||
|
J2K_STATE_DONE = 2,
|
||||||
|
J2K_STATE_FAILED = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
j2k_decode_entry(Imaging im, ImagingCodecState state,
|
||||||
|
ImagingIncrementalCodec decoder)
|
||||||
|
{
|
||||||
|
JPEG2KDECODESTATE *context = (JPEG2KDECODESTATE *) state->context;
|
||||||
|
opj_stream_t *stream = NULL;
|
||||||
|
opj_image_t *image = NULL;
|
||||||
|
opj_codec_t *codec = NULL;
|
||||||
|
opj_dparameters_t params;
|
||||||
|
OPJ_COLOR_SPACE color_space;
|
||||||
|
j2k_unpacker_t unpack = NULL;
|
||||||
|
size_t buffer_size = 0;
|
||||||
|
unsigned n;
|
||||||
|
|
||||||
|
stream = opj_stream_default_create(OPJ_TRUE);
|
||||||
|
|
||||||
|
if (!stream) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
opj_stream_set_read_function(stream, j2k_read);
|
||||||
|
opj_stream_set_skip_function(stream, j2k_skip);
|
||||||
|
|
||||||
|
opj_stream_set_user_data(stream, decoder);
|
||||||
|
|
||||||
|
/* Setup decompression context */
|
||||||
|
context->error_msg = NULL;
|
||||||
|
|
||||||
|
opj_set_default_decoder_parameters(¶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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Colorspace Number of components PIL mode
|
||||||
|
------------------------------------------------------
|
||||||
|
sRGB 3 RGB
|
||||||
|
sRGB 4 RGBA
|
||||||
|
gray 1 L or I
|
||||||
|
gray 2 LA
|
||||||
|
YCC 3 YCbCr
|
||||||
|
|
||||||
|
|
||||||
|
If colorspace is unspecified, we assume:
|
||||||
|
|
||||||
|
Number of components Colorspace
|
||||||
|
-----------------------------------------
|
||||||
|
1 gray
|
||||||
|
2 gray (+ alpha)
|
||||||
|
3 sRGB
|
||||||
|
4 sRGB (+ alpha)
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Find the correct unpacker */
|
||||||
|
color_space = image->color_space;
|
||||||
|
|
||||||
|
if (color_space == OPJ_CLRSPC_UNSPECIFIED) {
|
||||||
|
switch (image->numcomps) {
|
||||||
|
case 1: case 2: color_space = OPJ_CLRSPC_GRAY; break;
|
||||||
|
case 3: case 4: color_space = OPJ_CLRSPC_SRGB; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (n = 0; n < sizeof(j2k_unpackers) / sizeof (j2k_unpackers[0]); ++n) {
|
||||||
|
if (color_space == j2k_unpackers[n].color_space
|
||||||
|
&& image->numcomps == j2k_unpackers[n].components
|
||||||
|
&& strcmp (im->mode, j2k_unpackers[n].mode) == 0) {
|
||||||
|
unpack = j2k_unpackers[n].unpacker;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!unpack) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Decode the image tile-by-tile; this means we only need use as much
|
||||||
|
memory as is required for one tile's worth of components. */
|
||||||
|
for (;;) {
|
||||||
|
JPEG2KTILEINFO tile_info;
|
||||||
|
OPJ_BOOL should_continue;
|
||||||
|
|
||||||
|
if (!opj_read_tile_header(codec,
|
||||||
|
stream,
|
||||||
|
&tile_info.tile_index,
|
||||||
|
&tile_info.data_size,
|
||||||
|
&tile_info.x0, &tile_info.y0,
|
||||||
|
&tile_info.x1, &tile_info.y1,
|
||||||
|
&tile_info.nb_comps,
|
||||||
|
&should_continue)) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!should_continue)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (buffer_size < tile_info.data_size) {
|
||||||
|
UINT8 *new = realloc (state->buffer, tile_info.data_size);
|
||||||
|
if (!new) {
|
||||||
|
state->errcode = IMAGING_CODEC_MEMORY;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
state->buffer = new;
|
||||||
|
buffer_size = tile_info.data_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!opj_decode_tile_data(codec,
|
||||||
|
tile_info.tile_index,
|
||||||
|
(OPJ_BYTE *)state->buffer,
|
||||||
|
tile_info.data_size,
|
||||||
|
stream)) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check the tile bounds; if the tile is outside the image area,
|
||||||
|
or if it has a negative width or height (i.e. the coordinates are
|
||||||
|
swapped), bail. */
|
||||||
|
if (tile_info.x0 >= tile_info.x1
|
||||||
|
|| tile_info.y0 >= tile_info.y1
|
||||||
|
|| tile_info.x0 < image->x0
|
||||||
|
|| tile_info.y0 < image->y0
|
||||||
|
|| tile_info.x1 - image->x0 > im->xsize
|
||||||
|
|| tile_info.y1 - image->y0 > im->ysize) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
unpack(image, &tile_info, state->buffer, im);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!opj_end_decompress(codec, stream)) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->state = J2K_STATE_DONE;
|
||||||
|
state->errcode = IMAGING_CODEC_END;
|
||||||
|
|
||||||
|
quick_exit:
|
||||||
|
if (codec)
|
||||||
|
opj_destroy_codec(codec);
|
||||||
|
if (image)
|
||||||
|
opj_image_destroy(image);
|
||||||
|
if (stream)
|
||||||
|
opj_stream_destroy(stream);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ImagingJpeg2KDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes)
|
||||||
|
{
|
||||||
|
JPEG2KDECODESTATE *context = (JPEG2KDECODESTATE *) state->context;
|
||||||
|
|
||||||
|
if (state->state == J2K_STATE_DONE || state->state == J2K_STATE_FAILED)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (state->state == J2K_STATE_START) {
|
||||||
|
context->decoder = ImagingIncrementalCodecCreate(j2k_decode_entry,
|
||||||
|
im, state,
|
||||||
|
INCREMENTAL_CODEC_READ,
|
||||||
|
INCREMENTAL_CODEC_NOT_SEEKABLE,
|
||||||
|
context->fd);
|
||||||
|
|
||||||
|
if (!context->decoder) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->state = J2K_STATE_DECODING;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ImagingIncrementalCodecPushBuffer(context->decoder, buf, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Cleanup */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
int
|
||||||
|
ImagingJpeg2KDecodeCleanup(ImagingCodecState state) {
|
||||||
|
JPEG2KDECODESTATE *context = (JPEG2KDECODESTATE *)state->context;
|
||||||
|
|
||||||
|
if (context->error_msg)
|
||||||
|
free ((void *)context->error_msg);
|
||||||
|
|
||||||
|
if (context->decoder)
|
||||||
|
ImagingIncrementalCodecDestroy(context->decoder);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
ImagingJpeg2KVersion(void)
|
||||||
|
{
|
||||||
|
return opj_version();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* HAVE_OPENJPEG */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Local Variables:
|
||||||
|
* c-basic-offset: 4
|
||||||
|
* End:
|
||||||
|
*
|
||||||
|
*/
|
562
libImaging/Jpeg2KEncode.c
Normal file
562
libImaging/Jpeg2KEncode.c
Normal file
|
@ -0,0 +1,562 @@
|
||||||
|
/*
|
||||||
|
* The Python Imaging Library.
|
||||||
|
* $Id$
|
||||||
|
*
|
||||||
|
* decoder for JPEG2000 image data.
|
||||||
|
*
|
||||||
|
* history:
|
||||||
|
* 2014-03-12 ajh Created
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Coriolis Systems Limited
|
||||||
|
* Copyright (c) 2014 Alastair Houghton
|
||||||
|
*
|
||||||
|
* See the README file for details on usage and redistribution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Imaging.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_OPENJPEG
|
||||||
|
|
||||||
|
#include "Jpeg2K.h"
|
||||||
|
|
||||||
|
#define CINEMA_24_CS_LENGTH 1302083
|
||||||
|
#define CINEMA_48_CS_LENGTH 651041
|
||||||
|
#define COMP_24_CS_MAX_LENGTH 1041666
|
||||||
|
#define COMP_48_CS_MAX_LENGTH 520833
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Error handler */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2k_error(const char *msg, void *client_data)
|
||||||
|
{
|
||||||
|
JPEG2KENCODESTATE *state = (JPEG2KENCODESTATE *) client_data;
|
||||||
|
free((void *)state->error_msg);
|
||||||
|
state->error_msg = strdup(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Buffer output stream */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static OPJ_SIZE_T
|
||||||
|
j2k_write(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data)
|
||||||
|
{
|
||||||
|
ImagingIncrementalCodec encoder = (ImagingIncrementalCodec)p_user_data;
|
||||||
|
size_t len = ImagingIncrementalCodecWrite(encoder, p_buffer, p_nb_bytes);
|
||||||
|
|
||||||
|
return len ? len : (OPJ_SIZE_T)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static OPJ_OFF_T
|
||||||
|
j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data)
|
||||||
|
{
|
||||||
|
ImagingIncrementalCodec encoder = (ImagingIncrementalCodec)p_user_data;
|
||||||
|
off_t pos = ImagingIncrementalCodecSkip(encoder, p_nb_bytes);
|
||||||
|
|
||||||
|
return pos ? pos : (OPJ_OFF_T)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static OPJ_BOOL
|
||||||
|
j2k_seek(OPJ_OFF_T p_nb_bytes, void *p_user_data)
|
||||||
|
{
|
||||||
|
ImagingIncrementalCodec encoder = (ImagingIncrementalCodec)p_user_data;
|
||||||
|
off_t pos = ImagingIncrementalCodecSeek(encoder, p_nb_bytes);
|
||||||
|
|
||||||
|
return pos == p_nb_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Encoder */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
typedef void (*j2k_pack_tile_t)(Imaging im, UINT8 *buf,
|
||||||
|
unsigned x0, unsigned y0,
|
||||||
|
unsigned w, unsigned h);
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2k_pack_l(Imaging im, UINT8 *buf,
|
||||||
|
unsigned x0, unsigned y0, unsigned w, unsigned h)
|
||||||
|
{
|
||||||
|
UINT8 *ptr = buf;
|
||||||
|
unsigned x,y;
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
UINT8 *data = (UINT8 *)(im->image[y + y0] + x0);
|
||||||
|
for (x = 0; x < w; ++x)
|
||||||
|
*ptr++ = *data++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2k_pack_la(Imaging im, UINT8 *buf,
|
||||||
|
unsigned x0, unsigned y0, unsigned w, unsigned h)
|
||||||
|
{
|
||||||
|
UINT8 *ptr = buf;
|
||||||
|
UINT8 *ptra = buf + w * h;
|
||||||
|
unsigned x,y;
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
UINT8 *data = (UINT8 *)(im->image[y + y0] + 4 * x0);
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
*ptr++ = data[0];
|
||||||
|
*ptra++ = data[3];
|
||||||
|
data += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2k_pack_rgb(Imaging im, UINT8 *buf,
|
||||||
|
unsigned x0, unsigned y0, unsigned w, unsigned h)
|
||||||
|
{
|
||||||
|
UINT8 *pr = buf;
|
||||||
|
UINT8 *pg = pr + w * h;
|
||||||
|
UINT8 *pb = pg + w * h;
|
||||||
|
unsigned x,y;
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
UINT8 *data = (UINT8 *)(im->image[y + y0] + 4 * x0);
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
*pr++ = data[0];
|
||||||
|
*pg++ = data[1];
|
||||||
|
*pb++ = data[2];
|
||||||
|
data += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2k_pack_rgba(Imaging im, UINT8 *buf,
|
||||||
|
unsigned x0, unsigned y0, unsigned w, unsigned h)
|
||||||
|
{
|
||||||
|
UINT8 *pr = buf;
|
||||||
|
UINT8 *pg = pr + w * h;
|
||||||
|
UINT8 *pb = pg + w * h;
|
||||||
|
UINT8 *pa = pb + w * h;
|
||||||
|
unsigned x,y;
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
UINT8 *data = (UINT8 *)(im->image[y + y0] + 4 * x0);
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
*pr++ = *data++;
|
||||||
|
*pg++ = *data++;
|
||||||
|
*pb++ = *data++;
|
||||||
|
*pa++ = *data++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum {
|
||||||
|
J2K_STATE_START = 0,
|
||||||
|
J2K_STATE_ENCODING = 1,
|
||||||
|
J2K_STATE_DONE = 2,
|
||||||
|
J2K_STATE_FAILED = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2k_set_cinema_params(Imaging im, int components, opj_cparameters_t *params)
|
||||||
|
{
|
||||||
|
float rate;
|
||||||
|
unsigned n;
|
||||||
|
|
||||||
|
/* These settings have been copied from opj_compress in the OpenJPEG
|
||||||
|
sources. */
|
||||||
|
|
||||||
|
params->tile_size_on = OPJ_FALSE;
|
||||||
|
params->cp_tdx = params->cp_tdy = 1;
|
||||||
|
params->tp_flag = 'C';
|
||||||
|
params->tp_on = 1;
|
||||||
|
params->cp_tx0 = params->cp_ty0 = 0;
|
||||||
|
params->image_offset_x0 = params->image_offset_y0 = 0;
|
||||||
|
params->cblockw_init = 32;
|
||||||
|
params->cblockh_init = 32;
|
||||||
|
params->csty |= 0x01;
|
||||||
|
params->prog_order = OPJ_CPRL;
|
||||||
|
params->roi_compno = -1;
|
||||||
|
params->subsampling_dx = params->subsampling_dy = 1;
|
||||||
|
params->irreversible = 1;
|
||||||
|
|
||||||
|
if (params->cp_cinema == OPJ_CINEMA4K_24) {
|
||||||
|
float max_rate = ((float)(components * im->xsize * im->ysize * 8)
|
||||||
|
/ (CINEMA_24_CS_LENGTH * 8));
|
||||||
|
|
||||||
|
params->POC[0].tile = 1;
|
||||||
|
params->POC[0].resno0 = 0;
|
||||||
|
params->POC[0].compno0 = 0;
|
||||||
|
params->POC[0].layno1 = 1;
|
||||||
|
params->POC[0].resno1 = params->numresolution - 1;
|
||||||
|
params->POC[0].compno1 = 3;
|
||||||
|
params->POC[0].prg1 = OPJ_CPRL;
|
||||||
|
params->POC[1].tile = 1;
|
||||||
|
params->POC[1].resno0 = 0;
|
||||||
|
params->POC[1].compno0 = 0;
|
||||||
|
params->POC[1].layno1 = 1;
|
||||||
|
params->POC[1].resno1 = params->numresolution - 1;
|
||||||
|
params->POC[1].compno1 = 3;
|
||||||
|
params->POC[1].prg1 = OPJ_CPRL;
|
||||||
|
params->numpocs = 2;
|
||||||
|
|
||||||
|
for (n = 0; n < params->tcp_numlayers; ++n) {
|
||||||
|
rate = 0;
|
||||||
|
if (params->tcp_rates[0] == 0) {
|
||||||
|
params->tcp_rates[n] = max_rate;
|
||||||
|
} else {
|
||||||
|
rate = ((float)(components * im->xsize * im->ysize * 8)
|
||||||
|
/ (params->tcp_rates[n] * 8));
|
||||||
|
if (rate > CINEMA_24_CS_LENGTH)
|
||||||
|
params->tcp_rates[n] = max_rate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params->max_comp_size = COMP_24_CS_MAX_LENGTH;
|
||||||
|
} else {
|
||||||
|
float max_rate = ((float)(components * im->xsize * im->ysize * 8)
|
||||||
|
/ (CINEMA_48_CS_LENGTH * 8));
|
||||||
|
|
||||||
|
for (n = 0; n < params->tcp_numlayers; ++n) {
|
||||||
|
rate = 0;
|
||||||
|
if (params->tcp_rates[0] == 0) {
|
||||||
|
params->tcp_rates[n] = max_rate;
|
||||||
|
} else {
|
||||||
|
rate = ((float)(components * im->xsize * im->ysize * 8)
|
||||||
|
/ (params->tcp_rates[n] * 8));
|
||||||
|
if (rate > CINEMA_48_CS_LENGTH)
|
||||||
|
params->tcp_rates[n] = max_rate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params->max_comp_size = COMP_48_CS_MAX_LENGTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
j2k_encode_entry(Imaging im, ImagingCodecState state,
|
||||||
|
ImagingIncrementalCodec encoder)
|
||||||
|
{
|
||||||
|
JPEG2KENCODESTATE *context = (JPEG2KENCODESTATE *)state->context;
|
||||||
|
opj_stream_t *stream = NULL;
|
||||||
|
opj_image_t *image = NULL;
|
||||||
|
opj_codec_t *codec = NULL;
|
||||||
|
opj_cparameters_t params;
|
||||||
|
unsigned components;
|
||||||
|
OPJ_COLOR_SPACE color_space;
|
||||||
|
opj_image_cmptparm_t image_params[4];
|
||||||
|
unsigned xsiz, ysiz;
|
||||||
|
unsigned tile_width, tile_height;
|
||||||
|
unsigned tiles_x, tiles_y, num_tiles;
|
||||||
|
unsigned x, y, tile_ndx;
|
||||||
|
unsigned n;
|
||||||
|
j2k_pack_tile_t pack;
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
stream = opj_stream_default_create(OPJ_FALSE);
|
||||||
|
|
||||||
|
if (!stream) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
opj_stream_set_write_function(stream, j2k_write);
|
||||||
|
opj_stream_set_skip_function(stream, j2k_skip);
|
||||||
|
opj_stream_set_seek_function(stream, j2k_seek);
|
||||||
|
|
||||||
|
opj_stream_set_user_data(stream, encoder);
|
||||||
|
|
||||||
|
/* Setup an opj_image */
|
||||||
|
if (strcmp (im->mode, "L") == 0) {
|
||||||
|
components = 1;
|
||||||
|
color_space = OPJ_CLRSPC_GRAY;
|
||||||
|
pack = j2k_pack_l;
|
||||||
|
} else if (strcmp (im->mode, "LA") == 0) {
|
||||||
|
components = 2;
|
||||||
|
color_space = OPJ_CLRSPC_GRAY;
|
||||||
|
pack = j2k_pack_la;
|
||||||
|
} else if (strcmp (im->mode, "RGB") == 0) {
|
||||||
|
components = 3;
|
||||||
|
color_space = OPJ_CLRSPC_SRGB;
|
||||||
|
pack = j2k_pack_rgb;
|
||||||
|
} else if (strcmp (im->mode, "YCbCr") == 0) {
|
||||||
|
components = 3;
|
||||||
|
color_space = OPJ_CLRSPC_SYCC;
|
||||||
|
pack = j2k_pack_rgb;
|
||||||
|
} else if (strcmp (im->mode, "RGBA") == 0) {
|
||||||
|
components = 4;
|
||||||
|
color_space = OPJ_CLRSPC_SRGB;
|
||||||
|
pack = j2k_pack_rgba;
|
||||||
|
} else {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (n = 0; n < components; ++n) {
|
||||||
|
image_params[n].dx = image_params[n].dy = 1;
|
||||||
|
image_params[n].w = im->xsize;
|
||||||
|
image_params[n].h = im->ysize;
|
||||||
|
image_params[n].x0 = image_params[n].y0 = 0;
|
||||||
|
image_params[n].prec = 8;
|
||||||
|
image_params[n].bpp = 8;
|
||||||
|
image_params[n].sgnd = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
image = opj_image_create(components, image_params, color_space);
|
||||||
|
|
||||||
|
/* Setup compression context */
|
||||||
|
context->error_msg = NULL;
|
||||||
|
|
||||||
|
opj_set_default_encoder_parameters(¶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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context->precinct_width >= 4 && context->precinct_height >= 4
|
||||||
|
&& context->precinct_width >= context->cblk_width
|
||||||
|
&& context->precinct_height > context->cblk_height) {
|
||||||
|
params.prcw_init[0] = context->precinct_width;
|
||||||
|
params.prch_init[0] = context->precinct_height;
|
||||||
|
params.res_spec = 1;
|
||||||
|
params.csty |= 0x01;
|
||||||
|
}
|
||||||
|
|
||||||
|
params.irreversible = context->irreversible;
|
||||||
|
|
||||||
|
params.prog_order = context->progression;
|
||||||
|
|
||||||
|
params.cp_cinema = context->cinema_mode;
|
||||||
|
|
||||||
|
switch (params.cp_cinema) {
|
||||||
|
case OPJ_OFF:
|
||||||
|
params.cp_rsiz = OPJ_STD_RSIZ;
|
||||||
|
break;
|
||||||
|
case OPJ_CINEMA2K_24:
|
||||||
|
case OPJ_CINEMA2K_48:
|
||||||
|
params.cp_rsiz = OPJ_CINEMA2K;
|
||||||
|
if (params.numresolution > 6)
|
||||||
|
params.numresolution = 6;
|
||||||
|
break;
|
||||||
|
case OPJ_CINEMA4K_24:
|
||||||
|
params.cp_rsiz = OPJ_CINEMA4K;
|
||||||
|
if (params.numresolution > 7)
|
||||||
|
params.numresolution = 7;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context->cinema_mode != OPJ_OFF)
|
||||||
|
j2k_set_cinema_params(im, components, ¶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 + (params.image_offset_x0 - params.cp_tx0)
|
||||||
|
+ tile_width - 1) / tile_width;
|
||||||
|
tiles_y = (im->ysize + (params.image_offset_y0 - params.cp_ty0)
|
||||||
|
+ tile_height - 1) / tile_height;
|
||||||
|
|
||||||
|
num_tiles = tiles_x * tiles_y;
|
||||||
|
|
||||||
|
state->buffer = malloc (tile_width * tile_height * components);
|
||||||
|
|
||||||
|
tile_ndx = 0;
|
||||||
|
for (y = 0; y < tiles_y; ++y) {
|
||||||
|
unsigned ty0 = params.cp_ty0 + y * tile_height;
|
||||||
|
unsigned ty1 = ty0 + tile_height;
|
||||||
|
unsigned pixy, pixh;
|
||||||
|
|
||||||
|
if (ty0 < params.image_offset_y0)
|
||||||
|
ty0 = params.image_offset_y0;
|
||||||
|
if (ty1 > ysiz)
|
||||||
|
ty1 = ysiz;
|
||||||
|
|
||||||
|
pixy = ty0 - params.image_offset_y0;
|
||||||
|
pixh = ty1 - ty0;
|
||||||
|
|
||||||
|
for (x = 0; x < tiles_x; ++x) {
|
||||||
|
unsigned tx0 = params.cp_tx0 + x * tile_width;
|
||||||
|
unsigned tx1 = tx0 + tile_width;
|
||||||
|
unsigned pixx, pixw;
|
||||||
|
unsigned data_size;
|
||||||
|
|
||||||
|
if (tx0 < params.image_offset_x0)
|
||||||
|
tx0 = params.image_offset_x0;
|
||||||
|
if (tx1 > xsiz)
|
||||||
|
tx1 = xsiz;
|
||||||
|
|
||||||
|
pixx = tx0 - params.image_offset_x0;
|
||||||
|
pixw = tx1 - tx0;
|
||||||
|
|
||||||
|
pack(im, state->buffer, pixx, pixy, pixw, pixh);
|
||||||
|
|
||||||
|
data_size = pixw * pixh * components;
|
||||||
|
|
||||||
|
if (!opj_write_tile(codec, tile_ndx++, state->buffer,
|
||||||
|
data_size, stream)) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!opj_end_compress(codec, stream)) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->errcode = IMAGING_CODEC_END;
|
||||||
|
state->state = J2K_STATE_DONE;
|
||||||
|
ret = (int)ImagingIncrementalCodecBytesInBuffer(encoder);
|
||||||
|
|
||||||
|
quick_exit:
|
||||||
|
if (codec)
|
||||||
|
opj_destroy_codec(codec);
|
||||||
|
if (image)
|
||||||
|
opj_image_destroy(image);
|
||||||
|
if (stream)
|
||||||
|
opj_stream_destroy(stream);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ImagingJpeg2KEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes)
|
||||||
|
{
|
||||||
|
JPEG2KENCODESTATE *context = (JPEG2KENCODESTATE *)state->context;
|
||||||
|
|
||||||
|
if (state->state == J2K_STATE_FAILED)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (state->state == J2K_STATE_START) {
|
||||||
|
int seekable = (context->format != OPJ_CODEC_J2K
|
||||||
|
? INCREMENTAL_CODEC_SEEKABLE
|
||||||
|
: INCREMENTAL_CODEC_NOT_SEEKABLE);
|
||||||
|
|
||||||
|
context->encoder = ImagingIncrementalCodecCreate(j2k_encode_entry,
|
||||||
|
im, state,
|
||||||
|
INCREMENTAL_CODEC_WRITE,
|
||||||
|
seekable,
|
||||||
|
context->fd);
|
||||||
|
|
||||||
|
if (!context->encoder) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->state = J2K_STATE_ENCODING;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ImagingIncrementalCodecPushBuffer(context->encoder, buf, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Cleanup */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
int
|
||||||
|
ImagingJpeg2KEncodeCleanup(ImagingCodecState state) {
|
||||||
|
JPEG2KENCODESTATE *context = (JPEG2KENCODESTATE *)state->context;
|
||||||
|
|
||||||
|
if (context->quality_layers)
|
||||||
|
Py_DECREF(context->quality_layers);
|
||||||
|
|
||||||
|
if (context->error_msg)
|
||||||
|
free ((void *)context->error_msg);
|
||||||
|
|
||||||
|
if (context->encoder)
|
||||||
|
ImagingIncrementalCodecDestroy(context->encoder);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* HAVE_OPENJPEG */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Local Variables:
|
||||||
|
* c-basic-offset: 4
|
||||||
|
* End:
|
||||||
|
*
|
||||||
|
*/
|
|
@ -192,6 +192,7 @@ if __name__ == "__main__":
|
||||||
check_module("PIL CORE", "PIL._imaging")
|
check_module("PIL CORE", "PIL._imaging")
|
||||||
check_module("TKINTER", "PIL._imagingtk")
|
check_module("TKINTER", "PIL._imagingtk")
|
||||||
check_codec("JPEG", "jpeg")
|
check_codec("JPEG", "jpeg")
|
||||||
|
check_codec("JPEG 2000", "jpeg2k")
|
||||||
check_codec("ZLIB (PNG/ZIP)", "zip")
|
check_codec("ZLIB (PNG/ZIP)", "zip")
|
||||||
check_codec("LIBTIFF", "libtiff")
|
check_codec("LIBTIFF", "libtiff")
|
||||||
check_module("FREETYPE2", "PIL._imagingft")
|
check_module("FREETYPE2", "PIL._imagingft")
|
||||||
|
|
26
setup.py
26
setup.py
|
@ -33,7 +33,8 @@ _LIB_IMAGING = (
|
||||||
"QuantHeap", "PcdDecode", "PcxDecode", "PcxEncode", "Point",
|
"QuantHeap", "PcdDecode", "PcxDecode", "PcxEncode", "Point",
|
||||||
"RankFilter", "RawDecode", "RawEncode", "Storage", "SunRleDecode",
|
"RankFilter", "RawDecode", "RawEncode", "Storage", "SunRleDecode",
|
||||||
"TgaRleDecode", "Unpack", "UnpackYCC", "UnsharpMask", "XbmDecode",
|
"TgaRleDecode", "Unpack", "UnpackYCC", "UnsharpMask", "XbmDecode",
|
||||||
"XbmEncode", "ZipDecode", "ZipEncode", "TiffDecode")
|
"XbmEncode", "ZipDecode", "ZipEncode", "TiffDecode", "Incremental",
|
||||||
|
"Jpeg2KDecode", "Jpeg2KEncode")
|
||||||
|
|
||||||
|
|
||||||
def _add_directory(path, dir, where=None):
|
def _add_directory(path, dir, where=None):
|
||||||
|
@ -88,6 +89,7 @@ NAME = 'Pillow'
|
||||||
VERSION = '2.3.0'
|
VERSION = '2.3.0'
|
||||||
TCL_ROOT = None
|
TCL_ROOT = None
|
||||||
JPEG_ROOT = None
|
JPEG_ROOT = None
|
||||||
|
JPEG2K_ROOT = None
|
||||||
ZLIB_ROOT = None
|
ZLIB_ROOT = None
|
||||||
TIFF_ROOT = None
|
TIFF_ROOT = None
|
||||||
FREETYPE_ROOT = None
|
FREETYPE_ROOT = None
|
||||||
|
@ -98,6 +100,7 @@ class pil_build_ext(build_ext):
|
||||||
|
|
||||||
class feature:
|
class feature:
|
||||||
zlib = jpeg = tiff = freetype = tcl = tk = lcms = webp = webpmux = None
|
zlib = jpeg = tiff = freetype = tcl = tk = lcms = webp = webpmux = None
|
||||||
|
jpeg2000 = None
|
||||||
required = []
|
required = []
|
||||||
|
|
||||||
def require(self, feat):
|
def require(self, feat):
|
||||||
|
@ -150,7 +153,7 @@ class pil_build_ext(build_ext):
|
||||||
#
|
#
|
||||||
# add configured kits
|
# 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):
|
FREETYPE_ROOT, LCMS_ROOT):
|
||||||
if isinstance(root, type(())):
|
if isinstance(root, type(())):
|
||||||
lib_root, include_root = root
|
lib_root, include_root = root
|
||||||
|
@ -321,6 +324,16 @@ class pil_build_ext(build_ext):
|
||||||
_add_directory(library_dirs, "/usr/lib")
|
_add_directory(library_dirs, "/usr/lib")
|
||||||
_add_directory(include_dirs, "/usr/include")
|
_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
|
# insert new dirs *before* default libs, to avoid conflicts
|
||||||
# between Python PYD stub libs and real libraries
|
# between Python PYD stub libs and real libraries
|
||||||
|
@ -349,6 +362,11 @@ class pil_build_ext(build_ext):
|
||||||
_find_library_file(self, "libjpeg")):
|
_find_library_file(self, "libjpeg")):
|
||||||
feature.jpeg = "libjpeg" # alternative name
|
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 feature.want('tiff'):
|
||||||
if _find_library_file(self, "tiff"):
|
if _find_library_file(self, "tiff"):
|
||||||
feature.tiff = "tiff"
|
feature.tiff = "tiff"
|
||||||
|
@ -430,6 +448,9 @@ class pil_build_ext(build_ext):
|
||||||
if feature.jpeg:
|
if feature.jpeg:
|
||||||
libs.append(feature.jpeg)
|
libs.append(feature.jpeg)
|
||||||
defs.append(("HAVE_LIBJPEG", None))
|
defs.append(("HAVE_LIBJPEG", None))
|
||||||
|
if feature.jpeg2000:
|
||||||
|
libs.append(feature.jpeg2000)
|
||||||
|
defs.append(("HAVE_OPENJPEG", None))
|
||||||
if feature.zlib:
|
if feature.zlib:
|
||||||
libs.append(feature.zlib)
|
libs.append(feature.zlib)
|
||||||
defs.append(("HAVE_LIBZ", None))
|
defs.append(("HAVE_LIBZ", None))
|
||||||
|
@ -537,6 +558,7 @@ class pil_build_ext(build_ext):
|
||||||
options = [
|
options = [
|
||||||
(feature.tcl and feature.tk, "TKINTER"),
|
(feature.tcl and feature.tk, "TKINTER"),
|
||||||
(feature.jpeg, "JPEG"),
|
(feature.jpeg, "JPEG"),
|
||||||
|
(feature.jpeg2000, "OPENJPEG (JPEG2000)"),
|
||||||
(feature.zlib, "ZLIB (PNG/ZIP)"),
|
(feature.zlib, "ZLIB (PNG/ZIP)"),
|
||||||
(feature.tiff, "LIBTIFF"),
|
(feature.tiff, "LIBTIFF"),
|
||||||
(feature.freetype, "FREETYPE2"),
|
(feature.freetype, "FREETYPE2"),
|
||||||
|
|
Loading…
Reference in New Issue
Block a user