Merge branch 'master' into block-storage

This commit is contained in:
Alexander 2017-09-19 21:08:21 +03:00
commit a78e92356f
22 changed files with 414 additions and 168 deletions

View File

@ -4,6 +4,30 @@ Changelog (Pillow)
4.3.0 (unreleased) 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 - Faster filter operations for Kernel, Gaussian, and Unsharp Mask filters #2679
[homm] [homm]

View File

@ -124,6 +124,16 @@ try:
except ImportError: except ImportError:
HAS_CFFI = False 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): def isImageType(t):
""" """
@ -151,6 +161,7 @@ ROTATE_90 = 2
ROTATE_180 = 3 ROTATE_180 = 3
ROTATE_270 = 4 ROTATE_270 = 4
TRANSPOSE = 5 TRANSPOSE = 5
TRANSVERSE = 6
# transforms # transforms
AFFINE = 0 AFFINE = 0
@ -1404,9 +1415,9 @@ class Image(object):
Performance Note: Not currently implemented in-place in the core layer. 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") 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") raise ValueError("Destination must be a tuple")
if not len(source) in (2, 4): if not len(source) in (2, 4):
raise ValueError("Source must be a 2 or 4-tuple") raise ValueError("Source must be a 2 or 4-tuple")
@ -1430,7 +1441,7 @@ class Image(object):
box = dest + (dest[0] + overlay.width, dest[1] + overlay.height) box = dest + (dest[0] + overlay.width, dest[1] + overlay.height)
# destination image. don't copy if we're using the whole image. # 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 background = self
else: else:
background = self.crop(box) background = self.crop(box)
@ -1875,11 +1886,9 @@ class Image(object):
if isPath(fp): if isPath(fp):
filename = fp filename = fp
open_fp = True open_fp = True
elif sys.version_info >= (3, 4): elif HAS_PATHLIB and isinstance(fp, Path):
from pathlib import Path filename = str(fp)
if isinstance(fp, Path): open_fp = True
filename = str(fp)
open_fp = True
if not filename and hasattr(fp, "name") and isPath(fp.name): if not filename and hasattr(fp, "name") and isPath(fp.name):
# only set the name for metadata purposes # only set the name for metadata purposes
filename = fp.name filename = fp.name
@ -2174,8 +2183,8 @@ class Image(object):
:param method: One of :py:attr:`PIL.Image.FLIP_LEFT_RIGHT`, :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.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.ROTATE_180`, :py:attr:`PIL.Image.ROTATE_270`,
:py:attr:`PIL.Image.TRANSPOSE`. :py:attr:`PIL.Image.TRANSPOSE` or :py:attr:`PIL.Image.TRANSVERSE`.
:returns: Returns a flipped or rotated copy of this image. :returns: Returns a flipped or rotated copy of this image.
""" """
@ -2516,13 +2525,8 @@ def open(fp, mode="r"):
filename = "" filename = ""
if isPath(fp): if isPath(fp):
filename = fp filename = fp
else: elif HAS_PATHLIB and isinstance(fp, Path):
try: filename = str(fp.resolve())
from pathlib import Path
if isinstance(fp, Path):
filename = str(fp.resolve())
except ImportError:
pass
if filename: if filename:
fp = builtins.open(filename, "rb") fp = builtins.open(filename, "rb")

View File

@ -160,6 +160,26 @@ class GaussianBlur(MultibandFilter):
return image.gaussian_blur(self.radius) 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): class UnsharpMask(MultibandFilter):
"""Unsharp mask filter. """Unsharp mask filter.

View File

@ -21,6 +21,7 @@ from . import Image
from ._util import isStringType from ._util import isStringType
import operator import operator
import functools import functools
import warnings
# #
@ -437,6 +438,13 @@ def solarize(image, threshold=128):
def gaussian_blur(im, radius=None): def gaussian_blur(im, radius=None):
""" PIL_usm.gblur(im, [radius])""" """ 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: if radius is None:
radius = 5.0 radius = 5.0
@ -444,12 +452,30 @@ def gaussian_blur(im, radius=None):
return im.im.gaussian_blur(radius) 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): def unsharp_mask(im, radius=None, percent=None, threshold=None):
""" PIL_usm.usm(im, [radius, percent, threshold])""" """ 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: if radius is None:
radius = 5.0 radius = 5.0
if percent is None: 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) 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): def box_blur(image, radius):
@ -478,6 +515,13 @@ def box_blur(image, radius):
in each direction, i.e. 9 pixels in total. in each direction, i.e. 9 pixels in total.
:return: An image. :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() image.load()
return image._new(image.im.box_blur(radius)) 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.image8 = ffi.cast('unsigned char **', vals['image8'])
self.image32 = ffi.cast('int **', vals['image32']) self.image32 = ffi.cast('int **', vals['image32'])
self.image = ffi.cast('unsigned char **', vals['image']) self.image = ffi.cast('unsigned char **', vals['image'])
self.xsize = vals['xsize'] self.xsize, self.ysize = img.im.size
self.ysize = vals['ysize']
# Keep pointer to im object to prevent dereferencing. # Keep pointer to im object to prevent dereferencing.
self._im = img.im 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 dest = self._tags_v1 if legacy_api else self._tags_v2
if info.length == 1: # Three branches:
if legacy_api and self.tagtype[tag] in [5, 10]: # 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, values = values,
dest[tag], = 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: else:
# Spec'd length > 1 or undefined
# Unspec'd, and length > 1
dest[tag] = values dest[tag] = values
def __delitem__(self, tag): def __delitem__(self, tag):
@ -1011,8 +1028,10 @@ class TiffImageFile(ImageFile.ImageFile):
args = rawmode, "" args = rawmode, ""
if JPEGTABLES in self.tag_v2: if JPEGTABLES in self.tag_v2:
# Hack to handle abbreviated JPEG headers # Hack to handle abbreviated JPEG headers
# FIXME This will fail with more than one value # Definition of JPEGTABLES is that the count
self.tile_prefix, = self.tag_v2[JPEGTABLES] # 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": elif compression == "packbits":
args = rawmode args = rawmode
elif compression == "tiff_lzw": elif compression == "tiff_lzw":

View File

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

View File

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

View File

@ -56,7 +56,7 @@ class TestFileTiffMetadata(PillowTestCase):
loaded = Image.open(f) loaded = Image.open(f)
self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], (len(bindata),)) 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[ImageJMetaData], bindata)
self.assertEqual(loaded.tag_v2[ImageJMetaData], bindata) self.assertEqual(loaded.tag_v2[ImageJMetaData], bindata)
@ -69,6 +69,16 @@ class TestFileTiffMetadata(PillowTestCase):
loaded_double = loaded.tag[tag_ids['YawAngle']][0] loaded_double = loaded.tag[tag_ids['YawAngle']][0]
self.assertAlmostEqual(loaded_double, doubledata) 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): def test_read_metadata(self):
img = Image.open('Tests/images/hopper_g4.tif') img = Image.open('Tests/images/hopper_g4.tif')
@ -202,8 +212,8 @@ class TestFileTiffMetadata(PillowTestCase):
im.save(out, tiffinfo=info, compression='raw') im.save(out, tiffinfo=info, compression='raw')
reloaded = Image.open(out) reloaded = Image.open(out)
self.assertEqual(0, reloaded.tag_v2[41988][0].numerator) self.assertEqual(0, reloaded.tag_v2[41988].numerator)
self.assertEqual(0, reloaded.tag_v2[41988][0].denominator) self.assertEqual(0, reloaded.tag_v2[41988].denominator)
def test_expty_values(self): def test_expty_values(self):
data = io.BytesIO( data = io.BytesIO(
@ -220,6 +230,27 @@ class TestFileTiffMetadata(PillowTestCase):
self.fail("Should not be struct value error there.") self.fail("Should not be struct value error there.")
self.assertIn(33432, info) 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__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

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

View File

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

View File

@ -2,7 +2,7 @@ import helper
from helper import unittest, PillowTestCase from helper import unittest, PillowTestCase
from PIL.Image import (FLIP_LEFT_RIGHT, FLIP_TOP_BOTTOM, ROTATE_90, ROTATE_180, from PIL.Image import (FLIP_LEFT_RIGHT, FLIP_TOP_BOTTOM, ROTATE_90, ROTATE_180,
ROTATE_270, TRANSPOSE) ROTATE_270, TRANSPOSE, TRANSVERSE)
class TestImageTranspose(PillowTestCase): class TestImageTranspose(PillowTestCase):
@ -108,6 +108,22 @@ class TestImageTranspose(PillowTestCase):
for mode in ("L", "RGB"): for mode in ("L", "RGB"):
transpose(mode) 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): def test_roundtrip(self):
im = self.hopper['L'] im = self.hopper['L']
@ -124,6 +140,12 @@ class TestImageTranspose(PillowTestCase):
im.transpose(TRANSPOSE), transpose(ROTATE_90, FLIP_TOP_BOTTOM)) im.transpose(TRANSPOSE), transpose(ROTATE_90, FLIP_TOP_BOTTOM))
self.assert_image_equal( self.assert_image_equal(
im.transpose(TRANSPOSE), transpose(ROTATE_270, FLIP_LEFT_RIGHT)) 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__': if __name__ == '__main__':

View File

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

View File

@ -1669,6 +1669,7 @@ _transpose(ImagingObject* self, PyObject* args)
case 2: /* rotate 90 */ case 2: /* rotate 90 */
case 4: /* rotate 270 */ case 4: /* rotate 270 */
case 5: /* transpose */ case 5: /* transpose */
case 6: /* transverse */
imOut = ImagingNewDirty(imIn->mode, imIn->ysize, imIn->xsize); imOut = ImagingNewDirty(imIn->mode, imIn->ysize, imIn->xsize);
break; break;
default: default:
@ -1696,6 +1697,9 @@ _transpose(ImagingObject* self, PyObject* args)
case 5: case 5:
(void) ImagingTranspose(imOut, imIn); (void) ImagingTranspose(imOut, imIn);
break; break;
case 6:
(void) ImagingTransverse(imOut, imIn);
break;
} }
return PyImagingNew(imOut); return PyImagingNew(imOut);
@ -3129,21 +3133,10 @@ _getattr_ptr(ImagingObject* self, void* closure)
static PyObject* static PyObject*
_getattr_unsafe_ptrs(ImagingObject* self, void* closure) _getattr_unsafe_ptrs(ImagingObject* self, void* closure)
{ {
return Py_BuildValue("(ss)(si)(si)(si)(si)(si)(sn)(sn)(sn)(sn)(sn)(si)(si)(sn)", return Py_BuildValue("(sn)(sn)(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,
"image8", self->image->image8, "image8", self->image->image8,
"image32", self->image->image32, "image32", self->image->image32,
"image", self->image->image, "image", self->image->image
"block", self->image->block,
"pixelsize", self->image->pixelsize,
"linesize", self->image->linesize,
"destroy", self->image->destroy
); );
}; };

View File

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

View File

@ -4,13 +4,19 @@
Get One Channel From Image 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, It returns single channel by index or name. For example,
``image.getchannel("A")`` will return alpha channel as separate image. ``image.getchannel("A")`` will return alpha channel as separate image.
``getchannel`` should work up to 6 times faster than ``image.split()[0]`` ``getchannel`` should work up to 6 times faster than ``image.split()[0]``
in previous Pillow versions. in previous Pillow versions.
Box Blur
========
New filter :py:class:`PIL.ImageFilter.BoxBlur` is added.
Partial Resampling Partial Resampling
================== ==================
@ -40,6 +46,22 @@ This release contains several performance improvements:
using a recent version of libjpeg-turbo. 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 Core Image API Changes
====================== ======================

View File

@ -11,6 +11,7 @@
#include "Imaging.h" #include "Imaging.h"
#define PRECISION_BITS 7
typedef struct typedef struct
{ {
@ -49,13 +50,11 @@ ImagingAlphaComposite(Imaging imDst, Imaging imSrc)
ImagingCopyInfo(imOut, imDst); ImagingCopyInfo(imOut, imDst);
for (y = 0; y < imDst->ysize; y++) { for (y = 0; y < imDst->ysize; y++) {
rgba8* dst = (rgba8*) imDst->image[y]; rgba8* dst = (rgba8*) imDst->image[y];
rgba8* src = (rgba8*) imSrc->image[y]; rgba8* src = (rgba8*) imSrc->image[y];
rgba8* out = (rgba8*) imOut->image[y]; rgba8* out = (rgba8*) imOut->image[y];
for (x = 0; x < imDst->xsize; x ++) { for (x = 0; x < imDst->xsize; x ++) {
if (src->a == 0) { if (src->a == 0) {
// Copy 4 bytes at once. // Copy 4 bytes at once.
*out = *dst; *out = *dst;
@ -64,25 +63,20 @@ ImagingAlphaComposite(Imaging imDst, Imaging imSrc)
// Each variable has extra meaningful bits. // Each variable has extra meaningful bits.
// Divisions are rounded. // 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; UINT32 tmpr, tmpg, tmpb;
UINT16 blend = dst->a * (255 - src->a); UINT32 blend = dst->a * (255 - src->a);
UINT16 outa255 = src->a * 255 + blend; UINT32 outa255 = src->a * 255 + blend;
// There we use 7 bits for precision. // There we use 7 bits for precision.
// We could use more, but we go beyond 32 bits. // We could use more, but we go beyond 32 bits.
UINT16 coef1 = src->a * 255 * 255 * 128 / outa255; UINT32 coef1 = src->a * 255 * 255 * (1<<PRECISION_BITS) / outa255;
UINT16 coef2 = 255 * 128 - coef1; UINT32 coef2 = 255 * (1<<PRECISION_BITS) - coef1;
tmpr = src->r * coef1 + dst->r * coef2 + (0x80 << 7); tmpr = src->r * coef1 + dst->r * coef2;
out->r = SHIFTFORDIV255(tmpr) >> 7; tmpg = src->g * coef1 + dst->g * coef2;
tmpg = src->g * coef1 + dst->g * coef2 + (0x80 << 7); tmpb = src->b * coef1 + dst->b * coef2;
out->g = SHIFTFORDIV255(tmpg) >> 7; out->r = SHIFTFORDIV255(tmpr + (0x80<<PRECISION_BITS)) >> PRECISION_BITS;
tmpb = src->b * coef1 + dst->b * coef2 + (0x80 << 7); out->g = SHIFTFORDIV255(tmpg + (0x80<<PRECISION_BITS)) >> PRECISION_BITS;
out->b = SHIFTFORDIV255(tmpb) >> 7; out->b = SHIFTFORDIV255(tmpb + (0x80<<PRECISION_BITS)) >> PRECISION_BITS;
out->a = SHIFTFORDIV255(outa255 + 0x80); out->a = SHIFTFORDIV255(outa255 + 0x80);
} }

View File

@ -5,7 +5,8 @@
Rotating in chunks that fit in the cache can speed up rotation 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 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. */ 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 COORD(v) ((v) < 0.0 ? -1 : ((int)(v)))
#define FLOOR(v) ((v) < 0.0 ? ((int)floor(v)) : ((int)(v))) #define FLOOR(v) ((v) < 0.0 ? ((int)floor(v)) : ((int)(v)))
@ -26,30 +27,27 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn)
ImagingCopyInfo(imOut, imIn); ImagingCopyInfo(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); ImagingSectionEnter(&cookie);
if (imIn->image8) { if (imIn->image8) {
for (y = 0; y < imIn->ysize; y++) { FLIP_LEFT_RIGHT(UINT8, image8)
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];
}
} else { } else {
for (y = 0; y < imIn->ysize; y++) { FLIP_LEFT_RIGHT(INT32, image32)
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];
}
} }
ImagingSectionLeave(&cookie); ImagingSectionLeave(&cookie);
#undef FLIP_LEFT_RIGHT
return imOut; return imOut;
} }
@ -84,6 +82,7 @@ ImagingRotate90(Imaging imOut, Imaging imIn)
{ {
ImagingSectionCookie cookie; ImagingSectionCookie cookie;
int x, y, xx, yy, xr, xxsize, yysize; int x, y, xx, yy, xr, xxsize, yysize;
int xxx, yyy, xxxsize, yyysize;
if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0)
return (Imaging) ImagingError_ModeError(); return (Imaging) ImagingError_ModeError();
@ -92,15 +91,22 @@ ImagingRotate90(Imaging imOut, Imaging imIn)
ImagingCopyInfo(imOut, imIn); ImagingCopyInfo(imOut, imIn);
#define ROTATE_90(image) \ #define ROTATE_90(INT, image) \
for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \ for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \
for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \ for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \
yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \ yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \
xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \ xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \
for (yy = y; yy < yysize; yy++) { \ for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \
xr = imIn->xsize - 1 - x; \ for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \
for (xx = x; xx < xxsize; xx++, xr--) { \ yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize ? yy + ROTATE_SMALL_CHUNK : imIn->ysize; \
imOut->image[xr][yy] = imIn->image[yy][xx]; \ 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); ImagingSectionEnter(&cookie);
if (imIn->image8) if (imIn->image8)
ROTATE_90(image8) ROTATE_90(UINT8, image8)
else else
ROTATE_90(image32) ROTATE_90(INT32, image32)
ImagingSectionLeave(&cookie); ImagingSectionLeave(&cookie);
@ -126,6 +132,7 @@ ImagingTranspose(Imaging imOut, Imaging imIn)
{ {
ImagingSectionCookie cookie; ImagingSectionCookie cookie;
int x, y, xx, yy, xxsize, yysize; int x, y, xx, yy, xxsize, yysize;
int xxx, yyy, xxxsize, yyysize;
if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0)
return (Imaging) ImagingError_ModeError(); return (Imaging) ImagingError_ModeError();
@ -134,14 +141,21 @@ ImagingTranspose(Imaging imOut, Imaging imIn)
ImagingCopyInfo(imOut, imIn); ImagingCopyInfo(imOut, imIn);
#define TRANSPOSE(image) \ #define TRANSPOSE(INT, image) \
for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \ for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \
for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \ for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \
yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \ yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \
xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \ xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \
for (yy = y; yy < yysize; yy++) { \ for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \
for (xx = x; xx < xxsize; xx++) { \ for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \
imOut->image[xx][yy] = imIn->image[yy][xx]; \ 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); ImagingSectionEnter(&cookie);
if (imIn->image8) if (imIn->image8)
TRANSPOSE(image8) TRANSPOSE(UINT8, image8)
else else
TRANSPOSE(image32) TRANSPOSE(INT32, image32)
ImagingSectionLeave(&cookie); 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 Imaging
ImagingRotate180(Imaging imOut, Imaging imIn) ImagingRotate180(Imaging imOut, Imaging imIn)
{ {
@ -175,20 +240,23 @@ ImagingRotate180(Imaging imOut, Imaging imIn)
ImagingCopyInfo(imOut, imIn); ImagingCopyInfo(imOut, imIn);
#define ROTATE_180(image)\ #define ROTATE_180(INT, image) \
for (y = 0; y < imIn->ysize; y++, yr--) {\ for (y = 0; y < imIn->ysize; y++, yr--) { \
xr = imIn->xsize-1;\ INT* in = imIn->image[y]; \
for (x = 0; x < imIn->xsize; x++, xr--)\ INT* out = imOut->image[yr]; \
imOut->image[y][x] = imIn->image[yr][xr];\ xr = imIn->xsize-1; \
for (x = 0; x < imIn->xsize; x++, xr--) \
out[xr] = in[x]; \
} }
ImagingSectionEnter(&cookie); ImagingSectionEnter(&cookie);
yr = imIn->ysize-1; yr = imIn->ysize-1;
if (imIn->image8) if (imIn->image8) {
ROTATE_180(image8) ROTATE_180(UINT8, image8)
else } else {
ROTATE_180(image32) ROTATE_180(INT32, image32)
}
ImagingSectionLeave(&cookie); ImagingSectionLeave(&cookie);
@ -203,6 +271,7 @@ ImagingRotate270(Imaging imOut, Imaging imIn)
{ {
ImagingSectionCookie cookie; ImagingSectionCookie cookie;
int x, y, xx, yy, yr, xxsize, yysize; int x, y, xx, yy, yr, xxsize, yysize;
int xxx, yyy, xxxsize, yyysize;
if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0)
return (Imaging) ImagingError_ModeError(); return (Imaging) ImagingError_ModeError();
@ -211,15 +280,22 @@ ImagingRotate270(Imaging imOut, Imaging imIn)
ImagingCopyInfo(imOut, imIn); ImagingCopyInfo(imOut, imIn);
#define ROTATE_270(image) \ #define ROTATE_270(INT, image) \
for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \ for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \
for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \ for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \
yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \ yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \
xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \ xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \
yr = imIn->ysize - 1 - y; \ for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \
for (yy = y; yy < yysize; yy++, yr--) { \ for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \
for (xx = x; xx < xxsize; xx++) { \ yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize ? yy + ROTATE_SMALL_CHUNK : imIn->ysize; \
imOut->image[xx][yr] = imIn->image[yy][xx]; \ 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); ImagingSectionEnter(&cookie);
if (imIn->image8) if (imIn->image8)
ROTATE_270(image8) ROTATE_270(UINT8, image8)
else else
ROTATE_270(image32) ROTATE_270(INT32, image32)
ImagingSectionLeave(&cookie); ImagingSectionLeave(&cookie);

View File

@ -310,8 +310,9 @@ extern Imaging ImagingRankFilter(Imaging im, int size, int rank);
extern Imaging ImagingRotate90(Imaging imOut, Imaging imIn); extern Imaging ImagingRotate90(Imaging imOut, Imaging imIn);
extern Imaging ImagingRotate180(Imaging imOut, Imaging imIn); extern Imaging ImagingRotate180(Imaging imOut, Imaging imIn);
extern Imaging ImagingRotate270(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 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( extern Imaging ImagingTransform(
Imaging imOut, Imaging imIn, int method, int x0, int y0, int x1, int y1, Imaging imOut, Imaging imIn, int method, int x0, int y0, int x1, int y1,
double *a, int filter, int fill); double *a, int filter, int fill);

View File

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