mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-27 09:44:31 +03:00
Merge branch 'master' into block-storage
This commit is contained in:
commit
a78e92356f
24
CHANGES.rst
24
CHANGES.rst
|
@ -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]
|
||||||
|
|
||||||
|
|
38
PIL/Image.py
38
PIL/Image.py
|
@ -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")
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
BIN
Tests/images/issue_2278.tif
Normal file
Binary file not shown.
|
@ -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):
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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))
|
||||||
|
|
||||||
|
|
|
@ -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__':
|
||||||
|
|
|
@ -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),
|
||||||
|
|
19
_imaging.c
19
_imaging.c
|
@ -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
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
======================
|
======================
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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',
|
||||||
|
|
Loading…
Reference in New Issue
Block a user