Merge branch 'python-pillow:main' into eps_plugin_perf

This commit is contained in:
Yay295 2023-03-29 10:27:25 -05:00 committed by GitHub
commit b8b153fd77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
115 changed files with 1392 additions and 609 deletions

View File

@ -27,8 +27,8 @@ install:
- mv c:\pillow-depends-main c:\pillow-depends
- xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images
- 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\
- ..\pillow-depends\gs1000w32.exe /S
- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs10.0.0\bin;%PATH%
- choco install ghostscript --version=10.0.0.20230317
- path c:\nasm-2.15.05;C:\Program Files\gs\gs10.00.0\bin;%PATH%
- cd c:\pillow\winbuild\
- ps: |
c:\python37\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\

View File

@ -3,10 +3,12 @@ name: CIFuzz
on:
push:
paths:
- ".github/workflows/cifuzz.yml"
- "**.c"
- "**.h"
pull_request:
paths:
- ".github/workflows/cifuzz.yml"
- "**.c"
- "**.h"
workflow_dispatch:
@ -14,7 +16,7 @@ on:
permissions:
contents: read
concurrency:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

55
.github/workflows/docs.yml vendored Normal file
View File

@ -0,0 +1,55 @@
name: Docs
on:
push:
paths:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
FORCE_COLOR: 1
jobs:
build:
runs-on: ubuntu-latest
name: Docs
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.x"
cache: pip
cache-dependency-path: ".ci/*.sh"
- name: Build system information
run: python3 .github/workflows/system-info.py
- name: Install Linux dependencies
run: |
.ci/install.sh
env:
GHA_PYTHON_VERSION: "3.x"
- name: Build
run: |
.ci/build.sh
- name: Docs
run: |
make doccheck

View File

@ -20,7 +20,7 @@ jobs:
steps:
- name: "Check issues"
uses: actions/stale@v7
uses: actions/stale@v8
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
only-labels: "Awaiting OP Action"

View File

@ -1,6 +1,15 @@
name: Test Cygwin
on: [push, pull_request, workflow_dispatch]
on:
push:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions:
contents: read

View File

@ -1,6 +1,15 @@
name: Test Docker
on: [push, pull_request, workflow_dispatch]
on:
push:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions:
contents: read
@ -24,11 +33,11 @@ jobs:
# Then run the remainder
alpine,
amazon-2-amd64,
amazon-2023-amd64,
arch,
centos-7-amd64,
centos-stream-8-amd64,
centos-stream-9-amd64,
debian-10-buster-x86,
debian-11-bullseye-x86,
fedora-36-amd64,
fedora-37-amd64,

View File

@ -1,6 +1,15 @@
name: Test MinGW
on: [push, pull_request, workflow_dispatch]
on:
push:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions:
contents: read

View File

@ -5,10 +5,12 @@ name: Test Valgrind
on:
push:
paths:
- ".github/workflows/test-valgrind.yml"
- "**.c"
- "**.h"
pull_request:
paths:
- ".github/workflows/test-valgrind.yml"
- "**.c"
- "**.h"
workflow_dispatch:
@ -16,7 +18,7 @@ on:
permissions:
contents: read
concurrency:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

View File

@ -1,6 +1,15 @@
name: Test Windows
on: [push, pull_request, workflow_dispatch]
on:
push:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions:
contents: read
@ -65,8 +74,8 @@ jobs:
7z x winbuild\depends\nasm-2.15.05-win64.zip "-o$env:RUNNER_WORKSPACE\"
echo "$env:RUNNER_WORKSPACE\nasm-2.15.05" >> $env:GITHUB_PATH
winbuild\depends\gs1000w32.exe /S
echo "C:\Program Files (x86)\gs\gs10.0.0\bin" >> $env:GITHUB_PATH
choco install ghostscript --version=10.0.0.20230317
echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH
# Install extra test images
xcopy /S /Y Tests\test-images\* Tests\images
@ -88,7 +97,7 @@ jobs:
- name: Prepare build
if: steps.build-cache.outputs.cache-hit != 'true'
run: |
& python.exe winbuild\build_prepare.py -v --python=$env:pythonLocation --srcdir
& python.exe winbuild\build_prepare.py -v --python $env:pythonLocation
shell: pwsh
- name: Build dependencies / libjpeg-turbo

View File

@ -1,6 +1,15 @@
name: Test
on: [push, pull_request, workflow_dispatch]
on:
push:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions:
contents: read
@ -96,11 +105,6 @@ jobs:
name: errors
path: Tests/errors
- name: Docs
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.11
run: |
make doccheck
- name: After success
run: |
.ci/after_success.sh

View File

@ -5,6 +5,54 @@ Changelog (Pillow)
9.5.0 (unreleased)
------------------
- Support custom comments and PLT markers when saving JPEG2000 images #6903
[joshware, radarhere, hugovk]
- Load before getting size in __array_interface__ #7034
[radarhere]
- Support creating BGR;15, BGR;16 and BGR;24 images, but drop support for BGR;32 #7010
[radarhere]
- Consider transparency when applying APNG blend mask #7018
[radarhere]
- Round duration when saving animated WebP images #6996
[radarhere]
- Added reading of JPEG2000 comments #6909
[radarhere]
- Decrement reference count #7003
[radarhere, nulano]
- Allow libtiff_support_custom_tags to be missing #7020
[radarhere]
- Improved I;16N support #6834
[radarhere]
- Added QOI reading #6852
[radarhere, hugovk]
- Added saving RGBA images as PDFs #6925
[radarhere]
- Do not raise an error if os.environ does not contain PATH #6935
[radarhere, hugovk]
- Close OleFileIO instance when closing or exiting FPX or MIC #7005
[radarhere]
- Added __int__ to IFDRational for Python >= 3.11 #6998
[radarhere]
- Added memoryview support to Dib.frombytes() #6988
[radarhere, nulano]
- Close file pointer copy in the libtiff encoder if still open #6986
[fcarron, radarhere]
- Raise an error if ImageDraw co-ordinates are incorrectly ordered #6978
[radarhere]

View File

@ -16,10 +16,16 @@ coverage:
python3 -m coverage report
.PHONY: doc
doc:
.PHONY: html
doc html:
python3 -c "import PIL" > /dev/null 2>&1 || python3 -m pip install .
$(MAKE) -C docs html
.PHONY: htmlview
htmlview:
python3 -c "import PIL" > /dev/null 2>&1 || python3 -m pip install .
$(MAKE) -C docs htmlview
.PHONY: doccheck
doccheck:
$(MAKE) doc
@ -38,7 +44,8 @@ help:
@echo " coverage run coverage test (in progress)"
@echo " doc make HTML docs"
@echo " docserve run an HTTP server on the docs directory"
@echo " html to make standalone HTML files"
@echo " html make HTML docs"
@echo " htmlview open the index page built by the html target in your browser"
@echo " inplace make inplace extension"
@echo " install make and install"
@echo " install-coverage make and install with C coverage"

View File

@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
HAS_UPLOADER = False
if os.environ.get("SHOW_ERRORS", None):
if os.environ.get("SHOW_ERRORS"):
# local img.show for errors.
HAS_UPLOADER = True
@ -271,7 +271,7 @@ def netpbm_available():
def magick_command():
if sys.platform == "win32":
magickhome = os.environ.get("MAGICK_HOME", "")
magickhome = os.environ.get("MAGICK_HOME")
if magickhome:
imagemagick = [os.path.join(magickhome, "convert.exe")]
graphicsmagick = [os.path.join(magickhome, "gm.exe"), "convert"]

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

BIN
Tests/images/comment.jp2 Normal file

Binary file not shown.

BIN
Tests/images/hopper.qoi Normal file

Binary file not shown.

BIN
Tests/images/pil123rgba.qoi Normal file

Binary file not shown.

View File

@ -177,13 +177,14 @@ class TestEnvVars:
Image._apply_env_variables({"PILLOW_BLOCK_SIZE": "2m"})
assert Image.core.get_block_size() == 2 * 1024 * 1024
def test_warnings(self):
pytest.warns(
UserWarning, Image._apply_env_variables, {"PILLOW_ALIGNMENT": "15"}
)
pytest.warns(
UserWarning, Image._apply_env_variables, {"PILLOW_BLOCK_SIZE": "1024"}
)
pytest.warns(
UserWarning, Image._apply_env_variables, {"PILLOW_BLOCKS_MAX": "wat"}
)
@pytest.mark.parametrize(
"var",
(
{"PILLOW_ALIGNMENT": "15"},
{"PILLOW_BLOCK_SIZE": "1024"},
{"PILLOW_BLOCKS_MAX": "wat"},
),
)
def test_warnings(self, var):
with pytest.warns(UserWarning):
Image._apply_env_variables(var)

View File

@ -36,12 +36,10 @@ class TestDecompressionBomb:
Image.MAX_IMAGE_PIXELS = 128 * 128 - 1
assert Image.MAX_IMAGE_PIXELS == 128 * 128 - 1
def open():
with pytest.warns(Image.DecompressionBombWarning):
with Image.open(TEST_FILE):
pass
pytest.warns(Image.DecompressionBombWarning, open)
def test_exception(self):
# Set limit to trigger exception on the test file
Image.MAX_IMAGE_PIXELS = 64 * 128 - 1
@ -87,7 +85,8 @@ class TestDecompressionCrop:
# same decompression bomb warnings on them.
with hopper() as src:
box = (0, 0, src.width * 2, src.height * 2)
pytest.warns(Image.DecompressionBombWarning, src.crop, box)
with pytest.warns(Image.DecompressionBombWarning):
src.crop(box)
def test_crop_decompression_checks(self):
im = Image.new("RGB", (100, 100))
@ -95,7 +94,8 @@ class TestDecompressionCrop:
for value in ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990)):
assert im.crop(value).size == (9, 9)
pytest.warns(Image.DecompressionBombWarning, im.crop, (-160, -160, 99, 99))
with pytest.warns(Image.DecompressionBombWarning):
im.crop((-160, -160, 99, 99))
with pytest.raises(Image.DecompressionBombError):
im.crop((-99909, -99990, 99999, 99999))

View File

@ -163,6 +163,12 @@ def test_apng_blend():
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
def test_apng_blend_transparency():
with Image.open("Tests/images/blend_transparency.png") as im:
im.seek(1)
assert im.getpixel((0, 0)) == (255, 0, 0)
def test_apng_chunk_order():
with Image.open("Tests/images/apng/fctl_actl.png") as im:
im.seek(im.n_frames - 1)
@ -263,13 +269,11 @@ def test_apng_chunk_errors():
with Image.open("Tests/images/apng/chunk_no_actl.png") as im:
assert not im.is_animated
def open():
with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/chunk_multi_actl.png") as im:
im.load()
assert not im.is_animated
pytest.warns(UserWarning, open)
with Image.open("Tests/images/apng/chunk_actl_after_idat.png") as im:
assert not im.is_animated
@ -287,21 +291,17 @@ def test_apng_chunk_errors():
def test_apng_syntax_errors():
def open_frames_zero():
with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_zero.png") as im:
assert not im.is_animated
with pytest.raises(OSError):
im.load()
pytest.warns(UserWarning, open_frames_zero)
def open_frames_zero_default():
with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_zero_default.png") as im:
assert not im.is_animated
im.load()
pytest.warns(UserWarning, open_frames_zero_default)
# we can handle this case gracefully
exception = None
with Image.open("Tests/images/apng/syntax_num_frames_low.png") as im:
@ -316,13 +316,11 @@ def test_apng_syntax_errors():
im.seek(im.n_frames - 1)
im.load()
def open():
with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_invalid.png") as im:
assert not im.is_animated
im.load()
pytest.warns(UserWarning, open)
@pytest.mark.parametrize(
"test_file",

View File

@ -56,6 +56,7 @@ def test_handler(tmp_path):
def load(self, im):
self.loaded = True
im.fp.close()
return Image.new("RGB", (1, 1))
def save(self, im, fp, filename):

View File

@ -28,7 +28,8 @@ def test_unclosed_file():
im = Image.open(TEST_FILE)
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():

View File

@ -60,6 +60,7 @@ def test_stub_deprecated():
def load(self, im):
self.loaded = True
im.fp.close()
return Image.new("RGB", (1, 1))
handler = Handler()

View File

@ -36,7 +36,8 @@ def test_unclosed_file():
im = Image.open(static_test_file)
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():

View File

@ -18,6 +18,16 @@ def test_sanity():
assert_image_equal_tofile(im, "Tests/images/input_bw_one_band.png")
def test_close():
with Image.open("Tests/images/input_bw_one_band.fpx") as im:
pass
assert im.ole.fp.closed
im = Image.open("Tests/images/input_bw_one_band.fpx")
im.close()
assert im.ole.fp.closed
def test_invalid_file():
# Test an invalid OLE file
invalid_file = "Tests/images/flower.jpg"

View File

@ -36,7 +36,8 @@ def test_unclosed_file():
im = Image.open(TEST_GIF)
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():
@ -1087,7 +1088,8 @@ def test_rgb_transparency(tmp_path):
im = Image.new("RGB", (1, 1))
im.info["transparency"] = b""
ims = [Image.new("RGB", (1, 1))]
pytest.warns(UserWarning, im.save, out, save_all=True, append_images=ims)
with pytest.warns(UserWarning):
im.save(out, save_all=True, append_images=ims)
with Image.open(out) as reloaded:
assert "transparency" not in reloaded.info

View File

@ -56,6 +56,7 @@ def test_handler(tmp_path):
def load(self, im):
self.loaded = True
im.fp.close()
return Image.new("RGB", (1, 1))
def save(self, im, fp, filename):

View File

@ -57,6 +57,7 @@ def test_handler(tmp_path):
def load(self, im):
self.loaded = True
im.fp.close()
return Image.new("RGB", (1, 1))
def save(self, im, fp, filename):

View File

@ -212,12 +212,10 @@ def test_save_append_images(tmp_path):
def test_unexpected_size():
# This image has been manually hexedited to state that it is 16x32
# while the image within is still 16x16
def open():
with pytest.warns(UserWarning):
with Image.open("Tests/images/hopper_unexpected.ico") as im:
assert im.size == (16, 16)
pytest.warns(UserWarning, open)
def test_draw_reloaded(tmp_path):
with Image.open(TEST_ICO_FILE) as im:

View File

@ -32,7 +32,8 @@ def test_unclosed_file():
im = Image.open(TEST_IM)
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():

View File

@ -4,13 +4,21 @@ from io import BytesIO
import pytest
from PIL import Image, ImageFile, Jpeg2KImagePlugin, UnidentifiedImageError, features
from PIL import (
Image,
ImageFile,
Jpeg2KImagePlugin,
UnidentifiedImageError,
_binary,
features,
)
from .helper import (
assert_image_equal,
assert_image_similar,
assert_image_similar_tofile,
skip_unless_feature,
skip_unless_feature_version,
)
EXTRA_DIR = "Tests/images/jpeg2000"
@ -353,6 +361,35 @@ def test_subsampling_decode(name):
assert_image_similar(im, expected, epsilon)
def test_comment():
with Image.open("Tests/images/comment.jp2") as im:
assert im.info["comment"] == b"Created by OpenJPEG version 2.5.0"
# Test an image that is truncated partway through a codestream
with open("Tests/images/comment.jp2", "rb") as fp:
b = BytesIO(fp.read(130))
with Image.open(b) as im:
pass
def test_save_comment():
for comment in ("Created by Pillow", b"Created by Pillow"):
out = BytesIO()
test_card.save(out, "JPEG2000", comment=comment)
with Image.open(out) as im:
assert im.info["comment"] == b"Created by Pillow"
out = BytesIO()
long_comment = b" " * 65531
test_card.save(out, "JPEG2000", comment=long_comment)
with Image.open(out) as im:
assert im.info["comment"] == long_comment
with pytest.raises(ValueError):
test_card.save(out, "JPEG2000", comment=long_comment + b" ")
@pytest.mark.parametrize(
"test_file",
[
@ -370,3 +407,29 @@ def test_crashes(test_file):
im.load()
except OSError:
pass
@skip_unless_feature_version("jpg_2000", "2.4.0")
def test_plt_marker():
# Search the start of the codesteam for PLT
out = BytesIO()
test_card.save(out, "JPEG2000", no_jp2=True, plt=True)
out.seek(0)
while True:
marker = out.read(2)
if not marker:
assert False, "End of stream without PLT"
jp2_boxid = _binary.i16be(marker)
if jp2_boxid == 0xFF4F:
# SOC has no length
continue
elif jp2_boxid == 0xFF58:
# PLT
return
elif jp2_boxid == 0xFF93:
assert False, "SOD without finding PLT first"
hdr = out.read(2)
length = _binary.i16be(hdr)
out.seek(length - 2, os.SEEK_CUR)

View File

@ -984,6 +984,36 @@ class TestFileLibTiff(LibTiffTestCase):
) as im:
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
@pytest.mark.parametrize(
"file_name, mode, size, tile",
[
(
"tiff_wrong_bits_per_sample.tiff",
"RGBA",
(52, 53),
[("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))],
),
(
"tiff_wrong_bits_per_sample_2.tiff",
"RGB",
(16, 16),
[("raw", (0, 0, 16, 16), 8, ("RGB", 0, 1))],
),
(
"tiff_wrong_bits_per_sample_3.tiff",
"RGBA",
(512, 256),
[("libtiff", (0, 0, 512, 256), 0, ("RGBA", "tiff_lzw", False, 48782))],
),
],
)
def test_wrong_bits_per_sample(self, file_name, mode, size, tile):
with Image.open("Tests/images/" + file_name) as im:
assert im.mode == mode
assert im.size == size
assert im.tile == tile
im.load()
def test_no_rows_per_strip(self):
# This image does not have a RowsPerStrip TIFF tag
infile = "Tests/images/no_rows_per_strip.tif"
@ -1065,3 +1095,27 @@ class TestFileLibTiff(LibTiffTestCase):
out = str(tmp_path / "temp.tif")
with pytest.raises(SystemError):
im.save(out, compression=compression)
def test_save_many_compressed(self, tmp_path):
im = hopper()
out = str(tmp_path / "temp.tif")
for _ in range(10000):
im.save(out, compression="jpeg")
@pytest.mark.parametrize(
"path, sizes",
(
("Tests/images/hopper.tif", ()),
("Tests/images/child_ifd.tiff", (16, 8)),
("Tests/images/child_ifd_jpeg.tiff", (20,)),
),
)
def test_get_child_images(self, path, sizes):
with Image.open(path) as im:
ims = im.get_child_images()
assert len(ims) == len(sizes)
for i, im in enumerate(ims):
w = sizes[i]
expected = Image.new("RGB", (w, w), "#f00")
assert_image_similar(im, expected, 1)

View File

@ -51,6 +51,16 @@ def test_seek():
assert im.tell() == 0
def test_close():
with Image.open(TEST_FILE) as im:
pass
assert im.ole.fp.closed
im = Image.open(TEST_FILE)
im.close()
assert im.ole.fp.closed
def test_invalid_file():
# Test an invalid OLE file
invalid_file = "Tests/images/flower.jpg"

View File

@ -42,7 +42,8 @@ def test_unclosed_file():
im = Image.open(test_files[0])
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():

View File

@ -8,7 +8,7 @@ import pytest
from PIL import Image, PdfParser, features
from .helper import hopper, mark_if_feature_version
from .helper import hopper, mark_if_feature_version, skip_unless_feature
def helper_save_as_pdf(tmp_path, mode, **kwargs):
@ -42,6 +42,11 @@ def test_save(tmp_path, mode):
helper_save_as_pdf(tmp_path, mode)
@skip_unless_feature("jpg_2000")
def test_save_rgba(tmp_path):
helper_save_as_pdf(tmp_path, "RGBA")
def test_monochrome(tmp_path):
# Arrange
mode = "1"

View File

@ -27,7 +27,8 @@ def test_unclosed_file():
im = Image.open(test_file)
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():

28
Tests/test_file_qoi.py Normal file
View File

@ -0,0 +1,28 @@
import pytest
from PIL import Image, QoiImagePlugin
from .helper import assert_image_equal_tofile, assert_image_similar_tofile
def test_sanity():
with Image.open("Tests/images/hopper.qoi") as im:
assert im.mode == "RGB"
assert im.size == (128, 128)
assert im.format == "QOI"
assert_image_equal_tofile(im, "Tests/images/hopper.png")
with Image.open("Tests/images/pil123rgba.qoi") as im:
assert im.mode == "RGBA"
assert im.size == (162, 150)
assert im.format == "QOI"
assert_image_similar_tofile(im, "Tests/images/pil123rgba.png", 0.03)
def test_invalid_file():
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):
QoiImagePlugin.QoiImageFile(invalid_file)

View File

@ -25,7 +25,8 @@ def test_unclosed_file():
im = Image.open(TEST_FILE)
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():

View File

@ -29,11 +29,9 @@ def test_sanity(codec, test_path, format):
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file():
def open():
with pytest.warns(ResourceWarning):
TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg")
pytest.warns(ResourceWarning, open)
def test_close():
with warnings.catch_warnings():

View File

@ -163,7 +163,9 @@ def test_save_id_section(tmp_path):
# Save with custom id section greater than 255 characters
id_section = b"Test content" * 25
pytest.warns(UserWarning, lambda: im.save(out, id_section=id_section))
with pytest.warns(UserWarning):
im.save(out, id_section=id_section)
with Image.open(out) as test_im:
assert test_im.info["id_section"] == id_section[:255]

View File

@ -61,7 +61,8 @@ class TestFileTiff:
im = Image.open("Tests/images/multipage.tiff")
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file(self):
with warnings.catch_warnings():
@ -83,24 +84,6 @@ class TestFileTiff:
with Image.open("Tests/images/multipage.tiff") as im:
im.load()
@pytest.mark.parametrize(
"path, sizes",
(
("Tests/images/hopper.tif", ()),
("Tests/images/child_ifd.tiff", (16, 8)),
("Tests/images/child_ifd_jpeg.tiff", (20,)),
),
)
def test_get_child_images(self, path, sizes):
with Image.open(path) as im:
ims = im.get_child_images()
assert len(ims) == len(sizes)
for i, im in enumerate(ims):
w = sizes[i]
expected = Image.new("RGB", (w, w), "#f00")
assert_image_similar(im, expected, 1)
def test_mac_tiff(self):
# Read RGBa images from macOS [@PIL136]
@ -117,36 +100,6 @@ class TestFileTiff:
with Image.open("Tests/images/hopper_bigtiff.tif") as im:
assert_image_equal_tofile(im, "Tests/images/hopper.tif")
@pytest.mark.parametrize(
"file_name,mode,size,tile",
[
(
"tiff_wrong_bits_per_sample.tiff",
"RGBA",
(52, 53),
[("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))],
),
(
"tiff_wrong_bits_per_sample_2.tiff",
"RGB",
(16, 16),
[("raw", (0, 0, 16, 16), 8, ("RGB", 0, 1))],
),
(
"tiff_wrong_bits_per_sample_3.tiff",
"RGBA",
(512, 256),
[("libtiff", (0, 0, 512, 256), 0, ("RGBA", "tiff_lzw", False, 48782))],
),
],
)
def test_wrong_bits_per_sample(self, file_name, mode, size, tile):
with Image.open("Tests/images/" + file_name) as im:
assert im.mode == mode
assert im.size == size
assert im.tile == tile
im.load()
def test_set_legacy_api(self):
ifd = TiffImagePlugin.ImageFileDirectory_v2()
with pytest.raises(Exception) as e:
@ -231,7 +184,8 @@ class TestFileTiff:
def test_bad_exif(self):
with Image.open("Tests/images/hopper_bad_exif.jpg") as i:
# Should not raise struct.error.
pytest.warns(UserWarning, i._getexif)
with pytest.warns(UserWarning):
i._getexif()
def test_save_rgba(self, tmp_path):
im = hopper("RGBA")

View File

@ -252,7 +252,8 @@ def test_empty_metadata():
head = f.read(8)
info = TiffImagePlugin.ImageFileDirectory(head)
# Should not raise struct.error.
pytest.warns(UserWarning, info.load, f)
with pytest.warns(UserWarning):
info.load(f)
def test_iccprofile(tmp_path):
@ -418,11 +419,12 @@ def test_too_many_entries():
ifd = TiffImagePlugin.ImageFileDirectory_v2()
# 277: ("SamplesPerPixel", SHORT, 1),
ifd._tagdata[277] = struct.pack("hh", 4, 4)
ifd._tagdata[277] = struct.pack("<hh", 4, 4)
ifd.tagtype[277] = TiffTags.SHORT
# Should not raise ValueError.
pytest.warns(UserWarning, lambda: ifd[277])
with pytest.warns(UserWarning):
assert ifd[277] == 4
def test_tag_group_data():

View File

@ -29,7 +29,10 @@ class TestUnsupportedWebp:
WebPImagePlugin.SUPPORTED = False
file_path = "Tests/images/hopper.webp"
pytest.warns(UserWarning, lambda: pytest.raises(OSError, Image.open, file_path))
with pytest.warns(UserWarning):
with pytest.raises(OSError):
with Image.open(file_path):
pass
if HAVE_WEBP:
WebPImagePlugin.SUPPORTED = True

View File

@ -134,6 +134,18 @@ def test_timestamp_and_duration(tmp_path):
ts += durations[frame]
def test_float_duration(tmp_path):
temp_file = str(tmp_path / "temp.webp")
with Image.open("Tests/images/iss634.apng") as im:
assert im.info["duration"] == 70.0
im.save(temp_file, save_all=True)
with Image.open(temp_file) as reloaded:
reloaded.load()
assert reloaded.info["duration"] == 70
def test_seeking(tmp_path):
"""
Create an animated WebP file, and then try seeking through frames in reverse-order,

View File

@ -48,6 +48,9 @@ class TestImage:
"RGBX",
"RGBA",
"RGBa",
"BGR;15",
"BGR;16",
"BGR;24",
"CMYK",
"YCbCr",
"LAB",
@ -57,9 +60,7 @@ class TestImage:
def test_image_modes_success(self, mode):
Image.new(mode, (1, 1))
@pytest.mark.parametrize(
"mode", ("", "bad", "very very long", "BGR;15", "BGR;16", "BGR;24", "BGR;32")
)
@pytest.mark.parametrize("mode", ("", "bad", "very very long"))
def test_image_modes_fail(self, mode):
with pytest.raises(ValueError) as e:
Image.new(mode, (1, 1))

View File

@ -132,22 +132,26 @@ class TestImageGetPixel(AccessTest):
return 1
return tuple(range(1, bands + 1))
def check(self, mode, c=None):
if not c:
c = self.color(mode)
def check(self, mode, expected_color=None):
if not expected_color:
expected_color = self.color(mode)
# check putpixel
im = Image.new(mode, (1, 1), None)
im.putpixel((0, 0), c)
assert (
im.getpixel((0, 0)) == c
), f"put/getpixel roundtrip failed for mode {mode}, color {c}"
im.putpixel((0, 0), expected_color)
actual_color = im.getpixel((0, 0))
assert actual_color == expected_color, (
f"put/getpixel roundtrip failed for mode {mode}, "
f"expected {expected_color} got {actual_color}"
)
# check putpixel negative index
im.putpixel((-1, -1), c)
assert (
im.getpixel((-1, -1)) == c
), f"put/getpixel roundtrip negative index failed for mode {mode}, color {c}"
im.putpixel((-1, -1), expected_color)
actual_color = im.getpixel((-1, -1))
assert actual_color == expected_color, (
f"put/getpixel roundtrip negative index failed for mode {mode}, "
f"expected {expected_color} got {actual_color}"
)
# Check 0
im = Image.new(mode, (0, 0), None)
@ -155,27 +159,32 @@ class TestImageGetPixel(AccessTest):
error = ValueError if self._need_cffi_access else IndexError
with pytest.raises(error):
im.putpixel((0, 0), c)
im.putpixel((0, 0), expected_color)
with pytest.raises(error):
im.getpixel((0, 0))
# Check 0 negative index
with pytest.raises(error):
im.putpixel((-1, -1), c)
im.putpixel((-1, -1), expected_color)
with pytest.raises(error):
im.getpixel((-1, -1))
# check initial color
im = Image.new(mode, (1, 1), c)
assert (
im.getpixel((0, 0)) == c
), f"initial color failed for mode {mode}, color {c} "
im = Image.new(mode, (1, 1), expected_color)
actual_color = im.getpixel((0, 0))
assert actual_color == expected_color, (
f"initial color failed for mode {mode}, "
f"expected {expected_color} got {actual_color}"
)
# check initial color negative index
assert (
im.getpixel((-1, -1)) == c
), f"initial color failed with negative index for mode {mode}, color {c} "
actual_color = im.getpixel((-1, -1))
assert actual_color == expected_color, (
f"initial color failed with negative index for mode {mode}, "
f"expected {expected_color} got {actual_color}"
)
# Check 0
im = Image.new(mode, (0, 0), c)
im = Image.new(mode, (0, 0), expected_color)
with pytest.raises(error):
im.getpixel((0, 0))
# Check 0 negative index
@ -205,13 +214,13 @@ class TestImageGetPixel(AccessTest):
self.check(mode)
@pytest.mark.parametrize("mode", ("I;16", "I;16B"))
def test_signedness(self, mode):
@pytest.mark.parametrize(
"expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1)
)
def test_signedness(self, mode, expected_color):
# see https://github.com/python-pillow/Pillow/issues/452
# pixelaccess is using signed int* instead of uint*
self.check(mode, 2**15 - 1)
self.check(mode, 2**15)
self.check(mode, 2**15 + 1)
self.check(mode, 2**16 - 1)
self.check(mode, expected_color)
@pytest.mark.parametrize("mode", ("P", "PA"))
@pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255)))
@ -266,15 +275,10 @@ class TestCffi(AccessTest):
# self._test_get_access(hopper('PA')) # PA -- how do I make a PA image?
self._test_get_access(hopper("F"))
im = Image.new("I;16", (10, 10), 40000)
self._test_get_access(im)
im = Image.new("I;16L", (10, 10), 40000)
self._test_get_access(im)
im = Image.new("I;16B", (10, 10), 40000)
self._test_get_access(im)
for mode in ("I;16", "I;16L", "I;16B", "I;16N", "I"):
im = Image.new(mode, (10, 10), 40000)
self._test_get_access(im)
im = Image.new("I", (10, 10), 40000)
self._test_get_access(im)
# These don't actually appear to be modes that I can actually make,
# as unpack sets them directly into the I mode.
# im = Image.new('I;32L', (10, 10), -2**10)
@ -313,15 +317,10 @@ class TestCffi(AccessTest):
# self._test_set_access(i, (128, 128)) #PA -- undone how to make
self._test_set_access(hopper("F"), 1024.0)
im = Image.new("I;16", (10, 10), 40000)
self._test_set_access(im, 45000)
im = Image.new("I;16L", (10, 10), 40000)
self._test_set_access(im, 45000)
im = Image.new("I;16B", (10, 10), 40000)
self._test_set_access(im, 45000)
for mode in ("I;16", "I;16L", "I;16B", "I;16N", "I"):
im = Image.new(mode, (10, 10), 40000)
self._test_set_access(im, 45000)
im = Image.new("I", (10, 10), 40000)
self._test_set_access(im, 45000)
# im = Image.new('I;32L', (10, 10), -(2**10))
# self._test_set_access(im, -(2**13)+1)
# im = Image.new('I;32B', (10, 10), 2**10)
@ -354,8 +353,8 @@ class TestCffi(AccessTest):
class TestImagePutPixelError(AccessTest):
IMAGE_MODES1 = ["L", "LA", "RGB", "RGBA"]
IMAGE_MODES2 = ["I", "I;16", "BGR;15"]
IMAGE_MODES1 = ["LA", "RGB", "RGBA", "BGR;15"]
IMAGE_MODES2 = ["L", "I", "I;16"]
INVALID_TYPES = ["foo", 1.0, None]
@pytest.mark.parametrize("mode", IMAGE_MODES1)
@ -370,6 +369,11 @@ class TestImagePutPixelError(AccessTest):
(
("L", (0, 2), "color must be int or single-element tuple"),
("LA", (0, 3), "color must be int, or tuple of one or two elements"),
(
"BGR;15",
(0, 2),
"color must be int, or tuple of one or three elements",
),
(
"RGB",
(0, 2, 5),
@ -398,11 +402,6 @@ class TestImagePutPixelError(AccessTest):
with pytest.raises(OverflowError):
im.putpixel((0, 0), 2**80)
def test_putpixel_unrecognized_mode(self):
im = hopper("BGR;15")
with pytest.raises(ValueError, match="unrecognized image mode"):
im.putpixel((0, 0), 0)
class TestEmbeddable:
@pytest.mark.xfail(reason="failing test")

View File

@ -254,17 +254,6 @@ def test_p2pa_palette():
assert im_pa.getpalette() == im.getpalette()
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
def test_rgb_lab(mode):
im = Image.new(mode, (1, 1))
converted_im = im.convert("LAB")
assert converted_im.getpixel((0, 0)) == (0, 128, 128)
im = Image.new("LAB", (1, 1), (255, 0, 0))
converted_im = im.convert(mode)
assert converted_im.getpixel((0, 0))[:3] == (0, 255, 255)
def test_matrix_illegal_conversion():
# Arrange
im = hopper("CMYK")

View File

@ -625,3 +625,14 @@ def test_constants_deprecation():
for name in enum.__members__:
with pytest.warns(DeprecationWarning):
assert getattr(ImageCms, prefix + name) == enum[name]
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
def test_rgb_lab(mode):
im = Image.new(mode, (1, 1))
converted_im = im.convert("LAB")
assert converted_im.getpixel((0, 0)) == (0, 128, 128)
im = Image.new("LAB", (1, 1), (255, 0, 0))
converted_im = im.convert(mode)
assert converted_im.getpixel((0, 0))[:3] == (0, 255, 255)

View File

@ -351,7 +351,8 @@ def test_rotated_transposed_font(font, orientation):
assert bbox_b[3] == 20 + bbox_a[2] - bbox_a[0]
# text length is undefined for vertical text
pytest.raises(ValueError, draw.textlength, word)
with pytest.raises(ValueError):
draw.textlength(word)
@pytest.mark.parametrize(
@ -872,25 +873,23 @@ def test_anchor_invalid(font):
d.font = font
for anchor in ["", "l", "a", "lax", "sa", "xa", "lx"]:
pytest.raises(ValueError, lambda: font.getmask2("hello", anchor=anchor))
pytest.raises(ValueError, lambda: font.getbbox("hello", anchor=anchor))
pytest.raises(ValueError, lambda: d.text((0, 0), "hello", anchor=anchor))
pytest.raises(ValueError, lambda: d.textbbox((0, 0), "hello", anchor=anchor))
pytest.raises(
ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
)
pytest.raises(
ValueError,
lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor),
)
with pytest.raises(ValueError):
font.getmask2("hello", anchor=anchor)
with pytest.raises(ValueError):
font.getbbox("hello", anchor=anchor)
with pytest.raises(ValueError):
d.text((0, 0), "hello", anchor=anchor)
with pytest.raises(ValueError):
d.textbbox((0, 0), "hello", anchor=anchor)
with pytest.raises(ValueError):
d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
with pytest.raises(ValueError):
d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor)
for anchor in ["lt", "lb"]:
pytest.raises(
ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
)
pytest.raises(
ValueError,
lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor),
)
with pytest.raises(ValueError):
d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
with pytest.raises(ValueError):
d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor)
@pytest.mark.parametrize("bpp", (1, 2, 4, 8))

View File

@ -360,37 +360,20 @@ def test_anchor_invalid_ttb():
d.font = font
for anchor in ["", "l", "a", "lax", "xa", "la", "ls", "ld", "lx"]:
pytest.raises(
ValueError, lambda: font.getmask2("hello", anchor=anchor, direction="ttb")
)
pytest.raises(
ValueError, lambda: font.getbbox("hello", anchor=anchor, direction="ttb")
)
pytest.raises(
ValueError, lambda: d.text((0, 0), "hello", anchor=anchor, direction="ttb")
)
pytest.raises(
ValueError,
lambda: d.textbbox((0, 0), "hello", anchor=anchor, direction="ttb"),
)
pytest.raises(
ValueError,
lambda: d.multiline_text(
(0, 0), "foo\nbar", anchor=anchor, direction="ttb"
),
)
pytest.raises(
ValueError,
lambda: d.multiline_textbbox(
(0, 0), "foo\nbar", anchor=anchor, direction="ttb"
),
)
with pytest.raises(ValueError):
font.getmask2("hello", anchor=anchor, direction="ttb")
with pytest.raises(ValueError):
font.getbbox("hello", anchor=anchor, direction="ttb")
with pytest.raises(ValueError):
d.text((0, 0), "hello", anchor=anchor, direction="ttb")
with pytest.raises(ValueError):
d.textbbox((0, 0), "hello", anchor=anchor, direction="ttb")
with pytest.raises(ValueError):
d.multiline_text((0, 0), "foo\nbar", anchor=anchor, direction="ttb")
with pytest.raises(ValueError):
d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor, direction="ttb")
# ttb multiline text does not support anchors at all
pytest.raises(
ValueError,
lambda: d.multiline_text((0, 0), "foo\nbar", anchor="mm", direction="ttb"),
)
pytest.raises(
ValueError,
lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor="mm", direction="ttb"),
)
with pytest.raises(ValueError):
d.multiline_text((0, 0), "foo\nbar", anchor="mm", direction="ttb")
with pytest.raises(ValueError):
d.multiline_textbbox((0, 0), "foo\nbar", anchor="mm", direction="ttb")

View File

@ -55,8 +55,8 @@ def test_show_without_viewers():
viewers = ImageShow._viewers
ImageShow._viewers = []
im = hopper()
assert not ImageShow.show(im)
with hopper() as im:
assert not ImageShow.show(im)
ImageShow._viewers = viewers

View File

@ -100,8 +100,11 @@ class TestImageWinDib:
# Act
# Make one the same as the using tobytes()/frombytes()
test_buffer = dib1.tobytes()
dib2.frombytes(test_buffer)
for datatype in ("bytes", "memoryview"):
if datatype == "memoryview":
test_buffer = memoryview(test_buffer)
dib2.frombytes(test_buffer)
# Assert
# Confirm they're the same
assert dib1.tobytes() == dib2.tobytes()
# Assert
# Confirm they're the same
assert dib1.tobytes() == dib2.tobytes()

View File

@ -207,6 +207,9 @@ class TestLibPack:
0x01000083,
)
def test_I16(self):
self.assert_pack("I;16N", "I;16N", 2, 0x0201, 0x0403, 0x0605)
def test_F_float(self):
self.assert_pack("F", "F;32F", 4, 1.539989614439558e-36, 4.063216068939723e-34)
@ -761,10 +764,12 @@ class TestLibUnpack:
self.assert_unpack("I;16", "I;16N", 2, 0x0201, 0x0403, 0x0605)
self.assert_unpack("I;16B", "I;16N", 2, 0x0201, 0x0403, 0x0605)
self.assert_unpack("I;16L", "I;16N", 2, 0x0201, 0x0403, 0x0605)
self.assert_unpack("I;16N", "I;16N", 2, 0x0201, 0x0403, 0x0605)
else:
self.assert_unpack("I;16", "I;16N", 2, 0x0102, 0x0304, 0x0506)
self.assert_unpack("I;16B", "I;16N", 2, 0x0102, 0x0304, 0x0506)
self.assert_unpack("I;16L", "I;16N", 2, 0x0102, 0x0304, 0x0506)
self.assert_unpack("I;16N", "I;16N", 2, 0x0102, 0x0304, 0x0506)
def test_CMYK16(self):
self.assert_unpack("CMYK", "CMYK;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16))

View File

@ -88,10 +88,7 @@ def test_tobytes():
def test_convert():
im = original.copy()
verify(im.convert("I;16"))
verify(im.convert("I;16").convert("L"))
verify(im.convert("I;16").convert("I"))
verify(im.convert("I;16B"))
verify(im.convert("I;16B").convert("L"))
verify(im.convert("I;16B").convert("I"))
for mode in ("I;16", "I;16B", "I;16N"):
verify(im.convert(mode))
verify(im.convert(mode).convert("L"))
verify(im.convert(mode).convert("I"))

View File

@ -4,7 +4,7 @@ import pytest
from PIL import Image
from .helper import assert_deep_equal, assert_image, hopper
from .helper import assert_deep_equal, assert_image, hopper, skip_unless_feature
numpy = pytest.importorskip("numpy", reason="NumPy not installed")
@ -219,6 +219,13 @@ def test_zero_size():
assert im.size == (0, 0)
@skip_unless_feature("libtiff")
def test_load_first():
with Image.open("Tests/images/g4_orientation_5.tif") as im:
a = numpy.array(im)
assert a.shape == (88, 590)
def test_bool():
# https://github.com/python-pillow/Pillow/issues/2044
a = numpy.zeros((10, 2), dtype=bool)

View File

@ -1,7 +1,7 @@
#!/bin/bash
# install libimagequant
archive=libimagequant-4.1.0
archive=libimagequant-4.1.1
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz

View File

@ -19,6 +19,7 @@ I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " htmlview to open the index page built by the html target in your browser"
@echo " serve to start a local server for viewing docs"
@echo " livehtml to start a local server for viewing docs and auto-reload on change"
@echo " dirhtml to make HTML files named index.html in directories"
@ -45,7 +46,7 @@ clean:
-rm -rf $(BUILDDIR)/*
install-sphinx:
$(PYTHON) -m pip install --quiet furo olefile sphinx sphinx-copybutton sphinx-inline-tabs sphinx-issues sphinx-removed-in sphinxext-opengraph
$(PYTHON) -m pip install --quiet furo olefile sphinx sphinx-copybutton sphinx-inline-tabs sphinx-removed-in sphinxext-opengraph
.PHONY: html
html:
@ -196,6 +197,10 @@ doctest:
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
.PHONY: htmlview
htmlview: html
$(PYTHON) -c "import os, webbrowser; webbrowser.open('file://' + os.path.realpath('$(BUILDDIR)/html/index.html'))"
.PHONY: livehtml
livehtml: html
livereload $(BUILDDIR)/html -p 33233

View File

@ -28,11 +28,11 @@ needs_sphinx = "2.4"
# ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.extlinks",
"sphinx.ext.intersphinx",
"sphinx.ext.viewcode",
"sphinx_copybutton",
"sphinx_inline_tabs",
"sphinx_issues",
"sphinx_removed_in",
"sphinxext.opengraph",
]
@ -317,8 +317,17 @@ def setup(app):
app.add_css_file("css/dark.css")
# GitHub repo for sphinx-issues
issues_github_path = "python-pillow/Pillow"
# sphinx.ext.extlinks
# This config is a dictionary of external sites,
# mapping unique short aliases to a base URL and a prefix.
# https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html
_repo = "https://github.com/python-pillow/Pillow/"
extlinks = {
"cve": ("https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-%s", "CVE-%s"),
"cwe": ("https://cwe.mitre.org/data/definitions/%s.html", "CWE-%s"),
"issue": (_repo + "issues/%s", "#%s"),
"pr": (_repo + "pull/%s", "#%s"),
}
# sphinxext.opengraph
ogp_image = (

View File

@ -271,7 +271,7 @@ FreeType 2.7
Support for FreeType 2.7 has been removed.
We recommend upgrading to at least `FreeType`_ 2.10.4, which fixed a severe
vulnerability introduced in FreeType 2.6 (:cve:`CVE-2020-15999`).
vulnerability introduced in FreeType 2.6 (:cve:`2020-15999`).
.. _FreeType: https://freetype.org/

View File

@ -62,7 +62,6 @@ Pillow also provides limited support for a few additional modes, including:
* ``BGR;15`` (15-bit reversed true colour)
* ``BGR;16`` (16-bit reversed true colour)
* ``BGR;24`` (24-bit reversed true colour)
* ``BGR;32`` (32-bit reversed true colour)
Premultiplied alpha is where the values for each other channel have been
multiplied by the alpha. For example, an RGBA pixel of ``(10, 20, 30, 127)``

View File

@ -589,6 +589,19 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
.. versionadded:: 9.1.0
**comment**
Adds a custom comment to the file, replacing the default
"Created by OpenJPEG version" comment.
.. versionadded:: 9.5.0
**plt**
If ``True`` and OpenJPEG 2.4.0 or later is available, then include a PLT
(packet length, tile-part header) marker in the produced file.
Defaults to ``False``.
.. versionadded:: 9.5.0
.. note::
To enable JPEG 2000 support, you need to build and install the OpenJPEG
@ -1457,8 +1470,13 @@ PDF
^^^
Pillow can write PDF (Acrobat) images. Such images are written as binary PDF 1.4
files, using either JPEG or HEX encoding depending on the image mode (and
whether JPEG support is available or not).
files. Different encoding methods are used, depending on the image mode.
* 1 mode images are saved using TIFF encoding, or JPEG encoding if libtiff support is
unavailable
* L, RGB and CMYK mode images use JPEG encoding
* P mode images use HEX encoding
* RGBA mode images use JPEG2000 encoding
.. _pdf-saving:
@ -1544,6 +1562,13 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
.. versionadded:: 5.3.0
QOI
^^^
.. versionadded:: 9.5.0
Pillow identifies and reads images in Quite OK Image format.
XV Thumbnails
^^^^^^^^^^^^^

View File

@ -68,6 +68,18 @@ Install Pillow with :command:`pip`::
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade Pillow
While we provide binaries for both x86-64 and arm64, we do not provide universal2
binaries. However, it is simple to combine our current binaries to create one::
python3 -m pip download --only-binary=:all: --platform macosx_10_10_x86_64 Pillow
python3 -m pip download --only-binary=:all: --platform macosx_11_0_arm64 Pillow
python3 -m pip install delocate
Then, with the names of the downloaded wheels, use Python to combine them::
from delocate.fuse import fuse_wheels
fuse_wheels('Pillow-9.4.0-2-cp39-cp39-macosx_10_10_x86_64.whl', 'Pillow-9.4.0-cp39-cp39-macosx_11_0_arm64.whl', 'Pillow-9.4.0-cp39-cp39-macosx_11_0_universal2.whl')
.. tab:: Windows
We provide Pillow binaries for Windows compiled for the matrix of
@ -169,7 +181,7 @@ Many of Pillow's features require external libraries:
* **libimagequant** provides improved color quantization
* Pillow has been tested with libimagequant **2.6-4.1**
* Pillow has been tested with libimagequant **2.6-4.1.1**
* Libimagequant is licensed GPLv3, which is more restrictive than
the Pillow license, therefore we will not be distributing binaries
with libimagequant support enabled.
@ -186,8 +198,8 @@ Many of Pillow's features require external libraries:
* Pillow wheels since version 8.2.0 include a modified version of libraqm that
loads libfribidi at runtime if it is installed.
On Windows this requires compiling FriBiDi and installing ``fribidi.dll``
into a directory listed in the `Dynamic-Link Library Search Order (Microsoft Docs)
<https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order#search-order-for-desktop-applications>`_
into a directory listed in the `Dynamic-link library search order (Microsoft Learn)
<https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order#search-order-for-unpackaged-apps>`_
(``fribidi-0.dll`` or ``libfribidi-0.dll`` are also detected).
See `Build Options`_ to see how to build this version.
* Previous versions of Pillow (5.0.0 to 8.1.2) linked libraqm dynamically at runtime.
@ -424,6 +436,8 @@ These platforms are built and tested for every change.
+----------------------------------+----------------------------+---------------------+
| Amazon Linux 2 | 3.7 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Amazon Linux 2023 | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Arch | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| CentOS 7 | 3.9 | x86-64 |
@ -432,8 +446,6 @@ These platforms are built and tested for every change.
+----------------------------------+----------------------------+---------------------+
| CentOS Stream 9 | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Debian 10 Buster | 3.7 | x86 |
+----------------------------------+----------------------------+---------------------+
| Debian 11 Bullseye | 3.9 | x86 |
+----------------------------------+----------------------------+---------------------+
| Fedora 36 | 3.10 | x86-64 |

View File

@ -19,6 +19,7 @@ if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. htmlview to open the index page built by the html target in your browser
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
@ -44,12 +45,23 @@ if "%1" == "clean" (
goto end
)
if "%1" == "html" (
set html=false
if "%1%" == "html" set html=true
if "%1%" == "htmlview" set html=true
if "%html%" == "true" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
if "%1" == "htmlview" (
if EXIST "%BUILDDIR%\html\index.html" (
echo.Opening "%BUILDDIR%\html\index.html" in the default web browser...
start "" "%BUILDDIR%\html\index.html"
)
)
goto end
)
if "%1" == "dirhtml" (

View File

@ -320,8 +320,8 @@ Methods
:param xy: Two points to define the bounding box. Sequence of either
``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0`` and
``y1 >= y0``. The bounding box is inclusive of both endpoints.
:param outline: Color to use for the outline.
:param fill: Color to use for the fill.
:param outline: Color to use for the outline.
:param width: The line width, in pixels.
.. versionadded:: 5.3.0
@ -334,11 +334,11 @@ Methods
``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0`` and
``y1 >= y0``. The bounding box is inclusive of both endpoints.
:param radius: Radius of the corners.
:param outline: Color to use for the outline.
:param fill: Color to use for the fill.
:param outline: Color to use for the outline.
:param width: The line width, in pixels.
:param corners: A tuple of whether to round each corner,
`(top_left, top_right, bottom_right, bottom_left)`.
``(top_left, top_right, bottom_right, bottom_left)``.
.. versionadded:: 8.2.0

View File

@ -10,19 +10,13 @@ distributions.
- ``python3-dbg`` package for the gdb extensions and python symbols
- ``gdb`` and ``valgrind``
- Potentially debug symbols for libraries. On ubuntu they're shipped
in package-dbgsym packages, from a different repo.
- Potentially debug symbols for libraries. On Ubuntu you can follow those
instructions to install the corresponding packages: `Debug Symbol Packages <https://wiki.ubuntu.com/Debug%20Symbol%20Packages#Getting_-dbgsym.ddeb_packages>`_
::
Then ``sudo apt-get install libtiff5-dbgsym``
deb http://ddebs.ubuntu.com focal main restricted universe multiverse
deb http://ddebs.ubuntu.com focal-updates main restricted universe multiverse
deb http://ddebs.ubuntu.com focal-proposed main restricted universe multiverse
Then ``sudo apt-get update && sudo apt-get install libtiff5-dbgsym``
- There's a bug with the dbg package for at least python 3.8 on ubuntu
20.04, and you need to add a new link or two to make it autoload when
- There's a bug with the ``python3-dbg`` package for at least Python 3.8 on
Ubuntu 20.04, and you need to add a new link or two to make it autoload when
running python:
::

View File

@ -6,7 +6,7 @@ CVE-2016-0740 -- Buffer overflow in TiffDecode.c
------------------------------------------------
Pillow 3.1.0 and earlier when linked against libtiff >= 4.0.0 on x64
may overflow a buffer when reading a specially crafted tiff file (:cve:`CVE-2016-0740`).
may overflow a buffer when reading a specially crafted tiff file (:cve:`2016-0740`).
Specifically, libtiff >= 4.0.0 changed the return type of
``TIFFScanlineSize`` from ``int32`` to machine dependent
@ -24,7 +24,7 @@ CVE-2016-0775 -- Buffer overflow in FliDecode.c
-----------------------------------------------
In all versions of Pillow, dating back at least to the last PIL 1.1.7
release, FliDecode.c has a buffer overflow error (:cve:`CVE-2016-0775`).
release, FliDecode.c has a buffer overflow error (:cve:`2016-0775`).
Around line 192:
@ -53,7 +53,7 @@ CVE-2016-2533 -- Buffer overflow in PcdDecode.c
-----------------------------------------------
In all versions of Pillow, dating back at least to the last PIL 1.1.7
release, ``PcdDecode.c`` has a buffer overflow error (:cve:`CVE-2016-2533`).
release, ``PcdDecode.c`` has a buffer overflow error (:cve:`2016-2533`).
The ``state.buffer`` for ``PcdDecode.c`` is allocated based on a 3
bytes per pixel sizing, where ``PcdDecode.c`` wrote into the buffer

View File

@ -7,7 +7,7 @@ CVE-2016-3076 -- Buffer overflow in Jpeg2KEncode.c
Pillow between 2.5.0 and 3.1.1 may overflow a buffer when writing
large Jpeg2000 files, allowing for code execution or other memory
corruption (:cve:`CVE-2016-3076`).
corruption (:cve:`2016-3076`).
This occurs specifically in the function ``j2k_encode_entry``, at the line:

View File

@ -69,7 +69,7 @@ Security
========
This release catches several buffer overruns, as well as addressing
:cve:`CVE-2019-16865`. The CVE is regarding DOS problems, such as consuming large
:cve:`2019-16865`. The CVE is regarding DOS problems, such as consuming large
amounts of memory, or taking a large amount of time to process an image.
In RawDecode.c, an error is now thrown if skip is calculated to be less than

View File

@ -6,13 +6,13 @@ Security
This release addresses several security problems.
:cve:`CVE-2019-19911` is regarding FPX images. If an image reports that it has a large
:cve:`2019-19911` is regarding FPX images. If an image reports that it has a large
number of bands, a large amount of resources will be used when trying to process the
image. This is fixed by limiting the number of bands to those usable by Pillow.
Buffer overruns were found when processing an SGI (:cve:`CVE-2020-5311`),
PCX (:cve:`CVE-2020-5312`) or FLI image (:cve:`CVE-2020-5313`). Checks have been added
Buffer overruns were found when processing an SGI (:cve:`2020-5311`),
PCX (:cve:`2020-5312`) or FLI image (:cve:`2020-5313`). Checks have been added
to prevent this.
:cve:`CVE-2020-5310`: Overflow checks have been added when calculating the size of a
:cve:`2020-5310`: Overflow checks have been added when calculating the size of a
memory block to be reallocated in the processing of a TIFF image.

View File

@ -72,11 +72,11 @@ Security
This release includes security fixes.
* :cve:`CVE-2020-10177` Fix multiple out-of-bounds reads in FLI decoding
* :cve:`CVE-2020-10378` Fix bounds overflow in PCX decoding
* :cve:`CVE-2020-10379` Fix two buffer overflows in TIFF decoding
* :cve:`CVE-2020-10994` Fix bounds overflow in JPEG 2000 decoding
* :cve:`CVE-2020-11538` Fix buffer overflow in SGI-RLE decoding
* :cve:`2020-10177` Fix multiple out-of-bounds reads in FLI decoding
* :cve:`2020-10378` Fix bounds overflow in PCX decoding
* :cve:`2020-10379` Fix two buffer overflows in TIFF decoding
* :cve:`2020-10994` Fix bounds overflow in JPEG 2000 decoding
* :cve:`2020-11538` Fix buffer overflow in SGI-RLE decoding
Other Changes
=============

View File

@ -4,7 +4,7 @@
Security
========
Update FreeType used in binary wheels to `2.10.4`_ to fix :cve:`CVE-2020-15999`:
Update FreeType used in binary wheels to `2.10.4`_ to fix :cve:`2020-15999`:
- A heap buffer overflow has been found in the handling of embedded PNG bitmaps,
introduced in FreeType version 2.6.

View File

@ -11,7 +11,7 @@ Support for FreeType 2.7 is deprecated and will be removed in Pillow 9.0.0 (2022
when FreeType 2.8 will be the minimum supported.
We recommend upgrading to at least FreeType `2.10.4`_, which fixed a severe
vulnerability introduced in FreeType 2.6 (:cve:`CVE-2020-15999`).
vulnerability introduced in FreeType 2.6 (:cve:`2020-15999`).
.. _2.10.4: https://sourceforge.net/projects/freetype/files/freetype2/2.10.4/
@ -40,13 +40,13 @@ This release includes security fixes.
* An out-of-bounds read when saving TIFFs with custom metadata through LibTIFF
* An out-of-bounds read when saving a GIF of 1px width
* :cve:`CVE-2020-35653` Buffer read overrun in PCX decoding
* :cve:`2020-35653` Buffer read overrun in PCX decoding
The PCX image decoder used the reported image stride to calculate the row buffer,
rather than calculating it from the image size. This issue dates back to the PIL fork.
Thanks to Google's `OSS-Fuzz`_ project for finding this.
* :cve:`CVE-2020-35654` Fix TIFF out-of-bounds write error
* :cve:`2020-35654` Fix TIFF out-of-bounds write error
Out-of-bounds write in ``TiffDecode.c`` when reading corrupt YCbCr files in some
LibTIFF versions (4.1.0/Ubuntu 20.04, but not 4.0.9/Ubuntu 18.04). In some cases
@ -55,7 +55,7 @@ an out-of-bounds write in ``TiffDecode.c``. This potentially affects Pillow vers
from 6.0.0 to 8.0.1, depending on the version of LibTIFF. This was reported through
`Tidelift`_.
* :cve:`CVE-2020-35655` Fix for SGI Decode buffer overrun
* :cve:`2020-35655` Fix for SGI Decode buffer overrun
4 byte read overflow in ``SgiRleDecode.c``, where the code was not correctly checking the
offsets and length tables. Independently reported through `Tidelift`_ and Google's

View File

@ -4,19 +4,19 @@
Security
========
:cve:`CVE-2021-25289`: The previous fix for :cve:`CVE-2020-35654` was insufficient
:cve:`2021-25289`: The previous fix for :cve:`2020-35654` was insufficient
due to incorrect error checking in ``TiffDecode.c``.
:cve:`CVE-2021-25290`: In ``TiffDecode.c``, there is a negative-offset ``memcpy``
:cve:`2021-25290`: In ``TiffDecode.c``, there is a negative-offset ``memcpy``
with an invalid size.
:cve:`CVE-2021-25291`: In ``TiffDecode.c``, invalid tile boundaries could lead to
:cve:`2021-25291`: In ``TiffDecode.c``, invalid tile boundaries could lead to
an out-of-bounds read in ``TIFFReadRGBATile``.
:cve:`CVE-2021-25292`: The PDF parser has a catastrophic backtracking regex
:cve:`2021-25292`: The PDF parser has a catastrophic backtracking regex
that could be used as a DOS attack.
:cve:`CVE-2021-25293`: There is an out-of-bounds read in ``SgiRleDecode.c``,
:cve:`2021-25293`: There is an out-of-bounds read in ``SgiRleDecode.c``,
since Pillow 4.3.0.

View File

@ -4,8 +4,8 @@
Security
========
There is an exhaustion of memory DOS in the BLP (:cve:`CVE-2021-27921`),
ICNS (:cve:`CVE-2021-27922`) and ICO (:cve:`CVE-2021-27923`) container formats
There is an exhaustion of memory DOS in the BLP (:cve:`2021-27921`),
ICNS (:cve:`2021-27922`) and ICO (:cve:`2021-27923`) container formats
where Pillow did not properly check the reported size of the contained image.
These images could cause arbitrarily large memory allocations. This was reported
by Jiayi Lin, Luke Shaffer, Xinran Xie, and Akshay Ajayan of

View File

@ -129,15 +129,15 @@ Security
These were all found with `OSS-Fuzz`_.
:cve:`CVE-2021-25287`, :cve:`CVE-2021-25288`: Fix OOB read in Jpeg2KDecode
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:cve:`2021-25287`, :cve:`2021-25288`: Fix OOB read in Jpeg2KDecode
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* For J2k images with multiple bands, it's legal to have different widths for each band,
e.g. 1 byte for ``L``, 4 bytes for ``A``.
* This dates to Pillow 2.4.0.
:cve:`CVE-2021-28675`: Fix DOS in PsdImagePlugin
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:cve:`2021-28675`: Fix DOS in PsdImagePlugin
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* :py:class:`.PsdImagePlugin.PsdImageFile` did not sanity check the number of input
layers with regard to the size of the data block, this could lead to a
@ -145,15 +145,15 @@ These were all found with `OSS-Fuzz`_.
:py:meth:`~PIL.Image.Image.load`.
* This dates to the PIL fork.
:cve:`CVE-2021-28676`: Fix FLI DOS
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:cve:`2021-28676`: Fix FLI DOS
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* ``FliDecode.c`` did not properly check that the block advance was non-zero,
potentially leading to an infinite loop on load.
* This dates to the PIL fork.
:cve:`CVE-2021-28677`: Fix EPS DOS on _open
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:cve:`2021-28677`: Fix EPS DOS on _open
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* The readline used in EPS has to deal with any combination of ``\r`` and ``\n`` as line
endings. It accidentally used a quadratic method of accumulating lines while looking
@ -162,8 +162,8 @@ These were all found with `OSS-Fuzz`_.
open phase, before an image was accepted for opening.
* This dates to the PIL fork.
:cve:`CVE-2021-28678`: Fix BLP DOS
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:cve:`2021-28678`: Fix BLP DOS
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* ``BlpImagePlugin`` did not properly check that reads after jumping to file offsets
returned data. This could lead to a denial-of-service where the decoder could be run a

View File

@ -85,7 +85,7 @@ Security
Buffer overflow
^^^^^^^^^^^^^^^
This release addresses :cve:`CVE-2021-34552`. PIL since 1.1.4 and Pillow since 1.0
This release addresses :cve:`2021-34552`. PIL since 1.1.4 and Pillow since 1.0
allowed parameters passed into a convert function to trigger buffer overflow in
Convert.c.

View File

@ -4,7 +4,7 @@
Security
========
* :cve:`CVE-2021-23437`: Avoid a potential ReDoS (regular expression denial of service)
* :cve:`2021-23437`: Avoid a potential ReDoS (regular expression denial of service)
in :py:class:`~PIL.ImageColor`'s :py:meth:`~PIL.ImageColor.getrgb` by raising
:py:exc:`ValueError` if the color specifier is too long. Present since Pillow 5.2.0.

View File

@ -43,7 +43,7 @@ FreeType 2.7
Support for FreeType 2.7 has been removed; FreeType 2.8 is the minimum supported.
We recommend upgrading to at least `FreeType`_ 2.10.4, which fixed a severe
vulnerability introduced in FreeType 2.6 (:cve:`CVE-2020-15999`).
vulnerability introduced in FreeType 2.6 (:cve:`2020-15999`).
.. _FreeType: https://freetype.org/
@ -119,7 +119,7 @@ Google's `OSS-Fuzz`_ project for finding this issue.
Restrict builtins available to ImageMath.eval
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:cve:`CVE-2022-22817`: To limit :py:class:`PIL.ImageMath` to working with images, Pillow
:cve:`2022-22817`: To limit :py:class:`PIL.ImageMath` to working with images, Pillow
will now restrict the builtins available to :py:meth:`PIL.ImageMath.eval`. This will
help prevent problems arising if users evaluate arbitrary expressions, such as
``ImageMath.eval("exec(exit())")``.
@ -127,7 +127,7 @@ help prevent problems arising if users evaluate arbitrary expressions, such as
Fixed ImagePath.Path array handling
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:cve:`CVE-2022-22815` (:cwe:`CWE-126`) and :cve:`CVE-2022-22816` (:cwe:`CWE-665`) were
:cve:`2022-22815` (:cwe:`126`) and :cve:`2022-22816` (:cwe:`665`) were
found when initializing ``ImagePath.Path``.
.. _OSS-Fuzz: https://github.com/google/oss-fuzz

View File

@ -6,12 +6,12 @@ Security
This release addresses several security problems.
:cve:`CVE-2022-24303`: If the path to the temporary directory on Linux or macOS
:cve:`2022-24303`: If the path to the temporary directory on Linux or macOS
contained a space, this would break removal of the temporary image file after
``im.show()`` (and related actions), and potentially remove an unrelated file. This
has been present since PIL.
:cve:`CVE-2022-22817`: While Pillow 9.0 restricted top-level builtins available to
:cve:`2022-22817`: While Pillow 9.0 restricted top-level builtins available to
:py:meth:`PIL.ImageMath.eval`, it did not prevent builtins available to lambda
expressions. These are now also restricted.

View File

@ -6,7 +6,7 @@ Security
This release addresses several security problems.
:cve:`CVE-2022-30595`: When reading a TGA file with RLE packets that cross scan lines,
:cve:`2022-30595`: When reading a TGA file with RLE packets that cross scan lines,
Pillow reads the information past the end of the first line without deducting that
from the length of the remaining file data. This vulnerability was introduced in Pillow
9.1.0, and can cause a heap buffer overflow.

View File

@ -0,0 +1,92 @@
9.5.0
-----
Backwards Incompatible Changes
==============================
TODO
^^^^
TODO
Deprecations
============
TODO
^^^^
TODO
API Changes
===========
TODO
^^^^
TODO
API Additions
=============
QOI file format
^^^^^^^^^^^^^^^
Pillow can now read images in Quite OK Image format.
Added ``dpi`` argument when saving PDFs
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When saving a PDF, resolution could already be specified using the
``resolution`` argument. Now, a tuple of ``(x_resolution, y_resolution)`` can
be provided as ``dpi``. If both are provided, ``dpi`` will override
``resolution``.
Added ``corners`` argument to ``ImageDraw.rounded_rectangle()``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:py:meth:`.ImageDraw.rounded_rectangle` now accepts a keyword argument of
``corners``. This a tuple of Booleans, specifying whether to round each corner,
``(top_left, top_right, bottom_right, bottom_left)``.
JPEG2000 comments and PLT marker
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When opening a JPEG2000 image, the comment may now be read into
:py:attr:`~PIL.Image.Image.info`. The ``comment`` keyword argument can be used
to save it back again.
If OpenJPEG 2.4.0 or later is available and the ``plt`` keyword argument
is present and true when saving JPEG2000 images, tell the encoder to generate
PLT markers.
Security
========
TODO
^^^^
TODO
Other Changes
=============
Added support for saving PDFs in RGBA mode
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Using the JPXDecode filter, PDFs can now be saved in RGBA mode.
Improved I;16N support
^^^^^^^^^^^^^^^^^^^^^^
Support has been added for I;16N access, packing and unpacking. Conversion to
and from L mode has also been added.
BGR;* modes
^^^^^^^^^^^
It is now possible to create new BGR;15, BGR;16 and BGR;24 images. Conversely, BGR;32
has been removed from ImageMode and its associated methods, dropping the little support
Pillow had for the mode.
With that, all modes listed under :ref:`concept-modes` can now be used to create a new
image.

View File

@ -14,6 +14,7 @@ expected to be backported to earlier versions.
.. toctree::
:maxdepth: 2
9.5.0
9.4.0
9.3.0
9.2.0

View File

@ -48,7 +48,6 @@ docs =
sphinx>=2.4
sphinx-copybutton
sphinx-inline-tabs
sphinx-issues>=3.0.1
sphinx-removed-in
sphinxext-opengraph
tests =

View File

@ -242,7 +242,9 @@ def _find_include_dir(self, dirname, include):
return subdir
def _cmd_exists(cmd):
def _cmd_exists(cmd: str) -> bool:
if "PATH" not in os.environ:
return False
return any(
os.access(os.path.join(path, cmd), os.X_OK)
for path in os.environ["PATH"].split(os.pathsep)
@ -570,9 +572,7 @@ class pil_build_ext(build_ext):
):
for dirname in _find_library_dirs_ldconfig():
_add_directory(library_dirs, dirname)
if sys.platform.startswith("linux") and os.environ.get(
"ANDROID_ROOT", None
):
if sys.platform.startswith("linux") and os.environ.get("ANDROID_ROOT"):
# termux support for android.
# system libraries (zlib) are installed in /system/lib
# headers are at $PREFIX/include

View File

@ -235,6 +235,14 @@ class FpxImageFile(ImageFile.ImageFile):
return ImageFile.ImageFile.load(self)
def close(self):
self.ole.close()
super().close()
def __exit__(self, *args):
self.ole.close()
super().__exit__()
#
# --------------------------------------------------------------------

View File

@ -686,11 +686,7 @@ class Image:
@property
def __array_interface__(self):
# numpy array interface support
new = {}
shape, typestr = _conv_type_shape(self)
new["shape"] = shape
new["typestr"] = typestr
new["version"] = 3
new = {"version": 3}
try:
if self.mode == "1":
# Binary images need to be extended from bits to bytes
@ -709,6 +705,7 @@ class Image:
if parse_version(numpy.__version__) < parse_version("1.23"):
warnings.warn(e)
raise
new["shape"], new["typestr"] = _conv_type_shape(self)
return new
def __getstate__(self):

View File

@ -423,7 +423,6 @@ class ImageDraw:
self.draw.draw_rectangle(right, ink, 1)
def _multiline_check(self, text):
"""Draw text."""
split_character = "\n" if isinstance(text, str) else b"\n"
return split_character in text
@ -464,6 +463,7 @@ class ImageDraw:
*args,
**kwargs,
):
"""Draw text."""
if self._multiline_check(text):
return self.multiline_text(
xy,

View File

@ -1012,7 +1012,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
if windir:
dirs.append(os.path.join(windir, "fonts"))
elif sys.platform in ("linux", "linux2"):
lindirs = os.environ.get("XDG_DATA_DIRS", "")
lindirs = os.environ.get("XDG_DATA_DIRS")
if not lindirs:
# According to the freedesktop spec, XDG_DATA_DIRS should
# default to /usr/share

View File

@ -58,10 +58,9 @@ def getmode(mode):
"HSV": ("RGB", "L", ("H", "S", "V"), "|u1"),
# extra experimental modes
"RGBa": ("RGB", "L", ("R", "G", "B", "a"), "|u1"),
"BGR;15": ("RGB", "L", ("B", "G", "R"), endian + "u2"),
"BGR;16": ("RGB", "L", ("B", "G", "R"), endian + "u2"),
"BGR;24": ("RGB", "L", ("B", "G", "R"), endian + "u3"),
"BGR;32": ("RGB", "L", ("B", "G", "R"), endian + "u4"),
"BGR;15": ("RGB", "L", ("B", "G", "R"), "|u1"),
"BGR;16": ("RGB", "L", ("B", "G", "R"), "|u1"),
"BGR;24": ("RGB", "L", ("B", "G", "R"), "|u1"),
"LA": ("L", "L", ("L", "A"), "|u1"),
"La": ("L", "L", ("L", "a"), "|u1"),
"PA": ("RGB", "L", ("P", "A"), "|u1"),

View File

@ -17,7 +17,7 @@ import io
import os
import struct
from . import Image, ImageFile
from . import Image, ImageFile, _binary
class BoxReader:
@ -99,7 +99,7 @@ def _parse_codestream(fp):
count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
hdr = fp.read(2)
lsiz = struct.unpack(">H", hdr)[0]
lsiz = _binary.i16be(hdr)
siz = hdr + fp.read(lsiz - 2)
lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, _, _, _, _, csiz = struct.unpack_from(
">HHIIIIIIIIH", siz
@ -218,6 +218,8 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
self._size, self.mode, self.custom_mimetype, dpi = header
if dpi is not None:
self.info["dpi"] = dpi
if self.fp.read(12).endswith(b"jp2c\xff\x4f\xff\x51"):
self._parse_comment()
else:
msg = "not a JPEG 2000 file"
raise SyntaxError(msg)
@ -254,6 +256,28 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
)
]
def _parse_comment(self):
hdr = self.fp.read(2)
length = _binary.i16be(hdr)
self.fp.seek(length - 2, os.SEEK_CUR)
while True:
marker = self.fp.read(2)
if not marker:
break
typ = marker[1]
if typ in (0x90, 0xD9):
# Start of tile or end of codestream
break
hdr = self.fp.read(2)
length = _binary.i16be(hdr)
if typ == 0x64:
# Comment
self.info["comment"] = self.fp.read(length - 2)[2:]
break
else:
self.fp.seek(length - 2, os.SEEK_CUR)
@property
def reduce(self):
# https://github.com/python-pillow/Pillow/issues/4343 found that the
@ -327,8 +351,12 @@ def _save(im, fp, filename):
cinema_mode = info.get("cinema_mode", "no")
mct = info.get("mct", 0)
signed = info.get("signed", False)
fd = -1
comment = info.get("comment")
if isinstance(comment, str):
comment = comment.encode()
plt = info.get("plt", False)
fd = -1
if hasattr(fp, "fileno"):
try:
fd = fp.fileno()
@ -350,6 +378,8 @@ def _save(im, fp, filename):
mct,
signed,
fd,
comment,
plt,
)
ImageFile._save(im, fp, [("jpeg2k", (0, 0) + im.size, 0, kind)])

View File

@ -89,6 +89,14 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
def tell(self):
return self.frame
def close(self):
self.ole.close()
super().close()
def __exit__(self, *args):
self.ole.close()
super().__exit__()
#
# --------------------------------------------------------------------

View File

@ -173,6 +173,10 @@ def _save(im, fp, filename, save_all=False):
filter = "DCTDecode"
colorspace = PdfParser.PdfName("DeviceRGB")
procset = "ImageC" # color images
elif im.mode == "RGBA":
filter = "JPXDecode"
colorspace = PdfParser.PdfName("DeviceRGB")
procset = "ImageC" # color images
elif im.mode == "CMYK":
filter = "DCTDecode"
colorspace = PdfParser.PdfName("DeviceCMYK")
@ -199,6 +203,8 @@ def _save(im, fp, filename, save_all=False):
)
elif filter == "DCTDecode":
Image.SAVE["JPEG"](im, op, filename)
elif filter == "JPXDecode":
Image.SAVE["JPEG2000"](im, op, filename)
elif filter == "FlateDecode":
ImageFile._save(im, op, [("zip", (0, 0) + im.size, 0, im.mode)])
elif filter == "RunLengthDecode":

View File

@ -1003,9 +1003,13 @@ class PngImageFile(ImageFile.ImageFile):
else:
if self._prev_im and self.blend_op == Blend.OP_OVER:
updated = self._crop(self.im, self.dispose_extent)
self._prev_im.paste(
updated, self.dispose_extent, updated.convert("RGBA")
)
if self.im.mode == "RGB" and "transparency" in self.info:
mask = updated.convert_transparent(
"RGBA", self.info["transparency"]
)
else:
mask = updated.convert("RGBA")
self._prev_im.paste(updated, self.dispose_extent, mask)
self.im = self._prev_im
if self.pyaccess:
self.pyaccess = None

View File

@ -320,6 +320,7 @@ mode_map = {
"1": _PyAccess8,
"L": _PyAccess8,
"P": _PyAccess8,
"I;16N": _PyAccessI16_N,
"LA": _PyAccess32_2,
"La": _PyAccess32_2,
"PA": _PyAccess32_2,

105
src/PIL/QoiImagePlugin.py Normal file
View File

@ -0,0 +1,105 @@
#
# The Python Imaging Library.
#
# QOI support for PIL
#
# See the README file for information on usage and redistribution.
#
import os
from . import Image, ImageFile
from ._binary import i32be as i32
from ._binary import o8
def _accept(prefix):
return prefix[:4] == b"qoif"
class QoiImageFile(ImageFile.ImageFile):
format = "QOI"
format_description = "Quite OK Image"
def _open(self):
if not _accept(self.fp.read(4)):
msg = "not a QOI file"
raise SyntaxError(msg)
self._size = tuple(i32(self.fp.read(4)) for i in range(2))
channels = self.fp.read(1)[0]
self.mode = "RGB" if channels == 3 else "RGBA"
self.fp.seek(1, os.SEEK_CUR) # colorspace
self.tile = [("qoi", (0, 0) + self._size, self.fp.tell(), None)]
class QoiDecoder(ImageFile.PyDecoder):
_pulls_fd = True
def _add_to_previous_pixels(self, value):
self._previous_pixel = value
r, g, b, a = value
hash_value = (r * 3 + g * 5 + b * 7 + a * 11) % 64
self._previously_seen_pixels[hash_value] = value
def decode(self, buffer):
self._previously_seen_pixels = {}
self._previous_pixel = None
self._add_to_previous_pixels(b"".join(o8(i) for i in (0, 0, 0, 255)))
data = bytearray()
bands = Image.getmodebands(self.mode)
while len(data) < self.state.xsize * self.state.ysize * bands:
byte = self.fd.read(1)[0]
if byte == 0b11111110: # QOI_OP_RGB
value = self.fd.read(3) + o8(255)
elif byte == 0b11111111: # QOI_OP_RGBA
value = self.fd.read(4)
else:
op = byte >> 6
if op == 0: # QOI_OP_INDEX
op_index = byte & 0b00111111
value = self._previously_seen_pixels.get(op_index, (0, 0, 0, 0))
elif op == 1: # QOI_OP_DIFF
value = (
(self._previous_pixel[0] + ((byte & 0b00110000) >> 4) - 2)
% 256,
(self._previous_pixel[1] + ((byte & 0b00001100) >> 2) - 2)
% 256,
(self._previous_pixel[2] + (byte & 0b00000011) - 2) % 256,
)
value += (self._previous_pixel[3],)
elif op == 2: # QOI_OP_LUMA
second_byte = self.fd.read(1)[0]
diff_green = (byte & 0b00111111) - 32
diff_red = ((second_byte & 0b11110000) >> 4) - 8
diff_blue = (second_byte & 0b00001111) - 8
value = tuple(
(self._previous_pixel[i] + diff_green + diff) % 256
for i, diff in enumerate((diff_red, 0, diff_blue))
)
value += (self._previous_pixel[3],)
elif op == 3: # QOI_OP_RUN
run_length = (byte & 0b00111111) + 1
value = self._previous_pixel
if bands == 3:
value = value[:3]
data += value * run_length
continue
value = b"".join(o8(i) for i in value)
self._add_to_previous_pixels(value)
if bands == 3:
value = value[:3]
data += value
self.set_as_raw(bytes(data))
return -1, 0
Image.register_open(QoiImageFile.format, QoiImageFile, _accept)
Image.register_decoder("qoi", QoiDecoder)
Image.register_extension(QoiImageFile.format, ".qoi")

View File

@ -425,6 +425,9 @@ class IFDRational(Rational):
__ceil__ = _delegate("__ceil__")
__floor__ = _delegate("__floor__")
__round__ = _delegate("__round__")
# Python >= 3.11
if hasattr(Fraction, "__int__"):
__int__ = _delegate("__int__")
class ImageFileDirectory_v2(MutableMapping):
@ -1804,7 +1807,7 @@ def _save(im, fp, filename):
# Custom items are supported for int, float, unicode, string and byte
# values. Other types and tuples require a tagtype.
if tag not in TiffTags.LIBTIFF_CORE:
if not Image.core.libtiff_support_custom_tags:
if not getattr(Image.core, "libtiff_support_custom_tags", False):
continue
if tag in ifd.tagtype:
@ -1850,6 +1853,11 @@ def _save(im, fp, filename):
fp.write(data)
if errcode:
break
if _fp:
try:
os.close(_fp)
except OSError:
pass
if errcode < 0:
msg = f"encoder error {errcode} when writing image file"
raise OSError(msg)

View File

@ -285,7 +285,7 @@ def _save_all(im, fp, filename):
# Append the frame to the animation encoder
enc.add(
frame.tobytes("raw", rawmode),
timestamp,
round(timestamp),
frame.size[0],
frame.size[1],
rawmode,
@ -305,7 +305,7 @@ def _save_all(im, fp, filename):
im.seek(cur_idx)
# Force encoder to flush frames
enc.add(None, timestamp, 0, 0, "", lossless, quality, 0)
enc.add(None, round(timestamp), 0, 0, "", lossless, quality, 0)
# Get the final output from the encoder
data = enc.assemble(icc_profile, exif, xmp)

View File

@ -59,6 +59,7 @@ _plugins = [
"PngImagePlugin",
"PpmImagePlugin",
"PsdImagePlugin",
"QoiImagePlugin",
"SgiImagePlugin",
"SpiderImagePlugin",
"SunImagePlugin",

View File

@ -491,7 +491,7 @@ getink(PyObject *color, Imaging im, char *ink) {
int g = 0, b = 0, a = 0;
double f = 0;
/* Windows 64 bit longs are 32 bits, and 0xFFFFFFFF (white) is a
python long (not int) that raises an overflow error when trying
Python long (not int) that raises an overflow error when trying
to return it into a 32 bit C long
*/
PY_LONG_LONG r = 0;
@ -502,8 +502,12 @@ getink(PyObject *color, Imaging im, char *ink) {
be cast to either UINT8 or INT32 */
int rIsInt = 0;
if (PyTuple_Check(color) && PyTuple_GET_SIZE(color) == 1) {
color = PyTuple_GetItem(color, 0);
int tupleSize;
if (PyTuple_Check(color)) {
tupleSize = PyTuple_GET_SIZE(color);
if (tupleSize == 1) {
color = PyTuple_GetItem(color, 0);
}
}
if (im->type == IMAGING_TYPE_UINT8 || im->type == IMAGING_TYPE_INT32 ||
im->type == IMAGING_TYPE_SPECIAL) {
@ -513,15 +517,13 @@ getink(PyObject *color, Imaging im, char *ink) {
return NULL;
}
rIsInt = 1;
} else if (im->type == IMAGING_TYPE_UINT8) {
if (!PyTuple_Check(color)) {
PyErr_SetString(PyExc_TypeError, "color must be int or tuple");
return NULL;
}
} else {
} else if (im->bands == 1) {
PyErr_SetString(
PyExc_TypeError, "color must be int or single-element tuple");
return NULL;
} else if (!PyTuple_Check(color)) {
PyErr_SetString(PyExc_TypeError, "color must be int or tuple");
return NULL;
}
}
@ -531,7 +533,7 @@ getink(PyObject *color, Imaging im, char *ink) {
if (im->bands == 1) {
/* unsigned integer, single layer */
if (rIsInt != 1) {
if (PyTuple_GET_SIZE(color) != 1) {
if (tupleSize != 1) {
PyErr_SetString(PyExc_TypeError, "color must be int or single-element tuple");
return NULL;
} else if (!PyArg_ParseTuple(color, "L", &r)) {
@ -541,7 +543,6 @@ getink(PyObject *color, Imaging im, char *ink) {
ink[0] = (char)CLIP8(r);
ink[1] = ink[2] = ink[3] = 0;
} else {
a = 255;
if (rIsInt) {
/* compatibility: ABGR */
a = (UINT8)(r >> 24);
@ -549,7 +550,7 @@ getink(PyObject *color, Imaging im, char *ink) {
g = (UINT8)(r >> 8);
r = (UINT8)r;
} else {
int tupleSize = PyTuple_GET_SIZE(color);
a = 255;
if (im->bands == 2) {
if (tupleSize != 1 && tupleSize != 2) {
PyErr_SetString(PyExc_TypeError, "color must be int, or tuple of one or two elements");
@ -593,6 +594,41 @@ getink(PyObject *color, Imaging im, char *ink) {
ink[1] = (UINT8)(r >> 8);
ink[2] = ink[3] = 0;
return ink;
} else {
if (rIsInt) {
b = (UINT8)(r >> 16);
g = (UINT8)(r >> 8);
r = (UINT8)r;
} else if (tupleSize != 3) {
PyErr_SetString(PyExc_TypeError, "color must be int, or tuple of one or three elements");
return NULL;
} else if (!PyArg_ParseTuple(color, "Lii", &r, &g, &b)) {
return NULL;
}
if (!strcmp(im->mode, "BGR;15")) {
UINT16 v = ((((UINT16)r) << 7) & 0x7c00) +
((((UINT16)g) << 2) & 0x03e0) +
((((UINT16)b) >> 3) & 0x001f);
ink[0] = (UINT8)v;
ink[1] = (UINT8)(v >> 8);
ink[2] = ink[3] = 0;
return ink;
} else if (!strcmp(im->mode, "BGR;16")) {
UINT16 v = ((((UINT16)r) << 8) & 0xf800) +
((((UINT16)g) << 3) & 0x07e0) +
((((UINT16)b) >> 3) & 0x001f);
ink[0] = (UINT8)v;
ink[1] = (UINT8)(v >> 8);
ink[2] = ink[3] = 0;
return ink;
} else if (!strcmp(im->mode, "BGR;24")) {
ink[0] = (UINT8)b;
ink[1] = (UINT8)g;
ink[2] = (UINT8)r;
ink[3] = 0;
return ink;
}
}
}
@ -3810,6 +3846,7 @@ static PyTypeObject PixelAccess_Type = {
static PyObject *
_get_stats(PyObject *self, PyObject *args) {
PyObject *d;
PyObject *v;
ImagingMemoryArena arena = &ImagingDefaultArena;
if (!PyArg_ParseTuple(args, ":get_stats")) {
@ -3820,15 +3857,29 @@ _get_stats(PyObject *self, PyObject *args) {
if (!d) {
return NULL;
}
PyDict_SetItemString(d, "new_count", PyLong_FromLong(arena->stats_new_count));
PyDict_SetItemString(
d, "allocated_blocks", PyLong_FromLong(arena->stats_allocated_blocks));
PyDict_SetItemString(
d, "reused_blocks", PyLong_FromLong(arena->stats_reused_blocks));
PyDict_SetItemString(
d, "reallocated_blocks", PyLong_FromLong(arena->stats_reallocated_blocks));
PyDict_SetItemString(d, "freed_blocks", PyLong_FromLong(arena->stats_freed_blocks));
PyDict_SetItemString(d, "blocks_cached", PyLong_FromLong(arena->blocks_cached));
v = PyLong_FromLong(arena->stats_new_count);
PyDict_SetItemString(d, "new_count", v ? v : Py_None);
Py_XDECREF(v);
v = PyLong_FromLong(arena->stats_allocated_blocks);
PyDict_SetItemString(d, "allocated_blocks", v ? v : Py_None);
Py_XDECREF(v);
v = PyLong_FromLong(arena->stats_reused_blocks);
PyDict_SetItemString(d, "reused_blocks", v ? v : Py_None);
Py_XDECREF(v);
v = PyLong_FromLong(arena->stats_reallocated_blocks);
PyDict_SetItemString(d, "reallocated_blocks", v ? v : Py_None);
Py_XDECREF(v);
v = PyLong_FromLong(arena->stats_freed_blocks);
PyDict_SetItemString(d, "freed_blocks", v ? v : Py_None);
Py_XDECREF(v);
v = PyLong_FromLong(arena->blocks_cached);
PyDict_SetItemString(d, "blocks_cached", v ? v : Py_None);
Py_XDECREF(v);
return d;
}
@ -4197,28 +4248,33 @@ setup_module(PyObject *m) {
#ifdef HAVE_LIBJPEG
{
extern const char *ImagingJpegVersion(void);
PyDict_SetItemString(
d, "jpeglib_version", PyUnicode_FromString(ImagingJpegVersion()));
PyObject *v = PyUnicode_FromString(ImagingJpegVersion());
PyDict_SetItemString(d, "jpeglib_version", v ? v : Py_None);
Py_XDECREF(v);
}
#endif
#ifdef HAVE_OPENJPEG
{
extern const char *ImagingJpeg2KVersion(void);
PyDict_SetItemString(
d, "jp2klib_version", PyUnicode_FromString(ImagingJpeg2KVersion()));
PyObject *v = PyUnicode_FromString(ImagingJpeg2KVersion());
PyDict_SetItemString(d, "jp2klib_version", v ? v : Py_None);
Py_XDECREF(v);
}
#endif
PyObject *have_libjpegturbo;
#ifdef LIBJPEG_TURBO_VERSION
have_libjpegturbo = Py_True;
{
#define tostr1(a) #a
#define tostr(a) tostr1(a)
PyDict_SetItemString(
d, "libjpeg_turbo_version", PyUnicode_FromString(tostr(LIBJPEG_TURBO_VERSION)));
PyObject *v = PyUnicode_FromString(tostr(LIBJPEG_TURBO_VERSION));
PyDict_SetItemString(d, "libjpeg_turbo_version", v ? v : Py_None);
Py_XDECREF(v);
#undef tostr
#undef tostr1
}
#else
have_libjpegturbo = Py_False;
#endif
@ -4230,8 +4286,9 @@ setup_module(PyObject *m) {
have_libimagequant = Py_True;
{
extern const char *ImagingImageQuantVersion(void);
PyDict_SetItemString(
d, "imagequant_version", PyUnicode_FromString(ImagingImageQuantVersion()));
PyObject *v = PyUnicode_FromString(ImagingImageQuantVersion());
PyDict_SetItemString(d, "imagequant_version", v ? v : Py_None);
Py_XDECREF(v);
}
#else
have_libimagequant = Py_False;
@ -4248,16 +4305,18 @@ setup_module(PyObject *m) {
PyModule_AddIntConstant(m, "FIXED", Z_FIXED);
{
extern const char *ImagingZipVersion(void);
PyDict_SetItemString(
d, "zlib_version", PyUnicode_FromString(ImagingZipVersion()));
PyObject *v = PyUnicode_FromString(ImagingZipVersion());
PyDict_SetItemString(d, "zlib_version", v ? v : Py_None);
Py_XDECREF(v);
}
#endif
#ifdef HAVE_LIBTIFF
{
extern const char *ImagingTiffVersion(void);
PyDict_SetItemString(
d, "libtiff_version", PyUnicode_FromString(ImagingTiffVersion()));
PyObject *v = PyUnicode_FromString(ImagingTiffVersion());
PyDict_SetItemString(d, "libtiff_version", v ? v : Py_None);
Py_XDECREF(v);
// Test for libtiff 4.0 or later, excluding libtiff 3.9.6 and 3.9.7
PyObject *support_custom_tags;
@ -4280,7 +4339,9 @@ setup_module(PyObject *m) {
Py_INCREF(have_xcb);
PyModule_AddObject(m, "HAVE_XCB", have_xcb);
PyDict_SetItemString(d, "PILLOW_VERSION", PyUnicode_FromString(version));
PyObject *pillow_version = PyUnicode_FromString(version);
PyDict_SetItemString(d, "PILLOW_VERSION", pillow_version ? pillow_version : Py_None);
Py_XDECREF(pillow_version);
return 0;
}

View File

@ -116,7 +116,7 @@ cms_profile_open(PyObject *self, PyObject *args) {
}
static PyObject *
cms_profile_fromstring(PyObject *self, PyObject *args) {
cms_profile_frombytes(PyObject *self, PyObject *args) {
cmsHPROFILE hProfile;
char *pProfile;
@ -950,6 +950,8 @@ _is_intent_supported(CmsProfileObject *self, int clut) {
return Py_None;
}
PyDict_SetItem(result, id, entry);
Py_DECREF(id);
Py_DECREF(entry);
}
return result;
}
@ -960,8 +962,7 @@ _is_intent_supported(CmsProfileObject *self, int clut) {
static PyMethodDef pyCMSdll_methods[] = {
{"profile_open", cms_profile_open, METH_VARARGS},
{"profile_frombytes", cms_profile_fromstring, METH_VARARGS},
{"profile_fromstring", cms_profile_fromstring, METH_VARARGS},
{"profile_frombytes", cms_profile_frombytes, METH_VARARGS},
{"profile_tobytes", cms_profile_tobytes, METH_VARARGS},
/* profile and transform functions */
@ -1532,7 +1533,8 @@ setup_module(PyObject *m) {
} else {
v = PyUnicode_FromFormat("%d.%d", vn / 1000, (vn / 10) % 100);
}
PyDict_SetItemString(d, "littlecms_version", v);
PyDict_SetItemString(d, "littlecms_version", v ? v : Py_None);
Py_XDECREF(v);
return 0;
}

Some files were not shown because too many files have changed in this diff Show More