Merge branch 'master' into sbix

This commit is contained in:
Ondrej Baranovič 2021-03-07 20:05:25 +01:00 committed by GitHub
commit 14671f715f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 533 additions and 406 deletions

47
.github/workflows/cifuzz.yml vendored Normal file
View File

@ -0,0 +1,47 @@
name: CIFuzz
on:
push:
paths:
- "**.c"
- "**.h"
pull_request:
paths:
- "**.c"
- "**.h"
jobs:
Fuzzing:
runs-on: ubuntu-latest
steps:
- name: Build Fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
with:
oss-fuzz-project-name: 'pillow'
language: python
dry-run: false
- name: Run Fuzzers
id: run
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: 'pillow'
fuzz-seconds: 600
language: python
dry-run: false
- name: Upload New Crash
uses: actions/upload-artifact@v2
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts
- name: Upload Legacy Crash
uses: actions/upload-artifact@v2
if: steps.run.outcome == 'success'
with:
name: crash
path: ./out/crash*
- name: Fail on legacy crash
if: success()
run: |
[ ! -e out/crash-* ]
echo No legacy crash detected

52
.github/workflows/test-valgrind.yml vendored Normal file
View File

@ -0,0 +1,52 @@
name: Test Valgrind
# like the docker tests, but running valgrind only on *.c/*.h changes.
on:
push:
paths:
- "**.c"
- "**.h"
pull_request:
paths:
- "**.c"
- "**.h"
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
docker: [
ubuntu-20.04-focal-amd64-valgrind,
]
dockerTag: [master]
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@v2
- name: Build system information
run: python3 .github/workflows/system-info.py
- name: Docker pull
run: |
docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
- name: Build and Run Valgrind
run: |
# The Pillow user in the docker container is UID 1000
sudo chown -R 1000 $GITHUB_WORKSPACE
docker run --name pillow_container -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
sudo chown -R runner $GITHUB_WORKSPACE
success:
needs: build
runs-on: ubuntu-latest
name: Valgrind Test Successful
steps:
- name: Success
run: echo Valgrind Test Successful

View File

@ -20,13 +20,37 @@ Changelog (Pillow)
- Support for ignoring tests when running valgrind #5150
[wiredfool, radarhere, hugovk]
- PyModule_AddObject fix for Python 3.10 #5194
[radarhere]
- OSS-Fuzz support #5189
[wiredfool, radarhere]
8.1.0 (2020-01-02)
8.1.2 (2021-03-06)
------------------
- Fix Memory DOS in BLP (CVE-2021-27921), ICNS (CVE-2021-27922) and ICO (CVE-2021-27923) Image Plugins
[wiredfool]
8.1.1 (2021-03-01)
------------------
- Use more specific regex chars to prevent ReDoS. CVE-2021-25292
[hugovk]
- Fix OOB Read in TiffDecode.c, and check the tile validity before reading. CVE-2021-25291
[wiredfool]
- Fix negative size read in TiffDecode.c. CVE-2021-25290
[wiredfool]
- Fix OOB read in SgiRleDecode.c. CVE-2021-25293
[wiredfool]
- Incorrect error code checking in TiffDecode.c. CVE-2021-25289
[wiredfool]
- PyModule_AddObject fix for Python 3.10 #5194
[radarhere]
8.1.0 (2021-01-02)
------------------
- Fix TIFF OOB Write error. CVE-2020-35654 #5175

View File

@ -0,0 +1,40 @@
DejaVuSans-24-{1,2,4,8}-stripped.ttf are based on DejaVuSans.ttf converted using FontForge to add bitmap strikes and keep only the ASCII range.
DejaVu Fonts — License
Fonts are © Bitstream (see below). DejaVu changes are in public domain. Explanation of copyright is on Gnome page on Bitstream Vera fonts. Glyphs imported from Arev fonts are © Tavmjung Bah (see below)
Bitstream Vera Fonts Copyright
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions:
The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera".
This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names.
The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org.
Arev Fonts Copyright
Original text
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the modifications to the Bitstream Vera Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions:
The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Tavmjong Bah" or the word "Arev".
This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Tavmjong Bah Arev" names.
The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr.

View File

@ -17,8 +17,9 @@ OpenSansCondensed-LightItalic.tt, from https://fonts.google.com/specimen/Open+Sa
chromacheck-sbix.woff, from https://github.com/RoelN/ChromaCheck, under The MIT License (MIT), Copyright (c) 2018 Roel Nieskens, https://pixelambacht.nl Copyright (c) 2018 Google LLC
DejaVuSans-24-{1,2,4,8}-stripped.ttf are based on DejaVuSans.ttf converted using FontForge to add bitmap strikes and keep only the ASCII range.
KhmerOSBattambang-Regular.ttf is licensed under LGPL-2.1 or later.
FreeMono.ttf is licensed under GPLv3.
10x20-ISO8859-1.pcf, from https://packages.ubuntu.com/xenial/xfonts-base

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

BIN
Tests/images/odd_stride.pcx Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

View File

@ -140,3 +140,11 @@ def test_not_an_icns_file():
with io.BytesIO(b"invalid\n") as fp:
with pytest.raises(SyntaxError):
IcnsImagePlugin.IcnsFile(fp)
def test_icns_decompression_bomb():
with pytest.raises(Image.DecompressionBombError):
im = Image.open(
"Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns"
)
im.load()

View File

@ -89,6 +89,20 @@ def test_frame_size():
assert im.size == (680, 480)
def test_ignore_frame_size():
# Ignore the different size of the second frame
# since this is not a "Large Thumbnail" image
with Image.open("Tests/images/ignore_frame_size.mpo") as im:
assert im.size == (64, 64)
im.seek(1)
assert (
im.mpinfo[0xB002][1]["Attribute"]["MPType"]
== "Multi-Frame Image: (Disparity)"
)
assert im.size == (64, 64)
def test_parallax():
# Nintendo
with Image.open("Tests/images/sugarshack.mpo") as im:
@ -132,7 +146,7 @@ def test_mp_attribute():
with Image.open(test_file) as im:
mpinfo = im._getmp()
frameNumber = 0
for mpentry in mpinfo[45058]:
for mpentry in mpinfo[0xB002]:
mpattr = mpentry["Attribute"]
if frameNumber:
assert not mpattr["RepresentativeImageFlag"]

View File

@ -44,6 +44,14 @@ def test_odd(tmp_path):
_roundtrip(tmp_path, hopper(mode).resize((511, 511)))
def test_odd_read():
# Reading an image with an odd stride, making it malformed
with Image.open("Tests/images/odd_stride.pcx") as im:
im.load()
assert im.size == (371, 150)
def test_pil184():
# Check reading of files where xmin/xmax is not zero.

View File

@ -692,6 +692,20 @@ def test_rectangle_I16():
assert_image_equal_tofile(im.convert("I"), "Tests/images/imagedraw_rectangle_I.png")
def test_rectangle_translucent_outline():
# Arrange
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im, "RGBA")
# Act
draw.rectangle(BBOX1, fill="black", outline=(0, 255, 0, 127), width=5)
# Assert
assert_image_equal_tofile(
im, "Tests/images/imagedraw_rectangle_translucent_outline.png"
)
def test_floodfill():
red = ImageColor.getrgb("red")

View File

@ -51,7 +51,7 @@ class TestImageFont:
ttf_copy = ttf.font_variant(size=FONT_SIZE + 1)
assert ttf_copy.size == FONT_SIZE + 1
second_font_path = "Tests/fonts/DejaVuSans.ttf"
second_font_path = "Tests/fonts/DejaVuSans/DejaVuSans.ttf"
ttf_copy = ttf.font_variant(font=second_font_path)
assert ttf_copy.path == second_font_path
@ -153,8 +153,8 @@ class TestImageFont:
("text", "L", "FreeMono.ttf", 15, 36, 36),
("text", "1", "FreeMono.ttf", 15, 36, 36),
# issue 4177
("rrr", "L", "DejaVuSans.ttf", 18, 21, 22.21875),
("rrr", "1", "DejaVuSans.ttf", 18, 24, 22.21875),
("rrr", "L", "DejaVuSans/DejaVuSans.ttf", 18, 21, 22.21875),
("rrr", "1", "DejaVuSans/DejaVuSans.ttf", 18, 24, 22.21875),
# test 'l' not including extra margin
# using exact value 2047 / 64 for raqm, checked with debugger
("ill", "L", "OpenSansCondensed-LightItalic.ttf", 63, 33, 31.984375),
@ -835,7 +835,7 @@ class TestImageFont:
layout_name = ["basic", "raqm"][self.LAYOUT_ENGINE]
target = f"Tests/images/bitmap_font_{bpp}_{layout_name}.png"
font = ImageFont.truetype(
f"Tests/fonts/DejaVuSans-24-{bpp}-stripped.ttf",
f"Tests/fonts/DejaVuSans/DejaVuSans-24-{bpp}-stripped.ttf",
24,
layout_engine=self.LAYOUT_ENGINE,
)
@ -978,7 +978,9 @@ def test_render_mono_size():
im = Image.new("P", (100, 30), "white")
draw = ImageDraw.Draw(im)
ttf = ImageFont.truetype(
"Tests/fonts/DejaVuSans.ttf", 18, layout_engine=ImageFont.LAYOUT_BASIC
"Tests/fonts/DejaVuSans/DejaVuSans.ttf",
18,
layout_engine=ImageFont.LAYOUT_BASIC,
)
draw.text((10, 10), "r" * 10, "black", ttf)

View File

@ -10,7 +10,7 @@ from .helper import (
)
FONT_SIZE = 20
FONT_PATH = "Tests/fonts/DejaVuSans.ttf"
FONT_PATH = "Tests/fonts/DejaVuSans/DejaVuSans.ttf"
pytestmark = skip_unless_feature("raqm")

View File

@ -62,4 +62,20 @@ def test_viewer():
def test_viewers():
for viewer in ImageShow._viewers:
viewer.get_command("test.jpg")
try:
viewer.get_command("test.jpg")
except NotImplementedError:
pass
def test_ipythonviewer():
pytest.importorskip("IPython", reason="IPython not installed")
for viewer in ImageShow._viewers:
if isinstance(viewer, ImageShow.IPythonViewer):
test_viewer = viewer
break
else:
assert False
im = hopper()
assert test_viewer.show(im) == 1

View File

@ -4,10 +4,6 @@ import pytest
from PIL import Image
from .helper import is_win32
pytestmark = pytest.mark.skipif(is_win32(), reason="Win32 does not call map_buffer")
def test_overflow():
# There is the potential to overflow comparisons in map.c
@ -27,6 +23,13 @@ def test_overflow():
Image.MAX_IMAGE_PIXELS = max_pixels
def test_tobytes():
# Previously raised an access violation on Windows
with Image.open("Tests/images/l2rgb_read.bmp") as im:
with pytest.raises((ValueError, MemoryError, OSError)):
im.tobytes()
@pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="Requires 64-bit system")
def test_ysize():
numpy = pytest.importorskip("numpy", reason="NumPy not installed")

View File

@ -31,7 +31,7 @@ def test_numpy_to_image():
return i
# Check supported 1-bit integer formats
assert_image(to_image(numpy.bool, 1, 1), "1", TEST_IMAGE_SIZE)
assert_image(to_image(bool, 1, 1), "1", TEST_IMAGE_SIZE)
assert_image(to_image(numpy.bool8, 1, 1), "1", TEST_IMAGE_SIZE)
# Check supported 8-bit integer formats
@ -65,7 +65,7 @@ def test_numpy_to_image():
to_image(numpy.int64)
# Check floating-point formats
assert_image(to_image(numpy.float), "F", TEST_IMAGE_SIZE)
assert_image(to_image(float), "F", TEST_IMAGE_SIZE)
with pytest.raises(TypeError):
to_image(numpy.float16)
assert_image(to_image(numpy.float32), "F", TEST_IMAGE_SIZE)
@ -191,7 +191,7 @@ def test_putdata():
def test_roundtrip_eye():
for dtype in (
numpy.bool,
bool,
numpy.bool8,
numpy.int8,
numpy.int16,
@ -199,7 +199,7 @@ def test_roundtrip_eye():
numpy.uint8,
numpy.uint16,
numpy.uint32,
numpy.float,
float,
numpy.float32,
numpy.float64,
):
@ -218,7 +218,7 @@ def test_zero_size():
def test_bool():
# https://github.com/python-pillow/Pillow/issues/2044
a = numpy.zeros((10, 2), dtype=numpy.bool)
a = numpy.zeros((10, 2), dtype=bool)
a[0][0] = True
im2 = Image.fromarray(a)

View File

@ -11,6 +11,13 @@ from PIL import Image
"Tests/images/sgi_crash.bin",
"Tests/images/crash-6b7f2244da6d0ae297ee0754a424213444e92778.sgi",
"Tests/images/ossfuzz-5730089102868480.sgi",
"Tests/images/crash-754d9c7ec485ffb76a90eeaab191ef69a2a3a3cd.sgi",
"Tests/images/crash-465703f71a0f0094873a3e0e82c9f798161171b8.sgi",
"Tests/images/crash-64834657ee604b8797bf99eac6a194c124a9a8ba.sgi",
"Tests/images/crash-abcf1c97b8fe42a6c68f1fb0b978530c98d57ced.sgi",
"Tests/images/crash-b82e64d4f3f76d7465b6af535283029eda211259.sgi",
"Tests/images/crash-c1b2595b8b0b92cc5f38b6635e98e3a119ade807.sgi",
"Tests/images/crash-db8bfa78b19721225425530c5946217720d7df4e.sgi",
],
)
def test_crashes(test_file):

View File

@ -24,6 +24,15 @@ from .helper import on_ci
"Tests/images/crash_1.tif",
"Tests/images/crash_2.tif",
"Tests/images/crash-2020-10-test.tif",
"Tests/images/crash-0c7e0e8e11ce787078f00b5b0ca409a167f070e0.tif",
"Tests/images/crash-0e16d3bfb83be87356d026d66919deaefca44dac.tif",
"Tests/images/crash-1152ec2d1a1a71395b6f2ce6721c38924d025bf3.tif",
"Tests/images/crash-1185209cf7655b5aed8ae5e77784dfdd18ab59e9.tif",
"Tests/images/crash-338516dbd2f0e83caddb8ce256c22db3bd6dc40f.tif",
"Tests/images/crash-4f085cc12ece8cde18758d42608bed6a2a2cfb1c.tif",
"Tests/images/crash-86214e58da443d2b80820cff9677a38a33dcbbca.tif",
"Tests/images/crash-f46f5b2f43c370fe65706c11449f567ecc345e74.tif",
"Tests/images/crash-63b1dffefc8c075ddc606c0a2f5fdc15ece78863.tif",
],
)
@pytest.mark.filterwarnings("ignore:Possibly corrupt EXIF data")

View File

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

View File

@ -312,3 +312,7 @@ def setup(app):
app.add_js_file("js/script.js")
app.add_css_file("css/dark.css")
app.add_css_file("css/light.css")
# GitHub repo for sphinx-issues
issues_github_path = "python-pillow/Pillow"

View File

@ -269,7 +269,7 @@ decoder that can be used to read various packed formats into a floating point
image memory.
To use the bit decoder with the :py:func:`PIL.Image.frombytes` function, use
the following syntax::
the following syntax:
.. code-block:: python

View File

@ -177,7 +177,7 @@ Many of Pillow's features require external libraries:
* **libimagequant** provides improved color quantization
* Pillow has been tested with libimagequant **2.6-2.14**
* Pillow has been tested with libimagequant **2.6-2.14.1**
* Libimagequant is licensed GPLv3, which is more restrictive than
the Pillow license, therefore we will not be distributing binaries
with libimagequant support enabled.

View File

@ -22,8 +22,8 @@ Windows).
.. code-block:: python
from PIL import Image
im = Image.open("hopper.jpg")
im.rotate(45).show()
with Image.open("hopper.jpg") as im:
im.rotate(45).show()
Create thumbnails
^^^^^^^^^^^^^^^^^
@ -40,9 +40,9 @@ current directory preserving aspect ratios with 128x128 max resolution.
for infile in glob.glob("*.jpg"):
file, ext = os.path.splitext(infile)
im = Image.open(infile)
im.thumbnail(size)
im.save(file + ".thumbnail", "JPEG")
with Image.open(infile) as im:
im.thumbnail(size)
im.save(file + ".thumbnail", "JPEG")
Functions
---------
@ -145,22 +145,21 @@ This crops the input image with the provided coordinates:
from PIL import Image
im = Image.open("hopper.jpg")
with Image.open("hopper.jpg") as im:
# The crop method from the Image module takes four coordinates as input.
# The right can also be represented as (left+width)
# and lower can be represented as (upper+height).
(left, upper, right, lower) = (20, 20, 100, 100)
# The crop method from the Image module takes four coordinates as input.
# The right can also be represented as (left+width)
# and lower can be represented as (upper+height).
(left, upper, right, lower) = (20, 20, 100, 100)
# Here the image "im" is cropped and assigned to new variable im_crop
im_crop = im.crop((left, upper, right, lower))
# Here the image "im" is cropped and assigned to new variable im_crop
im_crop = im.crop((left, upper, right, lower))
.. automethod:: PIL.Image.Image.draft
.. automethod:: PIL.Image.Image.effect_spread
.. automethod:: PIL.Image.Image.entropy
.. automethod:: PIL.Image.Image.filter
.. automethod:: PIL.Image.Image.frombytes
This blurs the input image using a filter from the ``ImageFilter`` module:
@ -168,11 +167,12 @@ This blurs the input image using a filter from the ``ImageFilter`` module:
from PIL import Image, ImageFilter
im = Image.open("hopper.jpg")
with Image.open("hopper.jpg") as im:
# Blur the input image using the filter ImageFilter.BLUR
im_blurred = im.filter(filter=ImageFilter.BLUR)
# Blur the input image using the filter ImageFilter.BLUR
im_blurred = im.filter(filter=ImageFilter.BLUR)
.. automethod:: PIL.Image.Image.frombytes
.. automethod:: PIL.Image.Image.getbands
This helps to get the bands of the input image:
@ -181,8 +181,8 @@ This helps to get the bands of the input image:
from PIL import Image
im = Image.open("hopper.jpg")
print(im.getbands()) # Returns ('R', 'G', 'B')
with Image.open("hopper.jpg") as im:
print(im.getbands()) # Returns ('R', 'G', 'B')
.. automethod:: PIL.Image.Image.getbbox
@ -192,9 +192,9 @@ This helps to get the bounding box coordinates of the input image:
from PIL import Image
im = Image.open("hopper.jpg")
print(im.getbbox())
# Returns four coordinates in the format (left, upper, right, lower)
with Image.open("hopper.jpg") as im:
print(im.getbbox())
# Returns four coordinates in the format (left, upper, right, lower)
.. automethod:: PIL.Image.Image.getchannel
.. automethod:: PIL.Image.Image.getcolors
@ -222,11 +222,11 @@ This resizes the given image from ``(width, height)`` to ``(width/2, height/2)``
from PIL import Image
im = Image.open("hopper.jpg")
with Image.open("hopper.jpg") as im:
# Provide the target width and height of the image
(width, height) = (im.width // 2, im.height // 2)
im_resized = im.resize((width, height))
# Provide the target width and height of the image
(width, height) = (im.width // 2, im.height // 2)
im_resized = im.resize((width, height))
.. automethod:: PIL.Image.Image.rotate
@ -236,12 +236,12 @@ This rotates the input image by ``theta`` degrees counter clockwise:
from PIL import Image
im = Image.open("hopper.jpg")
with Image.open("hopper.jpg") as im:
# Rotate the image by 60 degrees counter clockwise
theta = 60
# Angle is in degrees counter clockwise
im_rotated = im.rotate(angle=theta)
# Rotate the image by 60 degrees counter clockwise
theta = 60
# Angle is in degrees counter clockwise
im_rotated = im.rotate(angle=theta)
.. automethod:: PIL.Image.Image.save
.. automethod:: PIL.Image.Image.seek
@ -260,12 +260,12 @@ This flips the input image by using the :data:`FLIP_LEFT_RIGHT` method.
from PIL import Image
im = Image.open("hopper.jpg")
with Image.open("hopper.jpg") as im:
# Flip the image from left to right
im_flipped = im.transpose(method=Image.FLIP_LEFT_RIGHT)
# To flip the image from top to bottom,
# use the method "Image.FLIP_TOP_BOTTOM"
# Flip the image from left to right
im_flipped = im.transpose(method=Image.FLIP_LEFT_RIGHT)
# To flip the image from top to bottom,
# use the method "Image.FLIP_TOP_BOTTOM"
.. automethod:: PIL.Image.Image.verify

View File

@ -81,24 +81,24 @@ Example: Draw Partial Opacity Text
from PIL import Image, ImageDraw, ImageFont
# get an image
base = Image.open("Pillow/Tests/images/hopper.png").convert("RGBA")
with Image.open("Pillow/Tests/images/hopper.png").convert("RGBA") as base:
# make a blank image for the text, initialized to transparent text color
txt = Image.new("RGBA", base.size, (255,255,255,0))
# make a blank image for the text, initialized to transparent text color
txt = Image.new("RGBA", base.size, (255,255,255,0))
# get a font
fnt = ImageFont.truetype("Pillow/Tests/fonts/FreeMono.ttf", 40)
# get a drawing context
d = ImageDraw.Draw(txt)
# get a font
fnt = ImageFont.truetype("Pillow/Tests/fonts/FreeMono.ttf", 40)
# get a drawing context
d = ImageDraw.Draw(txt)
# draw text, half opacity
d.text((10,10), "Hello", font=fnt, fill=(255,255,255,128))
# draw text, full opacity
d.text((10,60), "World", font=fnt, fill=(255,255,255,255))
# draw text, half opacity
d.text((10,10), "Hello", font=fnt, fill=(255,255,255,128))
# draw text, full opacity
d.text((10,60), "World", font=fnt, fill=(255,255,255,255))
out = Image.alpha_composite(base, txt)
out = Image.alpha_composite(base, txt)
out.show()
out.show()
Example: Draw Multiline Text
----------------------------

View File

@ -15,11 +15,11 @@ Example: Using the :py:mod:`~PIL.ImageMath` module
from PIL import Image, ImageMath
im1 = Image.open("image1.jpg")
im2 = Image.open("image2.jpg")
with Image.open("image1.jpg") as im1:
with Image.open("image2.jpg") as im2:
out = ImageMath.eval("convert(min(a, b), 'L')", a=im1, b=im2)
out.save("result.png")
out = ImageMath.eval("convert(min(a, b), 'L')", a=im1, b=im2)
out.save("result.png")
.. py:function:: eval(expression, environment)

View File

@ -9,6 +9,7 @@ All default viewers convert the image to be shown to PNG format.
.. autofunction:: PIL.ImageShow.show
.. autoclass:: IPythonViewer
.. autoclass:: WindowsViewer
.. autoclass:: MacViewer

View File

@ -63,8 +63,8 @@ Take your test image, and make a really simple harness.
::
from PIL import Image
im = Image.open(path)
im.load()
with Image.open(path) as im:
im.load()
- Run this through valgrind, but note that python triggers some issues
on its own, so you're looking for items within the Pillow hierarchy

View File

@ -74,7 +74,7 @@ Security
This release includes security fixes.
* :cve:`CVE-2020-10177` Fix multiple OOB reads in FLI decoding
* :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

View File

@ -18,7 +18,7 @@ vulnerability introduced in FreeType 2.6 (:cve:`CVE-2020-15999`).
Makefile
^^^^^^^^
The 'install-venv' target has been deprecated.
The ``install-venv`` target has been deprecated.
API Additions
=============
@ -46,17 +46,18 @@ 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 OOB Write error
* :cve:`CVE-2020-35654` Fix TIFF out-of-bounds write error
OOB 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 LibTIFF's
interpretation of the file is different when reading in RGBA mode, leading to an Out of
bounds write in TiffDecode.c. This potentially affects Pillow versions from 6.0.0 to
8.0.1, depending on the version of LibTIFF. This was reported through `Tidelift`_.
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
LibTIFF's interpretation of the file is different when reading in RGBA mode, leading to
an out-of-bounds write in ``TiffDecode.c``. This potentially affects Pillow versions
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
4 byte read overflow in SGIRleDecode.c, where the code was not correctly checking the
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
`OSS-Fuzz`_. This vulnerability covers Pillow versions 4.3.0->8.0.1.
@ -78,7 +79,7 @@ Other Changes
Makefile
^^^^^^^^
The 'co' target has been removed.
The ``co`` target has been removed.
PyPy wheels
^^^^^^^^^^^

View File

@ -0,0 +1,27 @@
8.1.1
-----
Security
========
:cve:`CVE-2021-25289`: The previous fix for :cve:`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``
with an invalid size.
:cve:`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
that could be used as a DOS attack.
:cve:`CVE-2021-25293`: There is an out-of-bounds read in ``SgiRleDecode.c``,
since Pillow 4.3.0.
Other Changes
=============
A crash with the feature flags for libimagequant, libjpeg-turbo, WebP and XCB on
unreleased Python 3.10 has been fixed (:issue:`5193`).

View File

@ -0,0 +1,12 @@
8.1.2
-----
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
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
`Arizona State University <https://www.asu.edu/>`_.

View File

@ -21,10 +21,17 @@ TODO
API Additions
=============
TODO
^^^^
ImageShow.IPythonViewer
^^^^^^^^^^^^^^^^^^^^^^^
TODO
If IPython is present, this new ``ImageShow.Viewer`` subclass will be
registered. It displays images on all IPython frontends. This will be helpful
to users of Google Colab, allowing ``im.show()`` to display images.
It is lower in priority than the other default Viewer instances, so it will
only be used by ``im.show()`` or ``ImageShow.show()`` if none of the other
viewers are available. This means that the behaviour of ``ImageShow`` will stay
the same for most Pillow users.
Security
========

View File

@ -15,6 +15,8 @@ expected to be backported to earlier versions.
:maxdepth: 2
8.2.0
8.1.2
8.1.1
8.1.0
8.0.1
8.0.0

View File

@ -353,6 +353,7 @@ class BLP1Decoder(_BLPBaseDecoder):
data = jpeg_header + data
data = BytesIO(data)
image = JpegImageFile(data)
Image._decompression_bomb_check(image.size)
self.tile = image.tile # :/
self.fd = image.fp
self.mode = image.mode

View File

@ -105,6 +105,7 @@ def read_png_or_jpeg2000(fobj, start_length, size):
if sig[:8] == b"\x89PNG\x0d\x0a\x1a\x0a":
fobj.seek(start)
im = PngImagePlugin.PngImageFile(fobj)
Image._decompression_bomb_check(im.size)
return {"RGBA": im}
elif (
sig[:4] == b"\xff\x4f\xff\x51"
@ -121,6 +122,7 @@ def read_png_or_jpeg2000(fobj, start_length, size):
jp2kstream = fobj.read(length)
f = io.BytesIO(jp2kstream)
im = Jpeg2KImagePlugin.Jpeg2KImageFile(f)
Image._decompression_bomb_check(im.size)
if im.mode != "RGBA":
im = im.convert("RGBA")
return {"RGBA": im}

View File

@ -178,6 +178,7 @@ class IcoFile:
if data[:8] == PngImagePlugin._MAGIC:
# png frame
im = PngImagePlugin.PngImageFile(self.buf)
Image._decompression_bomb_check(im.size)
else:
# XOR + AND mask bmp frame
im = BmpImagePlugin.DibImageFile(self.buf)

View File

@ -192,24 +192,14 @@ class ImageFile(Image.Image):
and args[0] in Image._MAPMODES
):
try:
if hasattr(Image.core, "map"):
# use built-in mapper WIN32 only
self.map = Image.core.map(self.filename)
self.map.seek(offset)
self.im = self.map.readimage(
self.mode, self.size, args[1], args[2]
)
else:
# use mmap, if possible
import mmap
# use mmap, if possible
import mmap
with open(self.filename) as fp:
self.map = mmap.mmap(
fp.fileno(), 0, access=mmap.ACCESS_READ
)
self.im = Image.core.map_buffer(
self.map, self.size, decoder_name, offset, args
)
with open(self.filename) as fp:
self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ)
self.im = Image.core.map_buffer(
self.map, self.size, decoder_name, offset, args
)
readonly = 1
# After trashing self.im,
# we might need to reload the palette data.

View File

@ -69,7 +69,6 @@ class Viewer:
Converts the given image to the target format and displays it.
"""
# save temporary image to disk
if not (
image.mode in ("1", "RGBA")
or (self.format == "PNG" and image.mode in ("I;16", "LA"))
@ -226,6 +225,23 @@ if sys.platform not in ("win32", "darwin"): # unixoids
if shutil.which("xv"):
register(XVViewer)
class IPythonViewer(Viewer):
"""The viewer for IPython frontends."""
def show_image(self, image, **options):
ipython_display(image)
return 1
try:
from IPython.display import display as ipython_display
except ImportError:
pass
else:
register(IPythonViewer)
if __name__ == "__main__":
if len(sys.argv) < 2:

View File

@ -82,9 +82,11 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
n = i16(self.fp.read(2)) - 2
self.info["exif"] = ImageFile._safe_read(self.fp, n)
exif = self.getexif()
if 40962 in exif and 40963 in exif:
self._size = (exif[40962], exif[40963])
mptype = self.mpinfo[0xB002][frame]["Attribute"]["MPType"]
if mptype.startswith("Large Thumbnail"):
exif = self.getexif()
if 40962 in exif and 40963 in exif:
self._size = (exif[40962], exif[40963])
elif "exif" in self.info:
del self.info["exif"]

View File

@ -66,13 +66,13 @@ class PcxImageFile(ImageFile.ImageFile):
version = s[1]
bits = s[3]
planes = s[65]
ignored_stride = i16(s, 66)
provided_stride = i16(s, 66)
logger.debug(
"PCX version %s, bits %s, planes %s, stride %s",
version,
bits,
planes,
ignored_stride,
provided_stride,
)
self.info["dpi"] = i16(s, 12), i16(s, 14)
@ -110,10 +110,15 @@ class PcxImageFile(ImageFile.ImageFile):
self.mode = mode
self._size = bbox[2] - bbox[0], bbox[3] - bbox[1]
# don't trust the passed in stride. Calculate for ourselves.
# Don't trust the passed in stride.
# Calculate the approximate position for ourselves.
# CVE-2020-35653
stride = (self._size[0] * bits + 7) // 8
stride += stride % 2
# While the specification states that this must be even,
# not all images follow this
if provided_stride != stride:
stride += stride % 2
bbox = (0, 0) + self.size
logger.debug("size: %sx%s", *self.size)

View File

@ -580,8 +580,9 @@ class PdfParser:
whitespace_or_hex = br"[\000\011\012\014\015\0400-9a-fA-F]"
whitespace_optional = whitespace + b"*"
whitespace_mandatory = whitespace + b"+"
whitespace_optional_no_nl = br"[\000\011\014\015\040]*" # no "\012" aka "\n"
newline_only = br"[\r\n]+"
newline = whitespace_optional + newline_only + whitespace_optional
newline = whitespace_optional_no_nl + newline_only + whitespace_optional_no_nl
re_trailer_end = re.compile(
whitespace_mandatory
+ br"trailer"

View File

@ -3973,8 +3973,6 @@ PyPath_Create(ImagingObject *self, PyObject *args);
extern PyObject *
PyOutline_Create(ImagingObject *self, PyObject *args);
extern PyObject *
PyImaging_Mapper(PyObject *self, PyObject *args);
extern PyObject *
PyImaging_MapBuffer(PyObject *self, PyObject *args);
@ -4030,9 +4028,6 @@ static PyMethodDef functions[] = {
/* Memory mapping */
#ifdef WITH_MAPPING
#ifdef _WIN32
{"map", (PyCFunction)PyImaging_Mapper, 1},
#endif
{"map_buffer", (PyCFunction)PyImaging_MapBuffer, 1},
#endif

View File

@ -724,8 +724,8 @@ ImagingDrawRectangle(
for (i = 0; i < width; i++) {
draw->hline(im, x0, y0 + i, x1, ink);
draw->hline(im, x0, y1 - i, x1, ink);
draw->line(im, x1 - i, y0, x1 - i, y1, ink);
draw->line(im, x0 + i, y1, x0 + i, y0, ink);
draw->line(im, x1 - i, y0 + width, x1 - i, y1 - width + 1, ink);
draw->line(im, x0 + i, y0 + width, x0 + i, y1 - width + 1, ink);
}
}

View File

@ -385,7 +385,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) {
float *pq;
if (len > 0) {
if ((unsigned)len >
if ((size_t)len >
sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0])) {
len = sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0]);
}

View File

@ -25,12 +25,58 @@ read4B(UINT32 *dest, UINT8 *buf) {
*dest = (UINT32)((buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]);
}
/*
SgiRleDecoding is done in a single channel row oriented set of RLE chunks.
* The file is arranged as
- SGI Header
- Rle Offset Table
- Rle Length Table
- Scanline Data
* Each RLE atom is c->bpc bytes wide (1 or 2)
* Each RLE Chunk is [specifier atom] [ 1 or n data atoms ]
* Copy Atoms are a byte with the high bit set, and the low 7 are
the number of bytes to copy from the source to the
destination. e.g.
CBBBBBBBB or 0CHLHLHLHLHLHL (B=byte, H/L = Hi low bytes)
* Run atoms do not have the high bit set, and the low 7 bits are
the number of copies of the next atom to copy to the
destination. e.g.:
RB -> BBBBB or RHL -> HLHLHLHLHL
The upshot of this is, there is no way to determine the required
length of the input buffer from reloffset and rlelength without
going through the data at that scan line.
Furthermore, there's no requirement that individual scan lines
pointed to from the rleoffset table are in any sort of order or
used only once, or even disjoint. There's also no requirement that
all of the data in the scan line area of the image file be used
*/
static int
expandrow(UINT8 *dest, UINT8 *src, int n, int z, int xsize) {
expandrow(UINT8 *dest, UINT8 *src, int n, int z, int xsize, UINT8 *end_of_buffer) {
/*
* n here is the number of rlechunks
* z is the number of channels, for calculating the interleave
* offset to go to RGBA style pixels
* xsize is the row width
* end_of_buffer is the address of the end of the input buffer
*/
UINT8 pixel, count;
int x = 0;
for (; n > 0; n--) {
if (src > end_of_buffer) {
return -1;
}
pixel = *src++;
if (n == 1 && pixel != 0) {
return n;
@ -44,12 +90,18 @@ expandrow(UINT8 *dest, UINT8 *src, int n, int z, int xsize) {
}
x += count;
if (pixel & RLE_COPY_FLAG) {
if (src + count > end_of_buffer) {
return -1;
}
while (count--) {
*dest = *src++;
dest += z;
}
} else {
if (src > end_of_buffer) {
return -1;
}
pixel = *src++;
while (count--) {
*dest = pixel;
@ -61,12 +113,14 @@ expandrow(UINT8 *dest, UINT8 *src, int n, int z, int xsize) {
}
static int
expandrow2(UINT8 *dest, const UINT8 *src, int n, int z, int xsize) {
expandrow2(UINT8 *dest, const UINT8 *src, int n, int z, int xsize, UINT8 *end_of_buffer) {
UINT8 pixel, count;
int x = 0;
for (; n > 0; n--) {
if (src + 1 > end_of_buffer) {
return -1;
}
pixel = src[1];
src += 2;
if (n == 1 && pixel != 0) {
@ -81,12 +135,18 @@ expandrow2(UINT8 *dest, const UINT8 *src, int n, int z, int xsize) {
}
x += count;
if (pixel & RLE_COPY_FLAG) {
if (src + 2 * count > end_of_buffer) {
return -1;
}
while (count--) {
memcpy(dest, src, 2);
src += 2;
dest += z * 2;
}
} else {
if (src + 2 > end_of_buffer) {
return -1;
}
while (count--) {
memcpy(dest, src, 2);
dest += z * 2;
@ -132,7 +192,11 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t
return -1;
}
_imaging_seek_pyFd(state->fd, SGI_HEADER_SIZE, SEEK_SET);
_imaging_read_pyFd(state->fd, (char *)ptr, c->bufsize);
if (_imaging_read_pyFd(state->fd, (char *)ptr, c->bufsize) != c->bufsize) {
state->errcode = IMAGING_CODEC_UNKNOWN;
return -1;
}
/* decoder initialization */
state->count = 0;
@ -166,20 +230,20 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t
read4B(&c->lengthtab[c->tabindex], &ptr[c->bufindex]);
}
state->count += c->tablen * sizeof(UINT32) * 2;
/* read compressed rows */
for (c->rowno = 0; c->rowno < im->ysize; c->rowno++, state->y += state->ystep) {
for (c->channo = 0; c->channo < im->bands; c->channo++) {
c->rleoffset = c->starttab[c->rowno + c->channo * im->ysize];
c->rlelength = c->lengthtab[c->rowno + c->channo * im->ysize];
c->rleoffset -= SGI_HEADER_SIZE;
if (c->rleoffset + c->rlelength > c->bufsize) {
// Check for underflow of rleoffset-SGI_HEADER_SIZE
if (c->rleoffset < SGI_HEADER_SIZE) {
state->errcode = IMAGING_CODEC_OVERRUN;
goto sgi_finish_decode;
}
c->rleoffset -= SGI_HEADER_SIZE;
/* row decompression */
if (c->bpc == 1) {
status = expandrow(
@ -187,14 +251,16 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t
&ptr[c->rleoffset],
c->rlelength,
im->bands,
im->xsize);
im->xsize,
&ptr[c->bufsize-1]);
} else {
status = expandrow2(
&state->buffer[c->channo * 2],
&ptr[c->rleoffset],
c->rlelength,
im->bands,
im->xsize);
im->xsize,
&ptr[c->bufsize-1]);
}
if (status == -1) {
state->errcode = IMAGING_CODEC_OVERRUN;
@ -203,15 +269,12 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t
goto sgi_finish_decode;
}
state->count += c->rlelength;
}
/* store decompressed data in image */
state->shuffle((UINT8 *)im->image[state->y], state->buffer, im->xsize);
}
c->bufsize++;
sgi_finish_decode:;
free(c->starttab);
@ -221,5 +284,5 @@ sgi_finish_decode:;
state->errcode = err;
return -1;
}
return state->count - c->bufsize;
return 0;
}

View File

@ -55,6 +55,10 @@ _tiffReadProc(thandle_t hdata, tdata_t buf, tsize_t size) {
TRACE(("_tiffReadProc: %d \n", (int)size));
dump_state(state);
if (state->loc > state->eof) {
TIFFError("_tiffReadProc", "Invalid Read at loc %d, eof: %d", state->loc, state->eof);
return 0;
}
to_read = min(size, min(state->size, (tsize_t)state->eof) - (tsize_t)state->loc);
TRACE(("to_read: %d\n", (int)to_read));
@ -282,8 +286,7 @@ _decodeStripYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) {
img.row_offset = state->y;
rows_to_read = min(rows_per_strip, img.height - state->y);
if (TIFFRGBAImageGet(&img, (UINT32 *)state->buffer, img.width, rows_to_read) ==
-1) {
if (!TIFFRGBAImageGet(&img, (UINT32 *)state->buffer, img.width, rows_to_read)) {
TRACE(("Decode Error, y: %d\n", state->y));
state->errcode = IMAGING_CODEC_BROKEN;
goto decodeycbcr_err;
@ -559,6 +562,15 @@ ImagingLibTiffDecode(
for (y = state->yoff; y < state->ysize; y += tile_length) {
for (x = state->xoff; x < state->xsize; x += tile_width) {
/* Sanity Check. Apparently in some cases, the TiffReadRGBA* functions
have a different view of the size of the tiff than we're getting from
other functions. So, we need to check here.
*/
if (!TIFFCheckTile(tiff, x, y, 0, 0)) {
TRACE(("Check Tile Error, Tile at %dx%d\n", x, y));
state->errcode = IMAGING_CODEC_BROKEN;
goto decode_err;
}
if (isYCbCr) {
/* To avoid dealing with YCbCr subsampling, let libtiff handle it */
if (!TIFFReadRGBATile(tiff, x, y, (UINT32 *)state->buffer)) {

260
src/map.c
View File

@ -28,269 +28,9 @@ PyImaging_CheckBuffer(PyObject *buffer);
extern int
PyImaging_GetBuffer(PyObject *buffer, Py_buffer *view);
/* -------------------------------------------------------------------- */
/* Standard mapper */
typedef struct {
PyObject_HEAD char *base;
int size;
int offset;
#ifdef _WIN32
HANDLE hFile;
HANDLE hMap;
#endif
} ImagingMapperObject;
static PyTypeObject ImagingMapperType;
ImagingMapperObject *
PyImaging_MapperNew(const char *filename, int readonly) {
ImagingMapperObject *mapper;
if (PyType_Ready(&ImagingMapperType) < 0) {
return NULL;
}
mapper = PyObject_New(ImagingMapperObject, &ImagingMapperType);
if (mapper == NULL) {
return NULL;
}
mapper->base = NULL;
mapper->size = mapper->offset = 0;
#ifdef _WIN32
mapper->hFile = (HANDLE)-1;
mapper->hMap = (HANDLE)-1;
/* FIXME: currently supports readonly mappings only */
mapper->hFile = CreateFile(
filename,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (mapper->hFile == (HANDLE)-1) {
PyErr_SetString(PyExc_OSError, "cannot open file");
Py_DECREF(mapper);
return NULL;
}
mapper->hMap = CreateFileMapping(mapper->hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (mapper->hMap == (HANDLE)-1) {
CloseHandle(mapper->hFile);
PyErr_SetString(PyExc_OSError, "cannot map file");
Py_DECREF(mapper);
return NULL;
}
mapper->base = (char *)MapViewOfFile(mapper->hMap, FILE_MAP_READ, 0, 0, 0);
mapper->size = GetFileSize(mapper->hFile, 0);
#endif
return mapper;
}
static void
mapping_dealloc(ImagingMapperObject *mapper) {
#ifdef _WIN32
if (mapper->base != 0) {
UnmapViewOfFile(mapper->base);
}
if (mapper->hMap != (HANDLE)-1) {
CloseHandle(mapper->hMap);
}
if (mapper->hFile != (HANDLE)-1) {
CloseHandle(mapper->hFile);
}
mapper->base = 0;
mapper->hMap = mapper->hFile = (HANDLE)-1;
#endif
PyObject_Del(mapper);
}
/* -------------------------------------------------------------------- */
/* standard file operations */
static PyObject *
mapping_read(ImagingMapperObject *mapper, PyObject *args) {
PyObject *buf;
int size = -1;
if (!PyArg_ParseTuple(args, "|i", &size)) {
return NULL;
}
/* check size */
if (size < 0 || mapper->offset + size > mapper->size) {
size = mapper->size - mapper->offset;
}
if (size < 0) {
size = 0;
}
buf = PyBytes_FromStringAndSize(NULL, size);
if (!buf) {
return NULL;
}
if (size > 0) {
memcpy(PyBytes_AsString(buf), mapper->base + mapper->offset, size);
mapper->offset += size;
}
return buf;
}
static PyObject *
mapping_seek(ImagingMapperObject *mapper, PyObject *args) {
int offset;
int whence = 0;
if (!PyArg_ParseTuple(args, "i|i", &offset, &whence)) {
return NULL;
}
switch (whence) {
case 0: /* SEEK_SET */
mapper->offset = offset;
break;
case 1: /* SEEK_CUR */
mapper->offset += offset;
break;
case 2: /* SEEK_END */
mapper->offset = mapper->size + offset;
break;
default:
/* FIXME: raise ValueError? */
break;
}
Py_INCREF(Py_None);
return Py_None;
}
/* -------------------------------------------------------------------- */
/* map entire image */
extern PyObject *
PyImagingNew(Imaging im);
static void
ImagingDestroyMap(Imaging im) {
return; /* nothing to do! */
}
static PyObject *
mapping_readimage(ImagingMapperObject *mapper, PyObject *args) {
int y, size;
Imaging im;
char *mode;
int xsize;
int ysize;
int stride;
int orientation;
if (!PyArg_ParseTuple(
args, "s(ii)ii", &mode, &xsize, &ysize, &stride, &orientation)) {
return NULL;
}
if (stride <= 0) {
/* FIXME: maybe we should call ImagingNewPrologue instead */
if (!strcmp(mode, "L") || !strcmp(mode, "P")) {
stride = xsize;
} else if (!strcmp(mode, "I;16") || !strcmp(mode, "I;16B")) {
stride = xsize * 2;
} else {
stride = xsize * 4;
}
}
size = ysize * stride;
if (mapper->offset + size > mapper->size) {
PyErr_SetString(PyExc_OSError, "image file truncated");
return NULL;
}
im = ImagingNewPrologue(mode, xsize, ysize);
if (!im) {
return NULL;
}
/* setup file pointers */
if (orientation > 0) {
for (y = 0; y < ysize; y++) {
im->image[y] = mapper->base + mapper->offset + y * stride;
}
} else {
for (y = 0; y < ysize; y++) {
im->image[ysize - y - 1] = mapper->base + mapper->offset + y * stride;
}
}
im->destroy = ImagingDestroyMap;
mapper->offset += size;
return PyImagingNew(im);
}
static struct PyMethodDef methods[] = {
/* standard file interface */
{"read", (PyCFunction)mapping_read, 1},
{"seek", (PyCFunction)mapping_seek, 1},
/* extensions */
{"readimage", (PyCFunction)mapping_readimage, 1},
{NULL, NULL} /* sentinel */
};
static PyTypeObject ImagingMapperType = {
PyVarObject_HEAD_INIT(NULL, 0) "ImagingMapper", /*tp_name*/
sizeof(ImagingMapperObject), /*tp_size*/
0, /*tp_itemsize*/
/* methods */
(destructor)mapping_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number */
0, /*tp_as_sequence */
0, /*tp_as_mapping */
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
methods, /*tp_methods*/
0, /*tp_members*/
0, /*tp_getset*/
};
PyObject *
PyImaging_Mapper(PyObject *self, PyObject *args) {
char *filename;
if (!PyArg_ParseTuple(args, "s", &filename)) {
return NULL;
}
return (PyObject *)PyImaging_MapperNew(filename, 1);
}
/* -------------------------------------------------------------------- */
/* Buffer mapper */

View File

@ -255,10 +255,10 @@ deps = {
"libs": [r"bin\*.lib"],
},
"libimagequant": {
# e5d454b: Merge tag '2.12.6' into msvc
"url": "https://github.com/ImageOptim/libimagequant/archive/e5d454bc7f5eb63ee50c84a83a7fa5ac94f68ec4.zip", # noqa: E501
"filename": "libimagequant-e5d454bc7f5eb63ee50c84a83a7fa5ac94f68ec4.zip",
"dir": "libimagequant-e5d454bc7f5eb63ee50c84a83a7fa5ac94f68ec4",
# Merge master into msvc (matches 2.14.1 except for version bump)
"url": "https://github.com/ImageOptim/libimagequant/archive/16adaded22d1f90db5c9154a06d00a8b672ca09a.zip", # noqa: E501
"filename": "libimagequant-16adaded22d1f90db5c9154a06d00a8b672ca09a.zip",
"dir": "libimagequant-16adaded22d1f90db5c9154a06d00a8b672ca09a",
"patch": {
"CMakeLists.txt": {
"add_library": "add_compile_options(-openmp-)\r\nadd_library",