Merge branch 'master' into release-notes

This commit is contained in:
Hugo 2019-07-01 12:58:30 +03:00 committed by GitHub
commit f10712f006
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 123 additions and 16 deletions

View File

@ -14,6 +14,9 @@ Changelog (Pillow)
- Respect the PKG_CONFIG environment variable when building #3928 - Respect the PKG_CONFIG environment variable when building #3928
[chewi] [chewi]
- Use explicit memcpy() to avoid unaligned memory accesses #3225
[DerDakon]
- Improve encoding of TIFF tags #3861 - Improve encoding of TIFF tags #3861
[olt] [olt]

View File

@ -435,18 +435,44 @@ class TestFileLibTiff(LibTiffTestCase):
self.assert_image_equal(im, im2) self.assert_image_equal(im, im2)
def test_compressions(self): def test_compressions(self):
# Test various tiff compressions and assert similar image content but reduced
# file sizes.
im = hopper("RGB") im = hopper("RGB")
out = self.tempfile("temp.tif") out = self.tempfile("temp.tif")
im.save(out)
size_raw = os.path.getsize(out)
for compression in ("packbits", "tiff_lzw"): for compression in ("packbits", "tiff_lzw"):
im.save(out, compression=compression) im.save(out, compression=compression)
size_compressed = os.path.getsize(out)
im2 = Image.open(out) im2 = Image.open(out)
self.assert_image_equal(im, im2) self.assert_image_equal(im, im2)
im.save(out, compression="jpeg") im.save(out, compression="jpeg")
size_jpeg = os.path.getsize(out)
im2 = Image.open(out) im2 = Image.open(out)
self.assert_image_similar(im, im2, 30) self.assert_image_similar(im, im2, 30)
im.save(out, compression="jpeg", quality=30)
size_jpeg_30 = os.path.getsize(out)
im3 = Image.open(out)
self.assert_image_similar(im2, im3, 30)
self.assertGreater(size_raw, size_compressed)
self.assertGreater(size_compressed, size_jpeg)
self.assertGreater(size_jpeg, size_jpeg_30)
def test_quality(self):
im = hopper("RGB")
out = self.tempfile("temp.tif")
self.assertRaises(ValueError, im.save, out, compression="tiff_lzw", quality=50)
self.assertRaises(ValueError, im.save, out, compression="jpeg", quality=-1)
self.assertRaises(ValueError, im.save, out, compression="jpeg", quality=101)
self.assertRaises(ValueError, im.save, out, compression="jpeg", quality="good")
im.save(out, compression="jpeg", quality=0)
im.save(out, compression="jpeg", quality=100)
def test_cmyk_save(self): def test_cmyk_save(self):
im = hopper("CMYK") im = hopper("CMYK")
out = self.tempfile("temp.tif") out = self.tempfile("temp.tif")

View File

@ -12,6 +12,29 @@ Deprecated features
Below are features which are considered deprecated. Where appropriate, Below are features which are considered deprecated. Where appropriate,
a ``DeprecationWarning`` is issued. a ``DeprecationWarning`` is issued.
Image.__del__
~~~~~~~~~~~~~
.. deprecated:: 6.1.0
Implicitly closing the image's underlying file in ``Image.__del__`` has been deprecated.
Use a context manager or call ``Image.close()`` instead to close the file in a
deterministic way.
Deprecated:
.. code-block:: python
im = Image.open("hopper.png")
im.save("out.jpg")
Use instead:
.. code-block:: python
with Image.open("hopper.png") as im:
im.save("out.jpg")
Python 2.7 Python 2.7
~~~~~~~~~~ ~~~~~~~~~~

View File

@ -738,6 +738,12 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
``"tiff_thunderscan"``, ``"tiff_deflate"``, ``"tiff_sgilog"``, ``"tiff_thunderscan"``, ``"tiff_deflate"``, ``"tiff_sgilog"``,
``"tiff_sgilog24"``, ``"tiff_raw_16"`` ``"tiff_sgilog24"``, ``"tiff_raw_16"``
**quality**
The image quality for JPEG compression, on a scale from 0 (worst) to 100
(best). The default is 75.
.. versionadded:: 6.1.0
These arguments to set the tiff header fields are an alternative to These arguments to set the tiff header fields are an alternative to
using the general tags available through tiffinfo. using the general tags available through tiffinfo.

View File

@ -1,6 +1,32 @@
6.1.0 6.1.0
----- -----
Deprecations
============
Image.__del__
^^^^^^^^^^^^^
.. deprecated:: 6.1.0
Implicitly closing the image's underlying file in ``Image.__del__`` has been deprecated.
Use a context manager or call ``Image.close()`` instead to close the file in a
deterministic way.
Deprecated:
.. code-block:: python
im = Image.open("hopper.png")
im.save("out.jpg")
Use instead:
.. code-block:: python
with Image.open("hopper.png") as im:
im.save("out.jpg")
API Additions API Additions
============= =============

View File

@ -116,6 +116,7 @@ PHOTOSHOP_CHUNK = 34377 # photoshop properties
ICCPROFILE = 34675 ICCPROFILE = 34675
EXIFIFD = 34665 EXIFIFD = 34665
XMP = 700 XMP = 700
JPEGQUALITY = 65537 # pseudo-tag by libtiff
# https://github.com/imagej/ImageJA/blob/master/src/main/java/ij/io/TiffDecoder.java # https://github.com/imagej/ImageJA/blob/master/src/main/java/ij/io/TiffDecoder.java
IMAGEJ_META_DATA_BYTE_COUNTS = 50838 IMAGEJ_META_DATA_BYTE_COUNTS = 50838
@ -1529,6 +1530,16 @@ def _save(im, fp, filename):
ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1) ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)
if libtiff: if libtiff:
if "quality" in im.encoderinfo:
quality = im.encoderinfo["quality"]
if not isinstance(quality, int) or quality < 0 or quality > 100:
raise ValueError("Invalid quality setting")
if compression != "jpeg":
raise ValueError(
"quality setting only supported for 'jpeg' compression"
)
ifd[JPEGQUALITY] = quality
if DEBUG: if DEBUG:
print("Saving using libtiff encoder") print("Saving using libtiff encoder")
print("Items: %s" % sorted(ifd.items())) print("Items: %s" % sorted(ifd.items()))
@ -1607,7 +1618,12 @@ def _save(im, fp, filename):
if im.mode in ("I;16B", "I;16"): if im.mode in ("I;16B", "I;16"):
rawmode = "I;16N" rawmode = "I;16N"
a = (rawmode, compression, _fp, filename, atts, types) # Pass tags as sorted list so that the tags are set in a fixed order.
# This is required by libtiff for some tags. For example, the JPEGQUALITY
# pseudo tag requires that the COMPRESS tag was already set.
tags = list(atts.items())
tags.sort()
a = (rawmode, compression, _fp, filename, tags, types)
e = Image._getencoder(im.mode, "libtiff", a, im.encoderconfig) e = Image._getencoder(im.mode, "libtiff", a, im.encoderconfig)
e.setimage(im.im, (0, 0) + im.size) e.setimage(im.im, (0, 0) + im.size)
while True: while True:

View File

@ -432,6 +432,9 @@ TYPES = {}
# 389: case TIFFTAG_REFERENCEBLACKWHITE: # 389: case TIFFTAG_REFERENCEBLACKWHITE:
# 393: case TIFFTAG_INKNAMES: # 393: case TIFFTAG_INKNAMES:
# Following pseudo-tags are also handled by default in libtiff:
# TIFFTAG_JPEGQUALITY 65537
# some of these are not in our TAGS_V2 dict and were included from tiff.h # some of these are not in our TAGS_V2 dict and were included from tiff.h
# This list also exists in encode.c # This list also exists in encode.c
@ -476,6 +479,7 @@ LIBTIFF_CORE = {
333, 333,
# as above # as above
269, # this has been in our tests forever, and works 269, # this has been in our tests forever, and works
65537,
} }
LIBTIFF_CORE.remove(320) # Array of short, crashes LIBTIFF_CORE.remove(320) # Array of short, crashes

View File

@ -643,27 +643,28 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
const int core_tags[] = { const int core_tags[] = {
256, 257, 258, 259, 262, 263, 266, 269, 274, 277, 278, 280, 281, 340, 256, 257, 258, 259, 262, 263, 266, 269, 274, 277, 278, 280, 281, 340,
341, 282, 283, 284, 286, 287, 296, 297, 321, 338, 32995, 32998, 32996, 341, 282, 283, 284, 286, 287, 296, 297, 321, 338, 32995, 32998, 32996,
339, 32997, 330, 531, 530 339, 32997, 330, 531, 530, 65537
}; };
Py_ssize_t d_size; Py_ssize_t tags_size;
PyObject *keys, *values; PyObject *item;
if (! PyArg_ParseTuple(args, "sssnsOO", &mode, &rawmode, &compname, &fp, &filename, &tags, &types)) { if (! PyArg_ParseTuple(args, "sssnsOO", &mode, &rawmode, &compname, &fp, &filename, &tags, &types)) {
return NULL; return NULL;
} }
if (!PyDict_Check(tags)) { if (!PyList_Check(tags)) {
PyErr_SetString(PyExc_ValueError, "Invalid tags dictionary"); PyErr_SetString(PyExc_ValueError, "Invalid tags list");
return NULL; return NULL;
} else { } else {
d_size = PyDict_Size(tags); tags_size = PyList_Size(tags);
TRACE(("dict size: %d\n", (int)d_size)); TRACE(("tags size: %d\n", (int)tags_size));
keys = PyDict_Keys(tags); for (pos=0;pos<tags_size;pos++){
values = PyDict_Values(tags); item = PyList_GetItem(tags, pos);
for (pos=0;pos<d_size;pos++){ if (!PyTuple_Check(item) || PyTuple_Size(item) != 2) {
TRACE((" key: %d\n", (int)PyInt_AsLong(PyList_GetItem(keys,pos)))); PyErr_SetString(PyExc_ValueError, "Invalid tags list");
return NULL;
}
} }
pos = 0; pos = 0;
} }
@ -688,10 +689,12 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
} }
num_core_tags = sizeof(core_tags) / sizeof(int); num_core_tags = sizeof(core_tags) / sizeof(int);
for (pos = 0; pos < d_size; pos++) { for (pos = 0; pos < tags_size; pos++) {
key = PyList_GetItem(keys, pos); item = PyList_GetItem(tags, pos);
// We already checked that tags is a 2-tuple list.
key = PyTuple_GetItem(item, 0);
key_int = (int)PyInt_AsLong(key); key_int = (int)PyInt_AsLong(key);
value = PyList_GetItem(values, pos); value = PyTuple_GetItem(item, 1);
status = 0; status = 0;
is_core_tag = 0; is_core_tag = 0;
is_var_length = 0; is_var_length = 0;