Merge branch 'master' into imaging-copy-palette-info

This commit is contained in:
Alexander 2017-09-19 20:51:52 +03:00
commit bca70a743d
29 changed files with 549 additions and 312 deletions

View File

@ -4,6 +4,30 @@ Changelog (Pillow)
4.3.0 (unreleased)
------------------
- Fix ValueError in Exif/Tiff IFD #2719
[wiredfool]
- Use pathlib2 for Path objects on Python < 3.4 #2291
[asergi]
- Export only required properties in unsafe_ptrs #2740
[homm]
- Alpha composite fixes #2709
[homm]
- Faster Transpose operations, added 'Transverse' option #2730
[homm]
- Deprecate ImageOps undocumented functions gaussian_blur, gblur, unsharp_mask, usm and box_blur in favor of ImageFilter implementations #2735
[homm]
- Dependencies: Updated freetype to 2.8.1 #2741
[radarhere]
- Bug: Player skipped first image #2742
[radarhere]
- Faster filter operations for Kernel, Gaussian, and Unsharp Mask filters #2679
[homm]

View File

@ -123,6 +123,16 @@ try:
except ImportError:
HAS_CFFI = False
try:
from pathlib import Path
HAS_PATHLIB = True
except ImportError:
try:
from pathlib2 import Path
HAS_PATHLIB = True
except ImportError:
HAS_PATHLIB = False
def isImageType(t):
"""
@ -150,6 +160,7 @@ ROTATE_90 = 2
ROTATE_180 = 3
ROTATE_270 = 4
TRANSPOSE = 5
TRANSVERSE = 6
# transforms
AFFINE = 0
@ -1403,9 +1414,9 @@ class Image(object):
Performance Note: Not currently implemented in-place in the core layer.
"""
if not isinstance(source, tuple):
if not isinstance(source, (list, tuple)):
raise ValueError("Source must be a tuple")
if not isinstance(dest, tuple):
if not isinstance(dest, (list, tuple)):
raise ValueError("Destination must be a tuple")
if not len(source) in (2, 4):
raise ValueError("Source must be a 2 or 4-tuple")
@ -1429,7 +1440,7 @@ class Image(object):
box = dest + (dest[0] + overlay.width, dest[1] + overlay.height)
# destination image. don't copy if we're using the whole image.
if dest == (0,0) + self.size:
if box == (0,0) + self.size:
background = self
else:
background = self.crop(box)
@ -1874,9 +1885,7 @@ class Image(object):
if isPath(fp):
filename = fp
open_fp = True
elif sys.version_info >= (3, 4):
from pathlib import Path
if isinstance(fp, Path):
elif HAS_PATHLIB and isinstance(fp, Path):
filename = str(fp)
open_fp = True
if not filename and hasattr(fp, "name") and isPath(fp.name):
@ -2173,8 +2182,8 @@ class Image(object):
:param method: One of :py:attr:`PIL.Image.FLIP_LEFT_RIGHT`,
:py:attr:`PIL.Image.FLIP_TOP_BOTTOM`, :py:attr:`PIL.Image.ROTATE_90`,
:py:attr:`PIL.Image.ROTATE_180`, :py:attr:`PIL.Image.ROTATE_270` or
:py:attr:`PIL.Image.TRANSPOSE`.
:py:attr:`PIL.Image.ROTATE_180`, :py:attr:`PIL.Image.ROTATE_270`,
:py:attr:`PIL.Image.TRANSPOSE` or :py:attr:`PIL.Image.TRANSVERSE`.
:returns: Returns a flipped or rotated copy of this image.
"""
@ -2515,13 +2524,8 @@ def open(fp, mode="r"):
filename = ""
if isPath(fp):
filename = fp
else:
try:
from pathlib import Path
if isinstance(fp, Path):
elif HAS_PATHLIB and isinstance(fp, Path):
filename = str(fp.resolve())
except ImportError:
pass
if filename:
fp = builtins.open(filename, "rb")

View File

@ -160,6 +160,26 @@ class GaussianBlur(MultibandFilter):
return image.gaussian_blur(self.radius)
class BoxBlur(MultibandFilter):
"""Blurs the image by setting each pixel to the average value of the pixels
in a square box extending radius pixels in each direction.
Supports float radius of arbitrary size. Uses an optimized implementation
which runs in linear time relative to the size of the image
for any radius value.
:param radius: Size of the box in one direction. Radius 0 does not blur,
returns an identical image. Radius 1 takes 1 pixel
in each direction, i.e. 9 pixels in total.
"""
name = "BoxBlur"
def __init__(self, radius):
self.radius = radius
def filter(self, image):
return image.box_blur(self.radius)
class UnsharpMask(MultibandFilter):
"""Unsharp mask filter.

View File

@ -21,6 +21,7 @@ from . import Image
from ._util import isStringType
import operator
import functools
import warnings
#
@ -437,6 +438,13 @@ def solarize(image, threshold=128):
def gaussian_blur(im, radius=None):
""" PIL_usm.gblur(im, [radius])"""
warnings.warn(
'PIL.ImageOps.gaussian_blur is deprecated. '
'Use PIL.ImageFilter.GaussianBlur instead. '
'This function will be removed in a future version.',
DeprecationWarning
)
if radius is None:
radius = 5.0
@ -444,12 +452,30 @@ def gaussian_blur(im, radius=None):
return im.im.gaussian_blur(radius)
gblur = gaussian_blur
def gblur(im, radius=None):
""" PIL_usm.gblur(im, [radius])"""
warnings.warn(
'PIL.ImageOps.gblur is deprecated. '
'Use PIL.ImageFilter.GaussianBlur instead. '
'This function will be removed in a future version.',
DeprecationWarning
)
return gaussian_blur(im, radius)
def unsharp_mask(im, radius=None, percent=None, threshold=None):
""" PIL_usm.usm(im, [radius, percent, threshold])"""
warnings.warn(
'PIL.ImageOps.unsharp_mask is deprecated. '
'Use PIL.ImageFilter.UnsharpMask instead. '
'This function will be removed in a future version.',
DeprecationWarning
)
if radius is None:
radius = 5.0
if percent is None:
@ -461,7 +487,18 @@ def unsharp_mask(im, radius=None, percent=None, threshold=None):
return im.im.unsharp_mask(radius, percent, threshold)
usm = unsharp_mask
def usm(im, radius=None, percent=None, threshold=None):
""" PIL_usm.usm(im, [radius, percent, threshold])"""
warnings.warn(
'PIL.ImageOps.usm is deprecated. '
'Use PIL.ImageFilter.UnsharpMask instead. '
'This function will be removed in a future version.',
DeprecationWarning
)
return unsharp_mask(im, radius, percent, threshold)
def box_blur(image, radius):
@ -478,6 +515,13 @@ def box_blur(image, radius):
in each direction, i.e. 9 pixels in total.
:return: An image.
"""
warnings.warn(
'PIL.ImageOps.box_blur is deprecated. '
'Use PIL.ImageFilter.BoxBlur instead. '
'This function will be removed in a future version.',
DeprecationWarning
)
image.load()
return image._new(image.im.box_blur(radius))

View File

@ -49,8 +49,7 @@ class PyAccess(object):
self.image8 = ffi.cast('unsigned char **', vals['image8'])
self.image32 = ffi.cast('int **', vals['image32'])
self.image = ffi.cast('unsigned char **', vals['image'])
self.xsize = vals['xsize']
self.ysize = vals['ysize']
self.xsize, self.ysize = img.im.size
# Keep pointer to im object to prevent dereferencing.
self._im = img.im

View File

@ -550,11 +550,28 @@ class ImageFileDirectory_v2(collections.MutableMapping):
dest = self._tags_v1 if legacy_api else self._tags_v2
if info.length == 1:
if legacy_api and self.tagtype[tag] in [5, 10]:
# Three branches:
# Spec'd length == 1, Actual length 1, store as element
# Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed.
# No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple.
# Don't mess with the legacy api, since it's frozen.
if ((info.length == 1) or
(info.length is None and len(values) == 1 and not legacy_api)):
# Don't mess with the legacy api, since it's frozen.
if legacy_api and self.tagtype[tag] in [5, 10]: # rationals
values = values,
try:
dest[tag], = values
except ValueError:
# We've got a builtin tag with 1 expected entry
warnings.warn(
"Metadata Warning, tag %s had too many entries: %s, expected 1" % (
tag, len(values)))
dest[tag] = values[0]
else:
# Spec'd length > 1 or undefined
# Unspec'd, and length > 1
dest[tag] = values
def __delitem__(self, tag):
@ -1011,8 +1028,10 @@ class TiffImageFile(ImageFile.ImageFile):
args = rawmode, ""
if JPEGTABLES in self.tag_v2:
# Hack to handle abbreviated JPEG headers
# FIXME This will fail with more than one value
self.tile_prefix, = self.tag_v2[JPEGTABLES]
# Definition of JPEGTABLES is that the count
# is the number of bytes in the tables datastream
# so, it should always be 1 in our tag info
self.tile_prefix = self.tag_v2[JPEGTABLES]
elif compression == "packbits":
args = rawmode
elif compression == "tiff_lzw":

View File

@ -23,7 +23,7 @@ from collections import namedtuple
class TagInfo(namedtuple("_TagInfo", "value name type length enum")):
__slots__ = []
def __new__(cls, value=None, name="unknown", type=None, length=0, enum=None):
def __new__(cls, value=None, name="unknown", type=None, length=None, enum=None):
return super(TagInfo, cls).__new__(
cls, value, name, type, length, enum or {})
@ -142,6 +142,8 @@ TAGS_V2 = {
341: ("SMaxSampleValue", DOUBLE, 0),
342: ("TransferRange", SHORT, 6),
347: ("JPEGTables", UNDEFINED, 1),
# obsolete JPEG tags
512: ("JPEGProc", SHORT, 1),
513: ("JPEGInterchangeFormat", LONG, 1),
@ -158,7 +160,10 @@ TAGS_V2 = {
531: ("YCbCrPositioning", SHORT, 1),
532: ("ReferenceBlackWhite", LONG, 0),
700: ('XMP', BYTE, 1),
33432: ("Copyright", ASCII, 1),
34377: ('PhotoshopInfo', BYTE, 1),
# FIXME add more tags here
34665: ("ExifIFD", SHORT, 1),
@ -188,8 +193,8 @@ TAGS_V2 = {
50741: ("MakerNoteSafety", SHORT, 1, {"Unsafe": 0, "Safe": 1}),
50780: ("BestQualityScale", RATIONAL, 1),
50838: ("ImageJMetaDataByteCounts", LONG, 1),
50839: ("ImageJMetaData", UNDEFINED, 1)
50838: ("ImageJMetaDataByteCounts", LONG, 0), # Can be more than one
50839: ("ImageJMetaData", UNDEFINED, 1) # see Issue #2006
}
# Legacy Tags structure

View File

@ -22,13 +22,10 @@ from PIL import Image, ImageTk
class UI(tkinter.Label):
def __init__(self, master, im):
if isinstance(im, list):
# list of images
self.im = im[1:]
im = self.im[0]
else:
# sequence
self.im = im
if isinstance(self.im, list):
# list of images
im = self.im.pop(0)
if im.mode == "1":
self.image = ImageTk.BitmapImage(im, foreground="white")

BIN
Tests/images/issue_2278.tif Normal file

Binary file not shown.

View File

@ -13,20 +13,6 @@ sample.putdata(sum([
], []))
class ImageMock(object):
def __init__(self):
self.im = self
def load(self):
pass
def _new(self, im):
return im
def box_blur(self, radius, n):
return radius, n
class TestBoxBlurApi(PillowTestCase):
def test_imageops_box_blur(self):

View File

@ -56,7 +56,7 @@ class TestFileTiffMetadata(PillowTestCase):
loaded = Image.open(f)
self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], (len(bindata),))
self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], len(bindata))
self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], (len(bindata),))
self.assertEqual(loaded.tag[ImageJMetaData], bindata)
self.assertEqual(loaded.tag_v2[ImageJMetaData], bindata)
@ -69,6 +69,16 @@ class TestFileTiffMetadata(PillowTestCase):
loaded_double = loaded.tag[tag_ids['YawAngle']][0]
self.assertAlmostEqual(loaded_double, doubledata)
# check with 2 element ImageJMetaDataByteCounts, issue #2006
info[ImageJMetaDataByteCounts] = (8, len(bindata) - 8)
img.save(f, tiffinfo=info)
loaded = Image.open(f)
self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], (8, len(bindata) - 8))
self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], (8, len(bindata) - 8))
def test_read_metadata(self):
img = Image.open('Tests/images/hopper_g4.tif')
@ -202,8 +212,8 @@ class TestFileTiffMetadata(PillowTestCase):
im.save(out, tiffinfo=info, compression='raw')
reloaded = Image.open(out)
self.assertEqual(0, reloaded.tag_v2[41988][0].numerator)
self.assertEqual(0, reloaded.tag_v2[41988][0].denominator)
self.assertEqual(0, reloaded.tag_v2[41988].numerator)
self.assertEqual(0, reloaded.tag_v2[41988].denominator)
def test_expty_values(self):
data = io.BytesIO(
@ -220,6 +230,27 @@ class TestFileTiffMetadata(PillowTestCase):
self.fail("Should not be struct value error there.")
self.assertIn(33432, info)
def test_PhotoshopInfo(self):
im = Image.open('Tests/images/issue_2278.tif')
self.assertIsInstance(im.tag_v2[34377], bytes)
out = self.tempfile('temp.tiff')
im.save(out)
reloaded = Image.open(out)
self.assertIsInstance(reloaded.tag_v2[34377], bytes)
def test_too_many_entries(self):
ifd = TiffImagePlugin.ImageFileDirectory_v2()
# 277: ("SamplesPerPixel", SHORT, 1),
ifd._tagdata[277] = struct.pack('hh', 4,4)
ifd.tagtype[277] = TiffTags.SHORT
try:
self.assert_warning(UserWarning, lambda: ifd[277])
except ValueError:
self.fail("Invalid Metadata count should not cause a Value Error.")
if __name__ == '__main__':
unittest.main()

View File

@ -2,7 +2,6 @@ from helper import unittest, PillowTestCase, hopper
from PIL import Image
import os
import sys
class TestImage(PillowTestCase):
@ -72,10 +71,9 @@ class TestImage(PillowTestCase):
def test_bad_mode(self):
self.assertRaises(ValueError, Image.open, "filename", "bad mode")
@unittest.skipIf(sys.version_info < (3, 4),
"pathlib only available in Python 3.4 or later")
@unittest.skipUnless(Image.HAS_PATHLIB, "requires pathlib/pathlib2")
def test_pathlib(self):
from pathlib import Path
from PIL.Image import Path
im = Image.open(Path("Tests/images/hopper.jpg"))
self.assertEqual(im.mode, "RGB")
self.assertEqual(im.size, (128, 128))

View File

@ -1,7 +1,6 @@
from helper import unittest, PillowTestCase, hopper
from PIL import Image
from PIL import ImageFilter
from PIL import Image, ImageFilter
class TestImageFilter(PillowTestCase):
@ -9,7 +8,8 @@ class TestImageFilter(PillowTestCase):
def test_sanity(self):
def filter(filter):
im = hopper("L")
for mode in ["L", "RGB", "CMYK"]:
im = hopper(mode)
out = im.filter(filter)
self.assertEqual(out.mode, im.mode)
self.assertEqual(out.size, im.size)
@ -28,9 +28,9 @@ class TestImageFilter(PillowTestCase):
filter(ImageFilter.MedianFilter)
filter(ImageFilter.MinFilter)
filter(ImageFilter.ModeFilter)
filter(ImageFilter.Kernel((3, 3), list(range(9))))
filter(ImageFilter.GaussianBlur)
filter(ImageFilter.GaussianBlur(5))
filter(ImageFilter.BoxBlur(5))
filter(ImageFilter.UnsharpMask)
filter(ImageFilter.UnsharpMask(10))

View File

@ -2,7 +2,7 @@ import helper
from helper import unittest, PillowTestCase
from PIL.Image import (FLIP_LEFT_RIGHT, FLIP_TOP_BOTTOM, ROTATE_90, ROTATE_180,
ROTATE_270, TRANSPOSE)
ROTATE_270, TRANSPOSE, TRANSVERSE)
class TestImageTranspose(PillowTestCase):
@ -108,6 +108,22 @@ class TestImageTranspose(PillowTestCase):
for mode in ("L", "RGB"):
transpose(mode)
def test_tranverse(self):
def transpose(mode):
im = self.hopper[mode]
out = im.transpose(TRANSVERSE)
self.assertEqual(out.mode, mode)
self.assertEqual(out.size, im.size[::-1])
x, y = im.size
self.assertEqual(im.getpixel((1, 1)), out.getpixel((y-2, x-2)))
self.assertEqual(im.getpixel((x-2, 1)), out.getpixel((y-2, 1)))
self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, x-2)))
self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, 1)))
for mode in ("L", "RGB"):
transpose(mode)
def test_roundtrip(self):
im = self.hopper['L']
@ -124,6 +140,12 @@ class TestImageTranspose(PillowTestCase):
im.transpose(TRANSPOSE), transpose(ROTATE_90, FLIP_TOP_BOTTOM))
self.assert_image_equal(
im.transpose(TRANSPOSE), transpose(ROTATE_270, FLIP_LEFT_RIGHT))
self.assert_image_equal(
im.transpose(TRANSVERSE), transpose(ROTATE_90, FLIP_LEFT_RIGHT))
self.assert_image_equal(
im.transpose(TRANSVERSE), transpose(ROTATE_270, FLIP_TOP_BOTTOM))
self.assert_image_equal(
im.transpose(TRANSVERSE), transpose(ROTATE_180, TRANSPOSE))
if __name__ == '__main__':

View File

@ -12,15 +12,25 @@ class TestImageOpsUsm(PillowTestCase):
def test_ops_api(self):
i = ImageOps.gaussian_blur(im, 2.0)
i = self.assert_warning(DeprecationWarning,
ImageOps.gaussian_blur, im, 2.0)
self.assertEqual(i.mode, "RGB")
self.assertEqual(i.size, (128, 128))
# i.save("blur.bmp")
i = ImageOps.unsharp_mask(im, 2.0, 125, 8)
i = self.assert_warning(DeprecationWarning,
ImageOps.gblur, im, 2.0)
self.assertEqual(i.mode, "RGB")
self.assertEqual(i.size, (128, 128))
i = self.assert_warning(DeprecationWarning,
ImageOps.unsharp_mask, im, 2.0, 125, 8)
self.assertEqual(i.mode, "RGB")
self.assertEqual(i.size, (128, 128))
i = self.assert_warning(DeprecationWarning,
ImageOps.usm, im, 2.0, 125, 8)
self.assertEqual(i.mode, "RGB")
self.assertEqual(i.size, (128, 128))
# i.save("usm.bmp")
def test_filter_api(self):
@ -36,38 +46,38 @@ class TestImageOpsUsm(PillowTestCase):
def test_usm_formats(self):
usm = ImageOps.unsharp_mask
self.assertRaises(ValueError, usm, im.convert("1"))
usm(im.convert("L"))
self.assertRaises(ValueError, usm, im.convert("I"))
self.assertRaises(ValueError, usm, im.convert("F"))
usm(im.convert("RGB"))
usm(im.convert("RGBA"))
usm(im.convert("CMYK"))
self.assertRaises(ValueError, usm, im.convert("YCbCr"))
usm = ImageFilter.UnsharpMask
self.assertRaises(ValueError, im.convert("1").filter, usm)
im.convert("L").filter(usm)
self.assertRaises(ValueError, im.convert("I").filter, usm)
self.assertRaises(ValueError, im.convert("F").filter, usm)
im.convert("RGB").filter(usm)
im.convert("RGBA").filter(usm)
im.convert("CMYK").filter(usm)
self.assertRaises(ValueError, im.convert("YCbCr").filter, usm)
def test_blur_formats(self):
blur = ImageOps.gaussian_blur
self.assertRaises(ValueError, blur, im.convert("1"))
blur = ImageFilter.GaussianBlur
self.assertRaises(ValueError, im.convert("1").filter, blur)
blur(im.convert("L"))
self.assertRaises(ValueError, blur, im.convert("I"))
self.assertRaises(ValueError, blur, im.convert("F"))
blur(im.convert("RGB"))
blur(im.convert("RGBA"))
blur(im.convert("CMYK"))
self.assertRaises(ValueError, blur, im.convert("YCbCr"))
self.assertRaises(ValueError, im.convert("I").filter, blur)
self.assertRaises(ValueError, im.convert("F").filter, blur)
im.convert("RGB").filter(blur)
im.convert("RGBA").filter(blur)
im.convert("CMYK").filter(blur)
self.assertRaises(ValueError, im.convert("YCbCr").filter, blur)
def test_usm_accuracy(self):
src = snakes.convert('RGB')
i = src._new(ImageOps.unsharp_mask(src, 5, 1024, 0))
i = src.filter(ImageFilter.UnsharpMask(5, 1024, 0))
# Image should not be changed because it have only 0 and 255 levels.
self.assertEqual(i.tobytes(), src.tobytes())
def test_blur_accuracy(self):
i = snakes._new(ImageOps.gaussian_blur(snakes, .4))
i = snakes.filter(ImageFilter.GaussianBlur(.4))
# These pixels surrounded with pixels with 255 intensity.
# They must be very close to 255.
for x, y, c in [(1, 0, 1), (2, 0, 1), (7, 8, 1), (8, 8, 1), (2, 9, 1),

View File

@ -1678,6 +1678,7 @@ _transpose(ImagingObject* self, PyObject* args)
case 2: /* rotate 90 */
case 4: /* rotate 270 */
case 5: /* transpose */
case 6: /* transverse */
imOut = ImagingNewDirty(imIn->mode, imIn->ysize, imIn->xsize);
break;
default:
@ -1705,6 +1706,9 @@ _transpose(ImagingObject* self, PyObject* args)
case 5:
(void) ImagingTranspose(imOut, imIn);
break;
case 6:
(void) ImagingTransverse(imOut, imIn);
break;
}
return PyImagingNew(imOut);
@ -3138,21 +3142,10 @@ _getattr_ptr(ImagingObject* self, void* closure)
static PyObject*
_getattr_unsafe_ptrs(ImagingObject* self, void* closure)
{
return Py_BuildValue("(ss)(si)(si)(si)(si)(si)(sn)(sn)(sn)(sn)(sn)(si)(si)(sn)",
"mode", self->image->mode,
"type", self->image->type,
"depth", self->image->depth,
"bands", self->image->bands,
"xsize", self->image->xsize,
"ysize", self->image->ysize,
"palette", self->image->palette,
return Py_BuildValue("(sn)(sn)(sn)",
"image8", self->image->image8,
"image32", self->image->image32,
"image", self->image->image,
"block", self->image->block,
"pixelsize", self->image->pixelsize,
"linesize", self->image->linesize,
"destroy", self->image->destroy
"image", self->image->image
);
};

View File

@ -38,6 +38,7 @@ image enhancement filters:
* **SHARPEN**
.. autoclass:: PIL.ImageFilter.GaussianBlur
.. autoclass:: PIL.ImageFilter.BoxBlur
.. autoclass:: PIL.ImageFilter.UnsharpMask
.. autoclass:: PIL.ImageFilter.Kernel
.. autoclass:: PIL.ImageFilter.RankFilter

View File

@ -4,13 +4,19 @@
Get One Channel From Image
==========================
New method :py:meth:`PIL.Image.Image.getchannel` added.
New method :py:meth:`PIL.Image.Image.getchannel` is added.
It returns single channel by index or name. For example,
``image.getchannel("A")`` will return alpha channel as separate image.
``getchannel`` should work up to 6 times faster than ``image.split()[0]``
in previous Pillow versions.
Box Blur
========
New filter :py:class:`PIL.ImageFilter.BoxBlur` is added.
Partial Resampling
==================
@ -40,6 +46,22 @@ This release contains several performance improvements:
using a recent version of libjpeg-turbo.
TIFF Metadata Changes
=====================
* TIFF tags with unknown type/quantity now default to being bare
values if they are 1 element, where previously they would be a
single element tuple. This is only with the new api, not the legacy
api. This normalizes the handling of fields, so that the metadata
with inferred or image specified counts are handled the same as
metadata with count specified in the TIFF spec.
* The ``PhotoshopInfo``, ``XMP``, and ``JPEGTables`` tags now have a
defined type (bytes) and a count of 1.
* The ``ImageJMetaDataByteCounts`` tag now has an arbitrary number of
items, as there can be multiple items, one for UTF-8, and one for
UTF-16.
Core Image API Changes
======================

View File

@ -11,6 +11,7 @@
#include "Imaging.h"
#define PRECISION_BITS 7
typedef struct
{
@ -47,13 +48,11 @@ ImagingAlphaComposite(Imaging imDst, Imaging imSrc)
return NULL;
for (y = 0; y < imDst->ysize; y++) {
rgba8* dst = (rgba8*) imDst->image[y];
rgba8* src = (rgba8*) imSrc->image[y];
rgba8* out = (rgba8*) imOut->image[y];
for (x = 0; x < imDst->xsize; x ++) {
if (src->a == 0) {
// Copy 4 bytes at once.
*out = *dst;
@ -62,25 +61,20 @@ ImagingAlphaComposite(Imaging imDst, Imaging imSrc)
// Each variable has extra meaningful bits.
// Divisions are rounded.
// This code uses trick from Paste.c:
// (a + (2 << (n-1)) - 1) / ((2 << n)-1)
// almost equivalent to:
// tmp = a + (2 << (n-1)), ((tmp >> n) + tmp) >> n
UINT32 tmpr, tmpg, tmpb;
UINT16 blend = dst->a * (255 - src->a);
UINT16 outa255 = src->a * 255 + blend;
UINT32 blend = dst->a * (255 - src->a);
UINT32 outa255 = src->a * 255 + blend;
// There we use 7 bits for precision.
// We could use more, but we go beyond 32 bits.
UINT16 coef1 = src->a * 255 * 255 * 128 / outa255;
UINT16 coef2 = 255 * 128 - coef1;
UINT32 coef1 = src->a * 255 * 255 * (1<<PRECISION_BITS) / outa255;
UINT32 coef2 = 255 * (1<<PRECISION_BITS) - coef1;
tmpr = src->r * coef1 + dst->r * coef2 + (0x80 << 7);
out->r = SHIFTFORDIV255(tmpr) >> 7;
tmpg = src->g * coef1 + dst->g * coef2 + (0x80 << 7);
out->g = SHIFTFORDIV255(tmpg) >> 7;
tmpb = src->b * coef1 + dst->b * coef2 + (0x80 << 7);
out->b = SHIFTFORDIV255(tmpb) >> 7;
tmpr = src->r * coef1 + dst->r * coef2;
tmpg = src->g * coef1 + dst->g * coef2;
tmpb = src->b * coef1 + dst->b * coef2;
out->r = SHIFTFORDIV255(tmpr + (0x80<<PRECISION_BITS)) >> PRECISION_BITS;
out->g = SHIFTFORDIV255(tmpg + (0x80<<PRECISION_BITS)) >> PRECISION_BITS;
out->b = SHIFTFORDIV255(tmpb + (0x80<<PRECISION_BITS)) >> PRECISION_BITS;
out->a = SHIFTFORDIV255(outa255 + 0x80);
}

View File

@ -84,7 +84,7 @@ ImagingSplit(Imaging imIn, Imaging bands[4])
}
for (i = 0; i < imIn->bands; i++) {
bands[i] = ImagingNew("L", imIn->xsize, imIn->ysize);
bands[i] = ImagingNewDirty("L", imIn->xsize, imIn->ysize);
if ( ! bands[i]) {
for (j = 0; j < i; ++j) {
ImagingDelete(bands[j]);

View File

@ -72,7 +72,7 @@ create(Imaging im1, Imaging im2, char* mode)
xsize = (im1->xsize < im2->xsize) ? im1->xsize : im2->xsize;
ysize = (im1->ysize < im2->ysize) ? im1->ysize : im2->ysize;
return ImagingNew(im1->mode, xsize, ysize);
return ImagingNewDirty(im1->mode, xsize, ysize);
}
Imaging

View File

@ -37,7 +37,7 @@ ImagingEffectMandelbrot(int xsize, int ysize, double extent[4], int quality)
if (width < 0.0 || height < 0.0 || quality < 2)
return (Imaging) ImagingError_ValueError(NULL);
im = ImagingNew("L", xsize, ysize);
im = ImagingNewDirty("L", xsize, ysize);
if (!im)
return NULL;
@ -81,7 +81,7 @@ ImagingEffectNoise(int xsize, int ysize, float sigma)
int nextok;
double this, next;
imOut = ImagingNew("L", xsize, ysize);
imOut = ImagingNewDirty("L", xsize, ysize);
if (!imOut)
return NULL;
@ -121,14 +121,14 @@ ImagingEffectSpread(Imaging imIn, int distance)
Imaging imOut;
int x, y;
imOut = ImagingNew(imIn->mode, imIn->xsize, imIn->ysize);
imOut = ImagingNewDirty(imIn->mode, imIn->xsize, imIn->ysize);
if (!imOut)
return NULL;
#define SPREAD(type, image)\
for (y = 0; y < imIn->ysize; y++)\
for (x = 0; x < imIn->xsize; x++) {\
for (y = 0; y < imOut->ysize; y++)\
for (x = 0; x < imOut->xsize; x++) {\
int xx = x + (rand() % distance) - distance/2;\
int yy = y + (rand() % distance) - distance/2;\
if (xx >= 0 && xx < imIn->xsize && yy >= 0 && yy < imIn->ysize) {\

View File

@ -27,13 +27,6 @@
#include "Imaging.h"
#ifdef WORDS_BIGENDIAN
#define MAKE_UINT32(u0, u1, u2, u3) (u3 | (u2<<8) | (u1<<16) | (u0<<24))
#else
#define MAKE_UINT32(u0, u1, u2, u3) (u0 | (u1<<8) | (u2<<16) | (u3<<24))
#endif
static inline UINT8 clip8(float in)
{
if (in <= 0.0)
@ -91,29 +84,14 @@ ImagingExpand(Imaging imIn, int xmargin, int ymargin, int mode)
}
/* This is work around bug in GCC prior 4.9 in 64 bit mode.
GCC generates code with partial dependency which 3 times slower.
See: http://stackoverflow.com/a/26588074/253146 */
#if defined(__x86_64__) && defined(__SSE__) && ! defined(__NO_INLINE__) && \
! defined(__clang__) && defined(GCC_VERSION) && (GCC_VERSION < 40900)
static float __attribute__((always_inline)) inline i2f(int v) {
float x;
__asm__("xorps %0, %0; cvtsi2ss %1, %0" : "=X"(x) : "r"(v) );
return x;
}
#else
static float inline i2f(int v) { return (float) v; }
#endif
void
ImagingFilter3x3(Imaging imOut, Imaging im, const float* kernel,
float offset)
{
#define KERNEL1x3(in0, x, kernel, d) ( \
i2f((UINT8) in0[x-d]) * (kernel)[0] + \
i2f((UINT8) in0[x]) * (kernel)[1] + \
i2f((UINT8) in0[x+d]) * (kernel)[2])
_i2f((UINT8) in0[x-d]) * (kernel)[0] + \
_i2f((UINT8) in0[x]) * (kernel)[1] + \
_i2f((UINT8) in0[x+d]) * (kernel)[2])
int x = 0, y = 0;
@ -210,11 +188,11 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float* kernel,
float offset)
{
#define KERNEL1x5(in0, x, kernel, d) ( \
i2f((UINT8) in0[x-d-d]) * (kernel)[0] + \
i2f((UINT8) in0[x-d]) * (kernel)[1] + \
i2f((UINT8) in0[x]) * (kernel)[2] + \
i2f((UINT8) in0[x+d]) * (kernel)[3] + \
i2f((UINT8) in0[x+d+d]) * (kernel)[4])
_i2f((UINT8) in0[x-d-d]) * (kernel)[0] + \
_i2f((UINT8) in0[x-d]) * (kernel)[1] + \
_i2f((UINT8) in0[x]) * (kernel)[2] + \
_i2f((UINT8) in0[x+d]) * (kernel)[3] + \
_i2f((UINT8) in0[x+d+d]) * (kernel)[4])
int x = 0, y = 0;

View File

@ -5,7 +5,8 @@
Rotating in chunks that fit in the cache can speed up rotation
8x on a modern CPU. A chunk size of 128 requires only 65k and is large enough
that the overhead from the extra loops are not apparent. */
#define ROTATE_CHUNK 128
#define ROTATE_CHUNK 512
#define ROTATE_SMALL_CHUNK 8
#define COORD(v) ((v) < 0.0 ? -1 : ((int)(v)))
#define FLOOR(v) ((v) < 0.0 ? ((int)floor(v)) : ((int)(v)))
@ -26,30 +27,27 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn)
ImagingCopyPalette(imOut, imIn);
#define FLIP_LEFT_RIGHT(INT, image) \
for (y = 0; y < imIn->ysize; y++) { \
INT* in = imIn->image[y]; \
INT* out = imOut->image[y]; \
xr = imIn->xsize-1; \
for (x = 0; x < imIn->xsize; x++, xr--) \
out[xr] = in[x]; \
}
ImagingSectionEnter(&cookie);
if (imIn->image8) {
for (y = 0; y < imIn->ysize; y++) {
UINT8* in = (UINT8*) imIn->image8[y];
UINT8* out = (UINT8*) imOut->image8[y];
x = 0;
xr = imIn->xsize-1;
for (; x < imIn->xsize; x++, xr--)
out[xr] = in[x];
}
FLIP_LEFT_RIGHT(UINT8, image8)
} else {
for (y = 0; y < imIn->ysize; y++) {
UINT32* in = (UINT32*) imIn->image32[y];
UINT32* out = (UINT32*) imOut->image32[y];
x = 0;
xr = imIn->xsize-1;
for (; x < imIn->xsize; x++, xr--)
out[xr] = in[x];
}
FLIP_LEFT_RIGHT(INT32, image32)
}
ImagingSectionLeave(&cookie);
#undef FLIP_LEFT_RIGHT
return imOut;
}
@ -84,6 +82,7 @@ ImagingRotate90(Imaging imOut, Imaging imIn)
{
ImagingSectionCookie cookie;
int x, y, xx, yy, xr, xxsize, yysize;
int xxx, yyy, xxxsize, yyysize;
if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0)
return (Imaging) ImagingError_ModeError();
@ -92,15 +91,22 @@ ImagingRotate90(Imaging imOut, Imaging imIn)
ImagingCopyPalette(imOut, imIn);
#define ROTATE_90(image) \
#define ROTATE_90(INT, image) \
for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \
for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \
yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \
xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \
for (yy = y; yy < yysize; yy++) { \
xr = imIn->xsize - 1 - x; \
for (xx = x; xx < xxsize; xx++, xr--) { \
imOut->image[xr][yy] = imIn->image[yy][xx]; \
for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \
for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \
yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize ? yy + ROTATE_SMALL_CHUNK : imIn->ysize; \
xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \
for (yyy = yy; yyy < yyysize; yyy++) { \
INT* in = imIn->image[yyy]; \
xr = imIn->xsize - 1 - xx; \
for (xxx = xx; xxx < xxxsize; xxx++, xr--) { \
imOut->image[xr][yyy] = in[xxx]; \
} \
} \
} \
} \
} \
@ -109,9 +115,9 @@ ImagingRotate90(Imaging imOut, Imaging imIn)
ImagingSectionEnter(&cookie);
if (imIn->image8)
ROTATE_90(image8)
ROTATE_90(UINT8, image8)
else
ROTATE_90(image32)
ROTATE_90(INT32, image32)
ImagingSectionLeave(&cookie);
@ -126,6 +132,7 @@ ImagingTranspose(Imaging imOut, Imaging imIn)
{
ImagingSectionCookie cookie;
int x, y, xx, yy, xxsize, yysize;
int xxx, yyy, xxxsize, yyysize;
if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0)
return (Imaging) ImagingError_ModeError();
@ -134,14 +141,21 @@ ImagingTranspose(Imaging imOut, Imaging imIn)
ImagingCopyPalette(imOut, imIn);
#define TRANSPOSE(image) \
#define TRANSPOSE(INT, image) \
for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \
for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \
yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \
xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \
for (yy = y; yy < yysize; yy++) { \
for (xx = x; xx < xxsize; xx++) { \
imOut->image[xx][yy] = imIn->image[yy][xx]; \
for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \
for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \
yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize ? yy + ROTATE_SMALL_CHUNK : imIn->ysize; \
xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \
for (yyy = yy; yyy < yyysize; yyy++) { \
INT* in = imIn->image[yyy]; \
for (xxx = xx; xxx < xxxsize; xxx++) { \
imOut->image[xxx][yyy] = in[xxx]; \
} \
} \
} \
} \
} \
@ -150,9 +164,9 @@ ImagingTranspose(Imaging imOut, Imaging imIn)
ImagingSectionEnter(&cookie);
if (imIn->image8)
TRANSPOSE(image8)
TRANSPOSE(UINT8, image8)
else
TRANSPOSE(image32)
TRANSPOSE(INT32, image32)
ImagingSectionLeave(&cookie);
@ -162,6 +176,57 @@ ImagingTranspose(Imaging imOut, Imaging imIn)
}
Imaging
ImagingTransverse(Imaging imOut, Imaging imIn)
{
ImagingSectionCookie cookie;
int x, y, xr, yr, xx, yy, xxsize, yysize;
int xxx, yyy, xxxsize, yyysize;
if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0)
return (Imaging) ImagingError_ModeError();
if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize)
return (Imaging) ImagingError_Mismatch();
ImagingCopyInfo(imOut, imIn);
#define TRANSVERSE(INT, image) \
for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \
for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \
yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \
xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \
for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \
for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \
yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize ? yy + ROTATE_SMALL_CHUNK : imIn->ysize; \
xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \
yr = imIn->ysize - 1 - yy; \
for (yyy = yy; yyy < yyysize; yyy++, yr--) { \
INT* in = imIn->image[yyy]; \
xr = imIn->xsize - 1 - xx; \
for (xxx = xx; xxx < xxxsize; xxx++, xr--) { \
imOut->image[xr][yr] = in[xxx]; \
} \
} \
} \
} \
} \
}
ImagingSectionEnter(&cookie);
if (imIn->image8)
TRANSVERSE(UINT8, image8)
else
TRANSVERSE(INT32, image32)
ImagingSectionLeave(&cookie);
#undef TRANSVERSE
return imOut;
}
Imaging
ImagingRotate180(Imaging imOut, Imaging imIn)
{
@ -175,20 +240,23 @@ ImagingRotate180(Imaging imOut, Imaging imIn)
ImagingCopyPalette(imOut, imIn);
#define ROTATE_180(image)\
for (y = 0; y < imIn->ysize; y++, yr--) {\
xr = imIn->xsize-1;\
for (x = 0; x < imIn->xsize; x++, xr--)\
imOut->image[y][x] = imIn->image[yr][xr];\
#define ROTATE_180(INT, image) \
for (y = 0; y < imIn->ysize; y++, yr--) { \
INT* in = imIn->image[y]; \
INT* out = imOut->image[yr]; \
xr = imIn->xsize-1; \
for (x = 0; x < imIn->xsize; x++, xr--) \
out[xr] = in[x]; \
}
ImagingSectionEnter(&cookie);
yr = imIn->ysize-1;
if (imIn->image8)
ROTATE_180(image8)
else
ROTATE_180(image32)
if (imIn->image8) {
ROTATE_180(UINT8, image8)
} else {
ROTATE_180(INT32, image32)
}
ImagingSectionLeave(&cookie);
@ -203,6 +271,7 @@ ImagingRotate270(Imaging imOut, Imaging imIn)
{
ImagingSectionCookie cookie;
int x, y, xx, yy, yr, xxsize, yysize;
int xxx, yyy, xxxsize, yyysize;
if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0)
return (Imaging) ImagingError_ModeError();
@ -211,15 +280,22 @@ ImagingRotate270(Imaging imOut, Imaging imIn)
ImagingCopyPalette(imOut, imIn);
#define ROTATE_270(image) \
#define ROTATE_270(INT, image) \
for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \
for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \
yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \
xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \
yr = imIn->ysize - 1 - y; \
for (yy = y; yy < yysize; yy++, yr--) { \
for (xx = x; xx < xxsize; xx++) { \
imOut->image[xx][yr] = imIn->image[yy][xx]; \
for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \
for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \
yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize ? yy + ROTATE_SMALL_CHUNK : imIn->ysize; \
xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \
yr = imIn->ysize - 1 - yy; \
for (yyy = yy; yyy < yyysize; yyy++, yr--) { \
INT* in = imIn->image[yyy]; \
for (xxx = xx; xxx < xxxsize; xxx++) { \
imOut->image[xxx][yr] = in[xxx]; \
} \
} \
} \
} \
} \
@ -228,9 +304,9 @@ ImagingRotate270(Imaging imOut, Imaging imIn)
ImagingSectionEnter(&cookie);
if (imIn->image8)
ROTATE_270(image8)
ROTATE_270(UINT8, image8)
else
ROTATE_270(image32)
ROTATE_270(INT32, image32)
ImagingSectionLeave(&cookie);

View File

@ -181,8 +181,6 @@ extern void ImagingAccessInit(void);
extern ImagingAccess ImagingAccessNew(Imaging im);
extern void _ImagingAccessDelete(Imaging im, ImagingAccess access);
#define ImagingAccessDelete(im, access) /* nop, for now */
/*#define ImagingAccessDelete(im, access) \
((access)->dynamic ? _ImagingAccessDelete((im), (access)), 0 : 0)) */
extern ImagingPalette ImagingPaletteNew(const char *mode);
extern ImagingPalette ImagingPaletteNewBrowser(void);
@ -291,8 +289,9 @@ extern Imaging ImagingRankFilter(Imaging im, int size, int rank);
extern Imaging ImagingRotate90(Imaging imOut, Imaging imIn);
extern Imaging ImagingRotate180(Imaging imOut, Imaging imIn);
extern Imaging ImagingRotate270(Imaging imOut, Imaging imIn);
extern Imaging ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]);
extern Imaging ImagingTranspose(Imaging imOut, Imaging imIn);
extern Imaging ImagingTransverse(Imaging imOut, Imaging imIn);
extern Imaging ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]);
extern Imaging ImagingTransform(
Imaging imOut, Imaging imIn, int method, int x0, int y0, int x1, int y1,
double *a, int filter, int fill);

View File

@ -28,3 +28,18 @@
#define PREBLEND(mask, in1, in2, tmp1)\
(MULDIV255(in1, (255 - mask), tmp1) + in2)
/* This is to work around a bug in GCC prior 4.9 in 64 bit mode.
GCC generates code with partial dependency which is 3 times slower.
See: http://stackoverflow.com/a/26588074/253146 */
#if defined(__x86_64__) && defined(__SSE__) && ! defined(__NO_INLINE__) && \
! defined(__clang__) && defined(GCC_VERSION) && (GCC_VERSION < 40900)
static float __attribute__((always_inline)) inline _i2f(int v) {
float x;
__asm__("xorps %0, %0; cvtsi2ss %1, %0" : "=X"(x) : "r"(v) );
return x;
}
#else
static float inline _i2f(int v) { return (float) v; }
#endif

View File

@ -28,7 +28,7 @@ ImagingModeFilter(Imaging im, int size)
if (!im || im->bands != 1 || im->type != IMAGING_TYPE_UINT8)
return (Imaging) ImagingError_ModeError();
imOut = ImagingNew(im->mode, im->xsize, im->ysize);
imOut = ImagingNewDirty(im->mode, im->xsize, im->ysize);
if (!imOut)
return NULL;

View File

@ -1642,11 +1642,11 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans)
ImagingSectionLeave(&cookie);
if (result > 0) {
imOut = ImagingNew("P", im->xsize, im->ysize);
imOut = ImagingNewDirty("P", im->xsize, im->ysize);
ImagingSectionEnter(&cookie);
for (i = y = 0; y < im->ysize; y++)
for (x=0; x < im->xsize; x++)
for (x = 0; x < im->xsize; x++)
imOut->image8[y][x] = (unsigned char) newData[i++];
free(newData);

View File

@ -34,9 +34,9 @@ libs = {
'dir': 'tiff-4.0.8',
},
'freetype': {
'url': 'https://download.savannah.gnu.org/releases/freetype/freetype-2.8.tar.gz',
'filename': PILLOW_DEPENDS_DIR + 'freetype-2.8.tar.gz',
'dir': 'freetype-2.8',
'url': 'https://download.savannah.gnu.org/releases/freetype/freetype-2.8.1.tar.gz',
'filename': PILLOW_DEPENDS_DIR + 'freetype-2.8.1.tar.gz',
'dir': 'freetype-2.8.1',
},
'lcms': {
'url': SF_MIRROR+'/project/lcms/lcms/2.7/lcms2-2.7.zip',