Merge branch 'master' into master
|
@ -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)
|
||||
}
|
||||
|
|
4
.github/CONTRIBUTING.md
vendored
|
@ -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
|
@ -0,0 +1 @@
|
|||
tidelift: pypi/pillow
|
19
.github/ISSUE_TEMPLATE.md
vendored
|
@ -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
|
@ -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
|
@ -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.
|
36
.travis.yml
|
@ -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
|
||||
|
|
83
CHANGES.rst
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
BIN
Tests/fonts/AdobeVFPrototype.ttf
Normal file
BIN
Tests/fonts/ArefRuqaa-Regular.ttf
Normal file
BIN
Tests/fonts/KhmerOSBattambang-Regular.ttf
Executable 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."
|
||||
|
|
BIN
Tests/fonts/NotoSansJP-Regular.otf
Normal file
BIN
Tests/fonts/TINY5x3GX.ttf
Executable file
BIN
Tests/images/app13.jpg
Normal file
After Width: | Height: | Size: 563 B |
BIN
Tests/images/hopper_draw.ico
Normal file
After Width: | Height: | Size: 846 B |
BIN
Tests/images/hopper_unexpected.ico
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Tests/images/imagedraw_arc_width_pieslice.png
Normal file
After Width: | Height: | Size: 402 B |
BIN
Tests/images/imagedraw_ellipse_width_large.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
Tests/images/test_complex_unicode_text2.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
Tests/images/test_direction_ttb.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
Tests/images/test_extents.gif
Normal file
After Width: | Height: | Size: 368 B |
BIN
Tests/images/test_x_max_and_y_offset.png
Normal file
After Width: | Height: | Size: 810 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
Tests/images/tiff_16bit_RGB.tiff
Normal file
BIN
Tests/images/tiff_16bit_RGB_target.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
Tests/images/tiff_strip_cmyk_16l_jpeg.tif
Normal file
BIN
Tests/images/variation_adobe.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Tests/images/variation_adobe_axes.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Tests/images/variation_adobe_name.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Tests/images/variation_tiny.png
Normal file
After Width: | Height: | Size: 239 B |
BIN
Tests/images/variation_tiny_axes.png
Normal file
After Width: | Height: | Size: 937 B |
BIN
Tests/images/variation_tiny_name.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
|
@ -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,
|
||||
[
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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)):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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, []))
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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, there’s also a :py:meth:`show` method which saves an image to
|
||||
For debugging, there’s also a :py:meth:`~PIL.Image.Image.show` method which saves an image to
|
||||
disk, and calls an external display utility.
|
||||
|
||||
Image Processing
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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 |
|
||||
+----------------------------------+------------------------------+--------------------------------+-----------------------+
|
||||
|
|
|
@ -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``
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
--------------------------------------------------
|
||||
|
|
|
@ -25,3 +25,4 @@ The :py:class:`~PIL.ImageSequence.Iterator` class
|
|||
-------------------------------------------------
|
||||
|
||||
.. autoclass:: PIL.ImageSequence.Iterator
|
||||
:members:
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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:
|
||||
|
|
39
docs/releasenotes/6.1.0.rst
Normal 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.
|
|
@ -6,6 +6,7 @@ Release Notes
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
6.1.0
|
||||
6.0.0
|
||||
5.4.1
|
||||
5.4.0
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
-e .
|
||||
alabaster
|
||||
Babel
|
||||
black; python_version >= '3.6'
|
||||
check-manifest
|
||||
cov-core
|
||||
coverage
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
[aliases]
|
||||
test=pytest
|
||||
|
||||
[metadata]
|
||||
license_file = LICENSE
|
||||
|
||||
[flake8]
|
||||
extend-ignore = E203, W503
|
||||
max-line-length = 88
|
||||
|
|
8
setup.py
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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))])
|
||||
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
|
|
@ -27,6 +27,7 @@ def register_handler(handler):
|
|||
# --------------------------------------------------------------------
|
||||
# Image adapter
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC"
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ import io
|
|||
|
||||
|
||||
class ContainerIO(object):
|
||||
|
||||
def __init__(self, file, offset, length):
|
||||
"""
|
||||
Create file object.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ def _accept(prefix):
|
|||
##
|
||||
# Image plugin for the Intel DCX format.
|
||||
|
||||
|
||||
class DcxImageFile(PcxImageFile):
|
||||
|
||||
format = "DCX"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
|
||||
##
|
||||
|
|
|
@ -23,6 +23,7 @@ def register_handler(handler):
|
|||
global _handler
|
||||
_handler = handler
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Image adapter
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|