Merge branch 'master' into master

This commit is contained in:
tzjtan 2019-06-20 18:42:54 -04:00 committed by GitHub
commit ca96f84c69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
213 changed files with 4977 additions and 3232 deletions

View File

@ -52,7 +52,7 @@ install:
}
else
{
c:\python34\python.exe c:\pillow\winbuild\build_dep.py
c:\python37\python.exe c:\pillow\winbuild\build_dep.py
c:\pillow\winbuild\build_deps.cmd
$host.SetShouldExit(0)
}

View File

@ -34,6 +34,4 @@ The best reproductions are self-contained scripts with minimal dependencies. If
## Security vulnerabilities
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.
Please see our [security policy](https://github.com/python-pillow/Pillow/blob/master/.github/SECURITY.md).

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
tidelift: pypi/pillow

View File

@ -1,19 +0,0 @@
### What did you do?
### What did you expect to happen?
### What actually happened?
### What are your OS, Python and Pillow versions?
* OS:
* Python:
* Pillow:
Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.
The best reproductions are self-contained scripts with minimal dependencies. If you are using a framework such as plone, Django, or buildout, try to replicate the issue just using Pillow.
```python
code goes here
```

59
.github/ISSUE_TEMPLATE/ISSUE_REPORT.md vendored Normal file
View File

@ -0,0 +1,59 @@
---
name: Issue report
about: Create a report to help us improve Pillow
---
<!--
Thank you for reporting an issue.
Follow these guidelines to ensure your issue is handled properly.
If you have a ...
1. General question: consider asking the question on Stack Overflow
with the python-imaging-library tag:
* https://stackoverflow.com/questions/tagged/python-imaging-library
Do not ask a question in both places.
If you think you have found a bug or have an unexplained exception
then file a bug report here.
2. Bug report: include a self-contained, copy-pastable example that
generates the issue if possible. Be concise with code posted.
Guidelines on how to provide a good bug report:
* https://stackoverflow.com/help/mcve
Bug reports which follow these guidelines are easier to diagnose,
and are often handled much more quickly.
3. Feature request: do a quick search of existing issues
to make sure this has not been asked before.
We know asking good questions takes effort, and we appreciate your time.
Thank you.
-->
### What did you do?
### What did you expect to happen?
### What actually happened?
### What are your OS, Python and Pillow versions?
* OS:
* Python:
* Pillow:
<!--
Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.
The best reproductions are self-contained scripts with minimal dependencies. If you are using a framework such as Plone, Django, or Buildout, try to replicate the issue just using Pillow.
-->
```python
code goes here
```

5
.github/SECURITY.md vendored Normal file
View File

@ -0,0 +1,5 @@
# Security policy
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.

View File

@ -16,50 +16,36 @@ matrix:
- python: "3.6"
name: "Lint"
env: LINT="true"
- python: "pypy2.7-6.0"
- python: "pypy"
name: "PyPy2 Xenial"
dist: xenial
- python: "pypy3.5-6.0"
- python: "pypy3"
name: "PyPy3 Xenial"
dist: xenial
- python: '3.7'
name: "3.7 Xenial"
- python: '2.7'
name: "2.7 Xenial"
- python: '2.7'
name: "2.7 Trusty"
dist: trusty
- python: "2.7_with_system_site_packages" # For PyQt4
name: "2.7_with_system_site_packages Xenial"
services: xvfb
- python: "2.7_with_system_site_packages" # For PyQt4
name: "2.7_with_system_site_packages Trusty"
dist: trusty
- python: '3.6'
name: "3.6 Xenial"
- python: '3.6'
name: "3.6 Trusty PYTHONOPTIMIZE=1"
dist: trusty
name: "3.6 Xenial PYTHONOPTIMIZE=1"
env: PYTHONOPTIMIZE=1
- python: '3.5'
name: "3.5 Xenial"
- python: '3.5'
name: "3.5 Trusty PYTHONOPTIMIZE=2"
dist: trusty
name: "3.5 Xenial PYTHONOPTIMIZE=2"
env: PYTHONOPTIMIZE=2
- python: "3.8-dev"
name: "3.8-dev Xenial"
- env: DOCKER="alpine" DOCKER_TAG="master"
- env: DOCKER="arch" DOCKER_TAG="master" # contains PyQt5
- env: DOCKER="ubuntu-trusty-x86" DOCKER_TAG="master"
- env: DOCKER="ubuntu-xenial-amd64" DOCKER_TAG="master"
- env: DOCKER="ubuntu-16.04-xenial-amd64" DOCKER_TAG="master"
- env: DOCKER="ubuntu-18.04-bionic-amd64" DOCKER_TAG="master"
- env: DOCKER="debian-stretch-x86" DOCKER_TAG="master"
- env: DOCKER="centos-6-amd64" DOCKER_TAG="master"
- env: DOCKER="centos-7-amd64" DOCKER_TAG="master"
- env: DOCKER="amazon-1-amd64" DOCKER_TAG="master"
- env: DOCKER="amazon-2-amd64" DOCKER_TAG="master"
- env: DOCKER="fedora-28-amd64" DOCKER_TAG="master"
- env: DOCKER="fedora-29-amd64" DOCKER_TAG="master"
- env: DOCKER="fedora-30-amd64" DOCKER_TAG="master"
services:
- docker
@ -75,14 +61,6 @@ install:
.travis/install.sh;
fi
before_script:
# Qt needs a display for some of the tests, and it's only run on the system site packages install
- |
if [ "$TRAVIS_JOB_NAME" == "2.7_with_system_site_packages Trusty" ]; then
export DISPLAY=:99.0
sh -e /etc/init.d/xvfb start
fi
script:
- |
if [ "$LINT" == "true" ]; then

View File

@ -2,6 +2,87 @@
Changelog (Pillow)
==================
6.1.0 (unreleased)
------------------
- Fixed bugs in calculating text size #3864
[radarhere]
- Add __main__.py to output basic format and support information #3870
[jdufresne]
- Added variation font support #3802
[radarhere]
- Do not down-convert if image is LA when showing with PNG format #3869
[radarhere]
- Improve handling of PSD frames #3759
[radarhere]
- Improved ICO and ICNS loading #3897
[radarhere]
- Changed Preview application path so that it is no longer static #3896
[radarhere]
- Corrected ttb text positioning #3856
[radarhere]
- Handle unexpected ICO image sizes #3836
[radarhere]
- Fixed bits value for RGB;16N unpackers #3837
[kkopachev]
- Travis CI: Add Fedora 30, remove Fedora 28 #3821
[hugovk]
- Added reading of CMYK;16L TIFF images #3817
[radarhere]
- Fixed dimensions of 1-bit PDFs #3827
[radarhere]
- Fixed opening mmap image through Path on Windows #3825
[radarhere]
- Fixed ImageDraw arc gaps #3824
[radarhere]
- Expand GIF to include frames with extents outside the image size #3822
[radarhere]
- Fixed ImageTk getimage #3814
[radarhere]
- Fixed bug in decoding large images #3791
[radarhere]
- Fixed reading APP13 marker without Photoshop data #3771
[radarhere]
- Added option to include layered windows in ImageGrab.grab on Windows #3808
[radarhere]
- Detect libimagequant when installed by pacman on MingW #3812
[radarhere]
- Fixed raqm layout bug #3787
[radarhere]
- Fixed loading font with non-Unicode path on Windows #3785
[radarhere]
- Travis CI: Upgrade PyPy from 6.0.0 to 7.1.1 #3783
[hugovk, johnthagen]
- Depends: Updated openjpeg to 2.3.1 #3794, raqm to 0.7.0 #3877, libimagequant to 2.12.3 #3889
[radarhere]
- Fix numpy bool bug #3790
[radarhere]
6.0.0 (2019-04-01)
------------------
@ -1408,7 +1489,7 @@ Changelog (Pillow)
- Test: Faster assert_image_similar #2279
[homm]
- Removed depreciated internal "stretch" method #2276
- Removed deprecated internal "stretch" method #2276
[homm]
- Removed the handles_eof flag in decode.c #2223

View File

@ -4,7 +4,7 @@ Pillow
Python Imaging Library (Fork)
-----------------------------
Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.com/python-pillow/Pillow/graphs/contributors>`_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.com/python-pillow/Pillow/graphs/contributors>`_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. As of 2019, Pillow development is `supported by Tidelift <https://tidelift.com/subscription/pkg/pypi-pillow>`_.
.. start-badges

View File

@ -102,4 +102,4 @@ Released as needed privately to individual vendors for critical security-related
## Documentation
* [ ] Make sure the default version for Read the Docs is the latest tagged release e.g. `d2d43879` (5.4.0)
* [ ] Make sure the [default version for Read the Docs](https://pillow.readthedocs.io/en/stable/) is up-to-date with the release changes

View File

@ -12,6 +12,12 @@ from .helper import unittest, PillowTestCase
# 2.7 and 3.2.
from PIL import Image
try:
import numpy
except ImportError:
numpy = None
YDIM = 32769
XDIM = 48000
@ -32,6 +38,11 @@ class LargeMemoryTest(PillowTestCase):
"""failed prepatch"""
self._write_png(XDIM, XDIM)
@unittest.skipIf(numpy is None, "Numpy is not installed")
def test_size_greater_than_int(self):
arr = numpy.ndarray(shape=(16394, 16394))
Image.fromarray(arr)
if __name__ == '__main__':
unittest.main()

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,13 +1,13 @@
NotoNastaliqUrdu-Regular.ttf:
NotoNastaliqUrdu-Regular.ttf, from https://github.com/googlei18n/noto-fonts
NotoSansJP-Thin.otf, from https://www.google.com/get/noto/help/cjk/
AdobeVFPrototype.ttf, from https://github.com/adobe-fonts/adobe-variable-font-prototype
TINY5x3GX.ttf, from http://velvetyne.fr/fonts/tiny
ArefRuqaa-Regular.ttf, from https://github.com/google/fonts/tree/master/ofl/arefruqaa
(from https://github.com/googlei18n/noto-fonts)
All Noto fonts are published under the SIL Open Font License (OFL) v1.1 (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL), which allows you to copy, modify, and redistribute them if you need to.
All of the above fonts are published under the SIL Open Font License (OFL) v1.1 (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL), which allows you to copy, modify, and redistribute them if you need to.
10x20-ISO8859-1.pcf
(from https://packages.ubuntu.com/xenial/xfonts-base)
10x20-ISO8859-1.pcf, from https://packages.ubuntu.com/xenial/xfonts-base
"Public domain font. Share and enjoy."

Binary file not shown.

BIN
Tests/fonts/TINY5x3GX.ttf Executable file

Binary file not shown.

BIN
Tests/images/app13.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -175,7 +175,7 @@ class TestBoxBlur(PillowTestCase):
delta=0,
)
def test_exteme_large_radius(self):
def test_extreme_large_radius(self):
self.assertBlur(
sample, 600,
[

View File

@ -1,3 +1,7 @@
from __future__ import unicode_literals
import io
from .helper import unittest, PillowTestCase
from PIL import features
@ -63,3 +67,25 @@ class TestFeatures(PillowTestCase):
module = "unsupported_module"
# Act / Assert
self.assertRaises(ValueError, features.check_module, module)
def test_pilinfo(self):
buf = io.StringIO()
features.pilinfo(buf)
out = buf.getvalue()
lines = out.splitlines()
self.assertEqual(lines[0], "-" * 68)
self.assertTrue(lines[1].startswith("Pillow "))
self.assertEqual(lines[2], "-" * 68)
self.assertTrue(lines[3].startswith("Python modules loaded from "))
self.assertTrue(lines[4].startswith("Binary modules loaded from "))
self.assertEqual(lines[5], "-" * 68)
self.assertTrue(lines[6].startswith("Python "))
jpeg = (
"\n" +
"-" * 68 + "\n" +
"JPEG image/jpeg\n" +
"Extensions: .jfif, .jpe, .jpeg, .jpg\n" +
"Features: open, save\n" +
"-" * 68 + "\n"
)
self.assertIn(jpeg, out)

View File

@ -663,3 +663,9 @@ class TestFileGif(PillowTestCase):
self.assertEqual(im.tile[0][3][0], 11) # LZW bits
# codec error prepatch
im.load()
def test_extents(self):
im = Image.open('Tests/images/test_extents.gif')
self.assertEqual(im.size, (100, 100))
im.seek(1)
self.assertEqual(im.size, (150, 150))

View File

@ -61,11 +61,10 @@ class TestFileIcns(PillowTestCase):
for w, h, r in im.info['sizes']:
wr = w * r
hr = h * r
im2 = Image.open(TEST_FILE)
im2.size = (w, h, r)
im2.load()
self.assertEqual(im2.mode, 'RGBA')
self.assertEqual(im2.size, (wr, hr))
im.size = (w, h, r)
im.load()
self.assertEqual(im.mode, 'RGBA')
self.assertEqual(im.size, (wr, hr))
# Check that we cannot load an incorrect size
with self.assertRaises(ValueError):

View File

@ -1,7 +1,7 @@
from .helper import PillowTestCase, hopper
import io
from PIL import Image, IcoImagePlugin
from PIL import Image, ImageDraw, IcoImagePlugin
TEST_ICO_FILE = "Tests/images/hopper.ico"
@ -83,3 +83,23 @@ class TestFileIco(PillowTestCase):
self.assertEqual(
im_saved.info['sizes'],
{(16, 16), (24, 24), (32, 32), (48, 48)})
def test_unexpected_size(self):
# This image has been manually hexedited to state that it is 16x32
# while the image within is still 16x16
im = self.assert_warning(UserWarning,
Image.open, "Tests/images/hopper_unexpected.ico")
self.assertEqual(im.size, (16, 16))
def test_draw_reloaded(self):
im = Image.open(TEST_ICO_FILE)
outfile = self.tempfile("temp_saved_hopper_draw.ico")
draw = ImageDraw.Draw(im)
draw.line((0, 0) + im.size, '#f00')
im.save(outfile)
im = Image.open(outfile)
im.save("Tests/images/hopper_draw.ico")
reloaded = Image.open("Tests/images/hopper_draw.ico")
self.assert_image_equal(im, reloaded)

View File

@ -48,7 +48,7 @@ class TestFileIm(PillowTestCase):
im.seek(n_frames-1)
def test_roundtrip(self):
for mode in ["RGB", "P"]:
for mode in ["RGB", "P", "PA"]:
out = self.tempfile('temp.im')
im = hopper(mode)
im.save(out)

View File

@ -620,6 +620,10 @@ class TestFileJpeg(PillowTestCase):
'DisplayedUnitsY': 1,
})
# This image does not contain a Photoshop header string
im = Image.open("Tests/images/app13.jpg")
self.assertNotIn("photoshop", im.info)
@unittest.skipUnless(sys.platform.startswith('win32'), "Windows only")
class TestFileCloseW32(PillowTestCase):

View File

@ -631,6 +631,21 @@ class TestFileLibTiff(LibTiffTestCase):
# Should not raise UnicodeDecodeError or anything else
im.save(outfile)
def test_16bit_RGB_tiff(self):
im = Image.open("Tests/images/tiff_16bit_RGB.tiff")
self.assertEqual(im.mode, "RGB")
self.assertEqual(im.size, (100, 40))
self.assertEqual(
im.tile,
[('tiff_adobe_deflate', (0, 0, 100, 40), 0,
('RGB;16N', 'tiff_adobe_deflate', False))]
)
im.load()
self.assert_image_equal_tofile(
im, "Tests/images/tiff_16bit_RGB_target.png")
def test_16bit_RGBa_tiff(self):
im = Image.open("Tests/images/tiff_16bit_RGBa.tiff")
@ -687,6 +702,12 @@ class TestFileLibTiff(LibTiffTestCase):
self.assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
def test_strip_cmyk_16l_jpeg(self):
infile = "Tests/images/tiff_strip_cmyk_16l_jpeg.tif"
im = Image.open(infile)
self.assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
def test_strip_ycbcr_jpeg_2x2_sampling(self):
infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif"
im = Image.open(infile)

View File

@ -46,6 +46,13 @@ class TestFilePalm(PillowTestCase):
self.skipKnownBadTest("Palm P image is wrong")
self.roundtrip(mode)
def test_l_ioerror(self):
# Arrange
mode = "L"
# Act / Assert
self.assertRaises(IOError, self.helper_save_as_palm, mode)
def test_rgb_ioerror(self):
# Arrange
mode = "RGB"

View File

@ -26,6 +26,11 @@ class TestFilePdf(PillowTestCase):
self.assertGreater(len(pdf.pages), 1)
else:
self.assertGreater(len(pdf.pages), 0)
with open(outfile, 'rb') as fp:
contents = fp.read()
size = tuple(int(d) for d in
contents.split(b'/MediaBox [ 0 0 ')[1].split(b']')[0].split())
self.assertEqual(im.size, size)
return outfile

View File

@ -17,6 +17,12 @@ class TestImagePsd(PillowTestCase):
im2 = hopper()
self.assert_image_similar(im, im2, 4.8)
def test_unclosed_file(self):
def open():
im = Image.open(test_file)
im.load()
self.assert_warning(None, open)
def test_invalid_file(self):
invalid_file = "Tests/images/flower.jpg"
@ -65,6 +71,12 @@ class TestImagePsd(PillowTestCase):
self.assertRaises(EOFError, im.seek, -1)
def test_open_after_exclusive_load(self):
im = Image.open(test_file)
im.load()
im.seek(im.tell()+1)
im.load()
def test_icc_profile(self):
im = Image.open(test_file)
self.assertIn("icc_profile", im.info)

View File

@ -489,6 +489,16 @@ class TestFileTiff(PillowTestCase):
self.assert_image_equal_tofile(im,
"Tests/images/tiff_adobe_deflate.png")
def test_palette(self):
for mode in ["P", "PA"]:
outfile = self.tempfile("temp.tif")
im = hopper(mode)
im.save(outfile)
reloaded = Image.open(outfile)
self.assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
def test_tiff_save_all(self):
import io
import os

View File

@ -76,6 +76,10 @@ class TestImage(PillowTestCase):
@unittest.skipUnless(Image.HAS_PATHLIB, "requires pathlib/pathlib2")
def test_pathlib(self):
from PIL.Image import Path
im = Image.open(Path("Tests/images/multipage-mmap.tiff"))
self.assertEqual(im.mode, "P")
self.assertEqual(im.size, (10, 10))
im = Image.open(Path("Tests/images/hopper.jpg"))
self.assertEqual(im.mode, "RGB")
self.assertEqual(im.size, (128, 128))

View File

@ -26,6 +26,17 @@ class TestImageMode(PillowTestCase):
self.assertEqual(m.basemode, "L")
self.assertEqual(m.basetype, "L")
for mode in ("I;16", "I;16S",
"I;16L", "I;16LS",
"I;16B", "I;16BS",
"I;16N", "I;16NS"):
m = ImageMode.getmode(mode)
self.assertEqual(m.mode, mode)
self.assertEqual(str(m), mode)
self.assertEqual(m.bands, ("I",))
self.assertEqual(m.basemode, "L")
self.assertEqual(m.basetype, "L")
m = ImageMode.getmode("RGB")
self.assertEqual(m.mode, "RGB")
self.assertEqual(str(m), "RGB")

View File

@ -160,6 +160,15 @@ class TestImageTransform(PillowTestCase):
im = hopper()
self.assertRaises(ValueError, im.transform, (100, 100), None)
def test_unknown_resampling_filter(self):
im = hopper()
(w, h) = im.size
for resample in (Image.BOX, "unknown"):
self.assertRaises(ValueError, im.transform, (100, 100), Image.EXTENT,
(0, 0,
w, h),
resample)
class TestImageTransformAffine(PillowTestCase):
transform = Image.AFFINE

View File

@ -114,6 +114,19 @@ class TestImageDraw(PillowTestCase):
# Assert
self.assert_image_similar(im, Image.open(expected), 1)
def test_arc_width_pieslice_large(self):
# Tests an arc with a large enough width that it is a pieslice
# Arrange
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_arc_width_pieslice.png"
# Act
draw.arc(BBOX1, 10, 260, fill="yellow", width=100)
# Assert
self.assert_image_similar(im, Image.open(expected), 1)
def test_arc_width_fill(self):
# Arrange
im = Image.new("RGB", (W, H))
@ -239,6 +252,18 @@ class TestImageDraw(PillowTestCase):
# Assert
self.assert_image_similar(im, Image.open(expected), 1)
def test_ellipse_width_large(self):
# Arrange
im = Image.new("RGB", (500, 500))
draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_ellipse_width_large.png"
# Act
draw.ellipse((25, 25, 475, 475), outline="blue", width=75)
# Assert
self.assert_image_similar(im, Image.open(expected), 1)
def test_ellipse_width_fill(self):
# Arrange
im = Image.new("RGB", (W, H))

View File

@ -7,6 +7,7 @@ import os
import sys
import copy
import re
import shutil
import distutils.version
FONT_PATH = "Tests/fonts/FreeMono.ttf"
@ -131,6 +132,27 @@ class TestImageFont(PillowTestCase):
with open(FONT_PATH, 'rb') as f:
self._render(f)
def test_non_unicode_path(self):
try:
tempfile = self.tempfile("temp_"+chr(128)+".ttf")
except UnicodeEncodeError:
self.skipTest("Unicode path could not be created")
shutil.copy(FONT_PATH, tempfile)
ImageFont.truetype(tempfile, FONT_SIZE)
def test_unavailable_layout_engine(self):
have_raqm = ImageFont.core.HAVE_RAQM
ImageFont.core.HAVE_RAQM = False
try:
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE,
layout_engine=ImageFont.LAYOUT_RAQM)
finally:
ImageFont.core.HAVE_RAQM = have_raqm
self.assertEqual(ttf.layout_engine, ImageFont.LAYOUT_BASIC)
def _render(self, font):
txt = "Hello World!"
ttf = ImageFont.truetype(font, FONT_SIZE,
@ -400,6 +422,7 @@ class TestImageFont(PillowTestCase):
# Act/Assert
self.assertRaises(IOError, ImageFont.load_path, filename)
self.assertRaises(IOError, ImageFont.truetype, filename)
def test_default_font(self):
# Arrange
@ -547,6 +570,91 @@ class TestImageFont(PillowTestCase):
self.assertRaises(KeyError, t.getmask, 'абвг', features=['-kern'])
self.assertRaises(KeyError, t.getmask, 'абвг', language='sr')
def test_variation_get(self):
font = self.get_font()
freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version)
if freetype < '2.9.1':
self.assertRaises(NotImplementedError, font.get_variation_names)
self.assertRaises(NotImplementedError, font.get_variation_axes)
return
self.assertRaises(IOError, font.get_variation_names)
self.assertRaises(IOError, font.get_variation_axes)
font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf")
self.assertEqual(
font.get_variation_names(),
[b'ExtraLight', b'Light', b'Regular', b'Semibold', b'Bold',
b'Black', b'Black Medium Contrast', b'Black High Contrast', b'Default'])
self.assertEqual(
font.get_variation_axes(),
[{'name': b'Weight', 'minimum': 200, 'maximum': 900, 'default': 389},
{'name': b'Contrast', 'minimum': 0, 'maximum': 100, 'default': 0}])
font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf")
self.assertEqual(
font.get_variation_names(),
[b'20', b'40', b'60', b'80', b'100', b'120', b'140', b'160', b'180',
b'200', b'220', b'240', b'260', b'280', b'300', b'Regular'])
self.assertEqual(
font.get_variation_axes(),
[{'name': b'Size', 'minimum': 0, 'maximum': 300, 'default': 0}])
def test_variation_set_by_name(self):
font = self.get_font()
freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version)
if freetype < '2.9.1':
self.assertRaises(NotImplementedError, font.set_variation_by_name, "Bold")
return
self.assertRaises(IOError, font.set_variation_by_name, "Bold")
def _check_text(font, path, epsilon):
im = Image.new("RGB", (100, 75), "white")
d = ImageDraw.Draw(im)
d.text((10, 10), "Text", font=font, fill="black")
expected = Image.open(path)
self.assert_image_similar(im, expected, epsilon)
font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36)
_check_text(font, "Tests/images/variation_adobe.png", 11)
for name in ["Bold", b"Bold"]:
font.set_variation_by_name(name)
_check_text(font, "Tests/images/variation_adobe_name.png", 11)
font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36)
_check_text(font, "Tests/images/variation_tiny.png", 40)
for name in ["200", b"200"]:
font.set_variation_by_name(name)
_check_text(font, "Tests/images/variation_tiny_name.png", 40)
def test_variation_set_by_axes(self):
font = self.get_font()
freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version)
if freetype < '2.9.1':
self.assertRaises(NotImplementedError, font.set_variation_by_axes, [100])
return
self.assertRaises(IOError, font.set_variation_by_axes, [500, 50])
def _check_text(font, path, epsilon):
im = Image.new("RGB", (100, 75), "white")
d = ImageDraw.Draw(im)
d.text((10, 10), "Text", font=font, fill="black")
expected = Image.open(path)
self.assert_image_similar(im, expected, epsilon)
font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36)
font.set_variation_by_axes([500, 50])
_check_text(font, "Tests/images/variation_adobe_axes.png", 5.1)
font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36)
font.set_variation_by_axes([100])
_check_text(font, "Tests/images/variation_tiny_axes.png", 32.5)
@unittest.skipUnless(HAS_RAQM, "Raqm not Available")
class TestImageFont_RaqmLayout(TestImageFont):

View File

@ -54,6 +54,18 @@ class TestImagecomplextext(PillowTestCase):
self.assert_image_similar(im, target_img, .5)
ttf = ImageFont.truetype("Tests/fonts/KhmerOSBattambang-Regular.ttf",
FONT_SIZE)
im = Image.new(mode='RGB', size=(300, 100))
draw = ImageDraw.Draw(im)
draw.text((0, 0), 'លោកុប្បត្តិ', font=ttf, fill=500)
target = 'Tests/images/test_complex_unicode_text2.png'
target_img = Image.open(target)
self.assert_image_similar(im, target_img, 2.3)
def test_text_direction_rtl(self):
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
@ -92,6 +104,22 @@ class TestImagecomplextext(PillowTestCase):
self.assert_image_similar(im, target_img, .5)
def test_text_direction_ttb(self):
ttf = ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf", FONT_SIZE)
im = Image.new(mode='RGB', size=(100, 300))
draw = ImageDraw.Draw(im)
try:
draw.text((0, 0), 'English あい', font=ttf, fill=500, direction='ttb')
except ValueError as ex:
if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction":
self.skipTest('libraqm 0.7 or greater not available')
target = 'Tests/images/test_direction_ttb.png'
target_img = Image.open(target)
self.assert_image_similar(im, target_img, 1.15)
def test_ligature_features(self):
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
@ -131,6 +159,18 @@ class TestImagecomplextext(PillowTestCase):
self.assert_image_similar(im, target_img, .5)
def test_x_max_and_y_offset(self):
ttf = ImageFont.truetype("Tests/fonts/ArefRuqaa-Regular.ttf", 40)
im = Image.new(mode='RGB', size=(50, 100))
draw = ImageDraw.Draw(im)
draw.text((0, 0), 'لح', font=ttf, fill=500)
target = 'Tests/images/test_x_max_and_y_offset.png'
target_img = Image.open(target)
self.assert_image_similar(im, target_img, .5)
def test_language(self):
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)

View File

@ -9,8 +9,11 @@ try:
class TestImageGrab(PillowTestCase):
def test_grab(self):
im = ImageGrab.grab()
self.assert_image(im, im.mode, im.size)
for im in [
ImageGrab.grab(),
ImageGrab.grab(include_layered_windows=True)
]:
self.assert_image(im, im.mode, im.size)
def test_grabclipboard(self):
if sys.platform == "darwin":

View File

@ -32,6 +32,12 @@ class TestImageSequence(PillowTestCase):
self.assertRaises(IndexError, lambda: i[index+1])
self.assertRaises(StopIteration, next, i)
def test_iterator_min_frame(self):
im = Image.open('Tests/images/hopper.psd')
i = ImageSequence.Iterator(im)
for index in range(1, im.n_frames):
self.assertEqual(i[index], next(i))
def _test_multipage_tiff(self):
im = Image.open('Tests/images/multipage.tiff')
for index, frame in enumerate(ImageSequence.Iterator(im)):

View File

@ -18,18 +18,19 @@ class TestImageShow(PillowTestCase):
ImageShow._viewers.pop()
def test_show(self):
class TestViewer:
class TestViewer(ImageShow.Viewer):
methodCalled = False
def show(self, image, title=None, **options):
def show_image(self, image, **options):
self.methodCalled = True
return True
viewer = TestViewer()
ImageShow.register(viewer, -1)
im = hopper()
self.assertTrue(ImageShow.show(im))
self.assertTrue(viewer.methodCalled)
for mode in ("1", "I;16", "LA", "RGB", "RGBA"):
im = hopper(mode)
self.assertTrue(ImageShow.show(im))
self.assertTrue(viewer.methodCalled)
# Restore original state
ImageShow._viewers.pop(0)

View File

@ -61,9 +61,8 @@ class TestImageTk(PillowTestCase):
self.assertEqual(im_tk.width(), im.width)
self.assertEqual(im_tk.height(), im.height)
# _tkinter.TclError: this function is not yet supported
# reloaded = ImageTk.getimage(im_tk)
# self.assert_image_equal(reloaded, im)
reloaded = ImageTk.getimage(im_tk)
self.assert_image_equal(reloaded, im.convert("RGBA"))
def test_photoimage_blank(self):
# test a image using mode/size:

28
Tests/test_main.py Normal file
View File

@ -0,0 +1,28 @@
from __future__ import unicode_literals
import os
import subprocess
import sys
from unittest import TestCase
class TestMain(TestCase):
def test_main(self):
out = subprocess.check_output([sys.executable, "-m", "PIL"]).decode("utf-8")
lines = out.splitlines()
self.assertEqual(lines[0], "-" * 68)
self.assertTrue(lines[1].startswith("Pillow "))
self.assertEqual(lines[2], "-" * 68)
self.assertTrue(lines[3].startswith("Python modules loaded from "))
self.assertTrue(lines[4].startswith("Binary modules loaded from "))
self.assertEqual(lines[5], "-" * 68)
self.assertTrue(lines[6].startswith("Python "))
jpeg = (
os.linesep +
"-" * 68 + os.linesep +
"JPEG image/jpeg" + os.linesep +
"Extensions: .jfif, .jpe, .jpeg, .jpg" + os.linesep +
"Features: open, save" + os.linesep +
"-" * 68 + os.linesep
)
self.assertIn(jpeg, out)

View File

@ -113,12 +113,7 @@ class TestNumpy(PillowTestCase):
img = Image.fromarray(arr * 255).convert('1')
self.assertEqual(img.mode, '1')
arr_back = numpy.array(img)
# numpy 1.8 and earlier return this as a boolean. (trusty/precise)
if arr_back.dtype == numpy.bool:
arr_bool = numpy.array([[1, 0, 0, 1, 0], [0, 1, 0, 0, 0]], numpy.bool)
numpy.testing.assert_array_equal(arr_bool, arr_back)
else:
numpy.testing.assert_array_equal(arr, arr_back)
numpy.testing.assert_array_equal(arr, arr_back)
def test_save_tiff_uint16(self):
# Tests that we're getting the pixel value in the right byte order.
@ -183,6 +178,14 @@ class TestNumpy(PillowTestCase):
self.assertEqual(len(im.getdata()), len(arr))
def test_roundtrip_eye(self):
for dtype in (numpy.bool, numpy.bool8,
numpy.int8, numpy.int16, numpy.int32,
numpy.uint8, numpy.uint16, numpy.uint32,
numpy.float, numpy.float32, numpy.float64):
arr = numpy.eye(10, dtype=dtype)
numpy.testing.assert_array_equal(arr, numpy.array(Image.fromarray(arr)))
def test_zero_size(self):
# Shouldn't cause floating point exception
# See https://github.com/python-pillow/Pillow/issues/2259

View File

@ -75,6 +75,15 @@ class TestPickle(PillowTestCase):
protocol=protocol,
test_file=test_file)
def test_pickle_pa_mode(self):
# Arrange
import pickle
# Act / Assert
for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
self.helper_pickle_string(pickle, protocol, mode="PA")
self.helper_pickle_file(pickle, protocol, mode="PA")
def test_pickle_l_mode(self):
# Arrange
import pickle
@ -84,6 +93,24 @@ class TestPickle(PillowTestCase):
self.helper_pickle_string(pickle, protocol, mode="L")
self.helper_pickle_file(pickle, protocol, mode="L")
def test_pickle_la_mode_with_palette(self):
# Arrange
import pickle
im = Image.open('Tests/images/hopper.jpg')
filename = self.tempfile('temp.pkl')
im = im.convert("PA")
# Act / Assert
for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
im.mode = "LA"
with open(filename, 'wb') as f:
pickle.dump(im, f, protocol)
with open(filename, 'rb') as f:
loaded_im = pickle.load(f)
im.mode = "PA"
self.assertEqual(im, loaded_im)
def test_cpickle_l_mode(self):
# Arrange
try:

View File

@ -26,5 +26,5 @@ class TestPyroma(PillowTestCase):
)
else:
# Should have a near-perfect score
self.assertEqual(rating, (9, ["Your package does not have license data."]))
# Should have a perfect score
self.assertEqual(rating, (10, []))

View File

@ -23,13 +23,13 @@ jobs:
- template: .azure-pipelines/jobs/test-docker.yml
parameters:
docker: 'ubuntu-trusty-x86'
name: 'ubuntu_trusty_x86'
docker: 'ubuntu-16.04-xenial-amd64'
name: 'ubuntu_16_04_xenial_amd64'
- template: .azure-pipelines/jobs/test-docker.yml
parameters:
docker: 'ubuntu-xenial-amd64'
name: 'ubuntu_xenial_amd64'
docker: 'ubuntu-18.04-bionic-amd64'
name: 'ubuntu_18_04_bionic_amd64'
- template: .azure-pipelines/jobs/test-docker.yml
parameters:
@ -58,10 +58,10 @@ jobs:
- template: .azure-pipelines/jobs/test-docker.yml
parameters:
docker: 'fedora-28-amd64'
name: 'fedora_28_amd64'
docker: 'fedora-29-amd64'
name: 'fedora_29_amd64'
- template: .azure-pipelines/jobs/test-docker.yml
parameters:
docker: 'fedora-29-amd64'
name: 'fedora_29_amd64'
docker: 'fedora-30-amd64'
name: 'fedora_30_amd64'

View File

@ -1,7 +1,7 @@
#!/bin/bash
# install libimagequant
archive=libimagequant-2.12.2
archive=libimagequant-2.12.3
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz

View File

@ -1,7 +1,7 @@
#!/bin/bash
# install openjpeg
archive=openjpeg-2.3.0
archive=openjpeg-2.3.1
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz

View File

@ -2,7 +2,7 @@
# install raqm
archive=raqm-0.5.0
archive=raqm-0.7.0
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz

View File

@ -2,7 +2,7 @@
# install raqm
archive=raqm-cmake-b517ba80
archive=raqm-cmake-99300ff3
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz

View File

@ -76,8 +76,8 @@ PILLOW_VERSION constant
.. deprecated:: 5.2.0
``PILLOW_VERSION`` has been deprecated and will be removed in the next
major release. Use ``__version__`` instead.
``PILLOW_VERSION`` has been deprecated and will be removed in 7.0.0. Use ``__version__``
instead.
ImageCms.CmsProfile attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -24,8 +24,10 @@ To get the number and names of bands in an image, use the
Modes
-----
The ``mode`` of an image defines the type and depth of a pixel in the
image. The current release supports the following standard modes:
The ``mode`` of an image defines the type and depth of a pixel in the image.
Each pixel uses the full range of the bit depth. So a 1-bit pixel has a range
of 0-1, an 8-bit pixel has a range of 0-255 and so on. The current release
supports the following standard modes:
* ``1`` (1-bit pixels, black and white, stored with one pixel per byte)
* ``L`` (8-bit pixels, black and white)

View File

@ -33,7 +33,7 @@ DIB interface <PIL.ImageWin>` that can be used with PythonWin and other
Windows-based toolkits. Many other GUI toolkits come with some kind of PIL
support.
For debugging, theres also a :py:meth:`show` method which saves an image to
For debugging, theres also a :py:meth:`~PIL.Image.Image.show` method which saves an image to
disk, and calls an external display utility.
Image Processing

View File

@ -473,8 +473,8 @@ Reading from a string
::
from PIL import Image
import StringIO
im = Image.open(StringIO.StringIO(buffer))
Note that the library rewinds the file (using ``seek(0)``) before reading the

View File

@ -255,9 +255,10 @@ If the raw decoder cannot handle your format, PIL also provides a special “bit
decoder that can be used to read various packed formats into a floating point
image memory.
To use the bit decoder with the frombytes function, use the following syntax::
To use the bit decoder with the :py:func:`PIL.Image.frombytes` function, use
the following syntax::
image = frombytes(
image = Image.frombytes(
mode, size, data, "bit",
bits, pad, fill, sign, orientation
)

View File

@ -163,17 +163,17 @@ Many of Pillow's features require external libraries:
* **openjpeg** provides JPEG 2000 functionality.
* Pillow has been tested with openjpeg **2.0.0** and **2.1.0**.
* Pillow has been tested with openjpeg **2.0.0**, **2.1.0** and **2.3.1**.
* Pillow does **not** support the earlier **1.5** series which ships
with Ubuntu <= 14.04 and Debian Jessie.
with Debian Jessie.
* **libimagequant** provides improved color quantization
* Pillow has been tested with libimagequant **2.6-2.12.2**
* Pillow has been tested with libimagequant **2.6-2.12.3**
* Libimagequant is licensed GPLv3, which is more restrictive than
the Pillow license, therefore we will not be distributing binaries
with libimagequant support enabled.
* Windows support: Libimagequant requires VS2013/MSVC 18 to compile,
* Windows support: Libimagequant requires VS2015/MSVC 19 to compile,
so it is unlikely to work with Python 2.7 on Windows.
* **libraqm** provides complex text layout support.
@ -382,37 +382,35 @@ These platforms are built and tested for every change.
+----------------------------------+-------------------------------+-----------------------+
|**Operating system** |**Tested Python versions** |**Tested Architecture**|
+----------------------------------+-------------------------------+-----------------------+
| Alpine | 2.7 |x86-64 |
| Alpine | 2.7, 3.6 |x86-64 |
+----------------------------------+-------------------------------+-----------------------+
| Arch | 2.7 |x86-64 |
| Arch | 2.7, 3.7 |x86-64 |
+----------------------------------+-------------------------------+-----------------------+
| Amazon | 2.7 |x86-64 |
| Amazon Linux 1 | 2.7, 3.6 |x86-64 |
+----------------------------------+-------------------------------+-----------------------+
| Centos 6 | 2.7 |x86-64 |
| Amazon Linux 2 | 2.7, 3.6 |x86-64 |
+----------------------------------+-------------------------------+-----------------------+
| Centos 7 | 2.7 |x86-64 |
| CentOS 6 | 2.7, 3.6 |x86-64 |
+----------------------------------+-------------------------------+-----------------------+
| Debian Stretch | 2.7 |x86 |
| CentOS 7 | 2.7, 3.6 |x86-64 |
+----------------------------------+-------------------------------+-----------------------+
| Fedora 28 | 2.7 |x86-64 |
| Debian 9 Stretch | 2.7, 3.5 |x86 |
+----------------------------------+-------------------------------+-----------------------+
| Fedora 29 | 2.7 |x86-64 |
| Fedora 29 | 2.7, 3.7 |x86-64 |
+----------------------------------+-------------------------------+-----------------------+
| Mac OS X 10.10 Yosemite* | 2.7, 3.5, 3.6, 3.7 |x86-64 |
| Fedora 30 | 2.7, 3.7 |x86-64 |
+----------------------------------+-------------------------------+-----------------------+
| macOS 10.13 High Sierra* | 2.7, 3.5, 3.6, 3.7 |x86-64 |
+----------------------------------+-------------------------------+-----------------------+
| Ubuntu Linux 16.04 LTS | 2.7, 3.5, 3.6, 3.7, |x86-64 |
| | PyPy, PyPy3 | |
+----------------------------------+-------------------------------+-----------------------+
| Ubuntu Linux 14.04 LTS | 2.7, 3.5, 3.6 |x86-64 |
| +-------------------------------+-----------------------+
| | 2.7 |x86 |
+----------------------------------+-------------------------------+-----------------------+
| Windows Server 2012 R2 | 2.7, 3.5, 3.6, 3.7 |x86, x86-64 |
| +-------------------------------+-----------------------+
| | PyPy, 3.7/MinGW |x86 |
+----------------------------------+-------------------------------+-----------------------+
\* Mac OS X CI is not run for every commit, but is run for every release.
\* macOS CI is not run for every commit, but is run for every release.
Other Platforms
^^^^^^^^^^^^^^^
@ -427,7 +425,9 @@ These platforms have been reported to work at the versions mentioned.
+----------------------------------+------------------------------+--------------------------------+-----------------------+
|**Operating system** |**Tested Python versions** |**Latest tested Pillow version**|**Tested processors** |
+----------------------------------+------------------------------+--------------------------------+-----------------------+
| macOS 10.14 Mojave | 2.7, 3.4, 3.5, 3.6, 3.7 | 5.4.1 |x86-64 |
| macOS 10.14 Mojave | 2.7, 3.5, 3.6, 3.7 | 6.0.0 |x86-64 |
| +------------------------------+--------------------------------+ +
| | 3.4 | 5.4.1 | |
+----------------------------------+------------------------------+--------------------------------+-----------------------+
| macOS 10.13 High Sierra | 2.7, 3.4, 3.5, 3.6 | 4.2.1 |x86-64 |
+----------------------------------+------------------------------+--------------------------------+-----------------------+

View File

@ -210,9 +210,9 @@ Instances of the :py:class:`Image` class have the following attributes:
.. py:attribute:: palette
Colour palette table, if any. If mode is “P”, this should be an instance of
the :py:class:`~PIL.ImagePalette.ImagePalette` class. Otherwise, it should
be set to ``None``.
Colour palette table, if any. If mode is "P" or "PA", this should be an
instance of the :py:class:`~PIL.ImagePalette.ImagePalette` class.
Otherwise, it should be set to ``None``.
:type: :py:class:`~PIL.ImagePalette.ImagePalette` or ``None``

View File

@ -287,11 +287,11 @@ Methods
.. versionadded:: 4.2.0
:param language: Language of the text. Different languages may use
: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
It should be a `BCP 47 language code
<https://www.w3.org/International/articles/language-tags/>`
Requires libraqm.
@ -326,11 +326,11 @@ Methods
.. versionadded:: 4.2.0
:param language: Language of the text. Different languages may use
: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
It should be a `BCP 47 language code
<https://www.w3.org/International/articles/language-tags/>`
Requires libraqm.
@ -362,11 +362,11 @@ Methods
Requires libraqm.
.. versionadded:: 4.2.0
:param language: Language of the text. Different languages may use
: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
It should be a `BCP 47 language code
<https://www.w3.org/International/articles/language-tags/>`
Requires libraqm.
@ -398,11 +398,11 @@ Methods
.. versionadded:: 4.2.0
:param language: Language of the text. Different languages may use
: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
It should be a `BCP 47 language code
<https://www.w3.org/International/articles/language-tags/>`
Requires libraqm.

View File

@ -38,12 +38,31 @@ image enhancement filters:
* **SMOOTH_MORE**
.. autoclass:: PIL.ImageFilter.Color3DLUT
:members:
.. autoclass:: PIL.ImageFilter.BoxBlur
:members:
.. autoclass:: PIL.ImageFilter.GaussianBlur
:members:
.. autoclass:: PIL.ImageFilter.UnsharpMask
:members:
.. autoclass:: PIL.ImageFilter.Kernel
:members:
.. autoclass:: PIL.ImageFilter.RankFilter
:members:
.. autoclass:: PIL.ImageFilter.MedianFilter
:members:
.. autoclass:: PIL.ImageFilter.MinFilter
:members:
.. autoclass:: PIL.ImageFilter.MaxFilter
:members:
.. autoclass:: PIL.ImageFilter.ModeFilter
:members:

View File

@ -51,9 +51,9 @@ Methods
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.
@ -73,11 +73,11 @@ Methods
.. versionadded:: 4.2.0
:param language: Language of the text. Different languages may use
: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
It should be a `BCP 47 language code
<https://www.w3.org/International/articles/language-tags/>`
Requires libraqm.
@ -119,11 +119,11 @@ Methods
.. versionadded:: 4.2.0
:param language: Language of the text. Different languages may use
: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
It should be a `BCP 47 language code
<https://www.w3.org/International/articles/language-tags/>`
Requires libraqm.
@ -131,3 +131,6 @@ Methods
:return: An internal PIL storage memory instance as defined by the
:py:mod:`PIL.Image.core` interface module.
.. autoclass:: PIL.ImageFont.FreeTypeFont
:members:

View File

@ -11,7 +11,7 @@ or the clipboard to a PIL image memory.
.. versionadded:: 1.1.3
.. py:function:: PIL.ImageGrab.grab(bbox=None)
.. py:function:: PIL.ImageGrab.grab(bbox=None, include_layered_windows=False)
Take a snapshot of the screen. The pixels inside the bounding box are
returned as an "RGB" image on Windows or "RGBA" on macOS.
@ -20,6 +20,7 @@ or the clipboard to a PIL image memory.
.. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS)
:param bbox: What region to copy. Default is the entire screen.
:param include_layered_windows: Includes layered windows. Windows OS only.
:return: An image
.. py:function:: PIL.ImageGrab.grabclipboard()

View File

@ -5,8 +5,8 @@
==========================
The :py:mod:`ImageMath` module can be used to evaluate “image expressions”. The
module provides a single eval function, which takes an expression string and
one or more images.
module provides a single :py:meth:`~PIL.ImageMath.eval` function, which takes
an expression string and one or more images.
Example: Using the :py:mod:`~PIL.ImageMath` module
--------------------------------------------------

View File

@ -25,3 +25,4 @@ The :py:class:`~PIL.ImageSequence.Iterator` class
-------------------------------------------------
.. autoclass:: PIL.ImageSequence.Iterator
:members:

View File

@ -20,6 +20,13 @@ for a region of an image.
Min/max values for each band in the image.
.. Note:: This relies on the :py:meth:`~PIL.Image.histogram` method, and simply
returns the low and high bins used. This is correct for images with 8 bits per
channel, but fails for other modes such as ``I`` or ``F``. Instead, use
:py:meth:`~PIL.Image.getextrema` to return per-band extrema for the image.
This is more correct and efficient because, for non-8-bit modes, the histogram
method uses :py:meth:`~PIL.Image.getextrema` to determine the bins used.
.. py:attribute:: count
Total number of pixels for each band in the image.

View File

@ -24,6 +24,8 @@ Tkinter makes the window handle available via the winfo_id method:
.. autoclass:: PIL.ImageWin.Dib
:members:
.. autoclass:: PIL.ImageWin.HDC
:members:
.. autoclass:: PIL.ImageWin.HWND
:members:

View File

@ -0,0 +1,39 @@
6.1.0
-----
API Additions
=============
ImageGrab.grab
^^^^^^^^^^^^^^
An optional ``include_layered_windows`` parameter has been added to ``ImageGrab.grab``,
defaulting to ``False``. If true, layered windows will be included in the resulting
image on Windows.
Variation fonts
^^^^^^^^^^^^^^^
Variation fonts are now supported, allowing for different styles from the same font
file. ``ImageFont.FreeTypeFont`` has four new methods,
:py:meth:`PIL.ImageFont.FreeTypeFont.get_variation_names` and
:py:meth:`PIL.ImageFont.FreeTypeFont.set_variation_by_name` for using named styles, and
:py:meth:`PIL.ImageFont.FreeTypeFont.get_variation_axes` and
:py:meth:`PIL.ImageFont.FreeTypeFont.set_variation_by_axes` for using font axes
instead. An ``IOError`` will be raised if the font is not a variation font. FreeType
2.9.1 or greater is required.
Other Changes
=============
ImageTk.getimage
^^^^^^^^^^^^^^^^
This function is now supported. It returns the contents of an ``ImageTk.PhotoImage`` as
an RGBA ``Image.Image`` instance.
Top To Bottom Complex Text Rendering
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Drawing text in the 'ttb' direction with ImageFont has been significantly improved
and requires Raqm 0.7 or greater.

View File

@ -6,6 +6,7 @@ Release Notes
.. toctree::
:maxdepth: 2
6.1.0
6.0.0
5.4.1
5.4.0

View File

@ -2,6 +2,7 @@
-e .
alabaster
Babel
black; python_version >= '3.6'
check-manifest
cov-core
coverage

View File

@ -1,8 +1,6 @@
[aliases]
test=pytest
[metadata]
license_file = LICENSE
[flake8]
extend-ignore = E203, W503
max-line-length = 88

View File

@ -439,9 +439,13 @@ class pil_build_ext(build_ext):
# alpine, at least
_add_directory(library_dirs, "/lib")
# on Windows, look for the OpenJPEG libraries in the location that
# the official installer puts them
if sys.platform == "win32":
if PLATFORM_MINGW:
_add_directory(include_dirs,
"C:\\msys64\\mingw32\\include\\libimagequant")
# on Windows, look for the OpenJPEG libraries in the location that
# the official installer puts them
program_files = os.environ.get('ProgramFiles', '')
best_version = (0, 0)
best_path = None

View File

@ -32,14 +32,10 @@ bdf_slant = {
"O": "Oblique",
"RI": "Reverse Italic",
"RO": "Reverse Oblique",
"OT": "Other"
"OT": "Other",
}
bdf_spacing = {
"P": "Proportional",
"M": "Monospaced",
"C": "Cell"
}
bdf_spacing = {"P": "Proportional", "M": "Monospaced", "C": "Cell"}
def bdf_char(f):
@ -50,7 +46,7 @@ def bdf_char(f):
return None
if s[:9] == b"STARTCHAR":
break
id = s[9:].strip().decode('ascii')
id = s[9:].strip().decode("ascii")
# load symbol properties
props = {}
@ -59,7 +55,7 @@ def bdf_char(f):
if not s or s[:6] == b"BITMAP":
break
i = s.find(b" ")
props[s[:i].decode('ascii')] = s[i+1:-1].decode('ascii')
props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
# load bitmap
bitmap = []
@ -73,7 +69,7 @@ def bdf_char(f):
[x, y, l, d] = [int(p) for p in props["BBX"].split()]
[dx, dy] = [int(p) for p in props["DWIDTH"].split()]
bbox = (dx, dy), (l, -d-y, x+l, -d), (0, 0, x, y)
bbox = (dx, dy), (l, -d - y, x + l, -d), (0, 0, x, y)
try:
im = Image.frombytes("1", (x, y), bitmap, "hex", "1")
@ -87,8 +83,8 @@ def bdf_char(f):
##
# Font file plugin for the X11 BDF format.
class BdfFontFile(FontFile.FontFile):
class BdfFontFile(FontFile.FontFile):
def __init__(self, fp):
FontFile.FontFile.__init__(self)
@ -105,10 +101,10 @@ class BdfFontFile(FontFile.FontFile):
if not s or s[:13] == b"ENDPROPERTIES":
break
i = s.find(b" ")
props[s[:i].decode('ascii')] = s[i+1:-1].decode('ascii')
props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
if s[:i] in [b"COMMENT", b"COPYRIGHT"]:
if s.find(b"LogicalFontDescription") < 0:
comments.append(s[i+1:-1].decode('ascii'))
comments.append(s[i + 1 : -1].decode("ascii"))
while True:
c = bdf_char(fp)

View File

@ -47,11 +47,7 @@ BLP_ALPHA_ENCODING_DXT5 = 7
def unpack_565(i):
return (
((i >> 11) & 0x1f) << 3,
((i >> 5) & 0x3f) << 2,
(i & 0x1f) << 3
)
return (((i >> 11) & 0x1F) << 3, ((i >> 5) & 0x3F) << 2, (i & 0x1F) << 3)
def decode_dxt1(data, alpha=False):
@ -119,7 +115,7 @@ def decode_dxt3(data):
for block in range(blocks):
idx = block * 16
block = data[idx:idx + 16]
block = data[idx : idx + 16]
# Decode next 16-byte block.
bits = struct.unpack_from("<8B", block)
color0, color1 = struct.unpack_from("<HH", block, 8)
@ -139,7 +135,7 @@ def decode_dxt3(data):
a >>= 4
else:
high = True
a &= 0xf
a &= 0xF
a *= 17 # We get a value between 0 and 15
color_code = (code >> 2 * (4 * j + i)) & 0x03
@ -172,14 +168,12 @@ def decode_dxt5(data):
for block in range(blocks):
idx = block * 16
block = data[idx:idx + 16]
block = data[idx : idx + 16]
# Decode next 16-byte block.
a0, a1 = struct.unpack_from("<BB", block)
bits = struct.unpack_from("<6B", block, 2)
alphacode1 = (
bits[2] | (bits[3] << 8) | (bits[4] << 16) | (bits[5] << 24)
)
alphacode1 = bits[2] | (bits[3] << 8) | (bits[4] << 16) | (bits[5] << 24)
alphacode2 = bits[0] | (bits[1] << 8)
color0, color1 = struct.unpack_from("<HH", block, 8)
@ -242,6 +236,7 @@ class BlpImageFile(ImageFile.ImageFile):
"""
Blizzard Mipmap Format
"""
format = "BLP"
format_description = "Blizzard Mipmap Format"
@ -258,9 +253,7 @@ class BlpImageFile(ImageFile.ImageFile):
else:
raise BLPFormatError("Bad BLP magic %r" % (self.magic))
self.tile = [
(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))
]
self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
def _read_blp_header(self):
self._blp_compression, = struct.unpack("<i", self.fp.read(4))
@ -324,7 +317,6 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
class BLP1Decoder(_BLPBaseDecoder):
def _load(self):
if self._blp_compression == BLP_FORMAT_JPEG:
self._decode_jpeg_stream()
@ -368,7 +360,6 @@ class BLP1Decoder(_BLPBaseDecoder):
class BLP2Decoder(_BLPBaseDecoder):
def _load(self):
palette = self._read_palette()
@ -393,8 +384,7 @@ class BLP2Decoder(_BLPBaseDecoder):
linesize = (self.size[0] + 3) // 4 * 8
for yb in range((self.size[1] + 3) // 4):
for d in decode_dxt1(
self.fd.read(linesize),
alpha=bool(self._blp_alpha_depth)
self.fd.read(linesize), alpha=bool(self._blp_alpha_depth)
):
data += d
@ -410,18 +400,14 @@ class BLP2Decoder(_BLPBaseDecoder):
for d in decode_dxt5(self.fd.read(linesize)):
data += d
else:
raise BLPFormatError("Unsupported alpha encoding %r" % (
self._blp_alpha_encoding
))
raise BLPFormatError(
"Unsupported alpha encoding %r" % (self._blp_alpha_encoding)
)
else:
raise BLPFormatError(
"Unknown BLP encoding %r" % (self._blp_encoding)
)
raise BLPFormatError("Unknown BLP encoding %r" % (self._blp_encoding))
else:
raise BLPFormatError(
"Unknown BLP compression %r" % (self._blp_compression)
)
raise BLPFormatError("Unknown BLP compression %r" % (self._blp_compression))
self.set_as_raw(bytes(data))

View File

@ -25,8 +25,7 @@
from . import Image, ImageFile, ImagePalette
from ._binary import i8, i16le as i16, i32le as i32, \
o8, o16le as o16, o32le as o32
from ._binary import i8, i16le as i16, i32le as i32, o8, o16le as o16, o32le as o32
# __version__ is deprecated and will be removed in a future version. Use
# PIL.__version__ instead.
@ -66,15 +65,9 @@ class BmpImageFile(ImageFile.ImageFile):
format = "BMP"
# -------------------------------------------------- BMP Compression values
COMPRESSIONS = {
'RAW': 0,
'RLE8': 1,
'RLE4': 2,
'BITFIELDS': 3,
'JPEG': 4,
'PNG': 5
}
RAW, RLE8, RLE4, BITFIELDS, JPEG, PNG = 0, 1, 2, 3, 4, 5
COMPRESSIONS = {"RAW": 0, "RLE8": 1, "RLE4": 2, "BITFIELDS": 3, "JPEG": 4, "PNG": 5}
for k, v in COMPRESSIONS.items():
vars()[k] = v
def _bitmap(self, header=0, offset=0):
""" Read relevant info about the BMP """
@ -83,53 +76,54 @@ class BmpImageFile(ImageFile.ImageFile):
seek(header)
file_info = {}
# read bmp header size @offset 14 (this is part of the header size)
file_info['header_size'] = i32(read(4))
file_info['direction'] = -1
file_info["header_size"] = i32(read(4))
file_info["direction"] = -1
# -------------------- If requested, read header at a specific position
# read the rest of the bmp header, without its size
header_data = ImageFile._safe_read(self.fp,
file_info['header_size'] - 4)
header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4)
# -------------------------------------------------- IBM OS/2 Bitmap v1
# ----- This format has different offsets because of width/height types
if file_info['header_size'] == 12:
file_info['width'] = i16(header_data[0:2])
file_info['height'] = i16(header_data[2:4])
file_info['planes'] = i16(header_data[4:6])
file_info['bits'] = i16(header_data[6:8])
file_info['compression'] = self.RAW
file_info['palette_padding'] = 3
if file_info["header_size"] == 12:
file_info["width"] = i16(header_data[0:2])
file_info["height"] = i16(header_data[2:4])
file_info["planes"] = i16(header_data[4:6])
file_info["bits"] = i16(header_data[6:8])
file_info["compression"] = self.RAW
file_info["palette_padding"] = 3
# --------------------------------------------- Windows Bitmap v2 to v5
# v3, OS/2 v2, v4, v5
elif file_info['header_size'] in (40, 64, 108, 124):
file_info['y_flip'] = i8(header_data[7]) == 0xff
file_info['direction'] = 1 if file_info['y_flip'] else -1
file_info['width'] = i32(header_data[0:4])
file_info['height'] = (i32(header_data[4:8])
if not file_info['y_flip']
else 2**32 - i32(header_data[4:8]))
file_info['planes'] = i16(header_data[8:10])
file_info['bits'] = i16(header_data[10:12])
file_info['compression'] = i32(header_data[12:16])
elif file_info["header_size"] in (40, 64, 108, 124):
file_info["y_flip"] = i8(header_data[7]) == 0xFF
file_info["direction"] = 1 if file_info["y_flip"] else -1
file_info["width"] = i32(header_data[0:4])
file_info["height"] = (
i32(header_data[4:8])
if not file_info["y_flip"]
else 2 ** 32 - i32(header_data[4:8])
)
file_info["planes"] = i16(header_data[8:10])
file_info["bits"] = i16(header_data[10:12])
file_info["compression"] = i32(header_data[12:16])
# byte size of pixel data
file_info['data_size'] = i32(header_data[16:20])
file_info['pixels_per_meter'] = (i32(header_data[20:24]),
i32(header_data[24:28]))
file_info['colors'] = i32(header_data[28:32])
file_info['palette_padding'] = 4
file_info["data_size"] = i32(header_data[16:20])
file_info["pixels_per_meter"] = (
i32(header_data[20:24]),
i32(header_data[24:28]),
)
file_info["colors"] = i32(header_data[28:32])
file_info["palette_padding"] = 4
self.info["dpi"] = tuple(
int(x / 39.3701 + 0.5) for x in file_info['pixels_per_meter'])
if file_info['compression'] == self.BITFIELDS:
int(x / 39.3701 + 0.5) for x in file_info["pixels_per_meter"]
)
if file_info["compression"] == self.BITFIELDS:
if len(header_data) >= 52:
for idx, mask in enumerate(['r_mask',
'g_mask',
'b_mask',
'a_mask']):
file_info[mask] = i32(
header_data[36 + idx * 4:40 + idx * 4]
)
for idx, mask in enumerate(
["r_mask", "g_mask", "b_mask", "a_mask"]
):
file_info[mask] = i32(header_data[36 + idx * 4 : 40 + idx * 4])
else:
# 40 byte headers only have the three components in the
# bitfields masks, ref:
@ -139,121 +133,133 @@ class BmpImageFile(ImageFile.ImageFile):
# There is a 4th component in the RGBQuad, in the alpha
# location, but it is listed as a reserved component,
# and it is not generally an alpha channel
file_info['a_mask'] = 0x0
for mask in ['r_mask', 'g_mask', 'b_mask']:
file_info["a_mask"] = 0x0
for mask in ["r_mask", "g_mask", "b_mask"]:
file_info[mask] = i32(read(4))
file_info['rgb_mask'] = (file_info['r_mask'],
file_info['g_mask'],
file_info['b_mask'])
file_info['rgba_mask'] = (file_info['r_mask'],
file_info['g_mask'],
file_info['b_mask'],
file_info['a_mask'])
file_info["rgb_mask"] = (
file_info["r_mask"],
file_info["g_mask"],
file_info["b_mask"],
)
file_info["rgba_mask"] = (
file_info["r_mask"],
file_info["g_mask"],
file_info["b_mask"],
file_info["a_mask"],
)
else:
raise IOError("Unsupported BMP header type (%d)" %
file_info['header_size'])
raise IOError("Unsupported BMP header type (%d)" % file_info["header_size"])
# ------------------ Special case : header is reported 40, which
# ---------------------- is shorter than real size for bpp >= 16
self._size = file_info['width'], file_info['height']
self._size = file_info["width"], file_info["height"]
# ------- If color count was not found in the header, compute from bits
file_info["colors"] = (file_info["colors"]
if file_info.get("colors", 0)
else (1 << file_info["bits"]))
file_info["colors"] = (
file_info["colors"]
if file_info.get("colors", 0)
else (1 << file_info["bits"])
)
# ------------------------------- Check abnormal values for DOS attacks
if file_info['width'] * file_info['height'] > 2**31:
if file_info["width"] * file_info["height"] > 2 ** 31:
raise IOError("Unsupported BMP Size: (%dx%d)" % self.size)
# ---------------------- Check bit depth for unusual unsupported values
self.mode, raw_mode = BIT2MODE.get(file_info['bits'], (None, None))
self.mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None))
if self.mode is None:
raise IOError("Unsupported BMP pixel depth (%d)"
% file_info['bits'])
raise IOError("Unsupported BMP pixel depth (%d)" % file_info["bits"])
# ---------------- Process BMP with Bitfields compression (not palette)
if file_info['compression'] == self.BITFIELDS:
if file_info["compression"] == self.BITFIELDS:
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)],
16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)]
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)],
16: [(0xF800, 0x7E0, 0x1F), (0x7C00, 0x3E0, 0x1F)],
}
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, (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",
(16, (0xf800, 0x7e0, 0x1f)): "BGR;16",
(16, (0x7c00, 0x3e0, 0x1f)): "BGR;15"
(24, (0xFF0000, 0xFF00, 0xFF)): "BGR",
(16, (0xF800, 0x7E0, 0x1F)): "BGR;16",
(16, (0x7C00, 0x3E0, 0x1F)): "BGR;15",
}
if file_info['bits'] in SUPPORTED:
if file_info['bits'] == 32 and \
file_info['rgba_mask'] in SUPPORTED[file_info['bits']]:
raw_mode = MASK_MODES[
(file_info["bits"], file_info["rgba_mask"])
]
if file_info["bits"] in SUPPORTED:
if (
file_info["bits"] == 32
and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]]
):
raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])]
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[
(file_info['bits'], file_info['rgb_mask'])
]
elif (
file_info["bits"] in (24, 16)
and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]]
):
raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])]
else:
raise IOError("Unsupported BMP bitfields layout")
else:
raise IOError("Unsupported BMP bitfields layout")
elif file_info['compression'] == self.RAW:
if file_info['bits'] == 32 and header == 22: # 32-bit .cur offset
elif file_info["compression"] == self.RAW:
if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset
raw_mode, self.mode = "BGRA", "RGBA"
else:
raise IOError("Unsupported BMP compression (%d)" %
file_info['compression'])
raise IOError("Unsupported BMP compression (%d)" % file_info["compression"])
# --------------- Once the header is processed, process the palette/LUT
if self.mode == "P": # Paletted for 1, 4 and 8 bit images
# ---------------------------------------------------- 1-bit images
if not (0 < file_info['colors'] <= 65536):
raise IOError("Unsupported BMP Palette size (%d)" %
file_info['colors'])
if not (0 < file_info["colors"] <= 65536):
raise IOError("Unsupported BMP Palette size (%d)" % file_info["colors"])
else:
padding = file_info['palette_padding']
palette = read(padding * file_info['colors'])
padding = file_info["palette_padding"]
palette = read(padding * file_info["colors"])
greyscale = True
indices = (0, 255) if file_info['colors'] == 2 else \
list(range(file_info['colors']))
indices = (
(0, 255)
if file_info["colors"] == 2
else list(range(file_info["colors"]))
)
# ----------------- Check if greyscale and ignore palette if so
for ind, val in enumerate(indices):
rgb = palette[ind*padding:ind*padding + 3]
rgb = palette[ind * padding : ind * padding + 3]
if rgb != o8(val) * 3:
greyscale = False
# ------- If all colors are grey, white or black, ditch palette
if greyscale:
self.mode = "1" if file_info['colors'] == 2 else "L"
self.mode = "1" if file_info["colors"] == 2 else "L"
raw_mode = self.mode
else:
self.mode = "P"
self.palette = ImagePalette.raw(
"BGRX" if padding == 4 else "BGR", palette)
"BGRX" if padding == 4 else "BGR", palette
)
# ---------------------------- Finally set the tile data for the plugin
self.info['compression'] = file_info['compression']
self.info["compression"] = file_info["compression"]
self.tile = [
('raw',
(0, 0, file_info['width'], file_info['height']),
offset or self.fp.tell(),
(raw_mode,
((file_info['width'] * file_info['bits'] + 31) >> 3) & (~3),
file_info['direction']))
(
"raw",
(0, 0, file_info["width"], file_info["height"]),
offset or self.fp.tell(),
(
raw_mode,
((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3),
file_info["direction"],
),
)
]
def _open(self):
@ -280,6 +286,7 @@ class DibImageFile(BmpImageFile):
def _open(self):
self._bitmap()
#
# --------------------------------------------------------------------
# Write BMP file
@ -311,31 +318,36 @@ def _save(im, fp, filename, bitmap_header=True):
# 1 meter == 39.3701 inches
ppm = tuple(map(lambda x: int(x * 39.3701 + 0.5), dpi))
stride = ((im.size[0]*bits+7)//8+3) & (~3)
stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3)
header = 40 # or 64 for OS/2 version 2
image = stride * im.size[1]
# bitmap header
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
fp.write(
b"BM"
+ o32(offset + image) # file type (magic)
+ o32(0) # file size
+ o32(offset) # reserved
) # image data offset
# bitmap info header
fp.write(o32(header) + # info header size
o32(im.size[0]) + # width
o32(im.size[1]) + # height
o16(1) + # planes
o16(bits) + # depth
o32(0) + # compression (0=uncompressed)
o32(image) + # size of bitmap
o32(ppm[0]) + o32(ppm[1]) + # resolution
o32(colors) + # colors used
o32(colors)) # colors important
fp.write(
o32(header) # info header size
+ o32(im.size[0]) # width
+ o32(im.size[1]) # height
+ o16(1) # planes
+ o16(bits) # depth
+ o32(0) # compression (0=uncompressed)
+ o32(image) # size of bitmap
+ o32(ppm[0]) # resolution
+ o32(ppm[1]) # resolution
+ o32(colors) # colors used
+ o32(colors) # colors important
)
fp.write(b"\0" * (header - 40)) # padding (for OS/2 format)
fp.write(b"\0" * (header - 40)) # padding (for OS/2 format)
if im.mode == "1":
for i in (0, 255):
@ -346,8 +358,8 @@ def _save(im, fp, filename, bitmap_header=True):
elif im.mode == "P":
fp.write(im.im.getpalette("RGB", "BGRX"))
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0,
(rawmode, stride, -1))])
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, stride, -1))])
#
# --------------------------------------------------------------------

View File

@ -27,6 +27,7 @@ def register_handler(handler):
# --------------------------------------------------------------------
# Image adapter
def _accept(prefix):
return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC"

View File

@ -22,7 +22,6 @@ import io
class ContainerIO(object):
def __init__(self, file, offset, length):
"""
Create file object.

View File

@ -36,6 +36,7 @@ def _accept(prefix):
##
# Image plugin for Windows Cursor files.
class CurImageFile(BmpImagePlugin.BmpImageFile):
format = "CUR"
@ -65,9 +66,9 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
self._bitmap(i32(m[12:]) + offset)
# patch up the bitmap height
self._size = self.size[0], self.size[1]//2
self._size = self.size[0], self.size[1] // 2
d, e, o, a = self.tile[0]
self.tile[0] = d, (0, 0)+self.size, o, a
self.tile[0] = d, (0, 0) + self.size, o, a
return

View File

@ -39,6 +39,7 @@ def _accept(prefix):
##
# Image plugin for the Intel DCX format.
class DcxImageFile(PcxImageFile):
format = "DCX"

View File

@ -61,8 +61,7 @@ DDS_LUMINANCEA = DDPF_LUMINANCE | DDPF_ALPHAPIXELS
DDS_ALPHA = DDPF_ALPHA
DDS_PAL8 = DDPF_PALETTEINDEXED8
DDS_HEADER_FLAGS_TEXTURE = (DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH |
DDSD_PIXELFORMAT)
DDS_HEADER_FLAGS_TEXTURE = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT
DDS_HEADER_FLAGS_MIPMAP = DDSD_MIPMAPCOUNT
DDS_HEADER_FLAGS_VOLUME = DDSD_DEPTH
DDS_HEADER_FLAGS_PITCH = DDSD_PITCH
@ -130,8 +129,8 @@ class DdsImageFile(ImageFile.ImageFile):
masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)}
rawmode = ""
if bitcount == 32:
rawmode += masks[0xff000000]
rawmode += masks[0xff0000] + masks[0xff00] + masks[0xff]
rawmode += masks[0xFF000000]
rawmode += masks[0xFF0000] + masks[0xFF00] + masks[0xFF]
self.tile = [("raw", (0, 0) + self.size, 0, (rawmode, 0, 1))]
else:
@ -151,24 +150,21 @@ class DdsImageFile(ImageFile.ImageFile):
# ignoring flags which pertain to volume textures and cubemaps
dxt10 = BytesIO(self.fp.read(20))
dxgi_format, dimension = struct.unpack("<II", dxt10.read(8))
if dxgi_format in (DXGI_FORMAT_BC7_TYPELESS,
DXGI_FORMAT_BC7_UNORM):
if dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM):
self.pixel_format = "BC7"
n = 7
elif dxgi_format == DXGI_FORMAT_BC7_UNORM_SRGB:
self.pixel_format = "BC7"
self.im_info["gamma"] = 1/2.2
self.im_info["gamma"] = 1 / 2.2
n = 7
else:
raise NotImplementedError("Unimplemented DXGI format %d" %
(dxgi_format))
raise NotImplementedError(
"Unimplemented DXGI format %d" % (dxgi_format)
)
else:
raise NotImplementedError("Unimplemented pixel format %r" %
(fourcc))
raise NotImplementedError("Unimplemented pixel format %r" % (fourcc))
self.tile = [
("bcn", (0, 0) + self.size, data_start, (n))
]
self.tile = [("bcn", (0, 0) + self.size, data_start, (n))]
def load_seek(self, pos):
pass

View File

@ -38,15 +38,17 @@ split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
gs_windows_binary = None
if sys.platform.startswith('win'):
if sys.platform.startswith("win"):
import shutil
if hasattr(shutil, 'which'):
if hasattr(shutil, "which"):
which = shutil.which
else:
# Python 2
import distutils.spawn
which = distutils.spawn.find_executable
for binary in ('gswin32c', 'gswin64c', 'gs'):
for binary in ("gswin32c", "gswin64c", "gs"):
if which(binary) is not None:
gs_windows_binary = binary
break
@ -57,11 +59,12 @@ if sys.platform.startswith('win'):
def has_ghostscript():
if gs_windows_binary:
return True
if not sys.platform.startswith('win'):
if not sys.platform.startswith("win"):
import subprocess
try:
with open(os.devnull, 'wb') as devnull:
subprocess.check_call(['gs', '--version'], stdout=devnull)
with open(os.devnull, "wb") as devnull:
subprocess.check_call(["gs", "--version"], stdout=devnull)
return True
except OSError:
# No Ghostscript
@ -82,8 +85,10 @@ def Ghostscript(tile, size, fp, scale=1):
# orig_bbox = bbox
size = (size[0] * scale, size[1] * scale)
# resolution is dependent on bbox and size
res = (float((72.0 * size[0]) / (bbox[2]-bbox[0])),
float((72.0 * size[1]) / (bbox[3]-bbox[1])))
res = (
float((72.0 * size[0]) / (bbox[2] - bbox[0])),
float((72.0 * size[1]) / (bbox[3] - bbox[1])),
)
import subprocess
import tempfile
@ -92,7 +97,7 @@ def Ghostscript(tile, size, fp, scale=1):
os.close(out_fd)
infile_temp = None
if hasattr(fp, 'name') and os.path.exists(fp.name):
if hasattr(fp, "name") and os.path.exists(fp.name):
infile = fp.name
else:
in_fd, infile_temp = tempfile.mkstemp()
@ -102,7 +107,7 @@ def Ghostscript(tile, size, fp, scale=1):
# Ignore length and offset!
# Ghostscript can read it
# Copy whole file to read in Ghostscript
with open(infile_temp, 'wb') as f:
with open(infile_temp, "wb") as f:
# fetch length of fp
fp.seek(0, io.SEEK_END)
fsize = fp.tell()
@ -111,38 +116,42 @@ def Ghostscript(tile, size, fp, scale=1):
fp.seek(0)
lengthfile = fsize
while lengthfile > 0:
s = fp.read(min(lengthfile, 100*1024))
s = fp.read(min(lengthfile, 100 * 1024))
if not s:
break
lengthfile -= len(s)
f.write(s)
# Build Ghostscript command
command = ["gs",
"-q", # quiet mode
"-g%dx%d" % size, # set output geometry (pixels)
"-r%fx%f" % res, # set input DPI (dots per inch)
"-dBATCH", # exit after processing
"-dNOPAUSE", # don't pause between pages
"-dSAFER", # safe mode
"-sDEVICE=ppmraw", # ppm driver
"-sOutputFile=%s" % outfile, # output file
# adjust for image origin
"-c", "%d %d translate" % (-bbox[0], -bbox[1]),
"-f", infile, # input file
# showpage (see https://bugs.ghostscript.com/show_bug.cgi?id=698272)
"-c", "showpage",
]
command = [
"gs",
"-q", # quiet mode
"-g%dx%d" % size, # set output geometry (pixels)
"-r%fx%f" % res, # set input DPI (dots per inch)
"-dBATCH", # exit after processing
"-dNOPAUSE", # don't pause between pages
"-dSAFER", # safe mode
"-sDEVICE=ppmraw", # ppm driver
"-sOutputFile=%s" % outfile, # output file
# adjust for image origin
"-c",
"%d %d translate" % (-bbox[0], -bbox[1]),
"-f",
infile, # input file
# showpage (see https://bugs.ghostscript.com/show_bug.cgi?id=698272)
"-c",
"showpage",
]
if gs_windows_binary is not None:
if not gs_windows_binary:
raise WindowsError('Unable to locate Ghostscript on paths')
raise WindowsError("Unable to locate Ghostscript on paths")
command[0] = gs_windows_binary
# push data through Ghostscript
try:
startupinfo = None
if sys.platform.startswith('win'):
if sys.platform.startswith("win"):
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
subprocess.check_call(command, startupinfo=startupinfo)
@ -163,6 +172,7 @@ class PSFile(object):
"""
Wrapper for bytesio object that treats either CR or LF as end of line.
"""
def __init__(self, fp):
self.fp = fp
self.char = None
@ -185,12 +195,12 @@ class PSFile(object):
if self.char in b"\r\n":
self.char = None
return s.decode('latin-1')
return s.decode("latin-1")
def _accept(prefix):
return prefix[:4] == b"%!PS" or \
(len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5)
return prefix[:4] == b"%!PS" or (len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5)
##
# Image plugin for Encapsulated Postscript. This plugin supports only
@ -224,7 +234,7 @@ class EpsImageFile(ImageFile.ImageFile):
# Load EPS header
s_raw = fp.readline()
s = s_raw.strip('\r\n')
s = s_raw.strip("\r\n")
while s_raw:
if s:
@ -246,8 +256,9 @@ class EpsImageFile(ImageFile.ImageFile):
# put floating point values there anyway.
box = [int(float(i)) for i in v.split()]
self._size = box[2] - box[0], box[3] - box[1]
self.tile = [("eps", (0, 0) + self.size, offset,
(length, box))]
self.tile = [
("eps", (0, 0) + self.size, offset, (length, box))
]
except Exception:
pass
@ -262,7 +273,7 @@ class EpsImageFile(ImageFile.ImageFile):
self.info[k[:8]] = k[9:]
else:
self.info[k] = ""
elif s[0] == '%':
elif s[0] == "%":
# handle non-DSC Postscript comments that some
# tools mistakenly put in the Comments section
pass
@ -270,7 +281,7 @@ class EpsImageFile(ImageFile.ImageFile):
raise IOError("bad EPS header")
s_raw = fp.readline()
s = s_raw.strip('\r\n')
s = s_raw.strip("\r\n")
if s and s[:1] != "%":
break
@ -297,7 +308,7 @@ class EpsImageFile(ImageFile.ImageFile):
self._size = int(x), int(y)
return
s = fp.readline().strip('\r\n')
s = fp.readline().strip("\r\n")
if not s:
break
@ -344,6 +355,7 @@ class EpsImageFile(ImageFile.ImageFile):
#
# --------------------------------------------------------------------
def _save(im, fp, filename, eps=1):
"""EPS Writer for the Python Imaging Library."""
@ -366,7 +378,7 @@ def _save(im, fp, filename, eps=1):
wrapped_fp = False
if fp != sys.stdout:
if sys.version_info.major > 2:
fp = io.TextIOWrapper(fp, encoding='latin-1')
fp = io.TextIOWrapper(fp, encoding="latin-1")
wrapped_fp = True
try:
@ -381,7 +393,7 @@ def _save(im, fp, filename, eps=1):
fp.write("%%EndComments\n")
fp.write("%%Page: 1 1\n")
fp.write("%%ImageData: %d %d " % im.size)
fp.write("%d %d 0 1 1 \"%s\"\n" % operator)
fp.write('%d %d 0 1 1 "%s"\n' % operator)
#
# image header
@ -396,7 +408,7 @@ def _save(im, fp, filename, eps=1):
if hasattr(fp, "flush"):
fp.flush()
ImageFile._save(im, base_fp, [("eps", (0, 0)+im.size, 0, None)])
ImageFile._save(im, base_fp, [("eps", (0, 0) + im.size, 0, None)])
fp.write("\n%%%%EndBinary\n")
fp.write("grestore end\n")
@ -406,6 +418,7 @@ def _save(im, fp, filename, eps=1):
if wrapped_fp:
fp.detach()
#
# --------------------------------------------------------------------

View File

@ -18,11 +18,10 @@
# Maps EXIF tags to tag names.
TAGS = {
# possibly incomplete
0x000b: "ProcessingSoftware",
0x00fe: "NewSubfileType",
0x00ff: "SubfileType",
0x000B: "ProcessingSoftware",
0x00FE: "NewSubfileType",
0x00FF: "SubfileType",
0x0100: "ImageWidth",
0x0101: "ImageLength",
0x0102: "BitsPerSample",
@ -31,10 +30,10 @@ TAGS = {
0x0107: "Thresholding",
0x0108: "CellWidth",
0x0109: "CellLength",
0x010a: "FillOrder",
0x010d: "DocumentName",
0x010e: "ImageDescription",
0x010f: "Make",
0x010A: "FillOrder",
0x010D: "DocumentName",
0x010E: "ImageDescription",
0x010F: "Make",
0x0110: "Model",
0x0111: "StripOffsets",
0x0112: "Orientation",
@ -43,10 +42,10 @@ TAGS = {
0x0117: "StripByteCounts",
0x0118: "MinSampleValue",
0x0119: "MaxSampleValue",
0x011a: "XResolution",
0x011b: "YResolution",
0x011c: "PlanarConfiguration",
0x011d: "PageName",
0x011A: "XResolution",
0x011B: "YResolution",
0x011C: "PlanarConfiguration",
0x011D: "PageName",
0x0120: "FreeOffsets",
0x0121: "FreeByteCounts",
0x0122: "GrayResponseUnit",
@ -55,24 +54,24 @@ TAGS = {
0x0125: "T6Options",
0x0128: "ResolutionUnit",
0x0129: "PageNumber",
0x012d: "TransferFunction",
0x012D: "TransferFunction",
0x0131: "Software",
0x0132: "DateTime",
0x013b: "Artist",
0x013c: "HostComputer",
0x013d: "Predictor",
0x013e: "WhitePoint",
0x013f: "PrimaryChromaticities",
0x013B: "Artist",
0x013C: "HostComputer",
0x013D: "Predictor",
0x013E: "WhitePoint",
0x013F: "PrimaryChromaticities",
0x0140: "ColorMap",
0x0141: "HalftoneHints",
0x0142: "TileWidth",
0x0143: "TileLength",
0x0144: "TileOffsets",
0x0145: "TileByteCounts",
0x014a: "SubIFDs",
0x014c: "InkSet",
0x014d: "InkNames",
0x014e: "NumberOfInks",
0x014A: "SubIFDs",
0x014C: "InkSet",
0x014D: "InkNames",
0x014E: "NumberOfInks",
0x0150: "DotRange",
0x0151: "TargetPrinter",
0x0152: "ExtraSamples",
@ -83,9 +82,9 @@ TAGS = {
0x0157: "ClipPath",
0x0158: "XClipPathUnits",
0x0159: "YClipPathUnits",
0x015a: "Indexed",
0x015b: "JPEGTables",
0x015f: "OPIProxy",
0x015A: "Indexed",
0x015B: "JPEGTables",
0x015F: "OPIProxy",
0x0200: "JPEGProc",
0x0201: "JpegIFOffset",
0x0202: "JpegIFByteCount",
@ -99,20 +98,20 @@ TAGS = {
0x0212: "YCbCrSubSampling",
0x0213: "YCbCrPositioning",
0x0214: "ReferenceBlackWhite",
0x02bc: "XMLPacket",
0x02BC: "XMLPacket",
0x1000: "RelatedImageFileFormat",
0x1001: "RelatedImageWidth",
0x1002: "RelatedImageLength",
0x4746: "Rating",
0x4749: "RatingPercent",
0x800d: "ImageID",
0x828d: "CFARepeatPatternDim",
0x828e: "CFAPattern",
0x828f: "BatteryLevel",
0x800D: "ImageID",
0x828D: "CFARepeatPatternDim",
0x828E: "CFAPattern",
0x828F: "BatteryLevel",
0x8298: "Copyright",
0x829a: "ExposureTime",
0x829d: "FNumber",
0x83bb: "IPTCNAA",
0x829A: "ExposureTime",
0x829D: "FNumber",
0x83BB: "IPTCNAA",
0x8649: "ImageResources",
0x8769: "ExifOffset",
0x8773: "InterColorProfile",
@ -122,8 +121,8 @@ TAGS = {
0x8827: "ISOSpeedRatings",
0x8828: "OECF",
0x8829: "Interlace",
0x882a: "TimeZoneOffset",
0x882b: "SelfTimerMode",
0x882A: "TimeZoneOffset",
0x882B: "SelfTimerMode",
0x9000: "ExifVersion",
0x9003: "DateTimeOriginal",
0x9004: "DateTimeDigitized",
@ -138,142 +137,142 @@ TAGS = {
0x9207: "MeteringMode",
0x9208: "LightSource",
0x9209: "Flash",
0x920a: "FocalLength",
0x920b: "FlashEnergy",
0x920c: "SpatialFrequencyResponse",
0x920d: "Noise",
0x920A: "FocalLength",
0x920B: "FlashEnergy",
0x920C: "SpatialFrequencyResponse",
0x920D: "Noise",
0x9211: "ImageNumber",
0x9212: "SecurityClassification",
0x9213: "ImageHistory",
0x9214: "SubjectLocation",
0x9215: "ExposureIndex",
0x9216: "TIFF/EPStandardID",
0x927c: "MakerNote",
0x927C: "MakerNote",
0x9286: "UserComment",
0x9290: "SubsecTime",
0x9291: "SubsecTimeOriginal",
0x9292: "SubsecTimeDigitized",
0x9c9b: "XPTitle",
0x9c9c: "XPComment",
0x9c9d: "XPAuthor",
0x9c9e: "XPKeywords",
0x9c9f: "XPSubject",
0xa000: "FlashPixVersion",
0xa001: "ColorSpace",
0xa002: "ExifImageWidth",
0xa003: "ExifImageHeight",
0xa004: "RelatedSoundFile",
0xa005: "ExifInteroperabilityOffset",
0xa20b: "FlashEnergy",
0xa20c: "SpatialFrequencyResponse",
0xa20e: "FocalPlaneXResolution",
0xa20f: "FocalPlaneYResolution",
0xa210: "FocalPlaneResolutionUnit",
0xa214: "SubjectLocation",
0xa215: "ExposureIndex",
0xa217: "SensingMethod",
0xa300: "FileSource",
0xa301: "SceneType",
0xa302: "CFAPattern",
0xa401: "CustomRendered",
0xa402: "ExposureMode",
0xa403: "WhiteBalance",
0xa404: "DigitalZoomRatio",
0xa405: "FocalLengthIn35mmFilm",
0xa406: "SceneCaptureType",
0xa407: "GainControl",
0xa408: "Contrast",
0xa409: "Saturation",
0xa40a: "Sharpness",
0xa40b: "DeviceSettingDescription",
0xa40c: "SubjectDistanceRange",
0xa420: "ImageUniqueID",
0xa430: "CameraOwnerName",
0xa431: "BodySerialNumber",
0xa432: "LensSpecification",
0xa433: "LensMake",
0xa434: "LensModel",
0xa435: "LensSerialNumber",
0xa500: "Gamma",
0xc4a5: "PrintImageMatching",
0xc612: "DNGVersion",
0xc613: "DNGBackwardVersion",
0xc614: "UniqueCameraModel",
0xc615: "LocalizedCameraModel",
0xc616: "CFAPlaneColor",
0xc617: "CFALayout",
0xc618: "LinearizationTable",
0xc619: "BlackLevelRepeatDim",
0xc61a: "BlackLevel",
0xc61b: "BlackLevelDeltaH",
0xc61c: "BlackLevelDeltaV",
0xc61d: "WhiteLevel",
0xc61e: "DefaultScale",
0xc61f: "DefaultCropOrigin",
0xc620: "DefaultCropSize",
0xc621: "ColorMatrix1",
0xc622: "ColorMatrix2",
0xc623: "CameraCalibration1",
0xc624: "CameraCalibration2",
0xc625: "ReductionMatrix1",
0xc626: "ReductionMatrix2",
0xc627: "AnalogBalance",
0xc628: "AsShotNeutral",
0xc629: "AsShotWhiteXY",
0xc62a: "BaselineExposure",
0xc62b: "BaselineNoise",
0xc62c: "BaselineSharpness",
0xc62d: "BayerGreenSplit",
0xc62e: "LinearResponseLimit",
0xc62f: "CameraSerialNumber",
0xc630: "LensInfo",
0xc631: "ChromaBlurRadius",
0xc632: "AntiAliasStrength",
0xc633: "ShadowScale",
0xc634: "DNGPrivateData",
0xc635: "MakerNoteSafety",
0xc65a: "CalibrationIlluminant1",
0xc65b: "CalibrationIlluminant2",
0xc65c: "BestQualityScale",
0xc65d: "RawDataUniqueID",
0xc68b: "OriginalRawFileName",
0xc68c: "OriginalRawFileData",
0xc68d: "ActiveArea",
0xc68e: "MaskedAreas",
0xc68f: "AsShotICCProfile",
0xc690: "AsShotPreProfileMatrix",
0xc691: "CurrentICCProfile",
0xc692: "CurrentPreProfileMatrix",
0xc6bf: "ColorimetricReference",
0xc6f3: "CameraCalibrationSignature",
0xc6f4: "ProfileCalibrationSignature",
0xc6f6: "AsShotProfileName",
0xc6f7: "NoiseReductionApplied",
0xc6f8: "ProfileName",
0xc6f9: "ProfileHueSatMapDims",
0xc6fa: "ProfileHueSatMapData1",
0xc6fb: "ProfileHueSatMapData2",
0xc6fc: "ProfileToneCurve",
0xc6fd: "ProfileEmbedPolicy",
0xc6fe: "ProfileCopyright",
0xc714: "ForwardMatrix1",
0xc715: "ForwardMatrix2",
0xc716: "PreviewApplicationName",
0xc717: "PreviewApplicationVersion",
0xc718: "PreviewSettingsName",
0xc719: "PreviewSettingsDigest",
0xc71a: "PreviewColorSpace",
0xc71b: "PreviewDateTime",
0xc71c: "RawImageDigest",
0xc71d: "OriginalRawFileDigest",
0xc71e: "SubTileBlockSize",
0xc71f: "RowInterleaveFactor",
0xc725: "ProfileLookTableDims",
0xc726: "ProfileLookTableData",
0xc740: "OpcodeList1",
0xc741: "OpcodeList2",
0xc74e: "OpcodeList3",
0xc761: "NoiseProfile"
0x9C9B: "XPTitle",
0x9C9C: "XPComment",
0x9C9D: "XPAuthor",
0x9C9E: "XPKeywords",
0x9C9F: "XPSubject",
0xA000: "FlashPixVersion",
0xA001: "ColorSpace",
0xA002: "ExifImageWidth",
0xA003: "ExifImageHeight",
0xA004: "RelatedSoundFile",
0xA005: "ExifInteroperabilityOffset",
0xA20B: "FlashEnergy",
0xA20C: "SpatialFrequencyResponse",
0xA20E: "FocalPlaneXResolution",
0xA20F: "FocalPlaneYResolution",
0xA210: "FocalPlaneResolutionUnit",
0xA214: "SubjectLocation",
0xA215: "ExposureIndex",
0xA217: "SensingMethod",
0xA300: "FileSource",
0xA301: "SceneType",
0xA302: "CFAPattern",
0xA401: "CustomRendered",
0xA402: "ExposureMode",
0xA403: "WhiteBalance",
0xA404: "DigitalZoomRatio",
0xA405: "FocalLengthIn35mmFilm",
0xA406: "SceneCaptureType",
0xA407: "GainControl",
0xA408: "Contrast",
0xA409: "Saturation",
0xA40A: "Sharpness",
0xA40B: "DeviceSettingDescription",
0xA40C: "SubjectDistanceRange",
0xA420: "ImageUniqueID",
0xA430: "CameraOwnerName",
0xA431: "BodySerialNumber",
0xA432: "LensSpecification",
0xA433: "LensMake",
0xA434: "LensModel",
0xA435: "LensSerialNumber",
0xA500: "Gamma",
0xC4A5: "PrintImageMatching",
0xC612: "DNGVersion",
0xC613: "DNGBackwardVersion",
0xC614: "UniqueCameraModel",
0xC615: "LocalizedCameraModel",
0xC616: "CFAPlaneColor",
0xC617: "CFALayout",
0xC618: "LinearizationTable",
0xC619: "BlackLevelRepeatDim",
0xC61A: "BlackLevel",
0xC61B: "BlackLevelDeltaH",
0xC61C: "BlackLevelDeltaV",
0xC61D: "WhiteLevel",
0xC61E: "DefaultScale",
0xC61F: "DefaultCropOrigin",
0xC620: "DefaultCropSize",
0xC621: "ColorMatrix1",
0xC622: "ColorMatrix2",
0xC623: "CameraCalibration1",
0xC624: "CameraCalibration2",
0xC625: "ReductionMatrix1",
0xC626: "ReductionMatrix2",
0xC627: "AnalogBalance",
0xC628: "AsShotNeutral",
0xC629: "AsShotWhiteXY",
0xC62A: "BaselineExposure",
0xC62B: "BaselineNoise",
0xC62C: "BaselineSharpness",
0xC62D: "BayerGreenSplit",
0xC62E: "LinearResponseLimit",
0xC62F: "CameraSerialNumber",
0xC630: "LensInfo",
0xC631: "ChromaBlurRadius",
0xC632: "AntiAliasStrength",
0xC633: "ShadowScale",
0xC634: "DNGPrivateData",
0xC635: "MakerNoteSafety",
0xC65A: "CalibrationIlluminant1",
0xC65B: "CalibrationIlluminant2",
0xC65C: "BestQualityScale",
0xC65D: "RawDataUniqueID",
0xC68B: "OriginalRawFileName",
0xC68C: "OriginalRawFileData",
0xC68D: "ActiveArea",
0xC68E: "MaskedAreas",
0xC68F: "AsShotICCProfile",
0xC690: "AsShotPreProfileMatrix",
0xC691: "CurrentICCProfile",
0xC692: "CurrentPreProfileMatrix",
0xC6BF: "ColorimetricReference",
0xC6F3: "CameraCalibrationSignature",
0xC6F4: "ProfileCalibrationSignature",
0xC6F6: "AsShotProfileName",
0xC6F7: "NoiseReductionApplied",
0xC6F8: "ProfileName",
0xC6F9: "ProfileHueSatMapDims",
0xC6FA: "ProfileHueSatMapData1",
0xC6FB: "ProfileHueSatMapData2",
0xC6FC: "ProfileToneCurve",
0xC6FD: "ProfileEmbedPolicy",
0xC6FE: "ProfileCopyright",
0xC714: "ForwardMatrix1",
0xC715: "ForwardMatrix2",
0xC716: "PreviewApplicationName",
0xC717: "PreviewApplicationVersion",
0xC718: "PreviewSettingsName",
0xC719: "PreviewSettingsDigest",
0xC71A: "PreviewColorSpace",
0xC71B: "PreviewDateTime",
0xC71C: "RawImageDigest",
0xC71D: "OriginalRawFileDigest",
0xC71E: "SubTileBlockSize",
0xC71F: "RowInterleaveFactor",
0xC725: "ProfileLookTableDims",
0xC726: "ProfileLookTableData",
0xC740: "OpcodeList1",
0xC741: "OpcodeList2",
0xC74E: "OpcodeList3",
0xC761: "NoiseProfile",
}
##

View File

@ -23,6 +23,7 @@ def register_handler(handler):
global _handler
_handler = handler
# --------------------------------------------------------------------
# Image adapter

View File

@ -27,6 +27,7 @@ __version__ = "0.2"
#
# decoder
def _accept(prefix):
return len(prefix) >= 6 and i16(prefix[4:6]) in [0xAF11, 0xAF12]
@ -35,6 +36,7 @@ def _accept(prefix):
# Image plugin for the FLI/FLC animation format. Use the <b>seek</b>
# method to load individual frames.
class FliImageFile(ImageFile.ImageFile):
format = "FLI"
@ -46,9 +48,11 @@ class FliImageFile(ImageFile.ImageFile):
# HEAD
s = self.fp.read(128)
magic = i16(s[4:6])
if not (magic in [0xAF11, 0xAF12] and
i16(s[14:16]) in [0, 3] and # flags
s[20:22] == b"\x00\x00"): # reserved
if not (
magic in [0xAF11, 0xAF12]
and i16(s[14:16]) in [0, 3] # flags
and s[20:22] == b"\x00\x00" # reserved
):
raise SyntaxError("not an FLI/FLC file")
# frames
@ -84,7 +88,7 @@ class FliImageFile(ImageFile.ImageFile):
elif i16(s[4:6]) == 4:
self._palette(palette, 0)
palette = [o8(r)+o8(g)+o8(b) for (r, g, b) in palette]
palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette]
self.palette = ImagePalette.raw("RGB", b"".join(palette))
# set things up to decode first frame
@ -106,8 +110,8 @@ class FliImageFile(ImageFile.ImageFile):
s = self.fp.read(n * 3)
for n in range(0, len(s), 3):
r = i8(s[n]) << shift
g = i8(s[n+1]) << shift
b = i8(s[n+2]) << shift
g = i8(s[n + 1]) << shift
b = i8(s[n + 2]) << shift
palette[i] = (r, g, b)
i += 1
@ -152,7 +156,7 @@ class FliImageFile(ImageFile.ImageFile):
framesize = i32(s)
self.decodermaxblock = framesize
self.tile = [("fli", (0, 0)+self.size, self.__offset, None)]
self.tile = [("fli", (0, 0) + self.size, self.__offset, None)]
self.__offset += framesize

View File

@ -33,6 +33,7 @@ def puti16(fp, values):
##
# Base class for raster font file handlers.
class FontFile(object):
bitmap = None
@ -61,7 +62,7 @@ class FontFile(object):
w = w + (src[2] - src[0])
if w > WIDTH:
lines += 1
w = (src[2] - src[0])
w = src[2] - src[0]
maxwidth = max(maxwidth, w)
xsize = maxwidth
@ -103,7 +104,7 @@ class FontFile(object):
# font metrics
with open(os.path.splitext(filename)[0] + ".pil", "wb") as fp:
fp.write(b"PILfont\n")
fp.write((";;;;;;%d;\n" % self.ysize).encode('ascii')) # HACK!!!
fp.write((";;;;;;%d;\n" % self.ysize).encode("ascii")) # HACK!!!
fp.write(b"DATA\n")
for id in range(256):
m = self.metrics[id]

Some files were not shown because too many files have changed in this diff Show More