mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-12 10:16:17 +03:00
Merge branch 'master' into gha-win
This commit is contained in:
commit
a0a5601689
|
@ -30,6 +30,10 @@ environment:
|
||||||
PIP_DIR: bin
|
PIP_DIR: bin
|
||||||
TEST_OPTIONS: --processes=0
|
TEST_OPTIONS: --processes=0
|
||||||
DEPLOY: NO
|
DEPLOY: NO
|
||||||
|
- PYTHON: C:/vp/pypy3
|
||||||
|
EXECUTABLE: bin/pypy.exe
|
||||||
|
PIP_DIR: bin
|
||||||
|
VENV: YES
|
||||||
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
@ -43,7 +47,12 @@ install:
|
||||||
- ps: |
|
- ps: |
|
||||||
if ($env:PYTHON -eq "c:/vp/pypy2")
|
if ($env:PYTHON -eq "c:/vp/pypy2")
|
||||||
{
|
{
|
||||||
c:\pillow\winbuild\appveyor_install_pypy.cmd
|
c:\pillow\winbuild\appveyor_install_pypy2.cmd
|
||||||
|
}
|
||||||
|
- ps: |
|
||||||
|
if ($env:PYTHON -eq "c:/vp/pypy3")
|
||||||
|
{
|
||||||
|
c:\pillow\winbuild\appveyor_install_pypy3.cmd
|
||||||
}
|
}
|
||||||
- ps: |
|
- ps: |
|
||||||
if ($env:PYTHON -eq "c:/msys64/mingw32")
|
if ($env:PYTHON -eq "c:/msys64/mingw32")
|
||||||
|
|
|
@ -22,19 +22,20 @@ matrix:
|
||||||
name: "PyPy3 Xenial"
|
name: "PyPy3 Xenial"
|
||||||
- python: '3.7'
|
- python: '3.7'
|
||||||
name: "3.7 Xenial"
|
name: "3.7 Xenial"
|
||||||
|
services: xvfb
|
||||||
- python: '2.7'
|
- python: '2.7'
|
||||||
name: "2.7 Xenial"
|
name: "2.7 Xenial"
|
||||||
- python: "2.7_with_system_site_packages" # For PyQt4
|
|
||||||
name: "2.7_with_system_site_packages Xenial"
|
|
||||||
services: xvfb
|
|
||||||
- python: '3.6'
|
- python: '3.6'
|
||||||
name: "3.6 Xenial PYTHONOPTIMIZE=1"
|
name: "3.6 Xenial PYTHONOPTIMIZE=1"
|
||||||
env: PYTHONOPTIMIZE=1
|
env: PYTHONOPTIMIZE=1
|
||||||
|
services: xvfb
|
||||||
- python: '3.5'
|
- python: '3.5'
|
||||||
name: "3.5 Xenial PYTHONOPTIMIZE=2"
|
name: "3.5 Xenial PYTHONOPTIMIZE=2"
|
||||||
env: PYTHONOPTIMIZE=2
|
env: PYTHONOPTIMIZE=2
|
||||||
|
services: xvfb
|
||||||
- python: "3.8-dev"
|
- python: "3.8-dev"
|
||||||
name: "3.8-dev Xenial"
|
name: "3.8-dev Xenial"
|
||||||
|
services: xvfb
|
||||||
- env: DOCKER="alpine" DOCKER_TAG="master"
|
- env: DOCKER="alpine" DOCKER_TAG="master"
|
||||||
- env: DOCKER="arch" DOCKER_TAG="master" # contains PyQt5
|
- env: DOCKER="arch" DOCKER_TAG="master" # contains PyQt5
|
||||||
- env: DOCKER="ubuntu-16.04-xenial-amd64" DOCKER_TAG="master"
|
- env: DOCKER="ubuntu-16.04-xenial-amd64" DOCKER_TAG="master"
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-tk python-qt4\
|
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-tk\
|
||||||
ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
|
ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
|
||||||
cmake imagemagick libharfbuzz-dev libfribidi-dev
|
cmake imagemagick libharfbuzz-dev libfribidi-dev
|
||||||
|
|
||||||
|
@ -15,6 +15,10 @@ pip install -U pytest-cov
|
||||||
pip install pyroma
|
pip install pyroma
|
||||||
pip install test-image-results
|
pip install test-image-results
|
||||||
pip install numpy
|
pip install numpy
|
||||||
|
if [[ $TRAVIS_PYTHON_VERSION == 3.* ]]; then
|
||||||
|
sudo apt-get -qq install pyqt5-dev-tools
|
||||||
|
pip install pyqt5
|
||||||
|
fi
|
||||||
|
|
||||||
# docs only on Python 2.7
|
# docs only on Python 2.7
|
||||||
if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then pip install -r requirements.txt ; fi
|
if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then pip install -r requirements.txt ; fi
|
||||||
|
|
41
CHANGES.rst
41
CHANGES.rst
|
@ -2,11 +2,50 @@
|
||||||
Changelog (Pillow)
|
Changelog (Pillow)
|
||||||
==================
|
==================
|
||||||
|
|
||||||
6.2.0 (unreleased)
|
7.0.0 (unreleased)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Drop support for EOL PyQt4 and PySide #4108
|
||||||
|
[hugovk, radarhere]
|
||||||
|
|
||||||
|
- Removed deprecated setting of TIFF image sizes #4114
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Removed deprecated PILLOW_VERSION #4107
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Changed default frombuffer raw decoder args #1730
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
6.2.0 (2019-10-01)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
- This is the last Pillow release to support Python 2.7 #3642
|
- This is the last Pillow release to support Python 2.7 #3642
|
||||||
|
|
||||||
|
- Catch buffer overruns #4104
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Initialize rows_per_strip when RowsPerStrip tag is missing #4034
|
||||||
|
[cgohlke, radarhere]
|
||||||
|
|
||||||
|
- Raise error if TIFF dimension is a string #4103
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added decompression bomb checks #4102
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fix ImageGrab.grab DPI scaling on Windows 10 version 1607+ #4000
|
||||||
|
[nulano, radarhere]
|
||||||
|
|
||||||
|
- Corrected negative seeks #4101
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added argument to capture all screens on Windows #3950
|
||||||
|
[nulano, radarhere]
|
||||||
|
|
||||||
|
- Updated warning to specify when Image.frombuffer defaults will change #4086
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
- Changed WindowsViewer format to PNG #4080
|
- Changed WindowsViewer format to PNG #4080
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ Released as needed privately to individual vendors for critical security-related
|
||||||
```
|
```
|
||||||
* [ ] Download distributions from the [Pillow Wheel Builder container](http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com/).
|
* [ ] Download distributions from the [Pillow Wheel Builder container](http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com/).
|
||||||
```bash
|
```bash
|
||||||
wget -m -A 'Pillow-<VERSION>*' \
|
wget -m -A 'Pillow-<VERSION>-*' \
|
||||||
http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com
|
http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,5 @@
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL._util import py3
|
|
||||||
|
|
||||||
if py3:
|
Image.open(BytesIO(b"icns\x00\x00\x00\x10hang\x00\x00\x00\x00"))
|
||||||
Image.open(BytesIO(bytes("icns\x00\x00\x00\x10hang\x00\x00\x00\x00", "latin-1")))
|
|
||||||
else:
|
|
||||||
Image.open(BytesIO(bytes("icns\x00\x00\x00\x10hang\x00\x00\x00\x00")))
|
|
||||||
|
|
|
@ -4,19 +4,5 @@
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL._util import py3
|
|
||||||
|
|
||||||
if py3:
|
Image.open(BytesIO(b"\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang"))
|
||||||
Image.open(
|
|
||||||
BytesIO(
|
|
||||||
bytes(
|
|
||||||
"\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang",
|
|
||||||
"latin-1",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
Image.open(
|
|
||||||
BytesIO(bytes("\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang"))
|
|
||||||
)
|
|
||||||
|
|
|
@ -5,9 +5,11 @@ from __future__ import print_function
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
from PIL import Image, ImageMath
|
from PIL import Image, ImageMath
|
||||||
from PIL._util import py3
|
from PIL._util import py3
|
||||||
|
@ -284,14 +286,10 @@ if not py3:
|
||||||
|
|
||||||
|
|
||||||
def fromstring(data):
|
def fromstring(data):
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
return Image.open(BytesIO(data))
|
return Image.open(BytesIO(data))
|
||||||
|
|
||||||
|
|
||||||
def tostring(im, string_format, **options):
|
def tostring(im, string_format, **options):
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
out = BytesIO()
|
out = BytesIO()
|
||||||
im.save(out, string_format, **options)
|
im.save(out, string_format, **options)
|
||||||
return out.getvalue()
|
return out.getvalue()
|
||||||
|
@ -323,8 +321,6 @@ def command_succeeds(cmd):
|
||||||
Runs the command, which must be a list of strings. Returns True if the
|
Runs the command, which must be a list of strings. Returns True if the
|
||||||
command succeeds, or False if an OSError was raised by subprocess.Popen.
|
command succeeds, or False if an OSError was raised by subprocess.Popen.
|
||||||
"""
|
"""
|
||||||
import subprocess
|
|
||||||
|
|
||||||
with open(os.devnull, "wb") as f:
|
with open(os.devnull, "wb") as f:
|
||||||
try:
|
try:
|
||||||
subprocess.call(cmd, stdout=f, stderr=subprocess.STDOUT)
|
subprocess.call(cmd, stdout=f, stderr=subprocess.STDOUT)
|
||||||
|
|
BIN
Tests/images/combined_larger_than_size.psd
Normal file
BIN
Tests/images/combined_larger_than_size.psd
Normal file
Binary file not shown.
BIN
Tests/images/decompression_bomb.gif
Normal file
BIN
Tests/images/decompression_bomb.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 B |
BIN
Tests/images/decompression_bomb.ico
Normal file
BIN
Tests/images/decompression_bomb.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 B |
BIN
Tests/images/fli_overrun.bin
Normal file
BIN
Tests/images/fli_overrun.bin
Normal file
Binary file not shown.
BIN
Tests/images/no_rows_per_strip.tif
Normal file
BIN
Tests/images/no_rows_per_strip.tif
Normal file
Binary file not shown.
BIN
Tests/images/pcx_overrun.bin
Normal file
BIN
Tests/images/pcx_overrun.bin
Normal file
Binary file not shown.
BIN
Tests/images/raw_negative_stride.bin
Normal file
BIN
Tests/images/raw_negative_stride.bin
Normal file
Binary file not shown.
BIN
Tests/images/sgi_overrun.bin
Normal file
BIN
Tests/images/sgi_overrun.bin
Normal file
Binary file not shown.
BIN
Tests/images/string_dimension.tiff
Normal file
BIN
Tests/images/string_dimension.tiff
Normal file
Binary file not shown.
|
@ -41,6 +41,14 @@ class TestDecompressionBomb(PillowTestCase):
|
||||||
|
|
||||||
self.assertRaises(Image.DecompressionBombError, lambda: Image.open(TEST_FILE))
|
self.assertRaises(Image.DecompressionBombError, lambda: Image.open(TEST_FILE))
|
||||||
|
|
||||||
|
def test_exception_ico(self):
|
||||||
|
with self.assertRaises(Image.DecompressionBombError):
|
||||||
|
Image.open("Tests/images/decompression_bomb.ico")
|
||||||
|
|
||||||
|
def test_exception_gif(self):
|
||||||
|
with self.assertRaises(Image.DecompressionBombError):
|
||||||
|
Image.open("Tests/images/decompression_bomb.gif")
|
||||||
|
|
||||||
|
|
||||||
class TestDecompressionCrop(PillowTestCase):
|
class TestDecompressionCrop(PillowTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import base64
|
||||||
import distutils.version
|
import distutils.version
|
||||||
import io
|
import io
|
||||||
import itertools
|
import itertools
|
||||||
|
@ -832,6 +833,13 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
im, "Tests/images/old-style-jpeg-compression.png"
|
im, "Tests/images/old-style-jpeg-compression.png"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_no_rows_per_strip(self):
|
||||||
|
# This image does not have a RowsPerStrip TIFF tag
|
||||||
|
infile = "Tests/images/no_rows_per_strip.tif"
|
||||||
|
im = Image.open(infile)
|
||||||
|
im.load()
|
||||||
|
self.assertEqual(im.size, (950, 975))
|
||||||
|
|
||||||
def test_orientation(self):
|
def test_orientation(self):
|
||||||
base_im = Image.open("Tests/images/g4_orientation_1.tif")
|
base_im = Image.open("Tests/images/g4_orientation_1.tif")
|
||||||
|
|
||||||
|
@ -840,3 +848,24 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
self.assert_image_similar(base_im, im, 0.7)
|
self.assert_image_similar(base_im, im, 0.7)
|
||||||
|
|
||||||
|
def test_sampleformat_not_corrupted(self):
|
||||||
|
# Assert that a TIFF image with SampleFormat=UINT tag is not corrupted
|
||||||
|
# when saving to a new file.
|
||||||
|
# Pillow 6.0 fails with "OSError: cannot identify image file".
|
||||||
|
tiff = io.BytesIO(
|
||||||
|
base64.b64decode(
|
||||||
|
b"SUkqAAgAAAAPAP4ABAABAAAAAAAAAAABBAABAAAAAQAAAAEBBAABAAAAAQAA"
|
||||||
|
b"AAIBAwADAAAAwgAAAAMBAwABAAAACAAAAAYBAwABAAAAAgAAABEBBAABAAAA"
|
||||||
|
b"4AAAABUBAwABAAAAAwAAABYBBAABAAAAAQAAABcBBAABAAAACwAAABoBBQAB"
|
||||||
|
b"AAAAyAAAABsBBQABAAAA0AAAABwBAwABAAAAAQAAACgBAwABAAAAAQAAAFMB"
|
||||||
|
b"AwADAAAA2AAAAAAAAAAIAAgACAABAAAAAQAAAAEAAAABAAAAAQABAAEAAAB4"
|
||||||
|
b"nGNgYAAAAAMAAQ=="
|
||||||
|
)
|
||||||
|
)
|
||||||
|
out = io.BytesIO()
|
||||||
|
with Image.open(tiff) as im:
|
||||||
|
im.save(out, format="tiff")
|
||||||
|
out.seek(0)
|
||||||
|
with Image.open(out) as im:
|
||||||
|
im.load()
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .test_file_libtiff import LibTiffTestCase
|
from .test_file_libtiff import LibTiffTestCase
|
||||||
|
@ -25,8 +27,6 @@ class TestFileLibTiffSmall(LibTiffTestCase):
|
||||||
|
|
||||||
def test_g4_hopper_bytesio(self):
|
def test_g4_hopper_bytesio(self):
|
||||||
"""Testing the bytesio loading code path"""
|
"""Testing the bytesio loading code path"""
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
test_file = "Tests/images/hopper_g4.tif"
|
test_file = "Tests/images/hopper_g4.tif"
|
||||||
s = BytesIO()
|
s = BytesIO()
|
||||||
with open(test_file, "rb") as f:
|
with open(test_file, "rb") as f:
|
||||||
|
|
|
@ -87,3 +87,12 @@ class TestImagePsd(PillowTestCase):
|
||||||
im = Image.open("Tests/images/hopper_merged.psd")
|
im = Image.open("Tests/images/hopper_merged.psd")
|
||||||
|
|
||||||
self.assertNotIn("icc_profile", im.info)
|
self.assertNotIn("icc_profile", im.info)
|
||||||
|
|
||||||
|
def test_combined_larger_than_size(self):
|
||||||
|
# The 'combined' sizes of the individual parts is larger than the
|
||||||
|
# declared 'size' of the extra data field, resulting in a backwards seek.
|
||||||
|
|
||||||
|
# If we instead take the 'size' of the extra data field as the source of truth,
|
||||||
|
# then the seek can't be negative
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
Image.open("Tests/images/combined_larger_than_size.psd")
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
from PIL import Image, ImageSequence, SpiderImagePlugin
|
from PIL import Image, ImageSequence, SpiderImagePlugin
|
||||||
|
|
||||||
|
@ -117,3 +118,14 @@ class TestImageSpider(PillowTestCase):
|
||||||
for i, frame in enumerate(ImageSequence.Iterator(im)):
|
for i, frame in enumerate(ImageSequence.Iterator(im)):
|
||||||
if i > 1:
|
if i > 1:
|
||||||
self.fail("Non-stack DOS file test failed")
|
self.fail("Non-stack DOS file test failed")
|
||||||
|
|
||||||
|
# for issue #4093
|
||||||
|
def test_odd_size(self):
|
||||||
|
data = BytesIO()
|
||||||
|
width = 100
|
||||||
|
im = Image.new("F", (width, 64))
|
||||||
|
im.save(data, format="SPIDER")
|
||||||
|
|
||||||
|
data.seek(0)
|
||||||
|
im2 = Image.open(data)
|
||||||
|
self.assert_image_equal(im, im2)
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from PIL import Image, TiffImagePlugin, features
|
from PIL import Image, TiffImagePlugin
|
||||||
from PIL._util import py3
|
from PIL._util import py3
|
||||||
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
|
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
|
||||||
|
|
||||||
|
@ -72,15 +73,6 @@ class TestFileTiff(PillowTestCase):
|
||||||
ifd.legacy_api = None
|
ifd.legacy_api = None
|
||||||
self.assertEqual(str(e.exception), "Not allowing setting of legacy api")
|
self.assertEqual(str(e.exception), "Not allowing setting of legacy api")
|
||||||
|
|
||||||
def test_size(self):
|
|
||||||
filename = "Tests/images/pil168.tif"
|
|
||||||
im = Image.open(filename)
|
|
||||||
|
|
||||||
def set_size():
|
|
||||||
im.size = (256, 256)
|
|
||||||
|
|
||||||
self.assert_warning(DeprecationWarning, set_size)
|
|
||||||
|
|
||||||
def test_xyres_tiff(self):
|
def test_xyres_tiff(self):
|
||||||
filename = "Tests/images/pil168.tif"
|
filename = "Tests/images/pil168.tif"
|
||||||
im = Image.open(filename)
|
im = Image.open(filename)
|
||||||
|
@ -512,10 +504,7 @@ class TestFileTiff(PillowTestCase):
|
||||||
self.assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
|
self.assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
|
||||||
|
|
||||||
def test_tiff_save_all(self):
|
def test_tiff_save_all(self):
|
||||||
import io
|
mp = BytesIO()
|
||||||
import os
|
|
||||||
|
|
||||||
mp = io.BytesIO()
|
|
||||||
with Image.open("Tests/images/multipage.tiff") as im:
|
with Image.open("Tests/images/multipage.tiff") as im:
|
||||||
im.save(mp, format="tiff", save_all=True)
|
im.save(mp, format="tiff", save_all=True)
|
||||||
|
|
||||||
|
@ -524,7 +513,7 @@ class TestFileTiff(PillowTestCase):
|
||||||
self.assertEqual(im.n_frames, 3)
|
self.assertEqual(im.n_frames, 3)
|
||||||
|
|
||||||
# Test appending images
|
# Test appending images
|
||||||
mp = io.BytesIO()
|
mp = BytesIO()
|
||||||
im = Image.new("RGB", (100, 100), "#f00")
|
im = Image.new("RGB", (100, 100), "#f00")
|
||||||
ims = [Image.new("RGB", (100, 100), color) for color in ["#0f0", "#00f"]]
|
ims = [Image.new("RGB", (100, 100), color) for color in ["#0f0", "#00f"]]
|
||||||
im.copy().save(mp, format="TIFF", save_all=True, append_images=ims)
|
im.copy().save(mp, format="TIFF", save_all=True, append_images=ims)
|
||||||
|
@ -538,7 +527,7 @@ class TestFileTiff(PillowTestCase):
|
||||||
for im in ims:
|
for im in ims:
|
||||||
yield im
|
yield im
|
||||||
|
|
||||||
mp = io.BytesIO()
|
mp = BytesIO()
|
||||||
im.save(mp, format="TIFF", save_all=True, append_images=imGenerator(ims))
|
im.save(mp, format="TIFF", save_all=True, append_images=imGenerator(ims))
|
||||||
|
|
||||||
mp.seek(0, os.SEEK_SET)
|
mp.seek(0, os.SEEK_SET)
|
||||||
|
@ -586,36 +575,16 @@ class TestFileTiff(PillowTestCase):
|
||||||
im.load()
|
im.load()
|
||||||
self.assertFalse(fp.closed)
|
self.assertFalse(fp.closed)
|
||||||
|
|
||||||
@unittest.skipUnless(features.check("libtiff"), "libtiff not installed")
|
def test_string_dimension(self):
|
||||||
def test_sampleformat_not_corrupted(self):
|
# Assert that an error is raised if one of the dimensions is a string
|
||||||
# Assert that a TIFF image with SampleFormat=UINT tag is not corrupted
|
with self.assertRaises(ValueError):
|
||||||
# when saving to a new file.
|
Image.open("Tests/images/string_dimension.tiff")
|
||||||
# Pillow 6.0 fails with "OSError: cannot identify image file".
|
|
||||||
import base64
|
|
||||||
|
|
||||||
tiff = BytesIO(
|
|
||||||
base64.b64decode(
|
|
||||||
b"SUkqAAgAAAAPAP4ABAABAAAAAAAAAAABBAABAAAAAQAAAAEBBAABAAAAAQAA"
|
|
||||||
b"AAIBAwADAAAAwgAAAAMBAwABAAAACAAAAAYBAwABAAAAAgAAABEBBAABAAAA"
|
|
||||||
b"4AAAABUBAwABAAAAAwAAABYBBAABAAAAAQAAABcBBAABAAAACwAAABoBBQAB"
|
|
||||||
b"AAAAyAAAABsBBQABAAAA0AAAABwBAwABAAAAAQAAACgBAwABAAAAAQAAAFMB"
|
|
||||||
b"AwADAAAA2AAAAAAAAAAIAAgACAABAAAAAQAAAAEAAAABAAAAAQABAAEAAAB4"
|
|
||||||
b"nGNgYAAAAAMAAQ=="
|
|
||||||
)
|
|
||||||
)
|
|
||||||
out = BytesIO()
|
|
||||||
with Image.open(tiff) as im:
|
|
||||||
im.save(out, format="tiff")
|
|
||||||
out.seek(0)
|
|
||||||
with Image.open(out) as im:
|
|
||||||
im.load()
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(is_win32(), "Windows only")
|
@unittest.skipUnless(is_win32(), "Windows only")
|
||||||
class TestFileTiffW32(PillowTestCase):
|
class TestFileTiffW32(PillowTestCase):
|
||||||
def test_fd_leak(self):
|
def test_fd_leak(self):
|
||||||
tmpfile = self.tempfile("temp.tif")
|
tmpfile = self.tempfile("temp.tif")
|
||||||
import os
|
|
||||||
|
|
||||||
# this is an mmaped file.
|
# this is an mmaped file.
|
||||||
with Image.open("Tests/images/uint16_1_4660.tif") as im:
|
with Image.open("Tests/images/uint16_1_4660.tif") as im:
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import PillowTestCase
|
from .helper import PillowTestCase
|
||||||
|
@ -39,8 +41,6 @@ class TestFileWebpMetadata(PillowTestCase):
|
||||||
self.assertEqual(exif_data, expected_exif)
|
self.assertEqual(exif_data, expected_exif)
|
||||||
|
|
||||||
def test_write_exif_metadata(self):
|
def test_write_exif_metadata(self):
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
file_path = "Tests/images/flower.jpg"
|
file_path = "Tests/images/flower.jpg"
|
||||||
image = Image.open(file_path)
|
image = Image.open(file_path)
|
||||||
expected_exif = image.info["exif"]
|
expected_exif = image.info["exif"]
|
||||||
|
@ -73,8 +73,6 @@ class TestFileWebpMetadata(PillowTestCase):
|
||||||
self.assertEqual(icc, expected_icc)
|
self.assertEqual(icc, expected_icc)
|
||||||
|
|
||||||
def test_write_icc_metadata(self):
|
def test_write_icc_metadata(self):
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
file_path = "Tests/images/flower2.jpg"
|
file_path = "Tests/images/flower2.jpg"
|
||||||
image = Image.open(file_path)
|
image = Image.open(file_path)
|
||||||
expected_icc_profile = image.info["icc_profile"]
|
expected_icc_profile = image.info["icc_profile"]
|
||||||
|
@ -95,8 +93,6 @@ class TestFileWebpMetadata(PillowTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_read_no_exif(self):
|
def test_read_no_exif(self):
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
file_path = "Tests/images/flower.jpg"
|
file_path = "Tests/images/flower.jpg"
|
||||||
image = Image.open(file_path)
|
image = Image.open(file_path)
|
||||||
self.assertIn("exif", image.info)
|
self.assertIn("exif", image.info)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import PillowTestCase
|
from .helper import PillowTestCase
|
||||||
|
@ -28,8 +30,6 @@ static char basic_bits[] = {
|
||||||
|
|
||||||
class TestFileXbm(PillowTestCase):
|
class TestFileXbm(PillowTestCase):
|
||||||
def test_pil151(self):
|
def test_pil151(self):
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
im = Image.open(BytesIO(PIL151))
|
im = Image.open(BytesIO(PIL151))
|
||||||
|
|
||||||
im.load()
|
im.load()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL._util import py3
|
from PIL._util import py3
|
||||||
|
@ -125,8 +126,6 @@ class TestImage(PillowTestCase):
|
||||||
def test_tempfile(self):
|
def test_tempfile(self):
|
||||||
# see #1460, pathlib support breaks tempfile.TemporaryFile on py27
|
# see #1460, pathlib support breaks tempfile.TemporaryFile on py27
|
||||||
# Will error out on save on 3.0.0
|
# Will error out on save on 3.0.0
|
||||||
import tempfile
|
|
||||||
|
|
||||||
im = hopper()
|
im = hopper()
|
||||||
with tempfile.TemporaryFile() as fp:
|
with tempfile.TemporaryFile() as fp:
|
||||||
im.save(fp, "JPEG")
|
im.save(fp, "JPEG")
|
||||||
|
@ -586,6 +585,15 @@ class TestImage(PillowTestCase):
|
||||||
|
|
||||||
self.assertFalse(fp.closed)
|
self.assertFalse(fp.closed)
|
||||||
|
|
||||||
|
def test_overrun(self):
|
||||||
|
for file in ["fli_overrun.bin", "sgi_overrun.bin", "pcx_overrun.bin"]:
|
||||||
|
im = Image.open(os.path.join("Tests/images", file))
|
||||||
|
try:
|
||||||
|
im.load()
|
||||||
|
self.assertFail()
|
||||||
|
except IOError as e:
|
||||||
|
self.assertEqual(str(e), "buffer overrun when reading image file")
|
||||||
|
|
||||||
|
|
||||||
class MockEncoder(object):
|
class MockEncoder(object):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
import ctypes
|
||||||
import os
|
import os
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
from distutils import ccompiler, sysconfig
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
@ -337,10 +340,6 @@ class TestEmbeddable(unittest.TestCase):
|
||||||
"Failing on AppVeyor / GitHub Actions when run from subprocess, not from shell",
|
"Failing on AppVeyor / GitHub Actions when run from subprocess, not from shell",
|
||||||
)
|
)
|
||||||
def test_embeddable(self):
|
def test_embeddable(self):
|
||||||
import subprocess
|
|
||||||
import ctypes
|
|
||||||
from distutils import ccompiler, sysconfig
|
|
||||||
|
|
||||||
with open("embed_pil.c", "w") as fh:
|
with open("embed_pil.c", "w") as fh:
|
||||||
fh.write(
|
fh.write(
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -103,6 +103,14 @@ class TestImageFile(PillowTestCase):
|
||||||
parser = ImageFile.Parser()
|
parser = ImageFile.Parser()
|
||||||
parser.feed(1)
|
parser.feed(1)
|
||||||
|
|
||||||
|
def test_negative_stride(self):
|
||||||
|
with open("Tests/images/raw_negative_stride.bin", "rb") as f:
|
||||||
|
input = f.read()
|
||||||
|
p = ImageFile.Parser()
|
||||||
|
p.feed(input)
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
p.close()
|
||||||
|
|
||||||
def test_truncated_with_errors(self):
|
def test_truncated_with_errors(self):
|
||||||
if "zip_encoder" not in codecs:
|
if "zip_encoder" not in codecs:
|
||||||
self.skipTest("PNG (zlib) encoder not available")
|
self.skipTest("PNG (zlib) encoder not available")
|
||||||
|
|
|
@ -8,7 +8,11 @@ try:
|
||||||
|
|
||||||
class TestImageGrab(PillowTestCase):
|
class TestImageGrab(PillowTestCase):
|
||||||
def test_grab(self):
|
def test_grab(self):
|
||||||
for im in [ImageGrab.grab(), ImageGrab.grab(include_layered_windows=True)]:
|
for im in [
|
||||||
|
ImageGrab.grab(),
|
||||||
|
ImageGrab.grab(include_layered_windows=True),
|
||||||
|
ImageGrab.grab(all_screens=True),
|
||||||
|
]:
|
||||||
self.assert_image(im, im.mode, im.size)
|
self.assert_image(im, im.mode, im.size)
|
||||||
|
|
||||||
def test_grabclipboard(self):
|
def test_grabclipboard(self):
|
||||||
|
|
|
@ -81,6 +81,16 @@ class TestImageOps(PillowTestCase):
|
||||||
newimg = ImageOps.fit(hopper("RGB").resize((100, 1)), (35, 35))
|
newimg = ImageOps.fit(hopper("RGB").resize((100, 1)), (35, 35))
|
||||||
self.assertEqual(newimg.size, (35, 35))
|
self.assertEqual(newimg.size, (35, 35))
|
||||||
|
|
||||||
|
def test_fit_same_ratio(self):
|
||||||
|
# The ratio for this image is 1000.0 / 755 = 1.3245033112582782
|
||||||
|
# If the ratios are not acknowledged to be the same,
|
||||||
|
# and Pillow attempts to adjust the width to
|
||||||
|
# 1.3245033112582782 * 755 = 1000.0000000000001
|
||||||
|
# then centering this greater width causes a negative x offset when cropping
|
||||||
|
with Image.new("RGB", (1000, 755)) as im:
|
||||||
|
new_im = ImageOps.fit(im, (1000, 755))
|
||||||
|
self.assertEqual(new_im.size, (1000, 755))
|
||||||
|
|
||||||
def test_pad(self):
|
def test_pad(self):
|
||||||
# Same ratio
|
# Same ratio
|
||||||
im = hopper()
|
im = hopper()
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
import sys
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
from PIL import ImageQt
|
from PIL import ImageQt
|
||||||
|
|
||||||
from .helper import PillowTestCase, hopper
|
from .helper import PillowTestCase, hopper
|
||||||
|
|
||||||
if sys.version_info.major >= 3:
|
|
||||||
from importlib import reload
|
|
||||||
|
|
||||||
if ImageQt.qt_is_installed:
|
if ImageQt.qt_is_installed:
|
||||||
from PIL.ImageQt import qRgba
|
from PIL.ImageQt import qRgba
|
||||||
|
|
||||||
|
@ -35,10 +29,6 @@ class PillowQPixmapTestCase(PillowQtTestCase):
|
||||||
try:
|
try:
|
||||||
if ImageQt.qt_version == "5":
|
if ImageQt.qt_version == "5":
|
||||||
from PyQt5.QtGui import QGuiApplication
|
from PyQt5.QtGui import QGuiApplication
|
||||||
elif ImageQt.qt_version == "4":
|
|
||||||
from PyQt4.QtGui import QGuiApplication
|
|
||||||
elif ImageQt.qt_version == "side":
|
|
||||||
from PySide.QtGui import QGuiApplication
|
|
||||||
elif ImageQt.qt_version == "side2":
|
elif ImageQt.qt_version == "side2":
|
||||||
from PySide2.QtGui import QGuiApplication
|
from PySide2.QtGui import QGuiApplication
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -59,10 +49,6 @@ class TestImageQt(PillowQtTestCase, PillowTestCase):
|
||||||
# equivalent to an unsigned int.
|
# equivalent to an unsigned int.
|
||||||
if ImageQt.qt_version == "5":
|
if ImageQt.qt_version == "5":
|
||||||
from PyQt5.QtGui import qRgb
|
from PyQt5.QtGui import qRgb
|
||||||
elif ImageQt.qt_version == "4":
|
|
||||||
from PyQt4.QtGui import qRgb
|
|
||||||
elif ImageQt.qt_version == "side":
|
|
||||||
from PySide.QtGui import qRgb
|
|
||||||
elif ImageQt.qt_version == "side2":
|
elif ImageQt.qt_version == "side2":
|
||||||
from PySide2.QtGui import qRgb
|
from PySide2.QtGui import qRgb
|
||||||
|
|
||||||
|
@ -83,13 +69,3 @@ class TestImageQt(PillowQtTestCase, PillowTestCase):
|
||||||
def test_image(self):
|
def test_image(self):
|
||||||
for mode in ("1", "RGB", "RGBA", "L", "P"):
|
for mode in ("1", "RGB", "RGBA", "L", "P"):
|
||||||
ImageQt.ImageQt(hopper(mode))
|
ImageQt.ImageQt(hopper(mode))
|
||||||
|
|
||||||
def test_deprecated(self):
|
|
||||||
with warnings.catch_warnings(record=True) as w:
|
|
||||||
reload(ImageQt)
|
|
||||||
if ImageQt.qt_version in ["4", "side"]:
|
|
||||||
self.assertEqual(len(w), 1)
|
|
||||||
self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
|
|
||||||
else:
|
|
||||||
# No warning.
|
|
||||||
self.assertEqual(w, [])
|
|
||||||
|
|
|
@ -31,4 +31,8 @@ class TestLocale(PillowTestCase):
|
||||||
locale.setlocale(locale.LC_ALL, "polish")
|
locale.setlocale(locale.LC_ALL, "polish")
|
||||||
except locale.Error:
|
except locale.Error:
|
||||||
unittest.skip("Polish locale not available")
|
unittest.skip("Polish locale not available")
|
||||||
Image.open(path)
|
|
||||||
|
try:
|
||||||
|
Image.open(path)
|
||||||
|
finally:
|
||||||
|
locale.setlocale(locale.LC_ALL, (None, None))
|
||||||
|
|
|
@ -9,25 +9,9 @@ if ImageQt.qt_is_installed:
|
||||||
try:
|
try:
|
||||||
from PyQt5 import QtGui
|
from PyQt5 import QtGui
|
||||||
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QApplication
|
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QApplication
|
||||||
|
|
||||||
QT_VERSION = 5
|
|
||||||
except (ImportError, RuntimeError):
|
except (ImportError, RuntimeError):
|
||||||
try:
|
from PySide2 import QtGui
|
||||||
from PySide2 import QtGui
|
from PySide2.QtWidgets import QWidget, QHBoxLayout, QLabel, QApplication
|
||||||
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
|
|
||||||
|
|
||||||
QT_VERSION = 4
|
|
||||||
except (ImportError, RuntimeError):
|
|
||||||
from PySide import QtGui
|
|
||||||
from PySide.QtGui import QWidget, QHBoxLayout, QLabel, QApplication
|
|
||||||
|
|
||||||
QT_VERSION = 4
|
|
||||||
|
|
||||||
|
|
||||||
class TestToQImage(PillowQtTestCase, PillowTestCase):
|
class TestToQImage(PillowQtTestCase, PillowTestCase):
|
||||||
|
@ -60,10 +44,6 @@ class TestToQImage(PillowQtTestCase, PillowTestCase):
|
||||||
|
|
||||||
# Check that it actually worked.
|
# Check that it actually worked.
|
||||||
reloaded = Image.open(tempfile)
|
reloaded = Image.open(tempfile)
|
||||||
# Gray images appear to come back in palette mode.
|
|
||||||
# They're roughly equivalent
|
|
||||||
if QT_VERSION == 4 and mode == "L":
|
|
||||||
src = src.convert("P")
|
|
||||||
self.assert_image_equal(reloaded, src)
|
self.assert_image_equal(reloaded, src)
|
||||||
|
|
||||||
def test_segfault(self):
|
def test_segfault(self):
|
||||||
|
|
|
@ -45,17 +45,6 @@ Python 2.7 reaches end-of-life on 2020-01-01.
|
||||||
Pillow 7.0.0 will be released on 2020-01-01 and will drop support for Python 2.7, making
|
Pillow 7.0.0 will be released on 2020-01-01 and will drop support for Python 2.7, making
|
||||||
Pillow 6.x the last series to support Python 2.
|
Pillow 6.x the last series to support Python 2.
|
||||||
|
|
||||||
PyQt4 and PySide
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. deprecated:: 6.0.0
|
|
||||||
|
|
||||||
Qt 4 reached end-of-life on 2015-12-19. Its Python bindings are also EOL: PyQt4 since
|
|
||||||
2018-08-31 and PySide since 2015-10-14.
|
|
||||||
|
|
||||||
Support for PyQt4 and PySide has been deprecated from ``ImageQt`` and will be removed in
|
|
||||||
a future version. Please upgrade to PyQt5 or PySide2.
|
|
||||||
|
|
||||||
PIL.*ImagePlugin.__version__ attributes
|
PIL.*ImagePlugin.__version__ attributes
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -81,27 +70,6 @@ Deprecated Deprecated Deprecated
|
||||||
``IptcImagePlugin.__version__`` ``PixarImagePlugin.__version__``
|
``IptcImagePlugin.__version__`` ``PixarImagePlugin.__version__``
|
||||||
=============================== ================================= ==================================
|
=============================== ================================= ==================================
|
||||||
|
|
||||||
Setting the size of TIFF images
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. deprecated:: 5.3.0
|
|
||||||
|
|
||||||
Setting the image size of a TIFF image (eg. ``im.size = (256, 256)``) issues
|
|
||||||
a ``DeprecationWarning``:
|
|
||||||
|
|
||||||
.. code-block:: none
|
|
||||||
|
|
||||||
Setting the size of a TIFF image directly is deprecated, and will
|
|
||||||
be removed in a future version. Use the resize method instead.
|
|
||||||
|
|
||||||
PILLOW_VERSION constant
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. deprecated:: 5.2.0
|
|
||||||
|
|
||||||
``PILLOW_VERSION`` has been deprecated and will be removed in 7.0.0. Use ``__version__``
|
|
||||||
instead.
|
|
||||||
|
|
||||||
ImageCms.CmsProfile attributes
|
ImageCms.CmsProfile attributes
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -128,6 +96,32 @@ Removed features
|
||||||
Deprecated features are only removed in major releases after an appropriate
|
Deprecated features are only removed in major releases after an appropriate
|
||||||
period of deprecation has passed.
|
period of deprecation has passed.
|
||||||
|
|
||||||
|
PILLOW_VERSION constant
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
*Removed in version 7.0.0.*
|
||||||
|
|
||||||
|
``PILLOW_VERSION`` has been removed. Use ``__version__`` instead.
|
||||||
|
|
||||||
|
PyQt4 and PySide
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
*Removed in version 7.0.0.*
|
||||||
|
|
||||||
|
Qt 4 reached end-of-life on 2015-12-19. Its Python bindings are also EOL: PyQt4 since
|
||||||
|
2018-08-31 and PySide since 2015-10-14.
|
||||||
|
|
||||||
|
Support for PyQt4 and PySide has been removed from ``ImageQt``. Please upgrade to PyQt5
|
||||||
|
or PySide2.
|
||||||
|
|
||||||
|
Setting the size of TIFF images
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
*Removed in version 7.0.0.*
|
||||||
|
|
||||||
|
Setting the size of a TIFF image directly (eg. ``im.size = (256, 256)``) throws
|
||||||
|
an error. Use ``Image.resize`` instead.
|
||||||
|
|
||||||
VERSION constant
|
VERSION constant
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -113,8 +113,8 @@ to seek to the next frame (``im.seek(im.tell() + 1)``).
|
||||||
Saving
|
Saving
|
||||||
~~~~~~
|
~~~~~~
|
||||||
|
|
||||||
When calling :py:meth:`~PIL.Image.Image.save`, the following options
|
When calling :py:meth:`~PIL.Image.Image.save` to write a GIF file, the
|
||||||
are available::
|
following options are available::
|
||||||
|
|
||||||
im.save(out, save_all=True, append_images=[im1, im2, ...])
|
im.save(out, save_all=True, append_images=[im1, im2, ...])
|
||||||
|
|
||||||
|
@ -813,8 +813,9 @@ Saving sequences
|
||||||
library is v0.5.0 or later. You can check webp animation support at
|
library is v0.5.0 or later. You can check webp animation support at
|
||||||
runtime by calling ``features.check("webp_anim")``.
|
runtime by calling ``features.check("webp_anim")``.
|
||||||
|
|
||||||
When calling :py:meth:`~PIL.Image.Image.save`, the following options
|
When calling :py:meth:`~PIL.Image.Image.save` to write a WebP file, the
|
||||||
are available when the ``save_all`` argument is present and true.
|
following options are available when the ``save_all`` argument is present and
|
||||||
|
true.
|
||||||
|
|
||||||
**append_images**
|
**append_images**
|
||||||
A list of images to append as additional frames. Each of the
|
A list of images to append as additional frames. Each of the
|
||||||
|
|
|
@ -11,7 +11,7 @@ or the clipboard to a PIL image memory.
|
||||||
|
|
||||||
.. versionadded:: 1.1.3
|
.. versionadded:: 1.1.3
|
||||||
|
|
||||||
.. py:function:: PIL.ImageGrab.grab(bbox=None, include_layered_windows=False)
|
.. py:function:: PIL.ImageGrab.grab(bbox=None, include_layered_windows=False, all_screens=False)
|
||||||
|
|
||||||
Take a snapshot of the screen. The pixels inside the bounding box are
|
Take a snapshot of the screen. The pixels inside the bounding box are
|
||||||
returned as an "RGB" image on Windows or "RGBA" on macOS.
|
returned as an "RGB" image on Windows or "RGBA" on macOS.
|
||||||
|
@ -20,7 +20,13 @@ or the clipboard to a PIL image memory.
|
||||||
.. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS)
|
.. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS)
|
||||||
|
|
||||||
:param bbox: What region to copy. Default is the entire screen.
|
:param bbox: What region to copy. Default is the entire screen.
|
||||||
|
Note that on Windows OS, the top-left point may be negative if ``all_screens=True`` is used.
|
||||||
:param include_layered_windows: Includes layered windows. Windows OS only.
|
:param include_layered_windows: Includes layered windows. Windows OS only.
|
||||||
|
|
||||||
|
.. versionadded:: 6.1.0
|
||||||
|
:param all_screens: Capture all monitors. Windows OS only.
|
||||||
|
|
||||||
|
.. versionadded:: 6.2.0
|
||||||
:return: An image
|
:return: An image
|
||||||
|
|
||||||
.. py:function:: PIL.ImageGrab.grabclipboard()
|
.. py:function:: PIL.ImageGrab.grabclipboard()
|
||||||
|
|
|
@ -8,13 +8,13 @@ The :py:mod:`ImageOps` module contains a number of ‘ready-made’ image
|
||||||
processing operations. This module is somewhat experimental, and most operators
|
processing operations. This module is somewhat experimental, and most operators
|
||||||
only work on L and RGB images.
|
only work on L and RGB images.
|
||||||
|
|
||||||
Only bug fixes have been added since the Pillow fork.
|
|
||||||
|
|
||||||
.. versionadded:: 1.1.3
|
.. versionadded:: 1.1.3
|
||||||
|
|
||||||
.. autofunction:: autocontrast
|
.. autofunction:: autocontrast
|
||||||
.. autofunction:: colorize
|
.. autofunction:: colorize
|
||||||
|
.. autofunction:: pad
|
||||||
.. autofunction:: crop
|
.. autofunction:: crop
|
||||||
|
.. autofunction:: scale
|
||||||
.. autofunction:: deform
|
.. autofunction:: deform
|
||||||
.. autofunction:: equalize
|
.. autofunction:: equalize
|
||||||
.. autofunction:: expand
|
.. autofunction:: expand
|
||||||
|
@ -25,3 +25,4 @@ Only bug fixes have been added since the Pillow fork.
|
||||||
.. autofunction:: mirror
|
.. autofunction:: mirror
|
||||||
.. autofunction:: posterize
|
.. autofunction:: posterize
|
||||||
.. autofunction:: solarize
|
.. autofunction:: solarize
|
||||||
|
.. autofunction:: exif_transpose
|
||||||
|
|
|
@ -4,14 +4,8 @@
|
||||||
:py:mod:`ImageQt` Module
|
:py:mod:`ImageQt` Module
|
||||||
========================
|
========================
|
||||||
|
|
||||||
The :py:mod:`ImageQt` module contains support for creating PyQt4, PyQt5, PySide or
|
The :py:mod:`ImageQt` module contains support for creating PyQt5 or PySide2 QImage
|
||||||
PySide2 QImage objects from PIL images.
|
objects from PIL images.
|
||||||
|
|
||||||
Qt 4 reached end-of-life on 2015-12-19. Its Python bindings are also EOL: PyQt4 since
|
|
||||||
2018-08-31 and PySide since 2015-10-14.
|
|
||||||
|
|
||||||
Support for PyQt4 and PySide is deprecated since Pillow 6.0.0 and will be removed in a
|
|
||||||
future version. Please upgrade to PyQt5 or PySide2.
|
|
||||||
|
|
||||||
.. versionadded:: 1.1.6
|
.. versionadded:: 1.1.6
|
||||||
|
|
||||||
|
@ -20,7 +14,7 @@ future version. Please upgrade to PyQt5 or PySide2.
|
||||||
Creates an :py:class:`~PIL.ImageQt.ImageQt` object from a PIL
|
Creates an :py:class:`~PIL.ImageQt.ImageQt` object from a PIL
|
||||||
:py:class:`~PIL.Image.Image` object. This class is a subclass of
|
:py:class:`~PIL.Image.Image` object. This class is a subclass of
|
||||||
QtGui.QImage, which means that you can pass the resulting objects directly
|
QtGui.QImage, which means that you can pass the resulting objects directly
|
||||||
to PyQt4/PyQt5/PySide API functions and methods.
|
to PyQt5/PySide2 API functions and methods.
|
||||||
|
|
||||||
This operation is currently supported for mode 1, L, P, RGB, and RGBA
|
This operation is currently supported for mode 1, L, P, RGB, and RGBA
|
||||||
images. To handle other modes, you need to convert the image first.
|
images. To handle other modes, you need to convert the image first.
|
||||||
|
|
|
@ -44,6 +44,12 @@ creates the following image:
|
||||||
|
|
||||||
.. image:: ../../Tests/images/imagedraw_stroke_different.png
|
.. image:: ../../Tests/images/imagedraw_stroke_different.png
|
||||||
|
|
||||||
|
ImageGrab on multi-monitor Windows
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
An ``all_screens`` argument has been added to ``ImageGrab.grab``. If ``True``,
|
||||||
|
all monitors will be included in the created image.
|
||||||
|
|
||||||
API Changes
|
API Changes
|
||||||
===========
|
===========
|
||||||
|
|
||||||
|
@ -64,6 +70,13 @@ Python 2.7 reaches end-of-life on 2020-01-01.
|
||||||
Pillow 7.0.0 will be released on 2020-01-01 and will drop support for Python
|
Pillow 7.0.0 will be released on 2020-01-01 and will drop support for Python
|
||||||
2.7, making Pillow 6.2.x the last release series to support Python 2.
|
2.7, making Pillow 6.2.x the last release series to support Python 2.
|
||||||
|
|
||||||
|
Image.frombuffer
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
There has been a longstanding warning that the defaults of ``Image.frombuffer``
|
||||||
|
may change in the future for the "raw" decoder. The change will now take place
|
||||||
|
in Pillow 7.0.
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,9 @@
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
from . import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
from ._binary import i32le as i32
|
from ._binary import i32le as i32
|
||||||
|
@ -61,8 +63,6 @@ def has_ghostscript():
|
||||||
if gs_windows_binary:
|
if gs_windows_binary:
|
||||||
return True
|
return True
|
||||||
if not sys.platform.startswith("win"):
|
if not sys.platform.startswith("win"):
|
||||||
import subprocess
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(os.devnull, "wb") as devnull:
|
with open(os.devnull, "wb") as devnull:
|
||||||
subprocess.check_call(["gs", "--version"], stdout=devnull)
|
subprocess.check_call(["gs", "--version"], stdout=devnull)
|
||||||
|
@ -91,9 +91,6 @@ def Ghostscript(tile, size, fp, scale=1):
|
||||||
float((72.0 * size[1]) / (bbox[3] - bbox[1])),
|
float((72.0 * size[1]) / (bbox[3] - bbox[1])),
|
||||||
)
|
)
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
out_fd, outfile = tempfile.mkstemp()
|
out_fd, outfile = tempfile.mkstemp()
|
||||||
os.close(out_fd)
|
os.close(out_fd)
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,8 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
|
from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
|
||||||
from ._binary import i8, i16le as i16, o8, o16le as o16
|
from ._binary import i8, i16le as i16, o8, o16le as o16
|
||||||
|
@ -265,6 +267,7 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
self.dispose = None
|
self.dispose = None
|
||||||
elif self.disposal_method == 2:
|
elif self.disposal_method == 2:
|
||||||
# replace with background colour
|
# replace with background colour
|
||||||
|
Image._decompression_bomb_check(self.size)
|
||||||
self.dispose = Image.core.fill("P", self.size, self.info["background"])
|
self.dispose = Image.core.fill("P", self.size, self.info["background"])
|
||||||
else:
|
else:
|
||||||
# replace with previous contents
|
# replace with previous contents
|
||||||
|
@ -616,24 +619,22 @@ def _save_netpbm(im, fp, filename):
|
||||||
# If you need real GIF compression and/or RGB quantization, you
|
# If you need real GIF compression and/or RGB quantization, you
|
||||||
# can use the external NETPBM/PBMPLUS utilities. See comments
|
# can use the external NETPBM/PBMPLUS utilities. See comments
|
||||||
# below for information on how to enable this.
|
# below for information on how to enable this.
|
||||||
|
|
||||||
import os
|
|
||||||
from subprocess import Popen, check_call, PIPE, CalledProcessError
|
|
||||||
|
|
||||||
tempfile = im._dump()
|
tempfile = im._dump()
|
||||||
|
|
||||||
with open(filename, "wb") as f:
|
with open(filename, "wb") as f:
|
||||||
if im.mode != "RGB":
|
if im.mode != "RGB":
|
||||||
with open(os.devnull, "wb") as devnull:
|
with open(os.devnull, "wb") as devnull:
|
||||||
check_call(["ppmtogif", tempfile], stdout=f, stderr=devnull)
|
subprocess.check_call(["ppmtogif", tempfile], stdout=f, stderr=devnull)
|
||||||
else:
|
else:
|
||||||
# Pipe ppmquant output into ppmtogif
|
# Pipe ppmquant output into ppmtogif
|
||||||
# "ppmquant 256 %s | ppmtogif > %s" % (tempfile, filename)
|
# "ppmquant 256 %s | ppmtogif > %s" % (tempfile, filename)
|
||||||
quant_cmd = ["ppmquant", "256", tempfile]
|
quant_cmd = ["ppmquant", "256", tempfile]
|
||||||
togif_cmd = ["ppmtogif"]
|
togif_cmd = ["ppmtogif"]
|
||||||
with open(os.devnull, "wb") as devnull:
|
with open(os.devnull, "wb") as devnull:
|
||||||
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=devnull)
|
quant_proc = subprocess.Popen(
|
||||||
togif_proc = Popen(
|
quant_cmd, stdout=subprocess.PIPE, stderr=devnull
|
||||||
|
)
|
||||||
|
togif_proc = subprocess.Popen(
|
||||||
togif_cmd, stdin=quant_proc.stdout, stdout=f, stderr=devnull
|
togif_cmd, stdin=quant_proc.stdout, stdout=f, stderr=devnull
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -642,11 +643,11 @@ def _save_netpbm(im, fp, filename):
|
||||||
|
|
||||||
retcode = quant_proc.wait()
|
retcode = quant_proc.wait()
|
||||||
if retcode:
|
if retcode:
|
||||||
raise CalledProcessError(retcode, quant_cmd)
|
raise subprocess.CalledProcessError(retcode, quant_cmd)
|
||||||
|
|
||||||
retcode = togif_proc.wait()
|
retcode = togif_proc.wait()
|
||||||
if retcode:
|
if retcode:
|
||||||
raise CalledProcessError(retcode, togif_cmd)
|
raise subprocess.CalledProcessError(retcode, togif_cmd)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.unlink(tempfile)
|
os.unlink(tempfile)
|
||||||
|
|
|
@ -19,6 +19,7 @@ import io
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import struct
|
import struct
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
@ -333,11 +334,12 @@ def _save(im, fp, filename):
|
||||||
last_w = w * 2
|
last_w = w * 2
|
||||||
|
|
||||||
# iconutil -c icns -o {} {}
|
# iconutil -c icns -o {} {}
|
||||||
from subprocess import Popen, PIPE, CalledProcessError
|
|
||||||
|
|
||||||
convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset]
|
convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset]
|
||||||
with open(os.devnull, "wb") as devnull:
|
with open(os.devnull, "wb") as devnull:
|
||||||
convert_proc = Popen(convert_cmd, stdout=PIPE, stderr=devnull)
|
convert_proc = subprocess.Popen(
|
||||||
|
convert_cmd, stdout=subprocess.PIPE, stderr=devnull
|
||||||
|
)
|
||||||
|
|
||||||
convert_proc.stdout.close()
|
convert_proc.stdout.close()
|
||||||
|
|
||||||
|
@ -347,7 +349,7 @@ def _save(im, fp, filename):
|
||||||
shutil.rmtree(iconset)
|
shutil.rmtree(iconset)
|
||||||
|
|
||||||
if retcode:
|
if retcode:
|
||||||
raise CalledProcessError(retcode, convert_cmd)
|
raise subprocess.CalledProcessError(retcode, convert_cmd)
|
||||||
|
|
||||||
|
|
||||||
Image.register_open(IcnsImageFile.format, IcnsImageFile, lambda x: x[:4] == b"icns")
|
Image.register_open(IcnsImageFile.format, IcnsImageFile, lambda x: x[:4] == b"icns")
|
||||||
|
|
|
@ -180,6 +180,7 @@ class IcoFile(object):
|
||||||
else:
|
else:
|
||||||
# XOR + AND mask bmp frame
|
# XOR + AND mask bmp frame
|
||||||
im = BmpImagePlugin.DibImageFile(self.buf)
|
im = BmpImagePlugin.DibImageFile(self.buf)
|
||||||
|
Image._decompression_bomb_check(im.size)
|
||||||
|
|
||||||
# change tile dimension to only encompass XOR image
|
# change tile dimension to only encompass XOR image
|
||||||
im._size = (im.size[0], int(im.size[1] / 2))
|
im._size = (im.size[0], int(im.size[1] / 2))
|
||||||
|
|
|
@ -32,12 +32,13 @@ import numbers
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
# VERSION was removed in Pillow 6.0.0.
|
# VERSION was removed in Pillow 6.0.0.
|
||||||
# PILLOW_VERSION is deprecated and will be removed in Pillow 7.0.0.
|
# PILLOW_VERSION was removed in Pillow 7.0.0.
|
||||||
# Use __version__ instead.
|
# Use __version__ instead.
|
||||||
from . import PILLOW_VERSION, ImageMode, TiffTags, __version__, _plugins
|
from . import ImageMode, TiffTags, __version__, _plugins
|
||||||
from ._binary import i8, i32le
|
from ._binary import i8, i32le
|
||||||
from ._util import deferred_error, isPath, isStringType, py3
|
from ._util import deferred_error, isPath, isStringType, py3
|
||||||
|
|
||||||
|
@ -57,9 +58,6 @@ except ImportError:
|
||||||
from collections import Callable, MutableMapping
|
from collections import Callable, MutableMapping
|
||||||
|
|
||||||
|
|
||||||
# Silence warning
|
|
||||||
assert PILLOW_VERSION
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -642,8 +640,6 @@ class Image(object):
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
def _dump(self, file=None, format=None, **options):
|
def _dump(self, file=None, format=None, **options):
|
||||||
import tempfile
|
|
||||||
|
|
||||||
suffix = ""
|
suffix = ""
|
||||||
if format:
|
if format:
|
||||||
suffix = "." + format
|
suffix = "." + format
|
||||||
|
@ -2592,14 +2588,7 @@ def frombuffer(mode, size, data, decoder_name="raw", *args):
|
||||||
|
|
||||||
if decoder_name == "raw":
|
if decoder_name == "raw":
|
||||||
if args == ():
|
if args == ():
|
||||||
warnings.warn(
|
args = mode, 0, 1
|
||||||
"the frombuffer defaults may change in a future release; "
|
|
||||||
"for portability, change the call to read:\n"
|
|
||||||
" frombuffer(mode, size, data, 'raw', mode, 0, 1)",
|
|
||||||
RuntimeWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
args = mode, 0, -1 # may change to (mode, 0, 1) post-1.1.6
|
|
||||||
if args[0] in _MAPMODES:
|
if args[0] in _MAPMODES:
|
||||||
im = new(mode, (1, 1))
|
im = new(mode, (1, 1))
|
||||||
im = im._new(core.map_buffer(data, size, decoder_name, None, 0, args))
|
im = im._new(core.map_buffer(data, size, decoder_name, None, 0, args))
|
||||||
|
|
|
@ -25,8 +25,10 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import base64
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
from . import Image
|
from . import Image
|
||||||
from ._util import isDirectory, isPath, py3
|
from ._util import isDirectory, isPath, py3
|
||||||
|
@ -713,9 +715,6 @@ def load_default():
|
||||||
|
|
||||||
:return: A font object.
|
:return: A font object.
|
||||||
"""
|
"""
|
||||||
from io import BytesIO
|
|
||||||
import base64
|
|
||||||
|
|
||||||
f = ImageFont()
|
f = ImageFont()
|
||||||
f._load_pilfont_data(
|
f._load_pilfont_data(
|
||||||
# courB08
|
# courB08
|
||||||
|
|
|
@ -15,21 +15,18 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
from . import Image
|
from . import Image
|
||||||
|
|
||||||
if sys.platform == "win32":
|
if sys.platform not in ["win32", "darwin"]:
|
||||||
grabber = Image.core.grabscreen
|
|
||||||
elif sys.platform == "darwin":
|
|
||||||
import os
|
|
||||||
import tempfile
|
|
||||||
import subprocess
|
|
||||||
else:
|
|
||||||
raise ImportError("ImageGrab is macOS and Windows only")
|
raise ImportError("ImageGrab is macOS and Windows only")
|
||||||
|
|
||||||
|
|
||||||
def grab(bbox=None, include_layered_windows=False):
|
def grab(bbox=None, include_layered_windows=False, all_screens=False):
|
||||||
if sys.platform == "darwin":
|
if sys.platform == "darwin":
|
||||||
fh, filepath = tempfile.mkstemp(".png")
|
fh, filepath = tempfile.mkstemp(".png")
|
||||||
os.close(fh)
|
os.close(fh)
|
||||||
|
@ -37,8 +34,10 @@ def grab(bbox=None, include_layered_windows=False):
|
||||||
im = Image.open(filepath)
|
im = Image.open(filepath)
|
||||||
im.load()
|
im.load()
|
||||||
os.unlink(filepath)
|
os.unlink(filepath)
|
||||||
|
if bbox:
|
||||||
|
im = im.crop(bbox)
|
||||||
else:
|
else:
|
||||||
size, data = grabber(include_layered_windows)
|
offset, size, data = Image.core.grabscreen(include_layered_windows, all_screens)
|
||||||
im = Image.frombytes(
|
im = Image.frombytes(
|
||||||
"RGB",
|
"RGB",
|
||||||
size,
|
size,
|
||||||
|
@ -49,8 +48,10 @@ def grab(bbox=None, include_layered_windows=False):
|
||||||
(size[0] * 3 + 3) & -4,
|
(size[0] * 3 + 3) & -4,
|
||||||
-1,
|
-1,
|
||||||
)
|
)
|
||||||
if bbox:
|
if bbox:
|
||||||
im = im.crop(bbox)
|
x0, y0 = offset
|
||||||
|
left, top, right, bottom = bbox
|
||||||
|
im = im.crop((left - x0, top - y0, right - x0, bottom - y0))
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -426,7 +426,11 @@ def fit(image, size, method=Image.NEAREST, bleed=0.0, centering=(0.5, 0.5)):
|
||||||
output_ratio = float(size[0]) / size[1]
|
output_ratio = float(size[0]) / size[1]
|
||||||
|
|
||||||
# figure out if the sides or top/bottom will be cropped off
|
# figure out if the sides or top/bottom will be cropped off
|
||||||
if live_size_ratio >= output_ratio:
|
if live_size_ratio == output_ratio:
|
||||||
|
# live_size is already the needed ratio
|
||||||
|
crop_width = live_size[0]
|
||||||
|
crop_height = live_size[1]
|
||||||
|
elif live_size_ratio >= output_ratio:
|
||||||
# live_size is wider than what's needed, crop the sides
|
# live_size is wider than what's needed, crop the sides
|
||||||
crop_width = output_ratio * live_size[1]
|
crop_width = output_ratio * live_size[1]
|
||||||
crop_height = live_size[1]
|
crop_height = live_size[1]
|
||||||
|
|
|
@ -17,18 +17,12 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from . import Image
|
from . import Image
|
||||||
from ._util import isPath, py3
|
from ._util import isPath, py3
|
||||||
|
|
||||||
qt_versions = [["5", "PyQt5"], ["side2", "PySide2"], ["4", "PyQt4"], ["side", "PySide"]]
|
qt_versions = [["5", "PyQt5"], ["side2", "PySide2"]]
|
||||||
|
|
||||||
WARNING_TEXT = (
|
|
||||||
"Support for EOL {} is deprecated and will be removed in a future version. "
|
|
||||||
"Please upgrade to PyQt5 or PySide2."
|
|
||||||
)
|
|
||||||
|
|
||||||
# If a version has already been imported, attempt it first
|
# If a version has already been imported, attempt it first
|
||||||
qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, reverse=True)
|
qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, reverse=True)
|
||||||
|
@ -40,16 +34,6 @@ for qt_version, qt_module in qt_versions:
|
||||||
elif qt_module == "PySide2":
|
elif qt_module == "PySide2":
|
||||||
from PySide2.QtGui import QImage, qRgba, QPixmap
|
from PySide2.QtGui import QImage, qRgba, QPixmap
|
||||||
from PySide2.QtCore import QBuffer, QIODevice
|
from PySide2.QtCore import QBuffer, QIODevice
|
||||||
elif qt_module == "PyQt4":
|
|
||||||
from PyQt4.QtGui import QImage, qRgba, QPixmap
|
|
||||||
from PyQt4.QtCore import QBuffer, QIODevice
|
|
||||||
|
|
||||||
warnings.warn(WARNING_TEXT.format(qt_module), DeprecationWarning)
|
|
||||||
elif qt_module == "PySide":
|
|
||||||
from PySide.QtGui import QImage, qRgba, QPixmap
|
|
||||||
from PySide.QtCore import QBuffer, QIODevice
|
|
||||||
|
|
||||||
warnings.warn(WARNING_TEXT.format(qt_module), DeprecationWarning)
|
|
||||||
except (ImportError, RuntimeError):
|
except (ImportError, RuntimeError):
|
||||||
continue
|
continue
|
||||||
qt_is_installed = True
|
qt_is_installed = True
|
||||||
|
|
|
@ -36,7 +36,10 @@ from __future__ import print_function
|
||||||
|
|
||||||
import array
|
import array
|
||||||
import io
|
import io
|
||||||
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from . import Image, ImageFile, TiffImagePlugin
|
from . import Image, ImageFile, TiffImagePlugin
|
||||||
|
@ -444,10 +447,6 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
# ALTERNATIVE: handle JPEGs via the IJG command line utilities
|
# ALTERNATIVE: handle JPEGs via the IJG command line utilities
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
import os
|
|
||||||
|
|
||||||
f, path = tempfile.mkstemp()
|
f, path = tempfile.mkstemp()
|
||||||
os.close(f)
|
os.close(f)
|
||||||
if os.path.exists(self.filename):
|
if os.path.exists(self.filename):
|
||||||
|
@ -772,9 +771,6 @@ def _save(im, fp, filename):
|
||||||
|
|
||||||
def _save_cjpeg(im, fp, filename):
|
def _save_cjpeg(im, fp, filename):
|
||||||
# ALTERNATIVE: handle JPEGs via the IJG command line utilities.
|
# ALTERNATIVE: handle JPEGs via the IJG command line utilities.
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
tempfile = im._dump()
|
tempfile = im._dump()
|
||||||
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
|
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -224,9 +224,11 @@ def _layerinfo(file):
|
||||||
# skip over blend flags and extra information
|
# skip over blend flags and extra information
|
||||||
read(12) # filler
|
read(12) # filler
|
||||||
name = ""
|
name = ""
|
||||||
size = i32(read(4))
|
size = i32(read(4)) # length of the extra data field
|
||||||
combined = 0
|
combined = 0
|
||||||
if size:
|
if size:
|
||||||
|
data_end = file.tell() + size
|
||||||
|
|
||||||
length = i32(read(4))
|
length = i32(read(4))
|
||||||
if length:
|
if length:
|
||||||
file.seek(length - 16, io.SEEK_CUR)
|
file.seek(length - 16, io.SEEK_CUR)
|
||||||
|
@ -244,7 +246,7 @@ def _layerinfo(file):
|
||||||
name = read(length).decode("latin-1", "replace")
|
name = read(length).decode("latin-1", "replace")
|
||||||
combined += length + 1
|
combined += length + 1
|
||||||
|
|
||||||
file.seek(size - combined, io.SEEK_CUR)
|
file.seek(data_end)
|
||||||
layers.append((name, mode, (x0, y0, x1, y1)))
|
layers.append((name, mode, (x0, y0, x1, y1)))
|
||||||
|
|
||||||
# get tiles
|
# get tiles
|
||||||
|
|
|
@ -236,7 +236,7 @@ def loadImageSeries(filelist=None):
|
||||||
def makeSpiderHeader(im):
|
def makeSpiderHeader(im):
|
||||||
nsam, nrow = im.size
|
nsam, nrow = im.size
|
||||||
lenbyt = nsam * 4 # There are labrec records in the header
|
lenbyt = nsam * 4 # There are labrec records in the header
|
||||||
labrec = 1024 / lenbyt
|
labrec = int(1024 / lenbyt)
|
||||||
if 1024 % lenbyt != 0:
|
if 1024 % lenbyt != 0:
|
||||||
labrec += 1
|
labrec += 1
|
||||||
labbyt = labrec * lenbyt
|
labbyt = labrec * lenbyt
|
||||||
|
|
|
@ -854,7 +854,7 @@ class ImageFileDirectory_v2(MutableMapping):
|
||||||
|
|
||||||
# pass 2: write entries to file
|
# pass 2: write entries to file
|
||||||
for tag, typ, count, value, data in entries:
|
for tag, typ, count, value, data in entries:
|
||||||
if DEBUG > 1:
|
if DEBUG:
|
||||||
print(tag, typ, count, repr(value), repr(data))
|
print(tag, typ, count, repr(value), repr(data))
|
||||||
result += self._pack("HHL4s", tag, typ, count, value)
|
result += self._pack("HHL4s", tag, typ, count, value)
|
||||||
|
|
||||||
|
@ -1079,19 +1079,6 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
"""Return the current frame number"""
|
"""Return the current frame number"""
|
||||||
return self.__frame
|
return self.__frame
|
||||||
|
|
||||||
@property
|
|
||||||
def size(self):
|
|
||||||
return self._size
|
|
||||||
|
|
||||||
@size.setter
|
|
||||||
def size(self, value):
|
|
||||||
warnings.warn(
|
|
||||||
"Setting the size of a TIFF image directly is deprecated, and will"
|
|
||||||
" be removed in a future version. Use the resize method instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
self._size = value
|
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
if self.use_load_libtiff:
|
if self.use_load_libtiff:
|
||||||
return self._load_libtiff()
|
return self._load_libtiff()
|
||||||
|
@ -1239,8 +1226,8 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
print("- YCbCr subsampling:", self.tag.get(530))
|
print("- YCbCr subsampling:", self.tag.get(530))
|
||||||
|
|
||||||
# size
|
# size
|
||||||
xsize = self.tag_v2.get(IMAGEWIDTH)
|
xsize = int(self.tag_v2.get(IMAGEWIDTH))
|
||||||
ysize = self.tag_v2.get(IMAGELENGTH)
|
ysize = int(self.tag_v2.get(IMAGELENGTH))
|
||||||
self._size = xsize, ysize
|
self._size = xsize, ysize
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
|
|
|
@ -120,7 +120,7 @@ TAGS_V2 = {
|
||||||
277: ("SamplesPerPixel", SHORT, 1),
|
277: ("SamplesPerPixel", SHORT, 1),
|
||||||
278: ("RowsPerStrip", LONG, 1),
|
278: ("RowsPerStrip", LONG, 1),
|
||||||
279: ("StripByteCounts", LONG, 0),
|
279: ("StripByteCounts", LONG, 0),
|
||||||
280: ("MinSampleValue", LONG, 0),
|
280: ("MinSampleValue", SHORT, 0),
|
||||||
281: ("MaxSampleValue", SHORT, 0),
|
281: ("MaxSampleValue", SHORT, 0),
|
||||||
282: ("XResolution", RATIONAL, 1),
|
282: ("XResolution", RATIONAL, 1),
|
||||||
283: ("YResolution", RATIONAL, 1),
|
283: ("YResolution", RATIONAL, 1),
|
||||||
|
@ -182,7 +182,7 @@ TAGS_V2 = {
|
||||||
# FIXME add more tags here
|
# FIXME add more tags here
|
||||||
34665: ("ExifIFD", LONG, 1),
|
34665: ("ExifIFD", LONG, 1),
|
||||||
34675: ("ICCProfile", UNDEFINED, 1),
|
34675: ("ICCProfile", UNDEFINED, 1),
|
||||||
34853: ("GPSInfoIFD", BYTE, 1),
|
34853: ("GPSInfoIFD", LONG, 1),
|
||||||
# MPInfo
|
# MPInfo
|
||||||
45056: ("MPFVersion", UNDEFINED, 1),
|
45056: ("MPFVersion", UNDEFINED, 1),
|
||||||
45057: ("NumberOfImages", LONG, 1),
|
45057: ("NumberOfImages", LONG, 1),
|
||||||
|
|
|
@ -9,7 +9,6 @@ PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
|
||||||
Copyright (c) 1999 by Secret Labs AB.
|
Copyright (c) 1999 by Secret Labs AB.
|
||||||
|
|
||||||
Use PIL.__version__ for this Pillow version.
|
Use PIL.__version__ for this Pillow version.
|
||||||
PIL.VERSION is the old PIL version and will be removed in the future.
|
|
||||||
|
|
||||||
;-)
|
;-)
|
||||||
"""
|
"""
|
||||||
|
@ -17,9 +16,9 @@ PIL.VERSION is the old PIL version and will be removed in the future.
|
||||||
from . import _version
|
from . import _version
|
||||||
|
|
||||||
# VERSION was removed in Pillow 6.0.0.
|
# VERSION was removed in Pillow 6.0.0.
|
||||||
# PILLOW_VERSION is deprecated and will be removed in Pillow 7.0.0.
|
# PILLOW_VERSION was removed in Pillow 7.0.0.
|
||||||
# Use __version__ instead.
|
# Use __version__ instead.
|
||||||
PILLOW_VERSION = __version__ = _version.__version__
|
__version__ = _version.__version__
|
||||||
|
|
||||||
del _version
|
del _version
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
# Master version for Pillow
|
# Master version for Pillow
|
||||||
__version__ = "6.2.0.dev0"
|
__version__ = "7.0.0.dev0"
|
||||||
|
|
|
@ -319,18 +319,23 @@ PyImaging_DisplayModeWin32(PyObject* self, PyObject* args)
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
/* Windows screen grabber */
|
/* Windows screen grabber */
|
||||||
|
|
||||||
|
typedef HANDLE(__stdcall* Func_SetThreadDpiAwarenessContext)(HANDLE);
|
||||||
|
|
||||||
PyObject*
|
PyObject*
|
||||||
PyImaging_GrabScreenWin32(PyObject* self, PyObject* args)
|
PyImaging_GrabScreenWin32(PyObject* self, PyObject* args)
|
||||||
{
|
{
|
||||||
int width, height;
|
int x = 0, y = 0, width, height;
|
||||||
int includeLayeredWindows = 0;
|
int includeLayeredWindows = 0, all_screens = 0;
|
||||||
HBITMAP bitmap;
|
HBITMAP bitmap;
|
||||||
BITMAPCOREHEADER core;
|
BITMAPCOREHEADER core;
|
||||||
HDC screen, screen_copy;
|
HDC screen, screen_copy;
|
||||||
DWORD rop;
|
DWORD rop;
|
||||||
PyObject* buffer;
|
PyObject* buffer;
|
||||||
|
HANDLE dpiAwareness;
|
||||||
|
HMODULE user32;
|
||||||
|
Func_SetThreadDpiAwarenessContext SetThreadDpiAwarenessContext_function;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "|i", &includeLayeredWindows))
|
if (!PyArg_ParseTuple(args, "|ii", &includeLayeredWindows, &all_screens))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
/* step 1: create a memory DC large enough to hold the
|
/* step 1: create a memory DC large enough to hold the
|
||||||
|
@ -339,8 +344,32 @@ PyImaging_GrabScreenWin32(PyObject* self, PyObject* args)
|
||||||
screen = CreateDC("DISPLAY", NULL, NULL, NULL);
|
screen = CreateDC("DISPLAY", NULL, NULL, NULL);
|
||||||
screen_copy = CreateCompatibleDC(screen);
|
screen_copy = CreateCompatibleDC(screen);
|
||||||
|
|
||||||
width = GetDeviceCaps(screen, HORZRES);
|
// added in Windows 10 (1607)
|
||||||
height = GetDeviceCaps(screen, VERTRES);
|
// loaded dynamically to avoid link errors
|
||||||
|
user32 = LoadLibraryA("User32.dll");
|
||||||
|
SetThreadDpiAwarenessContext_function =
|
||||||
|
(Func_SetThreadDpiAwarenessContext)
|
||||||
|
GetProcAddress(user32, "SetThreadDpiAwarenessContext");
|
||||||
|
if (SetThreadDpiAwarenessContext_function != NULL) {
|
||||||
|
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = ((DPI_CONTEXT_HANDLE)-3)
|
||||||
|
dpiAwareness = SetThreadDpiAwarenessContext_function((HANDLE) -3);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (all_screens) {
|
||||||
|
x = GetSystemMetrics(SM_XVIRTUALSCREEN);
|
||||||
|
y = GetSystemMetrics(SM_YVIRTUALSCREEN);
|
||||||
|
width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
||||||
|
height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
||||||
|
} else {
|
||||||
|
width = GetDeviceCaps(screen, HORZRES);
|
||||||
|
height = GetDeviceCaps(screen, VERTRES);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SetThreadDpiAwarenessContext_function != NULL) {
|
||||||
|
SetThreadDpiAwarenessContext_function(dpiAwareness);
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeLibrary(user32);
|
||||||
|
|
||||||
bitmap = CreateCompatibleBitmap(screen, width, height);
|
bitmap = CreateCompatibleBitmap(screen, width, height);
|
||||||
if (!bitmap)
|
if (!bitmap)
|
||||||
|
@ -354,7 +383,7 @@ PyImaging_GrabScreenWin32(PyObject* self, PyObject* args)
|
||||||
rop = SRCCOPY;
|
rop = SRCCOPY;
|
||||||
if (includeLayeredWindows)
|
if (includeLayeredWindows)
|
||||||
rop |= CAPTUREBLT;
|
rop |= CAPTUREBLT;
|
||||||
if (!BitBlt(screen_copy, 0, 0, width, height, screen, 0, 0, rop))
|
if (!BitBlt(screen_copy, 0, 0, width, height, screen, x, y, rop))
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
/* step 3: extract bits from bitmap */
|
/* step 3: extract bits from bitmap */
|
||||||
|
@ -376,7 +405,7 @@ PyImaging_GrabScreenWin32(PyObject* self, PyObject* args)
|
||||||
DeleteDC(screen_copy);
|
DeleteDC(screen_copy);
|
||||||
DeleteDC(screen);
|
DeleteDC(screen);
|
||||||
|
|
||||||
return Py_BuildValue("(ii)N", width, height, buffer);
|
return Py_BuildValue("(ii)(ii)N", x, y, width, height, buffer);
|
||||||
|
|
||||||
error:
|
error:
|
||||||
PyErr_SetString(PyExc_IOError, "screen grab failed");
|
PyErr_SetString(PyExc_IOError, "screen grab failed");
|
||||||
|
|
|
@ -30,7 +30,7 @@ ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t byt
|
||||||
{
|
{
|
||||||
UINT8* ptr;
|
UINT8* ptr;
|
||||||
int framesize;
|
int framesize;
|
||||||
int c, chunks;
|
int c, chunks, advance;
|
||||||
int l, lines;
|
int l, lines;
|
||||||
int i, j, x = 0, y, ymax;
|
int i, j, x = 0, y, ymax;
|
||||||
|
|
||||||
|
@ -59,10 +59,16 @@ ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t byt
|
||||||
|
|
||||||
chunks = I16(ptr+6);
|
chunks = I16(ptr+6);
|
||||||
ptr += 16;
|
ptr += 16;
|
||||||
|
bytes -= 16;
|
||||||
|
|
||||||
/* Process subchunks */
|
/* Process subchunks */
|
||||||
for (c = 0; c < chunks; c++) {
|
for (c = 0; c < chunks; c++) {
|
||||||
UINT8 *data = ptr + 6;
|
UINT8* data;
|
||||||
|
if (bytes < 10) {
|
||||||
|
state->errcode = IMAGING_CODEC_OVERRUN;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
data = ptr + 6;
|
||||||
switch (I16(ptr+4)) {
|
switch (I16(ptr+4)) {
|
||||||
case 4: case 11:
|
case 4: case 11:
|
||||||
/* FLI COLOR chunk */
|
/* FLI COLOR chunk */
|
||||||
|
@ -198,7 +204,9 @@ ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t byt
|
||||||
state->errcode = IMAGING_CODEC_UNKNOWN;
|
state->errcode = IMAGING_CODEC_UNKNOWN;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
ptr += I32(ptr);
|
advance = I32(ptr);
|
||||||
|
ptr += advance;
|
||||||
|
bytes -= advance;
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1; /* end of frame */
|
return -1; /* end of frame */
|
||||||
|
|
|
@ -22,6 +22,11 @@ ImagingPcxDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t byt
|
||||||
UINT8 n;
|
UINT8 n;
|
||||||
UINT8* ptr;
|
UINT8* ptr;
|
||||||
|
|
||||||
|
if (strcmp(im->mode, "1") == 0 && state->xsize > state->bytes * 8) {
|
||||||
|
state->errcode = IMAGING_CODEC_OVERRUN;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
ptr = buf;
|
ptr = buf;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|
|
@ -33,8 +33,15 @@ ImagingRawDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t byt
|
||||||
|
|
||||||
/* get size of image data and padding */
|
/* get size of image data and padding */
|
||||||
state->bytes = (state->xsize * state->bits + 7) / 8;
|
state->bytes = (state->xsize * state->bits + 7) / 8;
|
||||||
rawstate->skip = (rawstate->stride) ?
|
if (rawstate->stride) {
|
||||||
rawstate->stride - state->bytes : 0;
|
rawstate->skip = rawstate->stride - state->bytes;
|
||||||
|
if (rawstate->skip < 0) {
|
||||||
|
state->errcode = IMAGING_CODEC_CONFIG;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rawstate->skip = 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* check image orientation */
|
/* check image orientation */
|
||||||
if (state->ystep < 0) {
|
if (state->ystep < 0) {
|
||||||
|
|
|
@ -157,6 +157,11 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state,
|
||||||
c->rlelength = c->lengthtab[c->rowno + c->channo * im->ysize];
|
c->rlelength = c->lengthtab[c->rowno + c->channo * im->ysize];
|
||||||
c->rleoffset -= SGI_HEADER_SIZE;
|
c->rleoffset -= SGI_HEADER_SIZE;
|
||||||
|
|
||||||
|
if (c->rleoffset + c->rlelength > c->bufsize) {
|
||||||
|
state->errcode = IMAGING_CODEC_OVERRUN;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/* row decompression */
|
/* row decompression */
|
||||||
if (c->bpc ==1) {
|
if (c->bpc ==1) {
|
||||||
if(expandrow(&state->buffer[c->channo], &ptr[c->rleoffset], c->rlelength, im->bands))
|
if(expandrow(&state->buffer[c->channo], &ptr[c->rleoffset], c->rlelength, im->bands))
|
||||||
|
|
|
@ -405,8 +405,12 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, Py_
|
||||||
UINT32 strip_row, row_byte_size;
|
UINT32 strip_row, row_byte_size;
|
||||||
UINT8 *new_data;
|
UINT8 *new_data;
|
||||||
UINT32 rows_per_strip;
|
UINT32 rows_per_strip;
|
||||||
|
int ret;
|
||||||
|
|
||||||
TIFFGetField(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip);
|
ret = TIFFGetField(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip);
|
||||||
|
if (ret != 1) {
|
||||||
|
rows_per_strip = state->ysize;
|
||||||
|
}
|
||||||
TRACE(("RowsPerStrip: %u \n", rows_per_strip));
|
TRACE(("RowsPerStrip: %u \n", rows_per_strip));
|
||||||
|
|
||||||
// We could use TIFFStripSize, but for YCbCr data it returns subsampled data size
|
// We could use TIFFStripSize, but for YCbCr data it returns subsampled data size
|
||||||
|
|
3
winbuild/appveyor_install_pypy3.cmd
Normal file
3
winbuild/appveyor_install_pypy3.cmd
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
curl -fsSL -o pypy3.zip http://buildbot.pypy.org/nightly/py3.6/pypy-c-jit-97588-7392d01b93d0-win32.zip
|
||||||
|
7z x pypy3.zip -oc:\
|
||||||
|
c:\Python37\Scripts\virtualenv.exe -p c:\pypy-c-jit-97588-7392d01b93d0-win32\pypy3.exe c:\vp\pypy3
|
|
@ -9,6 +9,7 @@ pythons = {
|
||||||
"pypy2": {"compiler": 7, "vc": 2010},
|
"pypy2": {"compiler": 7, "vc": 2010},
|
||||||
"35": {"compiler": 7.1, "vc": 2015},
|
"35": {"compiler": 7.1, "vc": 2015},
|
||||||
"36": {"compiler": 7.1, "vc": 2015},
|
"36": {"compiler": 7.1, "vc": 2015},
|
||||||
|
"pypy3": {"compiler": 7.1, "vc": 2015},
|
||||||
"37": {"compiler": 7.1, "vc": 2015},
|
"37": {"compiler": 7.1, "vc": 2015},
|
||||||
# for GitHub Actions
|
# for GitHub Actions
|
||||||
"3.5": {"compiler": 7.1, "vc": 2015},
|
"3.5": {"compiler": 7.1, "vc": 2015},
|
||||||
|
|
Loading…
Reference in New Issue
Block a user