Merge branch 'master' into tiff-jpeg-quality

This commit is contained in:
Andrew Murray 2019-06-30 14:03:09 +10:00 committed by GitHub
commit c994b783ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 562 additions and 96 deletions

3
.gitignore vendored
View File

@ -67,6 +67,9 @@ docs/_build/
\#*#
.#*
#VS Code
.vscode
#Komodo
*.komodoproject

View File

@ -7,7 +7,7 @@ make clean
make install-coverage
python selftest.py
python -m pytest -vx --cov PIL --cov-report term Tests
python -m pytest -v -x --cov PIL --cov-report term Tests
pushd /tmp/check-manifest && check-manifest --ignore ".coveragerc,.editorconfig,*.yml,*.yaml,tox.ini" && popd

View File

@ -5,6 +5,36 @@ Changelog (Pillow)
6.1.0 (unreleased)
------------------
- Create GIF deltas from background colour of GIF frames if disposal mode is 2 #3708
[sircinnamon, radarhere]
- Added ImageSequence all_frames #3778
[radarhere]
- Use unsigned int to store TIFF IFD offsets #3923
[cgohlke]
- Include CPPFLAGS when searching for libraries #3819
[jefferyto]
- Updated TIFF tile descriptors to match current decoding functionality #3795
[dmnisson]
- Added an `image.entropy()` method (second revision) #3608
[fish2000]
- Pass the correct types to PyArg_ParseTuple #3880
[QuLogic]
- Fixed crash when loading non-font bytes #3912
[radarhere]
- Fix SPARC memory alignment issues in Pack/Unpack functions #3858
[kulikjak]
- Added CMYK;16B and CMYK;16N unpackers #3913
[radarhere]
- Fixed bugs in calculating text size #3864
[radarhere]

View File

@ -105,6 +105,8 @@ class TestCoreMemory(PillowTestCase):
Image.new("RGB", (10, 10))
self.assertRaises(ValueError, Image.core.set_blocks_max, -1)
if sys.maxsize < 2 ** 32:
self.assertRaises(ValueError, Image.core.set_blocks_max, 2 ** 29)
@unittest.skipIf(is_pypy, "images are not collected")
def test_set_blocks_max_stats(self):

View File

@ -1,6 +1,6 @@
from .helper import unittest, PillowTestCase, hopper, netpbm_available
from PIL import Image, ImagePalette, GifImagePlugin
from PIL import Image, ImagePalette, GifImagePlugin, ImageDraw
from io import BytesIO
@ -59,7 +59,7 @@ class TestFileGif(PillowTestCase):
return len(test_file.getvalue())
self.assertEqual(test_grayscale(0), 800)
self.assertEqual(test_grayscale(1), 38)
self.assertEqual(test_grayscale(1), 44)
self.assertEqual(test_bilevel(0), 800)
self.assertEqual(test_bilevel(1), 800)
@ -318,6 +318,103 @@ class TestFileGif(PillowTestCase):
img.seek(img.tell() + 1)
self.assertEqual(img.disposal_method, i + 1)
def test_dispose2_palette(self):
out = self.tempfile("temp.gif")
# 4 backgrounds: White, Grey, Black, Red
circles = [(255, 255, 255), (153, 153, 153), (0, 0, 0), (255, 0, 0)]
im_list = []
for circle in circles:
img = Image.new("RGB", (100, 100), (255, 0, 0))
# Red circle in center of each frame
d = ImageDraw.Draw(img)
d.ellipse([(40, 40), (60, 60)], fill=circle)
im_list.append(img)
im_list[0].save(out, save_all=True, append_images=im_list[1:], disposal=2)
img = Image.open(out)
for i, circle in enumerate(circles):
img.seek(i)
rgb_img = img.convert("RGB")
# Check top left pixel matches background
self.assertEqual(rgb_img.getpixel((0, 0)), (255, 0, 0))
# Center remains red every frame
self.assertEqual(rgb_img.getpixel((50, 50)), circle)
def test_dispose2_diff(self):
out = self.tempfile("temp.gif")
# 4 frames: red/blue, red/red, blue/blue, red/blue
circles = [
((255, 0, 0, 255), (0, 0, 255, 255)),
((255, 0, 0, 255), (255, 0, 0, 255)),
((0, 0, 255, 255), (0, 0, 255, 255)),
((255, 0, 0, 255), (0, 0, 255, 255)),
]
im_list = []
for i in range(len(circles)):
# Transparent BG
img = Image.new("RGBA", (100, 100), (255, 255, 255, 0))
# Two circles per frame
d = ImageDraw.Draw(img)
d.ellipse([(0, 30), (40, 70)], fill=circles[i][0])
d.ellipse([(60, 30), (100, 70)], fill=circles[i][1])
im_list.append(img)
im_list[0].save(
out, save_all=True, append_images=im_list[1:], disposal=2, transparency=0
)
img = Image.open(out)
for i, colours in enumerate(circles):
img.seek(i)
rgb_img = img.convert("RGBA")
# Check left circle is correct colour
self.assertEqual(rgb_img.getpixel((20, 50)), colours[0])
# Check right circle is correct colour
self.assertEqual(rgb_img.getpixel((80, 50)), colours[1])
# Check BG is correct colour
self.assertEqual(rgb_img.getpixel((1, 1)), (255, 255, 255, 0))
def test_dispose2_background(self):
out = self.tempfile("temp.gif")
im_list = []
im = Image.new("P", (100, 100))
d = ImageDraw.Draw(im)
d.rectangle([(50, 0), (100, 100)], fill="#f00")
d.rectangle([(0, 0), (50, 100)], fill="#0f0")
im_list.append(im)
im = Image.new("P", (100, 100))
d = ImageDraw.Draw(im)
d.rectangle([(0, 0), (100, 50)], fill="#f00")
d.rectangle([(0, 50), (100, 100)], fill="#0f0")
im_list.append(im)
im_list[0].save(
out, save_all=True, append_images=im_list[1:], disposal=[0, 2], background=1
)
im = Image.open(out)
im.seek(1)
self.assertEqual(im.getpixel((0, 0)), 0)
def test_iss634(self):
img = Image.open("Tests/images/iss634.gif")
# seek to the second frame

View File

@ -122,7 +122,7 @@ class TestFileLibTiff(LibTiffTestCase):
self.assertEqual(im.mode, "RGB")
self.assertEqual(im.size, (278, 374))
self.assertEqual(im.tile[0][:3], ("tiff_adobe_deflate", (0, 0, 278, 374), 0))
self.assertEqual(im.tile[0][:3], ("libtiff", (0, 0, 278, 374), 0))
im.load()
self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
@ -710,10 +710,10 @@ class TestFileLibTiff(LibTiffTestCase):
im.tile,
[
(
"tiff_adobe_deflate",
"libtiff",
(0, 0, 100, 40),
0,
("RGB;16N", "tiff_adobe_deflate", False),
("RGB;16N", "tiff_adobe_deflate", False, 8),
)
],
)
@ -727,7 +727,8 @@ class TestFileLibTiff(LibTiffTestCase):
self.assertEqual(im.mode, "RGBA")
self.assertEqual(im.size, (100, 40))
self.assertEqual(
im.tile, [("tiff_lzw", (0, 0, 100, 40), 0, ("RGBa;16N", "tiff_lzw", False))]
im.tile,
[("libtiff", (0, 0, 100, 40), 0, ("RGBa;16N", "tiff_lzw", False, 38236))],
)
im.load()
@ -746,7 +747,7 @@ class TestFileLibTiff(LibTiffTestCase):
self.assertEqual(im.mode, "RGB")
self.assertEqual(im.size, (256, 256))
self.assertEqual(
im.tile, [("jpeg", (0, 0, 256, 256), 0, ("RGB", "jpeg", False))]
im.tile, [("libtiff", (0, 0, 256, 256), 0, ("RGB", "jpeg", False, 5122))]
)
im.load()

View File

@ -0,0 +1,17 @@
from .helper import PillowTestCase, hopper
class TestImageEntropy(PillowTestCase):
def test_entropy(self):
def entropy(mode):
return hopper(mode).entropy()
self.assertAlmostEqual(entropy("1"), 0.9138803254693582)
self.assertAlmostEqual(entropy("L"), 7.06650513081286)
self.assertAlmostEqual(entropy("I"), 7.06650513081286)
self.assertAlmostEqual(entropy("F"), 7.06650513081286)
self.assertAlmostEqual(entropy("P"), 5.0530452472519745)
self.assertAlmostEqual(entropy("RGB"), 8.821286587714319)
self.assertAlmostEqual(entropy("RGBA"), 7.42724306524488)
self.assertAlmostEqual(entropy("CMYK"), 7.4272430652448795)
self.assertAlmostEqual(entropy("YCbCr"), 7.698360534903628)

View File

@ -420,6 +420,10 @@ class TestImageFont(PillowTestCase):
self.assertRaises(IOError, ImageFont.load_path, filename)
self.assertRaises(IOError, ImageFont.truetype, filename)
def test_load_non_font_bytes(self):
with open("Tests/images/hopper.jpg", "rb") as f:
self.assertRaises(IOError, ImageFont.truetype, f)
def test_default_font(self):
# Arrange
txt = 'This is a "better than nothing" default font.'

View File

@ -74,3 +74,25 @@ class TestImageSequence(PillowTestCase):
im.seek(0)
color2 = im.getpalette()[0:3]
self.assertEqual(color1, color2)
def test_all_frames(self):
# Test a single image
im = Image.open("Tests/images/iss634.gif")
ims = ImageSequence.all_frames(im)
self.assertEqual(len(ims), 42)
for i, im_frame in enumerate(ims):
self.assertFalse(im_frame is im)
im.seek(i)
self.assert_image_equal(im, im_frame)
# Test a series of images
ims = ImageSequence.all_frames([im, hopper(), im])
self.assertEqual(len(ims), 85)
# Test an operation
ims = ImageSequence.all_frames(im, lambda im_frame: im_frame.rotate(90))
for i, im_frame in enumerate(ims):
im.seek(i)
self.assert_image_equal(im.rotate(90), im_frame)

View File

@ -16,7 +16,7 @@ Open, rotate, and display an image (using the default viewer)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The following script loads an image, rotates it 45 degrees, and displays it
using an external viewer (usually xv on Unix, and the paint program on
using an external viewer (usually xv on Unix, and the Paint program on
Windows).
.. code-block:: python
@ -116,15 +116,66 @@ ITU-R 709, using the D65 luminant) to the CIE XYZ color space:
rgb2xyz = (
0.412453, 0.357580, 0.180423, 0,
0.212671, 0.715160, 0.072169, 0,
0.019334, 0.119193, 0.950227, 0 )
0.019334, 0.119193, 0.950227, 0)
out = im.convert("RGB", rgb2xyz)
.. automethod:: PIL.Image.Image.copy
.. automethod:: PIL.Image.Image.crop
This crops the input image with the provided coordinates:
.. code-block:: python
from PIL import Image
im = Image.open("hopper.jpg")
# The crop method from the Image module takes four coordinates as input.
# The right can also be represented as (left+width)
# and lower can be represented as (upper+height).
(left, upper, right, lower) = (20, 20, 100, 100)
# Here the image "im" is cropped and assigned to new variable im_crop
im_crop = im.crop((left, upper, right, lower))
.. automethod:: PIL.Image.Image.draft
.. automethod:: PIL.Image.Image.filter
This blurs the input image using a filter from the ``ImageFilter`` module:
.. code-block:: python
from PIL import Image, ImageFilter
im = Image.open("hopper.jpg")
# Blur the input image using the filter ImageFilter.BLUR
im_blurred = im.filter(filter=ImageFilter.BLUR)
.. automethod:: PIL.Image.Image.getbands
This helps to get the bands of the input image:
.. code-block:: python
from PIL import Image
im = Image.open("hopper.jpg")
print(im.getbands()) # Returns ('R', 'G', 'B')
.. automethod:: PIL.Image.Image.getbbox
This helps to get the bounding box coordinates of the input image:
.. code-block:: python
from PIL import Image
im = Image.open("hopper.jpg")
print(im.getbbox())
# Returns four coordinates in the format (left, upper, right, lower)
.. automethod:: PIL.Image.Image.getcolors
.. automethod:: PIL.Image.Image.getdata
.. automethod:: PIL.Image.Image.getextrema
@ -140,8 +191,35 @@ ITU-R 709, using the D65 luminant) to the CIE XYZ color space:
.. automethod:: PIL.Image.Image.putpixel
.. automethod:: PIL.Image.Image.quantize
.. automethod:: PIL.Image.Image.resize
This resizes the given image from ``(width, height)`` to ``(width/2, height/2)``:
.. code-block:: python
from PIL import Image
im = Image.open("hopper.jpg")
# Provide the target width and height of the image
(width, height) = (im.width // 2, im.height // 2)
im_resized = im.resize((width, height))
.. automethod:: PIL.Image.Image.remap_palette
.. automethod:: PIL.Image.Image.rotate
This rotates the input image by ``theta`` degrees counter clockwise:
.. code-block:: python
from PIL import Image
im = Image.open("hopper.jpg")
# Rotate the image by 60 degrees counter clockwise
theta = 60
# Angle is in degrees counter clockwise
im_rotated = im.rotate(angle=theta)
.. automethod:: PIL.Image.Image.save
.. automethod:: PIL.Image.Image.seek
.. automethod:: PIL.Image.Image.show
@ -154,6 +232,21 @@ ITU-R 709, using the D65 luminant) to the CIE XYZ color space:
.. automethod:: PIL.Image.Image.tostring
.. automethod:: PIL.Image.Image.transform
.. automethod:: PIL.Image.Image.transpose
This flips the input image by using the ``Image.FLIP_LEFT_RIGHT`` method.
.. code-block:: python
from PIL import Image
im = Image.open("hopper.jpg")
# Flip the image from left to right
im_flipped = im.transpose(method=Image.FLIP_LEFT_RIGHT)
# To flip the image from top to bottom,
# use the method "Image.FLIP_TOP_BOTTOM"
.. automethod:: PIL.Image.Image.verify
.. automethod:: PIL.Image.Image.fromstring

View File

@ -11,6 +11,14 @@ An optional ``include_layered_windows`` parameter has been added to ``ImageGrab.
defaulting to ``False``. If true, layered windows will be included in the resulting
image on Windows.
ImageSequence.all_frames
^^^^^^^^^^^^^^^^^^^^^^^^
A new method to facilitate applying a given function to all frames in an image, or to
all frames in a list of images. The frames are returned as a list of separate images.
For example, ``ImageSequence.all_frames(im, lambda im_frame: im_frame.rotate(90))``
could be used to return all frames from an image, each rotated 90 degrees.
Variation fonts
^^^^^^^^^^^^^^^

View File

@ -90,6 +90,8 @@ def testimage():
2
>>> len(im.histogram())
768
>>> '%.7f' % im.entropy()
'8.8212866'
>>> _info(im.point(list(range(256))*3))
(None, 'RGB', (128, 128))
>>> _info(im.resize((64, 64)))

View File

@ -386,8 +386,8 @@ class pil_build_ext(build_ext):
_add_directory(library_dirs, lib_root)
_add_directory(include_dirs, include_root)
# respect CFLAGS/LDFLAGS
for k in ("CFLAGS", "LDFLAGS"):
# respect CFLAGS/CPPFLAGS/LDFLAGS
for k in ("CFLAGS", "CPPFLAGS", "LDFLAGS"):
if k in os.environ:
for match in re.finditer(r"-I([^\s]+)", os.environ[k]):
_add_directory(include_dirs, match.group(1))

View File

@ -426,6 +426,7 @@ def _write_multiple_frames(im, fp, palette):
im_frames = []
frame_count = 0
background_im = None
for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])):
for im_frame in ImageSequence.Iterator(imSequence):
# a copy is required here since seek can still mutate the image
@ -445,11 +446,22 @@ def _write_multiple_frames(im, fp, palette):
if im_frames:
# delta frame
previous = im_frames[-1]
if _get_palette_bytes(im_frame) == _get_palette_bytes(previous["im"]):
delta = ImageChops.subtract_modulo(im_frame, previous["im"])
if encoderinfo.get("disposal") == 2:
if background_im is None:
background = _get_background(
im,
im.encoderinfo.get("background", im.info.get("background")),
)
background_im = Image.new("P", im_frame.size, background)
background_im.putpalette(im_frames[0]["im"].palette)
base_im = background_im
else:
base_im = previous["im"]
if _get_palette_bytes(im_frame) == _get_palette_bytes(base_im):
delta = ImageChops.subtract_modulo(im_frame, base_im)
else:
delta = ImageChops.subtract_modulo(
im_frame.convert("RGB"), previous["im"].convert("RGB")
im_frame.convert("RGB"), base_im.convert("RGB")
)
bbox = delta.getbbox()
if not bbox:
@ -683,10 +695,12 @@ def _get_color_table_size(palette_bytes):
# calculate the palette size for the header
import math
color_table_size = int(math.ceil(math.log(len(palette_bytes) // 3, 2))) - 1
if color_table_size < 0:
color_table_size = 0
return color_table_size
if not palette_bytes:
return 0
elif len(palette_bytes) < 9:
return 1
else:
return int(math.ceil(math.log(len(palette_bytes) // 3, 2))) - 1
def _get_header_palette(palette_bytes):
@ -717,6 +731,18 @@ def _get_palette_bytes(im):
return im.palette.palette
def _get_background(im, infoBackground):
background = 0
if infoBackground:
background = infoBackground
if isinstance(background, tuple):
# WebPImagePlugin stores an RGBA value in info["background"]
# So it must be converted to the same format as GifImagePlugin's
# info["background"] - a global color table index
background = im.palette.getcolor(background)
return background
def _get_global_header(im, info):
"""Return a list of strings representing a GIF header"""
@ -736,14 +762,7 @@ def _get_global_header(im, info):
if im.info.get("version") == b"89a":
version = b"89a"
background = 0
if "background" in info:
background = info["background"]
if isinstance(background, tuple):
# WebPImagePlugin stores an RGBA value in info["background"]
# So it must be converted to the same format as GifImagePlugin's
# info["background"] - a global color table index
background = im.palette.getcolor(background)
background = _get_background(im, info.get("background"))
palette_bytes = _get_palette_bytes(im)
color_table_size = _get_color_table_size(palette_bytes)

View File

@ -1405,6 +1405,7 @@ class Image(object):
bi-level image (mode "1") or a greyscale image ("L").
:param mask: An optional mask.
:param extrema: An optional tuple of manually-specified extrema.
:returns: A list containing pixel counts.
"""
self.load()
@ -1417,6 +1418,32 @@ class Image(object):
return self.im.histogram(extrema)
return self.im.histogram()
def entropy(self, mask=None, extrema=None):
"""
Calculates and returns the entropy for the image.
A bilevel image (mode "1") is treated as a greyscale ("L")
image by this method.
If a mask is provided, the method employs the histogram for
those parts of the image where the mask image is non-zero.
The mask image must have the same size as the image, and be
either a bi-level image (mode "1") or a greyscale image ("L").
:param mask: An optional mask.
:param extrema: An optional tuple of manually-specified extrema.
:returns: A float value representing the image entropy
"""
self.load()
if mask:
mask.load()
return self.im.entropy((0, 0), mask.im)
if self.mode in ("I", "F"):
if extrema is None:
extrema = self.getextrema()
return self.im.entropy(extrema)
return self.im.entropy()
def offset(self, xoffset, yoffset=None):
raise NotImplementedError(
"offset() has been removed. Please call ImageChops.offset() instead."

View File

@ -545,6 +545,8 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
try:
return freetype(font)
except IOError:
if not isPath(font):
raise
ttf_filename = os.path.basename(font)
dirs = []

View File

@ -54,3 +54,25 @@ class Iterator(object):
def next(self):
return self.__next__()
def all_frames(im, func=None):
"""
Applies a given function to all frames in an image or a list of images.
The frames are returned as a list of separate images.
:param im: An image, or a list of images.
:param func: The function to apply to all of the image frames.
:returns: A list of images.
"""
if not isinstance(im, list):
im = [im]
ims = []
for imSequence in im:
current = imSequence.tell()
ims += [im_frame.copy() for im_frame in Iterator(imSequence)]
imSequence.seek(current)
return [func(im) for im in ims] if func else ims

View File

@ -1124,7 +1124,7 @@ class TiffImageFile(ImageFile.ImageFile):
# (self._compression, (extents tuple),
# 0, (rawmode, self._compression, fp))
extents = self.tile[0][1]
args = list(self.tile[0][3]) + [self.tag_v2.offset]
args = list(self.tile[0][3])
# To be nice on memory footprint, if there's a
# file descriptor, use that instead of reading
@ -1331,8 +1331,8 @@ class TiffImageFile(ImageFile.ImageFile):
# Offset in the tile tuple is 0, we go from 0,0 to
# w,h, and we only do this once -- eds
a = (rawmode, self._compression, False)
self.tile.append((self._compression, (0, 0, xsize, ysize), 0, a))
a = (rawmode, self._compression, False, self.tag_v2.offset)
self.tile.append(("libtiff", (0, 0, xsize, ysize), 0, a))
elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2:
# striped image

View File

@ -37,8 +37,8 @@ def i16le(c, o=0):
"""
Converts a 2-bytes (16 bits) string to an unsigned integer.
c: string containing bytes to convert
o: offset of bytes to convert in string
:param c: string containing bytes to convert
:param o: offset of bytes to convert in string
"""
return unpack_from("<H", c, o)[0]
@ -47,8 +47,8 @@ def si16le(c, o=0):
"""
Converts a 2-bytes (16 bits) string to a signed integer.
c: string containing bytes to convert
o: offset of bytes to convert in string
:param c: string containing bytes to convert
:param o: offset of bytes to convert in string
"""
return unpack_from("<h", c, o)[0]
@ -57,8 +57,8 @@ def i32le(c, o=0):
"""
Converts a 4-bytes (32 bits) string to an unsigned integer.
c: string containing bytes to convert
o: offset of bytes to convert in string
:param c: string containing bytes to convert
:param o: offset of bytes to convert in string
"""
return unpack_from("<I", c, o)[0]
@ -67,8 +67,8 @@ def si32le(c, o=0):
"""
Converts a 4-bytes (32 bits) string to a signed integer.
c: string containing bytes to convert
o: offset of bytes to convert in string
:param c: string containing bytes to convert
:param o: offset of bytes to convert in string
"""
return unpack_from("<i", c, o)[0]

View File

@ -86,6 +86,9 @@
#include "py3.h"
#define _USE_MATH_DEFINES
#include <math.h>
/* Configuration stuff. Feel free to undef things you don't need. */
#define WITH_IMAGECHOPS /* ImageChops support */
#define WITH_IMAGEDRAW /* ImageDraw support */
@ -1176,59 +1179,68 @@ _getpixel(ImagingObject* self, PyObject* args)
return getpixel(self->image, self->access, x, y);
}
union hist_extrema {
UINT8 u[2];
INT32 i[2];
FLOAT32 f[2];
};
static union hist_extrema*
parse_histogram_extremap(ImagingObject* self, PyObject* extremap,
union hist_extrema* ep)
{
int i0, i1;
double f0, f1;
if (extremap) {
switch (self->image->type) {
case IMAGING_TYPE_UINT8:
if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1))
return NULL;
ep->u[0] = CLIP8(i0);
ep->u[1] = CLIP8(i1);
break;
case IMAGING_TYPE_INT32:
if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1))
return NULL;
ep->i[0] = i0;
ep->i[1] = i1;
break;
case IMAGING_TYPE_FLOAT32:
if (!PyArg_ParseTuple(extremap, "dd", &f0, &f1))
return NULL;
ep->f[0] = (FLOAT32) f0;
ep->f[1] = (FLOAT32) f1;
break;
default:
return NULL;
}
} else {
return NULL;
}
return ep;
}
static PyObject*
_histogram(ImagingObject* self, PyObject* args)
{
ImagingHistogram h;
PyObject* list;
int i;
union {
UINT8 u[2];
INT32 i[2];
FLOAT32 f[2];
} extrema;
void* ep;
int i0, i1;
double f0, f1;
union hist_extrema extrema;
union hist_extrema* ep;
PyObject* extremap = NULL;
ImagingObject* maskp = NULL;
if (!PyArg_ParseTuple(args, "|OO!", &extremap, &Imaging_Type, &maskp))
return NULL;
if (extremap) {
ep = &extrema;
switch (self->image->type) {
case IMAGING_TYPE_UINT8:
if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1))
return NULL;
/* FIXME: clip */
extrema.u[0] = i0;
extrema.u[1] = i1;
break;
case IMAGING_TYPE_INT32:
if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1))
return NULL;
extrema.i[0] = i0;
extrema.i[1] = i1;
break;
case IMAGING_TYPE_FLOAT32:
if (!PyArg_ParseTuple(extremap, "dd", &f0, &f1))
return NULL;
extrema.f[0] = (FLOAT32) f0;
extrema.f[1] = (FLOAT32) f1;
break;
default:
ep = NULL;
break;
}
} else
ep = NULL;
return NULL;
/* Using a var to avoid allocations. */
ep = parse_histogram_extremap(self, extremap, &extrema);
h = ImagingGetHistogram(self->image, (maskp) ? maskp->image : NULL, ep);
if (!h)
return NULL;
return NULL;
/* Build an integer list containing the histogram */
list = PyList_New(h->bands * 256);
@ -1243,11 +1255,59 @@ _histogram(ImagingObject* self, PyObject* args)
PyList_SetItem(list, i, item);
}
/* Destroy the histogram structure */
ImagingHistogramDelete(h);
return list;
}
static PyObject*
_entropy(ImagingObject* self, PyObject* args)
{
ImagingHistogram h;
int idx, length;
long sum;
double entropy, fsum, p;
union hist_extrema extrema;
union hist_extrema* ep;
PyObject* extremap = NULL;
ImagingObject* maskp = NULL;
if (!PyArg_ParseTuple(args, "|OO!", &extremap, &Imaging_Type, &maskp))
return NULL;
/* Using a local var to avoid allocations. */
ep = parse_histogram_extremap(self, extremap, &extrema);
h = ImagingGetHistogram(self->image, (maskp) ? maskp->image : NULL, ep);
if (!h)
return NULL;
/* Calculate the histogram entropy */
/* First, sum the histogram data */
length = h->bands * 256;
sum = 0;
for (idx = 0; idx < length; idx++) {
sum += h->histogram[idx];
}
/* Next, normalize the histogram data, */
/* using the histogram sum value */
fsum = (double)sum;
entropy = 0.0;
for (idx = 0; idx < length; idx++) {
p = (double)h->histogram[idx] / fsum;
if (p != 0.0) {
entropy += p * log(p) * M_LOG2E;
}
}
/* Destroy the histogram structure */
ImagingHistogramDelete(h);
return PyFloat_FromDouble(-entropy);
}
#ifdef WITH_MODEFILTER
static PyObject*
_modefilter(ImagingObject* self, PyObject* args)
@ -3193,6 +3253,7 @@ static struct PyMethodDef methods[] = {
{"expand", (PyCFunction)_expand_image, 1},
{"filter", (PyCFunction)_filter, 1},
{"histogram", (PyCFunction)_histogram, 1},
{"entropy", (PyCFunction)_entropy, 1},
#ifdef WITH_MODEFILTER
{"modefilter", (PyCFunction)_modefilter, 1},
#endif
@ -3625,6 +3686,12 @@ _set_blocks_max(PyObject* self, PyObject* args)
"blocks_max should be greater than 0");
return NULL;
}
else if ( blocks_max > SIZE_MAX/sizeof(ImagingDefaultArena.blocks_pool[0])) {
PyErr_SetString(PyExc_ValueError,
"blocks_max is too large");
return NULL;
}
if ( ! ImagingMemorySetBlocksMax(&ImagingDefaultArena, blocks_max)) {
ImagingError_MemoryError();
@ -3912,4 +3979,3 @@ init_imaging(void)
setup_module(m);
}
#endif

View File

@ -265,7 +265,7 @@ getfont(PyObject* self_, PyObject* args, PyObject* kw)
return NULL;
}
if (!PyArg_ParseTupleAndKeywords(args, kw, "eti|is"PY_ARG_BYTES_LENGTH"i",
if (!PyArg_ParseTupleAndKeywords(args, kw, "etn|ns"PY_ARG_BYTES_LENGTH"n",
kwlist,
Py_FileSystemDefaultEncoding, &filename,
&size, &index, &encoding, &font_bytes,
@ -315,6 +315,7 @@ getfont(PyObject* self_, PyObject* args, PyObject* kw)
if (error) {
if (self->font_bytes) {
PyMem_Free(self->font_bytes);
self->font_bytes = NULL;
}
Py_DECREF(self);
return geterror(error);
@ -1006,6 +1007,9 @@ font_render(FontObject* self, PyObject* args)
num_coords = PyObject_Length(axes);
coords = malloc(2 * sizeof(coords));
if (coords == NULL) {
return PyErr_NoMemory();
}
for (i = 0; i < num_coords; i++) {
item = PyList_GET_ITEM(axes, i);
if (PyFloat_Check(item))
@ -1015,6 +1019,7 @@ font_render(FontObject* self, PyObject* args)
else if (PyNumber_Check(item))
coord = PyFloat_AsDouble(item);
else {
free(coords);
PyErr_SetString(PyExc_TypeError, "list must contain numbers");
return NULL;
}
@ -1022,6 +1027,7 @@ font_render(FontObject* self, PyObject* args)
}
error = FT_Set_Var_Design_Coordinates(self->face, num_coords, coords);
free(coords);
if (error)
return geterror(error);

View File

@ -503,9 +503,9 @@ PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args)
char* rawmode;
char* compname;
int fp;
int ifdoffset;
uint32 ifdoffset;
if (! PyArg_ParseTuple(args, "sssii", &mode, &rawmode, &compname, &fp, &ifdoffset))
if (! PyArg_ParseTuple(args, "sssiI", &mode, &rawmode, &compname, &fp, &ifdoffset))
return NULL;
TRACE(("new tiff decoder %s\n", compname));

View File

@ -126,7 +126,7 @@ _encode(ImagingEncoderObject* encoder, PyObject* args)
Py_ssize_t bufsize = 16384;
if (!PyArg_ParseTuple(args, "|i", &bufsize))
if (!PyArg_ParseTuple(args, "|n", &bufsize))
return NULL;
buf = PyBytes_FromStringAndSize(NULL, bufsize);
@ -180,7 +180,7 @@ _encode_to_file(ImagingEncoderObject* encoder, PyObject* args)
Py_ssize_t fh;
Py_ssize_t bufsize = 16384;
if (!PyArg_ParseTuple(args, "i|i", &fh, &bufsize))
if (!PyArg_ParseTuple(args, "n|n", &fh, &bufsize))
return NULL;
/* Allocate an encoder buffer */
@ -229,7 +229,7 @@ _setimage(ImagingEncoderObject* encoder, PyObject* args)
x0 = y0 = x1 = y1 = 0;
/* FIXME: should publish the ImagingType descriptor */
if (!PyArg_ParseTuple(args, "O|(iiii)", &op, &x0, &y0, &x1, &y1))
if (!PyArg_ParseTuple(args, "O|(nnnn)", &op, &x0, &y0, &x1, &y1))
return NULL;
im = PyImaging_AsImaging(op);
if (!im)
@ -409,7 +409,7 @@ PyImaging_GifEncoderNew(PyObject* self, PyObject* args)
char *rawmode;
Py_ssize_t bits = 8;
Py_ssize_t interlace = 0;
if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &bits, &interlace))
if (!PyArg_ParseTuple(args, "ss|nn", &mode, &rawmode, &bits, &interlace))
return NULL;
encoder = PyImaging_EncoderNew(sizeof(GIFENCODERSTATE));
@ -441,7 +441,7 @@ PyImaging_PcxEncoderNew(PyObject* self, PyObject* args)
char *rawmode;
Py_ssize_t bits = 8;
if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &bits)) {
if (!PyArg_ParseTuple(args, "ss|n", &mode, &rawmode, &bits)) {
return NULL;
}
@ -474,7 +474,7 @@ PyImaging_RawEncoderNew(PyObject* self, PyObject* args)
Py_ssize_t stride = 0;
Py_ssize_t ystep = 1;
if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &stride, &ystep))
if (!PyArg_ParseTuple(args, "ss|nn", &mode, &rawmode, &stride, &ystep))
return NULL;
encoder = PyImaging_EncoderNew(0);
@ -506,7 +506,7 @@ PyImaging_TgaRleEncoderNew(PyObject* self, PyObject* args)
char *rawmode;
Py_ssize_t ystep = 1;
if (!PyArg_ParseTuple(args, "ss|i", &mode, &rawmode, &ystep))
if (!PyArg_ParseTuple(args, "ss|n", &mode, &rawmode, &ystep))
return NULL;
encoder = PyImaging_EncoderNew(0);
@ -567,7 +567,7 @@ PyImaging_ZipEncoderNew(PyObject* self, PyObject* args)
Py_ssize_t compress_type = -1;
char* dictionary = NULL;
Py_ssize_t dictionary_size = 0;
if (!PyArg_ParseTuple(args, "ss|iii"PY_ARG_BYTES_LENGTH, &mode, &rawmode,
if (!PyArg_ParseTuple(args, "ss|nnn"PY_ARG_BYTES_LENGTH, &mode, &rawmode,
&optimize,
&compress_level, &compress_type,
&dictionary, &dictionary_size))
@ -650,7 +650,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
PyObject *item;
if (! PyArg_ParseTuple(args, "sssisOO", &mode, &rawmode, &compname, &fp, &filename, &tags, &types)) {
if (! PyArg_ParseTuple(args, "sssnsOO", &mode, &rawmode, &compname, &fp, &filename, &tags, &types)) {
return NULL;
}
@ -1025,7 +1025,7 @@ PyImaging_JpegEncoderNew(PyObject* self, PyObject* args)
char* rawExif = NULL;
Py_ssize_t rawExifLen = 0;
if (!PyArg_ParseTuple(args, "ss|iiiiiiiiO"PY_ARG_BYTES_LENGTH""PY_ARG_BYTES_LENGTH,
if (!PyArg_ParseTuple(args, "ss|nnnnnnnnO"PY_ARG_BYTES_LENGTH""PY_ARG_BYTES_LENGTH,
&mode, &rawmode, &quality,
&progressive, &smooth, &optimize, &streamtype,
&xdpi, &ydpi, &subsampling, &qtables, &extra, &extra_size,
@ -1095,7 +1095,6 @@ PyImaging_JpegEncoderNew(PyObject* self, PyObject* args)
#endif
/* -------------------------------------------------------------------- */
/* JPEG 2000 */
/* -------------------------------------------------------------------- */
@ -1141,7 +1140,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args)
OPJ_CINEMA_MODE cine_mode;
Py_ssize_t fd = -1;
if (!PyArg_ParseTuple(args, "ss|OOOsOIOOOssi", &mode, &format,
if (!PyArg_ParseTuple(args, "ss|OOOsOnOOOssn", &mode, &format,
&offset, &tile_offset, &tile_size,
&quality_mode, &quality_layers, &num_resolutions,
&cblk_size, &precinct_size,

View File

@ -251,6 +251,15 @@ ImagingPackRGB(UINT8* out, const UINT8* in, int pixels)
{
int i = 0;
/* RGB triplets */
#ifdef __sparc
/* SPARC CPUs cannot read integers from nonaligned addresses. */
for (; i < pixels; i++) {
out[0] = in[R];
out[1] = in[G];
out[2] = in[B];
out += 3; in += 4;
}
#else
for (; i < pixels-1; i++) {
((UINT32*)out)[0] = ((UINT32*)in)[i];
out += 3;
@ -261,6 +270,7 @@ ImagingPackRGB(UINT8* out, const UINT8* in, int pixels)
out[2] = in[i*4+B];
out += 3;
}
#endif
}
void

View File

@ -147,7 +147,7 @@ void _tiffUnmapProc(thandle_t hdata, tdata_t base, toff_t size) {
(void) hdata; (void) base; (void) size;
}
int ImagingLibTiffInit(ImagingCodecState state, int fp, int offset) {
int ImagingLibTiffInit(ImagingCodecState state, int fp, uint32 offset) {
TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
TRACE(("initing libtiff\n"));
@ -194,6 +194,9 @@ int ReadTile(TIFF* tiff, UINT32 col, UINT32 row, UINT32* buffer) {
}
swap_line = (UINT32*)malloc(swap_line_size);
if (swap_line == NULL) {
return -1;
}
/*
* For some reason the TIFFReadRGBATile() function chooses the
* lower left corner as the origin. Vertically mirror scanlines.

View File

@ -43,7 +43,7 @@ typedef struct {
extern int ImagingLibTiffInit(ImagingCodecState state, int fp, int offset);
extern int ImagingLibTiffInit(ImagingCodecState state, int fp, uint32 offset);
extern int ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp);
extern int ImagingLibTiffMergeFieldInfo(ImagingCodecState state, TIFFDataType field_type, int key, int is_var_length);
extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...);

View File

@ -480,6 +480,16 @@ void
ImagingUnpackRGB(UINT8* _out, const UINT8* in, int pixels)
{
int i = 0;
#ifdef __sparc
/* SPARC CPUs cannot read integers from nonaligned addresses. */
for (; i < pixels; i++) {
_out[R] = in[0];
_out[G] = in[1];
_out[B] = in[2];
_out[A] = 255;
_out += 4; in += 3;
}
#else
UINT32* out = (UINT32*) _out;
/* RGB triplets */
for (; i < pixels-1; i++) {
@ -490,6 +500,7 @@ ImagingUnpackRGB(UINT8* _out, const UINT8* in, int pixels)
out[i] = MAKE_UINT32(in[0], in[1], in[2], 255);
in += 3;
}
#endif
}
void
@ -1085,22 +1096,44 @@ static void
copy4skip1(UINT8* _out, const UINT8* in, int pixels)
{
int i;
#ifdef __sparc
/* SPARC CPUs cannot read integers from nonaligned addresses. */
for (i = 0; i < pixels; i++) {
_out[0] = in[0];
_out[1] = in[1];
_out[2] = in[2];
_out[3] = in[3];
_out += 4; in += 5;
}
#else
UINT32* out = (UINT32*) _out;
for (i = 0; i < pixels; i++) {
out[i] = *(UINT32*)&in[0];
in += 5;
}
#endif
}
static void
copy4skip2(UINT8* _out, const UINT8* in, int pixels)
{
int i;
#ifdef __sparc
/* SPARC CPUs cannot read integers from nonaligned addresses. */
for (i = 0; i < pixels; i++) {
_out[0] = in[0];
_out[1] = in[1];
_out[2] = in[2];
_out[3] = in[3];
_out += 4; in += 6;
}
#else
UINT32* out = (UINT32*) _out;
for (i = 0; i < pixels; i++) {
out[i] = *(UINT32*)&in[0];
in += 6;
}
#endif
}

View File

@ -133,8 +133,8 @@ PyPath_Flatten(PyObject* data, double **pxy)
/* Assume the buffer contains floats */
Py_buffer buffer;
if (PyImaging_GetBuffer(data, &buffer) == 0) {
int n = buffer.len / (2 * sizeof(float));
float *ptr = (float*) buffer.buf;
n = buffer.len / (2 * sizeof(float));
xy = alloc_array(n);
if (!xy)
return -1;