Merge branch 'master' into gitignore-review

This commit is contained in:
Andrew Murray 2018-09-29 07:26:46 +10:00 committed by GitHub
commit 33f1a2563e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
90 changed files with 1096 additions and 497 deletions

View File

@ -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.

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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]]

View File

@ -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))

View File

@ -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
""" """

View File

@ -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

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

View File

Before

Width:  |  Height:  |  Size: 232 B

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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"

View File

@ -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())

View File

@ -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:

View File

@ -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)

View File

@ -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")

View File

@ -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__':

View File

@ -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()

View File

@ -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")

View File

@ -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")

View File

@ -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

View File

@ -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')

View File

@ -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")

View File

@ -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__':

View File

@ -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

View File

@ -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()

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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))

View File

@ -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)

View File

@ -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)

View File

@ -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,

View File

@ -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

View File

@ -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")

View File

@ -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'))

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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). |
+-----------+-----------------------------------------------------------------+ +-----------+-----------------------------------------------------------------+

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -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:

View File

@ -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")

View File

@ -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()
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -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):

View File

@ -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")

View File

@ -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"):

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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):
""" """

View File

@ -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

View File

@ -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]')

View File

@ -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

View File

@ -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

View File

@ -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(

View File

@ -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])

View File

@ -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

View File

@ -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

View File

@ -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"))

View File

@ -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:

View File

@ -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),

View File

@ -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(

View File

@ -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")

View File

@ -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',

View File

@ -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},

View File

@ -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}
}; };

View File

@ -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

View File

@ -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},

View File

@ -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 \