mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-24 17:06:16 +03:00
merge from master
This commit is contained in:
commit
caf27c6908
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -6,3 +6,12 @@ dist
|
||||||
*.so
|
*.so
|
||||||
docs/_build
|
docs/_build
|
||||||
*~
|
*~
|
||||||
|
|
||||||
|
# Vim cruft
|
||||||
|
.*.swp
|
||||||
|
|
||||||
|
#emacs
|
||||||
|
*~
|
||||||
|
\#*#
|
||||||
|
.#*
|
||||||
|
|
||||||
|
|
15
.travis.yml
15
.travis.yml
|
@ -4,19 +4,32 @@ language: python
|
||||||
virtualenv:
|
virtualenv:
|
||||||
system_site_packages: true
|
system_site_packages: true
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
irc: "chat.freenode.net#pil"
|
||||||
|
|
||||||
python:
|
python:
|
||||||
- 2.6
|
- 2.6
|
||||||
- 2.7
|
- 2.7
|
||||||
- 3.2
|
- 3.2
|
||||||
- 3.3
|
- 3.3
|
||||||
|
- "pypy"
|
||||||
|
|
||||||
install:
|
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"
|
- "pip install cffi"
|
||||||
|
|
||||||
|
# webp
|
||||||
|
- pushd depends && ./install_webp.sh && popd
|
||||||
|
|
||||||
|
# openjpeg
|
||||||
|
- pushd depends && ./install_openjpeg.sh && popd
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- python setup.py clean
|
- python setup.py clean
|
||||||
- python setup.py build_ext --inplace
|
- python setup.py build_ext --inplace
|
||||||
- python selftest.py
|
- python selftest.py
|
||||||
- python Tests/run.py
|
- python Tests/run.py
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- python: "pypy"
|
||||||
|
|
15
CHANGES.rst
15
CHANGES.rst
|
@ -4,6 +4,21 @@ Changelog (Pillow)
|
||||||
2.4.0 (unreleased)
|
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
|
- Avoid conflicting _expand functions in PIL & MINGW, fixes #538
|
||||||
[aclark]
|
[aclark]
|
||||||
|
|
||||||
|
|
BIN
Images/pillow.icns
Normal file
BIN
Images/pillow.icns
Normal file
Binary file not shown.
BIN
Images/pillow.ico
Normal file
BIN
Images/pillow.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 100 KiB |
|
@ -10,12 +10,17 @@
|
||||||
# Copyright (c) 2004 by Bob Ippolito.
|
# Copyright (c) 2004 by Bob Ippolito.
|
||||||
# Copyright (c) 2004 by Secret Labs.
|
# Copyright (c) 2004 by Secret Labs.
|
||||||
# Copyright (c) 2004 by Fredrik Lundh.
|
# Copyright (c) 2004 by Fredrik Lundh.
|
||||||
|
# Copyright (c) 2014 by Alastair Houghton.
|
||||||
#
|
#
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image, ImageFile, _binary
|
from PIL import Image, ImageFile, PngImagePlugin, _binary
|
||||||
import struct
|
import struct, io
|
||||||
|
|
||||||
|
enable_jpeg2k = hasattr(Image.core, 'jp2klib_version')
|
||||||
|
if enable_jpeg2k:
|
||||||
|
from PIL import Jpeg2KImagePlugin
|
||||||
|
|
||||||
i8 = _binary.i8
|
i8 = _binary.i8
|
||||||
|
|
||||||
|
@ -40,14 +45,15 @@ def read_32(fobj, start_length, size):
|
||||||
"""
|
"""
|
||||||
(start, length) = start_length
|
(start, length) = start_length
|
||||||
fobj.seek(start)
|
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:
|
if length == sizesq * 3:
|
||||||
# uncompressed ("RGBRGBGB")
|
# uncompressed ("RGBRGBGB")
|
||||||
indata = fobj.read(length)
|
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:
|
else:
|
||||||
# decode image
|
# decode image
|
||||||
im = Image.new("RGB", size, None)
|
im = Image.new("RGB", pixel_size, None)
|
||||||
for band_ix in range(3):
|
for band_ix in range(3):
|
||||||
data = []
|
data = []
|
||||||
bytesleft = sizesq
|
bytesleft = sizesq
|
||||||
|
@ -72,7 +78,7 @@ def read_32(fobj, start_length, size):
|
||||||
"Error reading channel [%r left]" % bytesleft
|
"Error reading channel [%r left]" % bytesleft
|
||||||
)
|
)
|
||||||
band = Image.frombuffer(
|
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)
|
im.im.putband(band.im, band_ix)
|
||||||
return {"RGB": im}
|
return {"RGB": im}
|
||||||
|
@ -81,27 +87,80 @@ def read_mk(fobj, start_length, size):
|
||||||
# Alpha masks seem to be uncompressed
|
# Alpha masks seem to be uncompressed
|
||||||
(start, length) = start_length
|
(start, length) = start_length
|
||||||
fobj.seek(start)
|
fobj.seek(start)
|
||||||
|
pixel_size = (size[0] * size[2], size[1] * size[2])
|
||||||
|
sizesq = pixel_size[0] * pixel_size[1]
|
||||||
band = Image.frombuffer(
|
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}
|
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:
|
class IcnsFile:
|
||||||
|
|
||||||
SIZES = {
|
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'it32', read_32t),
|
||||||
(b't8mk', read_mk),
|
(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'ih32', read_32),
|
||||||
(b'h8mk', read_mk),
|
(b'h8mk', read_mk),
|
||||||
],
|
],
|
||||||
(32, 32): [
|
(32, 32, 1): [
|
||||||
|
(b'icp5', read_png_or_jpeg2000),
|
||||||
(b'il32', read_32),
|
(b'il32', read_32),
|
||||||
(b'l8mk', read_mk),
|
(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'is32', read_32),
|
||||||
(b's8mk', read_mk),
|
(b's8mk', read_mk),
|
||||||
],
|
],
|
||||||
|
@ -115,7 +174,7 @@ class IcnsFile:
|
||||||
self.dct = dct = {}
|
self.dct = dct = {}
|
||||||
self.fobj = fobj
|
self.fobj = fobj
|
||||||
sig, filesize = nextheader(fobj)
|
sig, filesize = nextheader(fobj)
|
||||||
if sig != 'icns':
|
if sig != b'icns':
|
||||||
raise SyntaxError('not an icns file')
|
raise SyntaxError('not an icns file')
|
||||||
i = HEADERSIZE
|
i = HEADERSIZE
|
||||||
while i < filesize:
|
while i < filesize:
|
||||||
|
@ -157,7 +216,14 @@ class IcnsFile:
|
||||||
def getimage(self, size=None):
|
def getimage(self, size=None):
|
||||||
if size is None:
|
if size is None:
|
||||||
size = self.bestsize()
|
size = self.bestsize()
|
||||||
|
if len(size) == 2:
|
||||||
|
size = (size[0], size[1], 1)
|
||||||
channels = self.dataforsize(size)
|
channels = self.dataforsize(size)
|
||||||
|
|
||||||
|
im = channels.get('RGBA', None)
|
||||||
|
if im:
|
||||||
|
return im
|
||||||
|
|
||||||
im = channels.get("RGB").copy()
|
im = channels.get("RGB").copy()
|
||||||
try:
|
try:
|
||||||
im.putalpha(channels["A"])
|
im.putalpha(channels["A"])
|
||||||
|
@ -185,18 +251,29 @@ class IcnsImageFile(ImageFile.ImageFile):
|
||||||
def _open(self):
|
def _open(self):
|
||||||
self.icns = IcnsFile(self.fp)
|
self.icns = IcnsFile(self.fp)
|
||||||
self.mode = 'RGBA'
|
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()
|
self.info['sizes'] = self.icns.itersizes()
|
||||||
# Just use this to see if it's loaded or not yet.
|
# Just use this to see if it's loaded or not yet.
|
||||||
self.tile = ('',)
|
self.tile = ('',)
|
||||||
|
|
||||||
def load(self):
|
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)
|
Image.Image.load(self)
|
||||||
if not self.tile:
|
if not self.tile:
|
||||||
return
|
return
|
||||||
self.load_prepare()
|
self.load_prepare()
|
||||||
# This is likely NOT the best way to do it, but whatever.
|
# 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.im = im.im
|
||||||
self.mode = im.mode
|
self.mode = im.mode
|
||||||
self.size = im.size
|
self.size = im.size
|
||||||
|
@ -205,12 +282,18 @@ class IcnsImageFile(ImageFile.ImageFile):
|
||||||
self.tile = ()
|
self.tile = ()
|
||||||
self.load_end()
|
self.load_end()
|
||||||
|
|
||||||
|
|
||||||
Image.register_open("ICNS", IcnsImageFile, lambda x: x[:4] == b'icns')
|
Image.register_open("ICNS", IcnsImageFile, lambda x: x[:4] == b'icns')
|
||||||
Image.register_extension("ICNS", '.icns')
|
Image.register_extension("ICNS", '.icns')
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import os, sys
|
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 = Image.open(open(sys.argv[1], "rb"))
|
||||||
im.save("out.png")
|
im.save("out.png")
|
||||||
os.startfile("out.png")
|
if sys.platform == 'windows':
|
||||||
|
os.startfile("out.png")
|
||||||
|
|
|
@ -1965,7 +1965,7 @@ def fromarray(obj, mode=None):
|
||||||
else:
|
else:
|
||||||
ndmax = 4
|
ndmax = 4
|
||||||
if ndim > ndmax:
|
if ndim > ndmax:
|
||||||
raise ValueError("Too many dimensions.")
|
raise ValueError("Too many dimensions: %d > %d." % (ndim, ndmax))
|
||||||
|
|
||||||
size = shape[1], shape[0]
|
size = shape[1], shape[0]
|
||||||
if strides is not None:
|
if strides is not None:
|
||||||
|
@ -2018,7 +2018,7 @@ def open(fp, mode="r"):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if mode != "r":
|
if mode != "r":
|
||||||
raise ValueError("bad mode")
|
raise ValueError("bad mode %r" % mode)
|
||||||
|
|
||||||
if isPath(fp):
|
if isPath(fp):
|
||||||
filename = fp
|
filename = fp
|
||||||
|
@ -2054,7 +2054,8 @@ def open(fp, mode="r"):
|
||||||
#traceback.print_exc()
|
#traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
raise IOError("cannot identify image file")
|
raise IOError("cannot identify image file %r"
|
||||||
|
% (filename if filename else fp))
|
||||||
|
|
||||||
#
|
#
|
||||||
# Image processing.
|
# Image processing.
|
||||||
|
|
|
@ -20,15 +20,6 @@
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import re
|
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):
|
def getrgb(color):
|
||||||
"""
|
"""
|
||||||
Convert a color string to an RGB tuple. If the string cannot be parsed,
|
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
|
.. versionadded:: 1.1.4
|
||||||
|
|
||||||
:param color: A color string
|
:param color: A color string
|
||||||
:return: ``(red, green, blue)``
|
:return: ``(red, green, blue[, alpha])``
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
rgb = colormap[color]
|
rgb = colormap[color]
|
||||||
|
@ -114,7 +105,7 @@ def getcolor(color, mode):
|
||||||
.. versionadded:: 1.1.4
|
.. versionadded:: 1.1.4
|
||||||
|
|
||||||
:param color: A color string
|
: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
|
# same as getrgb, but converts the result to the given mode
|
||||||
color, alpha = getrgb(color), 255
|
color, alpha = getrgb(color), 255
|
||||||
|
|
|
@ -205,7 +205,7 @@ class ImageFile(Image.Image):
|
||||||
else:
|
else:
|
||||||
raise IndexError(ie)
|
raise IndexError(ie)
|
||||||
|
|
||||||
if not s: # truncated jpeg
|
if not s and not d.handles_eof: # truncated jpeg
|
||||||
self.tile = []
|
self.tile = []
|
||||||
|
|
||||||
# JpegDecode needs to clean things up here either way
|
# JpegDecode needs to clean things up here either way
|
||||||
|
|
249
PIL/Jpeg2KImagePlugin.py
Normal file
249
PIL/Jpeg2KImagePlugin.py
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
#
|
||||||
|
# The Python Imaging Library
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
# JPEG2000 file handling
|
||||||
|
#
|
||||||
|
# History:
|
||||||
|
# 2014-03-12 ajh Created
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014 Coriolis Systems Limited
|
||||||
|
# Copyright (c) 2014 Alastair Houghton
|
||||||
|
#
|
||||||
|
# See the README file for information on usage and redistribution.
|
||||||
|
#
|
||||||
|
|
||||||
|
__version__ = "0.1"
|
||||||
|
|
||||||
|
from PIL import Image, ImageFile, _binary
|
||||||
|
import struct
|
||||||
|
import os
|
||||||
|
import io
|
||||||
|
|
||||||
|
def _parse_codestream(fp):
|
||||||
|
"""Parse the JPEG 2000 codestream to extract the size and component
|
||||||
|
count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
|
||||||
|
|
||||||
|
hdr = fp.read(2)
|
||||||
|
lsiz = struct.unpack('>H', hdr)[0]
|
||||||
|
siz = hdr + fp.read(lsiz - 2)
|
||||||
|
lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, \
|
||||||
|
xtosiz, ytosiz, csiz \
|
||||||
|
= struct.unpack('>HHIIIIIIIIH', siz[:38])
|
||||||
|
ssiz = [None]*csiz
|
||||||
|
xrsiz = [None]*csiz
|
||||||
|
yrsiz = [None]*csiz
|
||||||
|
for i in range(csiz):
|
||||||
|
ssiz[i], xrsiz[i], yrsiz[i] \
|
||||||
|
= struct.unpack('>BBB', siz[36 + 3 * i:39 + 3 * i])
|
||||||
|
|
||||||
|
size = (xsiz - xosiz, ysiz - yosiz)
|
||||||
|
if csiz == 1:
|
||||||
|
mode = 'L'
|
||||||
|
elif csiz == 2:
|
||||||
|
mode = 'LA'
|
||||||
|
elif csiz == 3:
|
||||||
|
mode = 'RGB'
|
||||||
|
elif csiz == 4:
|
||||||
|
mode == 'RGBA'
|
||||||
|
else:
|
||||||
|
mode = None
|
||||||
|
|
||||||
|
return (size, mode)
|
||||||
|
|
||||||
|
def _parse_jp2_header(fp):
|
||||||
|
"""Parse the JP2 header box to extract size, component count and
|
||||||
|
color space information, returning a PIL (size, mode) tuple."""
|
||||||
|
|
||||||
|
# Find the JP2 header box
|
||||||
|
header = None
|
||||||
|
while True:
|
||||||
|
lbox, tbox = struct.unpack('>I4s', fp.read(8))
|
||||||
|
if lbox == 1:
|
||||||
|
lbox = struct.unpack('>Q', fp.read(8))[0]
|
||||||
|
hlen = 16
|
||||||
|
else:
|
||||||
|
hlen = 8
|
||||||
|
|
||||||
|
if tbox == b'jp2h':
|
||||||
|
header = fp.read(lbox - hlen)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
fp.seek(lbox - hlen, os.SEEK_CUR)
|
||||||
|
|
||||||
|
if header is None:
|
||||||
|
raise SyntaxError('could not find JP2 header')
|
||||||
|
|
||||||
|
size = None
|
||||||
|
mode = None
|
||||||
|
|
||||||
|
hio = io.BytesIO(header)
|
||||||
|
while True:
|
||||||
|
lbox, tbox = struct.unpack('>I4s', hio.read(8))
|
||||||
|
if lbox == 1:
|
||||||
|
lbox = struct.unpack('>Q', hio.read(8))[0]
|
||||||
|
hlen = 16
|
||||||
|
else:
|
||||||
|
hlen = 8
|
||||||
|
|
||||||
|
content = hio.read(lbox - hlen)
|
||||||
|
|
||||||
|
if tbox == b'ihdr':
|
||||||
|
height, width, nc, bpc, c, unkc, ipr \
|
||||||
|
= struct.unpack('>IIHBBBB', content)
|
||||||
|
size = (width, height)
|
||||||
|
if unkc:
|
||||||
|
if nc == 1:
|
||||||
|
mode = 'L'
|
||||||
|
elif nc == 2:
|
||||||
|
mode = 'LA'
|
||||||
|
elif nc == 3:
|
||||||
|
mode = 'RGB'
|
||||||
|
elif nc == 4:
|
||||||
|
mode = 'RGBA'
|
||||||
|
break
|
||||||
|
elif tbox == b'colr':
|
||||||
|
meth, prec, approx = struct.unpack('>BBB', content[:3])
|
||||||
|
if meth == 1:
|
||||||
|
cs = struct.unpack('>I', content[3:7])[0]
|
||||||
|
if cs == 16: # sRGB
|
||||||
|
if nc == 3:
|
||||||
|
mode = 'RGB'
|
||||||
|
elif nc == 4:
|
||||||
|
mode = 'RGBA'
|
||||||
|
break
|
||||||
|
elif cs == 17: # grayscale
|
||||||
|
if nc == 1:
|
||||||
|
mode = 'L'
|
||||||
|
elif nc == 2:
|
||||||
|
mode = 'LA'
|
||||||
|
break
|
||||||
|
elif cs == 18: # sYCC
|
||||||
|
if nc == 3:
|
||||||
|
mode = 'RGB'
|
||||||
|
elif nc == 4:
|
||||||
|
mode == 'RGBA'
|
||||||
|
break
|
||||||
|
|
||||||
|
return (size, mode)
|
||||||
|
|
||||||
|
##
|
||||||
|
# Image plugin for JPEG2000 images.
|
||||||
|
|
||||||
|
class Jpeg2KImageFile(ImageFile.ImageFile):
|
||||||
|
format = "JPEG2000"
|
||||||
|
format_description = "JPEG 2000 (ISO 15444)"
|
||||||
|
|
||||||
|
def _open(self):
|
||||||
|
sig = self.fp.read(4)
|
||||||
|
if sig == b'\xff\x4f\xff\x51':
|
||||||
|
self.codec = "j2k"
|
||||||
|
self.size, self.mode = _parse_codestream(self.fp)
|
||||||
|
else:
|
||||||
|
sig = sig + self.fp.read(8)
|
||||||
|
|
||||||
|
if sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a':
|
||||||
|
self.codec = "jp2"
|
||||||
|
self.size, self.mode = _parse_jp2_header(self.fp)
|
||||||
|
else:
|
||||||
|
raise SyntaxError('not a JPEG 2000 file')
|
||||||
|
|
||||||
|
if self.size is None or self.mode is None:
|
||||||
|
raise SyntaxError('unable to determine size/mode')
|
||||||
|
|
||||||
|
self.reduce = 0
|
||||||
|
self.layers = 0
|
||||||
|
|
||||||
|
fd = -1
|
||||||
|
|
||||||
|
if hasattr(self.fp, "fileno"):
|
||||||
|
try:
|
||||||
|
fd = self.fp.fileno()
|
||||||
|
except:
|
||||||
|
fd = -1
|
||||||
|
|
||||||
|
self.tile = [('jpeg2k', (0, 0) + self.size, 0,
|
||||||
|
(self.codec, self.reduce, self.layers, fd))]
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
if self.reduce:
|
||||||
|
power = 1 << self.reduce
|
||||||
|
adjust = power >> 1
|
||||||
|
self.size = (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')
|
|
@ -442,7 +442,7 @@ samplings = {
|
||||||
}
|
}
|
||||||
|
|
||||||
def convert_dict_qtables(qtables):
|
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):
|
for idx, table in enumerate(qtables):
|
||||||
qtables[idx] = [table[i] for i in zigzag_index]
|
qtables[idx] = [table[i] for i in zigzag_index]
|
||||||
return qtables
|
return qtables
|
||||||
|
@ -504,7 +504,7 @@ def _save(im, fp, filename):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError("Invalid quantization table")
|
raise ValueError("Invalid quantization table")
|
||||||
else:
|
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, (tuple, list, dict)):
|
||||||
if isinstance(qtables, dict):
|
if isinstance(qtables, dict):
|
||||||
qtables = convert_dict_qtables(qtables)
|
qtables = convert_dict_qtables(qtables)
|
||||||
|
|
|
@ -693,10 +693,11 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
if not len(self.tile) == 1:
|
if not len(self.tile) == 1:
|
||||||
raise IOError("Not exactly one tile")
|
raise IOError("Not exactly one tile")
|
||||||
|
|
||||||
d, e, o, a = self.tile[0]
|
# (self._compression, (extents tuple), 0, (rawmode, self._compression, fp))
|
||||||
d = Image._getdecoder(self.mode, 'libtiff', a, self.decoderconfig)
|
ignored, extents, ignored_2, args = self.tile[0]
|
||||||
|
decoder = Image._getdecoder(self.mode, 'libtiff', args, self.decoderconfig)
|
||||||
try:
|
try:
|
||||||
d.setimage(self.im, e)
|
decoder.setimage(self.im, extents)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise IOError("Couldn't set the image")
|
raise IOError("Couldn't set the image")
|
||||||
|
|
||||||
|
@ -712,27 +713,30 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
# with here by reordering.
|
# with here by reordering.
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
print ("have getvalue. just sending in a string from getvalue")
|
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"):
|
elif hasattr(self.fp, "fileno"):
|
||||||
# we've got a actual file on disk, pass in the fp.
|
# we've got a actual file on disk, pass in the fp.
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
print ("have fileno, calling fileno version of the decoder.")
|
print ("have fileno, calling fileno version of the decoder.")
|
||||||
self.fp.seek(0)
|
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:
|
else:
|
||||||
# we have something else.
|
# we have something else.
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
print ("don't have fileno or getvalue. just reading")
|
print ("don't have fileno or getvalue. just reading")
|
||||||
# UNDONE -- so much for that buffer size thing.
|
# 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.tile = []
|
||||||
self.readonly = 0
|
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
|
self.fp = None # might be shared
|
||||||
|
|
||||||
if e < 0:
|
if err < 0:
|
||||||
raise IOError(e)
|
raise IOError(err)
|
||||||
|
|
||||||
self.load_end()
|
self.load_end()
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ _plugins = ['ArgImagePlugin',
|
||||||
'ImtImagePlugin',
|
'ImtImagePlugin',
|
||||||
'IptcImagePlugin',
|
'IptcImagePlugin',
|
||||||
'JpegImagePlugin',
|
'JpegImagePlugin',
|
||||||
|
'Jpeg2KImagePlugin',
|
||||||
'McIdasImagePlugin',
|
'McIdasImagePlugin',
|
||||||
'MicImagePlugin',
|
'MicImagePlugin',
|
||||||
'MpegImagePlugin',
|
'MpegImagePlugin',
|
||||||
|
|
BIN
Tests/images/pillow2.icns
Normal file
BIN
Tests/images/pillow2.icns
Normal file
Binary file not shown.
BIN
Tests/images/pillow3.icns
Normal file
BIN
Tests/images/pillow3.icns
Normal file
Binary file not shown.
BIN
Tests/images/test-card-lossless.jp2
Normal file
BIN
Tests/images/test-card-lossless.jp2
Normal file
Binary file not shown.
BIN
Tests/images/test-card-lossy-tiled.jp2
Normal file
BIN
Tests/images/test-card-lossy-tiled.jp2
Normal file
Binary file not shown.
BIN
Tests/images/test-card.png
Normal file
BIN
Tests/images/test-card.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
29
Tests/run.py
29
Tests/run.py
|
@ -2,7 +2,7 @@ from __future__ import print_function
|
||||||
|
|
||||||
# minimal test runner
|
# minimal test runner
|
||||||
|
|
||||||
import glob, os, os.path, sys, tempfile
|
import glob, os, os.path, sys, tempfile, re
|
||||||
|
|
||||||
try:
|
try:
|
||||||
root = os.path.dirname(__file__)
|
root = os.path.dirname(__file__)
|
||||||
|
@ -38,6 +38,8 @@ skipped = []
|
||||||
python_options = " ".join(python_options)
|
python_options = " ".join(python_options)
|
||||||
tester_options = " ".join(tester_options)
|
tester_options = " ".join(tester_options)
|
||||||
|
|
||||||
|
ignore_re = re.compile('^ignore: (.*)$', re.MULTILINE)
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
test, ext = os.path.splitext(os.path.basename(file))
|
test, ext = os.path.splitext(os.path.basename(file))
|
||||||
if include and test not in include:
|
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" % (
|
out = os.popen("%s %s -u %s %s 2>&1" % (
|
||||||
sys.executable, python_options, file, tester_options
|
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":
|
if result == "ok":
|
||||||
result = None
|
result = None
|
||||||
elif result == "skip":
|
elif result == "skip":
|
||||||
|
|
|
@ -3,7 +3,7 @@ from tester import *
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import os
|
import os
|
||||||
|
|
||||||
base = 'Tests/images/bmp/'
|
base = os.path.join('Tests', 'images', 'bmp')
|
||||||
|
|
||||||
|
|
||||||
def get_files(d, ext='.bmp'):
|
def get_files(d, ext='.bmp'):
|
||||||
|
@ -78,9 +78,9 @@ def test_good():
|
||||||
|
|
||||||
except Exception as msg:
|
except Exception as msg:
|
||||||
# there are three here that are unsupported:
|
# there are three here that are unsupported:
|
||||||
unsupported = ('Tests/images/bmp/g/rgb32bf.bmp',
|
unsupported = (os.path.join(base, 'g', 'rgb32bf.bmp'),
|
||||||
'Tests/images/bmp/g/pal8rle.bmp',
|
os.path.join(base, 'g', 'pal8rle.bmp'),
|
||||||
'Tests/images/bmp/g/pal4rle.bmp')
|
os.path.join(base, 'g', 'pal4rle.bmp'))
|
||||||
if f not in unsupported:
|
if f not in unsupported:
|
||||||
assert_true(False, "Unsupported Image %s: %s" %(f,msg))
|
assert_true(False, "Unsupported Image %s: %s" %(f,msg))
|
||||||
|
|
||||||
|
|
66
Tests/test_file_icns.py
Normal file
66
Tests/test_file_icns.py
Normal file
|
@ -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))
|
||||||
|
|
|
@ -10,9 +10,7 @@ codecs = dir(Image.core)
|
||||||
if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs:
|
if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs:
|
||||||
skip("jpeg support not available")
|
skip("jpeg support not available")
|
||||||
|
|
||||||
# sample jpeg stream
|
test_file = "Images/lena.jpg"
|
||||||
file = "Images/lena.jpg"
|
|
||||||
data = open(file, "rb").read()
|
|
||||||
|
|
||||||
def roundtrip(im, **options):
|
def roundtrip(im, **options):
|
||||||
out = BytesIO()
|
out = BytesIO()
|
||||||
|
@ -30,7 +28,7 @@ def test_sanity():
|
||||||
# internal version number
|
# internal version number
|
||||||
assert_match(Image.core.jpeglib_version, "\d+\.\d+$")
|
assert_match(Image.core.jpeglib_version, "\d+\.\d+$")
|
||||||
|
|
||||||
im = Image.open(file)
|
im = Image.open(test_file)
|
||||||
im.load()
|
im.load()
|
||||||
assert_equal(im.mode, "RGB")
|
assert_equal(im.mode, "RGB")
|
||||||
assert_equal(im.size, (128, 128))
|
assert_equal(im.size, (128, 128))
|
||||||
|
@ -40,7 +38,7 @@ def test_sanity():
|
||||||
|
|
||||||
def test_app():
|
def test_app():
|
||||||
# Test APP/COM reader (@PIL135)
|
# Test APP/COM reader (@PIL135)
|
||||||
im = Image.open(file)
|
im = Image.open(test_file)
|
||||||
assert_equal(im.applist[0],
|
assert_equal(im.applist[0],
|
||||||
("APP0", b"JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00"))
|
("APP0", b"JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00"))
|
||||||
assert_equal(im.applist[1], ("COM", b"Python Imaging Library"))
|
assert_equal(im.applist[1], ("COM", b"Python Imaging Library"))
|
||||||
|
@ -49,8 +47,8 @@ def test_app():
|
||||||
def test_cmyk():
|
def test_cmyk():
|
||||||
# Test CMYK handling. Thanks to Tim and Charlie for test data,
|
# Test CMYK handling. Thanks to Tim and Charlie for test data,
|
||||||
# Michael for getting me to look one more time.
|
# Michael for getting me to look one more time.
|
||||||
file = "Tests/images/pil_sample_cmyk.jpg"
|
f = "Tests/images/pil_sample_cmyk.jpg"
|
||||||
im = Image.open(file)
|
im = Image.open(f)
|
||||||
# the source image has red pixels in the upper left corner.
|
# 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))]
|
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)
|
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_dpi():
|
||||||
def test(xdpi, ydpi=None):
|
def test(xdpi, ydpi=None):
|
||||||
im = Image.open(file)
|
im = Image.open(test_file)
|
||||||
im = roundtrip(im, dpi=(xdpi, ydpi or xdpi))
|
im = roundtrip(im, dpi=(xdpi, ydpi or xdpi))
|
||||||
return im.info.get("dpi")
|
return im.info.get("dpi")
|
||||||
assert_equal(test(72), (72, 72))
|
assert_equal(test(72), (72, 72))
|
||||||
|
@ -80,9 +78,9 @@ def test_icc():
|
||||||
icc_profile = im1.info["icc_profile"]
|
icc_profile = im1.info["icc_profile"]
|
||||||
assert_equal(len(icc_profile), 3144)
|
assert_equal(len(icc_profile), 3144)
|
||||||
# Roundtrip via physical file.
|
# Roundtrip via physical file.
|
||||||
file = tempfile("temp.jpg")
|
f = tempfile("temp.jpg")
|
||||||
im1.save(file, icc_profile=icc_profile)
|
im1.save(f, icc_profile=icc_profile)
|
||||||
im2 = Image.open(file)
|
im2 = Image.open(f)
|
||||||
assert_equal(im2.info.get("icc_profile"), icc_profile)
|
assert_equal(im2.info.get("icc_profile"), icc_profile)
|
||||||
# Roundtrip via memory buffer.
|
# Roundtrip via memory buffer.
|
||||||
im1 = roundtrip(lena())
|
im1 = roundtrip(lena())
|
||||||
|
@ -203,3 +201,9 @@ def test_exif():
|
||||||
im = Image.open("Tests/images/pil_sample_rgb.jpg")
|
im = Image.open("Tests/images/pil_sample_rgb.jpg")
|
||||||
info = im._getexif()
|
info = im._getexif()
|
||||||
assert_equal(info[305], 'Adobe Photoshop CS Macintosh')
|
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'))
|
||||||
|
|
110
Tests/test_file_jpeg2k.py
Normal file
110
Tests/test_file_jpeg2k.py
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
from tester import *
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from PIL import ImageFile
|
||||||
|
|
||||||
|
codecs = dir(Image.core)
|
||||||
|
|
||||||
|
if "jpeg2k_encoder" not in codecs or "jpeg2k_decoder" not in codecs:
|
||||||
|
skip('JPEG 2000 support not available')
|
||||||
|
|
||||||
|
# OpenJPEG 2.0.0 outputs this debugging message sometimes; we should
|
||||||
|
# ignore it---it doesn't represent a test failure.
|
||||||
|
ignore('Not enough memory to handle tile data')
|
||||||
|
|
||||||
|
test_card = Image.open('Tests/images/test-card.png')
|
||||||
|
test_card.load()
|
||||||
|
|
||||||
|
def roundtrip(im, **options):
|
||||||
|
out = BytesIO()
|
||||||
|
im.save(out, "JPEG2000", **options)
|
||||||
|
bytes = out.tell()
|
||||||
|
out.seek(0)
|
||||||
|
im = Image.open(out)
|
||||||
|
im.bytes = bytes # for testing only
|
||||||
|
im.load()
|
||||||
|
return im
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_sanity():
|
||||||
|
# Internal version number
|
||||||
|
assert_match(Image.core.jp2klib_version, '\d+\.\d+\.\d+$')
|
||||||
|
|
||||||
|
im = Image.open('Tests/images/test-card-lossless.jp2')
|
||||||
|
im.load()
|
||||||
|
assert_equal(im.mode, 'RGB')
|
||||||
|
assert_equal(im.size, (640, 480))
|
||||||
|
assert_equal(im.format, 'JPEG2000')
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
# These two test pre-written JPEG 2000 files that were not written with
|
||||||
|
# PIL (they were made using Adobe Photoshop)
|
||||||
|
|
||||||
|
def test_lossless():
|
||||||
|
im = Image.open('Tests/images/test-card-lossless.jp2')
|
||||||
|
im.load()
|
||||||
|
im.save('/tmp/test-card.png')
|
||||||
|
assert_image_similar(im, test_card, 1.0e-3)
|
||||||
|
|
||||||
|
def test_lossy_tiled():
|
||||||
|
im = Image.open('Tests/images/test-card-lossy-tiled.jp2')
|
||||||
|
im.load()
|
||||||
|
assert_image_similar(im, test_card, 2.0)
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_lossless_rt():
|
||||||
|
im = roundtrip(test_card)
|
||||||
|
assert_image_equal(im, test_card)
|
||||||
|
|
||||||
|
def test_lossy_rt():
|
||||||
|
im = roundtrip(test_card, quality_layers=[20])
|
||||||
|
assert_image_similar(im, test_card, 2.0)
|
||||||
|
|
||||||
|
def test_tiled_rt():
|
||||||
|
im = roundtrip(test_card, tile_size=(128, 128))
|
||||||
|
assert_image_equal(im, test_card)
|
||||||
|
|
||||||
|
def test_tiled_offset_rt():
|
||||||
|
im = roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0),
|
||||||
|
offset=(32, 32))
|
||||||
|
assert_image_equal(im, test_card)
|
||||||
|
|
||||||
|
def test_irreversible_rt():
|
||||||
|
im = roundtrip(test_card, irreversible=True, quality_layers=[20])
|
||||||
|
assert_image_similar(im, test_card, 2.0)
|
||||||
|
|
||||||
|
def test_prog_qual_rt():
|
||||||
|
im = roundtrip(test_card, quality_layers=[60, 40, 20], progression='LRCP')
|
||||||
|
assert_image_similar(im, test_card, 2.0)
|
||||||
|
|
||||||
|
def test_prog_res_rt():
|
||||||
|
im = roundtrip(test_card, num_resolutions=8, progression='RLCP')
|
||||||
|
assert_image_equal(im, test_card)
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_reduce():
|
||||||
|
im = Image.open('Tests/images/test-card-lossless.jp2')
|
||||||
|
im.reduce = 2
|
||||||
|
im.load()
|
||||||
|
assert_equal(im.size, (160, 120))
|
||||||
|
|
||||||
|
def test_layers():
|
||||||
|
out = BytesIO()
|
||||||
|
test_card.save(out, 'JPEG2000', quality_layers=[100, 50, 10],
|
||||||
|
progression='LRCP')
|
||||||
|
out.seek(0)
|
||||||
|
|
||||||
|
im = Image.open(out)
|
||||||
|
im.layers = 1
|
||||||
|
im.load()
|
||||||
|
assert_image_similar(im, test_card, 13)
|
||||||
|
|
||||||
|
out.seek(0)
|
||||||
|
im = Image.open(out)
|
||||||
|
im.layers = 3
|
||||||
|
im.load()
|
||||||
|
assert_image_similar(im, test_card, 0.4)
|
|
@ -1,4 +1,5 @@
|
||||||
from tester import *
|
from tester import *
|
||||||
|
import os
|
||||||
|
|
||||||
from PIL import Image, TiffImagePlugin
|
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='group3'))
|
||||||
assert_exception(IOError, lambda: im.save(out, compression='group4'))
|
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))
|
||||||
|
|
|
@ -2,6 +2,11 @@ from tester import *
|
||||||
|
|
||||||
from PIL import Image
|
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():
|
def test_sanity():
|
||||||
|
|
||||||
im = lena()
|
im = lena()
|
||||||
|
|
|
@ -41,7 +41,11 @@ Image.new("L", (1, 1), "white")
|
||||||
|
|
||||||
assert_equal(0, ImageColor.getcolor("black", "1"))
|
assert_equal(0, ImageColor.getcolor("black", "1"))
|
||||||
assert_equal(255, ImageColor.getcolor("white", "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"))
|
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")
|
Image.new("1", (1, 1), "white")
|
||||||
|
|
||||||
assert_equal((0, 255), ImageColor.getcolor("black", "LA"))
|
assert_equal((0, 255), ImageColor.getcolor("black", "LA"))
|
||||||
|
|
|
@ -3,7 +3,7 @@ from tester import *
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
try:
|
try:
|
||||||
from PIL import ImageTk
|
from PIL import ImageTk
|
||||||
except ImportError as v:
|
except (OSError, ImportError) as v:
|
||||||
skip(v)
|
skip(v)
|
||||||
|
|
||||||
success()
|
success()
|
||||||
|
|
|
@ -260,6 +260,11 @@ def skip(msg=None):
|
||||||
print("skip")
|
print("skip")
|
||||||
os._exit(0) # don't run exit handlers
|
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():
|
def _setup():
|
||||||
global _logfile
|
global _logfile
|
||||||
def report():
|
def report():
|
||||||
|
|
13
_imaging.c
13
_imaging.c
|
@ -3325,6 +3325,7 @@ extern PyObject* PyImaging_FliDecoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_GifDecoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_GifDecoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_HexDecoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_HexDecoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_JpegDecoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_JpegDecoderNew(PyObject* self, PyObject* args);
|
||||||
|
extern PyObject* PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_TiffLzwDecoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_TiffLzwDecoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_MspDecoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_MspDecoderNew(PyObject* self, PyObject* args);
|
||||||
|
@ -3341,6 +3342,7 @@ extern PyObject* PyImaging_ZipDecoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_EpsEncoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_EpsEncoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_GifEncoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_GifEncoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_JpegEncoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_JpegEncoderNew(PyObject* self, PyObject* args);
|
||||||
|
extern PyObject* PyImaging_Jpeg2KEncoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_PcxEncoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_PcxEncoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_RawEncoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_RawEncoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_XbmEncoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_XbmEncoderNew(PyObject* self, PyObject* args);
|
||||||
|
@ -3393,6 +3395,10 @@ static PyMethodDef functions[] = {
|
||||||
#ifdef HAVE_LIBJPEG
|
#ifdef HAVE_LIBJPEG
|
||||||
{"jpeg_decoder", (PyCFunction)PyImaging_JpegDecoderNew, 1},
|
{"jpeg_decoder", (PyCFunction)PyImaging_JpegDecoderNew, 1},
|
||||||
{"jpeg_encoder", (PyCFunction)PyImaging_JpegEncoderNew, 1},
|
{"jpeg_encoder", (PyCFunction)PyImaging_JpegEncoderNew, 1},
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_OPENJPEG
|
||||||
|
{"jpeg2k_decoder", (PyCFunction)PyImaging_Jpeg2KDecoderNew, 1},
|
||||||
|
{"jpeg2k_encoder", (PyCFunction)PyImaging_Jpeg2KEncoderNew, 1},
|
||||||
#endif
|
#endif
|
||||||
{"tiff_lzw_decoder", (PyCFunction)PyImaging_TiffLzwDecoderNew, 1},
|
{"tiff_lzw_decoder", (PyCFunction)PyImaging_TiffLzwDecoderNew, 1},
|
||||||
#ifdef HAVE_LIBTIFF
|
#ifdef HAVE_LIBTIFF
|
||||||
|
@ -3497,6 +3503,13 @@ setup_module(PyObject* m) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_OPENJPEG
|
||||||
|
{
|
||||||
|
extern const char *ImagingJpeg2KVersion(void);
|
||||||
|
PyDict_SetItemString(d, "jp2klib_version", PyUnicode_FromString(ImagingJpeg2KVersion()));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_LIBZ
|
#ifdef HAVE_LIBZ
|
||||||
/* zip encoding strategies */
|
/* zip encoding strategies */
|
||||||
PyModule_AddIntConstant(m, "DEFAULT_STRATEGY", Z_DEFAULT_STRATEGY);
|
PyModule_AddIntConstant(m, "DEFAULT_STRATEGY", Z_DEFAULT_STRATEGY);
|
||||||
|
|
71
decode.c
71
decode.c
|
@ -52,6 +52,7 @@ typedef struct {
|
||||||
struct ImagingCodecStateInstance state;
|
struct ImagingCodecStateInstance state;
|
||||||
Imaging im;
|
Imaging im;
|
||||||
PyObject* lock;
|
PyObject* lock;
|
||||||
|
int handles_eof;
|
||||||
} ImagingDecoderObject;
|
} ImagingDecoderObject;
|
||||||
|
|
||||||
static PyTypeObject ImagingDecoderType;
|
static PyTypeObject ImagingDecoderType;
|
||||||
|
@ -93,6 +94,9 @@ PyImaging_DecoderNew(int contextsize)
|
||||||
/* Initialize the cleanup function pointer */
|
/* Initialize the cleanup function pointer */
|
||||||
decoder->cleanup = NULL;
|
decoder->cleanup = NULL;
|
||||||
|
|
||||||
|
/* Most decoders don't want to handle EOF themselves */
|
||||||
|
decoder->handles_eof = 0;
|
||||||
|
|
||||||
return decoder;
|
return decoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,6 +198,12 @@ _setimage(ImagingDecoderObject* decoder, PyObject* args)
|
||||||
return Py_None;
|
return Py_None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_get_handles_eof(ImagingDecoderObject *decoder)
|
||||||
|
{
|
||||||
|
return PyBool_FromLong(decoder->handles_eof);
|
||||||
|
}
|
||||||
|
|
||||||
static struct PyMethodDef methods[] = {
|
static struct PyMethodDef methods[] = {
|
||||||
{"decode", (PyCFunction)_decode, 1},
|
{"decode", (PyCFunction)_decode, 1},
|
||||||
{"cleanup", (PyCFunction)_decode_cleanup, 1},
|
{"cleanup", (PyCFunction)_decode_cleanup, 1},
|
||||||
|
@ -201,6 +211,13 @@ static struct PyMethodDef methods[] = {
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static struct PyGetSetDef getseters[] = {
|
||||||
|
{"handles_eof", (getter)_get_handles_eof, NULL,
|
||||||
|
"True if this decoder expects to handle EOF itself.",
|
||||||
|
NULL},
|
||||||
|
{NULL, NULL, NULL, NULL, NULL} /* sentinel */
|
||||||
|
};
|
||||||
|
|
||||||
static PyTypeObject ImagingDecoderType = {
|
static PyTypeObject ImagingDecoderType = {
|
||||||
PyVarObject_HEAD_INIT(NULL, 0)
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
"ImagingDecoder", /*tp_name*/
|
"ImagingDecoder", /*tp_name*/
|
||||||
|
@ -232,7 +249,7 @@ static PyTypeObject ImagingDecoderType = {
|
||||||
0, /*tp_iternext*/
|
0, /*tp_iternext*/
|
||||||
methods, /*tp_methods*/
|
methods, /*tp_methods*/
|
||||||
0, /*tp_members*/
|
0, /*tp_members*/
|
||||||
0, /*tp_getset*/
|
getseters, /*tp_getset*/
|
||||||
};
|
};
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
|
@ -762,3 +779,55 @@ PyImaging_JpegDecoderNew(PyObject* self, PyObject* args)
|
||||||
return (PyObject*) decoder;
|
return (PyObject*) decoder;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* JPEG 2000 */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
#ifdef HAVE_OPENJPEG
|
||||||
|
|
||||||
|
#include "Jpeg2K.h"
|
||||||
|
|
||||||
|
PyObject*
|
||||||
|
PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args)
|
||||||
|
{
|
||||||
|
ImagingDecoderObject* decoder;
|
||||||
|
JPEG2KDECODESTATE *context;
|
||||||
|
|
||||||
|
char* mode;
|
||||||
|
char* format;
|
||||||
|
OPJ_CODEC_FORMAT codec_format;
|
||||||
|
int reduce = 0;
|
||||||
|
int layers = 0;
|
||||||
|
int fd = -1;
|
||||||
|
if (!PyArg_ParseTuple(args, "ss|iii", &mode, &format,
|
||||||
|
&reduce, &layers, &fd))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (strcmp(format, "j2k") == 0)
|
||||||
|
codec_format = OPJ_CODEC_J2K;
|
||||||
|
else if (strcmp(format, "jpt") == 0)
|
||||||
|
codec_format = OPJ_CODEC_JPT;
|
||||||
|
else if (strcmp(format, "jp2") == 0)
|
||||||
|
codec_format = OPJ_CODEC_JP2;
|
||||||
|
else
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
decoder = PyImaging_DecoderNew(sizeof(JPEG2KDECODESTATE));
|
||||||
|
if (decoder == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
decoder->handles_eof = 1;
|
||||||
|
decoder->decode = ImagingJpeg2KDecode;
|
||||||
|
decoder->cleanup = ImagingJpeg2KDecodeCleanup;
|
||||||
|
|
||||||
|
context = (JPEG2KDECODESTATE *)decoder->state.context;
|
||||||
|
|
||||||
|
context->fd = fd;
|
||||||
|
context->format = codec_format;
|
||||||
|
context->reduce = reduce;
|
||||||
|
context->layers = layers;
|
||||||
|
|
||||||
|
return (PyObject*) decoder;
|
||||||
|
}
|
||||||
|
#endif /* HAVE_OPENJPEG */
|
||||||
|
|
18
depends/install_openjpeg.sh
Executable file
18
depends/install_openjpeg.sh
Executable file
|
@ -0,0 +1,18 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# install openjpeg
|
||||||
|
|
||||||
|
|
||||||
|
if [ ! -f openjpeg-2.0.0.tar.gz ]; then
|
||||||
|
wget 'https://openjpeg.googlecode.com/files/openjpeg-2.0.0.tar.gz'
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -r openjpeg-2.0.0
|
||||||
|
tar -xvzf openjpeg-2.0.0.tar.gz
|
||||||
|
|
||||||
|
|
||||||
|
pushd openjpeg-2.0.0
|
||||||
|
|
||||||
|
cmake -DCMAKE_INSTALL_PREFIX=/usr . && make && sudo make install
|
||||||
|
|
||||||
|
popd
|
||||||
|
|
18
depends/install_webp.sh
Executable file
18
depends/install_webp.sh
Executable file
|
@ -0,0 +1,18 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# install webp
|
||||||
|
|
||||||
|
|
||||||
|
if [ ! -f libwebp-0.4.0.tar.gz ]; then
|
||||||
|
wget 'https://webp.googlecode.com/files/libwebp-0.4.0.tar.gz'
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -r libwebp-0.4.0
|
||||||
|
tar -xvzf libwebp-0.4.0.tar.gz
|
||||||
|
|
||||||
|
|
||||||
|
pushd libwebp-0.4.0
|
||||||
|
|
||||||
|
./configure --prefix=/usr --enable-libwebpmux --enable-libwebpdemux && make && sudo make install
|
||||||
|
|
||||||
|
popd
|
||||||
|
|
|
@ -153,6 +153,92 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
||||||
before building the Python Imaging Library. See the distribution README for
|
before building the Python Imaging Library. See the distribution README for
|
||||||
details.
|
details.
|
||||||
|
|
||||||
|
JPEG 2000
|
||||||
|
^^^^^^^^^
|
||||||
|
|
||||||
|
PIL reads and writes JPEG 2000 files containing ``L``, ``LA``, ``RGB`` or
|
||||||
|
``RGBA`` data. It can also read files containing ``YCbCr`` data, which it
|
||||||
|
converts on read into ``RGB`` or ``RGBA`` depending on whether or not there is
|
||||||
|
an alpha channel. PIL supports JPEG 2000 raw codestreams (``.j2k`` files), as
|
||||||
|
well as boxed JPEG 2000 files (``.j2p`` or ``.jpx`` files). PIL does *not*
|
||||||
|
support files whose components have different sampling frequencies.
|
||||||
|
|
||||||
|
When loading, if you set the ``mode`` on the image prior to the
|
||||||
|
:py:meth:`~PIL.Image.Image.load` method being invoked, you can ask PIL to
|
||||||
|
convert the image to either ``RGB`` or ``RGBA`` rather than choosing for
|
||||||
|
itself. It is also possible to set ``reduce`` to the number of resolutions to
|
||||||
|
discard (each one reduces the size of the resulting image by a factor of 2),
|
||||||
|
and ``layers`` to specify the number of quality layers to load.
|
||||||
|
|
||||||
|
The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
||||||
|
|
||||||
|
**offset**
|
||||||
|
The image offset, as a tuple of integers, e.g. (16, 16)
|
||||||
|
|
||||||
|
**tile_offset**
|
||||||
|
The tile offset, again as a 2-tuple of integers.
|
||||||
|
|
||||||
|
**tile_size**
|
||||||
|
The tile size as a 2-tuple. If not specified, or if set to None, the
|
||||||
|
image will be saved without tiling.
|
||||||
|
|
||||||
|
**quality_mode**
|
||||||
|
Either `"rates"` or `"dB"` depending on the units you want to use to
|
||||||
|
specify image quality.
|
||||||
|
|
||||||
|
**quality_layers**
|
||||||
|
A sequence of numbers, each of which represents either an approximate size
|
||||||
|
reduction (if quality mode is `"rates"`) or a signal to noise ratio value
|
||||||
|
in decibels. If not specified, defaults to a single layer of full quality.
|
||||||
|
|
||||||
|
**num_resolutions**
|
||||||
|
The number of different image resolutions to be stored (which corresponds
|
||||||
|
to the number of Discrete Wavelet Transform decompositions plus one).
|
||||||
|
|
||||||
|
**codeblock_size**
|
||||||
|
The code-block size as a 2-tuple. Minimum size is 4 x 4, maximum is 1024 x
|
||||||
|
1024, with the additional restriction that no code-block may have more
|
||||||
|
than 4096 coefficients (i.e. the product of the two numbers must be no
|
||||||
|
greater than 4096).
|
||||||
|
|
||||||
|
**precinct_size**
|
||||||
|
The precinct size as a 2-tuple. Must be a power of two along both axes,
|
||||||
|
and must be greater than the code-block size.
|
||||||
|
|
||||||
|
**irreversible**
|
||||||
|
If ``True``, use the lossy Irreversible Color Transformation
|
||||||
|
followed by DWT 9-7. Defaults to ``False``, which means to use the
|
||||||
|
Reversible Color Transformation with DWT 5-3.
|
||||||
|
|
||||||
|
**progression**
|
||||||
|
Controls the progression order; must be one of ``"LRCP"``, ``"RLCP"``,
|
||||||
|
``"RPCL"``, ``"PCRL"``, ``"CPRL"``. The letters stand for Component,
|
||||||
|
Position, Resolution and Layer respectively and control the order of
|
||||||
|
encoding, the idea being that e.g. an image encoded using LRCP mode can
|
||||||
|
have its quality layers decoded as they arrive at the decoder, while one
|
||||||
|
encoded using RLCP mode will have increasing resolutions decoded as they
|
||||||
|
arrive, and so on.
|
||||||
|
|
||||||
|
**cinema_mode**
|
||||||
|
Set the encoder to produce output compliant with the digital cinema
|
||||||
|
specifications. The options here are ``"no"`` (the default),
|
||||||
|
``"cinema2k-24"`` for 24fps 2K, ``"cinema2k-48"`` for 48fps 2K, and
|
||||||
|
``"cinema4k-24"`` for 24fps 4K. Note that for compliant 2K files,
|
||||||
|
*at least one* of your image dimensions must match 2048 x 1080, while
|
||||||
|
for compliant 4K files, *at least one* of the dimensions must match
|
||||||
|
4096 x 2160.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
To enable JPEG 2000 support, you need to build and install the OpenJPEG
|
||||||
|
library, version 2.0.0 or higher, before building the Python Imaging
|
||||||
|
Library.
|
||||||
|
|
||||||
|
Windows users can install the OpenJPEG binaries available on the
|
||||||
|
OpenJPEG website, but must add them to their PATH in order to use PIL (if
|
||||||
|
you fail to do this, you will get errors about not being able to load the
|
||||||
|
``_imaging`` DLL).
|
||||||
|
|
||||||
MSP
|
MSP
|
||||||
^^^
|
^^^
|
||||||
|
|
||||||
|
@ -437,6 +523,25 @@ ICO
|
||||||
|
|
||||||
ICO is used to store icons on Windows. The largest available icon is read.
|
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
|
IMT
|
||||||
^^^
|
^^^
|
||||||
|
|
||||||
|
|
|
@ -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.
|
Python Imaging Library by Fredrik Lundh and Contributors.
|
||||||
|
|
||||||
.. image:: https://travis-ci.org/python-imaging/Pillow.png
|
.. 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/
|
:target: https://pypi.python.org/pypi/Pillow/
|
||||||
:alt: Number of PyPI downloads
|
:alt: Number of PyPI downloads
|
||||||
|
|
||||||
To start using Pillow, read the :doc:`installation
|
To start using Pillow, please read the :doc:`installation
|
||||||
instructions <installation>`.
|
instructions <installation>`.
|
||||||
|
|
||||||
If you can't find the information you need, try the old `PIL Handbook`_, but be
|
If you can't find the information you need, try the old `PIL Handbook`_, but be
|
||||||
|
|
|
@ -172,6 +172,15 @@ Python Wheels
|
||||||
|
|
||||||
$ pip install --use-wheel Pillow
|
$ 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
|
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 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 |
|
||||||
|
+----------------------------------+-------------+------------------------------+------------------------------+-----------------------+
|
||||||
|
|
||||||
|
|
|
@ -153,6 +153,14 @@ Plugin reference
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
:mod:`Jpeg2KImagePlugin` Module
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
.. automodule:: PIL.Jpeg2KImagePlugin
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
:mod:`McIdasImagePlugin` Module
|
:mod:`McIdasImagePlugin` Module
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
|
|
160
encode.c
160
encode.c
|
@ -40,6 +40,7 @@ typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
int (*encode)(Imaging im, ImagingCodecState state,
|
int (*encode)(Imaging im, ImagingCodecState state,
|
||||||
UINT8* buffer, int bytes);
|
UINT8* buffer, int bytes);
|
||||||
|
int (*cleanup)(ImagingCodecState state);
|
||||||
struct ImagingCodecStateInstance state;
|
struct ImagingCodecStateInstance state;
|
||||||
Imaging im;
|
Imaging im;
|
||||||
PyObject* lock;
|
PyObject* lock;
|
||||||
|
@ -77,6 +78,9 @@ PyImaging_EncoderNew(int contextsize)
|
||||||
/* Initialize encoder context */
|
/* Initialize encoder context */
|
||||||
encoder->state.context = context;
|
encoder->state.context = context;
|
||||||
|
|
||||||
|
/* Most encoders don't need this */
|
||||||
|
encoder->cleanup = NULL;
|
||||||
|
|
||||||
/* Target image */
|
/* Target image */
|
||||||
encoder->lock = NULL;
|
encoder->lock = NULL;
|
||||||
encoder->im = NULL;
|
encoder->im = NULL;
|
||||||
|
@ -87,6 +91,8 @@ PyImaging_EncoderNew(int contextsize)
|
||||||
static void
|
static void
|
||||||
_dealloc(ImagingEncoderObject* encoder)
|
_dealloc(ImagingEncoderObject* encoder)
|
||||||
{
|
{
|
||||||
|
if (encoder->cleanup)
|
||||||
|
encoder->cleanup(&encoder->state);
|
||||||
free(encoder->state.buffer);
|
free(encoder->state.buffer);
|
||||||
free(encoder->state.context);
|
free(encoder->state.context);
|
||||||
Py_XDECREF(encoder->lock);
|
Py_XDECREF(encoder->lock);
|
||||||
|
@ -797,4 +803,158 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* JPEG 2000 */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
#ifdef HAVE_OPENJPEG
|
||||||
|
|
||||||
|
#include "Jpeg2K.h"
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2k_decode_coord_tuple(PyObject *tuple, int *x, int *y)
|
||||||
|
{
|
||||||
|
*x = *y = 0;
|
||||||
|
|
||||||
|
if (tuple && PyTuple_Check(tuple) && PyTuple_GET_SIZE(tuple) == 2) {
|
||||||
|
*x = (int)PyInt_AsLong(PyTuple_GET_ITEM(tuple, 0));
|
||||||
|
*y = (int)PyInt_AsLong(PyTuple_GET_ITEM(tuple, 1));
|
||||||
|
|
||||||
|
if (*x < 0)
|
||||||
|
*x = 0;
|
||||||
|
if (*y < 0)
|
||||||
|
*y = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject*
|
||||||
|
PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args)
|
||||||
|
{
|
||||||
|
ImagingEncoderObject *encoder;
|
||||||
|
JPEG2KENCODESTATE *context;
|
||||||
|
|
||||||
|
char *mode;
|
||||||
|
char *format;
|
||||||
|
OPJ_CODEC_FORMAT codec_format;
|
||||||
|
PyObject *offset = NULL, *tile_offset = NULL, *tile_size = NULL;
|
||||||
|
char *quality_mode = "rates";
|
||||||
|
PyObject *quality_layers = NULL;
|
||||||
|
int num_resolutions = 0;
|
||||||
|
PyObject *cblk_size = NULL, *precinct_size = NULL;
|
||||||
|
PyObject *irreversible = NULL;
|
||||||
|
char *progression = "LRCP";
|
||||||
|
OPJ_PROG_ORDER prog_order;
|
||||||
|
char *cinema_mode = "no";
|
||||||
|
OPJ_CINEMA_MODE cine_mode;
|
||||||
|
int fd = -1;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "ss|OOOsOIOOOssi", &mode, &format,
|
||||||
|
&offset, &tile_offset, &tile_size,
|
||||||
|
&quality_mode, &quality_layers, &num_resolutions,
|
||||||
|
&cblk_size, &precinct_size,
|
||||||
|
&irreversible, &progression, &cinema_mode,
|
||||||
|
&fd))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (strcmp (format, "j2k") == 0)
|
||||||
|
codec_format = OPJ_CODEC_J2K;
|
||||||
|
else if (strcmp (format, "jpt") == 0)
|
||||||
|
codec_format = OPJ_CODEC_JPT;
|
||||||
|
else if (strcmp (format, "jp2") == 0)
|
||||||
|
codec_format = OPJ_CODEC_JP2;
|
||||||
|
else
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (strcmp(progression, "LRCP") == 0)
|
||||||
|
prog_order = OPJ_LRCP;
|
||||||
|
else if (strcmp(progression, "RLCP") == 0)
|
||||||
|
prog_order = OPJ_RLCP;
|
||||||
|
else if (strcmp(progression, "RPCL") == 0)
|
||||||
|
prog_order = OPJ_RPCL;
|
||||||
|
else if (strcmp(progression, "PCRL") == 0)
|
||||||
|
prog_order = OPJ_PCRL;
|
||||||
|
else if (strcmp(progression, "CPRL") == 0)
|
||||||
|
prog_order = OPJ_CPRL;
|
||||||
|
else
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (strcmp(cinema_mode, "no") == 0)
|
||||||
|
cine_mode = OPJ_OFF;
|
||||||
|
else if (strcmp(cinema_mode, "cinema2k-24") == 0)
|
||||||
|
cine_mode = OPJ_CINEMA2K_24;
|
||||||
|
else if (strcmp(cinema_mode, "cinema2k-48") == 0)
|
||||||
|
cine_mode = OPJ_CINEMA2K_48;
|
||||||
|
else if (strcmp(cinema_mode, "cinema4k-24") == 0)
|
||||||
|
cine_mode = OPJ_CINEMA4K_24;
|
||||||
|
else
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
encoder = PyImaging_EncoderNew(sizeof(JPEG2KENCODESTATE));
|
||||||
|
if (!encoder)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
encoder->encode = ImagingJpeg2KEncode;
|
||||||
|
encoder->cleanup = ImagingJpeg2KEncodeCleanup;
|
||||||
|
|
||||||
|
context = (JPEG2KENCODESTATE *)encoder->state.context;
|
||||||
|
|
||||||
|
context->fd = fd;
|
||||||
|
context->format = codec_format;
|
||||||
|
context->offset_x = context->offset_y = 0;
|
||||||
|
|
||||||
|
j2k_decode_coord_tuple(offset, &context->offset_x, &context->offset_y);
|
||||||
|
j2k_decode_coord_tuple(tile_offset,
|
||||||
|
&context->tile_offset_x,
|
||||||
|
&context->tile_offset_y);
|
||||||
|
j2k_decode_coord_tuple(tile_size,
|
||||||
|
&context->tile_size_x,
|
||||||
|
&context->tile_size_y);
|
||||||
|
|
||||||
|
/* Error on illegal tile offsets */
|
||||||
|
if (context->tile_size_x && context->tile_size_y) {
|
||||||
|
if (context->tile_offset_x <= context->offset_x - context->tile_size_x
|
||||||
|
|| context->tile_offset_y <= context->offset_y - context->tile_size_y) {
|
||||||
|
PyErr_SetString(PyExc_ValueError,
|
||||||
|
"JPEG 2000 tile offset too small; top left tile must "
|
||||||
|
"intersect image area");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context->tile_offset_x > context->offset_x
|
||||||
|
|| context->tile_offset_y > context->offset_y) {
|
||||||
|
PyErr_SetString(PyExc_ValueError,
|
||||||
|
"JPEG 2000 tile offset too large to cover image area");
|
||||||
|
Py_DECREF(encoder);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quality_layers && PySequence_Check(quality_layers)) {
|
||||||
|
context->quality_is_in_db = strcmp (quality_mode, "dB") == 0;
|
||||||
|
context->quality_layers = quality_layers;
|
||||||
|
Py_INCREF(quality_layers);
|
||||||
|
}
|
||||||
|
|
||||||
|
context->num_resolutions = num_resolutions;
|
||||||
|
|
||||||
|
j2k_decode_coord_tuple(cblk_size,
|
||||||
|
&context->cblk_width,
|
||||||
|
&context->cblk_height);
|
||||||
|
j2k_decode_coord_tuple(precinct_size,
|
||||||
|
&context->precinct_width,
|
||||||
|
&context->precinct_height);
|
||||||
|
|
||||||
|
context->irreversible = PyObject_IsTrue(irreversible);
|
||||||
|
context->progression = prog_order;
|
||||||
|
context->cinema_mode = cine_mode;
|
||||||
|
|
||||||
|
return (PyObject *)encoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Local Variables:
|
||||||
|
* c-basic-offset: 4
|
||||||
|
* End:
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
|
@ -424,6 +424,14 @@ extern int ImagingJpegDecodeCleanup(ImagingCodecState state);
|
||||||
extern int ImagingJpegEncode(Imaging im, ImagingCodecState state,
|
extern int ImagingJpegEncode(Imaging im, ImagingCodecState state,
|
||||||
UINT8* buffer, int bytes);
|
UINT8* buffer, int bytes);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_OPENJPEG
|
||||||
|
extern int ImagingJpeg2KDecode(Imaging im, ImagingCodecState state,
|
||||||
|
UINT8* buffer, int bytes);
|
||||||
|
extern int ImagingJpeg2KDecodeCleanup(ImagingCodecState state);
|
||||||
|
extern int ImagingJpeg2KEncode(Imaging im, ImagingCodecState state,
|
||||||
|
UINT8* buffer, int bytes);
|
||||||
|
extern int ImagingJpeg2KEncodeCleanup(ImagingCodecState state);
|
||||||
|
#endif
|
||||||
extern int ImagingLzwDecode(Imaging im, ImagingCodecState state,
|
extern int ImagingLzwDecode(Imaging im, ImagingCodecState state,
|
||||||
UINT8* buffer, int bytes);
|
UINT8* buffer, int bytes);
|
||||||
#ifdef HAVE_LIBTIFF
|
#ifdef HAVE_LIBTIFF
|
||||||
|
@ -497,6 +505,32 @@ struct ImagingCodecStateInstance {
|
||||||
void *context;
|
void *context;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Incremental encoding/decoding support */
|
||||||
|
typedef struct ImagingIncrementalCodecStruct *ImagingIncrementalCodec;
|
||||||
|
|
||||||
|
typedef int (*ImagingIncrementalCodecEntry)(Imaging im,
|
||||||
|
ImagingCodecState state,
|
||||||
|
ImagingIncrementalCodec codec);
|
||||||
|
|
||||||
|
enum {
|
||||||
|
INCREMENTAL_CODEC_READ = 1,
|
||||||
|
INCREMENTAL_CODEC_WRITE = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
INCREMENTAL_CODEC_NOT_SEEKABLE = 0,
|
||||||
|
INCREMENTAL_CODEC_SEEKABLE = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
extern ImagingIncrementalCodec ImagingIncrementalCodecCreate(ImagingIncrementalCodecEntry codec_entry, Imaging im, ImagingCodecState state, int read_or_write, int seekable, int fd);
|
||||||
|
extern void ImagingIncrementalCodecDestroy(ImagingIncrementalCodec codec);
|
||||||
|
extern int ImagingIncrementalCodecPushBuffer(ImagingIncrementalCodec codec, UINT8 *buf, int bytes);
|
||||||
|
extern ssize_t ImagingIncrementalCodecRead(ImagingIncrementalCodec codec, void *buffer, size_t bytes);
|
||||||
|
extern off_t ImagingIncrementalCodecSkip(ImagingIncrementalCodec codec, off_t bytes);
|
||||||
|
extern ssize_t ImagingIncrementalCodecWrite(ImagingIncrementalCodec codec, const void *buffer, size_t bytes);
|
||||||
|
extern off_t ImagingIncrementalCodecSeek(ImagingIncrementalCodec codec, off_t bytes);
|
||||||
|
extern size_t ImagingIncrementalCodecBytesInBuffer(ImagingIncrementalCodec codec);
|
||||||
|
|
||||||
/* Errcodes */
|
/* Errcodes */
|
||||||
#define IMAGING_CODEC_END 1
|
#define IMAGING_CODEC_END 1
|
||||||
#define IMAGING_CODEC_OVERRUN -1
|
#define IMAGING_CODEC_OVERRUN -1
|
||||||
|
|
677
libImaging/Incremental.c
Normal file
677
libImaging/Incremental.c
Normal file
|
@ -0,0 +1,677 @@
|
||||||
|
/*
|
||||||
|
* The Python Imaging Library
|
||||||
|
* $Id$
|
||||||
|
*
|
||||||
|
* incremental decoding adaptor.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Coriolis Systems Limited
|
||||||
|
* Copyright (c) 2014 Alastair Houghton
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Imaging.h"
|
||||||
|
|
||||||
|
/* The idea behind this interface is simple: the actual decoding proceeds in
|
||||||
|
a thread, which is run in lock step with the main thread. Whenever the
|
||||||
|
ImagingIncrementalCodecRead() call runs short on data, it suspends the
|
||||||
|
decoding thread and wakes the main thread. Conversely, the
|
||||||
|
ImagingIncrementalCodecPushBuffer() call suspends the main thread and wakes
|
||||||
|
the decoding thread, providing a buffer of data.
|
||||||
|
|
||||||
|
The two threads are never running simultaneously, so there is no need for
|
||||||
|
any addition synchronisation measures outside of this file.
|
||||||
|
|
||||||
|
Note also that we start the thread suspended (on Windows), or make it
|
||||||
|
immediately wait (other platforms), so that it's possible to initialise
|
||||||
|
things before the thread starts running.
|
||||||
|
|
||||||
|
This interface is useful to allow PIL to interact efficiently with any
|
||||||
|
third-party imaging library that does not support suspendable reads;
|
||||||
|
one example is OpenJPEG (which is used for J2K support). The TIFF library
|
||||||
|
might also benefit from using this code.
|
||||||
|
|
||||||
|
Note that if using this module, you want to set handles_eof on your
|
||||||
|
decoder to true. Why? Because otherwise ImageFile.load() will abort,
|
||||||
|
thinking that the image is truncated, whereas generally you want it to
|
||||||
|
pass the EOF condition (0 bytes to read) through to your code. */
|
||||||
|
|
||||||
|
/* Additional complication: *Some* codecs need to seek; this is fine if
|
||||||
|
there is a file descriptor, but if we're buffering data it becomes
|
||||||
|
awkward. The incremental adaptor now contains code to handle these
|
||||||
|
two cases. */
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#include <process.h>
|
||||||
|
#else
|
||||||
|
#include <pthread.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define DEBUG_INCREMENTAL 0
|
||||||
|
|
||||||
|
#if DEBUG_INCREMENTAL
|
||||||
|
#define DEBUG(...) printf(__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define DEBUG(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct ImagingIncrementalCodecStruct {
|
||||||
|
#ifdef _WIN32
|
||||||
|
HANDLE hCodecEvent;
|
||||||
|
HANDLE hDataEvent;
|
||||||
|
HANDLE hThread;
|
||||||
|
#else
|
||||||
|
pthread_mutex_t start_mutex;
|
||||||
|
pthread_cond_t start_cond;
|
||||||
|
pthread_mutex_t codec_mutex;
|
||||||
|
pthread_cond_t codec_cond;
|
||||||
|
pthread_mutex_t data_mutex;
|
||||||
|
pthread_cond_t data_cond;
|
||||||
|
pthread_t thread;
|
||||||
|
#endif
|
||||||
|
ImagingIncrementalCodecEntry entry;
|
||||||
|
Imaging im;
|
||||||
|
ImagingCodecState state;
|
||||||
|
struct {
|
||||||
|
int fd;
|
||||||
|
UINT8 *buffer; /* Base of buffer */
|
||||||
|
UINT8 *ptr; /* Current pointer in buffer */
|
||||||
|
UINT8 *top; /* Highest point in buffer we've used */
|
||||||
|
UINT8 *end; /* End of buffer */
|
||||||
|
} stream;
|
||||||
|
int read_or_write;
|
||||||
|
int seekable;
|
||||||
|
int started;
|
||||||
|
int result;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void flush_stream(ImagingIncrementalCodec codec);
|
||||||
|
|
||||||
|
#if _WIN32
|
||||||
|
static unsigned int __stdcall
|
||||||
|
codec_thread(void *ptr)
|
||||||
|
{
|
||||||
|
ImagingIncrementalCodec codec = (ImagingIncrementalCodec)ptr;
|
||||||
|
|
||||||
|
DEBUG("Entering thread\n");
|
||||||
|
|
||||||
|
codec->result = codec->entry(codec->im, codec->state, codec);
|
||||||
|
|
||||||
|
DEBUG("Leaving thread (%d)\n", codec->result);
|
||||||
|
|
||||||
|
flush_stream(codec);
|
||||||
|
|
||||||
|
SetEvent(codec->hCodecEvent);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static void *
|
||||||
|
codec_thread(void *ptr)
|
||||||
|
{
|
||||||
|
ImagingIncrementalCodec codec = (ImagingIncrementalCodec)ptr;
|
||||||
|
|
||||||
|
DEBUG("Entering thread\n");
|
||||||
|
|
||||||
|
codec->result = codec->entry(codec->im, codec->state, codec);
|
||||||
|
|
||||||
|
DEBUG("Leaving thread (%d)\n", codec->result);
|
||||||
|
|
||||||
|
flush_stream(codec);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&codec->codec_mutex);
|
||||||
|
pthread_cond_signal(&codec->codec_cond);
|
||||||
|
pthread_mutex_unlock(&codec->codec_mutex);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void
|
||||||
|
flush_stream(ImagingIncrementalCodec codec)
|
||||||
|
{
|
||||||
|
UINT8 *buffer;
|
||||||
|
size_t bytes;
|
||||||
|
|
||||||
|
/* This is to flush data from the write buffer for a seekable write
|
||||||
|
codec. */
|
||||||
|
if (codec->read_or_write != INCREMENTAL_CODEC_WRITE
|
||||||
|
|| codec->state->errcode != IMAGING_CODEC_END
|
||||||
|
|| !codec->seekable
|
||||||
|
|| codec->stream.fd >= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DEBUG("flushing data\n");
|
||||||
|
|
||||||
|
buffer = codec->stream.buffer;
|
||||||
|
bytes = codec->stream.ptr - codec->stream.buffer;
|
||||||
|
|
||||||
|
codec->state->errcode = 0;
|
||||||
|
codec->seekable = INCREMENTAL_CODEC_NOT_SEEKABLE;
|
||||||
|
codec->stream.buffer = codec->stream.ptr = codec->stream.end
|
||||||
|
= codec->stream.top = NULL;
|
||||||
|
|
||||||
|
ImagingIncrementalCodecWrite(codec, buffer, bytes);
|
||||||
|
|
||||||
|
codec->state->errcode = IMAGING_CODEC_END;
|
||||||
|
codec->result = (int)ImagingIncrementalCodecBytesInBuffer(codec);
|
||||||
|
|
||||||
|
free(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new incremental codec */
|
||||||
|
ImagingIncrementalCodec
|
||||||
|
ImagingIncrementalCodecCreate(ImagingIncrementalCodecEntry codec_entry,
|
||||||
|
Imaging im,
|
||||||
|
ImagingCodecState state,
|
||||||
|
int read_or_write,
|
||||||
|
int seekable,
|
||||||
|
int fd)
|
||||||
|
{
|
||||||
|
ImagingIncrementalCodec codec = (ImagingIncrementalCodec)malloc(sizeof(struct ImagingIncrementalCodecStruct));
|
||||||
|
|
||||||
|
codec->entry = codec_entry;
|
||||||
|
codec->im = im;
|
||||||
|
codec->state = state;
|
||||||
|
codec->result = 0;
|
||||||
|
codec->stream.fd = fd;
|
||||||
|
codec->stream.buffer = codec->stream.ptr = codec->stream.end
|
||||||
|
= codec->stream.top = NULL;
|
||||||
|
codec->started = 0;
|
||||||
|
codec->seekable = seekable;
|
||||||
|
codec->read_or_write = read_or_write;
|
||||||
|
|
||||||
|
if (fd >= 0)
|
||||||
|
lseek(fd, 0, SEEK_SET);
|
||||||
|
|
||||||
|
/* System specific set-up */
|
||||||
|
#if _WIN32
|
||||||
|
codec->hCodecEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||||
|
|
||||||
|
if (!codec->hCodecEvent) {
|
||||||
|
free(codec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
codec->hDataEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||||
|
|
||||||
|
if (!codec->hDataEvent) {
|
||||||
|
CloseHandle(codec->hCodecEvent);
|
||||||
|
free(codec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
codec->hThread = _beginthreadex(NULL, 0, codec_thread, codec,
|
||||||
|
CREATE_SUSPENDED, NULL);
|
||||||
|
|
||||||
|
if (!codec->hThread) {
|
||||||
|
CloseHandle(codec->hCodecEvent);
|
||||||
|
CloseHandle(codec->hDataEvent);
|
||||||
|
free(codec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (pthread_mutex_init(&codec->start_mutex, NULL)) {
|
||||||
|
free (codec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pthread_mutex_init(&codec->codec_mutex, NULL)) {
|
||||||
|
pthread_mutex_destroy(&codec->start_mutex);
|
||||||
|
free(codec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pthread_mutex_init(&codec->data_mutex, NULL)) {
|
||||||
|
pthread_mutex_destroy(&codec->start_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->codec_mutex);
|
||||||
|
free(codec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pthread_cond_init(&codec->start_cond, NULL)) {
|
||||||
|
pthread_mutex_destroy(&codec->start_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->codec_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->data_mutex);
|
||||||
|
free(codec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pthread_cond_init(&codec->codec_cond, NULL)) {
|
||||||
|
pthread_mutex_destroy(&codec->start_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->codec_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->data_mutex);
|
||||||
|
pthread_cond_destroy(&codec->start_cond);
|
||||||
|
free(codec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pthread_cond_init(&codec->data_cond, NULL)) {
|
||||||
|
pthread_mutex_destroy(&codec->start_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->codec_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->data_mutex);
|
||||||
|
pthread_cond_destroy(&codec->start_cond);
|
||||||
|
pthread_cond_destroy(&codec->codec_cond);
|
||||||
|
free(codec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pthread_create(&codec->thread, NULL, codec_thread, codec)) {
|
||||||
|
pthread_mutex_destroy(&codec->start_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->codec_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->data_mutex);
|
||||||
|
pthread_cond_destroy(&codec->start_cond);
|
||||||
|
pthread_cond_destroy(&codec->codec_cond);
|
||||||
|
pthread_cond_destroy(&codec->data_cond);
|
||||||
|
free(codec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return codec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy an incremental codec */
|
||||||
|
void
|
||||||
|
ImagingIncrementalCodecDestroy(ImagingIncrementalCodec codec)
|
||||||
|
{
|
||||||
|
DEBUG("destroying\n");
|
||||||
|
|
||||||
|
if (!codec->started) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
ResumeThread(codec->hThread);
|
||||||
|
#else
|
||||||
|
pthread_cond_signal(&codec->start_cond);
|
||||||
|
#endif
|
||||||
|
codec->started = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_lock(&codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (codec->seekable && codec->stream.fd < 0)
|
||||||
|
free (codec->stream.buffer);
|
||||||
|
|
||||||
|
codec->stream.buffer = codec->stream.ptr = codec->stream.end
|
||||||
|
= codec->stream.top = NULL;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
SetEvent(codec->hDataEvent);
|
||||||
|
|
||||||
|
WaitForSingleObject(codec->hThread, INFINITE);
|
||||||
|
|
||||||
|
CloseHandle(codec->hThread);
|
||||||
|
CloseHandle(codec->hCodecEvent);
|
||||||
|
CloseHandle(codec->hDataEvent);
|
||||||
|
#else
|
||||||
|
pthread_cond_signal(&codec->data_cond);
|
||||||
|
pthread_mutex_unlock(&codec->data_mutex);
|
||||||
|
|
||||||
|
pthread_join(codec->thread, NULL);
|
||||||
|
|
||||||
|
pthread_mutex_destroy(&codec->start_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->codec_mutex);
|
||||||
|
pthread_mutex_destroy(&codec->data_mutex);
|
||||||
|
pthread_cond_destroy(&codec->start_cond);
|
||||||
|
pthread_cond_destroy(&codec->codec_cond);
|
||||||
|
pthread_cond_destroy(&codec->data_cond);
|
||||||
|
#endif
|
||||||
|
free (codec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push a data buffer for an incremental codec */
|
||||||
|
int
|
||||||
|
ImagingIncrementalCodecPushBuffer(ImagingIncrementalCodec codec,
|
||||||
|
UINT8 *buf, int bytes)
|
||||||
|
{
|
||||||
|
if (!codec->started) {
|
||||||
|
DEBUG("starting\n");
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
ResumeThread(codec->hThread);
|
||||||
|
#else
|
||||||
|
pthread_cond_signal(&codec->start_cond);
|
||||||
|
#endif
|
||||||
|
codec->started = 1;
|
||||||
|
|
||||||
|
/* Wait for the thread to ask for data */
|
||||||
|
#ifdef _WIN32
|
||||||
|
WaitForSingleObject(codec->hCodecEvent, INFINITE);
|
||||||
|
#else
|
||||||
|
pthread_mutex_lock(&codec->codec_mutex);
|
||||||
|
pthread_cond_wait(&codec->codec_cond, &codec->codec_mutex);
|
||||||
|
pthread_mutex_unlock(&codec->codec_mutex);
|
||||||
|
#endif
|
||||||
|
if (codec->result < 0) {
|
||||||
|
DEBUG("got result %d\n", codec->result);
|
||||||
|
|
||||||
|
return codec->result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Codecs using an fd don't need data, so when we get here, we're done */
|
||||||
|
if (codec->stream.fd >= 0) {
|
||||||
|
DEBUG("got result %d\n", codec->result);
|
||||||
|
|
||||||
|
return codec->result;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG("providing %p, %d\n", buf, bytes);
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_lock(&codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (codec->read_or_write == INCREMENTAL_CODEC_READ
|
||||||
|
&& codec->seekable && codec->stream.fd < 0) {
|
||||||
|
/* In this specific case, we append to a buffer we allocate ourselves */
|
||||||
|
size_t old_size = codec->stream.end - codec->stream.buffer;
|
||||||
|
size_t new_size = codec->stream.end - codec->stream.buffer + bytes;
|
||||||
|
UINT8 *new = (UINT8 *)realloc (codec->stream.buffer, new_size);
|
||||||
|
|
||||||
|
if (!new) {
|
||||||
|
codec->state->errcode = IMAGING_CODEC_MEMORY;
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_unlock(&codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
codec->stream.ptr = codec->stream.ptr - codec->stream.buffer + new;
|
||||||
|
codec->stream.end = new + new_size;
|
||||||
|
codec->stream.buffer = new;
|
||||||
|
|
||||||
|
memcpy(new + old_size, buf, bytes);
|
||||||
|
} else {
|
||||||
|
codec->stream.buffer = codec->stream.ptr = buf;
|
||||||
|
codec->stream.end = buf + bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
SetEvent(codec->hDataEvent);
|
||||||
|
WaitForSingleObject(codec->hCodecEvent, INFINITE);
|
||||||
|
#else
|
||||||
|
pthread_cond_signal(&codec->data_cond);
|
||||||
|
pthread_mutex_unlock(&codec->data_mutex);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&codec->codec_mutex);
|
||||||
|
pthread_cond_wait(&codec->codec_cond, &codec->codec_mutex);
|
||||||
|
pthread_mutex_unlock(&codec->codec_mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DEBUG("got result %d\n", codec->result);
|
||||||
|
|
||||||
|
return codec->result;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
ImagingIncrementalCodecBytesInBuffer(ImagingIncrementalCodec codec)
|
||||||
|
{
|
||||||
|
return codec->stream.ptr - codec->stream.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
ImagingIncrementalCodecRead(ImagingIncrementalCodec codec,
|
||||||
|
void *buffer, size_t bytes)
|
||||||
|
{
|
||||||
|
UINT8 *ptr = (UINT8 *)buffer;
|
||||||
|
size_t done = 0;
|
||||||
|
|
||||||
|
if (codec->read_or_write == INCREMENTAL_CODEC_WRITE) {
|
||||||
|
DEBUG("attempt to read from write codec\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG("reading (want %llu bytes)\n", (unsigned long long)bytes);
|
||||||
|
|
||||||
|
if (codec->stream.fd >= 0) {
|
||||||
|
ssize_t ret = read(codec->stream.fd, buffer, bytes);
|
||||||
|
DEBUG("read %lld bytes from fd\n", (long long)ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_lock(&codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
while (bytes) {
|
||||||
|
size_t todo = bytes;
|
||||||
|
size_t remaining = codec->stream.end - codec->stream.ptr;
|
||||||
|
|
||||||
|
if (!remaining) {
|
||||||
|
DEBUG("waiting for data\n");
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_lock(&codec->codec_mutex);
|
||||||
|
#endif
|
||||||
|
codec->result = (int)(codec->stream.ptr - codec->stream.buffer);
|
||||||
|
#if _WIN32
|
||||||
|
SetEvent(codec->hCodecEvent);
|
||||||
|
WaitForSingleObject(codec->hDataEvent, INFINITE);
|
||||||
|
#else
|
||||||
|
pthread_cond_signal(&codec->codec_cond);
|
||||||
|
pthread_mutex_unlock(&codec->codec_mutex);
|
||||||
|
pthread_cond_wait(&codec->data_cond, &codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
remaining = codec->stream.end - codec->stream.ptr;
|
||||||
|
codec->stream.top = codec->stream.end;
|
||||||
|
|
||||||
|
DEBUG("got %llu bytes\n", (unsigned long long)remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (todo > remaining)
|
||||||
|
todo = remaining;
|
||||||
|
|
||||||
|
if (!todo)
|
||||||
|
break;
|
||||||
|
|
||||||
|
memcpy (ptr, codec->stream.ptr, todo);
|
||||||
|
codec->stream.ptr += todo;
|
||||||
|
bytes -= todo;
|
||||||
|
done += todo;
|
||||||
|
ptr += todo;
|
||||||
|
}
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_unlock(&codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DEBUG("read total %llu bytes\n", (unsigned long long)done);
|
||||||
|
|
||||||
|
return done;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t
|
||||||
|
ImagingIncrementalCodecSkip(ImagingIncrementalCodec codec,
|
||||||
|
off_t bytes)
|
||||||
|
{
|
||||||
|
off_t done = 0;
|
||||||
|
|
||||||
|
DEBUG("skipping (want %llu bytes)\n", (unsigned long long)bytes);
|
||||||
|
|
||||||
|
/* In write mode, explicitly fill with zeroes */
|
||||||
|
if (codec->read_or_write == INCREMENTAL_CODEC_WRITE) {
|
||||||
|
static const UINT8 zeroes[256] = { 0 };
|
||||||
|
off_t done = 0;
|
||||||
|
while (bytes) {
|
||||||
|
size_t todo = (size_t)(bytes > 256 ? 256 : bytes);
|
||||||
|
ssize_t written = ImagingIncrementalCodecWrite(codec, zeroes, todo);
|
||||||
|
if (written <= 0)
|
||||||
|
break;
|
||||||
|
done += written;
|
||||||
|
bytes -= written;
|
||||||
|
}
|
||||||
|
return done;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codec->stream.fd >= 0)
|
||||||
|
return lseek(codec->stream.fd, bytes, SEEK_CUR);
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_lock(&codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
while (bytes) {
|
||||||
|
off_t todo = bytes;
|
||||||
|
off_t remaining = codec->stream.end - codec->stream.ptr;
|
||||||
|
|
||||||
|
if (!remaining) {
|
||||||
|
DEBUG("waiting for data\n");
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_lock(&codec->codec_mutex);
|
||||||
|
#endif
|
||||||
|
codec->result = (int)(codec->stream.ptr - codec->stream.buffer);
|
||||||
|
#if _WIN32
|
||||||
|
SetEvent(codec->hCodecEvent);
|
||||||
|
WaitForSingleObject(codec->hDataEvent, INFINITE);
|
||||||
|
#else
|
||||||
|
pthread_cond_signal(&codec->codec_cond);
|
||||||
|
pthread_mutex_unlock(&codec->codec_mutex);
|
||||||
|
pthread_cond_wait(&codec->data_cond, &codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
remaining = codec->stream.end - codec->stream.ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (todo > remaining)
|
||||||
|
todo = remaining;
|
||||||
|
|
||||||
|
if (!todo)
|
||||||
|
break;
|
||||||
|
|
||||||
|
codec->stream.ptr += todo;
|
||||||
|
bytes -= todo;
|
||||||
|
done += todo;
|
||||||
|
}
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_unlock(&codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DEBUG("skipped total %llu bytes\n", (unsigned long long)done);
|
||||||
|
|
||||||
|
return done;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
ImagingIncrementalCodecWrite(ImagingIncrementalCodec codec,
|
||||||
|
const void *buffer, size_t bytes)
|
||||||
|
{
|
||||||
|
const UINT8 *ptr = (const UINT8 *)buffer;
|
||||||
|
size_t done = 0;
|
||||||
|
|
||||||
|
if (codec->read_or_write == INCREMENTAL_CODEC_READ) {
|
||||||
|
DEBUG("attempt to write from read codec\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG("write (have %llu bytes)\n", (unsigned long long)bytes);
|
||||||
|
|
||||||
|
if (codec->stream.fd >= 0)
|
||||||
|
return write(codec->stream.fd, buffer, bytes);
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_lock(&codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
while (bytes) {
|
||||||
|
size_t todo = bytes;
|
||||||
|
size_t remaining = codec->stream.end - codec->stream.ptr;
|
||||||
|
|
||||||
|
if (!remaining) {
|
||||||
|
if (codec->seekable && codec->stream.fd < 0) {
|
||||||
|
/* In this case, we maintain the stream buffer ourselves */
|
||||||
|
size_t old_size = codec->stream.top - codec->stream.buffer;
|
||||||
|
size_t new_size = (old_size + bytes + 65535) & ~65535;
|
||||||
|
UINT8 *new = (UINT8 *)realloc(codec->stream.buffer, new_size);
|
||||||
|
|
||||||
|
if (!new) {
|
||||||
|
codec->state->errcode = IMAGING_CODEC_MEMORY;
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_unlock(&codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
return done == 0 ? -1 : done;
|
||||||
|
}
|
||||||
|
|
||||||
|
codec->stream.ptr = codec->stream.ptr - codec->stream.buffer + new;
|
||||||
|
codec->stream.buffer = new;
|
||||||
|
codec->stream.end = new + new_size;
|
||||||
|
codec->stream.top = new + old_size;
|
||||||
|
} else {
|
||||||
|
DEBUG("waiting for space\n");
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_lock(&codec->codec_mutex);
|
||||||
|
#endif
|
||||||
|
codec->result = (int)(codec->stream.ptr - codec->stream.buffer);
|
||||||
|
#if _WIN32
|
||||||
|
SetEvent(codec->hCodecEvent);
|
||||||
|
WaitForSingleObject(codec->hDataEvent, INFINITE);
|
||||||
|
#else
|
||||||
|
pthread_cond_signal(&codec->codec_cond);
|
||||||
|
pthread_mutex_unlock(&codec->codec_mutex);
|
||||||
|
pthread_cond_wait(&codec->data_cond, &codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
remaining = codec->stream.end - codec->stream.ptr;
|
||||||
|
|
||||||
|
DEBUG("got %llu bytes\n", (unsigned long long)remaining);
|
||||||
|
}
|
||||||
|
if (todo > remaining)
|
||||||
|
todo = remaining;
|
||||||
|
|
||||||
|
if (!todo)
|
||||||
|
break;
|
||||||
|
|
||||||
|
memcpy (codec->stream.ptr, ptr, todo);
|
||||||
|
codec->stream.ptr += todo;
|
||||||
|
bytes -= todo;
|
||||||
|
done += todo;
|
||||||
|
ptr += todo;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codec->stream.ptr > codec->stream.top)
|
||||||
|
codec->stream.top = codec->stream.ptr;
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
pthread_mutex_unlock(&codec->data_mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DEBUG("wrote total %llu bytes\n", (unsigned long long)done);
|
||||||
|
|
||||||
|
return done;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t
|
||||||
|
ImagingIncrementalCodecSeek(ImagingIncrementalCodec codec,
|
||||||
|
off_t bytes)
|
||||||
|
{
|
||||||
|
off_t buffered;
|
||||||
|
|
||||||
|
DEBUG("seeking (going to %llu bytes)\n", (unsigned long long)bytes);
|
||||||
|
|
||||||
|
if (codec->stream.fd >= 0)
|
||||||
|
return lseek(codec->stream.fd, bytes, SEEK_SET);
|
||||||
|
|
||||||
|
if (!codec->seekable) {
|
||||||
|
DEBUG("attempt to seek non-seekable stream\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes < 0) {
|
||||||
|
DEBUG("attempt to seek before stream start\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffered = codec->stream.top - codec->stream.buffer;
|
||||||
|
|
||||||
|
if (bytes <= buffered) {
|
||||||
|
DEBUG("seek within buffer\n");
|
||||||
|
codec->stream.ptr = codec->stream.buffer + bytes;
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffered + ImagingIncrementalCodecSkip(codec, bytes - buffered);
|
||||||
|
}
|
91
libImaging/Jpeg2K.h
Normal file
91
libImaging/Jpeg2K.h
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* The Python Imaging Library
|
||||||
|
* $Id$
|
||||||
|
*
|
||||||
|
* declarations for the OpenJPEG codec interface.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 by Coriolis Systems Limited
|
||||||
|
* Copyright (c) 2014 by Alastair Houghton
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <openjpeg-2.0/openjpeg.h>
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Decoder */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
/* CONFIGURATION */
|
||||||
|
|
||||||
|
/* File descriptor, if available; otherwise, -1 */
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
/* Specify the desired format */
|
||||||
|
OPJ_CODEC_FORMAT format;
|
||||||
|
|
||||||
|
/* Set to divide image resolution by 2**reduce. */
|
||||||
|
int reduce;
|
||||||
|
|
||||||
|
/* Set to limit the number of quality layers to decode (0 = all layers) */
|
||||||
|
int layers;
|
||||||
|
|
||||||
|
/* PRIVATE CONTEXT (set by decoder) */
|
||||||
|
const char *error_msg;
|
||||||
|
|
||||||
|
ImagingIncrementalCodec decoder;
|
||||||
|
} JPEG2KDECODESTATE;
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Encoder */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
/* CONFIGURATION */
|
||||||
|
|
||||||
|
/* File descriptor, if available; otherwise, -1 */
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
/* Specify the desired format */
|
||||||
|
OPJ_CODEC_FORMAT format;
|
||||||
|
|
||||||
|
/* Image offset */
|
||||||
|
int offset_x, offset_y;
|
||||||
|
|
||||||
|
/* Tile information */
|
||||||
|
int tile_offset_x, tile_offset_y;
|
||||||
|
int tile_size_x, tile_size_y;
|
||||||
|
|
||||||
|
/* Quality layers (a sequence of numbers giving *either* rates or dB) */
|
||||||
|
int quality_is_in_db;
|
||||||
|
PyObject *quality_layers;
|
||||||
|
|
||||||
|
/* Number of resolutions (DWT decompositions + 1 */
|
||||||
|
int num_resolutions;
|
||||||
|
|
||||||
|
/* Code block size */
|
||||||
|
int cblk_width, cblk_height;
|
||||||
|
|
||||||
|
/* Precinct size */
|
||||||
|
int precinct_width, precinct_height;
|
||||||
|
|
||||||
|
/* Compression style */
|
||||||
|
int irreversible;
|
||||||
|
|
||||||
|
/* Progression order (LRCP/RLCP/RPCL/PCRL/CPRL) */
|
||||||
|
OPJ_PROG_ORDER progression;
|
||||||
|
|
||||||
|
/* Cinema mode */
|
||||||
|
OPJ_CINEMA_MODE cinema_mode;
|
||||||
|
|
||||||
|
/* PRIVATE CONTEXT (set by decoder) */
|
||||||
|
const char *error_msg;
|
||||||
|
|
||||||
|
ImagingIncrementalCodec encoder;
|
||||||
|
} JPEG2KENCODESTATE;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Local Variables:
|
||||||
|
* c-basic-offset: 4
|
||||||
|
* End:
|
||||||
|
*
|
||||||
|
*/
|
753
libImaging/Jpeg2KDecode.c
Normal file
753
libImaging/Jpeg2KDecode.c
Normal file
|
@ -0,0 +1,753 @@
|
||||||
|
/*
|
||||||
|
* The Python Imaging Library.
|
||||||
|
* $Id$
|
||||||
|
*
|
||||||
|
* decoder for JPEG2000 image data.
|
||||||
|
*
|
||||||
|
* history:
|
||||||
|
* 2014-03-12 ajh Created
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Coriolis Systems Limited
|
||||||
|
* Copyright (c) 2014 Alastair Houghton
|
||||||
|
*
|
||||||
|
* See the README file for details on usage and redistribution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Imaging.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_OPENJPEG
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "Jpeg2K.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
OPJ_UINT32 tile_index;
|
||||||
|
OPJ_UINT32 data_size;
|
||||||
|
OPJ_INT32 x0, y0, x1, y1;
|
||||||
|
OPJ_UINT32 nb_comps;
|
||||||
|
} JPEG2KTILEINFO;
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Error handler */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2k_error(const char *msg, void *client_data)
|
||||||
|
{
|
||||||
|
JPEG2KDECODESTATE *state = (JPEG2KDECODESTATE *) client_data;
|
||||||
|
free((void *)state->error_msg);
|
||||||
|
state->error_msg = strdup(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Buffer input stream */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static OPJ_SIZE_T
|
||||||
|
j2k_read(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data)
|
||||||
|
{
|
||||||
|
ImagingIncrementalCodec decoder = (ImagingIncrementalCodec)p_user_data;
|
||||||
|
|
||||||
|
size_t len = ImagingIncrementalCodecRead(decoder, p_buffer, p_nb_bytes);
|
||||||
|
|
||||||
|
return len ? len : (OPJ_SIZE_T)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static OPJ_OFF_T
|
||||||
|
j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data)
|
||||||
|
{
|
||||||
|
ImagingIncrementalCodec decoder = (ImagingIncrementalCodec)p_user_data;
|
||||||
|
off_t pos = ImagingIncrementalCodecSkip(decoder, p_nb_bytes);
|
||||||
|
|
||||||
|
return pos ? pos : (OPJ_OFF_T)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Unpackers */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
typedef void (*j2k_unpacker_t)(opj_image_t *in,
|
||||||
|
const JPEG2KTILEINFO *tileInfo,
|
||||||
|
const UINT8 *data,
|
||||||
|
Imaging im);
|
||||||
|
|
||||||
|
struct j2k_decode_unpacker {
|
||||||
|
const char *mode;
|
||||||
|
OPJ_COLOR_SPACE color_space;
|
||||||
|
unsigned components;
|
||||||
|
j2k_unpacker_t unpacker;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline
|
||||||
|
unsigned j2ku_shift(unsigned x, int n)
|
||||||
|
{
|
||||||
|
if (n < 0)
|
||||||
|
return x >> -n;
|
||||||
|
else
|
||||||
|
return x << n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2ku_gray_l(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
|
||||||
|
const UINT8 *tiledata, Imaging im)
|
||||||
|
{
|
||||||
|
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
|
||||||
|
unsigned w = tileinfo->x1 - tileinfo->x0;
|
||||||
|
unsigned h = tileinfo->y1 - tileinfo->y0;
|
||||||
|
|
||||||
|
int shift = 8 - in->comps[0].prec;
|
||||||
|
int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0;
|
||||||
|
int csiz = (in->comps[0].prec + 7) >> 3;
|
||||||
|
|
||||||
|
unsigned x, y;
|
||||||
|
|
||||||
|
if (csiz == 3)
|
||||||
|
csiz = 4;
|
||||||
|
|
||||||
|
if (shift < 0)
|
||||||
|
offset += 1 << (-shift - 1);
|
||||||
|
|
||||||
|
switch (csiz) {
|
||||||
|
case 1:
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT8 *data = &tiledata[y * w];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
|
||||||
|
for (x = 0; x < w; ++x)
|
||||||
|
*row++ = j2ku_shift(offset + *data++, shift);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT16 *data = (const UINT16 *)&tiledata[2 * y * w];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
|
||||||
|
for (x = 0; x < w; ++x)
|
||||||
|
*row++ = j2ku_shift(offset + *data++, shift);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT32 *data = (const UINT32 *)&tiledata[4 * y * w];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
|
||||||
|
for (x = 0; x < w; ++x)
|
||||||
|
*row++ = j2ku_shift(offset + *data++, shift);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2ku_gray_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
|
||||||
|
const UINT8 *tiledata, Imaging im)
|
||||||
|
{
|
||||||
|
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
|
||||||
|
unsigned w = tileinfo->x1 - tileinfo->x0;
|
||||||
|
unsigned h = tileinfo->y1 - tileinfo->y0;
|
||||||
|
|
||||||
|
int shift = 8 - in->comps[0].prec;
|
||||||
|
int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0;
|
||||||
|
int csiz = (in->comps[0].prec + 7) >> 3;
|
||||||
|
|
||||||
|
unsigned x, y;
|
||||||
|
|
||||||
|
if (shift < 0)
|
||||||
|
offset += 1 << (-shift - 1);
|
||||||
|
|
||||||
|
if (csiz == 3)
|
||||||
|
csiz = 4;
|
||||||
|
|
||||||
|
switch (csiz) {
|
||||||
|
case 1:
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT8 *data = &tiledata[y * w];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
UINT8 byte = j2ku_shift(offset + *data++, shift);
|
||||||
|
row[0] = row[1] = row[2] = byte;
|
||||||
|
row[3] = 0xff;
|
||||||
|
row += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT16 *data = (UINT16 *)&tiledata[2 * y * w];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
UINT8 byte = j2ku_shift(offset + *data++, shift);
|
||||||
|
row[0] = row[1] = row[2] = byte;
|
||||||
|
row[3] = 0xff;
|
||||||
|
row += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT32 *data = (UINT32 *)&tiledata[4 * y * w];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0;
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
UINT8 byte = j2ku_shift(offset + *data++, shift);
|
||||||
|
row[0] = row[1] = row[2] = byte;
|
||||||
|
row[3] = 0xff;
|
||||||
|
row += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2ku_graya_la(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
|
||||||
|
const UINT8 *tiledata, Imaging im)
|
||||||
|
{
|
||||||
|
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
|
||||||
|
unsigned w = tileinfo->x1 - tileinfo->x0;
|
||||||
|
unsigned h = tileinfo->y1 - tileinfo->y0;
|
||||||
|
|
||||||
|
int shift = 8 - in->comps[0].prec;
|
||||||
|
int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0;
|
||||||
|
int csiz = (in->comps[0].prec + 7) >> 3;
|
||||||
|
int ashift = 8 - in->comps[1].prec;
|
||||||
|
int aoffset = in->comps[1].sgnd ? 1 << (in->comps[1].prec - 1) : 0;
|
||||||
|
int acsiz = (in->comps[1].prec + 7) >> 3;
|
||||||
|
const UINT8 *atiledata;
|
||||||
|
|
||||||
|
unsigned x, y;
|
||||||
|
|
||||||
|
if (csiz == 3)
|
||||||
|
csiz = 4;
|
||||||
|
if (acsiz == 3)
|
||||||
|
acsiz = 4;
|
||||||
|
|
||||||
|
if (shift < 0)
|
||||||
|
offset += 1 << (-shift - 1);
|
||||||
|
if (ashift < 0)
|
||||||
|
aoffset += 1 << (-ashift - 1);
|
||||||
|
|
||||||
|
atiledata = tiledata + csiz * w * h;
|
||||||
|
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT8 *data = &tiledata[csiz * y * w];
|
||||||
|
const UINT8 *adata = &atiledata[acsiz * y * w];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
UINT32 word = 0, aword = 0, byte;
|
||||||
|
|
||||||
|
switch (csiz) {
|
||||||
|
case 1: word = *data++; break;
|
||||||
|
case 2: word = *(const UINT16 *)data; data += 2; break;
|
||||||
|
case 4: word = *(const UINT32 *)data; data += 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (acsiz) {
|
||||||
|
case 1: aword = *adata++; break;
|
||||||
|
case 2: aword = *(const UINT16 *)adata; adata += 2; break;
|
||||||
|
case 4: aword = *(const UINT32 *)adata; adata += 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte = j2ku_shift(offset + word, shift);
|
||||||
|
row[0] = row[1] = row[2] = byte;
|
||||||
|
row[3] = j2ku_shift(aoffset + aword, ashift);
|
||||||
|
row += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2ku_srgb_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
|
||||||
|
const UINT8 *tiledata, Imaging im)
|
||||||
|
{
|
||||||
|
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
|
||||||
|
unsigned w = tileinfo->x1 - tileinfo->x0;
|
||||||
|
unsigned h = tileinfo->y1 - tileinfo->y0;
|
||||||
|
|
||||||
|
int shifts[3], offsets[3], csiz[3];
|
||||||
|
const UINT8 *cdata[3];
|
||||||
|
const UINT8 *cptr = tiledata;
|
||||||
|
unsigned n, x, y;
|
||||||
|
|
||||||
|
for (n = 0; n < 3; ++n) {
|
||||||
|
cdata[n] = cptr;
|
||||||
|
shifts[n] = 8 - in->comps[n].prec;
|
||||||
|
offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0;
|
||||||
|
csiz[n] = (in->comps[n].prec + 7) >> 3;
|
||||||
|
|
||||||
|
if (csiz[n] == 3)
|
||||||
|
csiz[n] = 4;
|
||||||
|
|
||||||
|
if (shifts[n] < 0)
|
||||||
|
offsets[n] += 1 << (-shifts[n] - 1);
|
||||||
|
|
||||||
|
cptr += csiz[n] * w * h;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT8 *data[3];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
|
||||||
|
for (n = 0; n < 3; ++n)
|
||||||
|
data[n] = &cdata[n][csiz[n] * y * w];
|
||||||
|
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
for (n = 0; n < 3; ++n) {
|
||||||
|
UINT32 word = 0;
|
||||||
|
|
||||||
|
switch (csiz[n]) {
|
||||||
|
case 1: word = *data[n]++; break;
|
||||||
|
case 2: word = *(const UINT16 *)data[n]; data[n] += 2; break;
|
||||||
|
case 4: word = *(const UINT32 *)data[n]; data[n] += 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
row[n] = j2ku_shift(offsets[n] + word, shifts[n]);
|
||||||
|
}
|
||||||
|
row[3] = 0xff;
|
||||||
|
row += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2ku_sycc_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
|
||||||
|
const UINT8 *tiledata, Imaging im)
|
||||||
|
{
|
||||||
|
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
|
||||||
|
unsigned w = tileinfo->x1 - tileinfo->x0;
|
||||||
|
unsigned h = tileinfo->y1 - tileinfo->y0;
|
||||||
|
|
||||||
|
int shifts[3], offsets[3], csiz[3];
|
||||||
|
const UINT8 *cdata[3];
|
||||||
|
const UINT8 *cptr = tiledata;
|
||||||
|
unsigned n, x, y;
|
||||||
|
|
||||||
|
for (n = 0; n < 3; ++n) {
|
||||||
|
cdata[n] = cptr;
|
||||||
|
shifts[n] = 8 - in->comps[n].prec;
|
||||||
|
offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0;
|
||||||
|
csiz[n] = (in->comps[n].prec + 7) >> 3;
|
||||||
|
|
||||||
|
if (csiz[n] == 3)
|
||||||
|
csiz[n] = 4;
|
||||||
|
|
||||||
|
if (shifts[n] < 0)
|
||||||
|
offsets[n] += 1 << (-shifts[n] - 1);
|
||||||
|
|
||||||
|
cptr += csiz[n] * w * h;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT8 *data[3];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
|
||||||
|
UINT8 *row_start = row;
|
||||||
|
for (n = 0; n < 3; ++n)
|
||||||
|
data[n] = &cdata[n][csiz[n] * y * w];
|
||||||
|
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
for (n = 0; n < 3; ++n) {
|
||||||
|
UINT32 word = 0;
|
||||||
|
|
||||||
|
switch (csiz[n]) {
|
||||||
|
case 1: word = *data[n]++; break;
|
||||||
|
case 2: word = *(const UINT16 *)data[n]; data[n] += 2; break;
|
||||||
|
case 4: word = *(const UINT32 *)data[n]; data[n] += 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
row[n] = j2ku_shift(offsets[n] + word, shifts[n]);
|
||||||
|
}
|
||||||
|
row[3] = 0xff;
|
||||||
|
row += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImagingConvertYCbCr2RGB(row_start, row_start, w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2ku_srgba_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
|
||||||
|
const UINT8 *tiledata, Imaging im)
|
||||||
|
{
|
||||||
|
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
|
||||||
|
unsigned w = tileinfo->x1 - tileinfo->x0;
|
||||||
|
unsigned h = tileinfo->y1 - tileinfo->y0;
|
||||||
|
|
||||||
|
int shifts[4], offsets[4], csiz[4];
|
||||||
|
const UINT8 *cdata[4];
|
||||||
|
const UINT8 *cptr = tiledata;
|
||||||
|
unsigned n, x, y;
|
||||||
|
|
||||||
|
for (n = 0; n < 4; ++n) {
|
||||||
|
cdata[n] = cptr;
|
||||||
|
shifts[n] = 8 - in->comps[n].prec;
|
||||||
|
offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0;
|
||||||
|
csiz[n] = (in->comps[n].prec + 7) >> 3;
|
||||||
|
|
||||||
|
if (csiz[n] == 3)
|
||||||
|
csiz[n] = 4;
|
||||||
|
|
||||||
|
if (shifts[n] < 0)
|
||||||
|
offsets[n] += 1 << (-shifts[n] - 1);
|
||||||
|
|
||||||
|
cptr += csiz[n] * w * h;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT8 *data[4];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
|
||||||
|
for (n = 0; n < 4; ++n)
|
||||||
|
data[n] = &cdata[n][csiz[n] * y * w];
|
||||||
|
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
for (n = 0; n < 4; ++n) {
|
||||||
|
UINT32 word = 0;
|
||||||
|
|
||||||
|
switch (csiz[n]) {
|
||||||
|
case 1: word = *data[n]++; break;
|
||||||
|
case 2: word = *(const UINT16 *)data[n]; data[n] += 2; break;
|
||||||
|
case 4: word = *(const UINT32 *)data[n]; data[n] += 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
row[n] = j2ku_shift(offsets[n] + word, shifts[n]);
|
||||||
|
}
|
||||||
|
row += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2ku_sycca_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
|
||||||
|
const UINT8 *tiledata, Imaging im)
|
||||||
|
{
|
||||||
|
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
|
||||||
|
unsigned w = tileinfo->x1 - tileinfo->x0;
|
||||||
|
unsigned h = tileinfo->y1 - tileinfo->y0;
|
||||||
|
|
||||||
|
int shifts[4], offsets[4], csiz[4];
|
||||||
|
const UINT8 *cdata[4];
|
||||||
|
const UINT8 *cptr = tiledata;
|
||||||
|
unsigned n, x, y;
|
||||||
|
|
||||||
|
for (n = 0; n < 4; ++n) {
|
||||||
|
cdata[n] = cptr;
|
||||||
|
shifts[n] = 8 - in->comps[n].prec;
|
||||||
|
offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0;
|
||||||
|
csiz[n] = (in->comps[n].prec + 7) >> 3;
|
||||||
|
|
||||||
|
if (csiz[n] == 3)
|
||||||
|
csiz[n] = 4;
|
||||||
|
|
||||||
|
if (shifts[n] < 0)
|
||||||
|
offsets[n] += 1 << (-shifts[n] - 1);
|
||||||
|
|
||||||
|
cptr += csiz[n] * w * h;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const UINT8 *data[4];
|
||||||
|
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
|
||||||
|
UINT8 *row_start = row;
|
||||||
|
for (n = 0; n < 4; ++n)
|
||||||
|
data[n] = &cdata[n][csiz[n] * y * w];
|
||||||
|
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
for (n = 0; n < 4; ++n) {
|
||||||
|
UINT32 word = 0;
|
||||||
|
|
||||||
|
switch (csiz[n]) {
|
||||||
|
case 1: word = *data[n]++; break;
|
||||||
|
case 2: word = *(const UINT16 *)data[n]; data[n] += 2; break;
|
||||||
|
case 4: word = *(const UINT32 *)data[n]; data[n] += 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
row[n] = j2ku_shift(offsets[n] + word, shifts[n]);
|
||||||
|
}
|
||||||
|
row += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImagingConvertYCbCr2RGB(row_start, row_start, w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct j2k_decode_unpacker j2k_unpackers[] = {
|
||||||
|
{ "L", OPJ_CLRSPC_GRAY, 1, j2ku_gray_l },
|
||||||
|
{ "LA", OPJ_CLRSPC_GRAY, 2, j2ku_graya_la },
|
||||||
|
{ "RGB", OPJ_CLRSPC_GRAY, 1, j2ku_gray_rgb },
|
||||||
|
{ "RGB", OPJ_CLRSPC_GRAY, 2, j2ku_gray_rgb },
|
||||||
|
{ "RGB", OPJ_CLRSPC_SRGB, 3, j2ku_srgb_rgb },
|
||||||
|
{ "RGB", OPJ_CLRSPC_SYCC, 3, j2ku_sycc_rgb },
|
||||||
|
{ "RGB", OPJ_CLRSPC_SRGB, 4, j2ku_srgb_rgb },
|
||||||
|
{ "RGB", OPJ_CLRSPC_SYCC, 4, j2ku_sycc_rgb },
|
||||||
|
{ "RGBA", OPJ_CLRSPC_GRAY, 1, j2ku_gray_rgb },
|
||||||
|
{ "RGBA", OPJ_CLRSPC_GRAY, 2, j2ku_graya_la },
|
||||||
|
{ "RGBA", OPJ_CLRSPC_SRGB, 3, j2ku_srgb_rgb },
|
||||||
|
{ "RGBA", OPJ_CLRSPC_SYCC, 3, j2ku_sycc_rgb },
|
||||||
|
{ "RGBA", OPJ_CLRSPC_SRGB, 4, j2ku_srgba_rgba },
|
||||||
|
{ "RGBA", OPJ_CLRSPC_SYCC, 4, j2ku_sycca_rgba },
|
||||||
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Decoder */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
enum {
|
||||||
|
J2K_STATE_START = 0,
|
||||||
|
J2K_STATE_DECODING = 1,
|
||||||
|
J2K_STATE_DONE = 2,
|
||||||
|
J2K_STATE_FAILED = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
j2k_decode_entry(Imaging im, ImagingCodecState state,
|
||||||
|
ImagingIncrementalCodec decoder)
|
||||||
|
{
|
||||||
|
JPEG2KDECODESTATE *context = (JPEG2KDECODESTATE *) state->context;
|
||||||
|
opj_stream_t *stream = NULL;
|
||||||
|
opj_image_t *image = NULL;
|
||||||
|
opj_codec_t *codec = NULL;
|
||||||
|
opj_dparameters_t params;
|
||||||
|
OPJ_COLOR_SPACE color_space;
|
||||||
|
j2k_unpacker_t unpack = NULL;
|
||||||
|
size_t buffer_size = 0;
|
||||||
|
unsigned n;
|
||||||
|
|
||||||
|
stream = opj_stream_default_create(OPJ_TRUE);
|
||||||
|
|
||||||
|
if (!stream) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
opj_stream_set_read_function(stream, j2k_read);
|
||||||
|
opj_stream_set_skip_function(stream, j2k_skip);
|
||||||
|
|
||||||
|
opj_stream_set_user_data(stream, decoder);
|
||||||
|
|
||||||
|
/* Setup decompression context */
|
||||||
|
context->error_msg = NULL;
|
||||||
|
|
||||||
|
opj_set_default_decoder_parameters(¶ms);
|
||||||
|
params.cp_reduce = context->reduce;
|
||||||
|
params.cp_layer = context->layers;
|
||||||
|
|
||||||
|
codec = opj_create_decompress(context->format);
|
||||||
|
|
||||||
|
if (!codec) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
opj_set_error_handler(codec, j2k_error, context);
|
||||||
|
opj_setup_decoder(codec, ¶ms);
|
||||||
|
|
||||||
|
if (!opj_read_header(stream, codec, &image)) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check that this image is something we can handle */
|
||||||
|
if (image->numcomps < 1 || image->numcomps > 4
|
||||||
|
|| image->color_space == OPJ_CLRSPC_UNKNOWN) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (n = 1; n < image->numcomps; ++n) {
|
||||||
|
if (image->comps[n].dx != 1 || image->comps[n].dy != 1) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Colorspace Number of components PIL mode
|
||||||
|
------------------------------------------------------
|
||||||
|
sRGB 3 RGB
|
||||||
|
sRGB 4 RGBA
|
||||||
|
gray 1 L or I
|
||||||
|
gray 2 LA
|
||||||
|
YCC 3 YCbCr
|
||||||
|
|
||||||
|
|
||||||
|
If colorspace is unspecified, we assume:
|
||||||
|
|
||||||
|
Number of components Colorspace
|
||||||
|
-----------------------------------------
|
||||||
|
1 gray
|
||||||
|
2 gray (+ alpha)
|
||||||
|
3 sRGB
|
||||||
|
4 sRGB (+ alpha)
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Find the correct unpacker */
|
||||||
|
color_space = image->color_space;
|
||||||
|
|
||||||
|
if (color_space == OPJ_CLRSPC_UNSPECIFIED) {
|
||||||
|
switch (image->numcomps) {
|
||||||
|
case 1: case 2: color_space = OPJ_CLRSPC_GRAY; break;
|
||||||
|
case 3: case 4: color_space = OPJ_CLRSPC_SRGB; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (n = 0; n < sizeof(j2k_unpackers) / sizeof (j2k_unpackers[0]); ++n) {
|
||||||
|
if (color_space == j2k_unpackers[n].color_space
|
||||||
|
&& image->numcomps == j2k_unpackers[n].components
|
||||||
|
&& strcmp (im->mode, j2k_unpackers[n].mode) == 0) {
|
||||||
|
unpack = j2k_unpackers[n].unpacker;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!unpack) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Decode the image tile-by-tile; this means we only need use as much
|
||||||
|
memory as is required for one tile's worth of components. */
|
||||||
|
for (;;) {
|
||||||
|
JPEG2KTILEINFO tile_info;
|
||||||
|
OPJ_BOOL should_continue;
|
||||||
|
unsigned correction = (1 << params.cp_reduce) - 1;
|
||||||
|
|
||||||
|
if (!opj_read_tile_header(codec,
|
||||||
|
stream,
|
||||||
|
&tile_info.tile_index,
|
||||||
|
&tile_info.data_size,
|
||||||
|
&tile_info.x0, &tile_info.y0,
|
||||||
|
&tile_info.x1, &tile_info.y1,
|
||||||
|
&tile_info.nb_comps,
|
||||||
|
&should_continue)) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!should_continue)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Adjust the tile co-ordinates based on the reduction (OpenJPEG
|
||||||
|
doesn't do this for us) */
|
||||||
|
tile_info.x0 = (tile_info.x0 + correction) >> context->reduce;
|
||||||
|
tile_info.y0 = (tile_info.y0 + correction) >> context->reduce;
|
||||||
|
tile_info.x1 = (tile_info.x1 + correction) >> context->reduce;
|
||||||
|
tile_info.y1 = (tile_info.y1 + correction) >> context->reduce;
|
||||||
|
|
||||||
|
if (buffer_size < tile_info.data_size) {
|
||||||
|
UINT8 *new = realloc (state->buffer, tile_info.data_size);
|
||||||
|
if (!new) {
|
||||||
|
state->errcode = IMAGING_CODEC_MEMORY;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
state->buffer = new;
|
||||||
|
buffer_size = tile_info.data_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!opj_decode_tile_data(codec,
|
||||||
|
tile_info.tile_index,
|
||||||
|
(OPJ_BYTE *)state->buffer,
|
||||||
|
tile_info.data_size,
|
||||||
|
stream)) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check the tile bounds; if the tile is outside the image area,
|
||||||
|
or if it has a negative width or height (i.e. the coordinates are
|
||||||
|
swapped), bail. */
|
||||||
|
if (tile_info.x0 >= tile_info.x1
|
||||||
|
|| tile_info.y0 >= tile_info.y1
|
||||||
|
|| tile_info.x0 < image->x0
|
||||||
|
|| tile_info.y0 < image->y0
|
||||||
|
|| tile_info.x1 - image->x0 > im->xsize
|
||||||
|
|| tile_info.y1 - image->y0 > im->ysize) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
unpack(image, &tile_info, state->buffer, im);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!opj_end_decompress(codec, stream)) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->state = J2K_STATE_DONE;
|
||||||
|
state->errcode = IMAGING_CODEC_END;
|
||||||
|
|
||||||
|
quick_exit:
|
||||||
|
if (codec)
|
||||||
|
opj_destroy_codec(codec);
|
||||||
|
if (image)
|
||||||
|
opj_image_destroy(image);
|
||||||
|
if (stream)
|
||||||
|
opj_stream_destroy(stream);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ImagingJpeg2KDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes)
|
||||||
|
{
|
||||||
|
JPEG2KDECODESTATE *context = (JPEG2KDECODESTATE *) state->context;
|
||||||
|
|
||||||
|
if (state->state == J2K_STATE_DONE || state->state == J2K_STATE_FAILED)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (state->state == J2K_STATE_START) {
|
||||||
|
context->decoder = ImagingIncrementalCodecCreate(j2k_decode_entry,
|
||||||
|
im, state,
|
||||||
|
INCREMENTAL_CODEC_READ,
|
||||||
|
INCREMENTAL_CODEC_NOT_SEEKABLE,
|
||||||
|
context->fd);
|
||||||
|
|
||||||
|
if (!context->decoder) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->state = J2K_STATE_DECODING;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ImagingIncrementalCodecPushBuffer(context->decoder, buf, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Cleanup */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
int
|
||||||
|
ImagingJpeg2KDecodeCleanup(ImagingCodecState state) {
|
||||||
|
JPEG2KDECODESTATE *context = (JPEG2KDECODESTATE *)state->context;
|
||||||
|
|
||||||
|
if (context->error_msg)
|
||||||
|
free ((void *)context->error_msg);
|
||||||
|
|
||||||
|
if (context->decoder)
|
||||||
|
ImagingIncrementalCodecDestroy(context->decoder);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
ImagingJpeg2KVersion(void)
|
||||||
|
{
|
||||||
|
return opj_version();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* HAVE_OPENJPEG */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Local Variables:
|
||||||
|
* c-basic-offset: 4
|
||||||
|
* End:
|
||||||
|
*
|
||||||
|
*/
|
562
libImaging/Jpeg2KEncode.c
Normal file
562
libImaging/Jpeg2KEncode.c
Normal file
|
@ -0,0 +1,562 @@
|
||||||
|
/*
|
||||||
|
* The Python Imaging Library.
|
||||||
|
* $Id$
|
||||||
|
*
|
||||||
|
* decoder for JPEG2000 image data.
|
||||||
|
*
|
||||||
|
* history:
|
||||||
|
* 2014-03-12 ajh Created
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Coriolis Systems Limited
|
||||||
|
* Copyright (c) 2014 Alastair Houghton
|
||||||
|
*
|
||||||
|
* See the README file for details on usage and redistribution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Imaging.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_OPENJPEG
|
||||||
|
|
||||||
|
#include "Jpeg2K.h"
|
||||||
|
|
||||||
|
#define CINEMA_24_CS_LENGTH 1302083
|
||||||
|
#define CINEMA_48_CS_LENGTH 651041
|
||||||
|
#define COMP_24_CS_MAX_LENGTH 1041666
|
||||||
|
#define COMP_48_CS_MAX_LENGTH 520833
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Error handler */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2k_error(const char *msg, void *client_data)
|
||||||
|
{
|
||||||
|
JPEG2KENCODESTATE *state = (JPEG2KENCODESTATE *) client_data;
|
||||||
|
free((void *)state->error_msg);
|
||||||
|
state->error_msg = strdup(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Buffer output stream */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static OPJ_SIZE_T
|
||||||
|
j2k_write(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data)
|
||||||
|
{
|
||||||
|
ImagingIncrementalCodec encoder = (ImagingIncrementalCodec)p_user_data;
|
||||||
|
size_t len = ImagingIncrementalCodecWrite(encoder, p_buffer, p_nb_bytes);
|
||||||
|
|
||||||
|
return len ? len : (OPJ_SIZE_T)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static OPJ_OFF_T
|
||||||
|
j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data)
|
||||||
|
{
|
||||||
|
ImagingIncrementalCodec encoder = (ImagingIncrementalCodec)p_user_data;
|
||||||
|
off_t pos = ImagingIncrementalCodecSkip(encoder, p_nb_bytes);
|
||||||
|
|
||||||
|
return pos ? pos : (OPJ_OFF_T)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static OPJ_BOOL
|
||||||
|
j2k_seek(OPJ_OFF_T p_nb_bytes, void *p_user_data)
|
||||||
|
{
|
||||||
|
ImagingIncrementalCodec encoder = (ImagingIncrementalCodec)p_user_data;
|
||||||
|
off_t pos = ImagingIncrementalCodecSeek(encoder, p_nb_bytes);
|
||||||
|
|
||||||
|
return pos == p_nb_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Encoder */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
typedef void (*j2k_pack_tile_t)(Imaging im, UINT8 *buf,
|
||||||
|
unsigned x0, unsigned y0,
|
||||||
|
unsigned w, unsigned h);
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2k_pack_l(Imaging im, UINT8 *buf,
|
||||||
|
unsigned x0, unsigned y0, unsigned w, unsigned h)
|
||||||
|
{
|
||||||
|
UINT8 *ptr = buf;
|
||||||
|
unsigned x,y;
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
UINT8 *data = (UINT8 *)(im->image[y + y0] + x0);
|
||||||
|
for (x = 0; x < w; ++x)
|
||||||
|
*ptr++ = *data++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2k_pack_la(Imaging im, UINT8 *buf,
|
||||||
|
unsigned x0, unsigned y0, unsigned w, unsigned h)
|
||||||
|
{
|
||||||
|
UINT8 *ptr = buf;
|
||||||
|
UINT8 *ptra = buf + w * h;
|
||||||
|
unsigned x,y;
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
UINT8 *data = (UINT8 *)(im->image[y + y0] + 4 * x0);
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
*ptr++ = data[0];
|
||||||
|
*ptra++ = data[3];
|
||||||
|
data += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2k_pack_rgb(Imaging im, UINT8 *buf,
|
||||||
|
unsigned x0, unsigned y0, unsigned w, unsigned h)
|
||||||
|
{
|
||||||
|
UINT8 *pr = buf;
|
||||||
|
UINT8 *pg = pr + w * h;
|
||||||
|
UINT8 *pb = pg + w * h;
|
||||||
|
unsigned x,y;
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
UINT8 *data = (UINT8 *)(im->image[y + y0] + 4 * x0);
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
*pr++ = data[0];
|
||||||
|
*pg++ = data[1];
|
||||||
|
*pb++ = data[2];
|
||||||
|
data += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2k_pack_rgba(Imaging im, UINT8 *buf,
|
||||||
|
unsigned x0, unsigned y0, unsigned w, unsigned h)
|
||||||
|
{
|
||||||
|
UINT8 *pr = buf;
|
||||||
|
UINT8 *pg = pr + w * h;
|
||||||
|
UINT8 *pb = pg + w * h;
|
||||||
|
UINT8 *pa = pb + w * h;
|
||||||
|
unsigned x,y;
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
UINT8 *data = (UINT8 *)(im->image[y + y0] + 4 * x0);
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
*pr++ = *data++;
|
||||||
|
*pg++ = *data++;
|
||||||
|
*pb++ = *data++;
|
||||||
|
*pa++ = *data++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum {
|
||||||
|
J2K_STATE_START = 0,
|
||||||
|
J2K_STATE_ENCODING = 1,
|
||||||
|
J2K_STATE_DONE = 2,
|
||||||
|
J2K_STATE_FAILED = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
j2k_set_cinema_params(Imaging im, int components, opj_cparameters_t *params)
|
||||||
|
{
|
||||||
|
float rate;
|
||||||
|
unsigned n;
|
||||||
|
|
||||||
|
/* These settings have been copied from opj_compress in the OpenJPEG
|
||||||
|
sources. */
|
||||||
|
|
||||||
|
params->tile_size_on = OPJ_FALSE;
|
||||||
|
params->cp_tdx = params->cp_tdy = 1;
|
||||||
|
params->tp_flag = 'C';
|
||||||
|
params->tp_on = 1;
|
||||||
|
params->cp_tx0 = params->cp_ty0 = 0;
|
||||||
|
params->image_offset_x0 = params->image_offset_y0 = 0;
|
||||||
|
params->cblockw_init = 32;
|
||||||
|
params->cblockh_init = 32;
|
||||||
|
params->csty |= 0x01;
|
||||||
|
params->prog_order = OPJ_CPRL;
|
||||||
|
params->roi_compno = -1;
|
||||||
|
params->subsampling_dx = params->subsampling_dy = 1;
|
||||||
|
params->irreversible = 1;
|
||||||
|
|
||||||
|
if (params->cp_cinema == OPJ_CINEMA4K_24) {
|
||||||
|
float max_rate = ((float)(components * im->xsize * im->ysize * 8)
|
||||||
|
/ (CINEMA_24_CS_LENGTH * 8));
|
||||||
|
|
||||||
|
params->POC[0].tile = 1;
|
||||||
|
params->POC[0].resno0 = 0;
|
||||||
|
params->POC[0].compno0 = 0;
|
||||||
|
params->POC[0].layno1 = 1;
|
||||||
|
params->POC[0].resno1 = params->numresolution - 1;
|
||||||
|
params->POC[0].compno1 = 3;
|
||||||
|
params->POC[0].prg1 = OPJ_CPRL;
|
||||||
|
params->POC[1].tile = 1;
|
||||||
|
params->POC[1].resno0 = 0;
|
||||||
|
params->POC[1].compno0 = 0;
|
||||||
|
params->POC[1].layno1 = 1;
|
||||||
|
params->POC[1].resno1 = params->numresolution - 1;
|
||||||
|
params->POC[1].compno1 = 3;
|
||||||
|
params->POC[1].prg1 = OPJ_CPRL;
|
||||||
|
params->numpocs = 2;
|
||||||
|
|
||||||
|
for (n = 0; n < params->tcp_numlayers; ++n) {
|
||||||
|
rate = 0;
|
||||||
|
if (params->tcp_rates[0] == 0) {
|
||||||
|
params->tcp_rates[n] = max_rate;
|
||||||
|
} else {
|
||||||
|
rate = ((float)(components * im->xsize * im->ysize * 8)
|
||||||
|
/ (params->tcp_rates[n] * 8));
|
||||||
|
if (rate > CINEMA_24_CS_LENGTH)
|
||||||
|
params->tcp_rates[n] = max_rate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params->max_comp_size = COMP_24_CS_MAX_LENGTH;
|
||||||
|
} else {
|
||||||
|
float max_rate = ((float)(components * im->xsize * im->ysize * 8)
|
||||||
|
/ (CINEMA_48_CS_LENGTH * 8));
|
||||||
|
|
||||||
|
for (n = 0; n < params->tcp_numlayers; ++n) {
|
||||||
|
rate = 0;
|
||||||
|
if (params->tcp_rates[0] == 0) {
|
||||||
|
params->tcp_rates[n] = max_rate;
|
||||||
|
} else {
|
||||||
|
rate = ((float)(components * im->xsize * im->ysize * 8)
|
||||||
|
/ (params->tcp_rates[n] * 8));
|
||||||
|
if (rate > CINEMA_48_CS_LENGTH)
|
||||||
|
params->tcp_rates[n] = max_rate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params->max_comp_size = COMP_48_CS_MAX_LENGTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
j2k_encode_entry(Imaging im, ImagingCodecState state,
|
||||||
|
ImagingIncrementalCodec encoder)
|
||||||
|
{
|
||||||
|
JPEG2KENCODESTATE *context = (JPEG2KENCODESTATE *)state->context;
|
||||||
|
opj_stream_t *stream = NULL;
|
||||||
|
opj_image_t *image = NULL;
|
||||||
|
opj_codec_t *codec = NULL;
|
||||||
|
opj_cparameters_t params;
|
||||||
|
unsigned components;
|
||||||
|
OPJ_COLOR_SPACE color_space;
|
||||||
|
opj_image_cmptparm_t image_params[4];
|
||||||
|
unsigned xsiz, ysiz;
|
||||||
|
unsigned tile_width, tile_height;
|
||||||
|
unsigned tiles_x, tiles_y, num_tiles;
|
||||||
|
unsigned x, y, tile_ndx;
|
||||||
|
unsigned n;
|
||||||
|
j2k_pack_tile_t pack;
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
stream = opj_stream_default_create(OPJ_FALSE);
|
||||||
|
|
||||||
|
if (!stream) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
opj_stream_set_write_function(stream, j2k_write);
|
||||||
|
opj_stream_set_skip_function(stream, j2k_skip);
|
||||||
|
opj_stream_set_seek_function(stream, j2k_seek);
|
||||||
|
|
||||||
|
opj_stream_set_user_data(stream, encoder);
|
||||||
|
|
||||||
|
/* Setup an opj_image */
|
||||||
|
if (strcmp (im->mode, "L") == 0) {
|
||||||
|
components = 1;
|
||||||
|
color_space = OPJ_CLRSPC_GRAY;
|
||||||
|
pack = j2k_pack_l;
|
||||||
|
} else if (strcmp (im->mode, "LA") == 0) {
|
||||||
|
components = 2;
|
||||||
|
color_space = OPJ_CLRSPC_GRAY;
|
||||||
|
pack = j2k_pack_la;
|
||||||
|
} else if (strcmp (im->mode, "RGB") == 0) {
|
||||||
|
components = 3;
|
||||||
|
color_space = OPJ_CLRSPC_SRGB;
|
||||||
|
pack = j2k_pack_rgb;
|
||||||
|
} else if (strcmp (im->mode, "YCbCr") == 0) {
|
||||||
|
components = 3;
|
||||||
|
color_space = OPJ_CLRSPC_SYCC;
|
||||||
|
pack = j2k_pack_rgb;
|
||||||
|
} else if (strcmp (im->mode, "RGBA") == 0) {
|
||||||
|
components = 4;
|
||||||
|
color_space = OPJ_CLRSPC_SRGB;
|
||||||
|
pack = j2k_pack_rgba;
|
||||||
|
} else {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (n = 0; n < components; ++n) {
|
||||||
|
image_params[n].dx = image_params[n].dy = 1;
|
||||||
|
image_params[n].w = im->xsize;
|
||||||
|
image_params[n].h = im->ysize;
|
||||||
|
image_params[n].x0 = image_params[n].y0 = 0;
|
||||||
|
image_params[n].prec = 8;
|
||||||
|
image_params[n].bpp = 8;
|
||||||
|
image_params[n].sgnd = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
image = opj_image_create(components, image_params, color_space);
|
||||||
|
|
||||||
|
/* Setup compression context */
|
||||||
|
context->error_msg = NULL;
|
||||||
|
|
||||||
|
opj_set_default_encoder_parameters(¶ms);
|
||||||
|
|
||||||
|
params.image_offset_x0 = context->offset_x;
|
||||||
|
params.image_offset_y0 = context->offset_y;
|
||||||
|
|
||||||
|
if (context->tile_size_x && context->tile_size_y) {
|
||||||
|
params.tile_size_on = OPJ_TRUE;
|
||||||
|
params.cp_tx0 = context->tile_offset_x;
|
||||||
|
params.cp_ty0 = context->tile_offset_y;
|
||||||
|
params.cp_tdx = context->tile_size_x;
|
||||||
|
params.cp_tdy = context->tile_size_y;
|
||||||
|
|
||||||
|
tile_width = params.cp_tdx;
|
||||||
|
tile_height = params.cp_tdy;
|
||||||
|
} else {
|
||||||
|
params.cp_tx0 = 0;
|
||||||
|
params.cp_ty0 = 0;
|
||||||
|
params.cp_tdx = 1;
|
||||||
|
params.cp_tdy = 1;
|
||||||
|
|
||||||
|
tile_width = im->xsize;
|
||||||
|
tile_height = im->ysize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context->quality_layers && PySequence_Check(context->quality_layers)) {
|
||||||
|
Py_ssize_t len = PySequence_Length(context->quality_layers);
|
||||||
|
Py_ssize_t n;
|
||||||
|
float *pq;
|
||||||
|
|
||||||
|
if (len) {
|
||||||
|
if (len > sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0]))
|
||||||
|
len = sizeof(params.tcp_rates)/sizeof(params.tcp_rates[0]);
|
||||||
|
|
||||||
|
params.tcp_numlayers = (int)len;
|
||||||
|
|
||||||
|
if (context->quality_is_in_db) {
|
||||||
|
params.cp_disto_alloc = params.cp_fixed_alloc = 0;
|
||||||
|
params.cp_fixed_quality = 1;
|
||||||
|
pq = params.tcp_distoratio;
|
||||||
|
} else {
|
||||||
|
params.cp_disto_alloc = 1;
|
||||||
|
params.cp_fixed_alloc = params.cp_fixed_quality = 0;
|
||||||
|
pq = params.tcp_rates;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (n = 0; n < len; ++n) {
|
||||||
|
PyObject *obj = PySequence_ITEM(context->quality_layers, n);
|
||||||
|
pq[n] = PyFloat_AsDouble(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
params.tcp_numlayers = 1;
|
||||||
|
params.tcp_rates[0] = 0;
|
||||||
|
params.cp_disto_alloc = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context->num_resolutions)
|
||||||
|
params.numresolution = context->num_resolutions;
|
||||||
|
|
||||||
|
if (context->cblk_width >= 4 && context->cblk_width <= 1024
|
||||||
|
&& context->cblk_height >= 4 && context->cblk_height <= 1024
|
||||||
|
&& context->cblk_width * context->cblk_height <= 4096) {
|
||||||
|
params.cblockw_init = context->cblk_width;
|
||||||
|
params.cblockh_init = context->cblk_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context->precinct_width >= 4 && context->precinct_height >= 4
|
||||||
|
&& context->precinct_width >= context->cblk_width
|
||||||
|
&& context->precinct_height > context->cblk_height) {
|
||||||
|
params.prcw_init[0] = context->precinct_width;
|
||||||
|
params.prch_init[0] = context->precinct_height;
|
||||||
|
params.res_spec = 1;
|
||||||
|
params.csty |= 0x01;
|
||||||
|
}
|
||||||
|
|
||||||
|
params.irreversible = context->irreversible;
|
||||||
|
|
||||||
|
params.prog_order = context->progression;
|
||||||
|
|
||||||
|
params.cp_cinema = context->cinema_mode;
|
||||||
|
|
||||||
|
switch (params.cp_cinema) {
|
||||||
|
case OPJ_OFF:
|
||||||
|
params.cp_rsiz = OPJ_STD_RSIZ;
|
||||||
|
break;
|
||||||
|
case OPJ_CINEMA2K_24:
|
||||||
|
case OPJ_CINEMA2K_48:
|
||||||
|
params.cp_rsiz = OPJ_CINEMA2K;
|
||||||
|
if (params.numresolution > 6)
|
||||||
|
params.numresolution = 6;
|
||||||
|
break;
|
||||||
|
case OPJ_CINEMA4K_24:
|
||||||
|
params.cp_rsiz = OPJ_CINEMA4K;
|
||||||
|
if (params.numresolution > 7)
|
||||||
|
params.numresolution = 7;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context->cinema_mode != OPJ_OFF)
|
||||||
|
j2k_set_cinema_params(im, components, ¶ms);
|
||||||
|
|
||||||
|
/* Set up the reference grid in the image */
|
||||||
|
image->x0 = params.image_offset_x0;
|
||||||
|
image->y0 = params.image_offset_y0;
|
||||||
|
image->x1 = xsiz = im->xsize + params.image_offset_x0;
|
||||||
|
image->y1 = ysiz = im->ysize + params.image_offset_y0;
|
||||||
|
|
||||||
|
/* Create the compressor */
|
||||||
|
codec = opj_create_compress(context->format);
|
||||||
|
|
||||||
|
if (!codec) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
opj_set_error_handler(codec, j2k_error, context);
|
||||||
|
opj_setup_encoder(codec, ¶ms, image);
|
||||||
|
|
||||||
|
/* Start encoding */
|
||||||
|
if (!opj_start_compress(codec, image, stream)) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write each tile */
|
||||||
|
tiles_x = (im->xsize + (params.image_offset_x0 - params.cp_tx0)
|
||||||
|
+ tile_width - 1) / tile_width;
|
||||||
|
tiles_y = (im->ysize + (params.image_offset_y0 - params.cp_ty0)
|
||||||
|
+ tile_height - 1) / tile_height;
|
||||||
|
|
||||||
|
num_tiles = tiles_x * tiles_y;
|
||||||
|
|
||||||
|
state->buffer = malloc (tile_width * tile_height * components);
|
||||||
|
|
||||||
|
tile_ndx = 0;
|
||||||
|
for (y = 0; y < tiles_y; ++y) {
|
||||||
|
unsigned ty0 = params.cp_ty0 + y * tile_height;
|
||||||
|
unsigned ty1 = ty0 + tile_height;
|
||||||
|
unsigned pixy, pixh;
|
||||||
|
|
||||||
|
if (ty0 < params.image_offset_y0)
|
||||||
|
ty0 = params.image_offset_y0;
|
||||||
|
if (ty1 > ysiz)
|
||||||
|
ty1 = ysiz;
|
||||||
|
|
||||||
|
pixy = ty0 - params.image_offset_y0;
|
||||||
|
pixh = ty1 - ty0;
|
||||||
|
|
||||||
|
for (x = 0; x < tiles_x; ++x) {
|
||||||
|
unsigned tx0 = params.cp_tx0 + x * tile_width;
|
||||||
|
unsigned tx1 = tx0 + tile_width;
|
||||||
|
unsigned pixx, pixw;
|
||||||
|
unsigned data_size;
|
||||||
|
|
||||||
|
if (tx0 < params.image_offset_x0)
|
||||||
|
tx0 = params.image_offset_x0;
|
||||||
|
if (tx1 > xsiz)
|
||||||
|
tx1 = xsiz;
|
||||||
|
|
||||||
|
pixx = tx0 - params.image_offset_x0;
|
||||||
|
pixw = tx1 - tx0;
|
||||||
|
|
||||||
|
pack(im, state->buffer, pixx, pixy, pixw, pixh);
|
||||||
|
|
||||||
|
data_size = pixw * pixh * components;
|
||||||
|
|
||||||
|
if (!opj_write_tile(codec, tile_ndx++, state->buffer,
|
||||||
|
data_size, stream)) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!opj_end_compress(codec, stream)) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
goto quick_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->errcode = IMAGING_CODEC_END;
|
||||||
|
state->state = J2K_STATE_DONE;
|
||||||
|
ret = (int)ImagingIncrementalCodecBytesInBuffer(encoder);
|
||||||
|
|
||||||
|
quick_exit:
|
||||||
|
if (codec)
|
||||||
|
opj_destroy_codec(codec);
|
||||||
|
if (image)
|
||||||
|
opj_image_destroy(image);
|
||||||
|
if (stream)
|
||||||
|
opj_stream_destroy(stream);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ImagingJpeg2KEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes)
|
||||||
|
{
|
||||||
|
JPEG2KENCODESTATE *context = (JPEG2KENCODESTATE *)state->context;
|
||||||
|
|
||||||
|
if (state->state == J2K_STATE_FAILED)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (state->state == J2K_STATE_START) {
|
||||||
|
int seekable = (context->format != OPJ_CODEC_J2K
|
||||||
|
? INCREMENTAL_CODEC_SEEKABLE
|
||||||
|
: INCREMENTAL_CODEC_NOT_SEEKABLE);
|
||||||
|
|
||||||
|
context->encoder = ImagingIncrementalCodecCreate(j2k_encode_entry,
|
||||||
|
im, state,
|
||||||
|
INCREMENTAL_CODEC_WRITE,
|
||||||
|
seekable,
|
||||||
|
context->fd);
|
||||||
|
|
||||||
|
if (!context->encoder) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
state->state = J2K_STATE_FAILED;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->state = J2K_STATE_ENCODING;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ImagingIncrementalCodecPushBuffer(context->encoder, buf, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Cleanup */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
int
|
||||||
|
ImagingJpeg2KEncodeCleanup(ImagingCodecState state) {
|
||||||
|
JPEG2KENCODESTATE *context = (JPEG2KENCODESTATE *)state->context;
|
||||||
|
|
||||||
|
if (context->quality_layers)
|
||||||
|
Py_DECREF(context->quality_layers);
|
||||||
|
|
||||||
|
if (context->error_msg)
|
||||||
|
free ((void *)context->error_msg);
|
||||||
|
|
||||||
|
if (context->encoder)
|
||||||
|
ImagingIncrementalCodecDestroy(context->encoder);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* HAVE_OPENJPEG */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Local Variables:
|
||||||
|
* c-basic-offset: 4
|
||||||
|
* End:
|
||||||
|
*
|
||||||
|
*/
|
|
@ -192,6 +192,7 @@ if __name__ == "__main__":
|
||||||
check_module("PIL CORE", "PIL._imaging")
|
check_module("PIL CORE", "PIL._imaging")
|
||||||
check_module("TKINTER", "PIL._imagingtk")
|
check_module("TKINTER", "PIL._imagingtk")
|
||||||
check_codec("JPEG", "jpeg")
|
check_codec("JPEG", "jpeg")
|
||||||
|
check_codec("JPEG 2000", "jpeg2k")
|
||||||
check_codec("ZLIB (PNG/ZIP)", "zip")
|
check_codec("ZLIB (PNG/ZIP)", "zip")
|
||||||
check_codec("LIBTIFF", "libtiff")
|
check_codec("LIBTIFF", "libtiff")
|
||||||
check_module("FREETYPE2", "PIL._imagingft")
|
check_module("FREETYPE2", "PIL._imagingft")
|
||||||
|
|
31
setup.py
31
setup.py
|
@ -33,7 +33,8 @@ _LIB_IMAGING = (
|
||||||
"QuantHeap", "PcdDecode", "PcxDecode", "PcxEncode", "Point",
|
"QuantHeap", "PcdDecode", "PcxDecode", "PcxEncode", "Point",
|
||||||
"RankFilter", "RawDecode", "RawEncode", "Storage", "SunRleDecode",
|
"RankFilter", "RawDecode", "RawEncode", "Storage", "SunRleDecode",
|
||||||
"TgaRleDecode", "Unpack", "UnpackYCC", "UnsharpMask", "XbmDecode",
|
"TgaRleDecode", "Unpack", "UnpackYCC", "UnsharpMask", "XbmDecode",
|
||||||
"XbmEncode", "ZipDecode", "ZipEncode", "TiffDecode")
|
"XbmEncode", "ZipDecode", "ZipEncode", "TiffDecode", "Incremental",
|
||||||
|
"Jpeg2KDecode", "Jpeg2KEncode")
|
||||||
|
|
||||||
|
|
||||||
def _add_directory(path, dir, where=None):
|
def _add_directory(path, dir, where=None):
|
||||||
|
@ -80,7 +81,8 @@ def _read(file):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import _tkinter
|
import _tkinter
|
||||||
except ImportError:
|
except (ImportError, OSError):
|
||||||
|
# pypy emits an oserror
|
||||||
_tkinter = None
|
_tkinter = None
|
||||||
|
|
||||||
|
|
||||||
|
@ -88,6 +90,7 @@ NAME = 'Pillow'
|
||||||
VERSION = '2.3.0'
|
VERSION = '2.3.0'
|
||||||
TCL_ROOT = None
|
TCL_ROOT = None
|
||||||
JPEG_ROOT = None
|
JPEG_ROOT = None
|
||||||
|
JPEG2K_ROOT = None
|
||||||
ZLIB_ROOT = None
|
ZLIB_ROOT = None
|
||||||
TIFF_ROOT = None
|
TIFF_ROOT = None
|
||||||
FREETYPE_ROOT = None
|
FREETYPE_ROOT = None
|
||||||
|
@ -98,6 +101,7 @@ class pil_build_ext(build_ext):
|
||||||
|
|
||||||
class feature:
|
class feature:
|
||||||
zlib = jpeg = tiff = freetype = tcl = tk = lcms = webp = webpmux = None
|
zlib = jpeg = tiff = freetype = tcl = tk = lcms = webp = webpmux = None
|
||||||
|
jpeg2000 = None
|
||||||
required = []
|
required = []
|
||||||
|
|
||||||
def require(self, feat):
|
def require(self, feat):
|
||||||
|
@ -150,7 +154,7 @@ class pil_build_ext(build_ext):
|
||||||
#
|
#
|
||||||
# add configured kits
|
# add configured kits
|
||||||
|
|
||||||
for root in (TCL_ROOT, JPEG_ROOT, TIFF_ROOT, ZLIB_ROOT,
|
for root in (TCL_ROOT, JPEG_ROOT, JPEG2K_ROOT, TIFF_ROOT, ZLIB_ROOT,
|
||||||
FREETYPE_ROOT, LCMS_ROOT):
|
FREETYPE_ROOT, LCMS_ROOT):
|
||||||
if isinstance(root, type(())):
|
if isinstance(root, type(())):
|
||||||
lib_root, include_root = root
|
lib_root, include_root = root
|
||||||
|
@ -321,6 +325,16 @@ class pil_build_ext(build_ext):
|
||||||
_add_directory(library_dirs, "/usr/lib")
|
_add_directory(library_dirs, "/usr/lib")
|
||||||
_add_directory(include_dirs, "/usr/include")
|
_add_directory(include_dirs, "/usr/include")
|
||||||
|
|
||||||
|
# on Windows, look for the OpenJPEG libraries in the location that
|
||||||
|
# the official installed puts them
|
||||||
|
if sys.platform == "win32":
|
||||||
|
_add_directory(library_dirs,
|
||||||
|
os.path.join(os.environ.get("ProgramFiles", ""),
|
||||||
|
"OpenJPEG 2.0", "lib"))
|
||||||
|
_add_directory(include_dirs,
|
||||||
|
os.path.join(os.environ.get("ProgramFiles", ""),
|
||||||
|
"OpenJPEG 2.0", "include"))
|
||||||
|
|
||||||
#
|
#
|
||||||
# insert new dirs *before* default libs, to avoid conflicts
|
# insert new dirs *before* default libs, to avoid conflicts
|
||||||
# between Python PYD stub libs and real libraries
|
# between Python PYD stub libs and real libraries
|
||||||
|
@ -349,6 +363,11 @@ class pil_build_ext(build_ext):
|
||||||
_find_library_file(self, "libjpeg")):
|
_find_library_file(self, "libjpeg")):
|
||||||
feature.jpeg = "libjpeg" # alternative name
|
feature.jpeg = "libjpeg" # alternative name
|
||||||
|
|
||||||
|
if feature.want('jpeg2000'):
|
||||||
|
if _find_include_file(self, "openjpeg-2.0/openjpeg.h"):
|
||||||
|
if _find_library_file(self, "openjp2"):
|
||||||
|
feature.jpeg2000 = "openjp2"
|
||||||
|
|
||||||
if feature.want('tiff'):
|
if feature.want('tiff'):
|
||||||
if _find_library_file(self, "tiff"):
|
if _find_library_file(self, "tiff"):
|
||||||
feature.tiff = "tiff"
|
feature.tiff = "tiff"
|
||||||
|
@ -430,6 +449,11 @@ class pil_build_ext(build_ext):
|
||||||
if feature.jpeg:
|
if feature.jpeg:
|
||||||
libs.append(feature.jpeg)
|
libs.append(feature.jpeg)
|
||||||
defs.append(("HAVE_LIBJPEG", None))
|
defs.append(("HAVE_LIBJPEG", None))
|
||||||
|
if feature.jpeg2000:
|
||||||
|
libs.append(feature.jpeg2000)
|
||||||
|
defs.append(("HAVE_OPENJPEG", None))
|
||||||
|
if sys.platform == "win32":
|
||||||
|
defs.append(("OPJ_STATIC", None))
|
||||||
if feature.zlib:
|
if feature.zlib:
|
||||||
libs.append(feature.zlib)
|
libs.append(feature.zlib)
|
||||||
defs.append(("HAVE_LIBZ", None))
|
defs.append(("HAVE_LIBZ", None))
|
||||||
|
@ -537,6 +561,7 @@ class pil_build_ext(build_ext):
|
||||||
options = [
|
options = [
|
||||||
(feature.tcl and feature.tk, "TKINTER"),
|
(feature.tcl and feature.tk, "TKINTER"),
|
||||||
(feature.jpeg, "JPEG"),
|
(feature.jpeg, "JPEG"),
|
||||||
|
(feature.jpeg2000, "OPENJPEG (JPEG2000)"),
|
||||||
(feature.zlib, "ZLIB (PNG/ZIP)"),
|
(feature.zlib, "ZLIB (PNG/ZIP)"),
|
||||||
(feature.tiff, "LIBTIFF"),
|
(feature.tiff, "LIBTIFF"),
|
||||||
(feature.freetype, "FREETYPE2"),
|
(feature.freetype, "FREETYPE2"),
|
||||||
|
|
Loading…
Reference in New Issue
Block a user