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