Merge branch 'master' into alpha_composite
4
.github/workflows/test-windows.yml
vendored
|
@ -110,7 +110,7 @@ jobs:
|
|||
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||
run: "& winbuild\\build\\build_dep_libwebp.cmd"
|
||||
|
||||
# for FreeType CBDT font support
|
||||
# for FreeType CBDT/SBIX font support
|
||||
- name: Build dependencies / libpng
|
||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||
run: "& winbuild\\build\\build_dep_libpng.cmd"
|
||||
|
@ -174,7 +174,7 @@ jobs:
|
|||
if: failure()
|
||||
run: |
|
||||
mkdir -p Tests/errors
|
||||
shell: pwsh
|
||||
shell: bash
|
||||
|
||||
- name: Upload errors
|
||||
uses: actions/upload-artifact@v2
|
||||
|
|
1
.github/workflows/test.yml
vendored
|
@ -92,7 +92,6 @@ jobs:
|
|||
if: failure()
|
||||
run: |
|
||||
mkdir -p Tests/errors
|
||||
shell: pwsh
|
||||
|
||||
- name: Upload errors
|
||||
uses: actions/upload-artifact@v2
|
||||
|
|
15
CHANGES.rst
|
@ -5,6 +5,21 @@ Changelog (Pillow)
|
|||
8.2.0 (unreleased)
|
||||
------------------
|
||||
|
||||
- Added IPythonViewer #5289
|
||||
[radarhere, Kipkurui-mutai]
|
||||
|
||||
- Only draw each rectangle outline pixel once #5183
|
||||
[radarhere]
|
||||
|
||||
- Use mmap instead of built-in Win32 mapper #5224
|
||||
[radarhere, cgohlke]
|
||||
|
||||
- Handle PCX images with an odd stride #5214
|
||||
[radarhere]
|
||||
|
||||
- Only read different sizes for "Large Thumbnail" MPO frames #5168
|
||||
[radarhere]
|
||||
|
||||
- Added PyQt6 support #5258
|
||||
[radarhere]
|
||||
|
||||
|
|
40
Tests/fonts/DejaVuSans/LICENSE.txt
Normal 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.
|
|
@ -15,8 +15,11 @@ FreeMono.ttf is licensed under GPLv3, with the GPL font exception.
|
|||
|
||||
OpenSansCondensed-LightItalic.tt, from https://fonts.google.com/specimen/Open+Sans, under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
|
BIN
Tests/fonts/chromacheck-sbix.woff
Normal file
BIN
Tests/images/chromacheck-sbix.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Tests/images/chromacheck-sbix_mask.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Tests/images/ignore_frame_size.mpo
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
Tests/images/imagedraw_rectangle_translucent_outline.png
Normal file
After Width: | Height: | Size: 235 B |
BIN
Tests/images/imagedraw_rounded_rectangle.png
Normal file
After Width: | Height: | Size: 934 B |
BIN
Tests/images/imagedraw_rounded_rectangle_both.png
Normal file
After Width: | Height: | Size: 530 B |
BIN
Tests/images/imagedraw_rounded_rectangle_x.png
Normal file
After Width: | Height: | Size: 567 B |
BIN
Tests/images/imagedraw_rounded_rectangle_y.png
Normal file
After Width: | Height: | Size: 528 B |
BIN
Tests/images/odd_stride.pcx
Normal file
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
|
@ -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"]
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -692,6 +692,72 @@ 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"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"xy",
|
||||
[(10, 20, 190, 180), ([10, 20], [190, 180]), ((10, 20), (190, 180))],
|
||||
)
|
||||
def test_rounded_rectangle(xy):
|
||||
# Arrange
|
||||
im = Image.new("RGB", (200, 200))
|
||||
draw = ImageDraw.Draw(im)
|
||||
|
||||
# Act
|
||||
draw.rounded_rectangle(xy, 30, fill="red", outline="green", width=5)
|
||||
|
||||
# Assert
|
||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_rounded_rectangle.png")
|
||||
|
||||
|
||||
def test_rounded_rectangle_zero_radius():
|
||||
# Arrange
|
||||
im = Image.new("RGB", (W, H))
|
||||
draw = ImageDraw.Draw(im)
|
||||
|
||||
# Act
|
||||
draw.rounded_rectangle(BBOX1, 0, fill="blue", outline="green", width=5)
|
||||
|
||||
# Assert
|
||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_width_fill.png")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"xy, suffix",
|
||||
[
|
||||
((20, 10, 80, 90), "x"),
|
||||
((10, 20, 90, 80), "y"),
|
||||
((20, 20, 80, 80), "both"),
|
||||
],
|
||||
)
|
||||
def test_rounded_rectangle_translucent(xy, suffix):
|
||||
# Arrange
|
||||
im = Image.new("RGB", (W, H))
|
||||
draw = ImageDraw.Draw(im, "RGBA")
|
||||
|
||||
# Act
|
||||
draw.rounded_rectangle(
|
||||
xy, 30, fill=(255, 0, 0, 127), outline=(0, 255, 0, 127), width=5
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert_image_equal_tofile(
|
||||
im, "Tests/images/imagedraw_rounded_rectangle_" + suffix + ".png"
|
||||
)
|
||||
|
||||
|
||||
def test_floodfill():
|
||||
red = ImageColor.getrgb("red")
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
@ -869,12 +869,12 @@ class TestImageFont:
|
|||
im = Image.new("RGB", (150, 150), "white")
|
||||
d = ImageDraw.Draw(im)
|
||||
|
||||
d.text((10, 10), "\U0001f469", embedded_color=True, font=font)
|
||||
d.text((10, 10), "\U0001f469", font=font, embedded_color=True)
|
||||
|
||||
assert_image_similar_tofile(im, "Tests/images/cbdt_notocoloremoji.png", 6.2)
|
||||
except IOError as e:
|
||||
except IOError as e: # pragma: no cover
|
||||
assert str(e) in ("unimplemented feature", "unknown file format")
|
||||
pytest.skip("freetype compiled without libpng or unsupported")
|
||||
pytest.skip("freetype compiled without libpng or CBDT support")
|
||||
|
||||
@skip_unless_feature_version("freetype2", "2.5.0")
|
||||
def test_cbdt_mask(self):
|
||||
|
@ -893,9 +893,47 @@ class TestImageFont:
|
|||
assert_image_similar_tofile(
|
||||
im, "Tests/images/cbdt_notocoloremoji_mask.png", 6.2
|
||||
)
|
||||
except IOError as e:
|
||||
except IOError as e: # pragma: no cover
|
||||
assert str(e) in ("unimplemented feature", "unknown file format")
|
||||
pytest.skip("freetype compiled without libpng or unsupported")
|
||||
pytest.skip("freetype compiled without libpng or CBDT support")
|
||||
|
||||
@skip_unless_feature_version("freetype2", "2.5.1")
|
||||
def test_sbix(self):
|
||||
try:
|
||||
font = ImageFont.truetype(
|
||||
"Tests/fonts/chromacheck-sbix.woff",
|
||||
size=300,
|
||||
layout_engine=self.LAYOUT_ENGINE,
|
||||
)
|
||||
|
||||
im = Image.new("RGB", (400, 400), "white")
|
||||
d = ImageDraw.Draw(im)
|
||||
|
||||
d.text((50, 50), "\uE901", font=font, embedded_color=True)
|
||||
|
||||
assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix.png", 1)
|
||||
except IOError as e: # pragma: no cover
|
||||
assert str(e) in ("unimplemented feature", "unknown file format")
|
||||
pytest.skip("freetype compiled without libpng or SBIX support")
|
||||
|
||||
@skip_unless_feature_version("freetype2", "2.5.1")
|
||||
def test_sbix_mask(self):
|
||||
try:
|
||||
font = ImageFont.truetype(
|
||||
"Tests/fonts/chromacheck-sbix.woff",
|
||||
size=300,
|
||||
layout_engine=self.LAYOUT_ENGINE,
|
||||
)
|
||||
|
||||
im = Image.new("RGB", (400, 400), "white")
|
||||
d = ImageDraw.Draw(im)
|
||||
|
||||
d.text((50, 50), "\uE901", (100, 0, 0), font=font)
|
||||
|
||||
assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix_mask.png", 1)
|
||||
except IOError as e: # pragma: no cover
|
||||
assert str(e) in ("unimplemented feature", "unknown file format")
|
||||
pytest.skip("freetype compiled without libpng or SBIX support")
|
||||
|
||||
@skip_unless_feature_version("freetype2", "2.10.0")
|
||||
def test_colr(self):
|
||||
|
@ -908,7 +946,7 @@ class TestImageFont:
|
|||
im = Image.new("RGB", (300, 75), "white")
|
||||
d = ImageDraw.Draw(im)
|
||||
|
||||
d.text((15, 5), "Bungee", embedded_color=True, font=font)
|
||||
d.text((15, 5), "Bungee", font=font, embedded_color=True)
|
||||
|
||||
assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 21)
|
||||
|
||||
|
@ -940,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)
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -4,11 +4,14 @@ from PIL import ImageQt
|
|||
|
||||
from .helper import hopper
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not ImageQt.qt_is_installed, reason="Qt bindings are not installed"
|
||||
)
|
||||
|
||||
if ImageQt.qt_is_installed:
|
||||
from PIL.ImageQt import qRgba
|
||||
|
||||
|
||||
@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")
|
||||
def test_rgb():
|
||||
# from https://doc.qt.io/archives/qt-4.8/qcolor.html
|
||||
# typedef QRgb
|
||||
|
@ -38,7 +41,13 @@ def test_rgb():
|
|||
checkrgb(0, 0, 255)
|
||||
|
||||
|
||||
@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")
|
||||
def test_image():
|
||||
for mode in ("1", "RGB", "RGBA", "L", "P"):
|
||||
ImageQt.ImageQt(hopper(mode))
|
||||
|
||||
|
||||
def test_closed_file():
|
||||
with pytest.warns(None) as record:
|
||||
ImageQt.ImageQt("Tests/images/hopper.gif")
|
||||
|
||||
assert not record
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -285,6 +285,20 @@ Methods
|
|||
|
||||
.. versionadded:: 5.3.0
|
||||
|
||||
.. py:method:: ImageDraw.rounded_rectangle(xy, radius=0, fill=None, outline=None, width=1)
|
||||
|
||||
Draws a rounded rectangle.
|
||||
|
||||
:param xy: Two points to define the bounding box. Sequence of either
|
||||
``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. The second point
|
||||
is just outside the drawn rectangle.
|
||||
:param radius: Radius of the corners.
|
||||
:param outline: Color to use for the outline.
|
||||
:param fill: Color to use for the fill.
|
||||
:param width: The line width, in pixels.
|
||||
|
||||
.. versionadded:: 8.2.0
|
||||
|
||||
.. py:method:: ImageDraw.shape(shape, fill=None, outline=None)
|
||||
|
||||
.. warning:: This method is experimental.
|
||||
|
@ -352,7 +366,7 @@ Methods
|
|||
|
||||
.. versionadded:: 6.2.0
|
||||
|
||||
:param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT).
|
||||
:param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
|
||||
|
||||
.. versionadded:: 8.0.0
|
||||
|
||||
|
@ -413,7 +427,7 @@ Methods
|
|||
|
||||
.. versionadded:: 6.2.0
|
||||
|
||||
:param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT).
|
||||
:param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
|
||||
|
||||
.. versionadded:: 8.0.0
|
||||
|
||||
|
@ -577,7 +591,7 @@ Methods
|
|||
correct substitutions as appropriate, if available.
|
||||
It should be a `BCP 47 language code`_.
|
||||
Requires libraqm.
|
||||
:param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT).
|
||||
:param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
|
||||
|
||||
.. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False)
|
||||
|
||||
|
@ -626,7 +640,7 @@ Methods
|
|||
It should be a `BCP 47 language code`_.
|
||||
Requires libraqm.
|
||||
:param stroke_width: The width of the text stroke.
|
||||
:param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT).
|
||||
:param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
|
||||
|
||||
.. py:method:: ImageDraw.multiline_textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False)
|
||||
|
||||
|
@ -669,7 +683,7 @@ Methods
|
|||
It should be a `BCP 47 language code`_.
|
||||
Requires libraqm.
|
||||
:param stroke_width: The width of the text stroke.
|
||||
:param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT).
|
||||
:param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
|
||||
|
||||
.. py:method:: getdraw(im=None, hints=None)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -115,8 +115,9 @@ now support fonts with embedded color data.
|
|||
To render text with embedded color data, use the parameter ``embedded_color=True``.
|
||||
|
||||
Support for CBDT fonts requires FreeType 2.5 compiled with libpng.
|
||||
Support for SBIX fonts requires FreeType 2.5.1 compiled with libpng.
|
||||
Support for COLR fonts requires FreeType 2.10.
|
||||
SBIX and SVG fonts are not yet supported.
|
||||
SVG fonts are not yet supported.
|
||||
|
||||
ImageDraw.textlength
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
|
@ -21,13 +21,35 @@ accepts negative co-ordinates, like the upper left corner of the ``box`` argumen
|
|||
:py:meth:`~PIL.Image.Image.paste` can be negative. Naturally, this has effect of
|
||||
cropping the overlaid image.
|
||||
|
||||
ImageDraw.rounded_rectangle
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Added :py:meth:`~PIL.ImageDraw.ImageDraw.rounded_rectangle`. It works the same as
|
||||
:py:meth:`~PIL.ImageDraw.ImageDraw.rectangle`, except with an additional ``radius``
|
||||
argument. ``radius`` is limited to half of the width or the height, so that users can
|
||||
create a circle, but not any other ellipse.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
im = Image.new("RGB", (200, 200))
|
||||
draw = ImageDraw.Draw(im)
|
||||
draw.rounded_rectangle(xy=(10, 20, 190, 180), radius=30, fill="red")
|
||||
|
||||
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
|
||||
========
|
||||
|
|
|
@ -257,6 +257,96 @@ class ImageDraw:
|
|||
if ink is not None and ink != fill and width != 0:
|
||||
self.draw.draw_rectangle(xy, ink, 0, width)
|
||||
|
||||
def rounded_rectangle(self, xy, radius=0, fill=None, outline=None, width=1):
|
||||
"""Draw a rounded rectangle."""
|
||||
if isinstance(xy[0], (list, tuple)):
|
||||
(x0, y0), (x1, y1) = xy
|
||||
else:
|
||||
x0, y0, x1, y1 = xy
|
||||
|
||||
d = radius * 2
|
||||
|
||||
full_x = d >= x1 - x0
|
||||
if full_x:
|
||||
# The two left and two right corners are joined
|
||||
d = x1 - x0
|
||||
full_y = d >= y1 - y0
|
||||
if full_y:
|
||||
# The two top and two bottom corners are joined
|
||||
d = y1 - y0
|
||||
if full_x and full_y:
|
||||
# If all corners are joined, that is a circle
|
||||
return self.ellipse(xy, fill, outline, width)
|
||||
|
||||
if d == 0:
|
||||
# If the corners have no curve, that is a rectangle
|
||||
return self.rectangle(xy, fill, outline, width)
|
||||
|
||||
ink, fill = self._getink(outline, fill)
|
||||
|
||||
def draw_corners(pieslice):
|
||||
if full_x:
|
||||
# Draw top and bottom halves
|
||||
parts = (
|
||||
((x0, y0, x0 + d, y0 + d), 180, 360),
|
||||
((x0, y1 - d, x0 + d, y1), 0, 180),
|
||||
)
|
||||
elif full_y:
|
||||
# Draw left and right halves
|
||||
parts = (
|
||||
((x0, y0, x0 + d, y0 + d), 90, 270),
|
||||
((x1 - d, y0, x1, y0 + d), 270, 90),
|
||||
)
|
||||
else:
|
||||
# Draw four separate corners
|
||||
parts = (
|
||||
((x1 - d, y0, x1, y0 + d), 270, 360),
|
||||
((x1 - d, y1 - d, x1, y1), 0, 90),
|
||||
((x0, y1 - d, x0 + d, y1), 90, 180),
|
||||
((x0, y0, x0 + d, y0 + d), 180, 270),
|
||||
)
|
||||
for part in parts:
|
||||
if pieslice:
|
||||
self.draw.draw_pieslice(*(part + (fill, 1)))
|
||||
else:
|
||||
self.draw.draw_arc(*(part + (ink, width)))
|
||||
|
||||
if fill is not None:
|
||||
draw_corners(True)
|
||||
|
||||
if full_x:
|
||||
self.draw.draw_rectangle(
|
||||
(x0, y0 + d / 2 + 1, x1, y1 - d / 2 - 1), fill, 1
|
||||
)
|
||||
else:
|
||||
self.draw.draw_rectangle(
|
||||
(x0 + d / 2 + 1, y0, x1 - d / 2 - 1, y1), fill, 1
|
||||
)
|
||||
if not full_x and not full_y:
|
||||
self.draw.draw_rectangle(
|
||||
(x0, y0 + d / 2 + 1, x0 + d / 2, y1 - d / 2 - 1), fill, 1
|
||||
)
|
||||
self.draw.draw_rectangle(
|
||||
(x1 - d / 2, y0 + d / 2 + 1, x1, y1 - d / 2 - 1), fill, 1
|
||||
)
|
||||
if ink is not None and ink != fill and width != 0:
|
||||
draw_corners(False)
|
||||
|
||||
if not full_x:
|
||||
self.draw.draw_rectangle(
|
||||
(x0 + d / 2 + 1, y0, x1 - d / 2 - 1, y0 + width - 1), ink, 1
|
||||
)
|
||||
self.draw.draw_rectangle(
|
||||
(x0 + d / 2 + 1, y1 - width + 1, x1 - d / 2 - 1, y1), ink, 1
|
||||
)
|
||||
if not full_y:
|
||||
self.draw.draw_rectangle(
|
||||
(x0, y0 + d / 2 + 1, x0 + width - 1, y1 - d / 2 - 1), ink, 1
|
||||
)
|
||||
self.draw.draw_rectangle(
|
||||
(x1 - width + 1, y0 + d / 2 + 1, x1, y1 - d / 2 - 1), ink, 1
|
||||
)
|
||||
|
||||
def _multiline_check(self, text):
|
||||
"""Draw text."""
|
||||
split_character = "\n" if isinstance(text, str) else b"\n"
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -128,6 +128,7 @@ def align8to32(bytes, width, mode):
|
|||
def _toqclass_helper(im):
|
||||
data = None
|
||||
colortable = None
|
||||
exclusive_fp = False
|
||||
|
||||
# handle filename, if given instead of image name
|
||||
if hasattr(im, "toUtf8"):
|
||||
|
@ -135,6 +136,7 @@ def _toqclass_helper(im):
|
|||
im = str(im.toUtf8(), "utf-8")
|
||||
if isPath(im):
|
||||
im = Image.open(im)
|
||||
exclusive_fp = True
|
||||
|
||||
qt_format = QImage.Format if qt_version == "6" else QImage
|
||||
if im.mode == "1":
|
||||
|
@ -157,10 +159,15 @@ def _toqclass_helper(im):
|
|||
data = im.tobytes("raw", "BGRA")
|
||||
format = qt_format.Format_ARGB32
|
||||
else:
|
||||
if exclusive_fp:
|
||||
im.close()
|
||||
raise ValueError(f"unsupported image mode {repr(im.mode)}")
|
||||
|
||||
__data = data or align8to32(im.tobytes(), im.size[0], im.mode)
|
||||
return {"data": __data, "im": im, "format": format, "colortable": colortable}
|
||||
size = im.size
|
||||
__data = data or align8to32(im.tobytes(), size[0], im.mode)
|
||||
if exclusive_fp:
|
||||
im.close()
|
||||
return {"data": __data, "size": size, "format": format, "colortable": colortable}
|
||||
|
||||
|
||||
if qt_is_installed:
|
||||
|
@ -182,8 +189,8 @@ if qt_is_installed:
|
|||
self.__data = im_data["data"]
|
||||
super().__init__(
|
||||
self.__data,
|
||||
im_data["im"].size[0],
|
||||
im_data["im"].size[1],
|
||||
im_data["size"][0],
|
||||
im_data["size"][1],
|
||||
im_data["format"],
|
||||
)
|
||||
if im_data["colortable"]:
|
||||
|
@ -197,8 +204,8 @@ def toqimage(im):
|
|||
def toqpixmap(im):
|
||||
# # This doesn't work. For now using a dumb approach.
|
||||
# im_data = _toqclass_helper(im)
|
||||
# result = QPixmap(im_data['im'].size[0], im_data['im'].size[1])
|
||||
# result.loadFromData(im_data['data'])
|
||||
# result = QPixmap(im_data["size"][0], im_data["size"][1])
|
||||
# result.loadFromData(im_data["data"])
|
||||
# Fix some strange bug that causes
|
||||
if im.mode == "RGB":
|
||||
im = im.convert("RGBA")
|
||||
|
|
|
@ -225,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:
|
||||
|
|
|
@ -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"]
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
260
src/map.c
|
@ -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 */
|
||||
|
||||
|
|