mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-26 09:14:27 +03:00
Merge branch 'master' into release-notes
This commit is contained in:
commit
f10712f006
|
@ -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]
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
33
src/encode.c
33
src/encode.c
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user