Merge branch 'master' into gitignore-review
6
.github/ISSUE_TEMPLATE.md
vendored
|
@ -4,7 +4,11 @@
|
|||
|
||||
### 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.
|
||||
|
||||
|
|
|
@ -27,9 +27,11 @@ matrix:
|
|||
- python: '3.6'
|
||||
- python: '3.6'
|
||||
dist: trusty
|
||||
env: PYTHONOPTIMIZE=1
|
||||
- python: '3.5'
|
||||
- python: '3.5'
|
||||
dist: trusty
|
||||
env: PYTHONOPTIMIZE=2
|
||||
- python: '3.4'
|
||||
dist: trusty
|
||||
- env: DOCKER="alpine" DOCKER_TAG="pytest"
|
||||
|
|
|
@ -7,7 +7,7 @@ sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-tk\
|
|||
python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick\
|
||||
libharfbuzz-dev libfribidi-dev
|
||||
|
||||
pip install cffi
|
||||
PYTHONOPTIMIZE=0 pip install cffi
|
||||
pip install check-manifest
|
||||
pip install coverage
|
||||
pip install olefile
|
||||
|
|
80
CHANGES.rst
|
@ -5,6 +5,84 @@ Changelog (Pillow)
|
|||
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
|
||||
[radarhere]
|
||||
|
||||
|
@ -17,7 +95,7 @@ Changelog (Pillow)
|
|||
- Skip outline if the draw operation fills with the same colour #2922
|
||||
[radarhere]
|
||||
|
||||
- Flake8 fixes #3173
|
||||
- Flake8 fixes #3173, #3380
|
||||
[radarhere]
|
||||
|
||||
- Avoid deprecated 'U' mode when opening files #2187
|
||||
|
|
|
@ -89,7 +89,7 @@ Released as needed privately to individual vendors for critical security-related
|
|||
$ git clone https://github.com/python-pillow/pillow-wheels
|
||||
$ cd pillow-wheels
|
||||
$ git submodule init
|
||||
$ git submodule update
|
||||
$ git submodule update Pillow
|
||||
$ cd Pillow
|
||||
$ git fetch --all
|
||||
$ git checkout [[release tag]]
|
||||
|
|
|
@ -25,9 +25,8 @@ class TestImagingLeaks(PillowTestCase):
|
|||
if i < min_iterations:
|
||||
mem_limit = mem + 1
|
||||
continue
|
||||
self.assertLessEqual(mem, mem_limit,
|
||||
msg='memory usage limit exceeded after %d iterations'
|
||||
% (i + 1))
|
||||
msg = 'memory usage limit exceeded after %d iterations' % (i + 1)
|
||||
self.assertLessEqual(mem, mem_limit, msg)
|
||||
|
||||
def test_leak_putdata(self):
|
||||
im = Image.new('RGB', (25, 25))
|
||||
|
|
|
@ -9,8 +9,7 @@ iterations = 5000
|
|||
When run on a system without the jpeg leak fixes,
|
||||
the valgrind runs look like this.
|
||||
|
||||
NOSE_PROCESSES=0 NOSE_TIMEOUT=600 valgrind --tool=massif \
|
||||
python test-installed.py -s -v Tests/check_jpeg_leaks.py
|
||||
valgrind --tool=massif python test-installed.py -s -v Tests/check_jpeg_leaks.py
|
||||
|
||||
"""
|
||||
|
||||
|
|
|
@ -272,8 +272,8 @@ class PillowLeakTestCase(PillowTestCase):
|
|||
for cycle in range(self.iterations):
|
||||
core()
|
||||
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
|
||||
|
|
BIN
Tests/images/16_bit_noise.tif
Normal file
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 48 KiB |
BIN
Tests/images/imagedraw_floodfill_L.png
Normal file
After Width: | Height: | Size: 144 B |
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 232 B |
BIN
Tests/images/imagedraw_floodfill_RGBA.png
Normal file
After Width: | Height: | Size: 253 B |
BIN
Tests/images/imagedraw_line_joint_curve.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
Tests/images/imageops_pad_h_0.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Tests/images/imageops_pad_h_1.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Tests/images/imageops_pad_h_2.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Tests/images/imageops_pad_v_0.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Tests/images/imageops_pad_v_1.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Tests/images/imageops_pad_v_2.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
|
@ -52,10 +52,5 @@ for i0 in range(65556):
|
|||
|
||||
print()
|
||||
|
||||
# print(check(min_size, min_start))
|
||||
|
||||
print("#define ACCESS_TABLE_SIZE", min_size)
|
||||
print("#define ACCESS_TABLE_HASH", min_start)
|
||||
|
||||
# for m in modes:
|
||||
# print(m, "=>", hash(m, min_start) % min_size)
|
||||
|
|
|
@ -22,7 +22,6 @@ class TestBmpReference(PillowTestCase):
|
|||
im.load()
|
||||
except Exception: # as msg:
|
||||
pass
|
||||
# print("Bad Image %s: %s" %(f,msg))
|
||||
|
||||
def test_questionable(self):
|
||||
""" 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.load()
|
||||
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:
|
||||
if os.path.basename(f) in supported:
|
||||
raise
|
||||
# print("Bad Image %s: %s" %(f,msg))
|
||||
|
||||
def test_good(self):
|
||||
""" These should all work. There's a set of target files in the
|
||||
|
|
|
@ -61,6 +61,29 @@ class TestDecompressionCrop(PillowTestCase):
|
|||
self.assert_warning(Image.DecompressionBombWarning,
|
||||
self.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__':
|
||||
unittest.main()
|
||||
|
|
|
@ -2,8 +2,6 @@ from helper import unittest, PillowTestCase
|
|||
|
||||
from PIL import GdImageFile
|
||||
|
||||
import io
|
||||
|
||||
TEST_GD_FILE = "Tests/images/hopper.gd"
|
||||
|
||||
|
||||
|
|
|
@ -141,11 +141,9 @@ class TestFileJpeg(PillowTestCase):
|
|||
im = Image.open('Tests/images/icc_profile_big.jpg')
|
||||
f = self.tempfile("temp.jpg")
|
||||
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,
|
||||
icc_profile=icc_profile, optimize=True)
|
||||
except IOError:
|
||||
self.fail("Failed saving image with icc larger than image size")
|
||||
|
||||
def test_optimize(self):
|
||||
im1 = self.roundtrip(hopper())
|
||||
|
|
|
@ -231,6 +231,16 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
|
||||
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):
|
||||
i = Image.open('Tests/images/hopper_g4_500.tif')
|
||||
out = self.tempfile("temp.tif")
|
||||
|
@ -529,10 +539,8 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
im = Image.open(tmpfile)
|
||||
im.n_frames
|
||||
im.close()
|
||||
try:
|
||||
os.remove(tmpfile) # Windows PermissionError here!
|
||||
except:
|
||||
self.fail("Should not get permission error here")
|
||||
# Should not raise PermissionError.
|
||||
os.remove(tmpfile)
|
||||
|
||||
def test_read_icc(self):
|
||||
with Image.open("Tests/images/hopper.iccprofile.tif") as img:
|
||||
|
|
|
@ -53,10 +53,10 @@ class TestFileTga(PillowTestCase):
|
|||
# Generate a new test name every time so the
|
||||
# test will not fail with permission error
|
||||
# on Windows.
|
||||
test_file = self.tempfile("temp.tga")
|
||||
out = self.tempfile("temp.tga")
|
||||
|
||||
original_im.save(test_file, rle=rle)
|
||||
saved_im = Image.open(test_file)
|
||||
original_im.save(out, rle=rle)
|
||||
saved_im = Image.open(out)
|
||||
if rle:
|
||||
self.assertEqual(
|
||||
saved_im.info["compression"],
|
||||
|
@ -95,34 +95,93 @@ class TestFileTga(PillowTestCase):
|
|||
test_file = "Tests/images/tga_id_field.tga"
|
||||
im = Image.open(test_file)
|
||||
|
||||
test_file = self.tempfile("temp.tga")
|
||||
out = self.tempfile("temp.tga")
|
||||
|
||||
# Save
|
||||
im.save(test_file)
|
||||
test_im = Image.open(test_file)
|
||||
im.save(out)
|
||||
test_im = Image.open(out)
|
||||
self.assertEqual(test_im.size, (100, 100))
|
||||
self.assertEqual(test_im.info["id_section"], im.info["id_section"])
|
||||
|
||||
# RGBA save
|
||||
im.convert("RGBA").save(test_file)
|
||||
test_im = Image.open(test_file)
|
||||
im.convert("RGBA").save(out)
|
||||
test_im = Image.open(out)
|
||||
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):
|
||||
test_file = "Tests/images/rgb32rle.tga"
|
||||
im = Image.open(test_file)
|
||||
self.assertEqual(im.info["compression"], "tga_rle")
|
||||
|
||||
test_file = self.tempfile("temp.tga")
|
||||
out = self.tempfile("temp.tga")
|
||||
|
||||
# Save
|
||||
im.save(test_file)
|
||||
test_im = Image.open(test_file)
|
||||
im.save(out)
|
||||
test_im = Image.open(out)
|
||||
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
|
||||
im.convert("RGBA").save(test_file)
|
||||
test_im = Image.open(test_file)
|
||||
im.convert("RGBA").save(out)
|
||||
test_im = Image.open(out)
|
||||
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):
|
||||
# There are 559 transparent pixels in la.tga.
|
||||
num_transparent = 559
|
||||
|
@ -133,10 +192,10 @@ class TestFileTga(PillowTestCase):
|
|||
self.assertEqual(
|
||||
im.getchannel("A").getcolors()[0][0], num_transparent)
|
||||
|
||||
test_file = self.tempfile("temp.tga")
|
||||
im.save(test_file)
|
||||
out = self.tempfile("temp.tga")
|
||||
im.save(out)
|
||||
|
||||
test_im = Image.open(test_file)
|
||||
test_im = Image.open(out)
|
||||
self.assertEqual(test_im.mode, "LA")
|
||||
self.assertEqual(
|
||||
test_im.getchannel("A").getcolors()[0][0], num_transparent)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import logging
|
||||
from io import BytesIO
|
||||
import struct
|
||||
import sys
|
||||
|
||||
from helper import unittest, PillowTestCase, hopper
|
||||
|
@ -59,7 +58,8 @@ class TestFileTiff(PillowTestCase):
|
|||
|
||||
self.assertEqual(im.mode, "RGBA")
|
||||
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()
|
||||
|
||||
def test_set_legacy_api(self):
|
||||
|
@ -133,11 +133,8 @@ class TestFileTiff(PillowTestCase):
|
|||
|
||||
def test_bad_exif(self):
|
||||
i = Image.open('Tests/images/hopper_bad_exif.jpg')
|
||||
try:
|
||||
# Should not raise struct.error.
|
||||
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):
|
||||
im = hopper("RGBA")
|
||||
|
|
|
@ -56,7 +56,8 @@ class TestFileTiffMetadata(PillowTestCase):
|
|||
loaded = Image.open(f)
|
||||
|
||||
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_v2[ImageJMetaData], bindata)
|
||||
|
@ -75,8 +76,10 @@ class TestFileTiffMetadata(PillowTestCase):
|
|||
img.save(f, tiffinfo=info)
|
||||
loaded = Image.open(f)
|
||||
|
||||
self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], (8, len(bindata) - 8))
|
||||
self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], (8, len(bindata) - 8))
|
||||
self.assertEqual(loaded.tag[ImageJMetaDataByteCounts],
|
||||
(8, len(bindata) - 8))
|
||||
self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts],
|
||||
(8, len(bindata) - 8))
|
||||
|
||||
def test_read_metadata(self):
|
||||
img = Image.open('Tests/images/hopper_g4.tif')
|
||||
|
@ -133,8 +136,8 @@ class TestFileTiffMetadata(PillowTestCase):
|
|||
if isinstance(v, IFDRational):
|
||||
original[k] = IFDRational(*_limit_rational(v, 2**31))
|
||||
if isinstance(v, tuple) and isinstance(v[0], IFDRational):
|
||||
original[k] = tuple([IFDRational(
|
||||
*_limit_rational(elt, 2**31)) for elt in v])
|
||||
original[k] = tuple([IFDRational(*_limit_rational(elt, 2**31))
|
||||
for elt in v])
|
||||
|
||||
ignored = ['StripByteCounts', 'RowsPerStrip',
|
||||
'PageNumber', 'StripOffsets']
|
||||
|
@ -169,10 +172,8 @@ class TestFileTiffMetadata(PillowTestCase):
|
|||
f = io.BytesIO(b'II*\x00\x08\x00\x00\x00')
|
||||
head = f.read(8)
|
||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||
try:
|
||||
# Should not raise struct.error.
|
||||
self.assert_warning(UserWarning, info.load, f)
|
||||
except struct.error:
|
||||
self.fail("Should not be struct errors there.")
|
||||
|
||||
def test_iccprofile(self):
|
||||
# https://github.com/python-pillow/Pillow/issues/1462
|
||||
|
@ -186,7 +187,8 @@ class TestFileTiffMetadata(PillowTestCase):
|
|||
|
||||
def test_iccprofile_binary(self):
|
||||
# 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')
|
||||
self.assertEqual(im.tag_v2.tagtype[34675], 1)
|
||||
|
@ -223,10 +225,8 @@ class TestFileTiffMetadata(PillowTestCase):
|
|||
head = data.read(8)
|
||||
info = TiffImagePlugin.ImageFileDirectory_v2(head)
|
||||
info.load(data)
|
||||
try:
|
||||
# Should not raise ValueError.
|
||||
info = dict(info)
|
||||
except ValueError:
|
||||
self.fail("Should not be struct value error there.")
|
||||
self.assertIn(33432, info)
|
||||
|
||||
def test_PhotoshopInfo(self):
|
||||
|
@ -245,10 +245,8 @@ class TestFileTiffMetadata(PillowTestCase):
|
|||
ifd._tagdata[277] = struct.pack('hh', 4, 4)
|
||||
ifd.tagtype[277] = TiffTags.SHORT
|
||||
|
||||
try:
|
||||
# Should not raise ValueError.
|
||||
self.assert_warning(UserWarning, lambda: ifd[277])
|
||||
except ValueError:
|
||||
self.fail("Invalid Metadata count should not cause a Value Error.")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -16,8 +16,7 @@ class TestFileWebp(PillowTestCase):
|
|||
self.skipTest('WebP support not installed')
|
||||
return
|
||||
|
||||
# WebPAnimDecoder only returns RGBA or RGBX, never RGB
|
||||
self.rgb_mode = "RGBX" if _webp.HAVE_WEBPANIM else "RGB"
|
||||
self.rgb_mode = "RGB"
|
||||
|
||||
def test_version(self):
|
||||
_webp.WebPDecoderVersion()
|
||||
|
@ -29,8 +28,7 @@ class TestFileWebp(PillowTestCase):
|
|||
Does it have the bits we expect?
|
||||
"""
|
||||
|
||||
file_path = "Tests/images/hopper.webp"
|
||||
image = Image.open(file_path)
|
||||
image = Image.open("Tests/images/hopper.webp")
|
||||
|
||||
self.assertEqual(image.mode, self.rgb_mode)
|
||||
self.assertEqual(image.size, (128, 128))
|
||||
|
@ -40,9 +38,8 @@ class TestFileWebp(PillowTestCase):
|
|||
|
||||
# generated with:
|
||||
# dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm
|
||||
target = Image.open('Tests/images/hopper_webp_bits.ppm')
|
||||
target = target.convert(self.rgb_mode)
|
||||
self.assert_image_similar(image, target, 20.0)
|
||||
self.assert_image_similar_tofile(
|
||||
image, 'Tests/images/hopper_webp_bits.ppm', 1.0)
|
||||
|
||||
def test_write_rgb(self):
|
||||
"""
|
||||
|
@ -61,13 +58,9 @@ class TestFileWebp(PillowTestCase):
|
|||
image.load()
|
||||
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
|
||||
# target = Image.open('Tests/images/hopper_webp_write.ppm')
|
||||
# self.assert_image_equal(image, target)
|
||||
self.assert_image_similar_tofile(
|
||||
image, 'Tests/images/hopper_webp_write.ppm', 12.0)
|
||||
|
||||
# This test asserts that the images are similar. If the average pixel
|
||||
# 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.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__':
|
||||
unittest.main()
|
||||
|
|
|
@ -19,8 +19,7 @@ class TestFileWebpLossless(PillowTestCase):
|
|||
if (_webp.WebPDecoderVersion() < 0x0200):
|
||||
self.skipTest('lossless not included')
|
||||
|
||||
# WebPAnimDecoder only returns RGBA or RGBX, never RGB
|
||||
self.rgb_mode = "RGBX" if _webp.HAVE_WEBPANIM else "RGB"
|
||||
self.rgb_mode = "RGB"
|
||||
|
||||
def test_write_lossless_rgb(self):
|
||||
temp_file = self.tempfile("temp.webp")
|
||||
|
|
|
@ -47,10 +47,6 @@ class TestFormatHSV(PillowTestCase):
|
|||
|
||||
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
|
||||
|
||||
def to_xxx_colorsys(self, im, func, mode):
|
||||
|
@ -95,15 +91,6 @@ class TestFormatHSV(PillowTestCase):
|
|||
im = src.convert('HSV')
|
||||
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),
|
||||
1, "Hue conversion is wrong")
|
||||
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),
|
||||
1, "Value conversion is wrong")
|
||||
|
||||
# print(im.getpixel((192, 64)))
|
||||
|
||||
comparable = src
|
||||
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),
|
||||
3, "R conversion is wrong")
|
||||
self.assert_image_similar(im.getchannel(1), comparable.getchannel(1),
|
||||
|
@ -132,12 +112,6 @@ class TestFormatHSV(PillowTestCase):
|
|||
im = hopper('RGB').convert('HSV')
|
||||
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),
|
||||
1, "Hue conversion is wrong")
|
||||
self.assert_image_similar(im.getchannel(1), comparable.getchannel(1),
|
||||
|
@ -150,12 +124,6 @@ class TestFormatHSV(PillowTestCase):
|
|||
converted = comparable.convert('RGB')
|
||||
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),
|
||||
comparable.getchannel(0),
|
||||
3, "R conversion is wrong")
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
from helper import unittest, PillowTestCase, hopper, on_appveyor
|
||||
|
||||
try:
|
||||
from PIL import PyAccess
|
||||
except ImportError:
|
||||
# Skip in setUp()
|
||||
pass
|
||||
|
||||
from PIL import Image
|
||||
import sys
|
||||
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):
|
||||
# initial value
|
||||
|
@ -113,38 +118,20 @@ class TestImageGetPixel(AccessTest):
|
|||
self.check(mode, 2**16-1)
|
||||
|
||||
|
||||
@unittest.skipIf(cffi is None, "No cffi")
|
||||
class TestCffiPutPixel(TestImagePutPixel):
|
||||
_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):
|
||||
_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):
|
||||
_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):
|
||||
"""Do we get the same thing as the old pixel access
|
||||
|
||||
|
|
|
@ -187,6 +187,7 @@ class TestImageConvert(PillowTestCase):
|
|||
def matrix_convert(mode):
|
||||
# Arrange
|
||||
im = hopper('RGB')
|
||||
im.info['transparency'] = (255, 0, 0)
|
||||
matrix = (
|
||||
0.412453, 0.357580, 0.180423, 0,
|
||||
0.212671, 0.715160, 0.072169, 0,
|
||||
|
@ -203,9 +204,12 @@ class TestImageConvert(PillowTestCase):
|
|||
target = Image.open('Tests/images/hopper-XYZ.png')
|
||||
if converted_im.mode == 'RGB':
|
||||
self.assert_image_similar(converted_im, target, 3)
|
||||
self.assertEqual(converted_im.info['transparency'],
|
||||
(105, 54, 4))
|
||||
else:
|
||||
self.assert_image_similar(converted_im,
|
||||
target.getchannel(0), 1)
|
||||
self.assertEqual(converted_im.info['transparency'], 105)
|
||||
|
||||
matrix_convert('RGB')
|
||||
matrix_convert('L')
|
||||
|
|
|
@ -94,6 +94,15 @@ class TestImageFilter(PillowTestCase):
|
|||
self.assertEqual(rankfilter.size, 1)
|
||||
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):
|
||||
source = Image.open("Tests/images/hopper.bmp")
|
||||
reference = Image.open("Tests/images/hopper_emboss.bmp")
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from PIL import Image
|
||||
from helper import unittest, PillowTestCase, hopper
|
||||
|
||||
|
||||
|
@ -19,6 +20,13 @@ class TestImageGetExtrema(PillowTestCase):
|
|||
extrema("RGBA"), ((0, 255), (0, 255), (0, 255), (255, 255)))
|
||||
self.assertEqual(
|
||||
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__':
|
||||
|
|
|
@ -352,10 +352,8 @@ class CoreResamplePassesTest(PillowTestCase):
|
|||
class CoreResampleCoefficientsTest(PillowTestCase):
|
||||
def test_reduce(self):
|
||||
test_color = 254
|
||||
# print()
|
||||
|
||||
for size in range(400000, 400010, 2):
|
||||
# print(size)
|
||||
i = Image.new('L', (size, 1), 0)
|
||||
draw = ImageDraw.Draw(i)
|
||||
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()
|
||||
if px[2, 0] != test_color // 2:
|
||||
self.assertEqual(test_color // 2, px[2, 0])
|
||||
# print('>', size, test_color // 2, px[2, 0])
|
||||
|
||||
def test_nonzero_coefficients(self):
|
||||
# regression test for the wrong coefficients calculation
|
||||
|
|
|
@ -124,5 +124,6 @@ class TestImageRotate(PillowTestCase):
|
|||
corner = im.getpixel((0, 0))
|
||||
self.assertEqual(corner, (255, 0, 0, 255))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -3,6 +3,16 @@ from helper import unittest, PillowTestCase, hopper
|
|||
from PIL import Image
|
||||
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):
|
||||
|
||||
|
@ -35,6 +45,303 @@ class TestImageChops(PillowTestCase):
|
|||
ImageChops.offset(im, 10)
|
||||
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 table(op, a, b):
|
||||
|
|
|
@ -446,20 +446,20 @@ class TestImageCms(PillowTestCase):
|
|||
self.assert_image_equal(source_image_aux, result_image_aux)
|
||||
|
||||
def test_preserve_auxiliary_channels_rgba(self):
|
||||
self.assert_aux_channel_preserved(mode='RGBA',
|
||||
transform_in_place=False, preserved_channel='A')
|
||||
self.assert_aux_channel_preserved(
|
||||
mode='RGBA', transform_in_place=False, preserved_channel='A')
|
||||
|
||||
def test_preserve_auxiliary_channels_rgba_in_place(self):
|
||||
self.assert_aux_channel_preserved(mode='RGBA',
|
||||
transform_in_place=True, preserved_channel='A')
|
||||
self.assert_aux_channel_preserved(
|
||||
mode='RGBA', transform_in_place=True, preserved_channel='A')
|
||||
|
||||
def test_preserve_auxiliary_channels_rgbx(self):
|
||||
self.assert_aux_channel_preserved(mode='RGBX',
|
||||
transform_in_place=False, preserved_channel='X')
|
||||
self.assert_aux_channel_preserved(
|
||||
mode='RGBX', transform_in_place=False, preserved_channel='X')
|
||||
|
||||
def test_preserve_auxiliary_channels_rgbx_in_place(self):
|
||||
self.assert_aux_channel_preserved(mode='RGBX',
|
||||
transform_in_place=True, preserved_channel='X')
|
||||
self.assert_aux_channel_preserved(
|
||||
mode='RGBX', transform_in_place=True, preserved_channel='X')
|
||||
|
||||
def test_auxiliary_channels_isolated(self):
|
||||
# test data in aux channels does not affect non-aux channels
|
||||
|
|
|
@ -344,18 +344,25 @@ class TestImageDraw(PillowTestCase):
|
|||
self.assert_image_similar(im, Image.open(expected), 1)
|
||||
|
||||
def test_floodfill(self):
|
||||
red = ImageColor.getrgb("red")
|
||||
|
||||
for mode, value in [
|
||||
("L", 1),
|
||||
("RGBA", (255, 0, 0, 0)),
|
||||
("RGB", red)
|
||||
]:
|
||||
# Arrange
|
||||
im = Image.new("RGB", (W, H))
|
||||
im = Image.new(mode, (W, H))
|
||||
draw = ImageDraw.Draw(im)
|
||||
draw.rectangle(BBOX2, outline="yellow", fill="green")
|
||||
centre_point = (int(W/2), int(H/2))
|
||||
red = ImageColor.getrgb("red")
|
||||
im_floodfill = Image.open("Tests/images/imagedraw_floodfill.png")
|
||||
|
||||
# Act
|
||||
ImageDraw.floodfill(im, centre_point, red)
|
||||
ImageDraw.floodfill(im, centre_point, value)
|
||||
|
||||
# Assert
|
||||
expected = "Tests/images/imagedraw_floodfill_"+mode+".png"
|
||||
im_floodfill = Image.open(expected)
|
||||
self.assert_image_equal(im, im_floodfill)
|
||||
|
||||
# Test that using the same colour does not change the image
|
||||
|
@ -366,6 +373,11 @@ class TestImageDraw(PillowTestCase):
|
|||
ImageDraw.floodfill(im, (W, H), red)
|
||||
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):
|
||||
# floodfill() is experimental
|
||||
|
||||
|
@ -563,6 +575,20 @@ class TestImageDraw(PillowTestCase):
|
|||
# Assert
|
||||
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):
|
||||
# https://github.com/python-pillow/Pillow/issues/2783
|
||||
# Arrange
|
||||
|
|
|
@ -215,7 +215,7 @@ class TestImageFont(PillowTestCase):
|
|||
|
||||
# Act/Assert
|
||||
self.assertRaises(
|
||||
AssertionError,
|
||||
ValueError,
|
||||
draw.multiline_text, (0, 0), TEST_TEXT, font=ttf, align="unknown")
|
||||
|
||||
def test_draw_align(self):
|
||||
|
|
|
@ -24,6 +24,9 @@ class TestImageOps(PillowTestCase):
|
|||
ImageOps.colorize(hopper("L"), (0, 0, 0), (255, 255, 255))
|
||||
ImageOps.colorize(hopper("L"), "black", "white")
|
||||
|
||||
ImageOps.pad(hopper("L"), (128, 128))
|
||||
ImageOps.pad(hopper("RGB"), (128, 128))
|
||||
|
||||
ImageOps.crop(hopper("L"), 1)
|
||||
ImageOps.crop(hopper("RGB"), 1)
|
||||
|
||||
|
@ -70,6 +73,26 @@ class TestImageOps(PillowTestCase):
|
|||
newimg = ImageOps.fit(hopper("RGB").resize((100, 1)), (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):
|
||||
# Division by zero in equalize if < 255 pixels in image (@PIL163)
|
||||
|
||||
|
|
|
@ -33,6 +33,8 @@ class PillowQPixmapTestCase(PillowQtTestCase):
|
|||
from PyQt4.QtGui import QGuiApplication
|
||||
elif ImageQt.qt_version == 'side':
|
||||
from PySide.QtGui import QGuiApplication
|
||||
elif ImageQt.qt_version == 'side2':
|
||||
from PySide2.QtGui import QGuiApplication
|
||||
except ImportError:
|
||||
self.skipTest('QGuiApplication not installed')
|
||||
|
||||
|
@ -56,6 +58,8 @@ class TestImageQt(PillowQtTestCase, PillowTestCase):
|
|||
from PyQt4.QtGui import qRgb
|
||||
elif ImageQt.qt_version == 'side':
|
||||
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))
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ class TestImageTk(PillowTestCase):
|
|||
self.skipTest("Tk not installed")
|
||||
try:
|
||||
# setup tk
|
||||
app = tk.Frame()
|
||||
tk.Frame()
|
||||
# root = tk.Tk()
|
||||
except (tk.TclError) as v:
|
||||
self.skipTest("TCL Error: %s" % v)
|
||||
|
|
|
@ -96,7 +96,6 @@ if sys.platform.startswith('win32'):
|
|||
hdr.biClrImportant = 0
|
||||
|
||||
hdc = CreateCompatibleDC(None)
|
||||
# print('hdc:',hex(hdc))
|
||||
pixels = ctypes.c_void_p()
|
||||
dib = CreateDIBSection(hdc, ctypes.byref(hdr), DIB_RGB_COLORS,
|
||||
ctypes.byref(pixels), None, 0)
|
||||
|
|
|
@ -221,7 +221,8 @@ class TestLibUnpack(PillowTestCase):
|
|||
data_len = data * len(pixels)
|
||||
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):
|
||||
self.assertEqual(pixel, im.getpixel((x, 0)))
|
||||
|
@ -265,9 +266,11 @@ class TestLibUnpack(PillowTestCase):
|
|||
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;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;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;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))
|
||||
self.assert_unpack(
|
||||
"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(
|
||||
"RGBA", "RGBa", 4,
|
||||
(63, 127, 191, 4), (159, 191, 223, 8), (191, 212, 233, 12))
|
||||
self.assert_unpack(
|
||||
"RGBA", "RGBa",
|
||||
b'\x01\x02\x03\x00\x10\x20\x30\xff',
|
||||
(0, 0, 0, 0), (16, 32, 48, 255))
|
||||
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", "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(
|
||||
"RGBA", "RGBa;16L", 8,
|
||||
(63, 127, 191, 8), (159, 191, 223, 16), (191, 212, 233, 24))
|
||||
|
@ -361,7 +376,8 @@ class TestLibUnpack(PillowTestCase):
|
|||
self.assert_unpack(
|
||||
"RGBA", "YCCA;P",
|
||||
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(
|
||||
"RGBA", "R", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0))
|
||||
self.assert_unpack(
|
||||
|
@ -413,7 +429,8 @@ class TestLibUnpack(PillowTestCase):
|
|||
self.assert_unpack(
|
||||
"RGBX", "YCC;P",
|
||||
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,
|
||||
(1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0))
|
||||
self.assert_unpack("RGBX", "G", 1,
|
||||
|
|
|
@ -34,7 +34,6 @@ class TestNumpy(PillowTestCase):
|
|||
i = Image.fromarray(a)
|
||||
if list(i.getchannel(0).getdata()) != list(range(100)):
|
||||
print("data mismatch for", dtype)
|
||||
# print(dtype, list(i.getdata()))
|
||||
return i
|
||||
|
||||
# Check supported 1-bit integer formats
|
||||
|
|
|
@ -26,7 +26,9 @@ class TestPdfParser(PillowTestCase):
|
|||
def test_parsing(self):
|
||||
self.assertEqual(PdfParser.interpret_name(b"Name#23Hash"),
|
||||
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),
|
||||
(IndirectReference(1, 2), 5))
|
||||
self.assertEqual(PdfParser.get_value(b"true[", 0), (True, 4))
|
||||
|
@ -72,7 +74,9 @@ class TestPdfParser(PillowTestCase):
|
|||
self.assertIsInstance(a, list)
|
||||
self.assertEqual(len(a), 4)
|
||||
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.assertEqual(s.dictionary.Name, "value")
|
||||
self.assertEqual(s.decode(), b"abcde")
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from helper import unittest, PillowTestCase, hopper
|
||||
from test_imageqt import PillowQtTestCase, PillowQPixmapTestCase
|
||||
from test_imageqt import PillowQPixmapTestCase
|
||||
|
||||
from PIL import ImageQt
|
||||
|
||||
|
@ -7,7 +7,6 @@ from PIL import ImageQt
|
|||
class TestFromQPixmap(PillowQPixmapTestCase, PillowTestCase):
|
||||
|
||||
def roundtrip(self, expected):
|
||||
PillowQtTestCase.setUp(self)
|
||||
result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected))
|
||||
# Qt saves all pixmaps as rgb
|
||||
self.assert_image_equal(result, expected.convert('RGB'))
|
||||
|
|
|
@ -11,21 +11,28 @@ if ImageQt.qt_is_installed:
|
|||
from PyQt5 import QtGui
|
||||
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QApplication
|
||||
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):
|
||||
try:
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4.QtGui import QWidget, QHBoxLayout, QLabel, QApplication
|
||||
from PyQt4.QtGui import QWidget, QHBoxLayout, QLabel, \
|
||||
QApplication
|
||||
QT_VERSION = 4
|
||||
except (ImportError, RuntimeError):
|
||||
from PySide import QtGui
|
||||
from PySide.QtGui import QWidget, QHBoxLayout, QLabel, QApplication
|
||||
from PySide.QtGui import QWidget, QHBoxLayout, QLabel, \
|
||||
QApplication
|
||||
QT_VERSION = 4
|
||||
|
||||
|
||||
class TestToQImage(PillowQtTestCase, PillowTestCase):
|
||||
|
||||
def test_sanity(self):
|
||||
PillowQtTestCase.setUp(self)
|
||||
for mode in ('RGB', 'RGBA', 'L', 'P', '1'):
|
||||
src = hopper(mode)
|
||||
data = ImageQt.toqimage(src)
|
||||
|
@ -61,8 +68,6 @@ class TestToQImage(PillowQtTestCase, PillowTestCase):
|
|||
self.assert_image_equal(reloaded, src)
|
||||
|
||||
def test_segfault(self):
|
||||
PillowQtTestCase.setUp(self)
|
||||
|
||||
app = QApplication([])
|
||||
ex = Example()
|
||||
assert(app) # Silence warning
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from helper import unittest, PillowTestCase, hopper
|
||||
from test_imageqt import PillowQtTestCase, PillowQPixmapTestCase
|
||||
from test_imageqt import PillowQPixmapTestCase
|
||||
|
||||
from PIL import ImageQt
|
||||
|
||||
|
@ -10,8 +10,6 @@ if ImageQt.qt_is_installed:
|
|||
class TestToQPixmap(PillowQPixmapTestCase, PillowTestCase):
|
||||
|
||||
def test_sanity(self):
|
||||
PillowQtTestCase.setUp(self)
|
||||
|
||||
for mode in ('1', 'RGB', 'RGBA', 'L', 'P'):
|
||||
data = ImageQt.toqpixmap(hopper(mode))
|
||||
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
#!/bin/bash
|
||||
# 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 || svn_checkout retry || svn_checkout retry || svn_checkout retry
|
||||
|
||||
cp -r test_images/* ../Tests/images
|
||||
|
||||
|
|
|
@ -171,7 +171,6 @@ The fields are used as follows:
|
|||
stride defaults to 0.
|
||||
|
||||
**orientation**
|
||||
|
||||
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.
|
||||
|
||||
|
@ -204,7 +203,7 @@ table describes some commonly used **raw modes**:
|
|||
+-----------+-----------------------------------------------------------------+
|
||||
| ``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). |
|
||||
+-----------+-----------------------------------------------------------------+
|
||||
|
||||
|
|
|
@ -171,19 +171,22 @@ Methods
|
|||
:param outline: Color to use for the outline.
|
||||
: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.
|
||||
|
||||
:param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or
|
||||
numeric values like ``[x, y, x, y, ...]``.
|
||||
:param fill: Color to use for the line.
|
||||
:param width: The line width, in pixels. Note that line
|
||||
joins are not handled well, so wide polylines will not look good.
|
||||
:param width: The line width, in pixels.
|
||||
|
||||
.. versionadded:: 1.1.5
|
||||
|
||||
.. 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)
|
||||
|
||||
|
@ -250,9 +253,8 @@ Methods
|
|||
:param align: If the text is passed on to multiline_text(),
|
||||
"left", "center" or "right".
|
||||
:param direction: Direction of the text. It can be 'rtl' (right to
|
||||
left), 'ltr' (left to right), 'ttb' (top to
|
||||
bottom) or 'btt' (bottom to top). Requires
|
||||
libraqm.
|
||||
left), 'ltr' (left to right) or 'ttb' (top to bottom).
|
||||
Requires libraqm.
|
||||
|
||||
.. versionadded:: 4.2.0
|
||||
|
||||
|
@ -280,9 +282,8 @@ Methods
|
|||
:param spacing: The number of pixels between lines.
|
||||
:param align: "left", "center" or "right".
|
||||
:param direction: Direction of the text. It can be 'rtl' (right to
|
||||
left), 'ltr' (left to right), 'ttb' (top to
|
||||
bottom) or 'btt' (bottom to top). Requires
|
||||
libraqm.
|
||||
left), 'ltr' (left to right) or 'ttb' (top to bottom).
|
||||
Requires libraqm.
|
||||
|
||||
.. versionadded:: 4.2.0
|
||||
|
||||
|
@ -309,9 +310,8 @@ Methods
|
|||
:param spacing: If the text is passed on to multiline_textsize(),
|
||||
the number of pixels between lines.
|
||||
:param direction: Direction of the text. It can be 'rtl' (right to
|
||||
left), 'ltr' (left to right), 'ttb' (top to
|
||||
bottom) or 'btt' (bottom to top). Requires
|
||||
libraqm.
|
||||
left), 'ltr' (left to right) or 'ttb' (top to bottom).
|
||||
Requires libraqm.
|
||||
|
||||
.. versionadded:: 4.2.0
|
||||
|
||||
|
@ -336,9 +336,8 @@ Methods
|
|||
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
|
||||
:param spacing: The number of pixels between lines.
|
||||
:param direction: Direction of the text. It can be 'rtl' (right to
|
||||
left), 'ltr' (left to right), 'ttb' (top to
|
||||
bottom) or 'btt' (bottom to top). Requires
|
||||
libraqm.
|
||||
left), 'ltr' (left to right) or 'ttb' (top to bottom).
|
||||
Requires libraqm.
|
||||
|
||||
.. versionadded:: 4.2.0
|
||||
|
||||
|
|
|
@ -67,9 +67,8 @@ Methods
|
|||
.. versionadded:: 1.1.5
|
||||
|
||||
:param direction: Direction of the text. It can be 'rtl' (right to
|
||||
left), 'ltr' (left to right), 'ttb' (top to
|
||||
bottom) or 'btt' (bottom to top). Requires
|
||||
libraqm.
|
||||
left), 'ltr' (left to right) or 'ttb' (top to bottom).
|
||||
Requires libraqm.
|
||||
|
||||
.. versionadded:: 4.2.0
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
:py:mod:`ImageQt` Module
|
||||
========================
|
||||
|
||||
The :py:mod:`ImageQt` module contains support for creating PyQt4, PyQt5 or
|
||||
PySide QImage objects from PIL images.
|
||||
The :py:mod:`ImageQt` module contains support for creating PyQt4, PyQt5, PySide or
|
||||
PySide2 QImage objects from PIL images.
|
||||
|
||||
.. versionadded:: 1.1.6
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# A monkey patch of the base distutils.ccompiler to use parallel builds
|
||||
# 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 multiprocessing import Pool, cpu_count
|
||||
|
@ -77,4 +79,6 @@ def install():
|
|||
"%s processes" % MAX_PROCS)
|
||||
|
||||
|
||||
# We monkeypatch only versions earlier than 3.5
|
||||
if sys.version_info < (3, 5):
|
||||
install()
|
||||
|
|
12
setup.py
|
@ -205,6 +205,12 @@ class pil_build_ext(build_ext):
|
|||
if self.debug:
|
||||
global DEBUG
|
||||
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:
|
||||
if getattr(self, 'disable_%s' % x):
|
||||
setattr(self.feature, x, False)
|
||||
|
@ -518,10 +524,7 @@ class pil_build_ext(build_ext):
|
|||
if _find_include_file(self, 'tiff.h'):
|
||||
if _find_library_file(self, "tiff"):
|
||||
feature.tiff = "tiff"
|
||||
if (sys.platform == "win32" and
|
||||
_find_library_file(self, "libtiff")):
|
||||
feature.tiff = "libtiff"
|
||||
if (sys.platform == "darwin" and
|
||||
if (sys.platform in ["win32", "darwin"] and
|
||||
_find_library_file(self, "libtiff")):
|
||||
feature.tiff = "libtiff"
|
||||
|
||||
|
@ -547,7 +550,6 @@ class pil_build_ext(build_ext):
|
|||
break
|
||||
if freetype_version:
|
||||
feature.freetype = "freetype"
|
||||
feature.freetype_version = freetype_version
|
||||
if subdir:
|
||||
_add_directory(self.compiler.include_dirs, subdir, 0)
|
||||
|
||||
|
|
|
@ -110,20 +110,6 @@ class BdfFontFile(FontFile.FontFile):
|
|||
if s.find(b"LogicalFontDescription") < 0:
|
||||
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:
|
||||
c = bdf_char(fp)
|
||||
if not c:
|
||||
|
|
|
@ -56,14 +56,6 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
|
|||
m = s
|
||||
elif i8(s[0]) > i8(m[0]) and i8(s[1]) > i8(m[1]):
|
||||
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:
|
||||
raise TypeError("No cursors were found")
|
||||
|
||||
|
|
|
@ -82,7 +82,6 @@ def Ghostscript(tile, size, fp, scale=1):
|
|||
# resolution is dependent on bbox and size
|
||||
res = (float((72.0 * size[0]) / (bbox[2]-bbox[0])),
|
||||
float((72.0 * size[1]) / (bbox[3]-bbox[1])))
|
||||
# print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res)
|
||||
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
@ -357,22 +356,14 @@ def _save(im, fp, filename, eps=1):
|
|||
else:
|
||||
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
|
||||
wrapped_fp = False
|
||||
if fp != sys.stdout:
|
||||
fp = NoCloseStream(fp)
|
||||
if sys.version_info.major > 2:
|
||||
fp = io.TextIOWrapper(fp, encoding='latin-1')
|
||||
wrapped_fp = True
|
||||
|
||||
try:
|
||||
if eps:
|
||||
#
|
||||
# write EPS header
|
||||
|
@ -405,6 +396,9 @@ def _save(im, fp, filename, eps=1):
|
|||
fp.write("grestore end\n")
|
||||
if hasattr(fp, "flush"):
|
||||
fp.flush()
|
||||
finally:
|
||||
if wrapped_fp:
|
||||
fp.detach()
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
|
|
@ -90,7 +90,6 @@ class FontFile(object):
|
|||
x = xx
|
||||
s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0
|
||||
self.bitmap.paste(im.crop(src), s)
|
||||
# print(chr(i), dst, s)
|
||||
self.metrics[i] = d, dst, s
|
||||
|
||||
def save(self, filename):
|
||||
|
|
|
@ -114,8 +114,6 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
if id in prop:
|
||||
self.jpeg[i] = prop[id]
|
||||
|
||||
# print(len(self.jpeg), "tables loaded")
|
||||
|
||||
self._open_subimage(1, self.maxid)
|
||||
|
||||
def _open_subimage(self, index=1, subimage=0):
|
||||
|
@ -143,8 +141,6 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
offset = i32(s, 28)
|
||||
length = i32(s, 32)
|
||||
|
||||
# print(size, self.mode, self.rawmode)
|
||||
|
||||
if size != self.size:
|
||||
raise IOError("subimage mismatch")
|
||||
|
||||
|
|
|
@ -61,7 +61,8 @@ class GdImageFile(ImageFile.ImageFile):
|
|||
|
||||
self.palette = ImagePalette.raw("XBGR", s[7+trueColorOffset+4:7+trueColorOffset+4+256*4])
|
||||
|
||||
self.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"):
|
||||
|
|
|
@ -443,7 +443,6 @@ def _getdecoder(mode, decoder_name, args, extra=()):
|
|||
try:
|
||||
# get decoder
|
||||
decoder = getattr(core, decoder_name + "_decoder")
|
||||
# print(decoder, mode, args + extra)
|
||||
return decoder(mode, *args + extra)
|
||||
except AttributeError:
|
||||
raise IOError("decoder %s not available" % decoder_name)
|
||||
|
@ -465,7 +464,6 @@ def _getencoder(mode, encoder_name, args, extra=()):
|
|||
try:
|
||||
# get encoder
|
||||
encoder = getattr(core, encoder_name + "_encoder")
|
||||
# print(encoder, mode, args + extra)
|
||||
return encoder(mode, *args + extra)
|
||||
except AttributeError:
|
||||
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):
|
||||
return self.copy()
|
||||
|
||||
has_transparency = self.info.get('transparency') is not None
|
||||
if matrix:
|
||||
# matrix conversion
|
||||
if mode not in ("L", "RGB"):
|
||||
raise ValueError("illegal conversion")
|
||||
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":
|
||||
return self.quantize(colors)
|
||||
|
@ -913,8 +927,7 @@ class Image(object):
|
|||
trns = None
|
||||
delete_trns = False
|
||||
# transparency handling
|
||||
if "transparency" in self.info and \
|
||||
self.info['transparency'] is not None:
|
||||
if has_transparency:
|
||||
if self.mode in ('L', 'RGB') and mode == 'RGBA':
|
||||
# Use transparent conversion to promote from transparent
|
||||
# color to an alpha channel.
|
||||
|
@ -1104,12 +1117,9 @@ class Image(object):
|
|||
|
||||
x0, y0, x1, y1 = map(int, map(round, box))
|
||||
|
||||
if x1 < x0:
|
||||
x1 = x0
|
||||
if y1 < y0:
|
||||
y1 = y0
|
||||
absolute_values = (abs(x1 - x0), abs(y1 - y0))
|
||||
|
||||
_decompression_bomb_check((x1, y1))
|
||||
_decompression_bomb_check(absolute_values)
|
||||
|
||||
return im.crop((x0, y0, x1, y1))
|
||||
|
||||
|
@ -1894,7 +1904,7 @@ class Image(object):
|
|||
parameter should always be used.
|
||||
:param params: Extra parameters to the image writer.
|
||||
: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.
|
||||
:exception IOError: If the file could not be written. The file
|
||||
may have been created, and may contain partial data.
|
||||
|
@ -2448,7 +2458,7 @@ def fromarray(obj, mode=None):
|
|||
from PIL import Image
|
||||
import numpy as np
|
||||
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::
|
||||
|
||||
|
@ -2470,7 +2480,6 @@ def fromarray(obj, mode=None):
|
|||
typekey = (1, 1) + shape[2:], arr['typestr']
|
||||
mode, rawmode = _fromarray_typemap[typekey]
|
||||
except KeyError:
|
||||
# print(typekey)
|
||||
raise TypeError("Cannot handle this data type")
|
||||
else:
|
||||
rawmode = mode
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
import math
|
||||
import numbers
|
||||
|
||||
from . import Image, ImageColor
|
||||
|
@ -149,11 +150,64 @@ class ImageDraw(object):
|
|||
if ink is not None and ink != fill:
|
||||
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."""
|
||||
ink, fill = self._getink(fill)
|
||||
ink = self._getink(fill)[0]
|
||||
if ink is not None:
|
||||
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):
|
||||
"""(Experimental) Draw a shape."""
|
||||
|
@ -246,7 +300,7 @@ class ImageDraw(object):
|
|||
elif align == "right":
|
||||
left += (max_width - widths[idx])
|
||||
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,
|
||||
direction=direction, features=features)
|
||||
top += line_spacing
|
||||
|
@ -341,6 +395,7 @@ def floodfill(image, xy, value, border=None, thresh=0):
|
|||
homogeneous, but similar, colors.
|
||||
"""
|
||||
# based on an implementation by Eric S. Raymond
|
||||
# amended by yo1995 @20180806
|
||||
pixel = image.load()
|
||||
x, y = xy
|
||||
try:
|
||||
|
@ -350,39 +405,36 @@ def floodfill(image, xy, value, border=None, thresh=0):
|
|||
pixel[x, y] = value
|
||||
except (ValueError, IndexError):
|
||||
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:
|
||||
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
|
||||
fill = _color_diff(p, background) <= thresh
|
||||
else:
|
||||
if _color_diff(p, background) <= thresh:
|
||||
fill = p != value and p != border
|
||||
if fill:
|
||||
pixel[s, t] = value
|
||||
newedge.append((s, t))
|
||||
edge = newedge
|
||||
else:
|
||||
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
|
||||
new_edge.add((s, t))
|
||||
full_edge = edge # discard pixels processed
|
||||
edge = new_edge
|
||||
|
||||
|
||||
def _color_diff(rgb1, rgb2):
|
||||
def _color_diff(color1, color2):
|
||||
"""
|
||||
Uses 1-norm distance to calculate difference between two rgb values.
|
||||
Uses 1-norm distance to calculate difference between two values.
|
||||
"""
|
||||
return abs(rgb1[0]-rgb2[0]) + abs(rgb1[1]-rgb2[1]) + abs(rgb1[2]-rgb2[2])
|
||||
if isinstance(color2, tuple):
|
||||
return sum([abs(color1[i]-color2[i]) for i in range(0, len(color2))])
|
||||
else:
|
||||
return abs(color1-color2)
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
from . import Image
|
||||
from ._util import isPath
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import struct
|
||||
|
||||
|
|
|
@ -33,7 +33,14 @@ class MultibandFilter(Filter):
|
|||
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
|
||||
supports 3x3 and 5x5 integer and floating point kernels.
|
||||
|
@ -60,16 +67,6 @@ class Kernel(MultibandFilter):
|
|||
raise ValueError("not enough coefficients in 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):
|
||||
"""
|
||||
|
|
|
@ -162,7 +162,8 @@ class FreeTypeFont(object):
|
|||
size, offset = self.font.getsize(text, direction, features)
|
||||
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
|
||||
lines = self._multiline_split(text)
|
||||
line_spacing = self.getsize('A')[1] + spacing
|
||||
|
|
|
@ -151,11 +151,6 @@ class LutBuilder(object):
|
|||
|
||||
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
|
||||
for i, pattern in enumerate(patterns):
|
||||
p = pattern[0].replace('.', 'X').replace('X', '[01]')
|
||||
|
|
|
@ -221,6 +221,50 @@ def colorize(image, black, white, mid=None, blackpoint=0,
|
|||
return _lut(image, red + green + blue)
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
Remove border from image. The same amount of pixels are removed
|
||||
|
|
|
@ -23,16 +23,21 @@ import sys
|
|||
|
||||
qt_versions = [
|
||||
['5', 'PyQt5'],
|
||||
['side2', 'PySide2'],
|
||||
['4', 'PyQt4'],
|
||||
['side', 'PySide']
|
||||
]
|
||||
# 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:
|
||||
try:
|
||||
if qt_module == 'PyQt5':
|
||||
from PyQt5.QtGui import QImage, qRgba, QPixmap
|
||||
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':
|
||||
from PyQt4.QtGui import QImage, qRgba, QPixmap
|
||||
from PyQt4.QtCore import QBuffer, QIODevice
|
||||
|
|
|
@ -32,13 +32,6 @@ if sys.version_info.major > 2:
|
|||
else:
|
||||
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 io import BytesIO
|
||||
|
||||
|
@ -192,7 +185,11 @@ class PhotoImage(object):
|
|||
from . import _imagingtk
|
||||
try:
|
||||
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
|
||||
# <cdata 'Tcl_Interp *' 0x3061b50>
|
||||
_imagingtk.tkinit(
|
||||
|
|
|
@ -103,8 +103,6 @@ class IptcImageFile(ImageFile.ImageFile):
|
|||
else:
|
||||
self.info[tag] = tagdata
|
||||
|
||||
# print(tag, self.info[tag])
|
||||
|
||||
# mode
|
||||
layers = i8(self.info[(3, 60)][0])
|
||||
component = i8(self.info[(3, 60)][1])
|
||||
|
|
|
@ -334,7 +334,6 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
|
||||
if i in MARKER:
|
||||
name, description, handler = MARKER[i]
|
||||
# print(hex(i), name, description)
|
||||
if handler is not None:
|
||||
handler(self, i)
|
||||
if i == 0xFFDA: # start of scan
|
||||
|
|
|
@ -74,7 +74,6 @@ def isSpiderHeader(t):
|
|||
labrec = int(h[13]) # no. records in file header
|
||||
labbyt = int(h[22]) # total no. of bytes in header
|
||||
lenbyt = int(h[23]) # record length in bytes
|
||||
# print("labrec = %d, labbyt = %d, lenbyt = %d" % (labrec,labbyt,lenbyt))
|
||||
if labbyt != (labrec * lenbyt):
|
||||
return 0
|
||||
# looks like a valid header
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
from . import Image, ImageFile, ImagePalette
|
||||
from ._binary import i8, i16le as i16, o8, o16le as o16
|
||||
|
||||
import warnings
|
||||
|
||||
__version__ = "0.3"
|
||||
|
||||
|
||||
|
@ -53,7 +55,7 @@ class TgaImageFile(ImageFile.ImageFile):
|
|||
# process header
|
||||
s = self.fp.read(18)
|
||||
|
||||
idlen = i8(s[0])
|
||||
id_len = i8(s[0])
|
||||
|
||||
colormaptype = i8(s[1])
|
||||
imagetype = i8(s[2])
|
||||
|
@ -100,8 +102,8 @@ class TgaImageFile(ImageFile.ImageFile):
|
|||
if imagetype & 8:
|
||||
self.info["compression"] = "tga_rle"
|
||||
|
||||
if idlen:
|
||||
self.info["id_section"] = self.fp.read(idlen)
|
||||
if id_len:
|
||||
self.info["id_section"] = self.fp.read(id_len)
|
||||
|
||||
if colormaptype:
|
||||
# read palette
|
||||
|
@ -151,11 +153,23 @@ def _save(im, fp, filename):
|
|||
except KeyError:
|
||||
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:
|
||||
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:
|
||||
colormapfirst, colormaplength, colormapentry = 0, 256, 24
|
||||
else:
|
||||
|
@ -166,11 +180,12 @@ def _save(im, fp, filename):
|
|||
else:
|
||||
flags = 0
|
||||
|
||||
orientation = im.info.get("orientation", -1)
|
||||
orientation = im.encoderinfo.get("orientation",
|
||||
im.info.get("orientation", -1))
|
||||
if orientation > 0:
|
||||
flags = flags | 0x20
|
||||
|
||||
fp.write(b"\000" +
|
||||
fp.write(o8(id_len) +
|
||||
o8(colormaptype) +
|
||||
o8(imagetype) +
|
||||
o16(colormapfirst) +
|
||||
|
@ -183,6 +198,9 @@ def _save(im, fp, filename):
|
|||
o8(bits) +
|
||||
o8(flags))
|
||||
|
||||
if id_section:
|
||||
fp.write(id_section)
|
||||
|
||||
if colormaptype:
|
||||
fp.write(im.im.getpalette("RGB", "BGR"))
|
||||
|
||||
|
|
|
@ -207,8 +207,16 @@ OPEN_INFO = {
|
|||
(MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("RGBX", "RGBXXX"),
|
||||
(II, 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"),
|
||||
(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
|
||||
(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"),
|
||||
(II, 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"),
|
||||
(MM, 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", "YCbCrXX"),
|
||||
(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"),
|
||||
|
||||
|
@ -567,6 +575,9 @@ class ImageFileDirectory_v2(MutableMapping):
|
|||
if self.tagtype[tag] == 7 and py3:
|
||||
values = [value.encode("ascii", 'replace') if isinstance(
|
||||
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)
|
||||
|
||||
|
@ -1254,9 +1265,6 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
h = self.tag_v2.get(ROWSPERSTRIP, ysize)
|
||||
w = self.size[0]
|
||||
if READ_LIBTIFF or self._compression != 'raw':
|
||||
# if DEBUG:
|
||||
# print("Activating g4 compression for whole file")
|
||||
|
||||
# Decoder expects entire file as one tile.
|
||||
# There's a buffer size limit in load (64k)
|
||||
# so large g4 images will fail if we use that
|
||||
|
@ -1529,7 +1537,6 @@ def _save(im, fp, filename):
|
|||
rawmode = 'I;16N'
|
||||
|
||||
a = (rawmode, compression, _fp, filename, atts)
|
||||
# print(im.mode, compression, a, im.encoderconfig)
|
||||
e = Image._getencoder(im.mode, 'libtiff', a, im.encoderconfig)
|
||||
e.setimage(im.im, (0, 0)+im.size)
|
||||
while True:
|
||||
|
|
|
@ -122,7 +122,7 @@ TAGS_V2 = {
|
|||
316: ("HostComputer", ASCII, 1),
|
||||
317: ("Predictor", SHORT, 1, {"none": 1, "Horizontal Differencing": 2}),
|
||||
318: ("WhitePoint", RATIONAL, 2),
|
||||
319: ("PrimaryChromaticities", SHORT, 6),
|
||||
319: ("PrimaryChromaticities", RATIONAL, 6),
|
||||
|
||||
320: ("ColorMap", SHORT, 0),
|
||||
321: ("HalftoneHints", SHORT, 2),
|
||||
|
@ -159,7 +159,7 @@ TAGS_V2 = {
|
|||
529: ("YCbCrCoefficients", RATIONAL, 3),
|
||||
530: ("YCbCrSubSampling", SHORT, 2),
|
||||
531: ("YCbCrPositioning", SHORT, 1),
|
||||
532: ("ReferenceBlackWhite", LONG, 0),
|
||||
532: ("ReferenceBlackWhite", RATIONAL, 6),
|
||||
|
||||
700: ('XMP', BYTE, 1),
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ from io import BytesIO
|
|||
_VALID_WEBP_MODES = {
|
||||
"RGBX": True,
|
||||
"RGBA": True,
|
||||
"RGB": True,
|
||||
}
|
||||
|
||||
_VALID_WEBP_LEGACY_MODES = {
|
||||
|
@ -63,7 +64,8 @@ class WebPImageFile(ImageFile.ImageFile):
|
|||
bgcolor & 0xFF
|
||||
self.info["background"] = (bg_r, bg_g, bg_b, bg_a)
|
||||
self._n_frames = frame_count
|
||||
self.mode = mode
|
||||
self.mode = 'RGB' if mode == 'RGBX' else mode
|
||||
self.rawmode = mode
|
||||
self.tile = []
|
||||
|
||||
# Attempt to read ICC / EXIF / XMP chunks from file
|
||||
|
@ -153,8 +155,10 @@ class WebPImageFile(ImageFile.ImageFile):
|
|||
self.__loaded = self.__logical_frame
|
||||
|
||||
# Set tile
|
||||
if self.fp:
|
||||
self.fp.close()
|
||||
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()
|
||||
|
||||
|
@ -240,16 +244,23 @@ def _save_all(im, fp, filename):
|
|||
|
||||
# Make sure image mode is supported
|
||||
frame = ims
|
||||
rawmode = ims.mode
|
||||
if ims.mode not in _VALID_WEBP_MODES:
|
||||
alpha = ims.mode == 'P' and 'A' in ims.im.getpalettemode()
|
||||
frame = ims.convert('RGBA' if alpha else 'RGBX')
|
||||
alpha = 'A' in ims.mode or 'a' in ims.mode \
|
||||
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
|
||||
enc.add(
|
||||
frame.tobytes(),
|
||||
frame.tobytes('raw', rawmode),
|
||||
timestamp,
|
||||
frame.size[0], frame.size[1],
|
||||
frame.mode,
|
||||
rawmode,
|
||||
lossless,
|
||||
quality,
|
||||
method
|
||||
|
@ -288,7 +299,8 @@ def _save(im, fp, filename):
|
|||
xmp = im.encoderinfo.get("xmp", "")
|
||||
|
||||
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')
|
||||
|
||||
data = _webp.WebPEncode(
|
||||
|
|
|
@ -109,8 +109,6 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
|||
|
||||
self.info["dpi"] = 72
|
||||
|
||||
# print(self.mode, self.size, self.info)
|
||||
|
||||
# sanity check (standard metafile header)
|
||||
if s[22:26] != b"\x01\x00\t\x00":
|
||||
raise SyntaxError("Unsupported WMF file format")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Pillow {} (Fork of the Python Imaging Library)
|
||||
"""Pillow (Fork of the Python Imaging Library)
|
||||
|
||||
Pillow is the friendly PIL fork by Alex Clark and Contributors.
|
||||
https://github.com/python-pillow/Pillow/
|
||||
|
@ -24,8 +24,6 @@ PILLOW_VERSION = __version__ = _version.__version__
|
|||
|
||||
del _version
|
||||
|
||||
__doc__ = __doc__.format(__version__) # include version in docstring
|
||||
|
||||
|
||||
_plugins = ['BlpImagePlugin',
|
||||
'BmpImagePlugin',
|
||||
|
|
|
@ -1998,6 +1998,7 @@ _getextrema(ImagingObject* self, PyObject* args)
|
|||
UINT8 u[2];
|
||||
INT32 i[2];
|
||||
FLOAT32 f[2];
|
||||
UINT16 s[2];
|
||||
} extrema;
|
||||
int status;
|
||||
|
||||
|
@ -2013,6 +2014,10 @@ _getextrema(ImagingObject* self, PyObject* args)
|
|||
return Py_BuildValue("ii", extrema.i[0], extrema.i[1]);
|
||||
case IMAGING_TYPE_FLOAT32:
|
||||
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);
|
||||
|
@ -2697,22 +2702,6 @@ _draw_ellipse(ImagingDrawObject* self, PyObject* args)
|
|||
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*
|
||||
_draw_lines(ImagingDrawObject* self, PyObject* args)
|
||||
{
|
||||
|
@ -2766,21 +2755,6 @@ _draw_lines(ImagingDrawObject* self, PyObject* args)
|
|||
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*
|
||||
_draw_points(ImagingDrawObject* self, PyObject* args)
|
||||
{
|
||||
|
@ -2961,14 +2935,12 @@ _draw_rectangle(ImagingDrawObject* self, PyObject* args)
|
|||
static struct PyMethodDef _draw_methods[] = {
|
||||
#ifdef WITH_IMAGEDRAW
|
||||
/* Graphics (ImageDraw) */
|
||||
{"draw_line", (PyCFunction)_draw_line, 1},
|
||||
{"draw_lines", (PyCFunction)_draw_lines, 1},
|
||||
#ifdef WITH_ARROW
|
||||
{"draw_outline", (PyCFunction)_draw_outline, 1},
|
||||
#endif
|
||||
{"draw_polygon", (PyCFunction)_draw_polygon, 1},
|
||||
{"draw_rectangle", (PyCFunction)_draw_rectangle, 1},
|
||||
{"draw_point", (PyCFunction)_draw_point, 1},
|
||||
{"draw_points", (PyCFunction)_draw_points, 1},
|
||||
{"draw_arc", (PyCFunction)_draw_arc, 1},
|
||||
{"draw_bitmap", (PyCFunction)_draw_bitmap, 1},
|
||||
|
|
|
@ -674,47 +674,6 @@ font_getsize(FontObject* self, PyObject* args)
|
|||
);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
font_getabc(FontObject* self, PyObject* args)
|
||||
{
|
||||
FT_ULong ch;
|
||||
FT_Face face;
|
||||
double a, b, c;
|
||||
|
||||
/* calculate ABC values for a given string */
|
||||
|
||||
PyObject* string;
|
||||
if (!PyArg_ParseTuple(args, "O:getabc", &string))
|
||||
return NULL;
|
||||
|
||||
#if PY_VERSION_HEX >= 0x03000000
|
||||
if (!PyUnicode_Check(string)) {
|
||||
#else
|
||||
if (!PyUnicode_Check(string) && !PyString_Check(string)) {
|
||||
#endif
|
||||
PyErr_SetString(PyExc_TypeError, "expected string");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (font_getchar(string, 0, &ch)) {
|
||||
int index, error;
|
||||
face = self->face;
|
||||
index = FT_Get_Char_Index(face, ch);
|
||||
/* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960 */
|
||||
error = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT|FT_LOAD_NO_BITMAP);
|
||||
if (error)
|
||||
return geterror(error);
|
||||
a = face->glyph->metrics.horiBearingX / 64.0;
|
||||
b = face->glyph->metrics.width / 64.0;
|
||||
c = (face->glyph->metrics.horiAdvance -
|
||||
face->glyph->metrics.horiBearingX -
|
||||
face->glyph->metrics.width) / 64.0;
|
||||
} else
|
||||
a = b = c = 0.0;
|
||||
|
||||
return Py_BuildValue("ddd", a, b, c);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
font_render(FontObject* self, PyObject* args)
|
||||
{
|
||||
|
@ -854,7 +813,6 @@ font_dealloc(FontObject* self)
|
|||
static PyMethodDef font_methods[] = {
|
||||
{"render", (PyCFunction) font_render, METH_VARARGS},
|
||||
{"getsize", (PyCFunction) font_getsize, METH_VARARGS},
|
||||
{"getabc", (PyCFunction) font_getabc, METH_VARARGS},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
|
|
@ -216,6 +216,8 @@ PyObject* _anim_encoder_add(PyObject* self, PyObject* args)
|
|||
WebPPictureImportRGBA(frame, rgb, 4 * width);
|
||||
} else if (strcmp(mode, "RGBX")==0) {
|
||||
WebPPictureImportRGBX(frame, rgb, 4 * width);
|
||||
} else {
|
||||
WebPPictureImportRGB(frame, rgb, 3 * width);
|
||||
}
|
||||
|
||||
// Add the frame to the encoder
|
||||
|
|
|
@ -797,6 +797,48 @@ unpackRGBa(UINT8* _out, const UINT8* in, int pixels)
|
|||
}
|
||||
}
|
||||
|
||||
static void
|
||||
unpackRGBaskip1(UINT8* _out, const UINT8* in, int pixels)
|
||||
{
|
||||
int i;
|
||||
UINT32* out = (UINT32*) _out;
|
||||
/* premultiplied RGBA */
|
||||
for (i = 0; i < pixels; i++) {
|
||||
int a = in[3];
|
||||
if ( ! a) {
|
||||
out[i] = 0;
|
||||
} else if (a == 255) {
|
||||
out[i] = MAKE_UINT32(in[0], in[1], in[2], a);
|
||||
} else {
|
||||
out[i] = MAKE_UINT32(CLIP8(in[0] * 255 / a),
|
||||
CLIP8(in[1] * 255 / a),
|
||||
CLIP8(in[2] * 255 / a), a);
|
||||
}
|
||||
in += 5;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
unpackRGBaskip2(UINT8* _out, const UINT8* in, int pixels)
|
||||
{
|
||||
int i;
|
||||
UINT32* out = (UINT32*) _out;
|
||||
/* premultiplied RGBA */
|
||||
for (i = 0; i < pixels; i++) {
|
||||
int a = in[3];
|
||||
if ( ! a) {
|
||||
out[i] = 0;
|
||||
} else if (a == 255) {
|
||||
out[i] = MAKE_UINT32(in[0], in[1], in[2], a);
|
||||
} else {
|
||||
out[i] = MAKE_UINT32(CLIP8(in[0] * 255 / a),
|
||||
CLIP8(in[1] * 255 / a),
|
||||
CLIP8(in[2] * 255 / a), a);
|
||||
}
|
||||
in += 6;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
unpackBGRa(UINT8* _out, const UINT8* in, int pixels)
|
||||
{
|
||||
|
@ -1301,7 +1343,11 @@ static struct {
|
|||
{"RGBA", "LA", 16, unpackRGBALA},
|
||||
{"RGBA", "LA;16B", 32, unpackRGBALA16B},
|
||||
{"RGBA", "RGBA", 32, copy4},
|
||||
{"RGBA", "RGBAX", 40, copy4skip1},
|
||||
{"RGBA", "RGBAXX", 48, copy4skip2},
|
||||
{"RGBA", "RGBa", 32, unpackRGBa},
|
||||
{"RGBA", "RGBaX", 40, unpackRGBaskip1},
|
||||
{"RGBA", "RGBaXX", 48, unpackRGBaskip2},
|
||||
{"RGBA", "RGBa;16L", 64, unpackRGBa16L},
|
||||
{"RGBA", "RGBa;16B", 64, unpackRGBa16B},
|
||||
{"RGBA", "BGRa", 32, unpackBGRa},
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#!/bin/sh
|
||||
|
||||
mkdir /var/cache/pacman/pkg
|
||||
pacman -S --noconfirm mingw32/mingw-w64-i686-python3-pip \
|
||||
mingw32/mingw-w64-i686-python3-setuptools \
|
||||
mingw32/mingw-w64-i686-python2-pip \
|
||||
|
|