diff --git a/.gitignore b/.gitignore index 498cbdd14..c05152e25 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,12 @@ dist *.so docs/_build *~ + +# Vim cruft +.*.swp + +#emacs +*~ +\#*# +.#* + diff --git a/.travis.yml b/.travis.yml index 472f8a9fa..d68de0b32 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,19 +4,32 @@ language: python virtualenv: system_site_packages: true +notifications: + irc: "chat.freenode.net#pil" + python: - 2.6 - 2.7 - 3.2 - 3.3 + - "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" + # webp + - pushd depends && ./install_webp.sh && popd + + # openjpeg + - pushd depends && ./install_openjpeg.sh && popd script: - python setup.py clean - python setup.py build_ext --inplace - python selftest.py - python Tests/run.py + +matrix: + allow_failures: + - python: "pypy" diff --git a/CHANGES.rst b/CHANGES.rst index bb4131151..8839a1782 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,21 @@ Changelog (Pillow) 2.4.0 (unreleased) ------------------ +- Improved icns support + [al45tair] + +- Fix libtiff leaking open files, fixes #580 + [wiredfool] + +- Fixes for Jpeg encoding in Python 3, fixes #577 + [wiredfool] + +- Added support for JPEG 2000 + [al45tair] + +- Add more detailed error messages to Image.py + [larsmans] + - Avoid conflicting _expand functions in PIL & MINGW, fixes #538 [aclark] diff --git a/Images/pillow.icns b/Images/pillow.icns new file mode 100644 index 000000000..a461329f0 Binary files /dev/null and b/Images/pillow.icns differ diff --git a/Images/pillow.ico b/Images/pillow.ico new file mode 100644 index 000000000..78eef9ae3 Binary files /dev/null and b/Images/pillow.ico differ diff --git a/PIL/IcnsImagePlugin.py b/PIL/IcnsImagePlugin.py index 8fac1308c..9a0864bad 100644 --- a/PIL/IcnsImagePlugin.py +++ b/PIL/IcnsImagePlugin.py @@ -10,12 +10,17 @@ # Copyright (c) 2004 by Bob Ippolito. # Copyright (c) 2004 by Secret Labs. # Copyright (c) 2004 by Fredrik Lundh. +# Copyright (c) 2014 by Alastair Houghton. # # See the README file for information on usage and redistribution. # -from PIL import Image, ImageFile, _binary -import struct +from PIL import Image, ImageFile, PngImagePlugin, _binary +import struct, io + +enable_jpeg2k = hasattr(Image.core, 'jp2klib_version') +if enable_jpeg2k: + from PIL import Jpeg2KImagePlugin i8 = _binary.i8 @@ -40,14 +45,15 @@ def read_32(fobj, start_length, size): """ (start, length) = start_length fobj.seek(start) - sizesq = size[0] * size[1] + pixel_size = (size[0] * size[2], size[1] * size[2]) + sizesq = pixel_size[0] * pixel_size[1] if length == sizesq * 3: # uncompressed ("RGBRGBGB") indata = fobj.read(length) - im = Image.frombuffer("RGB", size, indata, "raw", "RGB", 0, 1) + im = Image.frombuffer("RGB", pixel_size, indata, "raw", "RGB", 0, 1) else: # decode image - im = Image.new("RGB", size, None) + im = Image.new("RGB", pixel_size, None) for band_ix in range(3): data = [] bytesleft = sizesq @@ -72,7 +78,7 @@ def read_32(fobj, start_length, size): "Error reading channel [%r left]" % bytesleft ) band = Image.frombuffer( - "L", size, b"".join(data), "raw", "L", 0, 1 + "L", pixel_size, b"".join(data), "raw", "L", 0, 1 ) im.im.putband(band.im, band_ix) return {"RGB": im} @@ -81,27 +87,80 @@ def read_mk(fobj, start_length, size): # Alpha masks seem to be uncompressed (start, length) = start_length fobj.seek(start) + pixel_size = (size[0] * size[2], size[1] * size[2]) + sizesq = pixel_size[0] * pixel_size[1] band = Image.frombuffer( - "L", size, fobj.read(size[0]*size[1]), "raw", "L", 0, 1 + "L", pixel_size, fobj.read(sizesq), "raw", "L", 0, 1 ) return {"A": band} +def read_png_or_jpeg2000(fobj, start_length, size): + (start, length) = start_length + fobj.seek(start) + sig = fobj.read(12) + if sig[:8] == b'\x89PNG\x0d\x0a\x1a\x0a': + fobj.seek(start) + im = PngImagePlugin.PngImageFile(fobj) + return {"RGBA": im} + elif sig[:4] == b'\xff\x4f\xff\x51' \ + or sig[:4] == b'\x0d\x0a\x87\x0a' \ + or sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a': + if not enable_jpeg2k: + raise ValueError('Unsupported icon subimage format (rebuild PIL with JPEG 2000 support to fix this)') + # j2k, jpc or j2c + fobj.seek(start) + jp2kstream = fobj.read(length) + f = io.BytesIO(jp2kstream) + im = Jpeg2KImagePlugin.Jpeg2KImageFile(f) + if im.mode != 'RGBA': + im = im.convert('RGBA') + return {"RGBA": im} + else: + raise ValueError('Unsupported icon subimage format') + class IcnsFile: SIZES = { - (128, 128): [ + (512, 512, 2): [ + (b'ic10', read_png_or_jpeg2000), + ], + (512, 512, 1): [ + (b'ic09', read_png_or_jpeg2000), + ], + (256, 256, 2): [ + (b'ic14', read_png_or_jpeg2000), + ], + (256, 256, 1): [ + (b'ic08', read_png_or_jpeg2000), + ], + (128, 128, 2): [ + (b'ic13', read_png_or_jpeg2000), + ], + (128, 128, 1): [ + (b'ic07', read_png_or_jpeg2000), (b'it32', read_32t), (b't8mk', read_mk), ], - (48, 48): [ + (64, 64, 1): [ + (b'icp6', read_png_or_jpeg2000), + ], + (32, 32, 2): [ + (b'ic12', read_png_or_jpeg2000), + ], + (48, 48, 1): [ (b'ih32', read_32), (b'h8mk', read_mk), ], - (32, 32): [ + (32, 32, 1): [ + (b'icp5', read_png_or_jpeg2000), (b'il32', read_32), (b'l8mk', read_mk), ], - (16, 16): [ + (16, 16, 2): [ + (b'ic11', read_png_or_jpeg2000), + ], + (16, 16, 1): [ + (b'icp4', read_png_or_jpeg2000), (b'is32', read_32), (b's8mk', read_mk), ], @@ -115,7 +174,7 @@ class IcnsFile: self.dct = dct = {} self.fobj = fobj sig, filesize = nextheader(fobj) - if sig != 'icns': + if sig != b'icns': raise SyntaxError('not an icns file') i = HEADERSIZE while i < filesize: @@ -157,7 +216,14 @@ class IcnsFile: def getimage(self, size=None): if size is None: size = self.bestsize() + if len(size) == 2: + size = (size[0], size[1], 1) channels = self.dataforsize(size) + + im = channels.get('RGBA', None) + if im: + return im + im = channels.get("RGB").copy() try: im.putalpha(channels["A"]) @@ -185,18 +251,29 @@ class IcnsImageFile(ImageFile.ImageFile): def _open(self): self.icns = IcnsFile(self.fp) self.mode = 'RGBA' - self.size = self.icns.bestsize() + self.best_size = self.icns.bestsize() + self.size = (self.best_size[0] * self.best_size[2], + self.best_size[1] * self.best_size[2]) self.info['sizes'] = self.icns.itersizes() # Just use this to see if it's loaded or not yet. self.tile = ('',) def load(self): + if len(self.size) == 3: + self.best_size = self.size + self.size = (self.best_size[0] * self.best_size[2], + self.best_size[1] * self.best_size[2]) + Image.Image.load(self) if not self.tile: return self.load_prepare() # This is likely NOT the best way to do it, but whatever. - im = self.icns.getimage(self.size) + im = self.icns.getimage(self.best_size) + + # If this is a PNG or JPEG 2000, it won't be loaded yet + im.load() + self.im = im.im self.mode = im.mode self.size = im.size @@ -205,12 +282,18 @@ class IcnsImageFile(ImageFile.ImageFile): self.tile = () self.load_end() - Image.register_open("ICNS", IcnsImageFile, lambda x: x[:4] == b'icns') Image.register_extension("ICNS", '.icns') if __name__ == '__main__': import os, sys + imf = IcnsImageFile(open(sys.argv[1], 'rb')) + for size in imf.info['sizes']: + imf.size = size + imf.load() + im = imf.im + im.save('out-%s-%s-%s.png' % size) im = Image.open(open(sys.argv[1], "rb")) im.save("out.png") - os.startfile("out.png") + if sys.platform == 'windows': + os.startfile("out.png") diff --git a/PIL/Image.py b/PIL/Image.py index 75e7efc75..7713fe7d9 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1965,7 +1965,7 @@ def fromarray(obj, mode=None): else: ndmax = 4 if ndim > ndmax: - raise ValueError("Too many dimensions.") + raise ValueError("Too many dimensions: %d > %d." % (ndim, ndmax)) size = shape[1], shape[0] if strides is not None: @@ -2018,7 +2018,7 @@ def open(fp, mode="r"): """ if mode != "r": - raise ValueError("bad mode") + raise ValueError("bad mode %r" % mode) if isPath(fp): filename = fp @@ -2054,7 +2054,8 @@ def open(fp, mode="r"): #traceback.print_exc() pass - raise IOError("cannot identify image file") + raise IOError("cannot identify image file %r" + % (filename if filename else fp)) # # Image processing. diff --git a/PIL/ImageColor.py b/PIL/ImageColor.py index 107df193a..98a241bb0 100644 --- a/PIL/ImageColor.py +++ b/PIL/ImageColor.py @@ -20,15 +20,6 @@ from PIL import Image import re - -## -# Convert color string to RGB tuple. -# -# @param color A CSS3-style colour string. -# @return An RGB-tuple. -# @exception ValueError If the color string could not be interpreted -# as an RGB value. - def getrgb(color): """ Convert a color string to an RGB tuple. If the string cannot be parsed, @@ -37,7 +28,7 @@ def getrgb(color): .. versionadded:: 1.1.4 :param color: A color string - :return: ``(red, green, blue)`` + :return: ``(red, green, blue[, alpha])`` """ try: rgb = colormap[color] @@ -114,7 +105,7 @@ def getcolor(color, mode): .. versionadded:: 1.1.4 :param color: A color string - :return: ``(red, green, blue)`` + :return: ``(graylevel [, alpha]) or (red, green, blue[, alpha])`` """ # same as getrgb, but converts the result to the given mode color, alpha = getrgb(color), 255 diff --git a/PIL/ImageFile.py b/PIL/ImageFile.py index a63fe757e..501e16b00 100644 --- a/PIL/ImageFile.py +++ b/PIL/ImageFile.py @@ -205,7 +205,7 @@ class ImageFile(Image.Image): else: raise IndexError(ie) - if not s: # truncated jpeg + if not s and not d.handles_eof: # truncated jpeg self.tile = [] # JpegDecode needs to clean things up here either way diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py new file mode 100644 index 000000000..f57f4a784 --- /dev/null +++ b/PIL/Jpeg2KImagePlugin.py @@ -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') diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 07a09232c..da52006ca 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -442,7 +442,7 @@ samplings = { } def convert_dict_qtables(qtables): - qtables = [qtables[key] for key in xrange(len(qtables)) if qtables.has_key(key)] + qtables = [qtables[key] for key in range(len(qtables)) if key in qtables] for idx, table in enumerate(qtables): qtables[idx] = [table[i] for i in zigzag_index] return qtables @@ -504,7 +504,7 @@ def _save(im, fp, filename): except ValueError: raise ValueError("Invalid quantization table") else: - qtables = [lines[s:s+64] for s in xrange(0, len(lines), 64)] + qtables = [lines[s:s+64] for s in range(0, len(lines), 64)] if isinstance(qtables, (tuple, list, dict)): if isinstance(qtables, dict): qtables = convert_dict_qtables(qtables) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 68a77e9ca..18d5909dc 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -693,10 +693,11 @@ class TiffImageFile(ImageFile.ImageFile): if not len(self.tile) == 1: raise IOError("Not exactly one tile") - d, e, o, a = self.tile[0] - d = Image._getdecoder(self.mode, 'libtiff', a, self.decoderconfig) + # (self._compression, (extents tuple), 0, (rawmode, self._compression, fp)) + ignored, extents, ignored_2, args = self.tile[0] + decoder = Image._getdecoder(self.mode, 'libtiff', args, self.decoderconfig) try: - d.setimage(self.im, e) + decoder.setimage(self.im, extents) except ValueError: raise IOError("Couldn't set the image") @@ -712,27 +713,30 @@ class TiffImageFile(ImageFile.ImageFile): # with here by reordering. if Image.DEBUG: print ("have getvalue. just sending in a string from getvalue") - n,e = d.decode(self.fp.getvalue()) + n,err = decoder.decode(self.fp.getvalue()) elif hasattr(self.fp, "fileno"): # we've got a actual file on disk, pass in the fp. if Image.DEBUG: print ("have fileno, calling fileno version of the decoder.") self.fp.seek(0) - n,e = d.decode(b"fpfp") # 4 bytes, otherwise the trace might error out + n,err = decoder.decode(b"fpfp") # 4 bytes, otherwise the trace might error out else: # we have something else. if Image.DEBUG: print ("don't have fileno or getvalue. just reading") # UNDONE -- so much for that buffer size thing. - n, e = d.decode(self.fp.read()) + n,err = decoder.decode(self.fp.read()) self.tile = [] self.readonly = 0 + # libtiff closed the fp in a, we need to close self.fp, if possible + if hasattr(self.fp, 'close'): + self.fp.close() self.fp = None # might be shared - if e < 0: - raise IOError(e) + if err < 0: + raise IOError(err) self.load_end() diff --git a/PIL/__init__.py b/PIL/__init__.py index 18bd42a5f..b83220097 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -33,6 +33,7 @@ _plugins = ['ArgImagePlugin', 'ImtImagePlugin', 'IptcImagePlugin', 'JpegImagePlugin', + 'Jpeg2KImagePlugin', 'McIdasImagePlugin', 'MicImagePlugin', 'MpegImagePlugin', diff --git a/Tests/images/pillow2.icns b/Tests/images/pillow2.icns new file mode 100644 index 000000000..5f0ff13b9 Binary files /dev/null and b/Tests/images/pillow2.icns differ diff --git a/Tests/images/pillow3.icns b/Tests/images/pillow3.icns new file mode 100644 index 000000000..ef9b89178 Binary files /dev/null and b/Tests/images/pillow3.icns differ diff --git a/Tests/images/test-card-lossless.jp2 b/Tests/images/test-card-lossless.jp2 new file mode 100644 index 000000000..497b97b8d Binary files /dev/null and b/Tests/images/test-card-lossless.jp2 differ diff --git a/Tests/images/test-card-lossy-tiled.jp2 b/Tests/images/test-card-lossy-tiled.jp2 new file mode 100644 index 000000000..482773188 Binary files /dev/null and b/Tests/images/test-card-lossy-tiled.jp2 differ diff --git a/Tests/images/test-card.png b/Tests/images/test-card.png new file mode 100644 index 000000000..4b0e1f8de Binary files /dev/null and b/Tests/images/test-card.png differ diff --git a/Tests/run.py b/Tests/run.py index 02b633c90..01a3f3603 100644 --- a/Tests/run.py +++ b/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": diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index 0e03df79a..99818229f 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -3,7 +3,7 @@ from tester import * from PIL import Image import os -base = 'Tests/images/bmp/' +base = os.path.join('Tests', 'images', 'bmp') def get_files(d, ext='.bmp'): @@ -78,9 +78,9 @@ def test_good(): except Exception as msg: # there are three here that are unsupported: - unsupported = ('Tests/images/bmp/g/rgb32bf.bmp', - 'Tests/images/bmp/g/pal8rle.bmp', - 'Tests/images/bmp/g/pal4rle.bmp') + unsupported = (os.path.join(base, 'g', 'rgb32bf.bmp'), + os.path.join(base, 'g', 'pal8rle.bmp'), + os.path.join(base, 'g', 'pal4rle.bmp')) if f not in unsupported: assert_true(False, "Unsupported Image %s: %s" %(f,msg)) diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py new file mode 100644 index 000000000..3e31f8879 --- /dev/null +++ b/Tests/test_file_icns.py @@ -0,0 +1,66 @@ +from tester import * + +from PIL import Image + +# sample icon file +file = "Images/pillow.icns" +data = open(file, "rb").read() + +enable_jpeg2k = hasattr(Image.core, 'jp2klib_version') + +def test_sanity(): + # Loading this icon by default should result in the largest size + # (512x512@2x) being loaded + im = Image.open(file) + im.load() + assert_equal(im.mode, "RGBA") + assert_equal(im.size, (1024, 1024)) + assert_equal(im.format, "ICNS") + +def test_sizes(): + # Check that we can load all of the sizes, and that the final pixel + # dimensions are as expected + im = Image.open(file) + for w,h,r in im.info['sizes']: + wr = w * r + hr = h * r + im2 = Image.open(file) + im2.size = (w, h, r) + im2.load() + assert_equal(im2.mode, 'RGBA') + assert_equal(im2.size, (wr, hr)) + +def test_older_icon(): + # This icon was made with Icon Composer rather than iconutil; it still + # uses PNG rather than JP2, however (since it was made on 10.9). + im = Image.open('Tests/images/pillow2.icns') + for w,h,r in im.info['sizes']: + wr = w * r + hr = h * r + im2 = Image.open('Tests/images/pillow2.icns') + im2.size = (w, h, r) + im2.load() + assert_equal(im2.mode, 'RGBA') + assert_equal(im2.size, (wr, hr)) + +def test_jp2_icon(): + # This icon was made by using Uli Kusterer's oldiconutil to replace + # the PNG images with JPEG 2000 ones. The advantage of doing this is + # that OS X 10.5 supports JPEG 2000 but not PNG; some commercial + # software therefore does just this. + + # (oldiconutil is here: https://github.com/uliwitness/oldiconutil) + + if not enable_jpeg2k: + return + + im = Image.open('Tests/images/pillow3.icns') + for w,h,r in im.info['sizes']: + wr = w * r + hr = h * r + im2 = Image.open('Tests/images/pillow3.icns') + im2.size = (w, h, r) + im2.load() + assert_equal(im2.mode, 'RGBA') + assert_equal(im2.size, (wr, hr)) + diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 07a7c9f96..095cad359 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -10,9 +10,7 @@ codecs = dir(Image.core) if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs: skip("jpeg support not available") -# sample jpeg stream -file = "Images/lena.jpg" -data = open(file, "rb").read() +test_file = "Images/lena.jpg" def roundtrip(im, **options): out = BytesIO() @@ -30,7 +28,7 @@ def test_sanity(): # internal version number assert_match(Image.core.jpeglib_version, "\d+\.\d+$") - im = Image.open(file) + im = Image.open(test_file) im.load() assert_equal(im.mode, "RGB") assert_equal(im.size, (128, 128)) @@ -40,7 +38,7 @@ def test_sanity(): def test_app(): # Test APP/COM reader (@PIL135) - im = Image.open(file) + im = Image.open(test_file) assert_equal(im.applist[0], ("APP0", b"JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00")) assert_equal(im.applist[1], ("COM", b"Python Imaging Library")) @@ -49,8 +47,8 @@ def test_app(): def test_cmyk(): # Test CMYK handling. Thanks to Tim and Charlie for test data, # Michael for getting me to look one more time. - file = "Tests/images/pil_sample_cmyk.jpg" - im = Image.open(file) + f = "Tests/images/pil_sample_cmyk.jpg" + im = Image.open(f) # the source image has red pixels in the upper left corner. c, m, y, k = [x / 255.0 for x in im.getpixel((0, 0))] assert_true(c == 0.0 and m > 0.8 and y > 0.8 and k == 0.0) @@ -66,7 +64,7 @@ def test_cmyk(): def test_dpi(): def test(xdpi, ydpi=None): - im = Image.open(file) + im = Image.open(test_file) im = roundtrip(im, dpi=(xdpi, ydpi or xdpi)) return im.info.get("dpi") assert_equal(test(72), (72, 72)) @@ -80,9 +78,9 @@ def test_icc(): icc_profile = im1.info["icc_profile"] assert_equal(len(icc_profile), 3144) # Roundtrip via physical file. - file = tempfile("temp.jpg") - im1.save(file, icc_profile=icc_profile) - im2 = Image.open(file) + f = tempfile("temp.jpg") + im1.save(f, icc_profile=icc_profile) + im2 = Image.open(f) assert_equal(im2.info.get("icc_profile"), icc_profile) # Roundtrip via memory buffer. im1 = roundtrip(lena()) @@ -203,3 +201,9 @@ def test_exif(): im = Image.open("Tests/images/pil_sample_rgb.jpg") info = im._getexif() assert_equal(info[305], 'Adobe Photoshop CS Macintosh') + + +def test_quality_keep(): + im = Image.open("Images/lena.jpg") + f = tempfile('temp.jpg') + assert_no_exception(lambda: im.save(f, quality='keep')) diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py new file mode 100644 index 000000000..b11e5e6ab --- /dev/null +++ b/Tests/test_file_jpeg2k.py @@ -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) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index a94257bc0..a53593a9e 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -1,4 +1,5 @@ from tester import * +import os from PIL import Image, TiffImagePlugin @@ -295,3 +296,13 @@ def xtest_bw_compression_wRGB(): assert_exception(IOError, lambda: im.save(out, compression='group3')) assert_exception(IOError, lambda: im.save(out, compression='group4')) +def test_fp_leak(): + im = Image.open("Tests/images/lena_g4_500.tif") + fn = im.fp.fileno() + + assert_no_exception(lambda: os.fstat(fn)) + im.load() # this should close it. + assert_exception(OSError, lambda: os.fstat(fn)) + im = None # this should force even more closed. + assert_exception(OSError, lambda: os.fstat(fn)) + assert_exception(OSError, lambda: os.close(fn)) diff --git a/Tests/test_image_point.py b/Tests/test_image_point.py index c70556f6a..34233f80e 100644 --- a/Tests/test_image_point.py +++ b/Tests/test_image_point.py @@ -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() diff --git a/Tests/test_imagecolor.py b/Tests/test_imagecolor.py index acdb84213..c67c20255 100644 --- a/Tests/test_imagecolor.py +++ b/Tests/test_imagecolor.py @@ -41,7 +41,11 @@ Image.new("L", (1, 1), "white") assert_equal(0, ImageColor.getcolor("black", "1")) assert_equal(255, ImageColor.getcolor("white", "1")) +# The following test is wrong, but is current behavior +# The correct result should be 255 due to the mode 1 assert_equal(162, ImageColor.getcolor("rgba(0, 255, 115, 33)", "1")) +# Correct behavior +# assert_equal(255, ImageColor.getcolor("rgba(0, 255, 115, 33)", "1")) Image.new("1", (1, 1), "white") assert_equal((0, 255), ImageColor.getcolor("black", "LA")) diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index 5c39c9283..b30971e8f 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -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() diff --git a/Tests/tester.py b/Tests/tester.py index 2c6fa071c..d4309e3e6 100644 --- a/Tests/tester.py +++ b/Tests/tester.py @@ -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(): diff --git a/_imaging.c b/_imaging.c index 215c56bf6..138ce0101 100644 --- a/_imaging.c +++ b/_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); diff --git a/decode.c b/decode.c index f3ac60e51..77038cc2c 100644 --- a/decode.c +++ b/decode.c @@ -52,6 +52,7 @@ typedef struct { struct ImagingCodecStateInstance state; Imaging im; PyObject* lock; + int handles_eof; } ImagingDecoderObject; static PyTypeObject ImagingDecoderType; @@ -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 */ diff --git a/depends/install_openjpeg.sh b/depends/install_openjpeg.sh new file mode 100755 index 000000000..bd6b83e3b --- /dev/null +++ b/depends/install_openjpeg.sh @@ -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 + diff --git a/depends/install_webp.sh b/depends/install_webp.sh new file mode 100755 index 000000000..5f5963712 --- /dev/null +++ b/depends/install_webp.sh @@ -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 + diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 21cd615fc..cdb9e2ca4 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -153,6 +153,92 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: before building the Python Imaging Library. See the distribution README for details. +JPEG 2000 +^^^^^^^^^ + +PIL reads and writes JPEG 2000 files containing ``L``, ``LA``, ``RGB`` or +``RGBA`` data. It can also read files containing ``YCbCr`` data, which it +converts on read into ``RGB`` or ``RGBA`` depending on whether or not there is +an alpha channel. PIL supports JPEG 2000 raw codestreams (``.j2k`` files), as +well as boxed JPEG 2000 files (``.j2p`` or ``.jpx`` files). PIL does *not* +support files whose components have different sampling frequencies. + +When loading, if you set the ``mode`` on the image prior to the +:py:meth:`~PIL.Image.Image.load` method being invoked, you can ask PIL to +convert the image to either ``RGB`` or ``RGBA`` rather than choosing for +itself. It is also possible to set ``reduce`` to the number of resolutions to +discard (each one reduces the size of the resulting image by a factor of 2), +and ``layers`` to specify the number of quality layers to load. + +The :py:meth:`~PIL.Image.Image.save` method supports the following options: + +**offset** + The image offset, as a tuple of integers, e.g. (16, 16) + +**tile_offset** + The tile offset, again as a 2-tuple of integers. + +**tile_size** + The tile size as a 2-tuple. If not specified, or if set to None, the + image will be saved without tiling. + +**quality_mode** + Either `"rates"` or `"dB"` depending on the units you want to use to + specify image quality. + +**quality_layers** + A sequence of numbers, each of which represents either an approximate size + reduction (if quality mode is `"rates"`) or a signal to noise ratio value + in decibels. If not specified, defaults to a single layer of full quality. + +**num_resolutions** + The number of different image resolutions to be stored (which corresponds + to the number of Discrete Wavelet Transform decompositions plus one). + +**codeblock_size** + The code-block size as a 2-tuple. Minimum size is 4 x 4, maximum is 1024 x + 1024, with the additional restriction that no code-block may have more + than 4096 coefficients (i.e. the product of the two numbers must be no + greater than 4096). + +**precinct_size** + The precinct size as a 2-tuple. Must be a power of two along both axes, + and must be greater than the code-block size. + +**irreversible** + If ``True``, use the lossy Irreversible Color Transformation + followed by DWT 9-7. Defaults to ``False``, which means to use the + Reversible Color Transformation with DWT 5-3. + +**progression** + Controls the progression order; must be one of ``"LRCP"``, ``"RLCP"``, + ``"RPCL"``, ``"PCRL"``, ``"CPRL"``. The letters stand for Component, + Position, Resolution and Layer respectively and control the order of + encoding, the idea being that e.g. an image encoded using LRCP mode can + have its quality layers decoded as they arrive at the decoder, while one + encoded using RLCP mode will have increasing resolutions decoded as they + arrive, and so on. + +**cinema_mode** + Set the encoder to produce output compliant with the digital cinema + specifications. The options here are ``"no"`` (the default), + ``"cinema2k-24"`` for 24fps 2K, ``"cinema2k-48"`` for 48fps 2K, and + ``"cinema4k-24"`` for 24fps 4K. Note that for compliant 2K files, + *at least one* of your image dimensions must match 2048 x 1080, while + for compliant 4K files, *at least one* of the dimensions must match + 4096 x 2160. + +.. note:: + + To enable JPEG 2000 support, you need to build and install the OpenJPEG + library, version 2.0.0 or higher, before building the Python Imaging + Library. + + Windows users can install the OpenJPEG binaries available on the + OpenJPEG website, but must add them to their PATH in order to use PIL (if + you fail to do this, you will get errors about not being able to load the + ``_imaging`` DLL). + MSP ^^^ @@ -437,6 +523,25 @@ ICO ICO is used to store icons on Windows. The largest available icon is read. +ICNS +^^^^ + +PIL reads Mac OS X ``.icns`` files. By default, the largest available icon is +read, though you can override this by setting the :py:attr:`~PIL.Image.Image.size` +property before calling :py:meth:`~PIL.Image.Image.load`. The +:py:meth:`~PIL.Image.Image.open` method sets the following +:py:attr:`~PIL.Image.Image.info` property: + +**sizes** + A list of supported sizes found in this icon file; these are a + 3-tuple, ``(width, height, scale)``, where ``scale`` is 2 for a retina + icon and 1 for a standard icon. You *are* permitted to use this 3-tuple + format for the :py:attr:`~PIL.Image.Image.size` property if you set it + before calling :py:meth:`~PIL.Image.Image.load`; after loading, the size + will be reset to a 2-tuple containing pixel dimensions (so, e.g. if you + ask for ``(512, 512, 2)``, the final value of + :py:attr:`~PIL.Image.Image.size` will be ``(1024, 1024)``). + IMT ^^^ diff --git a/docs/index.rst b/docs/index.rst index 52a054e22..36f600c85 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,7 +1,7 @@ -Pillow: a modern fork of PIL -============================ +Pillow +====== -Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the +Pillow is the 'friendly' PIL fork by Alex Clark and Contributors. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. .. image:: https://travis-ci.org/python-imaging/Pillow.png @@ -15,7 +15,7 @@ Python Imaging Library by Fredrik Lundh and Contributors. :target: https://pypi.python.org/pypi/Pillow/ :alt: Number of PyPI downloads -To start using Pillow, read the :doc:`installation +To start using Pillow, please read the :doc:`installation instructions `. If you can't find the information you need, try the old `PIL Handbook`_, but be diff --git a/docs/installation.rst b/docs/installation.rst index ff08dee17..1cab14c14 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -172,6 +172,15 @@ Python Wheels $ pip install --use-wheel Pillow +If the above does not work, it's likely because we haven't uploaded a +wheel for the latest version of Pillow. In that case, try pinning it +to a specific version: + +:: + + $ pip install --use-wheel Pillow==2.3.0 + + Platform support ---------------- @@ -216,4 +225,6 @@ current versions of Linux, OS X, and Windows. +----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ | Windows 8 Pro |Yes | 2.6,2.7,3.2,3.3,3.4a3 | 2.2.0 |x86,x86-64 | +----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ +| Windows 8.1 Pro |Yes | 2.6,2.7,3.2,3.3,3.4 | 2.3.0 |x86,x86-64 | ++----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ diff --git a/docs/plugins.rst b/docs/plugins.rst index b92b500c1..001cee949 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -153,6 +153,14 @@ Plugin reference :undoc-members: :show-inheritance: +:mod:`Jpeg2KImagePlugin` Module +----------------------------- + +.. automodule:: PIL.Jpeg2KImagePlugin + :members: + :undoc-members: + :show-inheritance: + :mod:`McIdasImagePlugin` Module ------------------------------- diff --git a/encode.c b/encode.c index 3ceb50731..d403ab072 100644 --- a/encode.c +++ b/encode.c @@ -40,6 +40,7 @@ typedef struct { PyObject_HEAD int (*encode)(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes); + int (*cleanup)(ImagingCodecState state); struct ImagingCodecStateInstance state; Imaging im; PyObject* lock; @@ -77,6 +78,9 @@ PyImaging_EncoderNew(int contextsize) /* Initialize encoder context */ encoder->state.context = context; + /* Most encoders don't need this */ + encoder->cleanup = NULL; + /* Target image */ encoder->lock = NULL; encoder->im = NULL; @@ -87,6 +91,8 @@ PyImaging_EncoderNew(int contextsize) static void _dealloc(ImagingEncoderObject* encoder) { + if (encoder->cleanup) + encoder->cleanup(&encoder->state); free(encoder->state.buffer); free(encoder->state.context); Py_XDECREF(encoder->lock); @@ -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: + * + */ diff --git a/libImaging/Imaging.h b/libImaging/Imaging.h index b45dcbe23..26207d121 100644 --- a/libImaging/Imaging.h +++ b/libImaging/Imaging.h @@ -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 diff --git a/libImaging/Incremental.c b/libImaging/Incremental.c new file mode 100644 index 000000000..9e7fb38ec --- /dev/null +++ b/libImaging/Incremental.c @@ -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 +#include +#else +#include +#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); +} diff --git a/libImaging/Jpeg2K.h b/libImaging/Jpeg2K.h new file mode 100644 index 000000000..be6e0770b --- /dev/null +++ b/libImaging/Jpeg2K.h @@ -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 + +/* -------------------------------------------------------------------- */ +/* 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: + * + */ diff --git a/libImaging/Jpeg2KDecode.c b/libImaging/Jpeg2KDecode.c new file mode 100644 index 000000000..6b6176c78 --- /dev/null +++ b/libImaging/Jpeg2KDecode.c @@ -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 +#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: + * + */ diff --git a/libImaging/Jpeg2KEncode.c b/libImaging/Jpeg2KEncode.c new file mode 100644 index 000000000..c1e16e97f --- /dev/null +++ b/libImaging/Jpeg2KEncode.c @@ -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: + * + */ diff --git a/selftest.py b/selftest.py index 1f905b9a7..248cb3937 100644 --- a/selftest.py +++ b/selftest.py @@ -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") diff --git a/setup.py b/setup.py index a8ff2e762..efe684e05 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,8 @@ _LIB_IMAGING = ( "QuantHeap", "PcdDecode", "PcxDecode", "PcxEncode", "Point", "RankFilter", "RawDecode", "RawEncode", "Storage", "SunRleDecode", "TgaRleDecode", "Unpack", "UnpackYCC", "UnsharpMask", "XbmDecode", - "XbmEncode", "ZipDecode", "ZipEncode", "TiffDecode") + "XbmEncode", "ZipDecode", "ZipEncode", "TiffDecode", "Incremental", + "Jpeg2KDecode", "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,11 @@ 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 sys.platform == "win32": + defs.append(("OPJ_STATIC", None)) if feature.zlib: libs.append(feature.zlib) defs.append(("HAVE_LIBZ", None)) @@ -537,6 +561,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"),