mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-26 01:46:18 +03:00
commit
47d1f0db2e
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -8,3 +8,8 @@ docs/_build
|
|||
|
||||
# Vim cruft
|
||||
.*.swp
|
||||
|
||||
#emacs
|
||||
*~
|
||||
\#*#
|
||||
.#*
|
||||
|
|
11
.travis.yml
11
.travis.yml
|
@ -4,6 +4,9 @@ language: python
|
|||
virtualenv:
|
||||
system_site_packages: true
|
||||
|
||||
notifications:
|
||||
irc: "chat.freenode.net#pil"
|
||||
|
||||
python:
|
||||
- 2.6
|
||||
- 2.7
|
||||
|
@ -12,10 +15,14 @@ python:
|
|||
- "pypy"
|
||||
|
||||
install:
|
||||
- "sudo apt-get -qq install libfreetype6-dev liblcms2-dev libwebp-dev python-qt4 ghostscript libffi-dev"
|
||||
- "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake"
|
||||
- "pip install cffi"
|
||||
- "sudo apt-get install python-tk"
|
||||
|
||||
# webp
|
||||
- pushd depends && ./install_webp.sh && popd
|
||||
|
||||
# openjpeg
|
||||
- pushd depends && ./install_openjpeg.sh && popd
|
||||
|
||||
script:
|
||||
- python setup.py clean
|
||||
|
|
|
@ -4,6 +4,9 @@ Changelog (Pillow)
|
|||
2.4.0 (unreleased)
|
||||
------------------
|
||||
|
||||
- Added support for JPEG 2000
|
||||
[al45tair]
|
||||
|
||||
- Add more detailed error messages to Image.py
|
||||
[larsmans]
|
||||
|
||||
|
|
|
@ -205,7 +205,7 @@ class ImageFile(Image.Image):
|
|||
else:
|
||||
raise IndexError(ie)
|
||||
|
||||
if not s: # truncated jpeg
|
||||
if not s and not d.handles_eof: # truncated jpeg
|
||||
self.tile = []
|
||||
|
||||
# JpegDecode needs to clean things up here either way
|
||||
|
|
249
PIL/Jpeg2KImagePlugin.py
Normal file
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 = (int((self.size[0] + adjust) / power),
|
||||
int((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], (0, 0) + self.size, 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',
|
||||
'IptcImagePlugin',
|
||||
'JpegImagePlugin',
|
||||
'Jpeg2KImagePlugin',
|
||||
'McIdasImagePlugin',
|
||||
'MicImagePlugin',
|
||||
'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 |
29
Tests/run.py
29
Tests/run.py
|
@ -2,7 +2,7 @@ from __future__ import print_function
|
|||
|
||||
# minimal test runner
|
||||
|
||||
import glob, os, os.path, sys, tempfile
|
||||
import glob, os, os.path, sys, tempfile, re
|
||||
|
||||
try:
|
||||
root = os.path.dirname(__file__)
|
||||
|
@ -38,6 +38,8 @@ skipped = []
|
|||
python_options = " ".join(python_options)
|
||||
tester_options = " ".join(tester_options)
|
||||
|
||||
ignore_re = re.compile('^ignore: (.*)$', re.MULTILINE)
|
||||
|
||||
for file in files:
|
||||
test, ext = os.path.splitext(os.path.basename(file))
|
||||
if include and test not in include:
|
||||
|
@ -48,7 +50,30 @@ for file in files:
|
|||
out = os.popen("%s %s -u %s %s 2>&1" % (
|
||||
sys.executable, python_options, file, tester_options
|
||||
))
|
||||
result = out.read().strip()
|
||||
result = out.read()
|
||||
|
||||
# Extract any ignore patterns
|
||||
ignore_pats = ignore_re.findall(result)
|
||||
result = ignore_re.sub('', result)
|
||||
|
||||
try:
|
||||
def fix_re(p):
|
||||
if not p.startswith('^'):
|
||||
p = '^' + p
|
||||
if not p.endswith('$'):
|
||||
p = p + '$'
|
||||
return p
|
||||
|
||||
ignore_res = [re.compile(fix_re(p), re.MULTILINE) for p in ignore_pats]
|
||||
except:
|
||||
print('(bad ignore patterns %r)' % ignore_pats)
|
||||
ignore_res = []
|
||||
|
||||
for r in ignore_res:
|
||||
result = r.sub('', result)
|
||||
|
||||
result = result.strip()
|
||||
|
||||
if result == "ok":
|
||||
result = None
|
||||
elif result == "skip":
|
||||
|
|
110
Tests/test_file_jpeg2k.py
Normal file
110
Tests/test_file_jpeg2k.py
Normal file
|
@ -0,0 +1,110 @@
|
|||
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')
|
||||
|
||||
# OpenJPEG 2.0.0 outputs this debugging message sometimes; we should
|
||||
# ignore it---it doesn't represent a test failure.
|
||||
ignore('Not enough memory to handle tile data')
|
||||
|
||||
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)
|
|
@ -2,6 +2,11 @@ from tester import *
|
|||
|
||||
from PIL import Image
|
||||
|
||||
if hasattr(sys, 'pypy_version_info'):
|
||||
# This takes _forever_ on pypy. Open Bug,
|
||||
# see https://github.com/python-imaging/Pillow/issues/484
|
||||
skip()
|
||||
|
||||
def test_sanity():
|
||||
|
||||
im = lena()
|
||||
|
|
|
@ -3,7 +3,7 @@ from tester import *
|
|||
from PIL import Image
|
||||
try:
|
||||
from PIL import ImageTk
|
||||
except ImportError as v:
|
||||
except (OSError, ImportError) as v:
|
||||
skip(v)
|
||||
|
||||
success()
|
||||
|
|
|
@ -260,6 +260,11 @@ def skip(msg=None):
|
|||
print("skip")
|
||||
os._exit(0) # don't run exit handlers
|
||||
|
||||
def ignore(pattern):
|
||||
"""Tells the driver to ignore messages matching the pattern, for the
|
||||
duration of the current test."""
|
||||
print('ignore: %s' % pattern)
|
||||
|
||||
def _setup():
|
||||
global _logfile
|
||||
def report():
|
||||
|
|
13
_imaging.c
13
_imaging.c
|
@ -3325,6 +3325,7 @@ extern PyObject* PyImaging_FliDecoderNew(PyObject* self, PyObject* args);
|
|||
extern PyObject* PyImaging_GifDecoderNew(PyObject* self, PyObject* args);
|
||||
extern PyObject* PyImaging_HexDecoderNew(PyObject* self, PyObject* args);
|
||||
extern PyObject* PyImaging_JpegDecoderNew(PyObject* self, PyObject* args);
|
||||
extern PyObject* PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args);
|
||||
extern PyObject* PyImaging_TiffLzwDecoderNew(PyObject* self, PyObject* args);
|
||||
extern PyObject* PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args);
|
||||
extern PyObject* PyImaging_MspDecoderNew(PyObject* self, PyObject* args);
|
||||
|
@ -3341,6 +3342,7 @@ extern PyObject* PyImaging_ZipDecoderNew(PyObject* self, PyObject* args);
|
|||
extern PyObject* PyImaging_EpsEncoderNew(PyObject* self, PyObject* args);
|
||||
extern PyObject* PyImaging_GifEncoderNew(PyObject* self, PyObject* args);
|
||||
extern PyObject* PyImaging_JpegEncoderNew(PyObject* self, PyObject* args);
|
||||
extern PyObject* PyImaging_Jpeg2KEncoderNew(PyObject* self, PyObject* args);
|
||||
extern PyObject* PyImaging_PcxEncoderNew(PyObject* self, PyObject* args);
|
||||
extern PyObject* PyImaging_RawEncoderNew(PyObject* self, PyObject* args);
|
||||
extern PyObject* PyImaging_XbmEncoderNew(PyObject* self, PyObject* args);
|
||||
|
@ -3393,6 +3395,10 @@ static PyMethodDef functions[] = {
|
|||
#ifdef HAVE_LIBJPEG
|
||||
{"jpeg_decoder", (PyCFunction)PyImaging_JpegDecoderNew, 1},
|
||||
{"jpeg_encoder", (PyCFunction)PyImaging_JpegEncoderNew, 1},
|
||||
#endif
|
||||
#ifdef HAVE_OPENJPEG
|
||||
{"jpeg2k_decoder", (PyCFunction)PyImaging_Jpeg2KDecoderNew, 1},
|
||||
{"jpeg2k_encoder", (PyCFunction)PyImaging_Jpeg2KEncoderNew, 1},
|
||||
#endif
|
||||
{"tiff_lzw_decoder", (PyCFunction)PyImaging_TiffLzwDecoderNew, 1},
|
||||
#ifdef HAVE_LIBTIFF
|
||||
|
@ -3497,6 +3503,13 @@ setup_module(PyObject* m) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_OPENJPEG
|
||||
{
|
||||
extern const char *ImagingJpeg2KVersion(void);
|
||||
PyDict_SetItemString(d, "jp2klib_version", PyUnicode_FromString(ImagingJpeg2KVersion()));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBZ
|
||||
/* zip encoding strategies */
|
||||
PyModule_AddIntConstant(m, "DEFAULT_STRATEGY", Z_DEFAULT_STRATEGY);
|
||||
|
|
71
decode.c
71
decode.c
|
@ -52,6 +52,7 @@ typedef struct {
|
|||
struct ImagingCodecStateInstance state;
|
||||
Imaging im;
|
||||
PyObject* lock;
|
||||
int handles_eof;
|
||||
} ImagingDecoderObject;
|
||||
|
||||
static PyTypeObject ImagingDecoderType;
|
||||
|
@ -93,6 +94,9 @@ PyImaging_DecoderNew(int contextsize)
|
|||
/* Initialize the cleanup function pointer */
|
||||
decoder->cleanup = NULL;
|
||||
|
||||
/* Most decoders don't want to handle EOF themselves */
|
||||
decoder->handles_eof = 0;
|
||||
|
||||
return decoder;
|
||||
}
|
||||
|
||||
|
@ -194,6 +198,12 @@ _setimage(ImagingDecoderObject* decoder, PyObject* args)
|
|||
return Py_None;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
_get_handles_eof(ImagingDecoderObject *decoder)
|
||||
{
|
||||
return PyBool_FromLong(decoder->handles_eof);
|
||||
}
|
||||
|
||||
static struct PyMethodDef methods[] = {
|
||||
{"decode", (PyCFunction)_decode, 1},
|
||||
{"cleanup", (PyCFunction)_decode_cleanup, 1},
|
||||
|
@ -201,6 +211,13 @@ static struct PyMethodDef methods[] = {
|
|||
{NULL, NULL} /* sentinel */
|
||||
};
|
||||
|
||||
static struct PyGetSetDef getseters[] = {
|
||||
{"handles_eof", (getter)_get_handles_eof, NULL,
|
||||
"True if this decoder expects to handle EOF itself.",
|
||||
NULL},
|
||||
{NULL, NULL, NULL, NULL, NULL} /* sentinel */
|
||||
};
|
||||
|
||||
static PyTypeObject ImagingDecoderType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"ImagingDecoder", /*tp_name*/
|
||||
|
@ -232,7 +249,7 @@ static PyTypeObject ImagingDecoderType = {
|
|||
0, /*tp_iternext*/
|
||||
methods, /*tp_methods*/
|
||||
0, /*tp_members*/
|
||||
0, /*tp_getset*/
|
||||
getseters, /*tp_getset*/
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
@ -762,3 +779,55 @@ PyImaging_JpegDecoderNew(PyObject* self, PyObject* args)
|
|||
return (PyObject*) decoder;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/* JPEG 2000 */
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
#ifdef HAVE_OPENJPEG
|
||||
|
||||
#include "Jpeg2K.h"
|
||||
|
||||
PyObject*
|
||||
PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args)
|
||||
{
|
||||
ImagingDecoderObject* decoder;
|
||||
JPEG2KDECODESTATE *context;
|
||||
|
||||
char* mode;
|
||||
char* format;
|
||||
OPJ_CODEC_FORMAT codec_format;
|
||||
int reduce = 0;
|
||||
int layers = 0;
|
||||
int fd = -1;
|
||||
if (!PyArg_ParseTuple(args, "ss|iii", &mode, &format,
|
||||
&reduce, &layers, &fd))
|
||||
return NULL;
|
||||
|
||||
if (strcmp(format, "j2k") == 0)
|
||||
codec_format = OPJ_CODEC_J2K;
|
||||
else if (strcmp(format, "jpt") == 0)
|
||||
codec_format = OPJ_CODEC_JPT;
|
||||
else if (strcmp(format, "jp2") == 0)
|
||||
codec_format = OPJ_CODEC_JP2;
|
||||
else
|
||||
return NULL;
|
||||
|
||||
decoder = PyImaging_DecoderNew(sizeof(JPEG2KDECODESTATE));
|
||||
if (decoder == NULL)
|
||||
return NULL;
|
||||
|
||||
decoder->handles_eof = 1;
|
||||
decoder->decode = ImagingJpeg2KDecode;
|
||||
decoder->cleanup = ImagingJpeg2KDecodeCleanup;
|
||||
|
||||
context = (JPEG2KDECODESTATE *)decoder->state.context;
|
||||
|
||||
context->fd = fd;
|
||||
context->format = codec_format;
|
||||
context->reduce = reduce;
|
||||
context->layers = layers;
|
||||
|
||||
return (PyObject*) decoder;
|
||||
}
|
||||
#endif /* HAVE_OPENJPEG */
|
||||
|
|
18
depends/install_openjpeg.sh
Executable file
18
depends/install_openjpeg.sh
Executable file
|
@ -0,0 +1,18 @@
|
|||
#!/bin/bash
|
||||
# install openjpeg
|
||||
|
||||
|
||||
if [ ! -f openjpeg-2.0.0.tar.gz ]; then
|
||||
wget 'https://openjpeg.googlecode.com/files/openjpeg-2.0.0.tar.gz'
|
||||
fi
|
||||
|
||||
rm -r openjpeg-2.0.0
|
||||
tar -xvzf openjpeg-2.0.0.tar.gz
|
||||
|
||||
|
||||
pushd openjpeg-2.0.0
|
||||
|
||||
cmake -DCMAKE_INSTALL_PREFIX=/usr . && make && sudo make install
|
||||
|
||||
popd
|
||||
|
18
depends/install_webp.sh
Executable file
18
depends/install_webp.sh
Executable file
|
@ -0,0 +1,18 @@
|
|||
#!/bin/bash
|
||||
# install webp
|
||||
|
||||
|
||||
if [ ! -f libwebp-0.4.0.tar.gz ]; then
|
||||
wget 'https://webp.googlecode.com/files/libwebp-0.4.0.tar.gz'
|
||||
fi
|
||||
|
||||
rm -r libwebp-0.4.0
|
||||
tar -xvzf libwebp-0.4.0.tar.gz
|
||||
|
||||
|
||||
pushd libwebp-0.4.0
|
||||
|
||||
./configure --prefix=/usr --enable-libwebpmux --enable-libwebpdemux && make && sudo make install
|
||||
|
||||
popd
|
||||
|
|
@ -153,6 +153,92 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
|||
before building the Python Imaging Library. See the distribution README for
|
||||
details.
|
||||
|
||||
JPEG 2000
|
||||
^^^^^^^^^
|
||||
|
||||
PIL reads and writes JPEG 2000 files containing ``L``, ``LA``, ``RGB`` or
|
||||
``RGBA`` data. It can also read files containing ``YCbCr`` data, which it
|
||||
converts on read into ``RGB`` or ``RGBA`` depending on whether or not there is
|
||||
an alpha channel. PIL supports JPEG 2000 raw codestreams (``.j2k`` files), as
|
||||
well as boxed JPEG 2000 files (``.j2p`` or ``.jpx`` files). PIL does *not*
|
||||
support files whose components have different sampling frequencies.
|
||||
|
||||
When loading, if you set the ``mode`` on the image prior to the
|
||||
:py:meth:`~PIL.Image.Image.load` method being invoked, you can ask PIL to
|
||||
convert the image to either ``RGB`` or ``RGBA`` rather than choosing for
|
||||
itself. It is also possible to set ``reduce`` to the number of resolutions to
|
||||
discard (each one reduces the size of the resulting image by a factor of 2),
|
||||
and ``layers`` to specify the number of quality layers to load.
|
||||
|
||||
The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
||||
|
||||
**offset**
|
||||
The image offset, as a tuple of integers, e.g. (16, 16)
|
||||
|
||||
**tile_offset**
|
||||
The tile offset, again as a 2-tuple of integers.
|
||||
|
||||
**tile_size**
|
||||
The tile size as a 2-tuple. If not specified, or if set to None, the
|
||||
image will be saved without tiling.
|
||||
|
||||
**quality_mode**
|
||||
Either `"rates"` or `"dB"` depending on the units you want to use to
|
||||
specify image quality.
|
||||
|
||||
**quality_layers**
|
||||
A sequence of numbers, each of which represents either an approximate size
|
||||
reduction (if quality mode is `"rates"`) or a signal to noise ratio value
|
||||
in decibels. If not specified, defaults to a single layer of full quality.
|
||||
|
||||
**num_resolutions**
|
||||
The number of different image resolutions to be stored (which corresponds
|
||||
to the number of Discrete Wavelet Transform decompositions plus one).
|
||||
|
||||
**codeblock_size**
|
||||
The code-block size as a 2-tuple. Minimum size is 4 x 4, maximum is 1024 x
|
||||
1024, with the additional restriction that no code-block may have more
|
||||
than 4096 coefficients (i.e. the product of the two numbers must be no
|
||||
greater than 4096).
|
||||
|
||||
**precinct_size**
|
||||
The precinct size as a 2-tuple. Must be a power of two along both axes,
|
||||
and must be greater than the code-block size.
|
||||
|
||||
**irreversible**
|
||||
If ``True``, use the lossy Irreversible Color Transformation
|
||||
followed by DWT 9-7. Defaults to ``False``, which means to use the
|
||||
Reversible Color Transformation with DWT 5-3.
|
||||
|
||||
**progression**
|
||||
Controls the progression order; must be one of ``"LRCP"``, ``"RLCP"``,
|
||||
``"RPCL"``, ``"PCRL"``, ``"CPRL"``. The letters stand for Component,
|
||||
Position, Resolution and Layer respectively and control the order of
|
||||
encoding, the idea being that e.g. an image encoded using LRCP mode can
|
||||
have its quality layers decoded as they arrive at the decoder, while one
|
||||
encoded using RLCP mode will have increasing resolutions decoded as they
|
||||
arrive, and so on.
|
||||
|
||||
**cinema_mode**
|
||||
Set the encoder to produce output compliant with the digital cinema
|
||||
specifications. The options here are ``"no"`` (the default),
|
||||
``"cinema2k-24"`` for 24fps 2K, ``"cinema2k-48"`` for 48fps 2K, and
|
||||
``"cinema4k-24"`` for 24fps 4K. Note that for compliant 2K files,
|
||||
*at least one* of your image dimensions must match 2048 x 1080, while
|
||||
for compliant 4K files, *at least one* of the dimensions must match
|
||||
4096 x 2160.
|
||||
|
||||
.. note::
|
||||
|
||||
To enable JPEG 2000 support, you need to build and install the OpenJPEG
|
||||
library, version 2.0.0 or higher, before building the Python Imaging
|
||||
Library.
|
||||
|
||||
Windows users can install the OpenJPEG binaries available on the
|
||||
OpenJPEG website, but must add them to their PATH in order to use PIL (if
|
||||
you fail to do this, you will get errors about not being able to load the
|
||||
``_imaging`` DLL).
|
||||
|
||||
MSP
|
||||
^^^
|
||||
|
||||
|
|
|
@ -153,6 +153,14 @@ Plugin reference
|
|||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
:mod:`Jpeg2KImagePlugin` Module
|
||||
-----------------------------
|
||||
|
||||
.. automodule:: PIL.Jpeg2KImagePlugin
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
:mod:`McIdasImagePlugin` Module
|
||||
-------------------------------
|
||||
|
||||
|
|
160
encode.c
160
encode.c
|
@ -40,6 +40,7 @@ typedef struct {
|
|||
PyObject_HEAD
|
||||
int (*encode)(Imaging im, ImagingCodecState state,
|
||||
UINT8* buffer, int bytes);
|
||||
int (*cleanup)(ImagingCodecState state);
|
||||
struct ImagingCodecStateInstance state;
|
||||
Imaging im;
|
||||
PyObject* lock;
|
||||
|
@ -77,6 +78,9 @@ PyImaging_EncoderNew(int contextsize)
|
|||
/* Initialize encoder context */
|
||||
encoder->state.context = context;
|
||||
|
||||
/* Most encoders don't need this */
|
||||
encoder->cleanup = NULL;
|
||||
|
||||
/* Target image */
|
||||
encoder->lock = NULL;
|
||||
encoder->im = NULL;
|
||||
|
@ -87,6 +91,8 @@ PyImaging_EncoderNew(int contextsize)
|
|||
static void
|
||||
_dealloc(ImagingEncoderObject* encoder)
|
||||
{
|
||||
if (encoder->cleanup)
|
||||
encoder->cleanup(&encoder->state);
|
||||
free(encoder->state.buffer);
|
||||
free(encoder->state.context);
|
||||
Py_XDECREF(encoder->lock);
|
||||
|
@ -797,4 +803,158 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
|
|||
|
||||
#endif
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/* JPEG 2000 */
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
#ifdef HAVE_OPENJPEG
|
||||
|
||||
#include "Jpeg2K.h"
|
||||
|
||||
static void
|
||||
j2k_decode_coord_tuple(PyObject *tuple, int *x, int *y)
|
||||
{
|
||||
*x = *y = 0;
|
||||
|
||||
if (tuple && PyTuple_Check(tuple) && PyTuple_GET_SIZE(tuple) == 2) {
|
||||
*x = (int)PyInt_AsLong(PyTuple_GET_ITEM(tuple, 0));
|
||||
*y = (int)PyInt_AsLong(PyTuple_GET_ITEM(tuple, 1));
|
||||
|
||||
if (*x < 0)
|
||||
*x = 0;
|
||||
if (*y < 0)
|
||||
*y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
PyObject*
|
||||
PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args)
|
||||
{
|
||||
ImagingEncoderObject *encoder;
|
||||
JPEG2KENCODESTATE *context;
|
||||
|
||||
char *mode;
|
||||
char *format;
|
||||
OPJ_CODEC_FORMAT codec_format;
|
||||
PyObject *offset = NULL, *tile_offset = NULL, *tile_size = NULL;
|
||||
char *quality_mode = "rates";
|
||||
PyObject *quality_layers = NULL;
|
||||
int num_resolutions = 0;
|
||||
PyObject *cblk_size = NULL, *precinct_size = NULL;
|
||||
PyObject *irreversible = NULL;
|
||||
char *progression = "LRCP";
|
||||
OPJ_PROG_ORDER prog_order;
|
||||
char *cinema_mode = "no";
|
||||
OPJ_CINEMA_MODE cine_mode;
|
||||
int fd = -1;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "ss|OOOsOIOOOssi", &mode, &format,
|
||||
&offset, &tile_offset, &tile_size,
|
||||
&quality_mode, &quality_layers, &num_resolutions,
|
||||
&cblk_size, &precinct_size,
|
||||
&irreversible, &progression, &cinema_mode,
|
||||
&fd))
|
||||
return NULL;
|
||||
|
||||
if (strcmp (format, "j2k") == 0)
|
||||
codec_format = OPJ_CODEC_J2K;
|
||||
else if (strcmp (format, "jpt") == 0)
|
||||
codec_format = OPJ_CODEC_JPT;
|
||||
else if (strcmp (format, "jp2") == 0)
|
||||
codec_format = OPJ_CODEC_JP2;
|
||||
else
|
||||
return NULL;
|
||||
|
||||
if (strcmp(progression, "LRCP") == 0)
|
||||
prog_order = OPJ_LRCP;
|
||||
else if (strcmp(progression, "RLCP") == 0)
|
||||
prog_order = OPJ_RLCP;
|
||||
else if (strcmp(progression, "RPCL") == 0)
|
||||
prog_order = OPJ_RPCL;
|
||||
else if (strcmp(progression, "PCRL") == 0)
|
||||
prog_order = OPJ_PCRL;
|
||||
else if (strcmp(progression, "CPRL") == 0)
|
||||
prog_order = OPJ_CPRL;
|
||||
else
|
||||
return NULL;
|
||||
|
||||
if (strcmp(cinema_mode, "no") == 0)
|
||||
cine_mode = OPJ_OFF;
|
||||
else if (strcmp(cinema_mode, "cinema2k-24") == 0)
|
||||
cine_mode = OPJ_CINEMA2K_24;
|
||||
else if (strcmp(cinema_mode, "cinema2k-48") == 0)
|
||||
cine_mode = OPJ_CINEMA2K_48;
|
||||
else if (strcmp(cinema_mode, "cinema4k-24") == 0)
|
||||
cine_mode = OPJ_CINEMA4K_24;
|
||||
else
|
||||
return NULL;
|
||||
|
||||
encoder = PyImaging_EncoderNew(sizeof(JPEG2KENCODESTATE));
|
||||
if (!encoder)
|
||||
return NULL;
|
||||
|
||||
encoder->encode = ImagingJpeg2KEncode;
|
||||
encoder->cleanup = ImagingJpeg2KEncodeCleanup;
|
||||
|
||||
context = (JPEG2KENCODESTATE *)encoder->state.context;
|
||||
|
||||
context->fd = fd;
|
||||
context->format = codec_format;
|
||||
context->offset_x = context->offset_y = 0;
|
||||
|
||||
j2k_decode_coord_tuple(offset, &context->offset_x, &context->offset_y);
|
||||
j2k_decode_coord_tuple(tile_offset,
|
||||
&context->tile_offset_x,
|
||||
&context->tile_offset_y);
|
||||
j2k_decode_coord_tuple(tile_size,
|
||||
&context->tile_size_x,
|
||||
&context->tile_size_y);
|
||||
|
||||
/* Error on illegal tile offsets */
|
||||
if (context->tile_size_x && context->tile_size_y) {
|
||||
if (context->tile_offset_x <= context->offset_x - context->tile_size_x
|
||||
|| context->tile_offset_y <= context->offset_y - context->tile_size_y) {
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"JPEG 2000 tile offset too small; top left tile must "
|
||||
"intersect image area");
|
||||
}
|
||||
|
||||
if (context->tile_offset_x > context->offset_x
|
||||
|| context->tile_offset_y > context->offset_y) {
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"JPEG 2000 tile offset too large to cover image area");
|
||||
Py_DECREF(encoder);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (quality_layers && PySequence_Check(quality_layers)) {
|
||||
context->quality_is_in_db = strcmp (quality_mode, "dB") == 0;
|
||||
context->quality_layers = quality_layers;
|
||||
Py_INCREF(quality_layers);
|
||||
}
|
||||
|
||||
context->num_resolutions = num_resolutions;
|
||||
|
||||
j2k_decode_coord_tuple(cblk_size,
|
||||
&context->cblk_width,
|
||||
&context->cblk_height);
|
||||
j2k_decode_coord_tuple(precinct_size,
|
||||
&context->precinct_width,
|
||||
&context->precinct_height);
|
||||
|
||||
context->irreversible = PyObject_IsTrue(irreversible);
|
||||
context->progression = prog_order;
|
||||
context->cinema_mode = cine_mode;
|
||||
|
||||
return (PyObject *)encoder;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* c-basic-offset: 4
|
||||
* End:
|
||||
*
|
||||
*/
|
||||
|
|
|
@ -424,6 +424,14 @@ extern int ImagingJpegDecodeCleanup(ImagingCodecState state);
|
|||
extern int ImagingJpegEncode(Imaging im, ImagingCodecState state,
|
||||
UINT8* buffer, int bytes);
|
||||
#endif
|
||||
#ifdef HAVE_OPENJPEG
|
||||
extern int ImagingJpeg2KDecode(Imaging im, ImagingCodecState state,
|
||||
UINT8* buffer, int bytes);
|
||||
extern int ImagingJpeg2KDecodeCleanup(ImagingCodecState state);
|
||||
extern int ImagingJpeg2KEncode(Imaging im, ImagingCodecState state,
|
||||
UINT8* buffer, int bytes);
|
||||
extern int ImagingJpeg2KEncodeCleanup(ImagingCodecState state);
|
||||
#endif
|
||||
extern int ImagingLzwDecode(Imaging im, ImagingCodecState state,
|
||||
UINT8* buffer, int bytes);
|
||||
#ifdef HAVE_LIBTIFF
|
||||
|
@ -497,6 +505,32 @@ struct ImagingCodecStateInstance {
|
|||
void *context;
|
||||
};
|
||||
|
||||
/* Incremental encoding/decoding support */
|
||||
typedef struct ImagingIncrementalCodecStruct *ImagingIncrementalCodec;
|
||||
|
||||
typedef int (*ImagingIncrementalCodecEntry)(Imaging im,
|
||||
ImagingCodecState state,
|
||||
ImagingIncrementalCodec codec);
|
||||
|
||||
enum {
|
||||
INCREMENTAL_CODEC_READ = 1,
|
||||
INCREMENTAL_CODEC_WRITE = 2
|
||||
};
|
||||
|
||||
enum {
|
||||
INCREMENTAL_CODEC_NOT_SEEKABLE = 0,
|
||||
INCREMENTAL_CODEC_SEEKABLE = 1
|
||||
};
|
||||
|
||||
extern ImagingIncrementalCodec ImagingIncrementalCodecCreate(ImagingIncrementalCodecEntry codec_entry, Imaging im, ImagingCodecState state, int read_or_write, int seekable, int fd);
|
||||
extern void ImagingIncrementalCodecDestroy(ImagingIncrementalCodec codec);
|
||||
extern int ImagingIncrementalCodecPushBuffer(ImagingIncrementalCodec codec, UINT8 *buf, int bytes);
|
||||
extern ssize_t ImagingIncrementalCodecRead(ImagingIncrementalCodec codec, void *buffer, size_t bytes);
|
||||
extern off_t ImagingIncrementalCodecSkip(ImagingIncrementalCodec codec, off_t bytes);
|
||||
extern ssize_t ImagingIncrementalCodecWrite(ImagingIncrementalCodec codec, const void *buffer, size_t bytes);
|
||||
extern off_t ImagingIncrementalCodecSeek(ImagingIncrementalCodec codec, off_t bytes);
|
||||
extern size_t ImagingIncrementalCodecBytesInBuffer(ImagingIncrementalCodec codec);
|
||||
|
||||
/* Errcodes */
|
||||
#define IMAGING_CODEC_END 1
|
||||
#define IMAGING_CODEC_OVERRUN -1
|
||||
|
|
677
libImaging/Incremental.c
Normal file
677
libImaging/Incremental.c
Normal file
|
@ -0,0 +1,677 @@
|
|||
/*
|
||||
* 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)
|
||||
{
|
||||
UINT8 *buffer;
|
||||
size_t bytes;
|
||||
|
||||
/* 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");
|
||||
|
||||
buffer = codec->stream.buffer;
|
||||
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)
|
||||
{
|
||||
off_t buffered;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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:
|
||||
*
|
||||
*/
|
753
libImaging/Jpeg2KDecode.c
Normal file
753
libImaging/Jpeg2KDecode.c
Normal file
|
@ -0,0 +1,753 @@
|
|||
/*
|
||||
* 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, byte;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (image->comps[n].dx != 1 || image->comps[n].dy != 1) {
|
||||
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;
|
||||
unsigned correction = (1 << params.cp_reduce) - 1;
|
||||
|
||||
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;
|
||||
|
||||
/* Adjust the tile co-ordinates based on the reduction (OpenJPEG
|
||||
doesn't do this for us) */
|
||||
tile_info.x0 = (tile_info.x0 + correction) >> context->reduce;
|
||||
tile_info.y0 = (tile_info.y0 + correction) >> context->reduce;
|
||||
tile_info.x1 = (tile_info.x1 + correction) >> context->reduce;
|
||||
tile_info.y1 = (tile_info.y1 + correction) >> context->reduce;
|
||||
|
||||
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 (context->quality_layers && PySequence_Check(context->quality_layers)) {
|
||||
Py_ssize_t len = PySequence_Length(context->quality_layers);
|
||||
Py_ssize_t n;
|
||||
float *pq;
|
||||
|
||||
if (len) {
|
||||
if (len > sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0]))
|
||||
len = sizeof(params.tcp_rates)/sizeof(params.tcp_rates[0]);
|
||||
|
||||
params.tcp_numlayers = (int)len;
|
||||
|
||||
if (context->quality_is_in_db) {
|
||||
params.cp_disto_alloc = params.cp_fixed_alloc = 0;
|
||||
params.cp_fixed_quality = 1;
|
||||
pq = params.tcp_distoratio;
|
||||
} else {
|
||||
params.cp_disto_alloc = 1;
|
||||
params.cp_fixed_alloc = params.cp_fixed_quality = 0;
|
||||
pq = params.tcp_rates;
|
||||
}
|
||||
|
||||
for (n = 0; n < len; ++n) {
|
||||
PyObject *obj = PySequence_ITEM(context->quality_layers, n);
|
||||
pq[n] = PyFloat_AsDouble(obj);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
params.tcp_numlayers = 1;
|
||||
params.tcp_rates[0] = 0;
|
||||
params.cp_disto_alloc = 1;
|
||||
}
|
||||
|
||||
if (context->num_resolutions)
|
||||
params.numresolution = context->num_resolutions;
|
||||
|
||||
if (context->cblk_width >= 4 && context->cblk_width <= 1024
|
||||
&& context->cblk_height >= 4 && context->cblk_height <= 1024
|
||||
&& context->cblk_width * context->cblk_height <= 4096) {
|
||||
params.cblockw_init = context->cblk_width;
|
||||
params.cblockh_init = context->cblk_height;
|
||||
}
|
||||
|
||||
if (context->precinct_width >= 4 && context->precinct_height >= 4
|
||||
&& context->precinct_width >= context->cblk_width
|
||||
&& context->precinct_height > context->cblk_height) {
|
||||
params.prcw_init[0] = context->precinct_width;
|
||||
params.prch_init[0] = context->precinct_height;
|
||||
params.res_spec = 1;
|
||||
params.csty |= 0x01;
|
||||
}
|
||||
|
||||
params.irreversible = context->irreversible;
|
||||
|
||||
params.prog_order = context->progression;
|
||||
|
||||
params.cp_cinema = context->cinema_mode;
|
||||
|
||||
switch (params.cp_cinema) {
|
||||
case OPJ_OFF:
|
||||
params.cp_rsiz = OPJ_STD_RSIZ;
|
||||
break;
|
||||
case OPJ_CINEMA2K_24:
|
||||
case OPJ_CINEMA2K_48:
|
||||
params.cp_rsiz = OPJ_CINEMA2K;
|
||||
if (params.numresolution > 6)
|
||||
params.numresolution = 6;
|
||||
break;
|
||||
case OPJ_CINEMA4K_24:
|
||||
params.cp_rsiz = OPJ_CINEMA4K;
|
||||
if (params.numresolution > 7)
|
||||
params.numresolution = 7;
|
||||
break;
|
||||
}
|
||||
|
||||
if (context->cinema_mode != OPJ_OFF)
|
||||
j2k_set_cinema_params(im, components, ¶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("TKINTER", "PIL._imagingtk")
|
||||
check_codec("JPEG", "jpeg")
|
||||
check_codec("JPEG 2000", "jpeg2k")
|
||||
check_codec("ZLIB (PNG/ZIP)", "zip")
|
||||
check_codec("LIBTIFF", "libtiff")
|
||||
check_module("FREETYPE2", "PIL._imagingft")
|
||||
|
|
29
setup.py
29
setup.py
|
@ -33,7 +33,8 @@ _LIB_IMAGING = (
|
|||
"QuantHeap", "PcdDecode", "PcxDecode", "PcxEncode", "Point",
|
||||
"RankFilter", "RawDecode", "RawEncode", "Storage", "SunRleDecode",
|
||||
"TgaRleDecode", "Unpack", "UnpackYCC", "UnsharpMask", "XbmDecode",
|
||||
"XbmEncode", "ZipDecode", "ZipEncode", "TiffDecode")
|
||||
"XbmEncode", "ZipDecode", "ZipEncode", "TiffDecode", "Incremental",
|
||||
"Jpeg2KDecode", "Jpeg2KEncode")
|
||||
|
||||
|
||||
def _add_directory(path, dir, where=None):
|
||||
|
@ -80,7 +81,8 @@ def _read(file):
|
|||
|
||||
try:
|
||||
import _tkinter
|
||||
except ImportError:
|
||||
except (ImportError, OSError):
|
||||
# pypy emits an oserror
|
||||
_tkinter = None
|
||||
|
||||
|
||||
|
@ -88,6 +90,7 @@ NAME = 'Pillow'
|
|||
VERSION = '2.3.0'
|
||||
TCL_ROOT = None
|
||||
JPEG_ROOT = None
|
||||
JPEG2K_ROOT = None
|
||||
ZLIB_ROOT = None
|
||||
TIFF_ROOT = None
|
||||
FREETYPE_ROOT = None
|
||||
|
@ -98,6 +101,7 @@ class pil_build_ext(build_ext):
|
|||
|
||||
class feature:
|
||||
zlib = jpeg = tiff = freetype = tcl = tk = lcms = webp = webpmux = None
|
||||
jpeg2000 = None
|
||||
required = []
|
||||
|
||||
def require(self, feat):
|
||||
|
@ -150,7 +154,7 @@ class pil_build_ext(build_ext):
|
|||
#
|
||||
# add configured kits
|
||||
|
||||
for root in (TCL_ROOT, JPEG_ROOT, TIFF_ROOT, ZLIB_ROOT,
|
||||
for root in (TCL_ROOT, JPEG_ROOT, JPEG2K_ROOT, TIFF_ROOT, ZLIB_ROOT,
|
||||
FREETYPE_ROOT, LCMS_ROOT):
|
||||
if isinstance(root, type(())):
|
||||
lib_root, include_root = root
|
||||
|
@ -321,6 +325,16 @@ class pil_build_ext(build_ext):
|
|||
_add_directory(library_dirs, "/usr/lib")
|
||||
_add_directory(include_dirs, "/usr/include")
|
||||
|
||||
# on Windows, look for the OpenJPEG libraries in the location that
|
||||
# the official installed puts them
|
||||
if sys.platform == "win32":
|
||||
_add_directory(library_dirs,
|
||||
os.path.join(os.environ.get("ProgramFiles", ""),
|
||||
"OpenJPEG 2.0", "lib"))
|
||||
_add_directory(include_dirs,
|
||||
os.path.join(os.environ.get("ProgramFiles", ""),
|
||||
"OpenJPEG 2.0", "include"))
|
||||
|
||||
#
|
||||
# insert new dirs *before* default libs, to avoid conflicts
|
||||
# between Python PYD stub libs and real libraries
|
||||
|
@ -349,6 +363,11 @@ class pil_build_ext(build_ext):
|
|||
_find_library_file(self, "libjpeg")):
|
||||
feature.jpeg = "libjpeg" # alternative name
|
||||
|
||||
if feature.want('jpeg2000'):
|
||||
if _find_include_file(self, "openjpeg-2.0/openjpeg.h"):
|
||||
if _find_library_file(self, "openjp2"):
|
||||
feature.jpeg2000 = "openjp2"
|
||||
|
||||
if feature.want('tiff'):
|
||||
if _find_library_file(self, "tiff"):
|
||||
feature.tiff = "tiff"
|
||||
|
@ -430,6 +449,9 @@ class pil_build_ext(build_ext):
|
|||
if feature.jpeg:
|
||||
libs.append(feature.jpeg)
|
||||
defs.append(("HAVE_LIBJPEG", None))
|
||||
if feature.jpeg2000:
|
||||
libs.append(feature.jpeg2000)
|
||||
defs.append(("HAVE_OPENJPEG", None))
|
||||
if feature.zlib:
|
||||
libs.append(feature.zlib)
|
||||
defs.append(("HAVE_LIBZ", None))
|
||||
|
@ -537,6 +559,7 @@ class pil_build_ext(build_ext):
|
|||
options = [
|
||||
(feature.tcl and feature.tk, "TKINTER"),
|
||||
(feature.jpeg, "JPEG"),
|
||||
(feature.jpeg2000, "OPENJPEG (JPEG2000)"),
|
||||
(feature.zlib, "ZLIB (PNG/ZIP)"),
|
||||
(feature.tiff, "LIBTIFF"),
|
||||
(feature.freetype, "FREETYPE2"),
|
||||
|
|
Loading…
Reference in New Issue
Block a user