Merge branch 'master' into gitignore-review
6
.github/ISSUE_TEMPLATE.md
vendored
|
@ -4,7 +4,11 @@
|
||||||
|
|
||||||
### What actually happened?
|
### What actually happened?
|
||||||
|
|
||||||
### What versions of Pillow and Python are you using?
|
### 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.
|
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.
|
||||||
|
|
||||||
|
|
|
@ -27,9 +27,11 @@ matrix:
|
||||||
- python: '3.6'
|
- python: '3.6'
|
||||||
- python: '3.6'
|
- python: '3.6'
|
||||||
dist: trusty
|
dist: trusty
|
||||||
|
env: PYTHONOPTIMIZE=1
|
||||||
- python: '3.5'
|
- python: '3.5'
|
||||||
- python: '3.5'
|
- python: '3.5'
|
||||||
dist: trusty
|
dist: trusty
|
||||||
|
env: PYTHONOPTIMIZE=2
|
||||||
- python: '3.4'
|
- python: '3.4'
|
||||||
dist: trusty
|
dist: trusty
|
||||||
- env: DOCKER="alpine" DOCKER_TAG="pytest"
|
- env: DOCKER="alpine" DOCKER_TAG="pytest"
|
||||||
|
|
|
@ -7,7 +7,7 @@ sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-tk\
|
||||||
python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick\
|
python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick\
|
||||||
libharfbuzz-dev libfribidi-dev
|
libharfbuzz-dev libfribidi-dev
|
||||||
|
|
||||||
pip install cffi
|
PYTHONOPTIMIZE=0 pip install cffi
|
||||||
pip install check-manifest
|
pip install check-manifest
|
||||||
pip install coverage
|
pip install coverage
|
||||||
pip install olefile
|
pip install olefile
|
||||||
|
|
80
CHANGES.rst
|
@ -5,6 +5,84 @@ Changelog (Pillow)
|
||||||
5.3.0 (unreleased)
|
5.3.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Fixed decompression bomb check in _crop #3313
|
||||||
|
[dinkolubina, hugovk]
|
||||||
|
|
||||||
|
- Added support to ImageDraw.floodfill for non-RGB colors #3377
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Tests: Avoid catching unexpected exceptions in tests #2203
|
||||||
|
[jdufresne]
|
||||||
|
|
||||||
|
- Use TextIOWrapper.detach() instead of NoCloseStream #2214
|
||||||
|
[jdufresne]
|
||||||
|
|
||||||
|
- Added transparency to matrix conversion #3205
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added ImageOps pad method #3364
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Give correct extrema for I;16 format images #3359
|
||||||
|
[bz2]
|
||||||
|
|
||||||
|
- Added PySide2 #3279
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Corrected TIFF tags #3369
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- CI: Install CFFI and pycparser without any PYTHONOPTIMIZE #3374
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Read/Save RGB webp as RGB (instead of RGBX) #3298
|
||||||
|
[kkopachev]
|
||||||
|
|
||||||
|
- ImageDraw: Add line joints #3250
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Improved performance of ImageDraw floodfill method #3294
|
||||||
|
[yo1995]
|
||||||
|
|
||||||
|
- Fix builds with --parallel #3272
|
||||||
|
[hsoft]
|
||||||
|
|
||||||
|
- Add more raw Tiff modes (RGBaX, RGBaXX, RGBAX, RGBAXX) #3335
|
||||||
|
[homm]
|
||||||
|
|
||||||
|
- Close existing WebP fp before setting new fp #3341
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Add orientation, compression and id_section as TGA save keyword arguments #3327
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Convert int values of RATIONAL TIFF tags to floats #3338
|
||||||
|
[radarhere, wiredfool]
|
||||||
|
|
||||||
|
- Fix code for PYTHONOPTIMIZE #3233
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Changed ImageFilter.Kernel to subclass ImageFilter.BuiltinFilter, instead of the other way around #3273
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Remove unused draw.draw_line, draw.draw_point and font.getabc methods #3232
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Tests: Added ImageFilter tests #3295
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Tests: Added ImageChops tests #3230
|
||||||
|
[hugovk, radarhere]
|
||||||
|
|
||||||
|
- AppVeyor: Download lib if not present in pillow-depends #3316
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Travis CI: Add Python 3.7 and Xenial #3234
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Docs: Added documentation for NumPy conversion #3301
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
- Depends: Update libimagequant to 2.12.1 #3281
|
- Depends: Update libimagequant to 2.12.1 #3281
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
|
@ -17,7 +95,7 @@ Changelog (Pillow)
|
||||||
- Skip outline if the draw operation fills with the same colour #2922
|
- Skip outline if the draw operation fills with the same colour #2922
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
- Flake8 fixes #3173
|
- Flake8 fixes #3173, #3380
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
- Avoid deprecated 'U' mode when opening files #2187
|
- Avoid deprecated 'U' mode when opening files #2187
|
||||||
|
|
|
@ -89,7 +89,7 @@ Released as needed privately to individual vendors for critical security-related
|
||||||
$ git clone https://github.com/python-pillow/pillow-wheels
|
$ git clone https://github.com/python-pillow/pillow-wheels
|
||||||
$ cd pillow-wheels
|
$ cd pillow-wheels
|
||||||
$ git submodule init
|
$ git submodule init
|
||||||
$ git submodule update
|
$ git submodule update Pillow
|
||||||
$ cd Pillow
|
$ cd Pillow
|
||||||
$ git fetch --all
|
$ git fetch --all
|
||||||
$ git checkout [[release tag]]
|
$ git checkout [[release tag]]
|
||||||
|
|
|
@ -25,9 +25,8 @@ class TestImagingLeaks(PillowTestCase):
|
||||||
if i < min_iterations:
|
if i < min_iterations:
|
||||||
mem_limit = mem + 1
|
mem_limit = mem + 1
|
||||||
continue
|
continue
|
||||||
self.assertLessEqual(mem, mem_limit,
|
msg = 'memory usage limit exceeded after %d iterations' % (i + 1)
|
||||||
msg='memory usage limit exceeded after %d iterations'
|
self.assertLessEqual(mem, mem_limit, msg)
|
||||||
% (i + 1))
|
|
||||||
|
|
||||||
def test_leak_putdata(self):
|
def test_leak_putdata(self):
|
||||||
im = Image.new('RGB', (25, 25))
|
im = Image.new('RGB', (25, 25))
|
||||||
|
|
|
@ -9,8 +9,7 @@ iterations = 5000
|
||||||
When run on a system without the jpeg leak fixes,
|
When run on a system without the jpeg leak fixes,
|
||||||
the valgrind runs look like this.
|
the valgrind runs look like this.
|
||||||
|
|
||||||
NOSE_PROCESSES=0 NOSE_TIMEOUT=600 valgrind --tool=massif \
|
valgrind --tool=massif python test-installed.py -s -v Tests/check_jpeg_leaks.py
|
||||||
python test-installed.py -s -v Tests/check_jpeg_leaks.py
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
@ -272,8 +272,8 @@ class PillowLeakTestCase(PillowTestCase):
|
||||||
for cycle in range(self.iterations):
|
for cycle in range(self.iterations):
|
||||||
core()
|
core()
|
||||||
mem = (self._get_mem_usage() - start_mem)
|
mem = (self._get_mem_usage() - start_mem)
|
||||||
self.assertLess(mem, self.mem_limit,
|
msg = 'memory usage limit exceeded in iteration %d' % cycle
|
||||||
msg='memory usage limit exceeded in iteration %d' % cycle)
|
self.assertLess(mem, self.mem_limit, msg)
|
||||||
|
|
||||||
|
|
||||||
# helpers
|
# helpers
|
||||||
|
|
BIN
Tests/images/16_bit_noise.tif
Normal file
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 48 KiB |
BIN
Tests/images/imagedraw_floodfill_L.png
Normal file
After Width: | Height: | Size: 144 B |
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 232 B |
BIN
Tests/images/imagedraw_floodfill_RGBA.png
Normal file
After Width: | Height: | Size: 253 B |
BIN
Tests/images/imagedraw_line_joint_curve.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
Tests/images/imageops_pad_h_0.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Tests/images/imageops_pad_h_1.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Tests/images/imageops_pad_h_2.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Tests/images/imageops_pad_v_0.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Tests/images/imageops_pad_v_1.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Tests/images/imageops_pad_v_2.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
|
@ -52,10 +52,5 @@ for i0 in range(65556):
|
||||||
|
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# print(check(min_size, min_start))
|
|
||||||
|
|
||||||
print("#define ACCESS_TABLE_SIZE", min_size)
|
print("#define ACCESS_TABLE_SIZE", min_size)
|
||||||
print("#define ACCESS_TABLE_HASH", min_start)
|
print("#define ACCESS_TABLE_HASH", min_start)
|
||||||
|
|
||||||
# for m in modes:
|
|
||||||
# print(m, "=>", hash(m, min_start) % min_size)
|
|
||||||
|
|
|
@ -22,7 +22,6 @@ class TestBmpReference(PillowTestCase):
|
||||||
im.load()
|
im.load()
|
||||||
except Exception: # as msg:
|
except Exception: # as msg:
|
||||||
pass
|
pass
|
||||||
# print("Bad Image %s: %s" %(f,msg))
|
|
||||||
|
|
||||||
def test_questionable(self):
|
def test_questionable(self):
|
||||||
""" These shouldn't crash/dos, but it's not well defined that these
|
""" These shouldn't crash/dos, but it's not well defined that these
|
||||||
|
@ -43,11 +42,11 @@ class TestBmpReference(PillowTestCase):
|
||||||
im = Image.open(f)
|
im = Image.open(f)
|
||||||
im.load()
|
im.load()
|
||||||
if os.path.basename(f) not in supported:
|
if os.path.basename(f) not in supported:
|
||||||
print("Please add %s to the partially supported bmp specs." % f)
|
print("Please add %s to the partially supported"
|
||||||
|
" bmp specs." % f)
|
||||||
except Exception: # as msg:
|
except Exception: # as msg:
|
||||||
if os.path.basename(f) in supported:
|
if os.path.basename(f) in supported:
|
||||||
raise
|
raise
|
||||||
# print("Bad Image %s: %s" %(f,msg))
|
|
||||||
|
|
||||||
def test_good(self):
|
def test_good(self):
|
||||||
""" These should all work. There's a set of target files in the
|
""" These should all work. There's a set of target files in the
|
||||||
|
|
|
@ -61,6 +61,29 @@ class TestDecompressionCrop(PillowTestCase):
|
||||||
self.assert_warning(Image.DecompressionBombWarning,
|
self.assert_warning(Image.DecompressionBombWarning,
|
||||||
self.src.crop, box)
|
self.src.crop, box)
|
||||||
|
|
||||||
|
def test_crop_decompression_checks(self):
|
||||||
|
|
||||||
|
im = Image.new("RGB", (100, 100))
|
||||||
|
|
||||||
|
good_values = ((-9999, -9999, -9990, -9990),
|
||||||
|
(-999, -999, -990, -990))
|
||||||
|
|
||||||
|
warning_values = ((-160, -160, 99, 99),
|
||||||
|
(160, 160, -99, -99))
|
||||||
|
|
||||||
|
error_values = ((-99909, -99990, 99999, 99999),
|
||||||
|
(99909, 99990, -99999, -99999))
|
||||||
|
|
||||||
|
for value in good_values:
|
||||||
|
self.assertEqual(im.crop(value).size, (9, 9))
|
||||||
|
|
||||||
|
for value in warning_values:
|
||||||
|
self.assert_warning(Image.DecompressionBombWarning, im.crop, value)
|
||||||
|
|
||||||
|
for value in error_values:
|
||||||
|
with self.assertRaises(Image.DecompressionBombError):
|
||||||
|
im.crop(value)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -2,8 +2,6 @@ from helper import unittest, PillowTestCase
|
||||||
|
|
||||||
from PIL import GdImageFile
|
from PIL import GdImageFile
|
||||||
|
|
||||||
import io
|
|
||||||
|
|
||||||
TEST_GD_FILE = "Tests/images/hopper.gd"
|
TEST_GD_FILE = "Tests/images/hopper.gd"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -141,11 +141,9 @@ class TestFileJpeg(PillowTestCase):
|
||||||
im = Image.open('Tests/images/icc_profile_big.jpg')
|
im = Image.open('Tests/images/icc_profile_big.jpg')
|
||||||
f = self.tempfile("temp.jpg")
|
f = self.tempfile("temp.jpg")
|
||||||
icc_profile = im.info["icc_profile"]
|
icc_profile = im.info["icc_profile"]
|
||||||
try:
|
# Should not raise IOError for image with icc larger than image size.
|
||||||
im.save(f, format='JPEG', progressive=True, quality=95,
|
im.save(f, format='JPEG', progressive=True, quality=95,
|
||||||
icc_profile=icc_profile, optimize=True)
|
icc_profile=icc_profile, optimize=True)
|
||||||
except IOError:
|
|
||||||
self.fail("Failed saving image with icc larger than image size")
|
|
||||||
|
|
||||||
def test_optimize(self):
|
def test_optimize(self):
|
||||||
im1 = self.roundtrip(hopper())
|
im1 = self.roundtrip(hopper())
|
||||||
|
|
|
@ -231,6 +231,16 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
|
|
||||||
TiffImagePlugin.WRITE_LIBTIFF = False
|
TiffImagePlugin.WRITE_LIBTIFF = False
|
||||||
|
|
||||||
|
def test_int_dpi(self):
|
||||||
|
# issue #1765
|
||||||
|
im = hopper('RGB')
|
||||||
|
out = self.tempfile('temp.tif')
|
||||||
|
TiffImagePlugin.WRITE_LIBTIFF = True
|
||||||
|
im.save(out, dpi=(72, 72))
|
||||||
|
TiffImagePlugin.WRITE_LIBTIFF = False
|
||||||
|
reloaded = Image.open(out)
|
||||||
|
self.assertEqual(reloaded.info['dpi'], (72.0, 72.0))
|
||||||
|
|
||||||
def test_g3_compression(self):
|
def test_g3_compression(self):
|
||||||
i = Image.open('Tests/images/hopper_g4_500.tif')
|
i = Image.open('Tests/images/hopper_g4_500.tif')
|
||||||
out = self.tempfile("temp.tif")
|
out = self.tempfile("temp.tif")
|
||||||
|
@ -529,10 +539,8 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
im = Image.open(tmpfile)
|
im = Image.open(tmpfile)
|
||||||
im.n_frames
|
im.n_frames
|
||||||
im.close()
|
im.close()
|
||||||
try:
|
# Should not raise PermissionError.
|
||||||
os.remove(tmpfile) # Windows PermissionError here!
|
os.remove(tmpfile)
|
||||||
except:
|
|
||||||
self.fail("Should not get permission error here")
|
|
||||||
|
|
||||||
def test_read_icc(self):
|
def test_read_icc(self):
|
||||||
with Image.open("Tests/images/hopper.iccprofile.tif") as img:
|
with Image.open("Tests/images/hopper.iccprofile.tif") as img:
|
||||||
|
|
|
@ -53,10 +53,10 @@ class TestFileTga(PillowTestCase):
|
||||||
# Generate a new test name every time so the
|
# Generate a new test name every time so the
|
||||||
# test will not fail with permission error
|
# test will not fail with permission error
|
||||||
# on Windows.
|
# on Windows.
|
||||||
test_file = self.tempfile("temp.tga")
|
out = self.tempfile("temp.tga")
|
||||||
|
|
||||||
original_im.save(test_file, rle=rle)
|
original_im.save(out, rle=rle)
|
||||||
saved_im = Image.open(test_file)
|
saved_im = Image.open(out)
|
||||||
if rle:
|
if rle:
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
saved_im.info["compression"],
|
saved_im.info["compression"],
|
||||||
|
@ -95,34 +95,93 @@ class TestFileTga(PillowTestCase):
|
||||||
test_file = "Tests/images/tga_id_field.tga"
|
test_file = "Tests/images/tga_id_field.tga"
|
||||||
im = Image.open(test_file)
|
im = Image.open(test_file)
|
||||||
|
|
||||||
test_file = self.tempfile("temp.tga")
|
out = self.tempfile("temp.tga")
|
||||||
|
|
||||||
# Save
|
# Save
|
||||||
im.save(test_file)
|
im.save(out)
|
||||||
test_im = Image.open(test_file)
|
test_im = Image.open(out)
|
||||||
self.assertEqual(test_im.size, (100, 100))
|
self.assertEqual(test_im.size, (100, 100))
|
||||||
|
self.assertEqual(test_im.info["id_section"], im.info["id_section"])
|
||||||
|
|
||||||
# RGBA save
|
# RGBA save
|
||||||
im.convert("RGBA").save(test_file)
|
im.convert("RGBA").save(out)
|
||||||
test_im = Image.open(test_file)
|
test_im = Image.open(out)
|
||||||
self.assertEqual(test_im.size, (100, 100))
|
self.assertEqual(test_im.size, (100, 100))
|
||||||
|
|
||||||
|
def test_save_id_section(self):
|
||||||
|
test_file = "Tests/images/rgb32rle.tga"
|
||||||
|
im = Image.open(test_file)
|
||||||
|
|
||||||
|
out = self.tempfile("temp.tga")
|
||||||
|
|
||||||
|
# Check there is no id section
|
||||||
|
im.save(out)
|
||||||
|
test_im = Image.open(out)
|
||||||
|
self.assertNotIn("id_section", test_im.info)
|
||||||
|
|
||||||
|
# Save with custom id section
|
||||||
|
im.save(out, id_section=b"Test content")
|
||||||
|
test_im = Image.open(out)
|
||||||
|
self.assertEqual(test_im.info["id_section"], b"Test content")
|
||||||
|
|
||||||
|
# Save with custom id section greater than 255 characters
|
||||||
|
id_section = b"Test content" * 25
|
||||||
|
self.assert_warning(UserWarning,
|
||||||
|
lambda: im.save(out, id_section=id_section))
|
||||||
|
test_im = Image.open(out)
|
||||||
|
self.assertEqual(test_im.info["id_section"], id_section[:255])
|
||||||
|
|
||||||
|
test_file = "Tests/images/tga_id_field.tga"
|
||||||
|
im = Image.open(test_file)
|
||||||
|
|
||||||
|
# Save with no id section
|
||||||
|
im.save(out, id_section="")
|
||||||
|
test_im = Image.open(out)
|
||||||
|
self.assertNotIn("id_section", test_im.info)
|
||||||
|
|
||||||
|
def test_save_orientation(self):
|
||||||
|
test_file = "Tests/images/rgb32rle.tga"
|
||||||
|
im = Image.open(test_file)
|
||||||
|
self.assertEqual(im.info["orientation"], -1)
|
||||||
|
|
||||||
|
out = self.tempfile("temp.tga")
|
||||||
|
|
||||||
|
im.save(out, orientation=1)
|
||||||
|
test_im = Image.open(out)
|
||||||
|
self.assertEqual(test_im.info["orientation"], 1)
|
||||||
|
|
||||||
def test_save_rle(self):
|
def test_save_rle(self):
|
||||||
test_file = "Tests/images/rgb32rle.tga"
|
test_file = "Tests/images/rgb32rle.tga"
|
||||||
im = Image.open(test_file)
|
im = Image.open(test_file)
|
||||||
|
self.assertEqual(im.info["compression"], "tga_rle")
|
||||||
|
|
||||||
test_file = self.tempfile("temp.tga")
|
out = self.tempfile("temp.tga")
|
||||||
|
|
||||||
# Save
|
# Save
|
||||||
im.save(test_file)
|
im.save(out)
|
||||||
test_im = Image.open(test_file)
|
test_im = Image.open(out)
|
||||||
self.assertEqual(test_im.size, (199, 199))
|
self.assertEqual(test_im.size, (199, 199))
|
||||||
|
self.assertEqual(test_im.info["compression"], "tga_rle")
|
||||||
|
|
||||||
|
# Save without compression
|
||||||
|
im.save(out, compression=None)
|
||||||
|
test_im = Image.open(out)
|
||||||
|
self.assertNotIn("compression", test_im.info)
|
||||||
|
|
||||||
# RGBA save
|
# RGBA save
|
||||||
im.convert("RGBA").save(test_file)
|
im.convert("RGBA").save(out)
|
||||||
test_im = Image.open(test_file)
|
test_im = Image.open(out)
|
||||||
self.assertEqual(test_im.size, (199, 199))
|
self.assertEqual(test_im.size, (199, 199))
|
||||||
|
|
||||||
|
test_file = "Tests/images/tga_id_field.tga"
|
||||||
|
im = Image.open(test_file)
|
||||||
|
self.assertNotIn("compression", im.info)
|
||||||
|
|
||||||
|
# Save with compression
|
||||||
|
im.save(out, compression="tga_rle")
|
||||||
|
test_im = Image.open(out)
|
||||||
|
self.assertEqual(test_im.info["compression"], "tga_rle")
|
||||||
|
|
||||||
def test_save_l_transparency(self):
|
def test_save_l_transparency(self):
|
||||||
# There are 559 transparent pixels in la.tga.
|
# There are 559 transparent pixels in la.tga.
|
||||||
num_transparent = 559
|
num_transparent = 559
|
||||||
|
@ -133,10 +192,10 @@ class TestFileTga(PillowTestCase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
im.getchannel("A").getcolors()[0][0], num_transparent)
|
im.getchannel("A").getcolors()[0][0], num_transparent)
|
||||||
|
|
||||||
test_file = self.tempfile("temp.tga")
|
out = self.tempfile("temp.tga")
|
||||||
im.save(test_file)
|
im.save(out)
|
||||||
|
|
||||||
test_im = Image.open(test_file)
|
test_im = Image.open(out)
|
||||||
self.assertEqual(test_im.mode, "LA")
|
self.assertEqual(test_im.mode, "LA")
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
test_im.getchannel("A").getcolors()[0][0], num_transparent)
|
test_im.getchannel("A").getcolors()[0][0], num_transparent)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
import struct
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from helper import unittest, PillowTestCase, hopper
|
from helper import unittest, PillowTestCase, hopper
|
||||||
|
@ -59,7 +58,8 @@ class TestFileTiff(PillowTestCase):
|
||||||
|
|
||||||
self.assertEqual(im.mode, "RGBA")
|
self.assertEqual(im.mode, "RGBA")
|
||||||
self.assertEqual(im.size, (52, 53))
|
self.assertEqual(im.size, (52, 53))
|
||||||
self.assertEqual(im.tile, [('raw', (0, 0, 52, 53), 160, ('RGBA', 0, 1))])
|
self.assertEqual(im.tile,
|
||||||
|
[('raw', (0, 0, 52, 53), 160, ('RGBA', 0, 1))])
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
def test_set_legacy_api(self):
|
def test_set_legacy_api(self):
|
||||||
|
@ -133,11 +133,8 @@ class TestFileTiff(PillowTestCase):
|
||||||
|
|
||||||
def test_bad_exif(self):
|
def test_bad_exif(self):
|
||||||
i = Image.open('Tests/images/hopper_bad_exif.jpg')
|
i = Image.open('Tests/images/hopper_bad_exif.jpg')
|
||||||
try:
|
# Should not raise struct.error.
|
||||||
self.assert_warning(UserWarning, i._getexif)
|
self.assert_warning(UserWarning, i._getexif)
|
||||||
except struct.error:
|
|
||||||
self.fail(
|
|
||||||
"Bad EXIF data passed incorrect values to _binary unpack")
|
|
||||||
|
|
||||||
def test_save_rgba(self):
|
def test_save_rgba(self):
|
||||||
im = hopper("RGBA")
|
im = hopper("RGBA")
|
||||||
|
|
|
@ -56,7 +56,8 @@ class TestFileTiffMetadata(PillowTestCase):
|
||||||
loaded = Image.open(f)
|
loaded = Image.open(f)
|
||||||
|
|
||||||
self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], (len(bindata),))
|
self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], (len(bindata),))
|
||||||
self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], (len(bindata),))
|
self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts],
|
||||||
|
(len(bindata),))
|
||||||
|
|
||||||
self.assertEqual(loaded.tag[ImageJMetaData], bindata)
|
self.assertEqual(loaded.tag[ImageJMetaData], bindata)
|
||||||
self.assertEqual(loaded.tag_v2[ImageJMetaData], bindata)
|
self.assertEqual(loaded.tag_v2[ImageJMetaData], bindata)
|
||||||
|
@ -75,8 +76,10 @@ class TestFileTiffMetadata(PillowTestCase):
|
||||||
img.save(f, tiffinfo=info)
|
img.save(f, tiffinfo=info)
|
||||||
loaded = Image.open(f)
|
loaded = Image.open(f)
|
||||||
|
|
||||||
self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], (8, len(bindata) - 8))
|
self.assertEqual(loaded.tag[ImageJMetaDataByteCounts],
|
||||||
self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], (8, len(bindata) - 8))
|
(8, len(bindata) - 8))
|
||||||
|
self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts],
|
||||||
|
(8, len(bindata) - 8))
|
||||||
|
|
||||||
def test_read_metadata(self):
|
def test_read_metadata(self):
|
||||||
img = Image.open('Tests/images/hopper_g4.tif')
|
img = Image.open('Tests/images/hopper_g4.tif')
|
||||||
|
@ -133,8 +136,8 @@ class TestFileTiffMetadata(PillowTestCase):
|
||||||
if isinstance(v, IFDRational):
|
if isinstance(v, IFDRational):
|
||||||
original[k] = IFDRational(*_limit_rational(v, 2**31))
|
original[k] = IFDRational(*_limit_rational(v, 2**31))
|
||||||
if isinstance(v, tuple) and isinstance(v[0], IFDRational):
|
if isinstance(v, tuple) and isinstance(v[0], IFDRational):
|
||||||
original[k] = tuple([IFDRational(
|
original[k] = tuple([IFDRational(*_limit_rational(elt, 2**31))
|
||||||
*_limit_rational(elt, 2**31)) for elt in v])
|
for elt in v])
|
||||||
|
|
||||||
ignored = ['StripByteCounts', 'RowsPerStrip',
|
ignored = ['StripByteCounts', 'RowsPerStrip',
|
||||||
'PageNumber', 'StripOffsets']
|
'PageNumber', 'StripOffsets']
|
||||||
|
@ -169,10 +172,8 @@ class TestFileTiffMetadata(PillowTestCase):
|
||||||
f = io.BytesIO(b'II*\x00\x08\x00\x00\x00')
|
f = io.BytesIO(b'II*\x00\x08\x00\x00\x00')
|
||||||
head = f.read(8)
|
head = f.read(8)
|
||||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||||
try:
|
# Should not raise struct.error.
|
||||||
self.assert_warning(UserWarning, info.load, f)
|
self.assert_warning(UserWarning, info.load, f)
|
||||||
except struct.error:
|
|
||||||
self.fail("Should not be struct errors there.")
|
|
||||||
|
|
||||||
def test_iccprofile(self):
|
def test_iccprofile(self):
|
||||||
# https://github.com/python-pillow/Pillow/issues/1462
|
# https://github.com/python-pillow/Pillow/issues/1462
|
||||||
|
@ -186,7 +187,8 @@ class TestFileTiffMetadata(PillowTestCase):
|
||||||
|
|
||||||
def test_iccprofile_binary(self):
|
def test_iccprofile_binary(self):
|
||||||
# https://github.com/python-pillow/Pillow/issues/1526
|
# https://github.com/python-pillow/Pillow/issues/1526
|
||||||
# We should be able to load this, but probably won't be able to save it.
|
# We should be able to load this,
|
||||||
|
# but probably won't be able to save it.
|
||||||
|
|
||||||
im = Image.open('Tests/images/hopper.iccprofile_binary.tif')
|
im = Image.open('Tests/images/hopper.iccprofile_binary.tif')
|
||||||
self.assertEqual(im.tag_v2.tagtype[34675], 1)
|
self.assertEqual(im.tag_v2.tagtype[34675], 1)
|
||||||
|
@ -223,10 +225,8 @@ class TestFileTiffMetadata(PillowTestCase):
|
||||||
head = data.read(8)
|
head = data.read(8)
|
||||||
info = TiffImagePlugin.ImageFileDirectory_v2(head)
|
info = TiffImagePlugin.ImageFileDirectory_v2(head)
|
||||||
info.load(data)
|
info.load(data)
|
||||||
try:
|
# Should not raise ValueError.
|
||||||
info = dict(info)
|
info = dict(info)
|
||||||
except ValueError:
|
|
||||||
self.fail("Should not be struct value error there.")
|
|
||||||
self.assertIn(33432, info)
|
self.assertIn(33432, info)
|
||||||
|
|
||||||
def test_PhotoshopInfo(self):
|
def test_PhotoshopInfo(self):
|
||||||
|
@ -245,10 +245,8 @@ class TestFileTiffMetadata(PillowTestCase):
|
||||||
ifd._tagdata[277] = struct.pack('hh', 4, 4)
|
ifd._tagdata[277] = struct.pack('hh', 4, 4)
|
||||||
ifd.tagtype[277] = TiffTags.SHORT
|
ifd.tagtype[277] = TiffTags.SHORT
|
||||||
|
|
||||||
try:
|
# Should not raise ValueError.
|
||||||
self.assert_warning(UserWarning, lambda: ifd[277])
|
self.assert_warning(UserWarning, lambda: ifd[277])
|
||||||
except ValueError:
|
|
||||||
self.fail("Invalid Metadata count should not cause a Value Error.")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -16,8 +16,7 @@ class TestFileWebp(PillowTestCase):
|
||||||
self.skipTest('WebP support not installed')
|
self.skipTest('WebP support not installed')
|
||||||
return
|
return
|
||||||
|
|
||||||
# WebPAnimDecoder only returns RGBA or RGBX, never RGB
|
self.rgb_mode = "RGB"
|
||||||
self.rgb_mode = "RGBX" if _webp.HAVE_WEBPANIM else "RGB"
|
|
||||||
|
|
||||||
def test_version(self):
|
def test_version(self):
|
||||||
_webp.WebPDecoderVersion()
|
_webp.WebPDecoderVersion()
|
||||||
|
@ -29,8 +28,7 @@ class TestFileWebp(PillowTestCase):
|
||||||
Does it have the bits we expect?
|
Does it have the bits we expect?
|
||||||
"""
|
"""
|
||||||
|
|
||||||
file_path = "Tests/images/hopper.webp"
|
image = Image.open("Tests/images/hopper.webp")
|
||||||
image = Image.open(file_path)
|
|
||||||
|
|
||||||
self.assertEqual(image.mode, self.rgb_mode)
|
self.assertEqual(image.mode, self.rgb_mode)
|
||||||
self.assertEqual(image.size, (128, 128))
|
self.assertEqual(image.size, (128, 128))
|
||||||
|
@ -40,9 +38,8 @@ class TestFileWebp(PillowTestCase):
|
||||||
|
|
||||||
# generated with:
|
# generated with:
|
||||||
# dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm
|
# dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm
|
||||||
target = Image.open('Tests/images/hopper_webp_bits.ppm')
|
self.assert_image_similar_tofile(
|
||||||
target = target.convert(self.rgb_mode)
|
image, 'Tests/images/hopper_webp_bits.ppm', 1.0)
|
||||||
self.assert_image_similar(image, target, 20.0)
|
|
||||||
|
|
||||||
def test_write_rgb(self):
|
def test_write_rgb(self):
|
||||||
"""
|
"""
|
||||||
|
@ -61,13 +58,9 @@ class TestFileWebp(PillowTestCase):
|
||||||
image.load()
|
image.load()
|
||||||
image.getdata()
|
image.getdata()
|
||||||
|
|
||||||
# If we're using the exact same version of WebP, this test should pass.
|
|
||||||
# but it doesn't if the WebP is generated on Ubuntu and tested on
|
|
||||||
# Fedora.
|
|
||||||
|
|
||||||
# generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm
|
# generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm
|
||||||
# target = Image.open('Tests/images/hopper_webp_write.ppm')
|
self.assert_image_similar_tofile(
|
||||||
# self.assert_image_equal(image, target)
|
image, 'Tests/images/hopper_webp_write.ppm', 12.0)
|
||||||
|
|
||||||
# This test asserts that the images are similar. If the average pixel
|
# This test asserts that the images are similar. If the average pixel
|
||||||
# difference between the two images is less than the epsilon value,
|
# difference between the two images is less than the epsilon value,
|
||||||
|
@ -135,6 +128,13 @@ class TestFileWebp(PillowTestCase):
|
||||||
self.assertRaises(TypeError, _webp.WebPAnimDecoder)
|
self.assertRaises(TypeError, _webp.WebPAnimDecoder)
|
||||||
self.assertRaises(TypeError, _webp.WebPDecode)
|
self.assertRaises(TypeError, _webp.WebPDecode)
|
||||||
|
|
||||||
|
def test_no_resource_warning(self):
|
||||||
|
file_path = "Tests/images/hopper.webp"
|
||||||
|
image = Image.open(file_path)
|
||||||
|
|
||||||
|
temp_file = self.tempfile("temp.webp")
|
||||||
|
self.assert_warning(None, image.save, temp_file)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -19,8 +19,7 @@ class TestFileWebpLossless(PillowTestCase):
|
||||||
if (_webp.WebPDecoderVersion() < 0x0200):
|
if (_webp.WebPDecoderVersion() < 0x0200):
|
||||||
self.skipTest('lossless not included')
|
self.skipTest('lossless not included')
|
||||||
|
|
||||||
# WebPAnimDecoder only returns RGBA or RGBX, never RGB
|
self.rgb_mode = "RGB"
|
||||||
self.rgb_mode = "RGBX" if _webp.HAVE_WEBPANIM else "RGB"
|
|
||||||
|
|
||||||
def test_write_lossless_rgb(self):
|
def test_write_lossless_rgb(self):
|
||||||
temp_file = self.tempfile("temp.webp")
|
temp_file = self.tempfile("temp.webp")
|
||||||
|
|
|
@ -47,10 +47,6 @@ class TestFormatHSV(PillowTestCase):
|
||||||
|
|
||||||
img = Image.merge('RGB', (r, g, b))
|
img = Image.merge('RGB', (r, g, b))
|
||||||
|
|
||||||
# print(("%d, %d -> "% (int(1.75*px),int(.25*px))) + \
|
|
||||||
# "(%s, %s, %s)"%img.getpixel((1.75*px, .25*px)))
|
|
||||||
# print(("%d, %d -> "% (int(.75*px),int(.25*px))) + \
|
|
||||||
# "(%s, %s, %s)"%img.getpixel((.75*px, .25*px)))
|
|
||||||
return img
|
return img
|
||||||
|
|
||||||
def to_xxx_colorsys(self, im, func, mode):
|
def to_xxx_colorsys(self, im, func, mode):
|
||||||
|
@ -95,15 +91,6 @@ class TestFormatHSV(PillowTestCase):
|
||||||
im = src.convert('HSV')
|
im = src.convert('HSV')
|
||||||
comparable = self.to_hsv_colorsys(src)
|
comparable = self.to_hsv_colorsys(src)
|
||||||
|
|
||||||
# print(im.getpixel((448, 64)))
|
|
||||||
# print(comparable.getpixel((448, 64)))
|
|
||||||
|
|
||||||
# print(im.split()[0].histogram())
|
|
||||||
# print(comparable.split()[0].histogram())
|
|
||||||
|
|
||||||
# im.split()[0].show()
|
|
||||||
# comparable.split()[0].show()
|
|
||||||
|
|
||||||
self.assert_image_similar(im.getchannel(0), comparable.getchannel(0),
|
self.assert_image_similar(im.getchannel(0), comparable.getchannel(0),
|
||||||
1, "Hue conversion is wrong")
|
1, "Hue conversion is wrong")
|
||||||
self.assert_image_similar(im.getchannel(1), comparable.getchannel(1),
|
self.assert_image_similar(im.getchannel(1), comparable.getchannel(1),
|
||||||
|
@ -111,16 +98,9 @@ class TestFormatHSV(PillowTestCase):
|
||||||
self.assert_image_similar(im.getchannel(2), comparable.getchannel(2),
|
self.assert_image_similar(im.getchannel(2), comparable.getchannel(2),
|
||||||
1, "Value conversion is wrong")
|
1, "Value conversion is wrong")
|
||||||
|
|
||||||
# print(im.getpixel((192, 64)))
|
|
||||||
|
|
||||||
comparable = src
|
comparable = src
|
||||||
im = im.convert('RGB')
|
im = im.convert('RGB')
|
||||||
|
|
||||||
# im.split()[0].show()
|
|
||||||
# comparable.split()[0].show()
|
|
||||||
# print(im.getpixel((192, 64)))
|
|
||||||
# print(comparable.getpixel((192, 64)))
|
|
||||||
|
|
||||||
self.assert_image_similar(im.getchannel(0), comparable.getchannel(0),
|
self.assert_image_similar(im.getchannel(0), comparable.getchannel(0),
|
||||||
3, "R conversion is wrong")
|
3, "R conversion is wrong")
|
||||||
self.assert_image_similar(im.getchannel(1), comparable.getchannel(1),
|
self.assert_image_similar(im.getchannel(1), comparable.getchannel(1),
|
||||||
|
@ -132,12 +112,6 @@ class TestFormatHSV(PillowTestCase):
|
||||||
im = hopper('RGB').convert('HSV')
|
im = hopper('RGB').convert('HSV')
|
||||||
comparable = self.to_hsv_colorsys(hopper('RGB'))
|
comparable = self.to_hsv_colorsys(hopper('RGB'))
|
||||||
|
|
||||||
# print([ord(x) for x in im.split()[0].tobytes()[:80]])
|
|
||||||
# print([ord(x) for x in comparable.split()[0].tobytes()[:80]])
|
|
||||||
|
|
||||||
# print(im.split()[0].histogram())
|
|
||||||
# print(comparable.split()[0].histogram())
|
|
||||||
|
|
||||||
self.assert_image_similar(im.getchannel(0), comparable.getchannel(0),
|
self.assert_image_similar(im.getchannel(0), comparable.getchannel(0),
|
||||||
1, "Hue conversion is wrong")
|
1, "Hue conversion is wrong")
|
||||||
self.assert_image_similar(im.getchannel(1), comparable.getchannel(1),
|
self.assert_image_similar(im.getchannel(1), comparable.getchannel(1),
|
||||||
|
@ -150,12 +124,6 @@ class TestFormatHSV(PillowTestCase):
|
||||||
converted = comparable.convert('RGB')
|
converted = comparable.convert('RGB')
|
||||||
comparable = self.to_rgb_colorsys(comparable)
|
comparable = self.to_rgb_colorsys(comparable)
|
||||||
|
|
||||||
# print(converted.split()[1].histogram())
|
|
||||||
# print(target.split()[1].histogram())
|
|
||||||
|
|
||||||
# print([ord(x) for x in target.split()[1].tobytes()[:80]])
|
|
||||||
# print([ord(x) for x in converted.split()[1].tobytes()[:80]])
|
|
||||||
|
|
||||||
self.assert_image_similar(converted.getchannel(0),
|
self.assert_image_similar(converted.getchannel(0),
|
||||||
comparable.getchannel(0),
|
comparable.getchannel(0),
|
||||||
3, "R conversion is wrong")
|
3, "R conversion is wrong")
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
from helper import unittest, PillowTestCase, hopper, on_appveyor
|
from helper import unittest, PillowTestCase, hopper, on_appveyor
|
||||||
|
|
||||||
try:
|
|
||||||
from PIL import PyAccess
|
|
||||||
except ImportError:
|
|
||||||
# Skip in setUp()
|
|
||||||
pass
|
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
# CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2
|
||||||
|
# https://github.com/eliben/pycparser/pull/198#issuecomment-317001670
|
||||||
|
if os.environ.get("PYTHONOPTIMIZE") == "2":
|
||||||
|
cffi = None
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
from PIL import PyAccess
|
||||||
|
import cffi
|
||||||
|
except ImportError:
|
||||||
|
cffi = None
|
||||||
|
|
||||||
|
|
||||||
class AccessTest(PillowTestCase):
|
class AccessTest(PillowTestCase):
|
||||||
# initial value
|
# initial value
|
||||||
|
@ -113,38 +118,20 @@ class TestImageGetPixel(AccessTest):
|
||||||
self.check(mode, 2**16-1)
|
self.check(mode, 2**16-1)
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(cffi is None, "No cffi")
|
||||||
class TestCffiPutPixel(TestImagePutPixel):
|
class TestCffiPutPixel(TestImagePutPixel):
|
||||||
_need_cffi_access = True
|
_need_cffi_access = True
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
try:
|
|
||||||
import cffi
|
|
||||||
assert cffi # silence warning
|
|
||||||
except ImportError:
|
|
||||||
self.skipTest("No cffi")
|
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(cffi is None, "No cffi")
|
||||||
class TestCffiGetPixel(TestImageGetPixel):
|
class TestCffiGetPixel(TestImageGetPixel):
|
||||||
_need_cffi_access = True
|
_need_cffi_access = True
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
try:
|
|
||||||
import cffi
|
|
||||||
assert cffi # silence warning
|
|
||||||
except ImportError:
|
|
||||||
self.skipTest("No cffi")
|
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(cffi is None, "No cffi")
|
||||||
class TestCffi(AccessTest):
|
class TestCffi(AccessTest):
|
||||||
_need_cffi_access = True
|
_need_cffi_access = True
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
try:
|
|
||||||
import cffi
|
|
||||||
assert cffi # silence warning
|
|
||||||
except ImportError:
|
|
||||||
self.skipTest("No cffi")
|
|
||||||
|
|
||||||
def _test_get_access(self, im):
|
def _test_get_access(self, im):
|
||||||
"""Do we get the same thing as the old pixel access
|
"""Do we get the same thing as the old pixel access
|
||||||
|
|
||||||
|
|
|
@ -187,6 +187,7 @@ class TestImageConvert(PillowTestCase):
|
||||||
def matrix_convert(mode):
|
def matrix_convert(mode):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = hopper('RGB')
|
im = hopper('RGB')
|
||||||
|
im.info['transparency'] = (255, 0, 0)
|
||||||
matrix = (
|
matrix = (
|
||||||
0.412453, 0.357580, 0.180423, 0,
|
0.412453, 0.357580, 0.180423, 0,
|
||||||
0.212671, 0.715160, 0.072169, 0,
|
0.212671, 0.715160, 0.072169, 0,
|
||||||
|
@ -203,9 +204,12 @@ class TestImageConvert(PillowTestCase):
|
||||||
target = Image.open('Tests/images/hopper-XYZ.png')
|
target = Image.open('Tests/images/hopper-XYZ.png')
|
||||||
if converted_im.mode == 'RGB':
|
if converted_im.mode == 'RGB':
|
||||||
self.assert_image_similar(converted_im, target, 3)
|
self.assert_image_similar(converted_im, target, 3)
|
||||||
|
self.assertEqual(converted_im.info['transparency'],
|
||||||
|
(105, 54, 4))
|
||||||
else:
|
else:
|
||||||
self.assert_image_similar(converted_im,
|
self.assert_image_similar(converted_im,
|
||||||
target.getchannel(0), 1)
|
target.getchannel(0), 1)
|
||||||
|
self.assertEqual(converted_im.info['transparency'], 105)
|
||||||
|
|
||||||
matrix_convert('RGB')
|
matrix_convert('RGB')
|
||||||
matrix_convert('L')
|
matrix_convert('L')
|
||||||
|
|
|
@ -94,6 +94,15 @@ class TestImageFilter(PillowTestCase):
|
||||||
self.assertEqual(rankfilter.size, 1)
|
self.assertEqual(rankfilter.size, 1)
|
||||||
self.assertEqual(rankfilter.rank, 2)
|
self.assertEqual(rankfilter.rank, 2)
|
||||||
|
|
||||||
|
def test_builtinfilter_p(self):
|
||||||
|
builtinFilter = ImageFilter.BuiltinFilter()
|
||||||
|
|
||||||
|
self.assertRaises(ValueError, builtinFilter.filter, hopper("P"))
|
||||||
|
|
||||||
|
def test_kernel_not_enough_coefficients(self):
|
||||||
|
self.assertRaises(ValueError,
|
||||||
|
lambda: ImageFilter.Kernel((3, 3), (0, 0)))
|
||||||
|
|
||||||
def test_consistency_3x3(self):
|
def test_consistency_3x3(self):
|
||||||
source = Image.open("Tests/images/hopper.bmp")
|
source = Image.open("Tests/images/hopper.bmp")
|
||||||
reference = Image.open("Tests/images/hopper_emboss.bmp")
|
reference = Image.open("Tests/images/hopper_emboss.bmp")
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from PIL import Image
|
||||||
from helper import unittest, PillowTestCase, hopper
|
from helper import unittest, PillowTestCase, hopper
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,6 +20,13 @@ class TestImageGetExtrema(PillowTestCase):
|
||||||
extrema("RGBA"), ((0, 255), (0, 255), (0, 255), (255, 255)))
|
extrema("RGBA"), ((0, 255), (0, 255), (0, 255), (255, 255)))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
extrema("CMYK"), (((0, 255), (0, 255), (0, 255), (0, 0))))
|
extrema("CMYK"), (((0, 255), (0, 255), (0, 255), (0, 0))))
|
||||||
|
self.assertEqual(extrema("I;16"), (0, 255))
|
||||||
|
|
||||||
|
def test_true_16(self):
|
||||||
|
im = Image.open("Tests/images/16_bit_noise.tif")
|
||||||
|
self.assertEqual(im.mode, 'I;16')
|
||||||
|
extrema = im.getextrema()
|
||||||
|
self.assertEqual(extrema, (106, 285))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -352,10 +352,8 @@ class CoreResamplePassesTest(PillowTestCase):
|
||||||
class CoreResampleCoefficientsTest(PillowTestCase):
|
class CoreResampleCoefficientsTest(PillowTestCase):
|
||||||
def test_reduce(self):
|
def test_reduce(self):
|
||||||
test_color = 254
|
test_color = 254
|
||||||
# print()
|
|
||||||
|
|
||||||
for size in range(400000, 400010, 2):
|
for size in range(400000, 400010, 2):
|
||||||
# print(size)
|
|
||||||
i = Image.new('L', (size, 1), 0)
|
i = Image.new('L', (size, 1), 0)
|
||||||
draw = ImageDraw.Draw(i)
|
draw = ImageDraw.Draw(i)
|
||||||
draw.rectangle((0, 0, i.size[0] // 2 - 1, 0), test_color)
|
draw.rectangle((0, 0, i.size[0] // 2 - 1, 0), test_color)
|
||||||
|
@ -363,7 +361,6 @@ class CoreResampleCoefficientsTest(PillowTestCase):
|
||||||
px = i.resize((5, i.size[1]), Image.BICUBIC).load()
|
px = i.resize((5, i.size[1]), Image.BICUBIC).load()
|
||||||
if px[2, 0] != test_color // 2:
|
if px[2, 0] != test_color // 2:
|
||||||
self.assertEqual(test_color // 2, px[2, 0])
|
self.assertEqual(test_color // 2, px[2, 0])
|
||||||
# print('>', size, test_color // 2, px[2, 0])
|
|
||||||
|
|
||||||
def test_nonzero_coefficients(self):
|
def test_nonzero_coefficients(self):
|
||||||
# regression test for the wrong coefficients calculation
|
# regression test for the wrong coefficients calculation
|
||||||
|
|
|
@ -124,5 +124,6 @@ class TestImageRotate(PillowTestCase):
|
||||||
corner = im.getpixel((0, 0))
|
corner = im.getpixel((0, 0))
|
||||||
self.assertEqual(corner, (255, 0, 0, 255))
|
self.assertEqual(corner, (255, 0, 0, 255))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -3,6 +3,16 @@ from helper import unittest, PillowTestCase, hopper
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL import ImageChops
|
from PIL import ImageChops
|
||||||
|
|
||||||
|
BLACK = (0, 0, 0)
|
||||||
|
BROWN = (127, 64, 0)
|
||||||
|
CYAN = (0, 255, 255)
|
||||||
|
DARK_GREEN = (0, 128, 0)
|
||||||
|
GREEN = (0, 255, 0)
|
||||||
|
ORANGE = (255, 128, 0)
|
||||||
|
WHITE = (255, 255, 255)
|
||||||
|
|
||||||
|
GREY = 128
|
||||||
|
|
||||||
|
|
||||||
class TestImageChops(PillowTestCase):
|
class TestImageChops(PillowTestCase):
|
||||||
|
|
||||||
|
@ -35,6 +45,303 @@ class TestImageChops(PillowTestCase):
|
||||||
ImageChops.offset(im, 10)
|
ImageChops.offset(im, 10)
|
||||||
ImageChops.offset(im, 10, 20)
|
ImageChops.offset(im, 10, 20)
|
||||||
|
|
||||||
|
def test_add(self):
|
||||||
|
# Arrange
|
||||||
|
im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png")
|
||||||
|
im2 = Image.open("Tests/images/imagedraw_floodfill_RGB.png")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.add(im1, im2)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(new.getbbox(), (25, 25, 76, 76))
|
||||||
|
self.assertEqual(new.getpixel((50, 50)), ORANGE)
|
||||||
|
|
||||||
|
def test_add_scale_offset(self):
|
||||||
|
# Arrange
|
||||||
|
im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png")
|
||||||
|
im2 = Image.open("Tests/images/imagedraw_floodfill_RGB.png")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.add(im1, im2, scale=2.5, offset=100)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(new.getbbox(), (0, 0, 100, 100))
|
||||||
|
self.assertEqual(new.getpixel((50, 50)), (202, 151, 100))
|
||||||
|
|
||||||
|
def test_add_clip(self):
|
||||||
|
# Arrange
|
||||||
|
im = hopper()
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.add(im, im)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(new.getpixel((50, 50)), (255, 255, 254))
|
||||||
|
|
||||||
|
def test_add_modulo(self):
|
||||||
|
# Arrange
|
||||||
|
im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png")
|
||||||
|
im2 = Image.open("Tests/images/imagedraw_floodfill_RGB.png")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.add_modulo(im1, im2)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(new.getbbox(), (25, 25, 76, 76))
|
||||||
|
self.assertEqual(new.getpixel((50, 50)), ORANGE)
|
||||||
|
|
||||||
|
def test_add_modulo_no_clip(self):
|
||||||
|
# Arrange
|
||||||
|
im = hopper()
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.add_modulo(im, im)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(new.getpixel((50, 50)), (224, 76, 254))
|
||||||
|
|
||||||
|
def test_blend(self):
|
||||||
|
# Arrange
|
||||||
|
im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png")
|
||||||
|
im2 = Image.open("Tests/images/imagedraw_floodfill_RGB.png")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.blend(im1, im2, 0.5)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(new.getbbox(), (25, 25, 76, 76))
|
||||||
|
self.assertEqual(new.getpixel((50, 50)), BROWN)
|
||||||
|
|
||||||
|
def test_constant(self):
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (20, 10))
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.constant(im, GREY)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(new.size, im.size)
|
||||||
|
self.assertEqual(new.getpixel((0, 0)), GREY)
|
||||||
|
self.assertEqual(new.getpixel((19, 9)), GREY)
|
||||||
|
|
||||||
|
def test_darker_image(self):
|
||||||
|
# Arrange
|
||||||
|
im1 = Image.open("Tests/images/imagedraw_chord_RGB.png")
|
||||||
|
im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.darker(im1, im2)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assert_image_equal(new, im2)
|
||||||
|
|
||||||
|
def test_darker_pixel(self):
|
||||||
|
# Arrange
|
||||||
|
im1 = hopper()
|
||||||
|
im2 = Image.open("Tests/images/imagedraw_chord_RGB.png")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.darker(im1, im2)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(new.getpixel((50, 50)), (240, 166, 0))
|
||||||
|
|
||||||
|
def test_difference(self):
|
||||||
|
# Arrange
|
||||||
|
im1 = Image.open("Tests/images/imagedraw_arc_end_le_start.png")
|
||||||
|
im2 = Image.open("Tests/images/imagedraw_arc_no_loops.png")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.difference(im1, im2)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(new.getbbox(), (25, 25, 76, 76))
|
||||||
|
|
||||||
|
def test_difference_pixel(self):
|
||||||
|
# Arrange
|
||||||
|
im1 = hopper()
|
||||||
|
im2 = Image.open("Tests/images/imagedraw_polygon_kite_RGB.png")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.difference(im1, im2)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(new.getpixel((50, 50)), (240, 166, 128))
|
||||||
|
|
||||||
|
def test_duplicate(self):
|
||||||
|
# Arrange
|
||||||
|
im = hopper()
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.duplicate(im)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assert_image_equal(new, im)
|
||||||
|
|
||||||
|
def test_invert(self):
|
||||||
|
# Arrange
|
||||||
|
im = Image.open("Tests/images/imagedraw_floodfill_RGB.png")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.invert(im)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(new.getbbox(), (0, 0, 100, 100))
|
||||||
|
self.assertEqual(new.getpixel((0, 0)), WHITE)
|
||||||
|
self.assertEqual(new.getpixel((50, 50)), CYAN)
|
||||||
|
|
||||||
|
def test_lighter_image(self):
|
||||||
|
# Arrange
|
||||||
|
im1 = Image.open("Tests/images/imagedraw_chord_RGB.png")
|
||||||
|
im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.lighter(im1, im2)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assert_image_equal(new, im1)
|
||||||
|
|
||||||
|
def test_lighter_pixel(self):
|
||||||
|
# Arrange
|
||||||
|
im1 = hopper()
|
||||||
|
im2 = Image.open("Tests/images/imagedraw_chord_RGB.png")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.lighter(im1, im2)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(new.getpixel((50, 50)), (255, 255, 127))
|
||||||
|
|
||||||
|
def test_multiply_black(self):
|
||||||
|
"""If you multiply an image with a solid black image,
|
||||||
|
the result is black."""
|
||||||
|
# Arrange
|
||||||
|
im1 = hopper()
|
||||||
|
black = Image.new("RGB", im1.size, "black")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.multiply(im1, black)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assert_image_equal(new, black)
|
||||||
|
|
||||||
|
def test_multiply_green(self):
|
||||||
|
# Arrange
|
||||||
|
im = Image.open("Tests/images/imagedraw_floodfill_RGB.png")
|
||||||
|
green = Image.new("RGB", im.size, "green")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.multiply(im, green)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(new.getbbox(), (25, 25, 76, 76))
|
||||||
|
self.assertEqual(new.getpixel((25, 25)), DARK_GREEN)
|
||||||
|
self.assertEqual(new.getpixel((50, 50)), BLACK)
|
||||||
|
|
||||||
|
def test_multiply_white(self):
|
||||||
|
"""If you multiply with a solid white image,
|
||||||
|
the image is unaffected."""
|
||||||
|
# Arrange
|
||||||
|
im1 = hopper()
|
||||||
|
white = Image.new("RGB", im1.size, "white")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.multiply(im1, white)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assert_image_equal(new, im1)
|
||||||
|
|
||||||
|
def test_offset(self):
|
||||||
|
# Arrange
|
||||||
|
im = Image.open("Tests/images/imagedraw_ellipse_RGB.png")
|
||||||
|
xoffset = 45
|
||||||
|
yoffset = 20
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.offset(im, xoffset, yoffset)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(new.getbbox(), (0, 45, 100, 96))
|
||||||
|
self.assertEqual(new.getpixel((50, 50)), BLACK)
|
||||||
|
self.assertEqual(new.getpixel((50+xoffset, 50+yoffset)), DARK_GREEN)
|
||||||
|
|
||||||
|
# Test no yoffset
|
||||||
|
self.assertEqual(ImageChops.offset(im, xoffset),
|
||||||
|
ImageChops.offset(im, xoffset, xoffset))
|
||||||
|
|
||||||
|
def test_screen(self):
|
||||||
|
# Arrange
|
||||||
|
im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png")
|
||||||
|
im2 = Image.open("Tests/images/imagedraw_floodfill_RGB.png")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.screen(im1, im2)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(new.getbbox(), (25, 25, 76, 76))
|
||||||
|
self.assertEqual(new.getpixel((50, 50)), ORANGE)
|
||||||
|
|
||||||
|
def test_subtract(self):
|
||||||
|
# Arrange
|
||||||
|
im1 = Image.open("Tests/images/imagedraw_chord_RGB.png")
|
||||||
|
im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.subtract(im1, im2)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(new.getbbox(), (25, 50, 76, 76))
|
||||||
|
self.assertEqual(new.getpixel((50, 50)), GREEN)
|
||||||
|
self.assertEqual(new.getpixel((50, 51)), BLACK)
|
||||||
|
|
||||||
|
def test_subtract_scale_offset(self):
|
||||||
|
# Arrange
|
||||||
|
im1 = Image.open("Tests/images/imagedraw_chord_RGB.png")
|
||||||
|
im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.subtract(im1, im2, scale=2.5, offset=100)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(new.getbbox(), (0, 0, 100, 100))
|
||||||
|
self.assertEqual(new.getpixel((50, 50)), (100, 202, 100))
|
||||||
|
|
||||||
|
def test_subtract_clip(self):
|
||||||
|
# Arrange
|
||||||
|
im1 = hopper()
|
||||||
|
im2 = Image.open("Tests/images/imagedraw_chord_RGB.png")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.subtract(im1, im2)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(new.getpixel((50, 50)), (0, 0, 127))
|
||||||
|
|
||||||
|
def test_subtract_modulo(self):
|
||||||
|
# Arrange
|
||||||
|
im1 = Image.open("Tests/images/imagedraw_chord_RGB.png")
|
||||||
|
im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.subtract_modulo(im1, im2)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(new.getbbox(), (25, 50, 76, 76))
|
||||||
|
self.assertEqual(new.getpixel((50, 50)), GREEN)
|
||||||
|
self.assertEqual(new.getpixel((50, 51)), BLACK)
|
||||||
|
|
||||||
|
def test_subtract_modulo_no_clip(self):
|
||||||
|
# Arrange
|
||||||
|
im1 = hopper()
|
||||||
|
im2 = Image.open("Tests/images/imagedraw_chord_RGB.png")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
new = ImageChops.subtract_modulo(im1, im2)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(new.getpixel((50, 50)), (241, 167, 127))
|
||||||
|
|
||||||
def test_logical(self):
|
def test_logical(self):
|
||||||
|
|
||||||
def table(op, a, b):
|
def table(op, a, b):
|
||||||
|
|
|
@ -446,20 +446,20 @@ class TestImageCms(PillowTestCase):
|
||||||
self.assert_image_equal(source_image_aux, result_image_aux)
|
self.assert_image_equal(source_image_aux, result_image_aux)
|
||||||
|
|
||||||
def test_preserve_auxiliary_channels_rgba(self):
|
def test_preserve_auxiliary_channels_rgba(self):
|
||||||
self.assert_aux_channel_preserved(mode='RGBA',
|
self.assert_aux_channel_preserved(
|
||||||
transform_in_place=False, preserved_channel='A')
|
mode='RGBA', transform_in_place=False, preserved_channel='A')
|
||||||
|
|
||||||
def test_preserve_auxiliary_channels_rgba_in_place(self):
|
def test_preserve_auxiliary_channels_rgba_in_place(self):
|
||||||
self.assert_aux_channel_preserved(mode='RGBA',
|
self.assert_aux_channel_preserved(
|
||||||
transform_in_place=True, preserved_channel='A')
|
mode='RGBA', transform_in_place=True, preserved_channel='A')
|
||||||
|
|
||||||
def test_preserve_auxiliary_channels_rgbx(self):
|
def test_preserve_auxiliary_channels_rgbx(self):
|
||||||
self.assert_aux_channel_preserved(mode='RGBX',
|
self.assert_aux_channel_preserved(
|
||||||
transform_in_place=False, preserved_channel='X')
|
mode='RGBX', transform_in_place=False, preserved_channel='X')
|
||||||
|
|
||||||
def test_preserve_auxiliary_channels_rgbx_in_place(self):
|
def test_preserve_auxiliary_channels_rgbx_in_place(self):
|
||||||
self.assert_aux_channel_preserved(mode='RGBX',
|
self.assert_aux_channel_preserved(
|
||||||
transform_in_place=True, preserved_channel='X')
|
mode='RGBX', transform_in_place=True, preserved_channel='X')
|
||||||
|
|
||||||
def test_auxiliary_channels_isolated(self):
|
def test_auxiliary_channels_isolated(self):
|
||||||
# test data in aux channels does not affect non-aux channels
|
# test data in aux channels does not affect non-aux channels
|
||||||
|
|
|
@ -344,18 +344,25 @@ class TestImageDraw(PillowTestCase):
|
||||||
self.assert_image_similar(im, Image.open(expected), 1)
|
self.assert_image_similar(im, Image.open(expected), 1)
|
||||||
|
|
||||||
def test_floodfill(self):
|
def test_floodfill(self):
|
||||||
|
red = ImageColor.getrgb("red")
|
||||||
|
|
||||||
|
for mode, value in [
|
||||||
|
("L", 1),
|
||||||
|
("RGBA", (255, 0, 0, 0)),
|
||||||
|
("RGB", red)
|
||||||
|
]:
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new(mode, (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
draw.rectangle(BBOX2, outline="yellow", fill="green")
|
draw.rectangle(BBOX2, outline="yellow", fill="green")
|
||||||
centre_point = (int(W/2), int(H/2))
|
centre_point = (int(W/2), int(H/2))
|
||||||
red = ImageColor.getrgb("red")
|
|
||||||
im_floodfill = Image.open("Tests/images/imagedraw_floodfill.png")
|
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
ImageDraw.floodfill(im, centre_point, red)
|
ImageDraw.floodfill(im, centre_point, value)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
|
expected = "Tests/images/imagedraw_floodfill_"+mode+".png"
|
||||||
|
im_floodfill = Image.open(expected)
|
||||||
self.assert_image_equal(im, im_floodfill)
|
self.assert_image_equal(im, im_floodfill)
|
||||||
|
|
||||||
# Test that using the same colour does not change the image
|
# Test that using the same colour does not change the image
|
||||||
|
@ -366,6 +373,11 @@ class TestImageDraw(PillowTestCase):
|
||||||
ImageDraw.floodfill(im, (W, H), red)
|
ImageDraw.floodfill(im, (W, H), red)
|
||||||
self.assert_image_equal(im, im_floodfill)
|
self.assert_image_equal(im, im_floodfill)
|
||||||
|
|
||||||
|
# Test filling at the edge of an image
|
||||||
|
im = Image.new("RGB", (1, 1))
|
||||||
|
ImageDraw.floodfill(im, (0, 0), red)
|
||||||
|
self.assert_image_equal(im, Image.new("RGB", (1, 1), red))
|
||||||
|
|
||||||
def test_floodfill_border(self):
|
def test_floodfill_border(self):
|
||||||
# floodfill() is experimental
|
# floodfill() is experimental
|
||||||
|
|
||||||
|
@ -563,6 +575,20 @@ class TestImageDraw(PillowTestCase):
|
||||||
# Assert
|
# Assert
|
||||||
self.assert_image_similar(im, Image.open(expected), 1)
|
self.assert_image_similar(im, Image.open(expected), 1)
|
||||||
|
|
||||||
|
def test_line_joint(self):
|
||||||
|
im = Image.new("RGB", (500, 325))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
expected = "Tests/images/imagedraw_line_joint_curve.png"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
xy = [(400, 280), (380, 280), (450, 280), (440, 120), (350, 200),
|
||||||
|
(310, 280), (300, 280), (250, 280), (250, 200), (150, 200),
|
||||||
|
(150, 260), (50, 200), (150, 50), (250, 100)]
|
||||||
|
draw.line(xy, GRAY, 50, "curve")
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assert_image_similar(im, Image.open(expected), 3)
|
||||||
|
|
||||||
def test_textsize_empty_string(self):
|
def test_textsize_empty_string(self):
|
||||||
# https://github.com/python-pillow/Pillow/issues/2783
|
# https://github.com/python-pillow/Pillow/issues/2783
|
||||||
# Arrange
|
# Arrange
|
||||||
|
|
|
@ -215,7 +215,7 @@ class TestImageFont(PillowTestCase):
|
||||||
|
|
||||||
# Act/Assert
|
# Act/Assert
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
AssertionError,
|
ValueError,
|
||||||
draw.multiline_text, (0, 0), TEST_TEXT, font=ttf, align="unknown")
|
draw.multiline_text, (0, 0), TEST_TEXT, font=ttf, align="unknown")
|
||||||
|
|
||||||
def test_draw_align(self):
|
def test_draw_align(self):
|
||||||
|
|
|
@ -24,6 +24,9 @@ class TestImageOps(PillowTestCase):
|
||||||
ImageOps.colorize(hopper("L"), (0, 0, 0), (255, 255, 255))
|
ImageOps.colorize(hopper("L"), (0, 0, 0), (255, 255, 255))
|
||||||
ImageOps.colorize(hopper("L"), "black", "white")
|
ImageOps.colorize(hopper("L"), "black", "white")
|
||||||
|
|
||||||
|
ImageOps.pad(hopper("L"), (128, 128))
|
||||||
|
ImageOps.pad(hopper("RGB"), (128, 128))
|
||||||
|
|
||||||
ImageOps.crop(hopper("L"), 1)
|
ImageOps.crop(hopper("L"), 1)
|
||||||
ImageOps.crop(hopper("RGB"), 1)
|
ImageOps.crop(hopper("RGB"), 1)
|
||||||
|
|
||||||
|
@ -70,6 +73,26 @@ class TestImageOps(PillowTestCase):
|
||||||
newimg = ImageOps.fit(hopper("RGB").resize((100, 1)), (35, 35))
|
newimg = ImageOps.fit(hopper("RGB").resize((100, 1)), (35, 35))
|
||||||
self.assertEqual(newimg.size, (35, 35))
|
self.assertEqual(newimg.size, (35, 35))
|
||||||
|
|
||||||
|
def test_pad(self):
|
||||||
|
# Same ratio
|
||||||
|
im = hopper()
|
||||||
|
new_size = (im.width * 2, im.height * 2)
|
||||||
|
new_im = ImageOps.pad(im, new_size)
|
||||||
|
self.assertEqual(new_im.size, new_size)
|
||||||
|
|
||||||
|
for label, color, new_size in [
|
||||||
|
("h", None, (im.width * 4, im.height * 2)),
|
||||||
|
("v", "#f00", (im.width * 2, im.height * 4))
|
||||||
|
]:
|
||||||
|
for i, centering in enumerate([(0, 0), (0.5, 0.5), (1, 1)]):
|
||||||
|
new_im = ImageOps.pad(im, new_size,
|
||||||
|
color=color, centering=centering)
|
||||||
|
self.assertEqual(new_im.size, new_size)
|
||||||
|
|
||||||
|
target = Image.open(
|
||||||
|
"Tests/images/imageops_pad_"+label+"_"+str(i)+".jpg")
|
||||||
|
self.assert_image_similar(new_im, target, 6)
|
||||||
|
|
||||||
def test_pil163(self):
|
def test_pil163(self):
|
||||||
# Division by zero in equalize if < 255 pixels in image (@PIL163)
|
# Division by zero in equalize if < 255 pixels in image (@PIL163)
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,8 @@ class PillowQPixmapTestCase(PillowQtTestCase):
|
||||||
from PyQt4.QtGui import QGuiApplication
|
from PyQt4.QtGui import QGuiApplication
|
||||||
elif ImageQt.qt_version == 'side':
|
elif ImageQt.qt_version == 'side':
|
||||||
from PySide.QtGui import QGuiApplication
|
from PySide.QtGui import QGuiApplication
|
||||||
|
elif ImageQt.qt_version == 'side2':
|
||||||
|
from PySide2.QtGui import QGuiApplication
|
||||||
except ImportError:
|
except ImportError:
|
||||||
self.skipTest('QGuiApplication not installed')
|
self.skipTest('QGuiApplication not installed')
|
||||||
|
|
||||||
|
@ -56,6 +58,8 @@ class TestImageQt(PillowQtTestCase, PillowTestCase):
|
||||||
from PyQt4.QtGui import qRgb
|
from PyQt4.QtGui import qRgb
|
||||||
elif ImageQt.qt_version == 'side':
|
elif ImageQt.qt_version == 'side':
|
||||||
from PySide.QtGui import qRgb
|
from PySide.QtGui import qRgb
|
||||||
|
elif ImageQt.qt_version == 'side2':
|
||||||
|
from PySide2.QtGui import qRgb
|
||||||
|
|
||||||
self.assertEqual(qRgb(0, 0, 0), qRgba(0, 0, 0, 255))
|
self.assertEqual(qRgb(0, 0, 0), qRgba(0, 0, 0, 255))
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ class TestImageTk(PillowTestCase):
|
||||||
self.skipTest("Tk not installed")
|
self.skipTest("Tk not installed")
|
||||||
try:
|
try:
|
||||||
# setup tk
|
# setup tk
|
||||||
app = tk.Frame()
|
tk.Frame()
|
||||||
# root = tk.Tk()
|
# root = tk.Tk()
|
||||||
except (tk.TclError) as v:
|
except (tk.TclError) as v:
|
||||||
self.skipTest("TCL Error: %s" % v)
|
self.skipTest("TCL Error: %s" % v)
|
||||||
|
|
|
@ -96,7 +96,6 @@ if sys.platform.startswith('win32'):
|
||||||
hdr.biClrImportant = 0
|
hdr.biClrImportant = 0
|
||||||
|
|
||||||
hdc = CreateCompatibleDC(None)
|
hdc = CreateCompatibleDC(None)
|
||||||
# print('hdc:',hex(hdc))
|
|
||||||
pixels = ctypes.c_void_p()
|
pixels = ctypes.c_void_p()
|
||||||
dib = CreateDIBSection(hdc, ctypes.byref(hdr), DIB_RGB_COLORS,
|
dib = CreateDIBSection(hdc, ctypes.byref(hdr), DIB_RGB_COLORS,
|
||||||
ctypes.byref(pixels), None, 0)
|
ctypes.byref(pixels), None, 0)
|
||||||
|
|
|
@ -221,7 +221,8 @@ class TestLibUnpack(PillowTestCase):
|
||||||
data_len = data * len(pixels)
|
data_len = data * len(pixels)
|
||||||
data = bytes(bytearray(range(1, data_len + 1)))
|
data = bytes(bytearray(range(1, data_len + 1)))
|
||||||
|
|
||||||
im = Image.frombytes(mode, (len(pixels), 1), data, "raw", rawmode, 0, 1)
|
im = Image.frombytes(mode, (len(pixels), 1), data,
|
||||||
|
"raw", rawmode, 0, 1)
|
||||||
|
|
||||||
for x, pixel in enumerate(pixels):
|
for x, pixel in enumerate(pixels):
|
||||||
self.assertEqual(pixel, im.getpixel((x, 0)))
|
self.assertEqual(pixel, im.getpixel((x, 0)))
|
||||||
|
@ -265,9 +266,11 @@ class TestLibUnpack(PillowTestCase):
|
||||||
def test_P(self):
|
def test_P(self):
|
||||||
self.assert_unpack("P", "P;1", b'\xe4', 1, 1, 1, 0, 0, 1, 0, 0)
|
self.assert_unpack("P", "P;1", b'\xe4', 1, 1, 1, 0, 0, 1, 0, 0)
|
||||||
self.assert_unpack("P", "P;2", b'\xe4', 3, 2, 1, 0)
|
self.assert_unpack("P", "P;2", b'\xe4', 3, 2, 1, 0)
|
||||||
# self.assert_unpack("P", "P;2L", b'\xe4', 1, 1, 1, 0) # erroneous?
|
# erroneous?
|
||||||
|
# self.assert_unpack("P", "P;2L", b'\xe4', 1, 1, 1, 0)
|
||||||
self.assert_unpack("P", "P;4", b'\x02\xef', 0, 2, 14, 15)
|
self.assert_unpack("P", "P;4", b'\x02\xef', 0, 2, 14, 15)
|
||||||
# self.assert_unpack("P", "P;4L", b'\x02\xef', 2, 10, 10, 0) # erroneous?
|
# erroneous?
|
||||||
|
# self.assert_unpack("P", "P;4L", b'\x02\xef', 2, 10, 10, 0)
|
||||||
self.assert_unpack("P", "P", 1, 1, 2, 3, 4)
|
self.assert_unpack("P", "P", 1, 1, 2, 3, 4)
|
||||||
self.assert_unpack("P", "P;R", 1, 128, 64, 192, 32)
|
self.assert_unpack("P", "P;R", 1, 128, 64, 192, 32)
|
||||||
|
|
||||||
|
@ -309,13 +312,25 @@ class TestLibUnpack(PillowTestCase):
|
||||||
"RGBA", "LA;16B", 4, (1, 1, 1, 3), (5, 5, 5, 7), (9, 9, 9, 11))
|
"RGBA", "LA;16B", 4, (1, 1, 1, 3), (5, 5, 5, 7), (9, 9, 9, 11))
|
||||||
self.assert_unpack(
|
self.assert_unpack(
|
||||||
"RGBA", "RGBA", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12))
|
"RGBA", "RGBA", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12))
|
||||||
|
self.assert_unpack(
|
||||||
|
"RGBA", "RGBAX", 5, (1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14))
|
||||||
|
self.assert_unpack(
|
||||||
|
"RGBA", "RGBAXX", 6, (1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16))
|
||||||
self.assert_unpack(
|
self.assert_unpack(
|
||||||
"RGBA", "RGBa", 4,
|
"RGBA", "RGBa", 4,
|
||||||
(63, 127, 191, 4), (159, 191, 223, 8), (191, 212, 233, 12))
|
(63, 127, 191, 4), (159, 191, 223, 8), (191, 212, 233, 12))
|
||||||
self.assert_unpack(
|
self.assert_unpack(
|
||||||
"RGBA", "RGBa",
|
"RGBA", "RGBa",
|
||||||
b'\x01\x02\x03\x00\x10\x20\x30\xff',
|
b'\x01\x02\x03\x00\x10\x20\x30\x7f\x10\x20\x30\xff',
|
||||||
(0, 0, 0, 0), (16, 32, 48, 255))
|
(0, 0, 0, 0), (32, 64, 96, 127), (16, 32, 48, 255))
|
||||||
|
self.assert_unpack(
|
||||||
|
"RGBA", "RGBaX",
|
||||||
|
b'\x01\x02\x03\x00-\x10\x20\x30\x7f-\x10\x20\x30\xff-',
|
||||||
|
(0, 0, 0, 0), (32, 64, 96, 127), (16, 32, 48, 255))
|
||||||
|
self.assert_unpack(
|
||||||
|
"RGBA", "RGBaXX",
|
||||||
|
b'\x01\x02\x03\x00==\x10\x20\x30\x7f!!\x10\x20\x30\xff??',
|
||||||
|
(0, 0, 0, 0), (32, 64, 96, 127), (16, 32, 48, 255))
|
||||||
self.assert_unpack(
|
self.assert_unpack(
|
||||||
"RGBA", "RGBa;16L", 8,
|
"RGBA", "RGBa;16L", 8,
|
||||||
(63, 127, 191, 8), (159, 191, 223, 16), (191, 212, 233, 24))
|
(63, 127, 191, 8), (159, 191, 223, 16), (191, 212, 233, 24))
|
||||||
|
@ -361,7 +376,8 @@ class TestLibUnpack(PillowTestCase):
|
||||||
self.assert_unpack(
|
self.assert_unpack(
|
||||||
"RGBA", "YCCA;P",
|
"RGBA", "YCCA;P",
|
||||||
b']bE\x04\xdd\xbej\xed57T\xce\xac\xce:\x11', # random data
|
b']bE\x04\xdd\xbej\xed57T\xce\xac\xce:\x11', # random data
|
||||||
(0, 161, 0, 4), (255, 255, 255, 237), (27, 158, 0, 206), (0, 118, 0, 17))
|
(0, 161, 0, 4), (255, 255, 255, 237),
|
||||||
|
(27, 158, 0, 206), (0, 118, 0, 17))
|
||||||
self.assert_unpack(
|
self.assert_unpack(
|
||||||
"RGBA", "R", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0))
|
"RGBA", "R", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0))
|
||||||
self.assert_unpack(
|
self.assert_unpack(
|
||||||
|
@ -413,7 +429,8 @@ class TestLibUnpack(PillowTestCase):
|
||||||
self.assert_unpack(
|
self.assert_unpack(
|
||||||
"RGBX", "YCC;P",
|
"RGBX", "YCC;P",
|
||||||
b'D]\x9c\x82\x1a\x91\xfaOC\xe7J\x12', # random data
|
b'D]\x9c\x82\x1a\x91\xfaOC\xe7J\x12', # random data
|
||||||
(127, 102, 0, X), (192, 227, 0, X), (213, 255, 170, X), (98, 255, 133, X))
|
(127, 102, 0, X), (192, 227, 0, X),
|
||||||
|
(213, 255, 170, X), (98, 255, 133, X))
|
||||||
self.assert_unpack("RGBX", "R", 1,
|
self.assert_unpack("RGBX", "R", 1,
|
||||||
(1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0))
|
(1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0))
|
||||||
self.assert_unpack("RGBX", "G", 1,
|
self.assert_unpack("RGBX", "G", 1,
|
||||||
|
|
|
@ -34,7 +34,6 @@ class TestNumpy(PillowTestCase):
|
||||||
i = Image.fromarray(a)
|
i = Image.fromarray(a)
|
||||||
if list(i.getchannel(0).getdata()) != list(range(100)):
|
if list(i.getchannel(0).getdata()) != list(range(100)):
|
||||||
print("data mismatch for", dtype)
|
print("data mismatch for", dtype)
|
||||||
# print(dtype, list(i.getdata()))
|
|
||||||
return i
|
return i
|
||||||
|
|
||||||
# Check supported 1-bit integer formats
|
# Check supported 1-bit integer formats
|
||||||
|
|
|
@ -26,7 +26,9 @@ class TestPdfParser(PillowTestCase):
|
||||||
def test_parsing(self):
|
def test_parsing(self):
|
||||||
self.assertEqual(PdfParser.interpret_name(b"Name#23Hash"),
|
self.assertEqual(PdfParser.interpret_name(b"Name#23Hash"),
|
||||||
b"Name#Hash")
|
b"Name#Hash")
|
||||||
self.assertEqual(PdfParser.interpret_name(b"Name#23Hash", as_text=True), "Name#Hash")
|
self.assertEqual(PdfParser.interpret_name(
|
||||||
|
b"Name#23Hash", as_text=True
|
||||||
|
), "Name#Hash")
|
||||||
self.assertEqual(PdfParser.get_value(b"1 2 R ", 0),
|
self.assertEqual(PdfParser.get_value(b"1 2 R ", 0),
|
||||||
(IndirectReference(1, 2), 5))
|
(IndirectReference(1, 2), 5))
|
||||||
self.assertEqual(PdfParser.get_value(b"true[", 0), (True, 4))
|
self.assertEqual(PdfParser.get_value(b"true[", 0), (True, 4))
|
||||||
|
@ -72,7 +74,9 @@ class TestPdfParser(PillowTestCase):
|
||||||
self.assertIsInstance(a, list)
|
self.assertIsInstance(a, list)
|
||||||
self.assertEqual(len(a), 4)
|
self.assertEqual(len(a), 4)
|
||||||
self.assertEqual(a[0], PdfName("Name"))
|
self.assertEqual(a[0], PdfName("Name"))
|
||||||
s = PdfParser.get_value(b"<</Name (value) /Length 5>>\nstream\nabcde\nendstream<<...", 0)[0]
|
s = PdfParser.get_value(
|
||||||
|
b"<</Name (value) /Length 5>>\nstream\nabcde\nendstream<<...", 0
|
||||||
|
)[0]
|
||||||
self.assertIsInstance(s, PdfStream)
|
self.assertIsInstance(s, PdfStream)
|
||||||
self.assertEqual(s.dictionary.Name, "value")
|
self.assertEqual(s.dictionary.Name, "value")
|
||||||
self.assertEqual(s.decode(), b"abcde")
|
self.assertEqual(s.decode(), b"abcde")
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from helper import unittest, PillowTestCase, hopper
|
from helper import unittest, PillowTestCase, hopper
|
||||||
from test_imageqt import PillowQtTestCase, PillowQPixmapTestCase
|
from test_imageqt import PillowQPixmapTestCase
|
||||||
|
|
||||||
from PIL import ImageQt
|
from PIL import ImageQt
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ from PIL import ImageQt
|
||||||
class TestFromQPixmap(PillowQPixmapTestCase, PillowTestCase):
|
class TestFromQPixmap(PillowQPixmapTestCase, PillowTestCase):
|
||||||
|
|
||||||
def roundtrip(self, expected):
|
def roundtrip(self, expected):
|
||||||
PillowQtTestCase.setUp(self)
|
|
||||||
result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected))
|
result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected))
|
||||||
# Qt saves all pixmaps as rgb
|
# Qt saves all pixmaps as rgb
|
||||||
self.assert_image_equal(result, expected.convert('RGB'))
|
self.assert_image_equal(result, expected.convert('RGB'))
|
||||||
|
|
|
@ -11,21 +11,28 @@ if ImageQt.qt_is_installed:
|
||||||
from PyQt5 import QtGui
|
from PyQt5 import QtGui
|
||||||
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QApplication
|
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QApplication
|
||||||
QT_VERSION = 5
|
QT_VERSION = 5
|
||||||
|
except (ImportError, RuntimeError):
|
||||||
|
try:
|
||||||
|
from PySide2 import QtGui
|
||||||
|
from PySide2.QtWidgets import QWidget, QHBoxLayout, QLabel, \
|
||||||
|
QApplication
|
||||||
|
QT_VERSION = 5
|
||||||
except (ImportError, RuntimeError):
|
except (ImportError, RuntimeError):
|
||||||
try:
|
try:
|
||||||
from PyQt4 import QtGui
|
from PyQt4 import QtGui
|
||||||
from PyQt4.QtGui import QWidget, QHBoxLayout, QLabel, QApplication
|
from PyQt4.QtGui import QWidget, QHBoxLayout, QLabel, \
|
||||||
|
QApplication
|
||||||
QT_VERSION = 4
|
QT_VERSION = 4
|
||||||
except (ImportError, RuntimeError):
|
except (ImportError, RuntimeError):
|
||||||
from PySide import QtGui
|
from PySide import QtGui
|
||||||
from PySide.QtGui import QWidget, QHBoxLayout, QLabel, QApplication
|
from PySide.QtGui import QWidget, QHBoxLayout, QLabel, \
|
||||||
|
QApplication
|
||||||
QT_VERSION = 4
|
QT_VERSION = 4
|
||||||
|
|
||||||
|
|
||||||
class TestToQImage(PillowQtTestCase, PillowTestCase):
|
class TestToQImage(PillowQtTestCase, PillowTestCase):
|
||||||
|
|
||||||
def test_sanity(self):
|
def test_sanity(self):
|
||||||
PillowQtTestCase.setUp(self)
|
|
||||||
for mode in ('RGB', 'RGBA', 'L', 'P', '1'):
|
for mode in ('RGB', 'RGBA', 'L', 'P', '1'):
|
||||||
src = hopper(mode)
|
src = hopper(mode)
|
||||||
data = ImageQt.toqimage(src)
|
data = ImageQt.toqimage(src)
|
||||||
|
@ -61,8 +68,6 @@ class TestToQImage(PillowQtTestCase, PillowTestCase):
|
||||||
self.assert_image_equal(reloaded, src)
|
self.assert_image_equal(reloaded, src)
|
||||||
|
|
||||||
def test_segfault(self):
|
def test_segfault(self):
|
||||||
PillowQtTestCase.setUp(self)
|
|
||||||
|
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
ex = Example()
|
ex = Example()
|
||||||
assert(app) # Silence warning
|
assert(app) # Silence warning
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from helper import unittest, PillowTestCase, hopper
|
from helper import unittest, PillowTestCase, hopper
|
||||||
from test_imageqt import PillowQtTestCase, PillowQPixmapTestCase
|
from test_imageqt import PillowQPixmapTestCase
|
||||||
|
|
||||||
from PIL import ImageQt
|
from PIL import ImageQt
|
||||||
|
|
||||||
|
@ -10,8 +10,6 @@ if ImageQt.qt_is_installed:
|
||||||
class TestToQPixmap(PillowQPixmapTestCase, PillowTestCase):
|
class TestToQPixmap(PillowQPixmapTestCase, PillowTestCase):
|
||||||
|
|
||||||
def test_sanity(self):
|
def test_sanity(self):
|
||||||
PillowQtTestCase.setUp(self)
|
|
||||||
|
|
||||||
for mode in ('1', 'RGB', 'RGBA', 'L', 'P'):
|
for mode in ('1', 'RGB', 'RGBA', 'L', 'P'):
|
||||||
data = ImageQt.toqpixmap(hopper(mode))
|
data = ImageQt.toqpixmap(hopper(mode))
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,20 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# install extra test images
|
# install extra test images
|
||||||
|
|
||||||
rm -r test_images
|
rm -rf test_images
|
||||||
|
|
||||||
|
# Use SVN to just fetch a single Git subdirectory
|
||||||
|
svn_checkout()
|
||||||
|
{
|
||||||
|
if [ ! -z $1 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "Retrying svn checkout..."
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
# Use SVN to just fetch a single git subdirectory
|
|
||||||
svn checkout https://github.com/python-pillow/pillow-depends/trunk/test_images
|
svn checkout https://github.com/python-pillow/pillow-depends/trunk/test_images
|
||||||
|
}
|
||||||
|
svn_checkout || svn_checkout retry || svn_checkout retry || svn_checkout retry
|
||||||
|
|
||||||
cp -r test_images/* ../Tests/images
|
cp -r test_images/* ../Tests/images
|
||||||
|
|
||||||
|
|
|
@ -171,7 +171,6 @@ The fields are used as follows:
|
||||||
stride defaults to 0.
|
stride defaults to 0.
|
||||||
|
|
||||||
**orientation**
|
**orientation**
|
||||||
|
|
||||||
Whether the first line in the image is the top line on the screen (1), or
|
Whether the first line in the image is the top line on the screen (1), or
|
||||||
the bottom line (-1). If omitted, the orientation defaults to 1.
|
the bottom line (-1). If omitted, the orientation defaults to 1.
|
||||||
|
|
||||||
|
@ -204,7 +203,7 @@ table describes some commonly used **raw modes**:
|
||||||
+-----------+-----------------------------------------------------------------+
|
+-----------+-----------------------------------------------------------------+
|
||||||
| ``RGBX`` | 24-bit true colour, stored as (red, green, blue, pad). |
|
| ``RGBX`` | 24-bit true colour, stored as (red, green, blue, pad). |
|
||||||
+-----------+-----------------------------------------------------------------+
|
+-----------+-----------------------------------------------------------------+
|
||||||
| ``RGB;L`` | 24-bit true colour, line interleaved (first all red pixels, the |
|
| ``RGB;L`` | 24-bit true colour, line interleaved (first all red pixels, then|
|
||||||
| | all green pixels, finally all blue pixels). |
|
| | all green pixels, finally all blue pixels). |
|
||||||
+-----------+-----------------------------------------------------------------+
|
+-----------+-----------------------------------------------------------------+
|
||||||
|
|
||||||
|
|
|
@ -171,19 +171,22 @@ Methods
|
||||||
:param outline: Color to use for the outline.
|
:param outline: Color to use for the outline.
|
||||||
:param fill: Color to use for the fill.
|
:param fill: Color to use for the fill.
|
||||||
|
|
||||||
.. py:method:: PIL.ImageDraw.ImageDraw.line(xy, fill=None, width=0)
|
.. py:method:: PIL.ImageDraw.ImageDraw.line(xy, fill=None, width=0, joint=None)
|
||||||
|
|
||||||
Draws a line between the coordinates in the **xy** list.
|
Draws a line between the coordinates in the **xy** list.
|
||||||
|
|
||||||
:param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or
|
:param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or
|
||||||
numeric values like ``[x, y, x, y, ...]``.
|
numeric values like ``[x, y, x, y, ...]``.
|
||||||
:param fill: Color to use for the line.
|
:param fill: Color to use for the line.
|
||||||
:param width: The line width, in pixels. Note that line
|
:param width: The line width, in pixels.
|
||||||
joins are not handled well, so wide polylines will not look good.
|
|
||||||
|
|
||||||
.. versionadded:: 1.1.5
|
.. versionadded:: 1.1.5
|
||||||
|
|
||||||
.. note:: This option was broken until version 1.1.6.
|
.. note:: This option was broken until version 1.1.6.
|
||||||
|
:param joint: Joint type between a sequence of lines. It can be "curve",
|
||||||
|
for rounded edges, or None.
|
||||||
|
|
||||||
|
.. versionadded:: 5.3.0
|
||||||
|
|
||||||
.. py:method:: PIL.ImageDraw.ImageDraw.pieslice(xy, start, end, fill=None, outline=None)
|
.. py:method:: PIL.ImageDraw.ImageDraw.pieslice(xy, start, end, fill=None, outline=None)
|
||||||
|
|
||||||
|
@ -250,9 +253,8 @@ Methods
|
||||||
:param align: If the text is passed on to multiline_text(),
|
:param align: If the text is passed on to multiline_text(),
|
||||||
"left", "center" or "right".
|
"left", "center" or "right".
|
||||||
:param direction: Direction of the text. It can be 'rtl' (right to
|
:param direction: Direction of the text. It can be 'rtl' (right to
|
||||||
left), 'ltr' (left to right), 'ttb' (top to
|
left), 'ltr' (left to right) or 'ttb' (top to bottom).
|
||||||
bottom) or 'btt' (bottom to top). Requires
|
Requires libraqm.
|
||||||
libraqm.
|
|
||||||
|
|
||||||
.. versionadded:: 4.2.0
|
.. versionadded:: 4.2.0
|
||||||
|
|
||||||
|
@ -280,9 +282,8 @@ Methods
|
||||||
:param spacing: The number of pixels between lines.
|
:param spacing: The number of pixels between lines.
|
||||||
:param align: "left", "center" or "right".
|
:param align: "left", "center" or "right".
|
||||||
:param direction: Direction of the text. It can be 'rtl' (right to
|
:param direction: Direction of the text. It can be 'rtl' (right to
|
||||||
left), 'ltr' (left to right), 'ttb' (top to
|
left), 'ltr' (left to right) or 'ttb' (top to bottom).
|
||||||
bottom) or 'btt' (bottom to top). Requires
|
Requires libraqm.
|
||||||
libraqm.
|
|
||||||
|
|
||||||
.. versionadded:: 4.2.0
|
.. versionadded:: 4.2.0
|
||||||
|
|
||||||
|
@ -309,9 +310,8 @@ Methods
|
||||||
:param spacing: If the text is passed on to multiline_textsize(),
|
:param spacing: If the text is passed on to multiline_textsize(),
|
||||||
the number of pixels between lines.
|
the number of pixels between lines.
|
||||||
:param direction: Direction of the text. It can be 'rtl' (right to
|
:param direction: Direction of the text. It can be 'rtl' (right to
|
||||||
left), 'ltr' (left to right), 'ttb' (top to
|
left), 'ltr' (left to right) or 'ttb' (top to bottom).
|
||||||
bottom) or 'btt' (bottom to top). Requires
|
Requires libraqm.
|
||||||
libraqm.
|
|
||||||
|
|
||||||
.. versionadded:: 4.2.0
|
.. versionadded:: 4.2.0
|
||||||
|
|
||||||
|
@ -336,9 +336,8 @@ Methods
|
||||||
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
|
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
|
||||||
:param spacing: The number of pixels between lines.
|
:param spacing: The number of pixels between lines.
|
||||||
:param direction: Direction of the text. It can be 'rtl' (right to
|
:param direction: Direction of the text. It can be 'rtl' (right to
|
||||||
left), 'ltr' (left to right), 'ttb' (top to
|
left), 'ltr' (left to right) or 'ttb' (top to bottom).
|
||||||
bottom) or 'btt' (bottom to top). Requires
|
Requires libraqm.
|
||||||
libraqm.
|
|
||||||
|
|
||||||
.. versionadded:: 4.2.0
|
.. versionadded:: 4.2.0
|
||||||
|
|
||||||
|
|
|
@ -67,9 +67,8 @@ Methods
|
||||||
.. versionadded:: 1.1.5
|
.. versionadded:: 1.1.5
|
||||||
|
|
||||||
:param direction: Direction of the text. It can be 'rtl' (right to
|
:param direction: Direction of the text. It can be 'rtl' (right to
|
||||||
left), 'ltr' (left to right), 'ttb' (top to
|
left), 'ltr' (left to right) or 'ttb' (top to bottom).
|
||||||
bottom) or 'btt' (bottom to top). Requires
|
Requires libraqm.
|
||||||
libraqm.
|
|
||||||
|
|
||||||
.. versionadded:: 4.2.0
|
.. versionadded:: 4.2.0
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
:py:mod:`ImageQt` Module
|
:py:mod:`ImageQt` Module
|
||||||
========================
|
========================
|
||||||
|
|
||||||
The :py:mod:`ImageQt` module contains support for creating PyQt4, PyQt5 or
|
The :py:mod:`ImageQt` module contains support for creating PyQt4, PyQt5, PySide or
|
||||||
PySide QImage objects from PIL images.
|
PySide2 QImage objects from PIL images.
|
||||||
|
|
||||||
.. versionadded:: 1.1.6
|
.. versionadded:: 1.1.6
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# A monkey patch of the base distutils.ccompiler to use parallel builds
|
# A monkey patch of the base distutils.ccompiler to use parallel builds
|
||||||
# Tested on 2.7, looks to be identical to 3.3.
|
# Tested on 2.7, looks to be identical to 3.3.
|
||||||
|
# Only applied on Python < 3.5 because otherwise, it conflicts with Python's
|
||||||
|
# own newly-added support for parallel builds.
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from multiprocessing import Pool, cpu_count
|
from multiprocessing import Pool, cpu_count
|
||||||
|
@ -77,4 +79,6 @@ def install():
|
||||||
"%s processes" % MAX_PROCS)
|
"%s processes" % MAX_PROCS)
|
||||||
|
|
||||||
|
|
||||||
|
# We monkeypatch only versions earlier than 3.5
|
||||||
|
if sys.version_info < (3, 5):
|
||||||
install()
|
install()
|
||||||
|
|
12
setup.py
|
@ -205,6 +205,12 @@ class pil_build_ext(build_ext):
|
||||||
if self.debug:
|
if self.debug:
|
||||||
global DEBUG
|
global DEBUG
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
if sys.version_info >= (3, 5) and not self.parallel:
|
||||||
|
# For Python < 3.5, we monkeypatch distutils to have parallel
|
||||||
|
# builds. If --parallel (or -j) wasn't specified, we want to
|
||||||
|
# reproduce the same behavior as before, that is, auto-detect the
|
||||||
|
# number of jobs.
|
||||||
|
self.parallel = mp_compile.MAX_PROCS
|
||||||
for x in self.feature:
|
for x in self.feature:
|
||||||
if getattr(self, 'disable_%s' % x):
|
if getattr(self, 'disable_%s' % x):
|
||||||
setattr(self.feature, x, False)
|
setattr(self.feature, x, False)
|
||||||
|
@ -518,10 +524,7 @@ class pil_build_ext(build_ext):
|
||||||
if _find_include_file(self, 'tiff.h'):
|
if _find_include_file(self, 'tiff.h'):
|
||||||
if _find_library_file(self, "tiff"):
|
if _find_library_file(self, "tiff"):
|
||||||
feature.tiff = "tiff"
|
feature.tiff = "tiff"
|
||||||
if (sys.platform == "win32" and
|
if (sys.platform in ["win32", "darwin"] and
|
||||||
_find_library_file(self, "libtiff")):
|
|
||||||
feature.tiff = "libtiff"
|
|
||||||
if (sys.platform == "darwin" and
|
|
||||||
_find_library_file(self, "libtiff")):
|
_find_library_file(self, "libtiff")):
|
||||||
feature.tiff = "libtiff"
|
feature.tiff = "libtiff"
|
||||||
|
|
||||||
|
@ -547,7 +550,6 @@ class pil_build_ext(build_ext):
|
||||||
break
|
break
|
||||||
if freetype_version:
|
if freetype_version:
|
||||||
feature.freetype = "freetype"
|
feature.freetype = "freetype"
|
||||||
feature.freetype_version = freetype_version
|
|
||||||
if subdir:
|
if subdir:
|
||||||
_add_directory(self.compiler.include_dirs, subdir, 0)
|
_add_directory(self.compiler.include_dirs, subdir, 0)
|
||||||
|
|
||||||
|
|
|
@ -110,20 +110,6 @@ class BdfFontFile(FontFile.FontFile):
|
||||||
if s.find(b"LogicalFontDescription") < 0:
|
if s.find(b"LogicalFontDescription") < 0:
|
||||||
comments.append(s[i+1:-1].decode('ascii'))
|
comments.append(s[i+1:-1].decode('ascii'))
|
||||||
|
|
||||||
# font = props["FONT"].split("-")
|
|
||||||
|
|
||||||
# font[4] = bdf_slant[font[4].upper()]
|
|
||||||
# font[11] = bdf_spacing[font[11].upper()]
|
|
||||||
|
|
||||||
# ascent = int(props["FONT_ASCENT"])
|
|
||||||
# descent = int(props["FONT_DESCENT"])
|
|
||||||
|
|
||||||
# fontname = ";".join(font[1:])
|
|
||||||
|
|
||||||
# print("#", fontname)
|
|
||||||
# for i in comments:
|
|
||||||
# print("#", i)
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
c = bdf_char(fp)
|
c = bdf_char(fp)
|
||||||
if not c:
|
if not c:
|
||||||
|
|
|
@ -56,14 +56,6 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
|
||||||
m = s
|
m = s
|
||||||
elif i8(s[0]) > i8(m[0]) and i8(s[1]) > i8(m[1]):
|
elif i8(s[0]) > i8(m[0]) and i8(s[1]) > i8(m[1]):
|
||||||
m = s
|
m = s
|
||||||
# print("width", i8(s[0]))
|
|
||||||
# print("height", i8(s[1]))
|
|
||||||
# print("colors", i8(s[2]))
|
|
||||||
# print("reserved", i8(s[3]))
|
|
||||||
# print("hotspot x", i16(s[4:]))
|
|
||||||
# print("hotspot y", i16(s[6:]))
|
|
||||||
# print("bytes", i32(s[8:]))
|
|
||||||
# print("offset", i32(s[12:]))
|
|
||||||
if not m:
|
if not m:
|
||||||
raise TypeError("No cursors were found")
|
raise TypeError("No cursors were found")
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,6 @@ def Ghostscript(tile, size, fp, scale=1):
|
||||||
# resolution is dependent on bbox and size
|
# resolution is dependent on bbox and size
|
||||||
res = (float((72.0 * size[0]) / (bbox[2]-bbox[0])),
|
res = (float((72.0 * size[0]) / (bbox[2]-bbox[0])),
|
||||||
float((72.0 * size[1]) / (bbox[3]-bbox[1])))
|
float((72.0 * size[1]) / (bbox[3]-bbox[1])))
|
||||||
# print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res)
|
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
|
@ -357,22 +356,14 @@ def _save(im, fp, filename, eps=1):
|
||||||
else:
|
else:
|
||||||
raise ValueError("image mode is not supported")
|
raise ValueError("image mode is not supported")
|
||||||
|
|
||||||
class NoCloseStream(object):
|
|
||||||
def __init__(self, fp):
|
|
||||||
self.fp = fp
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
return getattr(self.fp, name)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
base_fp = fp
|
base_fp = fp
|
||||||
|
wrapped_fp = False
|
||||||
if fp != sys.stdout:
|
if fp != sys.stdout:
|
||||||
fp = NoCloseStream(fp)
|
|
||||||
if sys.version_info.major > 2:
|
if sys.version_info.major > 2:
|
||||||
fp = io.TextIOWrapper(fp, encoding='latin-1')
|
fp = io.TextIOWrapper(fp, encoding='latin-1')
|
||||||
|
wrapped_fp = True
|
||||||
|
|
||||||
|
try:
|
||||||
if eps:
|
if eps:
|
||||||
#
|
#
|
||||||
# write EPS header
|
# write EPS header
|
||||||
|
@ -405,6 +396,9 @@ def _save(im, fp, filename, eps=1):
|
||||||
fp.write("grestore end\n")
|
fp.write("grestore end\n")
|
||||||
if hasattr(fp, "flush"):
|
if hasattr(fp, "flush"):
|
||||||
fp.flush()
|
fp.flush()
|
||||||
|
finally:
|
||||||
|
if wrapped_fp:
|
||||||
|
fp.detach()
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
|
@ -90,7 +90,6 @@ class FontFile(object):
|
||||||
x = xx
|
x = xx
|
||||||
s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0
|
s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0
|
||||||
self.bitmap.paste(im.crop(src), s)
|
self.bitmap.paste(im.crop(src), s)
|
||||||
# print(chr(i), dst, s)
|
|
||||||
self.metrics[i] = d, dst, s
|
self.metrics[i] = d, dst, s
|
||||||
|
|
||||||
def save(self, filename):
|
def save(self, filename):
|
||||||
|
|
|
@ -114,8 +114,6 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
if id in prop:
|
if id in prop:
|
||||||
self.jpeg[i] = prop[id]
|
self.jpeg[i] = prop[id]
|
||||||
|
|
||||||
# print(len(self.jpeg), "tables loaded")
|
|
||||||
|
|
||||||
self._open_subimage(1, self.maxid)
|
self._open_subimage(1, self.maxid)
|
||||||
|
|
||||||
def _open_subimage(self, index=1, subimage=0):
|
def _open_subimage(self, index=1, subimage=0):
|
||||||
|
@ -143,8 +141,6 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
offset = i32(s, 28)
|
offset = i32(s, 28)
|
||||||
length = i32(s, 32)
|
length = i32(s, 32)
|
||||||
|
|
||||||
# print(size, self.mode, self.rawmode)
|
|
||||||
|
|
||||||
if size != self.size:
|
if size != self.size:
|
||||||
raise IOError("subimage mismatch")
|
raise IOError("subimage mismatch")
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,8 @@ class GdImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
self.palette = ImagePalette.raw("XBGR", s[7+trueColorOffset+4:7+trueColorOffset+4+256*4])
|
self.palette = ImagePalette.raw("XBGR", s[7+trueColorOffset+4:7+trueColorOffset+4+256*4])
|
||||||
|
|
||||||
self.tile = [("raw", (0, 0)+self.size, 7+trueColorOffset+4+256*4, ("L", 0, 1))]
|
self.tile = [("raw", (0, 0)+self.size, 7+trueColorOffset+4+256*4,
|
||||||
|
("L", 0, 1))]
|
||||||
|
|
||||||
|
|
||||||
def open(fp, mode="r"):
|
def open(fp, mode="r"):
|
||||||
|
|
|
@ -443,7 +443,6 @@ def _getdecoder(mode, decoder_name, args, extra=()):
|
||||||
try:
|
try:
|
||||||
# get decoder
|
# get decoder
|
||||||
decoder = getattr(core, decoder_name + "_decoder")
|
decoder = getattr(core, decoder_name + "_decoder")
|
||||||
# print(decoder, mode, args + extra)
|
|
||||||
return decoder(mode, *args + extra)
|
return decoder(mode, *args + extra)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise IOError("decoder %s not available" % decoder_name)
|
raise IOError("decoder %s not available" % decoder_name)
|
||||||
|
@ -465,7 +464,6 @@ def _getencoder(mode, encoder_name, args, extra=()):
|
||||||
try:
|
try:
|
||||||
# get encoder
|
# get encoder
|
||||||
encoder = getattr(core, encoder_name + "_encoder")
|
encoder = getattr(core, encoder_name + "_encoder")
|
||||||
# print(encoder, mode, args + extra)
|
|
||||||
return encoder(mode, *args + extra)
|
return encoder(mode, *args + extra)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise IOError("encoder %s not available" % encoder_name)
|
raise IOError("encoder %s not available" % encoder_name)
|
||||||
|
@ -900,12 +898,28 @@ class Image(object):
|
||||||
if not mode or (mode == self.mode and not matrix):
|
if not mode or (mode == self.mode and not matrix):
|
||||||
return self.copy()
|
return self.copy()
|
||||||
|
|
||||||
|
has_transparency = self.info.get('transparency') is not None
|
||||||
if matrix:
|
if matrix:
|
||||||
# matrix conversion
|
# matrix conversion
|
||||||
if mode not in ("L", "RGB"):
|
if mode not in ("L", "RGB"):
|
||||||
raise ValueError("illegal conversion")
|
raise ValueError("illegal conversion")
|
||||||
im = self.im.convert_matrix(mode, matrix)
|
im = self.im.convert_matrix(mode, matrix)
|
||||||
return self._new(im)
|
new = self._new(im)
|
||||||
|
if has_transparency and self.im.bands == 3:
|
||||||
|
transparency = new.info['transparency']
|
||||||
|
|
||||||
|
def convert_transparency(m, v):
|
||||||
|
v = m[0]*v[0] + m[1]*v[1] + m[2]*v[2] + m[3]*0.5
|
||||||
|
return max(0, min(255, int(v)))
|
||||||
|
if mode == "L":
|
||||||
|
transparency = convert_transparency(matrix, transparency)
|
||||||
|
elif len(mode) == 3:
|
||||||
|
transparency = tuple([
|
||||||
|
convert_transparency(matrix[i*4:i*4+4], transparency)
|
||||||
|
for i in range(0, len(transparency))
|
||||||
|
])
|
||||||
|
new.info['transparency'] = transparency
|
||||||
|
return new
|
||||||
|
|
||||||
if mode == "P" and self.mode == "RGBA":
|
if mode == "P" and self.mode == "RGBA":
|
||||||
return self.quantize(colors)
|
return self.quantize(colors)
|
||||||
|
@ -913,8 +927,7 @@ class Image(object):
|
||||||
trns = None
|
trns = None
|
||||||
delete_trns = False
|
delete_trns = False
|
||||||
# transparency handling
|
# transparency handling
|
||||||
if "transparency" in self.info and \
|
if has_transparency:
|
||||||
self.info['transparency'] is not None:
|
|
||||||
if self.mode in ('L', 'RGB') and mode == 'RGBA':
|
if self.mode in ('L', 'RGB') and mode == 'RGBA':
|
||||||
# Use transparent conversion to promote from transparent
|
# Use transparent conversion to promote from transparent
|
||||||
# color to an alpha channel.
|
# color to an alpha channel.
|
||||||
|
@ -1104,12 +1117,9 @@ class Image(object):
|
||||||
|
|
||||||
x0, y0, x1, y1 = map(int, map(round, box))
|
x0, y0, x1, y1 = map(int, map(round, box))
|
||||||
|
|
||||||
if x1 < x0:
|
absolute_values = (abs(x1 - x0), abs(y1 - y0))
|
||||||
x1 = x0
|
|
||||||
if y1 < y0:
|
|
||||||
y1 = y0
|
|
||||||
|
|
||||||
_decompression_bomb_check((x1, y1))
|
_decompression_bomb_check(absolute_values)
|
||||||
|
|
||||||
return im.crop((x0, y0, x1, y1))
|
return im.crop((x0, y0, x1, y1))
|
||||||
|
|
||||||
|
@ -1894,7 +1904,7 @@ class Image(object):
|
||||||
parameter should always be used.
|
parameter should always be used.
|
||||||
:param params: Extra parameters to the image writer.
|
:param params: Extra parameters to the image writer.
|
||||||
:returns: None
|
:returns: None
|
||||||
:exception KeyError: If the output format could not be determined
|
:exception ValueError: If the output format could not be determined
|
||||||
from the file name. Use the format option to solve this.
|
from the file name. Use the format option to solve this.
|
||||||
:exception IOError: If the file could not be written. The file
|
:exception IOError: If the file could not be written. The file
|
||||||
may have been created, and may contain partial data.
|
may have been created, and may contain partial data.
|
||||||
|
@ -2448,7 +2458,7 @@ def fromarray(obj, mode=None):
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import numpy as np
|
import numpy as np
|
||||||
im = Image.open('hopper.jpg')
|
im = Image.open('hopper.jpg')
|
||||||
a = numpy.asarray(im)
|
a = np.asarray(im)
|
||||||
|
|
||||||
Then this can be used to convert it to a Pillow image::
|
Then this can be used to convert it to a Pillow image::
|
||||||
|
|
||||||
|
@ -2470,7 +2480,6 @@ def fromarray(obj, mode=None):
|
||||||
typekey = (1, 1) + shape[2:], arr['typestr']
|
typekey = (1, 1) + shape[2:], arr['typestr']
|
||||||
mode, rawmode = _fromarray_typemap[typekey]
|
mode, rawmode = _fromarray_typemap[typekey]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# print(typekey)
|
|
||||||
raise TypeError("Cannot handle this data type")
|
raise TypeError("Cannot handle this data type")
|
||||||
else:
|
else:
|
||||||
rawmode = mode
|
rawmode = mode
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import math
|
||||||
import numbers
|
import numbers
|
||||||
|
|
||||||
from . import Image, ImageColor
|
from . import Image, ImageColor
|
||||||
|
@ -149,11 +150,64 @@ class ImageDraw(object):
|
||||||
if ink is not None and ink != fill:
|
if ink is not None and ink != fill:
|
||||||
self.draw.draw_ellipse(xy, ink, 0)
|
self.draw.draw_ellipse(xy, ink, 0)
|
||||||
|
|
||||||
def line(self, xy, fill=None, width=0):
|
def line(self, xy, fill=None, width=0, joint=None):
|
||||||
"""Draw a line, or a connected sequence of line segments."""
|
"""Draw a line, or a connected sequence of line segments."""
|
||||||
ink, fill = self._getink(fill)
|
ink = self._getink(fill)[0]
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
self.draw.draw_lines(xy, ink, width)
|
self.draw.draw_lines(xy, ink, width)
|
||||||
|
if joint == "curve" and width > 4:
|
||||||
|
for i in range(1, len(xy)-1):
|
||||||
|
point = xy[i]
|
||||||
|
angles = [
|
||||||
|
math.degrees(math.atan2(
|
||||||
|
end[0] - start[0], start[1] - end[1]
|
||||||
|
)) % 360
|
||||||
|
for start, end in ((xy[i-1], point), (point, xy[i+1]))
|
||||||
|
]
|
||||||
|
if angles[0] == angles[1]:
|
||||||
|
# This is a straight line, so no joint is required
|
||||||
|
continue
|
||||||
|
|
||||||
|
def coord_at_angle(coord, angle):
|
||||||
|
x, y = coord
|
||||||
|
angle -= 90
|
||||||
|
distance = width/2 - 1
|
||||||
|
return tuple([
|
||||||
|
p +
|
||||||
|
(math.floor(p_d) if p_d > 0 else math.ceil(p_d))
|
||||||
|
for p, p_d in
|
||||||
|
((x, distance * math.cos(math.radians(angle))),
|
||||||
|
(y, distance * math.sin(math.radians(angle))))
|
||||||
|
])
|
||||||
|
flipped = ((angles[1] > angles[0] and
|
||||||
|
angles[1] - 180 > angles[0]) or
|
||||||
|
(angles[1] < angles[0] and
|
||||||
|
angles[1] + 180 > angles[0]))
|
||||||
|
coords = [
|
||||||
|
(point[0] - width/2 + 1, point[1] - width/2 + 1),
|
||||||
|
(point[0] + width/2 - 1, point[1] + width/2 - 1)
|
||||||
|
]
|
||||||
|
if flipped:
|
||||||
|
start, end = (angles[1] + 90, angles[0] + 90)
|
||||||
|
else:
|
||||||
|
start, end = (angles[0] - 90, angles[1] - 90)
|
||||||
|
self.pieslice(coords, start - 90, end - 90, fill)
|
||||||
|
|
||||||
|
if width > 8:
|
||||||
|
# Cover potential gaps between the line and the joint
|
||||||
|
if flipped:
|
||||||
|
gapCoords = [
|
||||||
|
coord_at_angle(point, angles[0]+90),
|
||||||
|
point,
|
||||||
|
coord_at_angle(point, angles[1]+90)
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
gapCoords = [
|
||||||
|
coord_at_angle(point, angles[0]-90),
|
||||||
|
point,
|
||||||
|
coord_at_angle(point, angles[1]-90)
|
||||||
|
]
|
||||||
|
self.line(gapCoords, fill, width=3)
|
||||||
|
|
||||||
def shape(self, shape, fill=None, outline=None):
|
def shape(self, shape, fill=None, outline=None):
|
||||||
"""(Experimental) Draw a shape."""
|
"""(Experimental) Draw a shape."""
|
||||||
|
@ -246,7 +300,7 @@ class ImageDraw(object):
|
||||||
elif align == "right":
|
elif align == "right":
|
||||||
left += (max_width - widths[idx])
|
left += (max_width - widths[idx])
|
||||||
else:
|
else:
|
||||||
assert False, 'align must be "left", "center" or "right"'
|
raise ValueError('align must be "left", "center" or "right"')
|
||||||
self.text((left, top), line, fill, font, anchor,
|
self.text((left, top), line, fill, font, anchor,
|
||||||
direction=direction, features=features)
|
direction=direction, features=features)
|
||||||
top += line_spacing
|
top += line_spacing
|
||||||
|
@ -341,6 +395,7 @@ def floodfill(image, xy, value, border=None, thresh=0):
|
||||||
homogeneous, but similar, colors.
|
homogeneous, but similar, colors.
|
||||||
"""
|
"""
|
||||||
# based on an implementation by Eric S. Raymond
|
# based on an implementation by Eric S. Raymond
|
||||||
|
# amended by yo1995 @20180806
|
||||||
pixel = image.load()
|
pixel = image.load()
|
||||||
x, y = xy
|
x, y = xy
|
||||||
try:
|
try:
|
||||||
|
@ -350,39 +405,36 @@ def floodfill(image, xy, value, border=None, thresh=0):
|
||||||
pixel[x, y] = value
|
pixel[x, y] = value
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
return # seed point outside image
|
return # seed point outside image
|
||||||
edge = [(x, y)]
|
edge = {(x, y)}
|
||||||
|
full_edge = set() # use a set to keep record of current and previous edge pixels to reduce memory consumption
|
||||||
|
while edge:
|
||||||
|
new_edge = set()
|
||||||
|
for (x, y) in edge: # 4 adjacent method
|
||||||
|
for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
|
||||||
|
if (s, t) in full_edge:
|
||||||
|
continue # if already processed, skip
|
||||||
|
try:
|
||||||
|
p = pixel[s, t]
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
full_edge.add((s, t))
|
||||||
if border is None:
|
if border is None:
|
||||||
while edge:
|
fill = _color_diff(p, background) <= thresh
|
||||||
newedge = []
|
|
||||||
for (x, y) in edge:
|
|
||||||
for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
|
|
||||||
try:
|
|
||||||
p = pixel[s, t]
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
else:
|
else:
|
||||||
if _color_diff(p, background) <= thresh:
|
fill = p != value and p != border
|
||||||
|
if fill:
|
||||||
pixel[s, t] = value
|
pixel[s, t] = value
|
||||||
newedge.append((s, t))
|
new_edge.add((s, t))
|
||||||
edge = newedge
|
full_edge = edge # discard pixels processed
|
||||||
else:
|
edge = new_edge
|
||||||
while edge:
|
|
||||||
newedge = []
|
|
||||||
for (x, y) in edge:
|
|
||||||
for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
|
|
||||||
try:
|
|
||||||
p = pixel[s, t]
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if p != value and p != border:
|
|
||||||
pixel[s, t] = value
|
|
||||||
newedge.append((s, t))
|
|
||||||
edge = newedge
|
|
||||||
|
|
||||||
|
|
||||||
def _color_diff(rgb1, rgb2):
|
def _color_diff(color1, color2):
|
||||||
"""
|
"""
|
||||||
Uses 1-norm distance to calculate difference between two rgb values.
|
Uses 1-norm distance to calculate difference between two values.
|
||||||
"""
|
"""
|
||||||
return abs(rgb1[0]-rgb2[0]) + abs(rgb1[1]-rgb2[1]) + abs(rgb1[2]-rgb2[2])
|
if isinstance(color2, tuple):
|
||||||
|
return sum([abs(color1[i]-color2[i]) for i in range(0, len(color2))])
|
||||||
|
else:
|
||||||
|
return abs(color1-color2)
|
||||||
|
|
|
@ -30,7 +30,6 @@
|
||||||
from . import Image
|
from . import Image
|
||||||
from ._util import isPath
|
from ._util import isPath
|
||||||
import io
|
import io
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,14 @@ class MultibandFilter(Filter):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Kernel(MultibandFilter):
|
class BuiltinFilter(MultibandFilter):
|
||||||
|
def filter(self, image):
|
||||||
|
if image.mode == "P":
|
||||||
|
raise ValueError("cannot filter palette images")
|
||||||
|
return image.filter(*self.filterargs)
|
||||||
|
|
||||||
|
|
||||||
|
class Kernel(BuiltinFilter):
|
||||||
"""
|
"""
|
||||||
Create a convolution kernel. The current version only
|
Create a convolution kernel. The current version only
|
||||||
supports 3x3 and 5x5 integer and floating point kernels.
|
supports 3x3 and 5x5 integer and floating point kernels.
|
||||||
|
@ -60,16 +67,6 @@ class Kernel(MultibandFilter):
|
||||||
raise ValueError("not enough coefficients in kernel")
|
raise ValueError("not enough coefficients in kernel")
|
||||||
self.filterargs = size, scale, offset, kernel
|
self.filterargs = size, scale, offset, kernel
|
||||||
|
|
||||||
def filter(self, image):
|
|
||||||
if image.mode == "P":
|
|
||||||
raise ValueError("cannot filter palette images")
|
|
||||||
return image.filter(*self.filterargs)
|
|
||||||
|
|
||||||
|
|
||||||
class BuiltinFilter(Kernel):
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class RankFilter(Filter):
|
class RankFilter(Filter):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -162,7 +162,8 @@ class FreeTypeFont(object):
|
||||||
size, offset = self.font.getsize(text, direction, features)
|
size, offset = self.font.getsize(text, direction, features)
|
||||||
return (size[0] + offset[0], size[1] + offset[1])
|
return (size[0] + offset[0], size[1] + offset[1])
|
||||||
|
|
||||||
def getsize_multiline(self, text, direction=None, spacing=4, features=None):
|
def getsize_multiline(self, text, direction=None,
|
||||||
|
spacing=4, features=None):
|
||||||
max_width = 0
|
max_width = 0
|
||||||
lines = self._multiline_split(text)
|
lines = self._multiline_split(text)
|
||||||
line_spacing = self.getsize('A')[1] + spacing
|
line_spacing = self.getsize('A')[1] + spacing
|
||||||
|
|
|
@ -151,11 +151,6 @@ class LutBuilder(object):
|
||||||
|
|
||||||
patterns += self._pattern_permute(pattern, options, result)
|
patterns += self._pattern_permute(pattern, options, result)
|
||||||
|
|
||||||
# # Debugging
|
|
||||||
# for p, r in patterns:
|
|
||||||
# print(p, r)
|
|
||||||
# print('--')
|
|
||||||
|
|
||||||
# compile the patterns into regular expressions for speed
|
# compile the patterns into regular expressions for speed
|
||||||
for i, pattern in enumerate(patterns):
|
for i, pattern in enumerate(patterns):
|
||||||
p = pattern[0].replace('.', 'X').replace('X', '[01]')
|
p = pattern[0].replace('.', 'X').replace('X', '[01]')
|
||||||
|
|
|
@ -221,6 +221,50 @@ def colorize(image, black, white, mid=None, blackpoint=0,
|
||||||
return _lut(image, red + green + blue)
|
return _lut(image, red + green + blue)
|
||||||
|
|
||||||
|
|
||||||
|
def pad(image, size, method=Image.NEAREST, color=None, centering=(0.5, 0.5)):
|
||||||
|
"""
|
||||||
|
Returns a sized and padded version of the image, expanded to fill the
|
||||||
|
requested aspect ratio and size.
|
||||||
|
|
||||||
|
:param image: The image to size and crop.
|
||||||
|
:param size: The requested output size in pixels, given as a
|
||||||
|
(width, height) tuple.
|
||||||
|
:param method: What resampling method to use. Default is
|
||||||
|
:py:attr:`PIL.Image.NEAREST`.
|
||||||
|
:param color: The background color of the padded image.
|
||||||
|
:param centering: Control the position of the original image within the
|
||||||
|
padded version.
|
||||||
|
(0.5, 0.5) will keep the image centered
|
||||||
|
(0, 0) will keep the image aligned to the top left
|
||||||
|
(1, 1) will keep the image aligned to the bottom
|
||||||
|
right
|
||||||
|
:return: An image.
|
||||||
|
"""
|
||||||
|
|
||||||
|
im_ratio = image.width / image.height
|
||||||
|
dest_ratio = float(size[0]) / size[1]
|
||||||
|
|
||||||
|
if im_ratio == dest_ratio:
|
||||||
|
out = image.resize(size, resample=method)
|
||||||
|
else:
|
||||||
|
out = Image.new(image.mode, size, color)
|
||||||
|
if im_ratio > dest_ratio:
|
||||||
|
new_height = int(image.height / image.width * size[0])
|
||||||
|
if new_height != size[1]:
|
||||||
|
image = image.resize((size[0], new_height), resample=method)
|
||||||
|
|
||||||
|
y = int((size[1] - new_height) * max(0, min(centering[1], 1)))
|
||||||
|
out.paste(image, (0, y))
|
||||||
|
else:
|
||||||
|
new_width = int(image.width / image.height * size[1])
|
||||||
|
if new_width != size[0]:
|
||||||
|
image = image.resize((new_width, size[1]), resample=method)
|
||||||
|
|
||||||
|
x = int((size[0] - new_width) * max(0, min(centering[0], 1)))
|
||||||
|
out.paste(image, (x, 0))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def crop(image, border=0):
|
def crop(image, border=0):
|
||||||
"""
|
"""
|
||||||
Remove border from image. The same amount of pixels are removed
|
Remove border from image. The same amount of pixels are removed
|
||||||
|
|
|
@ -23,16 +23,21 @@ import sys
|
||||||
|
|
||||||
qt_versions = [
|
qt_versions = [
|
||||||
['5', 'PyQt5'],
|
['5', 'PyQt5'],
|
||||||
|
['side2', 'PySide2'],
|
||||||
['4', 'PyQt4'],
|
['4', 'PyQt4'],
|
||||||
['side', 'PySide']
|
['side', 'PySide']
|
||||||
]
|
]
|
||||||
# If a version has already been imported, attempt it first
|
# If a version has already been imported, attempt it first
|
||||||
qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, reverse=True)
|
qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules,
|
||||||
|
reverse=True)
|
||||||
for qt_version, qt_module in qt_versions:
|
for qt_version, qt_module in qt_versions:
|
||||||
try:
|
try:
|
||||||
if qt_module == 'PyQt5':
|
if qt_module == 'PyQt5':
|
||||||
from PyQt5.QtGui import QImage, qRgba, QPixmap
|
from PyQt5.QtGui import QImage, qRgba, QPixmap
|
||||||
from PyQt5.QtCore import QBuffer, QIODevice
|
from PyQt5.QtCore import QBuffer, QIODevice
|
||||||
|
elif qt_module == 'PySide2':
|
||||||
|
from PySide2.QtGui import QImage, qRgba, QPixmap
|
||||||
|
from PySide2.QtCore import QBuffer, QIODevice
|
||||||
elif qt_module == 'PyQt4':
|
elif qt_module == 'PyQt4':
|
||||||
from PyQt4.QtGui import QImage, qRgba, QPixmap
|
from PyQt4.QtGui import QImage, qRgba, QPixmap
|
||||||
from PyQt4.QtCore import QBuffer, QIODevice
|
from PyQt4.QtCore import QBuffer, QIODevice
|
||||||
|
|
|
@ -32,13 +32,6 @@ if sys.version_info.major > 2:
|
||||||
else:
|
else:
|
||||||
import Tkinter as tkinter
|
import Tkinter as tkinter
|
||||||
|
|
||||||
# required for pypy, which always has cffi installed
|
|
||||||
try:
|
|
||||||
from cffi import FFI
|
|
||||||
ffi = FFI()
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
from . import Image
|
from . import Image
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
@ -192,7 +185,11 @@ class PhotoImage(object):
|
||||||
from . import _imagingtk
|
from . import _imagingtk
|
||||||
try:
|
try:
|
||||||
if hasattr(tk, 'interp'):
|
if hasattr(tk, 'interp'):
|
||||||
# Pypy is using a ffi cdata element
|
# Required for PyPy, which always has CFFI installed
|
||||||
|
from cffi import FFI
|
||||||
|
ffi = FFI()
|
||||||
|
|
||||||
|
# PyPy is using an FFI CDATA element
|
||||||
# (Pdb) self.tk.interp
|
# (Pdb) self.tk.interp
|
||||||
# <cdata 'Tcl_Interp *' 0x3061b50>
|
# <cdata 'Tcl_Interp *' 0x3061b50>
|
||||||
_imagingtk.tkinit(
|
_imagingtk.tkinit(
|
||||||
|
|
|
@ -103,8 +103,6 @@ class IptcImageFile(ImageFile.ImageFile):
|
||||||
else:
|
else:
|
||||||
self.info[tag] = tagdata
|
self.info[tag] = tagdata
|
||||||
|
|
||||||
# print(tag, self.info[tag])
|
|
||||||
|
|
||||||
# mode
|
# mode
|
||||||
layers = i8(self.info[(3, 60)][0])
|
layers = i8(self.info[(3, 60)][0])
|
||||||
component = i8(self.info[(3, 60)][1])
|
component = i8(self.info[(3, 60)][1])
|
||||||
|
|
|
@ -334,7 +334,6 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
if i in MARKER:
|
if i in MARKER:
|
||||||
name, description, handler = MARKER[i]
|
name, description, handler = MARKER[i]
|
||||||
# print(hex(i), name, description)
|
|
||||||
if handler is not None:
|
if handler is not None:
|
||||||
handler(self, i)
|
handler(self, i)
|
||||||
if i == 0xFFDA: # start of scan
|
if i == 0xFFDA: # start of scan
|
||||||
|
|
|
@ -74,7 +74,6 @@ def isSpiderHeader(t):
|
||||||
labrec = int(h[13]) # no. records in file header
|
labrec = int(h[13]) # no. records in file header
|
||||||
labbyt = int(h[22]) # total no. of bytes in header
|
labbyt = int(h[22]) # total no. of bytes in header
|
||||||
lenbyt = int(h[23]) # record length in bytes
|
lenbyt = int(h[23]) # record length in bytes
|
||||||
# print("labrec = %d, labbyt = %d, lenbyt = %d" % (labrec,labbyt,lenbyt))
|
|
||||||
if labbyt != (labrec * lenbyt):
|
if labbyt != (labrec * lenbyt):
|
||||||
return 0
|
return 0
|
||||||
# looks like a valid header
|
# looks like a valid header
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
from . import Image, ImageFile, ImagePalette
|
from . import Image, ImageFile, ImagePalette
|
||||||
from ._binary import i8, i16le as i16, o8, o16le as o16
|
from ._binary import i8, i16le as i16, o8, o16le as o16
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
__version__ = "0.3"
|
__version__ = "0.3"
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,7 +55,7 @@ class TgaImageFile(ImageFile.ImageFile):
|
||||||
# process header
|
# process header
|
||||||
s = self.fp.read(18)
|
s = self.fp.read(18)
|
||||||
|
|
||||||
idlen = i8(s[0])
|
id_len = i8(s[0])
|
||||||
|
|
||||||
colormaptype = i8(s[1])
|
colormaptype = i8(s[1])
|
||||||
imagetype = i8(s[2])
|
imagetype = i8(s[2])
|
||||||
|
@ -100,8 +102,8 @@ class TgaImageFile(ImageFile.ImageFile):
|
||||||
if imagetype & 8:
|
if imagetype & 8:
|
||||||
self.info["compression"] = "tga_rle"
|
self.info["compression"] = "tga_rle"
|
||||||
|
|
||||||
if idlen:
|
if id_len:
|
||||||
self.info["id_section"] = self.fp.read(idlen)
|
self.info["id_section"] = self.fp.read(id_len)
|
||||||
|
|
||||||
if colormaptype:
|
if colormaptype:
|
||||||
# read palette
|
# read palette
|
||||||
|
@ -151,11 +153,23 @@ def _save(im, fp, filename):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise IOError("cannot write mode %s as TGA" % im.mode)
|
raise IOError("cannot write mode %s as TGA" % im.mode)
|
||||||
|
|
||||||
rle = im.encoderinfo.get("rle", False)
|
if "rle" in im.encoderinfo:
|
||||||
|
rle = im.encoderinfo["rle"]
|
||||||
|
else:
|
||||||
|
compression = im.encoderinfo.get("compression",
|
||||||
|
im.info.get("compression"))
|
||||||
|
rle = compression == "tga_rle"
|
||||||
if rle:
|
if rle:
|
||||||
imagetype += 8
|
imagetype += 8
|
||||||
|
|
||||||
|
id_section = im.encoderinfo.get("id_section",
|
||||||
|
im.info.get("id_section", ""))
|
||||||
|
id_len = len(id_section)
|
||||||
|
if id_len > 255:
|
||||||
|
id_len = 255
|
||||||
|
id_section = id_section[:255]
|
||||||
|
warnings.warn("id_section has been trimmed to 255 characters")
|
||||||
|
|
||||||
if colormaptype:
|
if colormaptype:
|
||||||
colormapfirst, colormaplength, colormapentry = 0, 256, 24
|
colormapfirst, colormaplength, colormapentry = 0, 256, 24
|
||||||
else:
|
else:
|
||||||
|
@ -166,11 +180,12 @@ def _save(im, fp, filename):
|
||||||
else:
|
else:
|
||||||
flags = 0
|
flags = 0
|
||||||
|
|
||||||
orientation = im.info.get("orientation", -1)
|
orientation = im.encoderinfo.get("orientation",
|
||||||
|
im.info.get("orientation", -1))
|
||||||
if orientation > 0:
|
if orientation > 0:
|
||||||
flags = flags | 0x20
|
flags = flags | 0x20
|
||||||
|
|
||||||
fp.write(b"\000" +
|
fp.write(o8(id_len) +
|
||||||
o8(colormaptype) +
|
o8(colormaptype) +
|
||||||
o8(imagetype) +
|
o8(imagetype) +
|
||||||
o16(colormapfirst) +
|
o16(colormapfirst) +
|
||||||
|
@ -183,6 +198,9 @@ def _save(im, fp, filename):
|
||||||
o8(bits) +
|
o8(bits) +
|
||||||
o8(flags))
|
o8(flags))
|
||||||
|
|
||||||
|
if id_section:
|
||||||
|
fp.write(id_section)
|
||||||
|
|
||||||
if colormaptype:
|
if colormaptype:
|
||||||
fp.write(im.im.getpalette("RGB", "BGR"))
|
fp.write(im.im.getpalette("RGB", "BGR"))
|
||||||
|
|
||||||
|
|
|
@ -207,8 +207,16 @@ OPEN_INFO = {
|
||||||
(MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("RGBX", "RGBXXX"),
|
(MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("RGBX", "RGBXXX"),
|
||||||
(II, 2, (1,), 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
|
(II, 2, (1,), 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
|
||||||
(MM, 2, (1,), 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
|
(MM, 2, (1,), 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
|
||||||
|
(II, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): ("RGBA", "RGBaX"),
|
||||||
|
(MM, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): ("RGBA", "RGBaX"),
|
||||||
|
(II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (1, 0, 0)): ("RGBA", "RGBaXX"),
|
||||||
|
(MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (1, 0, 0)): ("RGBA", "RGBaXX"),
|
||||||
(II, 2, (1,), 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
|
(II, 2, (1,), 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
|
||||||
(MM, 2, (1,), 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
|
(MM, 2, (1,), 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
|
||||||
|
(II, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): ("RGBA", "RGBAX"),
|
||||||
|
(MM, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): ("RGBA", "RGBAX"),
|
||||||
|
(II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): ("RGBA", "RGBAXX"),
|
||||||
|
(MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): ("RGBA", "RGBAXX"),
|
||||||
(II, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10
|
(II, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10
|
||||||
(MM, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10
|
(MM, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10
|
||||||
|
|
||||||
|
@ -253,8 +261,8 @@ OPEN_INFO = {
|
||||||
(MM, 6, (1,), 1, (8, 8, 8), ()): ("YCbCr", "YCbCr"),
|
(MM, 6, (1,), 1, (8, 8, 8), ()): ("YCbCr", "YCbCr"),
|
||||||
(II, 6, (1,), 1, (8, 8, 8, 8), (0,)): ("YCbCr", "YCbCrX"),
|
(II, 6, (1,), 1, (8, 8, 8, 8), (0,)): ("YCbCr", "YCbCrX"),
|
||||||
(MM, 6, (1,), 1, (8, 8, 8, 8), (0,)): ("YCbCr", "YCbCrX"),
|
(MM, 6, (1,), 1, (8, 8, 8, 8), (0,)): ("YCbCr", "YCbCrX"),
|
||||||
(II, 6, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("YCbCr", "YCbCrXXX"),
|
(II, 6, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("YCbCr", "YCbCrXX"),
|
||||||
(MM, 6, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("YCbCr", "YCbCrXXX"),
|
(MM, 6, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("YCbCr", "YCbCrXX"),
|
||||||
(II, 6, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("YCbCr", "YCbCrXXX"),
|
(II, 6, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("YCbCr", "YCbCrXXX"),
|
||||||
(MM, 6, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("YCbCr", "YCbCrXXX"),
|
(MM, 6, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("YCbCr", "YCbCrXXX"),
|
||||||
|
|
||||||
|
@ -567,6 +575,9 @@ class ImageFileDirectory_v2(MutableMapping):
|
||||||
if self.tagtype[tag] == 7 and py3:
|
if self.tagtype[tag] == 7 and py3:
|
||||||
values = [value.encode("ascii", 'replace') if isinstance(
|
values = [value.encode("ascii", 'replace') if isinstance(
|
||||||
value, str) else value]
|
value, str) else value]
|
||||||
|
elif self.tagtype[tag] == 5:
|
||||||
|
values = [float(v) if isinstance(v, int) else v
|
||||||
|
for v in values]
|
||||||
|
|
||||||
values = tuple(info.cvt_enum(value) for value in values)
|
values = tuple(info.cvt_enum(value) for value in values)
|
||||||
|
|
||||||
|
@ -1254,9 +1265,6 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
h = self.tag_v2.get(ROWSPERSTRIP, ysize)
|
h = self.tag_v2.get(ROWSPERSTRIP, ysize)
|
||||||
w = self.size[0]
|
w = self.size[0]
|
||||||
if READ_LIBTIFF or self._compression != 'raw':
|
if READ_LIBTIFF or self._compression != 'raw':
|
||||||
# if DEBUG:
|
|
||||||
# print("Activating g4 compression for whole file")
|
|
||||||
|
|
||||||
# Decoder expects entire file as one tile.
|
# Decoder expects entire file as one tile.
|
||||||
# There's a buffer size limit in load (64k)
|
# There's a buffer size limit in load (64k)
|
||||||
# so large g4 images will fail if we use that
|
# so large g4 images will fail if we use that
|
||||||
|
@ -1529,7 +1537,6 @@ def _save(im, fp, filename):
|
||||||
rawmode = 'I;16N'
|
rawmode = 'I;16N'
|
||||||
|
|
||||||
a = (rawmode, compression, _fp, filename, atts)
|
a = (rawmode, compression, _fp, filename, atts)
|
||||||
# print(im.mode, compression, a, im.encoderconfig)
|
|
||||||
e = Image._getencoder(im.mode, 'libtiff', a, im.encoderconfig)
|
e = Image._getencoder(im.mode, 'libtiff', a, im.encoderconfig)
|
||||||
e.setimage(im.im, (0, 0)+im.size)
|
e.setimage(im.im, (0, 0)+im.size)
|
||||||
while True:
|
while True:
|
||||||
|
|
|
@ -122,7 +122,7 @@ TAGS_V2 = {
|
||||||
316: ("HostComputer", ASCII, 1),
|
316: ("HostComputer", ASCII, 1),
|
||||||
317: ("Predictor", SHORT, 1, {"none": 1, "Horizontal Differencing": 2}),
|
317: ("Predictor", SHORT, 1, {"none": 1, "Horizontal Differencing": 2}),
|
||||||
318: ("WhitePoint", RATIONAL, 2),
|
318: ("WhitePoint", RATIONAL, 2),
|
||||||
319: ("PrimaryChromaticities", SHORT, 6),
|
319: ("PrimaryChromaticities", RATIONAL, 6),
|
||||||
|
|
||||||
320: ("ColorMap", SHORT, 0),
|
320: ("ColorMap", SHORT, 0),
|
||||||
321: ("HalftoneHints", SHORT, 2),
|
321: ("HalftoneHints", SHORT, 2),
|
||||||
|
@ -159,7 +159,7 @@ TAGS_V2 = {
|
||||||
529: ("YCbCrCoefficients", RATIONAL, 3),
|
529: ("YCbCrCoefficients", RATIONAL, 3),
|
||||||
530: ("YCbCrSubSampling", SHORT, 2),
|
530: ("YCbCrSubSampling", SHORT, 2),
|
||||||
531: ("YCbCrPositioning", SHORT, 1),
|
531: ("YCbCrPositioning", SHORT, 1),
|
||||||
532: ("ReferenceBlackWhite", LONG, 0),
|
532: ("ReferenceBlackWhite", RATIONAL, 6),
|
||||||
|
|
||||||
700: ('XMP', BYTE, 1),
|
700: ('XMP', BYTE, 1),
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ from io import BytesIO
|
||||||
_VALID_WEBP_MODES = {
|
_VALID_WEBP_MODES = {
|
||||||
"RGBX": True,
|
"RGBX": True,
|
||||||
"RGBA": True,
|
"RGBA": True,
|
||||||
|
"RGB": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
_VALID_WEBP_LEGACY_MODES = {
|
_VALID_WEBP_LEGACY_MODES = {
|
||||||
|
@ -63,7 +64,8 @@ class WebPImageFile(ImageFile.ImageFile):
|
||||||
bgcolor & 0xFF
|
bgcolor & 0xFF
|
||||||
self.info["background"] = (bg_r, bg_g, bg_b, bg_a)
|
self.info["background"] = (bg_r, bg_g, bg_b, bg_a)
|
||||||
self._n_frames = frame_count
|
self._n_frames = frame_count
|
||||||
self.mode = mode
|
self.mode = 'RGB' if mode == 'RGBX' else mode
|
||||||
|
self.rawmode = mode
|
||||||
self.tile = []
|
self.tile = []
|
||||||
|
|
||||||
# Attempt to read ICC / EXIF / XMP chunks from file
|
# Attempt to read ICC / EXIF / XMP chunks from file
|
||||||
|
@ -153,8 +155,10 @@ class WebPImageFile(ImageFile.ImageFile):
|
||||||
self.__loaded = self.__logical_frame
|
self.__loaded = self.__logical_frame
|
||||||
|
|
||||||
# Set tile
|
# Set tile
|
||||||
|
if self.fp:
|
||||||
|
self.fp.close()
|
||||||
self.fp = BytesIO(data)
|
self.fp = BytesIO(data)
|
||||||
self.tile = [("raw", (0, 0) + self.size, 0, self.mode)]
|
self.tile = [("raw", (0, 0) + self.size, 0, self.rawmode)]
|
||||||
|
|
||||||
return super(WebPImageFile, self).load()
|
return super(WebPImageFile, self).load()
|
||||||
|
|
||||||
|
@ -240,16 +244,23 @@ def _save_all(im, fp, filename):
|
||||||
|
|
||||||
# Make sure image mode is supported
|
# Make sure image mode is supported
|
||||||
frame = ims
|
frame = ims
|
||||||
|
rawmode = ims.mode
|
||||||
if ims.mode not in _VALID_WEBP_MODES:
|
if ims.mode not in _VALID_WEBP_MODES:
|
||||||
alpha = ims.mode == 'P' and 'A' in ims.im.getpalettemode()
|
alpha = 'A' in ims.mode or 'a' in ims.mode \
|
||||||
frame = ims.convert('RGBA' if alpha else 'RGBX')
|
or (ims.mode == 'P' and 'A' in ims.im.getpalettemode())
|
||||||
|
rawmode = 'RGBA' if alpha else 'RGB'
|
||||||
|
frame = ims.convert(rawmode)
|
||||||
|
|
||||||
|
if rawmode == 'RGB':
|
||||||
|
# For faster conversion, use RGBX
|
||||||
|
rawmode = 'RGBX'
|
||||||
|
|
||||||
# Append the frame to the animation encoder
|
# Append the frame to the animation encoder
|
||||||
enc.add(
|
enc.add(
|
||||||
frame.tobytes(),
|
frame.tobytes('raw', rawmode),
|
||||||
timestamp,
|
timestamp,
|
||||||
frame.size[0], frame.size[1],
|
frame.size[0], frame.size[1],
|
||||||
frame.mode,
|
rawmode,
|
||||||
lossless,
|
lossless,
|
||||||
quality,
|
quality,
|
||||||
method
|
method
|
||||||
|
@ -288,7 +299,8 @@ def _save(im, fp, filename):
|
||||||
xmp = im.encoderinfo.get("xmp", "")
|
xmp = im.encoderinfo.get("xmp", "")
|
||||||
|
|
||||||
if im.mode not in _VALID_WEBP_LEGACY_MODES:
|
if im.mode not in _VALID_WEBP_LEGACY_MODES:
|
||||||
alpha = im.mode == 'P' and 'A' in im.im.getpalettemode()
|
alpha = 'A' in im.mode or 'a' in im.mode \
|
||||||
|
or (im.mode == 'P' and 'A' in im.im.getpalettemode())
|
||||||
im = im.convert('RGBA' if alpha else 'RGB')
|
im = im.convert('RGBA' if alpha else 'RGB')
|
||||||
|
|
||||||
data = _webp.WebPEncode(
|
data = _webp.WebPEncode(
|
||||||
|
|
|
@ -109,8 +109,6 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
||||||
|
|
||||||
self.info["dpi"] = 72
|
self.info["dpi"] = 72
|
||||||
|
|
||||||
# print(self.mode, self.size, self.info)
|
|
||||||
|
|
||||||
# sanity check (standard metafile header)
|
# sanity check (standard metafile header)
|
||||||
if s[22:26] != b"\x01\x00\t\x00":
|
if s[22:26] != b"\x01\x00\t\x00":
|
||||||
raise SyntaxError("Unsupported WMF file format")
|
raise SyntaxError("Unsupported WMF file format")
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
"""Pillow {} (Fork of the Python Imaging Library)
|
"""Pillow (Fork of the Python Imaging Library)
|
||||||
|
|
||||||
Pillow is the friendly PIL fork by Alex Clark and Contributors.
|
Pillow is the friendly PIL fork by Alex Clark and Contributors.
|
||||||
https://github.com/python-pillow/Pillow/
|
https://github.com/python-pillow/Pillow/
|
||||||
|
@ -24,8 +24,6 @@ PILLOW_VERSION = __version__ = _version.__version__
|
||||||
|
|
||||||
del _version
|
del _version
|
||||||
|
|
||||||
__doc__ = __doc__.format(__version__) # include version in docstring
|
|
||||||
|
|
||||||
|
|
||||||
_plugins = ['BlpImagePlugin',
|
_plugins = ['BlpImagePlugin',
|
||||||
'BmpImagePlugin',
|
'BmpImagePlugin',
|
||||||
|
|
|
@ -1998,6 +1998,7 @@ _getextrema(ImagingObject* self, PyObject* args)
|
||||||
UINT8 u[2];
|
UINT8 u[2];
|
||||||
INT32 i[2];
|
INT32 i[2];
|
||||||
FLOAT32 f[2];
|
FLOAT32 f[2];
|
||||||
|
UINT16 s[2];
|
||||||
} extrema;
|
} extrema;
|
||||||
int status;
|
int status;
|
||||||
|
|
||||||
|
@ -2013,6 +2014,10 @@ _getextrema(ImagingObject* self, PyObject* args)
|
||||||
return Py_BuildValue("ii", extrema.i[0], extrema.i[1]);
|
return Py_BuildValue("ii", extrema.i[0], extrema.i[1]);
|
||||||
case IMAGING_TYPE_FLOAT32:
|
case IMAGING_TYPE_FLOAT32:
|
||||||
return Py_BuildValue("dd", extrema.f[0], extrema.f[1]);
|
return Py_BuildValue("dd", extrema.f[0], extrema.f[1]);
|
||||||
|
case IMAGING_TYPE_SPECIAL:
|
||||||
|
if (strcmp(self->image->mode, "I;16") == 0) {
|
||||||
|
return Py_BuildValue("HH", extrema.s[0], extrema.s[1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Py_INCREF(Py_None);
|
Py_INCREF(Py_None);
|
||||||
|
@ -2697,22 +2702,6 @@ _draw_ellipse(ImagingDrawObject* self, PyObject* args)
|
||||||
return Py_None;
|
return Py_None;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject*
|
|
||||||
_draw_line(ImagingDrawObject* self, PyObject* args)
|
|
||||||
{
|
|
||||||
int x0, y0, x1, y1;
|
|
||||||
int ink;
|
|
||||||
if (!PyArg_ParseTuple(args, "(ii)(ii)i", &x0, &y0, &x1, &y1, &ink))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
if (ImagingDrawLine(self->image->image, x0, y0, x1, y1,
|
|
||||||
&ink, self->blend) < 0)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
Py_INCREF(Py_None);
|
|
||||||
return Py_None;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
_draw_lines(ImagingDrawObject* self, PyObject* args)
|
_draw_lines(ImagingDrawObject* self, PyObject* args)
|
||||||
{
|
{
|
||||||
|
@ -2766,21 +2755,6 @@ _draw_lines(ImagingDrawObject* self, PyObject* args)
|
||||||
return Py_None;
|
return Py_None;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject*
|
|
||||||
_draw_point(ImagingDrawObject* self, PyObject* args)
|
|
||||||
{
|
|
||||||
int x, y;
|
|
||||||
int ink;
|
|
||||||
if (!PyArg_ParseTuple(args, "(ii)i", &x, &y, &ink))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
if (ImagingDrawPoint(self->image->image, x, y, &ink, self->blend) < 0)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
Py_INCREF(Py_None);
|
|
||||||
return Py_None;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
_draw_points(ImagingDrawObject* self, PyObject* args)
|
_draw_points(ImagingDrawObject* self, PyObject* args)
|
||||||
{
|
{
|
||||||
|
@ -2961,14 +2935,12 @@ _draw_rectangle(ImagingDrawObject* self, PyObject* args)
|
||||||
static struct PyMethodDef _draw_methods[] = {
|
static struct PyMethodDef _draw_methods[] = {
|
||||||
#ifdef WITH_IMAGEDRAW
|
#ifdef WITH_IMAGEDRAW
|
||||||
/* Graphics (ImageDraw) */
|
/* Graphics (ImageDraw) */
|
||||||
{"draw_line", (PyCFunction)_draw_line, 1},
|
|
||||||
{"draw_lines", (PyCFunction)_draw_lines, 1},
|
{"draw_lines", (PyCFunction)_draw_lines, 1},
|
||||||
#ifdef WITH_ARROW
|
#ifdef WITH_ARROW
|
||||||
{"draw_outline", (PyCFunction)_draw_outline, 1},
|
{"draw_outline", (PyCFunction)_draw_outline, 1},
|
||||||
#endif
|
#endif
|
||||||
{"draw_polygon", (PyCFunction)_draw_polygon, 1},
|
{"draw_polygon", (PyCFunction)_draw_polygon, 1},
|
||||||
{"draw_rectangle", (PyCFunction)_draw_rectangle, 1},
|
{"draw_rectangle", (PyCFunction)_draw_rectangle, 1},
|
||||||
{"draw_point", (PyCFunction)_draw_point, 1},
|
|
||||||
{"draw_points", (PyCFunction)_draw_points, 1},
|
{"draw_points", (PyCFunction)_draw_points, 1},
|
||||||
{"draw_arc", (PyCFunction)_draw_arc, 1},
|
{"draw_arc", (PyCFunction)_draw_arc, 1},
|
||||||
{"draw_bitmap", (PyCFunction)_draw_bitmap, 1},
|
{"draw_bitmap", (PyCFunction)_draw_bitmap, 1},
|
||||||
|
|
|
@ -674,47 +674,6 @@ font_getsize(FontObject* self, PyObject* args)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject*
|
|
||||||
font_getabc(FontObject* self, PyObject* args)
|
|
||||||
{
|
|
||||||
FT_ULong ch;
|
|
||||||
FT_Face face;
|
|
||||||
double a, b, c;
|
|
||||||
|
|
||||||
/* calculate ABC values for a given string */
|
|
||||||
|
|
||||||
PyObject* string;
|
|
||||||
if (!PyArg_ParseTuple(args, "O:getabc", &string))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
#if PY_VERSION_HEX >= 0x03000000
|
|
||||||
if (!PyUnicode_Check(string)) {
|
|
||||||
#else
|
|
||||||
if (!PyUnicode_Check(string) && !PyString_Check(string)) {
|
|
||||||
#endif
|
|
||||||
PyErr_SetString(PyExc_TypeError, "expected string");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (font_getchar(string, 0, &ch)) {
|
|
||||||
int index, error;
|
|
||||||
face = self->face;
|
|
||||||
index = FT_Get_Char_Index(face, ch);
|
|
||||||
/* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960 */
|
|
||||||
error = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT|FT_LOAD_NO_BITMAP);
|
|
||||||
if (error)
|
|
||||||
return geterror(error);
|
|
||||||
a = face->glyph->metrics.horiBearingX / 64.0;
|
|
||||||
b = face->glyph->metrics.width / 64.0;
|
|
||||||
c = (face->glyph->metrics.horiAdvance -
|
|
||||||
face->glyph->metrics.horiBearingX -
|
|
||||||
face->glyph->metrics.width) / 64.0;
|
|
||||||
} else
|
|
||||||
a = b = c = 0.0;
|
|
||||||
|
|
||||||
return Py_BuildValue("ddd", a, b, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
font_render(FontObject* self, PyObject* args)
|
font_render(FontObject* self, PyObject* args)
|
||||||
{
|
{
|
||||||
|
@ -854,7 +813,6 @@ font_dealloc(FontObject* self)
|
||||||
static PyMethodDef font_methods[] = {
|
static PyMethodDef font_methods[] = {
|
||||||
{"render", (PyCFunction) font_render, METH_VARARGS},
|
{"render", (PyCFunction) font_render, METH_VARARGS},
|
||||||
{"getsize", (PyCFunction) font_getsize, METH_VARARGS},
|
{"getsize", (PyCFunction) font_getsize, METH_VARARGS},
|
||||||
{"getabc", (PyCFunction) font_getabc, METH_VARARGS},
|
|
||||||
{NULL, NULL}
|
{NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -216,6 +216,8 @@ PyObject* _anim_encoder_add(PyObject* self, PyObject* args)
|
||||||
WebPPictureImportRGBA(frame, rgb, 4 * width);
|
WebPPictureImportRGBA(frame, rgb, 4 * width);
|
||||||
} else if (strcmp(mode, "RGBX")==0) {
|
} else if (strcmp(mode, "RGBX")==0) {
|
||||||
WebPPictureImportRGBX(frame, rgb, 4 * width);
|
WebPPictureImportRGBX(frame, rgb, 4 * width);
|
||||||
|
} else {
|
||||||
|
WebPPictureImportRGB(frame, rgb, 3 * width);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the frame to the encoder
|
// Add the frame to the encoder
|
||||||
|
|
|
@ -797,6 +797,48 @@ unpackRGBa(UINT8* _out, const UINT8* in, int pixels)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
unpackRGBaskip1(UINT8* _out, const UINT8* in, int pixels)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
UINT32* out = (UINT32*) _out;
|
||||||
|
/* premultiplied RGBA */
|
||||||
|
for (i = 0; i < pixels; i++) {
|
||||||
|
int a = in[3];
|
||||||
|
if ( ! a) {
|
||||||
|
out[i] = 0;
|
||||||
|
} else if (a == 255) {
|
||||||
|
out[i] = MAKE_UINT32(in[0], in[1], in[2], a);
|
||||||
|
} else {
|
||||||
|
out[i] = MAKE_UINT32(CLIP8(in[0] * 255 / a),
|
||||||
|
CLIP8(in[1] * 255 / a),
|
||||||
|
CLIP8(in[2] * 255 / a), a);
|
||||||
|
}
|
||||||
|
in += 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
unpackRGBaskip2(UINT8* _out, const UINT8* in, int pixels)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
UINT32* out = (UINT32*) _out;
|
||||||
|
/* premultiplied RGBA */
|
||||||
|
for (i = 0; i < pixels; i++) {
|
||||||
|
int a = in[3];
|
||||||
|
if ( ! a) {
|
||||||
|
out[i] = 0;
|
||||||
|
} else if (a == 255) {
|
||||||
|
out[i] = MAKE_UINT32(in[0], in[1], in[2], a);
|
||||||
|
} else {
|
||||||
|
out[i] = MAKE_UINT32(CLIP8(in[0] * 255 / a),
|
||||||
|
CLIP8(in[1] * 255 / a),
|
||||||
|
CLIP8(in[2] * 255 / a), a);
|
||||||
|
}
|
||||||
|
in += 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
unpackBGRa(UINT8* _out, const UINT8* in, int pixels)
|
unpackBGRa(UINT8* _out, const UINT8* in, int pixels)
|
||||||
{
|
{
|
||||||
|
@ -1301,7 +1343,11 @@ static struct {
|
||||||
{"RGBA", "LA", 16, unpackRGBALA},
|
{"RGBA", "LA", 16, unpackRGBALA},
|
||||||
{"RGBA", "LA;16B", 32, unpackRGBALA16B},
|
{"RGBA", "LA;16B", 32, unpackRGBALA16B},
|
||||||
{"RGBA", "RGBA", 32, copy4},
|
{"RGBA", "RGBA", 32, copy4},
|
||||||
|
{"RGBA", "RGBAX", 40, copy4skip1},
|
||||||
|
{"RGBA", "RGBAXX", 48, copy4skip2},
|
||||||
{"RGBA", "RGBa", 32, unpackRGBa},
|
{"RGBA", "RGBa", 32, unpackRGBa},
|
||||||
|
{"RGBA", "RGBaX", 40, unpackRGBaskip1},
|
||||||
|
{"RGBA", "RGBaXX", 48, unpackRGBaskip2},
|
||||||
{"RGBA", "RGBa;16L", 64, unpackRGBa16L},
|
{"RGBA", "RGBa;16L", 64, unpackRGBa16L},
|
||||||
{"RGBA", "RGBa;16B", 64, unpackRGBa16B},
|
{"RGBA", "RGBa;16B", 64, unpackRGBa16B},
|
||||||
{"RGBA", "BGRa", 32, unpackBGRa},
|
{"RGBA", "BGRa", 32, unpackBGRa},
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
mkdir /var/cache/pacman/pkg
|
||||||
pacman -S --noconfirm mingw32/mingw-w64-i686-python3-pip \
|
pacman -S --noconfirm mingw32/mingw-w64-i686-python3-pip \
|
||||||
mingw32/mingw-w64-i686-python3-setuptools \
|
mingw32/mingw-w64-i686-python3-setuptools \
|
||||||
mingw32/mingw-w64-i686-python2-pip \
|
mingw32/mingw-w64-i686-python2-pip \
|
||||||
|
|