diff --git a/.azure-pipelines/jobs/lint.yml b/.azure-pipelines/jobs/lint.yml
new file mode 100644
index 000000000..d017590f8
--- /dev/null
+++ b/.azure-pipelines/jobs/lint.yml
@@ -0,0 +1,28 @@
+parameters:
+ name: '' # defaults for any parameters that aren't specified
+ vmImage: ''
+
+jobs:
+
+- job: ${{ parameters.name }}
+ pool:
+ vmImage: ${{ parameters.vmImage }}
+
+ strategy:
+ matrix:
+ Python37:
+ python.version: '3.7'
+
+ steps:
+ - task: UsePythonVersion@0
+ inputs:
+ versionSpec: '$(python.version)'
+ architecture: 'x64'
+
+ - script: |
+ python -m pip install --upgrade tox
+ displayName: 'Install dependencies'
+
+ - script: |
+ tox -e lint
+ displayName: 'Lint'
diff --git a/.azure-pipelines/jobs/test-docker.yml b/.azure-pipelines/jobs/test-docker.yml
new file mode 100644
index 000000000..41dc2daec
--- /dev/null
+++ b/.azure-pipelines/jobs/test-docker.yml
@@ -0,0 +1,22 @@
+parameters:
+ docker: '' # defaults for any parameters that aren't specified
+ dockerTag: 'master'
+ name: ''
+ vmImage: 'Ubuntu-16.04'
+
+jobs:
+
+- job: ${{ parameters.name }}
+ pool:
+ vmImage: ${{ parameters.vmImage }}
+
+ steps:
+ - script: |
+ docker pull pythonpillow/${{ parameters.docker }}:${{ parameters.dockerTag }}
+ displayName: 'Docker pull'
+
+ - script: |
+ # The Pillow user in the docker container is UID 1000
+ sudo chown -R 1000 $(Build.SourcesDirectory)
+ docker run -v $(Build.SourcesDirectory):/Pillow pythonpillow/${{ parameters.docker }}:${{ parameters.dockerTag }}
+ displayName: 'Docker build'
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index eef2a30bd..e3c7379ba 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -34,6 +34,6 @@ The best reproductions are self-contained scripts with minimal dependencies. If
## Security vulnerabilities
-To report sensitive vulnerability information, email security@python-pillow.org.
+To report sensitive vulnerability information, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
If your organisation/employer is a distributor of Pillow and would like advance notification of security-related bugs, please let us know your preferred contact method.
diff --git a/CHANGES.rst b/CHANGES.rst
index 01fc87bf7..4b38dca4d 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -8,6 +8,39 @@ Changelog (Pillow)
- Python 2.7 support will be removed in Pillow 7.0.0 #3682
[hugovk]
+- Add I;16 PNG save #3566
+ [radarhere]
+
+- Add support for BMP RGBA bitfield compression #3705
+ [radarhere]
+
+- Added ability to set language for text rendering #3693
+ [iwsfutcmd]
+
+- Only close exclusive fp on Image __exit__ #3698
+ [radarhere]
+
+- Changed EPS subprocess stdout from devnull to None #3635
+ [radarhere]
+
+- Add reading old-JPEG compressed TIFFs #3489
+ [kkopachev]
+
+- Add EXIF support for PNG #3674
+ [radarhere]
+
+- Add option to set dither param on quantize #3699
+ [glasnt]
+
+- Add reading of DDS uncompressed RGB data #3673
+ [radarhere]
+
+- Correct length of Tiff BYTE tags #3672
+ [radarhere]
+
+- Add DIB saving and loading through Image open #3691
+ [radarhere]
+
- Removed deprecated VERSION #3624
[hugovk]
diff --git a/MANIFEST.in b/MANIFEST.in
index 809d0d667..f11ee174c 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -23,9 +23,10 @@ exclude .codecov.yml
exclude .editorconfig
exclude .landscape.yaml
exclude .readthedocs.yml
-exclude .travis
-exclude .travis/*
+exclude azure-pipelines.yml
exclude tox.ini
global-exclude .git*
global-exclude *.pyc
global-exclude *.so
+prune .azure-pipelines
+prune .travis
diff --git a/Tests/images/exif.png b/Tests/images/exif.png
new file mode 100644
index 000000000..0388b6b8a
Binary files /dev/null and b/Tests/images/exif.png differ
diff --git a/Tests/images/old-style-jpeg-compression.png b/Tests/images/old-style-jpeg-compression.png
new file mode 100644
index 000000000..c035542ea
Binary files /dev/null and b/Tests/images/old-style-jpeg-compression.png differ
diff --git a/Tests/images/old-style-jpeg-compression.tif b/Tests/images/old-style-jpeg-compression.tif
new file mode 100644
index 000000000..8d726c404
Binary files /dev/null and b/Tests/images/old-style-jpeg-compression.tif differ
diff --git a/Tests/images/rgb32bf-rgba.bmp b/Tests/images/rgb32bf-rgba.bmp
new file mode 100644
index 000000000..467c2570b
Binary files /dev/null and b/Tests/images/rgb32bf-rgba.bmp differ
diff --git a/Tests/images/test_language.png b/Tests/images/test_language.png
new file mode 100644
index 000000000..8daf007b0
Binary files /dev/null and b/Tests/images/test_language.png differ
diff --git a/Tests/images/uncompressed_rgb.dds b/Tests/images/uncompressed_rgb.dds
new file mode 100755
index 000000000..cd5189532
Binary files /dev/null and b/Tests/images/uncompressed_rgb.dds differ
diff --git a/Tests/images/uncompressed_rgb.png b/Tests/images/uncompressed_rgb.png
new file mode 100644
index 000000000..50bca09ee
Binary files /dev/null and b/Tests/images/uncompressed_rgb.png differ
diff --git a/Tests/images/unimplemented_dxgi_format.dds b/Tests/images/unimplemented_dxgi_format.dds
new file mode 100644
index 000000000..5ecb42006
Binary files /dev/null and b/Tests/images/unimplemented_dxgi_format.dds differ
diff --git a/Tests/images/unimplemented_pixel_format.dds b/Tests/images/unimplemented_pixel_format.dds
new file mode 100755
index 000000000..41a343886
Binary files /dev/null and b/Tests/images/unimplemented_pixel_format.dds differ
diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py
index c51e2bb4e..ec651ae8c 100644
--- a/Tests/test_file_bmp.py
+++ b/Tests/test_file_bmp.py
@@ -16,6 +16,7 @@ class TestFileBmp(PillowTestCase):
self.assertEqual(im.mode, reloaded.mode)
self.assertEqual(im.size, reloaded.size)
self.assertEqual(reloaded.format, "BMP")
+ self.assertEqual(reloaded.get_format_mimetype(), "image/bmp")
def test_sanity(self):
self.roundtrip(hopper())
@@ -72,6 +73,32 @@ class TestFileBmp(PillowTestCase):
def test_load_dib(self):
# test for #1293, Imagegrab returning Unsupported Bitfields Format
- im = BmpImagePlugin.DibImageFile('Tests/images/clipboard.dib')
+ im = Image.open('Tests/images/clipboard.dib')
+ self.assertEqual(im.format, "DIB")
+ self.assertEqual(im.get_format_mimetype(), "image/bmp")
+
target = Image.open('Tests/images/clipboard_target.png')
self.assert_image_equal(im, target)
+
+ def test_save_dib(self):
+ outfile = self.tempfile("temp.dib")
+
+ im = Image.open('Tests/images/clipboard.dib')
+ im.save(outfile)
+
+ reloaded = Image.open(outfile)
+ self.assertEqual(reloaded.format, "DIB")
+ self.assertEqual(reloaded.get_format_mimetype(), "image/bmp")
+ self.assert_image_equal(im, reloaded)
+
+ def test_rgba_bitfields(self):
+ # This test image has been manually hexedited
+ # to change the bitfield compression in the header from XBGR to RGBA
+ im = Image.open("Tests/images/rgb32bf-rgba.bmp")
+
+ # So before the comparing the image, swap the channels
+ b, g, r = im.split()[1:]
+ im = Image.merge("RGB", (r, g, b))
+
+ target = Image.open("Tests/images/bmp/q/rgb32bf-xbgr.bmp")
+ self.assert_image_equal(im, target)
diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py
index af2524c4a..605c5f69b 100644
--- a/Tests/test_file_dds.py
+++ b/Tests/test_file_dds.py
@@ -7,6 +7,7 @@ TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds"
TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds"
TEST_FILE_DXT5 = "Tests/images/dxt5-argb-8bbp-interpolatedalpha_MipMaps-1.dds"
TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds"
+TEST_FILE_UNCOMPRESSED_RGB = "Tests/images/uncompressed_rgb.dds"
class TestFileDds(PillowTestCase):
@@ -67,6 +68,24 @@ class TestFileDds(PillowTestCase):
self.assert_image_equal(target, im)
+ def test_unimplemented_dxgi_format(self):
+ self.assertRaises(NotImplementedError, Image.open,
+ "Tests/images/unimplemented_dxgi_format.dds")
+
+ def test_uncompressed_rgb(self):
+ """Check uncompressed RGB images can be opened"""
+
+ target = Image.open(TEST_FILE_UNCOMPRESSED_RGB.replace('.dds', '.png'))
+
+ im = Image.open(TEST_FILE_UNCOMPRESSED_RGB)
+ im.load()
+
+ self.assertEqual(im.format, "DDS")
+ self.assertEqual(im.mode, "RGBA")
+ self.assertEqual(im.size, (800, 600))
+
+ self.assert_image_equal(target, im)
+
def test__validate_true(self):
"""Check valid prefix"""
# Arrange
@@ -110,3 +129,7 @@ class TestFileDds(PillowTestCase):
im.load()
self.assertRaises(IOError, short_file)
+
+ def test_unimplemented_pixel_format(self):
+ self.assertRaises(NotImplementedError, Image.open,
+ "Tests/images/unimplemented_pixel_format.dds")
diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py
index 56564ebde..0292b7733 100644
--- a/Tests/test_file_libtiff.py
+++ b/Tests/test_file_libtiff.py
@@ -234,11 +234,11 @@ class TestFileLibTiff(LibTiffTestCase):
def test_custom_metadata(self):
custom = {
- 37000: 4,
- 37001: 4.2,
- 37002: 'custom tag value',
- 37003: u'custom tag value',
- 37004: b'custom tag value'
+ 37000: [4, TiffTags.SHORT],
+ 37001: [4.2, TiffTags.RATIONAL],
+ 37002: ['custom tag value', TiffTags.ASCII],
+ 37003: [u'custom tag value', TiffTags.ASCII],
+ 37004: [b'custom tag value', TiffTags.BYTE]
}
libtiff_version = TiffImagePlugin._libtiff_version()
@@ -251,17 +251,33 @@ class TestFileLibTiff(LibTiffTestCase):
for libtiff in libtiffs:
TiffImagePlugin.WRITE_LIBTIFF = libtiff
- im = hopper()
+ def check_tags(tiffinfo):
+ im = hopper()
- out = self.tempfile("temp.tif")
- im.save(out, tiffinfo=custom)
- TiffImagePlugin.WRITE_LIBTIFF = False
+ out = self.tempfile("temp.tif")
+ im.save(out, tiffinfo=tiffinfo)
- reloaded = Image.open(out)
- for tag, value in custom.items():
- if libtiff and isinstance(value, bytes):
- value = value.decode()
- self.assertEqual(reloaded.tag_v2[tag], value)
+ reloaded = Image.open(out)
+ for tag, value in tiffinfo.items():
+ reloaded_value = reloaded.tag_v2[tag]
+ if isinstance(reloaded_value, TiffImagePlugin.IFDRational):
+ reloaded_value = float(reloaded_value)
+
+ if libtiff and isinstance(value, bytes):
+ value = value.decode()
+
+ self.assertEqual(reloaded_value, value)
+
+ # Test with types
+ ifd = TiffImagePlugin.ImageFileDirectory_v2()
+ for tag, tagdata in custom.items():
+ ifd[tag] = tagdata[0]
+ ifd.tagtype[tag] = tagdata[1]
+ check_tags(ifd)
+
+ # Test without types
+ check_tags({tag: tagdata[0] for tag, tagdata in custom.items()})
+ TiffImagePlugin.WRITE_LIBTIFF = False
def test_int_dpi(self):
# issue #1765
@@ -700,3 +716,10 @@ class TestFileLibTiff(LibTiffTestCase):
im = Image.open(infile)
self.assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)
+
+ def test_old_style_jpeg(self):
+ infile = "Tests/images/old-style-jpeg-compression.tif"
+ im = Image.open(infile)
+
+ self.assert_image_equal_tofile(im,
+ "Tests/images/old-style-jpeg-compression.png")
diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py
index 2b80bf357..7f73678fa 100644
--- a/Tests/test_file_png.py
+++ b/Tests/test_file_png.py
@@ -88,20 +88,13 @@ class TestFilePng(PillowTestCase):
self.assertEqual(im.format, "PNG")
self.assertEqual(im.get_format_mimetype(), 'image/png')
- hopper("1").save(test_file)
- Image.open(test_file)
-
- hopper("L").save(test_file)
- Image.open(test_file)
-
- hopper("P").save(test_file)
- Image.open(test_file)
-
- hopper("RGB").save(test_file)
- Image.open(test_file)
-
- hopper("I").save(test_file)
- Image.open(test_file)
+ for mode in ["1", "L", "P", "RGB", "I", "I;16"]:
+ im = hopper(mode)
+ im.save(test_file)
+ reloaded = Image.open(test_file)
+ if mode == "I;16":
+ reloaded = reloaded.convert(mode)
+ self.assert_image_equal(reloaded, im)
def test_invalid_file(self):
invalid_file = "Tests/images/flower.jpg"
@@ -590,6 +583,40 @@ class TestFilePng(PillowTestCase):
im = Image.open("Tests/images/hopper_idat_after_image_end.png")
self.assertEqual(im.text, {'TXT': 'VALUE', 'ZIP': 'VALUE'})
+ def test_exif(self):
+ im = Image.open("Tests/images/exif.png")
+ exif = im._getexif()
+ self.assertEqual(exif[274], 1)
+
+ def test_exif_save(self):
+ im = Image.open("Tests/images/exif.png")
+
+ test_file = self.tempfile("temp.png")
+ im.save(test_file)
+
+ reloaded = Image.open(test_file)
+ exif = reloaded._getexif()
+ self.assertEqual(exif[274], 1)
+
+ def test_exif_from_jpg(self):
+ im = Image.open("Tests/images/pil_sample_rgb.jpg")
+
+ test_file = self.tempfile("temp.png")
+ im.save(test_file)
+
+ reloaded = Image.open(test_file)
+ exif = reloaded._getexif()
+ self.assertEqual(exif[305], "Adobe Photoshop CS Macintosh")
+
+ def test_exif_argument(self):
+ im = Image.open(TEST_PNG_FILE)
+
+ test_file = self.tempfile("temp.png")
+ im.save(test_file, exif=b"exifstring")
+
+ reloaded = Image.open(test_file)
+ self.assertEqual(reloaded.info["exif"], b"Exif\x00\x00exifstring")
+
@unittest.skipUnless(HAVE_WEBP and _webp.HAVE_WEBPANIM,
"WebP support not installed with animation")
def test_apng(self):
diff --git a/Tests/test_image_load.py b/Tests/test_image_load.py
index d8f323eb8..9937218cd 100644
--- a/Tests/test_image_load.py
+++ b/Tests/test_image_load.py
@@ -28,3 +28,10 @@ class TestImageLoad(PillowTestCase):
os.fstat(fn)
self.assertRaises(OSError, os.fstat, fn)
+
+ def test_contextmanager_non_exclusive_fp(self):
+ with open("Tests/images/hopper.gif", "rb") as fp:
+ with Image.open(fp):
+ pass
+
+ self.assertFalse(fp.closed)
diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py
index 2f0b65758..762b4bcab 100644
--- a/Tests/test_image_quantize.py
+++ b/Tests/test_image_quantize.py
@@ -46,3 +46,19 @@ class TestImageQuantize(PillowTestCase):
converted = image.quantize()
self.assert_image(converted, 'P', converted.size)
self.assert_image_similar(converted.convert('RGB'), image, 1)
+
+ def test_quantize_no_dither(self):
+ image = hopper()
+ palette = Image.open('Tests/images/caption_6_33_22.png').convert('P')
+
+ converted = image.quantize(dither=0, palette=palette)
+ self.assert_image(converted, 'P', converted.size)
+
+ def test_quantize_dither_diff(self):
+ image = hopper()
+ palette = Image.open('Tests/images/caption_6_33_22.png').convert('P')
+
+ dither = image.quantize(dither=1, palette=palette)
+ nodither = image.quantize(dither=0, palette=palette)
+
+ self.assertNotEqual(dither.tobytes(), nodither.tobytes())
diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py
index be8667211..ba5821c36 100644
--- a/Tests/test_imagefont.py
+++ b/Tests/test_imagefont.py
@@ -525,6 +525,15 @@ class TestImageFont(PillowTestCase):
self.assertEqual(t.getsize_multiline('ABC\nA'), (36, 36))
self.assertEqual(t.getsize_multiline('ABC\nAaaa'), (48, 36))
+ def test_complex_font_settings(self):
+ # Arrange
+ t = self.get_font()
+ # Act / Assert
+ if t.layout_engine == ImageFont.LAYOUT_BASIC:
+ self.assertRaises(KeyError, t.getmask, 'абвг', direction='rtl')
+ self.assertRaises(KeyError, t.getmask, 'абвг', features=['-kern'])
+ self.assertRaises(KeyError, t.getmask, 'абвг', language='sr')
+
@unittest.skipUnless(HAS_RAQM, "Raqm not Available")
class TestImageFont_RaqmLayout(TestImageFont):
diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py
index d23f6d86f..a00971058 100644
--- a/Tests/test_imagefontctl.py
+++ b/Tests/test_imagefontctl.py
@@ -130,3 +130,16 @@ class TestImagecomplextext(PillowTestCase):
target_img = Image.open(target)
self.assert_image_similar(im, target_img, .5)
+
+ def test_language(self):
+ ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
+
+ im = Image.new(mode='RGB', size=(300, 100))
+ draw = ImageDraw.Draw(im)
+ draw.text((0, 0), 'абвг', font=ttf, fill=500,
+ language='sr')
+
+ target = 'Tests/images/test_language.png'
+ target_img = Image.open(target)
+
+ self.assert_image_similar(im, target_img, .5)
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
new file mode 100644
index 000000000..bef5eeee6
--- /dev/null
+++ b/azure-pipelines.yml
@@ -0,0 +1,67 @@
+# Python package
+# Create and test a Python package on multiple Python versions.
+# Add steps that analyze code, save the dist with the build record,
+# publish to a PyPI-compatible index, and more:
+# https://docs.microsoft.com/azure/devops/pipelines/languages/python
+
+jobs:
+
+- template: .azure-pipelines/jobs/lint.yml
+ parameters:
+ name: Lint
+ vmImage: 'Ubuntu-16.04'
+
+- template: .azure-pipelines/jobs/test-docker.yml
+ parameters:
+ docker: 'alpine'
+ name: 'alpine'
+
+- template: .azure-pipelines/jobs/test-docker.yml
+ parameters:
+ docker: 'arch'
+ name: 'arch'
+
+- template: .azure-pipelines/jobs/test-docker.yml
+ parameters:
+ docker: 'ubuntu-trusty-x86'
+ name: 'ubuntu_trusty_x86'
+
+- template: .azure-pipelines/jobs/test-docker.yml
+ parameters:
+ docker: 'ubuntu-xenial-amd64'
+ name: 'ubuntu_xenial_amd64'
+
+- template: .azure-pipelines/jobs/test-docker.yml
+ parameters:
+ docker: 'debian-stretch-x86'
+ name: 'debian_stretch_x86'
+
+- template: .azure-pipelines/jobs/test-docker.yml
+ parameters:
+ docker: 'centos-6-amd64'
+ name: 'centos_6_amd64'
+
+- template: .azure-pipelines/jobs/test-docker.yml
+ parameters:
+ docker: 'centos-7-amd64'
+ name: 'centos_7_amd64'
+
+- template: .azure-pipelines/jobs/test-docker.yml
+ parameters:
+ docker: 'amazon-1-amd64'
+ name: 'amazon_1_amd64'
+
+- template: .azure-pipelines/jobs/test-docker.yml
+ parameters:
+ docker: 'amazon-2-amd64'
+ name: 'amazon_2_amd64'
+
+- template: .azure-pipelines/jobs/test-docker.yml
+ parameters:
+ docker: 'fedora-28-amd64'
+ name: 'fedora_28_amd64'
+
+- template: .azure-pipelines/jobs/test-docker.yml
+ parameters:
+ docker: 'fedora-29-amd64'
+ name: 'fedora_29_amd64'
diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst
index fec7be3e5..9aafbdd1c 100644
--- a/docs/handbook/image-file-formats.rst
+++ b/docs/handbook/image-file-formats.rst
@@ -21,7 +21,7 @@ Fully supported formats
BMP
^^^
-PIL reads and writes Windows and OS/2 BMP files containing ``1``, ``L``, ``P``,
+Pillow reads and writes Windows and OS/2 BMP files containing ``1``, ``L``, ``P``,
or ``RGB`` data. 16-colour images are read as ``P`` images. Run-length encoding
is not supported.
@@ -31,13 +31,21 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
**compression**
Set to ``bmp_rle`` if the file is run-length encoded.
+DIB
+^^^
+
+Pillow reads and writes DIB files. DIB files are similar to BMP files, so see
+above for more information.
+
+ .. versionadded:: 6.0.0
+
EPS
^^^
-PIL identifies EPS files containing image data, and can read files that contain
-embedded raster images (ImageData descriptors). If Ghostscript is available,
-other EPS files can be read as well. The EPS driver can also write EPS
-images. The EPS driver can read EPS images in ``L``, ``LAB``, ``RGB`` and
+Pillow identifies EPS files containing image data, and can read files that
+contain embedded raster images (ImageData descriptors). If Ghostscript is
+available, other EPS files can be read as well. The EPS driver can also write
+EPS images. The EPS driver can read EPS images in ``L``, ``LAB``, ``RGB`` and
``CMYK`` mode, but Ghostscript may convert the images to ``RGB`` mode rather
than leaving them in the original color space. The EPS driver can write images
in ``L``, ``RGB`` and ``CMYK`` modes.
@@ -59,8 +67,8 @@ method with the following parameter to affect how Ghostscript renders the EPS
GIF
^^^
-PIL reads GIF87a and GIF89a versions of the GIF file format. The library writes
-run-length encoded files in GIF87a by default, unless GIF89a features
+Pillow reads GIF87a and GIF89a versions of the GIF file format. The library
+writes run-length encoded files in GIF87a by default, unless GIF89a features
are used or GIF89a is already in use.
Note that GIF files are always read as grayscale (``L``)
@@ -192,7 +200,7 @@ attributes before loading the file::
ICNS
^^^^
-PIL reads and (macOS only) writes macOS ``.icns`` files. By default, the
+Pillow reads and (macOS only) writes macOS ``.icns`` files. By default, the
largest available icon is read, though you can override this by setting the
:py:attr:`~PIL.Image.Image.size` property before calling
:py:meth:`~PIL.Image.Image.load`. The :py:meth:`~PIL.Image.Image.open` method
@@ -237,12 +245,12 @@ IM is a format used by LabEye and other applications based on the IFUNC image
processing library. The library reads and writes most uncompressed interchange
versions of this format.
-IM is the only format that can store all internal PIL formats.
+IM is the only format that can store all internal Pillow formats.
JPEG
^^^^
-PIL reads JPEG, JFIF, and Adobe JPEG files containing ``L``, ``RGB``, or
+Pillow reads JPEG, JFIF, and Adobe JPEG files containing ``L``, ``RGB``, or
``CMYK`` data. It writes standard and progressive JFIF files.
Using the :py:meth:`~PIL.Image.Image.draft` method, you can speed things up by
@@ -354,15 +362,15 @@ JPEG 2000
.. versionadded:: 2.4.0
-PIL reads and writes JPEG 2000 files containing ``L``, ``LA``, ``RGB`` or
+Pillow reads and writes JPEG 2000 files containing ``L``, ``LA``, ``RGB`` or
``RGBA`` data. It can also read files containing ``YCbCr`` data, which it
converts on read into ``RGB`` or ``RGBA`` depending on whether or not there is
-an alpha channel. PIL supports JPEG 2000 raw codestreams (``.j2k`` files), as
-well as boxed JPEG 2000 files (``.j2p`` or ``.jpx`` files). PIL does *not*
-support files whose components have different sampling frequencies.
+an alpha channel. Pillow supports JPEG 2000 raw codestreams (``.j2k`` files),
+as well as boxed JPEG 2000 files (``.j2p`` or ``.jpx`` files). Pillow does
+*not* support files whose components have different sampling frequencies.
When loading, if you set the ``mode`` on the image prior to the
-:py:meth:`~PIL.Image.Image.load` method being invoked, you can ask PIL to
+:py:meth:`~PIL.Image.Image.load` method being invoked, you can ask Pillow to
convert the image to either ``RGB`` or ``RGBA`` rather than choosing for
itself. It is also possible to set ``reduce`` to the number of resolutions to
discard (each one reduces the size of the resulting image by a factor of 2),
@@ -433,26 +441,31 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
Library.
Windows users can install the OpenJPEG binaries available on the
- OpenJPEG website, but must add them to their PATH in order to use PIL (if
+ OpenJPEG website, but must add them to their PATH in order to use Pillow (if
you fail to do this, you will get errors about not being able to load the
``_imaging`` DLL).
MSP
^^^
-PIL identifies and reads MSP files from Windows 1 and 2. The library writes
+Pillow identifies and reads MSP files from Windows 1 and 2. The library writes
uncompressed (Windows 1) versions of this format.
PCX
^^^
-PIL reads and writes PCX files containing ``1``, ``L``, ``P``, or ``RGB`` data.
+Pillow reads and writes PCX files containing ``1``, ``L``, ``P``, or ``RGB`` data.
PNG
^^^
-PIL identifies, reads, and writes PNG files containing ``1``, ``L``, ``P``,
-``RGB``, or ``RGBA`` data. Interlaced files are supported as of v1.1.7.
+Pillow identifies, reads, and writes PNG files containing ``1``, ``L``, ``LA``,
+``I``, ``P``, ``RGB`` or ``RGBA`` data. Interlaced files are supported as of
+v1.1.7.
+
+As of Pillow 6.0, EXIF data can be read from PNG images. However, unlike other
+image formats, EXIF data is not guaranteed to have been read until
+:py:meth:`~PIL.Image.Image.load` has been called.
The :py:meth:`~PIL.Image.Image.open` method sets the following
:py:attr:`~PIL.Image.Image.info` properties, when appropriate:
@@ -519,6 +532,11 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
**icc_profile**
The ICC Profile to include in the saved file.
+**exif**
+ The exif data to include in the saved file.
+
+ .. versionadded:: 6.0.0
+
**bits (experimental)**
For ``P`` images, this option controls how many bits to store. If omitted,
the PNG writer uses 8 bits (256 colors).
@@ -535,7 +553,7 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
PPM
^^^
-PIL reads and writes PBM, PGM, PPM and PNM files containing ``1``, ``L`` or
+Pillow reads and writes PBM, PGM, PPM and PNM files containing ``1``, ``L`` or
``RGB`` data.
SGI
@@ -547,10 +565,10 @@ Pillow reads and writes uncompressed ``L``, ``RGB``, and ``RGBA`` files.
SPIDER
^^^^^^
-PIL reads and writes SPIDER image files of 32-bit floating point data
+Pillow reads and writes SPIDER image files of 32-bit floating point data
("F;32F").
-PIL also reads SPIDER stack files containing sequences of SPIDER images. The
+Pillow also reads SPIDER stack files containing sequences of SPIDER images. The
:py:meth:`~file.seek` and :py:meth:`~file.tell` methods are supported, and
random access is allowed.
@@ -587,8 +605,8 @@ For more information about the SPIDER image processing package, see the
TGA
^^^
-PIL reads and writes TGA images containing ``L``, ``LA``, ``P``,
-``RGB``, and ``RGBA`` data. PIL can read and write both uncompressed and
+Pillow reads and writes TGA images containing ``L``, ``LA``, ``P``,
+``RGB``, and ``RGBA`` data. Pillow can read and write both uncompressed and
run-length encoded TGAs.
TIFF
@@ -596,8 +614,8 @@ TIFF
Pillow reads and writes TIFF files. It can read both striped and tiled
images, pixel and plane interleaved multi-band images. If you have
-libtiff and its headers installed, PIL can read and write many kinds
-of compressed TIFF files. If not, PIL will only read and write
+libtiff and its headers installed, Pillow can read and write many kinds
+of compressed TIFF files. If not, Pillow will only read and write
uncompressed files.
.. note::
@@ -734,8 +752,8 @@ using the general tags available through tiffinfo.
WebP
^^^^
-PIL reads and writes WebP files. The specifics of PIL's capabilities with this
-format are currently undocumented.
+Pillow reads and writes WebP files. The specifics of Pillow's capabilities with
+this format are currently undocumented.
The :py:meth:`~PIL.Image.Image.save` method supports the following options:
@@ -807,7 +825,7 @@ are available when the `save_all` argument is present and true.
XBM
^^^
-PIL reads and writes X bitmap files (mode ``1``).
+Pillow reads and writes X bitmap files (mode ``1``).
Read-only formats
-----------------
@@ -841,15 +859,15 @@ DDS
DDS is a popular container texture format used in video games and natively
supported by DirectX.
-Currently, DXT1, DXT3, and DXT5 pixel formats are supported and only in ``RGBA``
-mode.
+Currently, uncompressed RGB data and DXT1, DXT3, and DXT5 pixel formats are
+supported, and only in ``RGBA`` mode.
.. versionadded:: 3.4.0 DXT3
FLI, FLC
^^^^^^^^
-PIL reads Autodesk FLI and FLC animations.
+Pillow reads Autodesk FLI and FLC animations.
The :py:meth:`~PIL.Image.Image.open` method sets the following
:py:attr:`~PIL.Image.Image.info` properties:
@@ -860,7 +878,7 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
FPX
^^^
-PIL reads Kodak FlashPix files. In the current version, only the highest
+Pillow reads Kodak FlashPix files. In the current version, only the highest
resolution image is read from the file, and the viewing transform is not taken
into account.
@@ -896,7 +914,7 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
GD
^^
-PIL reads uncompressed GD2 files. Note that you must use
+Pillow reads uncompressed GD2 files. Note that you must use
:py:func:`PIL.GdImageFile.open` to read such a file.
The :py:meth:`~PIL.Image.Image.open` method sets the following
@@ -909,23 +927,23 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
IMT
^^^
-PIL reads Image Tools images containing ``L`` data.
+Pillow reads Image Tools images containing ``L`` data.
IPTC/NAA
^^^^^^^^
-PIL provides limited read support for IPTC/NAA newsphoto files.
+Pillow provides limited read support for IPTC/NAA newsphoto files.
MCIDAS
^^^^^^
-PIL identifies and reads 8-bit McIdas area files.
+Pillow identifies and reads 8-bit McIdas area files.
MIC
^^^
-PIL identifies and reads Microsoft Image Composer (MIC) files. When opened, the
-first sprite in the file is loaded. You can use :py:meth:`~file.seek` and
+Pillow identifies and reads Microsoft Image Composer (MIC) files. When opened,
+the first sprite in the file is loaded. You can use :py:meth:`~file.seek` and
:py:meth:`~file.tell` to read other sprites from the file.
Note that there may be an embedded gamma of 2.2 in MIC files.
@@ -941,22 +959,22 @@ zero-indexed and random access is supported.
PCD
^^^
-PIL reads PhotoCD files containing ``RGB`` data. This only reads the 768x512
+Pillow reads PhotoCD files containing ``RGB`` data. This only reads the 768x512
resolution image from the file. Higher resolutions are encoded in a proprietary
encoding.
PIXAR
^^^^^
-PIL provides limited support for PIXAR raster files. The library can identify
-and read “dumped” RGB files.
+Pillow provides limited support for PIXAR raster files. The library can
+identify and read “dumped” RGB files.
The format code is ``PIXAR``.
PSD
^^^
-PIL identifies and reads PSD files written by Adobe Photoshop 2.5 and 3.0.
+Pillow identifies and reads PSD files written by Adobe Photoshop 2.5 and 3.0.
WAL
@@ -964,7 +982,7 @@ WAL
.. versionadded:: 1.1.4
-PIL reads Quake2 WAL texture files.
+Pillow reads Quake2 WAL texture files.
Note that this file format cannot be automatically identified, so you must use
the open function in the :py:mod:`~PIL.WalImageFile` module to read files in
@@ -976,7 +994,7 @@ the palette, use the putpalette method.
XPM
^^^
-PIL reads X pixmap files (mode ``P``) with 256 colors or less.
+Pillow reads X pixmap files (mode ``P``) with 256 colors or less.
The :py:meth:`~PIL.Image.Image.open` method sets the following
:py:attr:`~PIL.Image.Image.info` properties:
@@ -991,14 +1009,14 @@ Write-only formats
PALM
^^^^
-PIL provides write-only support for PALM pixmap files.
+Pillow provides write-only support for PALM pixmap files.
The format code is ``Palm``, the extension is ``.palm``.
PDF
^^^
-PIL can write PDF (Acrobat) images. Such images are written as binary PDF 1.4
+Pillow can write PDF (Acrobat) images. Such images are written as binary PDF 1.4
files, using either JPEG or HEX encoding depending on the image mode (and
whether JPEG support is available or not).
@@ -1077,7 +1095,7 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
XV Thumbnails
^^^^^^^^^^^^^
-PIL can read XV thumbnail files.
+Pillow can read XV thumbnail files.
Identify-only formats
---------------------
@@ -1087,7 +1105,7 @@ BUFR
.. versionadded:: 1.1.3
-PIL provides a stub driver for BUFR files.
+Pillow provides a stub driver for BUFR files.
To add read or write support to your application, use
:py:func:`PIL.BufrStubImagePlugin.register_handler`.
@@ -1097,7 +1115,7 @@ FITS
.. versionadded:: 1.1.5
-PIL provides a stub driver for FITS files.
+Pillow provides a stub driver for FITS files.
To add read or write support to your application, use
:py:func:`PIL.FitsStubImagePlugin.register_handler`.
@@ -1107,11 +1125,11 @@ GRIB
.. versionadded:: 1.1.5
-PIL provides a stub driver for GRIB files.
+Pillow provides a stub driver for GRIB files.
The driver requires the file to start with a GRIB header. If you have files
with embedded GRIB data, or files with multiple GRIB fields, your application
-has to seek to the header before passing the file handle to PIL.
+has to seek to the header before passing the file handle to Pillow.
To add read or write support to your application, use
:py:func:`PIL.GribStubImagePlugin.register_handler`.
@@ -1121,7 +1139,7 @@ HDF5
.. versionadded:: 1.1.5
-PIL provides a stub driver for HDF5 files.
+Pillow provides a stub driver for HDF5 files.
To add read or write support to your application, use
:py:func:`PIL.Hdf5StubImagePlugin.register_handler`.
@@ -1129,12 +1147,12 @@ To add read or write support to your application, use
MPEG
^^^^
-PIL identifies MPEG files.
+Pillow identifies MPEG files.
WMF
^^^
-PIL can identify playable WMF files.
+Pillow can identify playable WMF files.
In PIL 1.1.4 and earlier, the WMF driver provides some limited rendering
support, but not enough to be useful for any real application.
diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst
index 7c24bae93..b50b770d0 100644
--- a/docs/reference/ImageDraw.rst
+++ b/docs/reference/ImageDraw.rst
@@ -255,7 +255,7 @@ Methods
Draw a shape.
-.. py:method:: PIL.ImageDraw.ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None)
+.. py:method:: PIL.ImageDraw.ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None, language=None)
Draws the string at the given position.
@@ -287,7 +287,17 @@ Methods
.. versionadded:: 4.2.0
-.. py:method:: PIL.ImageDraw.ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None)
+ :param language: Language of the text. Different languages may use
+ different glyph shapes or ligatures. This parameter tells
+ the font which language the text is in, and to apply the
+ correct substitutions as appropriate, if available.
+ It should be a `BCP47 language code
+ `
+ Requires libraqm.
+
+ .. versionadded:: 6.0.0
+
+.. py:method:: PIL.ImageDraw.ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None, language=None)
Draws the string at the given position.
@@ -316,7 +326,17 @@ Methods
.. versionadded:: 4.2.0
-.. py:method:: PIL.ImageDraw.ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None)
+ :param language: Language of the text. Different languages may use
+ different glyph shapes or ligatures. This parameter tells
+ the font which language the text is in, and to apply the
+ correct substitutions as appropriate, if available.
+ It should be a `BCP47 language code
+ `
+ Requires libraqm.
+
+ .. versionadded:: 6.0.0
+
+.. py:method:: PIL.ImageDraw.ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None, language=None)
Return the size of the given string, in pixels.
@@ -330,7 +350,6 @@ Methods
Requires libraqm.
.. versionadded:: 4.2.0
-
:param features: A list of OpenType font features to be used during text
layout. This is usually used to turn on optional
font features that are not enabled by default,
@@ -343,8 +362,17 @@ Methods
Requires libraqm.
.. versionadded:: 4.2.0
+ :param language: Language of the text. Different languages may use
+ different glyph shapes or ligatures. This parameter tells
+ the font which language the text is in, and to apply the
+ correct substitutions as appropriate, if available.
+ It should be a `BCP47 language code
+ `
+ Requires libraqm.
-.. py:method:: PIL.ImageDraw.ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None)
+ .. versionadded:: 6.0.0
+
+.. py:method:: PIL.ImageDraw.ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None, language=None)
Return the size of the given string, in pixels.
@@ -370,6 +398,16 @@ Methods
.. versionadded:: 4.2.0
+ :param language: Language of the text. Different languages may use
+ different glyph shapes or ligatures. This parameter tells
+ the font which language the text is in, and to apply the
+ correct substitutions as appropriate, if available.
+ It should be a `BCP47 language code
+ `
+ Requires libraqm.
+
+ .. versionadded:: 6.0.0
+
.. py:method:: PIL.ImageDraw.getdraw(im=None, hints=None)
.. warning:: This method is experimental.
diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst
index 55ce3d382..b30bdac03 100644
--- a/docs/reference/ImageFont.rst
+++ b/docs/reference/ImageFont.rst
@@ -47,11 +47,45 @@ Functions
Methods
-------
-.. py:method:: PIL.ImageFont.ImageFont.getsize(text)
+.. py:method:: PIL.ImageFont.ImageFont.getsize(text, direction=None, features=[], language=None)
+
+ Returns width and height (in pixels) of given text if rendered in font with
+ provided direction, features, and language.
+
+ :param text: Text to measure.
+
+ :param direction: Direction of the text. It can be 'rtl' (right to
+ left), 'ltr' (left to right) or 'ttb' (top to bottom).
+ Requires libraqm.
+
+ .. versionadded:: 4.2.0
+
+ :param features: A list of OpenType font features to be used during text
+ layout. This is usually used to turn on optional
+ font features that are not enabled by default,
+ for example 'dlig' or 'ss01', but can be also
+ used to turn off default font features for
+ example '-liga' to disable ligatures or '-kern'
+ to disable kerning. To get all supported
+ features, see
+ https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist
+ Requires libraqm.
+
+ .. versionadded:: 4.2.0
+
+ :param language: Language of the text. Different languages may use
+ different glyph shapes or ligatures. This parameter tells
+ the font which language the text is in, and to apply the
+ correct substitutions as appropriate, if available.
+ It should be a `BCP47 language code
+ `
+ Requires libraqm.
+
+ .. versionadded:: 6.0.0
:return: (width, height)
-.. py:method:: PIL.ImageFont.ImageFont.getmask(text, mode='', direction=None, features=[])
+.. py:method:: PIL.ImageFont.ImageFont.getmask(text, mode='', direction=None, features=[], language=None)
Create a bitmap for the text.
@@ -85,5 +119,15 @@ Methods
.. versionadded:: 4.2.0
+ :param language: Language of the text. Different languages may use
+ different glyph shapes or ligatures. This parameter tells
+ the font which language the text is in, and to apply the
+ correct substitutions as appropriate, if available.
+ It should be a `BCP47 language code
+ `
+ Requires libraqm.
+
+ .. versionadded:: 6.0.0
+
:return: An internal PIL storage memory instance as defined by the
:py:mod:`PIL.Image.core` interface module.
diff --git a/docs/releasenotes/6.0.0.rst b/docs/releasenotes/6.0.0.rst
index c9712ed8a..87fcce3ae 100644
--- a/docs/releasenotes/6.0.0.rst
+++ b/docs/releasenotes/6.0.0.rst
@@ -102,15 +102,48 @@ Use ``PIL.__version__`` instead.
API Additions
=============
-TODO
-^^^^
+DIB file format
+^^^^^^^^^^^^^^^
-TODO
+Pillow now supports reading and writing the Device Independent Bitmap file format.
+
+Image.quantize
+^^^^^^^^^^^^^^
+
+The ``dither`` option is now a customisable parameter (was previously hardcoded to ``1``).
+This parameter takes the same values used in ``Image.convert``.
+
+New language parameter
+^^^^^^^^^^^^^^^^^^^^^^
+
+These text-rendering functions now accept a ``language`` parameter to request
+language-specific glyphs and ligatures from the font:
+
+* ``ImageDraw.ImageDraw.multiline_text()``
+* ``ImageDraw.ImageDraw.multiline_textsize()``
+* ``ImageDraw.ImageDraw.text()``
+* ``ImageDraw.ImageDraw.textsize()``
+* ``ImageFont.ImageFont.getmask()``
+* ``ImageFont.ImageFont.getsize_multiline()``
+* ``ImageFont.ImageFont.getsize()``
+
+PNG EXIF data
+^^^^^^^^^^^^^
+
+EXIF data can now be read from and saved to PNG images. However, unlike other image
+formats, EXIF data is not guaranteed to have been read until
+:py:meth:`~PIL.Image.Image.load` has been called.
Other Changes
=============
-TODO
-^^^^
+Reading new DDS image format
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-TODO
+Pillow can now read uncompressed RGB data from DDS images.
+
+Reading TIFF with old-style JPEG compression
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Added support reading TIFF files with old-style JPEG compression through LibTIFF. All YCbCr
+TIFF images are now always read as RGB.
diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py
index f261bf6ef..53020b86d 100644
--- a/src/PIL/BmpImagePlugin.py
+++ b/src/PIL/BmpImagePlugin.py
@@ -52,6 +52,10 @@ def _accept(prefix):
return prefix[:2] == b"BM"
+def _dib_accept(prefix):
+ return i32(prefix[:4]) in [12, 40, 64, 108, 124]
+
+
# =============================================================================
# Image plugin for the Windows BMP format.
# =============================================================================
@@ -176,6 +180,7 @@ class BmpImageFile(ImageFile.ImageFile):
SUPPORTED = {
32: [(0xff0000, 0xff00, 0xff, 0x0),
(0xff0000, 0xff00, 0xff, 0xff000000),
+ (0xff, 0xff00, 0xff0000, 0xff000000),
(0x0, 0x0, 0x0, 0x0),
(0xff000000, 0xff0000, 0xff00, 0x0)],
24: [(0xff0000, 0xff00, 0xff)],
@@ -184,6 +189,7 @@ class BmpImageFile(ImageFile.ImageFile):
MASK_MODES = {
(32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX",
(32, (0xff000000, 0xff0000, 0xff00, 0x0)): "XBGR",
+ (32, (0xff, 0xff00, 0xff0000, 0xff000000)): "RGBA",
(32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA",
(32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
(24, (0xff0000, 0xff00, 0xff)): "BGR",
@@ -196,7 +202,7 @@ class BmpImageFile(ImageFile.ImageFile):
raw_mode = MASK_MODES[
(file_info["bits"], file_info["rgba_mask"])
]
- self.mode = "RGBA" if raw_mode in ("BGRA",) else self.mode
+ self.mode = "RGBA" if "A" in raw_mode else self.mode
elif (file_info['bits'] in (24, 16) and
file_info['rgb_mask'] in SUPPORTED[file_info['bits']]):
raw_mode = MASK_MODES[
@@ -291,7 +297,11 @@ SAVE = {
}
-def _save(im, fp, filename):
+def _dib_save(im, fp, filename):
+ _save(im, fp, filename, False)
+
+
+def _save(im, fp, filename, bitmap_header=True):
try:
rawmode, bits, colors = SAVE[im.mode]
except KeyError:
@@ -306,14 +316,15 @@ def _save(im, fp, filename):
stride = ((im.size[0]*bits+7)//8+3) & (~3)
header = 40 # or 64 for OS/2 version 2
- offset = 14 + header + colors * 4
image = stride * im.size[1]
# bitmap header
- fp.write(b"BM" + # file type (magic)
- o32(offset+image) + # file size
- o32(0) + # reserved
- o32(offset)) # image data offset
+ if bitmap_header:
+ offset = 14 + header + colors * 4
+ fp.write(b"BM" + # file type (magic)
+ o32(offset+image) + # file size
+ o32(0) + # reserved
+ o32(offset)) # image data offset
# bitmap info header
fp.write(o32(header) + # info header size
@@ -352,3 +363,10 @@ Image.register_save(BmpImageFile.format, _save)
Image.register_extension(BmpImageFile.format, ".bmp")
Image.register_mime(BmpImageFile.format, "image/bmp")
+
+Image.register_open(DibImageFile.format, DibImageFile, _dib_accept)
+Image.register_save(DibImageFile.format, _dib_save)
+
+Image.register_extension(DibImageFile.format, ".dib")
+
+Image.register_mime(DibImageFile.format, "image/bmp")
diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py
index edc1b7aa3..3954a1d6e 100644
--- a/src/PIL/DdsImagePlugin.py
+++ b/src/PIL/DdsImagePlugin.py
@@ -123,43 +123,52 @@ class DdsImageFile(ImageFile.ImageFile):
# pixel format
pfsize, pfflags = struct.unpack("<2I", header.read(8))
fourcc = header.read(4)
- bitcount, rmask, gmask, bmask, amask = struct.unpack("<5I",
- header.read(20))
+ bitcount, = struct.unpack("= 3:
def __del__(self):
- if hasattr(self, "_close__fp"):
- self._close__fp()
- if (hasattr(self, 'fp') and hasattr(self, '_exclusive_fp')
- and self.fp and self._exclusive_fp):
- self.fp.close()
- self.fp = None
+ self.__exit__()
def _copy(self):
self.load()
@@ -1049,7 +1049,7 @@ class Image(object):
new_im.info['transparency'] = trns
return new_im
- def quantize(self, colors=256, method=None, kmeans=0, palette=None):
+ def quantize(self, colors=256, method=None, kmeans=0, palette=None, dither=1):
"""
Convert the image to 'P' mode with the specified number
of colors.
@@ -1062,6 +1062,10 @@ class Image(object):
:param kmeans: Integer
:param palette: Quantize to the palette of given
:py:class:`PIL.Image.Image`.
+ :param dither: Dithering method, used when converting from
+ mode "RGB" to "P" or from "RGB" or "L" to "1".
+ Available methods are NONE or FLOYDSTEINBERG (default).
+ Default: 1 (legacy setting)
:returns: A new image
"""
@@ -1089,7 +1093,7 @@ class Image(object):
raise ValueError(
"only RGB or L mode images can be quantized to a palette"
)
- im = self.im.convert("P", 1, palette.im)
+ im = self.im.convert("P", dither, palette.im)
return self._new(im)
return self._new(self.im.quantize(colors, method, kmeans))
@@ -2002,7 +2006,7 @@ class Image(object):
library automatically seeks to frame 0.
Note that in the current version of the library, most sequence
- formats only allows you to seek to the next frame.
+ formats only allow you to seek to the next frame.
See :py:meth:`~PIL.Image.Image.tell`.
@@ -2021,10 +2025,10 @@ class Image(object):
debugging purposes.
On Unix platforms, this method saves the image to a temporary
- PPM file, and calls either the **xv** utility or the **display**
+ PPM file, and calls the **display**, **eog** or **xv**
utility, depending on which one can be found.
- On macOS, this method saves the image to a temporary BMP file, and
+ On macOS, this method saves the image to a temporary PNG file, and
opens it with the native Preview application.
On Windows, it saves the image to a temporary BMP file, and uses
diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py
index ac549790a..86512bb82 100644
--- a/src/PIL/ImageDraw.py
+++ b/src/PIL/ImageDraw.py
@@ -282,13 +282,17 @@ class ImageDraw(object):
self.draw.draw_bitmap(xy, mask, ink)
def multiline_text(self, xy, text, fill=None, font=None, anchor=None,
- spacing=4, align="left", direction=None, features=None):
+ spacing=4, align="left", direction=None, features=None,
+ language=None):
widths = []
max_width = 0
lines = self._multiline_split(text)
line_spacing = self.textsize('A', font=font)[1] + spacing
for line in lines:
- line_width, line_height = self.textsize(line, font)
+ line_width, line_height = self.textsize(line, font,
+ direction=direction,
+ features=features,
+ language=language)
widths.append(line_width)
max_width = max(max_width, line_width)
left, top = xy
@@ -302,29 +306,30 @@ class ImageDraw(object):
else:
raise ValueError('align must be "left", "center" or "right"')
self.text((left, top), line, fill, font, anchor,
- direction=direction, features=features)
+ direction=direction, features=features, language=language)
top += line_spacing
left = xy[0]
def textsize(self, text, font=None, spacing=4, direction=None,
- features=None):
+ features=None, language=None):
"""Get the size of a given string, in pixels."""
if self._multiline_check(text):
return self.multiline_textsize(text, font, spacing,
- direction, features)
+ direction, features, language)
if font is None:
font = self.getfont()
- return font.getsize(text, direction, features)
+ return font.getsize(text, direction, features, language)
def multiline_textsize(self, text, font=None, spacing=4, direction=None,
- features=None):
+ features=None, language=None):
max_width = 0
lines = self._multiline_split(text)
line_spacing = self.textsize('A', font=font)[1] + spacing
for line in lines:
line_width, line_height = self.textsize(line, font, spacing,
- direction, features)
+ direction, features,
+ language)
max_width = max(max_width, line_width)
return max_width, len(lines)*line_spacing - spacing
diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py
index 38838996b..2e1f28003 100644
--- a/src/PIL/ImageFile.py
+++ b/src/PIL/ImageFile.py
@@ -120,9 +120,10 @@ class ImageFile(Image.Image):
pass
def get_format_mimetype(self):
- if self.format is None:
- return
- return self.custom_mimetype or Image.MIME.get(self.format.upper())
+ if self.custom_mimetype:
+ return self.custom_mimetype
+ if self.format is not None:
+ return Image.MIME.get(self.format.upper())
def verify(self):
"""Check file integrity"""
diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py
index 7454b4413..580aa8744 100644
--- a/src/PIL/ImageFont.py
+++ b/src/PIL/ImageFont.py
@@ -158,17 +158,17 @@ class FreeTypeFont(object):
def getmetrics(self):
return self.font.ascent, self.font.descent
- def getsize(self, text, direction=None, features=None):
- size, offset = self.font.getsize(text, direction, features)
+ def getsize(self, text, direction=None, features=None, language=None):
+ size, offset = self.font.getsize(text, direction, features, language)
return (size[0] + offset[0], size[1] + offset[1])
- def getsize_multiline(self, text, direction=None,
- spacing=4, features=None):
+ def getsize_multiline(self, text, direction=None, spacing=4,
+ features=None, language=None):
max_width = 0
lines = self._multiline_split(text)
line_spacing = self.getsize('A')[1] + spacing
for line in lines:
- line_width, line_height = self.getsize(line, direction, features)
+ line_width, line_height = self.getsize(line, direction, features, language)
max_width = max(max_width, line_width)
return max_width, len(lines)*line_spacing - spacing
@@ -176,15 +176,15 @@ class FreeTypeFont(object):
def getoffset(self, text):
return self.font.getsize(text)[1]
- def getmask(self, text, mode="", direction=None, features=None):
- return self.getmask2(text, mode, direction=direction,
- features=features)[0]
+ def getmask(self, text, mode="", direction=None, features=None, language=None):
+ return self.getmask2(text, mode, direction=direction, features=features,
+ language=language)[0]
def getmask2(self, text, mode="", fill=Image.core.fill, direction=None,
- features=None, *args, **kwargs):
- size, offset = self.font.getsize(text, direction, features)
+ features=None, language=None, *args, **kwargs):
+ size, offset = self.font.getsize(text, direction, features, language)
im = fill("L", size, 0)
- self.font.render(text, im.id, mode == "1", direction, features)
+ self.font.render(text, im.id, mode == "1", direction, features, language)
return im, offset
def font_variant(self, font=None, size=None, index=None, encoding=None,
diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py
index e04ae2274..b05ba6c07 100644
--- a/src/PIL/PngImagePlugin.py
+++ b/src/PIL/PngImagePlugin.py
@@ -529,6 +529,11 @@ class PngStream(ChunkStream):
return s
+ def chunk_eXIf(self, pos, length):
+ s = ImageFile._safe_read(self.fp, length)
+ self.im_info["exif"] = b"Exif\x00\x00"+s
+ return s
+
# APNG chunks
def chunk_acTL(self, pos, length):
s = ImageFile._safe_read(self.fp, length)
@@ -683,6 +688,12 @@ class PngImageFile(ImageFile.ImageFile):
self.png.close()
self.png = None
+ def _getexif(self):
+ if "exif" not in self.info:
+ self.load()
+ from .JpegImagePlugin import _getexif
+ return _getexif(self)
+
# --------------------------------------------------------------------
# PNG writer
@@ -696,6 +707,7 @@ _OUTMODES = {
"L": ("L", b'\x08\x00'),
"LA": ("LA", b'\x08\x04'),
"I": ("I;16B", b'\x10\x00'),
+ "I;16": ("I;16B", b'\x10\x00'),
"P;1": ("P;1", b'\x01\x03'),
"P;2": ("P;2", b'\x02\x03'),
"P;4": ("P;4", b'\x04\x03'),
@@ -861,6 +873,12 @@ def _save(im, fp, filename, chunk=putchunk):
chunks.remove(cid)
chunk(fp, cid, data)
+ exif = im.encoderinfo.get("exif", im.info.get("exif"))
+ if exif:
+ if exif.startswith(b"Exif\x00\x00"):
+ exif = exif[6:]
+ chunk(fp, b"eXIf", exif)
+
ImageFile._save(im, _idat(fp, chunk),
[("zip", (0, 0)+im.size, 0, rawmode)])
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
index aac630ea1..2ea0f106e 100644
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -263,10 +263,10 @@ OPEN_INFO = {
(II, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
(MM, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
- # JPEG compressed images handled by LibTiff and auto-converted to RGB
+ # JPEG compressed images handled by LibTiff and auto-converted to RGBX
# Minimal Baseline TIFF requires YCbCr images to have 3 SamplesPerPixel
- (II, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"),
- (MM, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"),
+ (II, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"),
+ (MM, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"),
(II, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
(MM, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
@@ -819,7 +819,7 @@ class ImageFileDirectory_v2(MutableMapping):
print("- value:", values)
# count is sum of lengths for string and arbitrary data
- if typ in [TiffTags.ASCII, TiffTags.UNDEFINED]:
+ if typ in [TiffTags.BYTE, TiffTags.ASCII, TiffTags.UNDEFINED]:
count = len(data)
else:
count = len(values)
@@ -1191,6 +1191,10 @@ class TiffImageFile(ImageFile.ImageFile):
# the specification
photo = self.tag_v2.get(PHOTOMETRIC_INTERPRETATION, 0)
+ # old style jpeg compression images most certainly are YCbCr
+ if self._compression == "tiff_jpeg":
+ photo = 6
+
fillorder = self.tag_v2.get(FILLORDER, 1)
if DEBUG:
diff --git a/src/_imagingft.c b/src/_imagingft.c
index f94e55803..b13c4030b 100644
--- a/src/_imagingft.c
+++ b/src/_imagingft.c
@@ -87,6 +87,10 @@ typedef bool (*t_raqm_set_text_utf8) (raqm_t *rq,
size_t len);
typedef bool (*t_raqm_set_par_direction) (raqm_t *rq,
raqm_direction_t dir);
+typedef bool (*t_raqm_set_language) (raqm_t *rq,
+ const char *lang,
+ size_t start,
+ size_t len);
typedef bool (*t_raqm_add_font_feature) (raqm_t *rq,
const char *feature,
int len);
@@ -106,6 +110,7 @@ typedef struct {
t_raqm_set_text set_text;
t_raqm_set_text_utf8 set_text_utf8;
t_raqm_set_par_direction set_par_direction;
+ t_raqm_set_language set_language;
t_raqm_add_font_feature add_font_feature;
t_raqm_set_freetype_face set_freetype_face;
t_raqm_layout layout;
@@ -160,6 +165,7 @@ setraqm(void)
p_raqm.set_text = (t_raqm_set_text)dlsym(p_raqm.raqm, "raqm_set_text");
p_raqm.set_text_utf8 = (t_raqm_set_text_utf8)dlsym(p_raqm.raqm, "raqm_set_text_utf8");
p_raqm.set_par_direction = (t_raqm_set_par_direction)dlsym(p_raqm.raqm, "raqm_set_par_direction");
+ p_raqm.set_language = (t_raqm_set_language)dlsym(p_raqm.raqm, "raqm_set_language");
p_raqm.add_font_feature = (t_raqm_add_font_feature)dlsym(p_raqm.raqm, "raqm_add_font_feature");
p_raqm.set_freetype_face = (t_raqm_set_freetype_face)dlsym(p_raqm.raqm, "raqm_set_freetype_face");
p_raqm.layout = (t_raqm_layout)dlsym(p_raqm.raqm, "raqm_layout");
@@ -176,6 +182,7 @@ setraqm(void)
p_raqm.set_text &&
p_raqm.set_text_utf8 &&
p_raqm.set_par_direction &&
+ p_raqm.set_language &&
p_raqm.add_font_feature &&
p_raqm.set_freetype_face &&
p_raqm.layout &&
@@ -190,6 +197,7 @@ setraqm(void)
p_raqm.set_text = (t_raqm_set_text)GetProcAddress(p_raqm.raqm, "raqm_set_text");
p_raqm.set_text_utf8 = (t_raqm_set_text_utf8)GetProcAddress(p_raqm.raqm, "raqm_set_text_utf8");
p_raqm.set_par_direction = (t_raqm_set_par_direction)GetProcAddress(p_raqm.raqm, "raqm_set_par_direction");
+ p_raqm.set_language = (t_raqm_set_language)GetProcAddress(p_raqm.raqm, "raqm_set_language");
p_raqm.add_font_feature = (t_raqm_add_font_feature)GetProcAddress(p_raqm.raqm, "raqm_add_font_feature");
p_raqm.set_freetype_face = (t_raqm_set_freetype_face)GetProcAddress(p_raqm.raqm, "raqm_set_freetype_face");
p_raqm.layout = (t_raqm_layout)GetProcAddress(p_raqm.raqm, "raqm_layout");
@@ -205,6 +213,7 @@ setraqm(void)
p_raqm.set_text &&
p_raqm.set_text_utf8 &&
p_raqm.set_par_direction &&
+ p_raqm.set_language &&
p_raqm.add_font_feature &&
p_raqm.set_freetype_face &&
p_raqm.layout &&
@@ -332,8 +341,8 @@ font_getchar(PyObject* string, int index, FT_ULong* char_out)
}
static size_t
-text_layout_raqm(PyObject* string, FontObject* self, const char* dir,
- PyObject *features ,GlyphInfo **glyph_info, int mask)
+text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject *features,
+ const char* lang, GlyphInfo **glyph_info, int mask)
{
int i = 0;
raqm_t *rq;
@@ -341,6 +350,7 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir,
raqm_glyph_t *glyphs = NULL;
raqm_glyph_t_01 *glyphs_01 = NULL;
raqm_direction_t direction;
+ size_t start = 0;
rq = (*p_raqm.create)();
if (rq == NULL) {
@@ -360,6 +370,13 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir,
PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed");
goto failed;
}
+ if (lang) {
+ if (!(*p_raqm.set_language)(rq, lang, start, size)) {
+ PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed");
+ goto failed;
+ }
+ }
+
}
#if PY_VERSION_HEX < 0x03000000
else if (PyString_Check(string)) {
@@ -372,6 +389,12 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir,
PyErr_SetString(PyExc_ValueError, "raqm_set_text_utf8() failed");
goto failed;
}
+ if (lang) {
+ if (!(*p_raqm.set_language)(rq, lang, start, size)) {
+ PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed");
+ goto failed;
+ }
+ }
}
#endif
else {
@@ -498,8 +521,8 @@ failed:
}
static size_t
-text_layout_fallback(PyObject* string, FontObject* self, const char* dir,
- PyObject *features ,GlyphInfo **glyph_info, int mask)
+text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObject *features,
+ const char* lang, GlyphInfo **glyph_info, int mask)
{
int error, load_flags;
FT_ULong ch;
@@ -509,8 +532,8 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir,
FT_UInt last_index = 0;
int i;
- if (features != Py_None || dir != NULL) {
- PyErr_SetString(PyExc_KeyError, "setting text direction or font features is not supported without libraqm");
+ if (features != Py_None || dir != NULL || lang != NULL) {
+ PyErr_SetString(PyExc_KeyError, "setting text direction, language or font features is not supported without libraqm");
}
#if PY_VERSION_HEX >= 0x03000000
if (!PyUnicode_Check(string)) {
@@ -564,15 +587,15 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir,
}
static size_t
-text_layout(PyObject* string, FontObject* self, const char* dir,
- PyObject *features, GlyphInfo **glyph_info, int mask)
+text_layout(PyObject* string, FontObject* self, const char* dir, PyObject *features,
+ const char* lang, GlyphInfo **glyph_info, int mask)
{
size_t count;
if (p_raqm.raqm && self->layout_engine == LAYOUT_RAQM) {
- count = text_layout_raqm(string, self, dir, features, glyph_info, mask);
+ count = text_layout_raqm(string, self, dir, features, lang, glyph_info, mask);
} else {
- count = text_layout_fallback(string, self, dir, features, glyph_info, mask);
+ count = text_layout_fallback(string, self, dir, features, lang, glyph_info, mask);
}
return count;
}
@@ -584,6 +607,7 @@ font_getsize(FontObject* self, PyObject* args)
FT_Face face;
int xoffset, yoffset;
const char *dir = NULL;
+ const char *lang = NULL;
size_t count;
GlyphInfo *glyph_info = NULL;
PyObject *features = Py_None;
@@ -591,14 +615,14 @@ font_getsize(FontObject* self, PyObject* args)
/* calculate size and bearing for a given string */
PyObject* string;
- if (!PyArg_ParseTuple(args, "O|zO:getsize", &string, &dir, &features))
+ if (!PyArg_ParseTuple(args, "O|zOz:getsize", &string, &dir, &features, &lang))
return NULL;
face = NULL;
xoffset = yoffset = 0;
y_max = y_min = 0;
- count = text_layout(string, self, dir, features, &glyph_info, 0);
+ count = text_layout(string, self, dir, features, lang, &glyph_info, 0);
if (PyErr_Occurred()) {
return NULL;
}
@@ -691,16 +715,17 @@ font_render(FontObject* self, PyObject* args)
int temp;
int xx, x0, x1;
const char *dir = NULL;
+ const char *lang = NULL;
size_t count;
GlyphInfo *glyph_info;
PyObject *features = NULL;
- if (!PyArg_ParseTuple(args, "On|izO:render", &string, &id, &mask, &dir, &features)) {
+ if (!PyArg_ParseTuple(args, "On|izOz:render", &string, &id, &mask, &dir, &features, &lang)) {
return NULL;
}
glyph_info = NULL;
- count = text_layout(string, self, dir, features, &glyph_info, mask);
+ count = text_layout(string, self, dir, features, lang, &glyph_info, mask);
if (PyErr_Occurred()) {
return NULL;
}
diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c
index 5c298c6c5..56aebf58e 100644
--- a/src/libImaging/Pack.c
+++ b/src/libImaging/Pack.c
@@ -639,6 +639,7 @@ static struct {
/* storage modes */
{"I;16", "I;16", 16, copy2},
+ {"I;16", "I;16B", 16, packI16N_I16B},
{"I;16B", "I;16B", 16, copy2},
{"I;16L", "I;16L", 16, copy2},
{"I;16", "I;16N", 16, packI16N_I16}, // LibTiff native->image endian.
diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c
index 64ac86e6a..381f795e0 100644
--- a/src/libImaging/TiffDecode.c
+++ b/src/libImaging/TiffDecode.c
@@ -124,6 +124,7 @@ toff_t _tiffSizeProc(thandle_t hdata) {
return (toff_t)state->size;
}
+
int _tiffMapProc(thandle_t hdata, tdata_t* pbase, toff_t* psize) {
TIFFSTATE *state = (TIFFSTATE *)hdata;
@@ -168,13 +169,117 @@ int ImagingLibTiffInit(ImagingCodecState state, int fp, int offset) {
return 1;
}
+
+int ReadTile(TIFF* tiff, UINT32 col, UINT32 row, UINT32* buffer) {
+ uint16 photometric;
+
+ TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric);
+
+ // To avoid dealing with YCbCr subsampling, let libtiff handle it
+ if (photometric == PHOTOMETRIC_YCBCR) {
+ UINT32 tile_width, tile_height, swap_line_size, i_row;
+ UINT32* swap_line;
+
+ TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width);
+ TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_height);
+
+ swap_line_size = tile_width * sizeof(UINT32);
+ if (tile_width != swap_line_size / sizeof(UINT32)) {
+ return -1;
+ }
+
+ /* Read the tile into an RGBA array */
+ if (!TIFFReadRGBATile(tiff, col, row, buffer)) {
+ return -1;
+ }
+
+ swap_line = (UINT32*)malloc(swap_line_size);
+ /*
+ * For some reason the TIFFReadRGBATile() function chooses the
+ * lower left corner as the origin. Vertically mirror scanlines.
+ */
+ for(i_row = 0; i_row < tile_height / 2; i_row++) {
+ UINT32 *top_line, *bottom_line;
+
+ top_line = buffer + tile_width * i_row;
+ bottom_line = buffer + tile_width * (tile_height - i_row - 1);
+
+ memcpy(swap_line, top_line, 4*tile_width);
+ memcpy(top_line, bottom_line, 4*tile_width);
+ memcpy(bottom_line, swap_line, 4*tile_width);
+ }
+
+ free(swap_line);
+
+ return 0;
+ }
+
+ if (TIFFReadTile(tiff, (tdata_t)buffer, col, row, 0, 0) == -1) {
+ TRACE(("Decode Error, Tile at %dx%d\n", col, row));
+ return -1;
+ }
+
+ TRACE(("Successfully read tile at %dx%d; \n\n", col, row));
+
+ return 0;
+}
+
+int ReadStrip(TIFF* tiff, UINT32 row, UINT32* buffer) {
+ uint16 photometric;
+ TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric);
+
+ // To avoid dealing with YCbCr subsampling, let libtiff handle it
+ if (photometric == PHOTOMETRIC_YCBCR) {
+ TIFFRGBAImage img;
+ char emsg[1024] = "";
+ UINT32 rows_per_strip, rows_to_read;
+ int ok;
+
+
+ TIFFGetFieldDefaulted(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip);
+ if ((row % rows_per_strip) != 0) {
+ TRACE(("Row passed to ReadStrip() must be first in a strip."));
+ return -1;
+ }
+
+ if (TIFFRGBAImageOK(tiff, emsg) && TIFFRGBAImageBegin(&img, tiff, 0, emsg)) {
+ TRACE(("Initialized RGBAImage\n"));
+
+ img.req_orientation = ORIENTATION_TOPLEFT;
+ img.row_offset = row;
+ img.col_offset = 0;
+
+ rows_to_read = min(rows_per_strip, img.height - row);
+
+ TRACE(("rows to read: %d\n", rows_to_read));
+ ok = TIFFRGBAImageGet(&img, buffer, img.width, rows_to_read);
+
+ TIFFRGBAImageEnd(&img);
+ } else {
+ ok = 0;
+ }
+
+ if (ok == 0) {
+ TRACE(("Decode Error, row %d; msg: %s\n", row, emsg));
+ return -1;
+ }
+
+ return 0;
+ }
+
+ if (TIFFReadEncodedStrip(tiff, TIFFComputeStrip(tiff, row, 0), (tdata_t)buffer, -1) == -1) {
+ TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, row, 0)));
+ return -1;
+ }
+
+ return 0;
+}
+
int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes) {
TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
char *filename = "tempfile.tif";
char *mode = "r";
TIFF *tiff;
- uint16 photometric = 0, compression;
-
/* buffer is the encoded file, bytes is the length of the encoded file */
/* it all ends up in state->buffer, which is a uint8* from Imaging.h */
@@ -235,19 +340,17 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int
}
}
- TIFFGetFieldDefaulted(tiff, TIFFTAG_COMPRESSION, &compression);
- TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric);
- if (compression == COMPRESSION_JPEG && photometric == PHOTOMETRIC_YCBCR) {
- /* Set pseudo-tag to force automatic YCbCr->RGB conversion */
- TIFFSetField(tiff, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB);
- }
-
if (TIFFIsTiled(tiff)) {
- uint32 x, y, tile_y;
- uint32 tileWidth, tileLength;
+ UINT32 x, y, tile_y, row_byte_size;
+ UINT32 tile_width, tile_length, current_tile_width;
UINT8 *new_data;
- state->bytes = TIFFTileSize(tiff);
+ TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width);
+ TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length);
+
+ // We could use TIFFTileSize, but for YCbCr data it returns subsampled data size
+ row_byte_size = (tile_width * state->bits + 7) / 8;
+ state->bytes = row_byte_size * tile_length;
/* overflow check for malloc */
if (state->bytes > INT_MAX - 1) {
@@ -268,12 +371,9 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int
TRACE(("TIFFTileSize: %d\n", state->bytes));
- TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tileWidth);
- TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tileLength);
-
- for (y = state->yoff; y < state->ysize; y += tileLength) {
- for (x = state->xoff; x < state->xsize; x += tileWidth) {
- if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, 0) == -1) {
+ for (y = state->yoff; y < state->ysize; y += tile_length) {
+ for (x = state->xoff; x < state->xsize; x += tile_width) {
+ if (ReadTile(tiff, x, y, (UINT32*) state->buffer) == -1) {
TRACE(("Decode Error, Tile at %dx%d\n", x, y));
state->errcode = IMAGING_CODEC_BROKEN;
TIFFClose(tiff);
@@ -282,53 +382,68 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int
TRACE(("Read tile at %dx%d; \n\n", x, y));
+ current_tile_width = min(tile_width, state->xsize - x);
+
// iterate over each line in the tile and stuff data into image
- for (tile_y = 0; tile_y < min(tileLength, state->ysize - y); tile_y++) {
+ for (tile_y = 0; tile_y < min(tile_length, state->ysize - y); tile_y++) {
+ TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width));
- TRACE(("Writing tile data at %dx%d using tilwWidth: %d; \n", tile_y + y, x, min(tileWidth, state->xsize - x)));
-
- // UINT8 * bbb = state->buffer + tile_y * (state->bytes / tileLength);
+ // UINT8 * bbb = state->buffer + tile_y * row_byte_size;
// TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
state->shuffle((UINT8*) im->image[tile_y + y] + x * im->pixelsize,
- state->buffer + tile_y * (state->bytes / tileLength),
- min(tileWidth, state->xsize - x)
+ state->buffer + tile_y * row_byte_size,
+ current_tile_width
);
}
}
}
} else {
- tsize_t size;
+ UINT32 strip_row, row_byte_size;
+ UINT8 *new_data;
+ UINT32 rows_per_strip;
- size = TIFFScanlineSize(tiff);
- TRACE(("ScanlineSize: %lu \n", size));
- if (size > state->bytes) {
- TRACE(("Error, scanline size > buffer size\n"));
- state->errcode = IMAGING_CODEC_BROKEN;
+ TIFFGetField(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip);
+ TRACE(("RowsPerStrip: %u \n", rows_per_strip));
+
+ // We could use TIFFStripSize, but for YCbCr data it returns subsampled data size
+ row_byte_size = (state->xsize * state->bits + 7) / 8;
+ state->bytes = rows_per_strip * row_byte_size;
+
+ TRACE(("StripSize: %d \n", state->bytes));
+
+ /* realloc to fit whole strip */
+ new_data = realloc (state->buffer, state->bytes);
+ if (!new_data) {
+ state->errcode = IMAGING_CODEC_MEMORY;
TIFFClose(tiff);
return -1;
}
- // Have to do this row by row and shove stuff into the buffer that way,
- // with shuffle. (or, just alloc a buffer myself, then figure out how to get it
- // back in. Can't use read encoded stripe.
+ state->buffer = new_data;
- // This thing pretty much requires that I have the whole image in one shot.
- // Perhaps a stub version would work better???
- while(state->y < state->ysize){
- if (TIFFReadScanline(tiff, (tdata_t)state->buffer, (uint32)state->y, 0) == -1) {
- TRACE(("Decode Error, row %d\n", state->y));
+ for (; state->y < state->ysize; state->y += rows_per_strip) {
+ if (ReadStrip(tiff, state->y, (UINT32 *)state->buffer) == -1) {
+ TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0)));
state->errcode = IMAGING_CODEC_BROKEN;
TIFFClose(tiff);
return -1;
}
- /* TRACE(("Decoded row %d \n", state->y)); */
- state->shuffle((UINT8*) im->image[state->y + state->yoff] +
- state->xoff * im->pixelsize,
- state->buffer,
- state->xsize);
- state->y++;
+ TRACE(("Decoded strip for row %d \n", state->y));
+
+ // iterate over each row in the strip and stuff data into image
+ for (strip_row = 0; strip_row < min(rows_per_strip, state->ysize - state->y); strip_row++) {
+ TRACE(("Writing data into line %d ; \n", state->y + strip_row));
+
+ // UINT8 * bbb = state->buffer + strip_row * (state->bytes / rows_per_strip);
+ // TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
+
+ state->shuffle((UINT8*) im->image[state->y + state->yoff + strip_row] +
+ state->xoff * im->pixelsize,
+ state->buffer + strip_row * row_byte_size,
+ state->xsize);
+ }
}
}
diff --git a/winbuild/fetch.py b/winbuild/fetch.py
index b7acb63ac..830a64ee5 100644
--- a/winbuild/fetch.py
+++ b/winbuild/fetch.py
@@ -9,7 +9,11 @@ def fetch(url):
if not os.path.exists(name):
print("Fetching", url)
- content = urllib.request.urlopen(url).read()
+ try:
+ r = urllib.request.urlopen(url)
+ except urllib.error.URLError:
+ r = urllib.request.urlopen(url)
+ content = r.read()
with open(name, 'wb') as fd:
fd.write(content)
return name