Skip to content

Commit

Permalink
Merge branch 'python-pillow:main' into parametrize
Browse files Browse the repository at this point in the history
  • Loading branch information
Yay295 committed Sep 5, 2022
2 parents 797eb39 + b6348d9 commit 69de03b
Show file tree
Hide file tree
Showing 28 changed files with 161 additions and 85 deletions.
3 changes: 1 addition & 2 deletions .ci/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma

if [[ $(uname) != CYGWIN* ]]; then
# TODO Remove condition when NumPy supports 3.11
if ! [ "$GHA_PYTHON_VERSION" == "3.11-dev" ]; then python3 -m pip install numpy ; fi
python3 -m pip install numpy

# PyQt6 doesn't support PyPy3
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/cifuzz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ on:
- "**.h"
workflow_dispatch:

permissions:
contents: read

jobs:
Fuzzing:
runs-on: ubuntu-latest
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/macos-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma

echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg
# TODO Remove condition when NumPy supports 3.11
if ! [ "$GHA_PYTHON_VERSION" == "3.11-dev" ]; then python3 -m pip install numpy ; fi
python3 -m pip install numpy

# extra test images
pushd depends && ./install_extra_test_images.sh && popd
3 changes: 3 additions & 0 deletions .github/workflows/test-cygwin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ name: Test Cygwin

on: [push, pull_request, workflow_dispatch]

permissions:
contents: read

jobs:
build:
runs-on: windows-latest
Expand Down
24 changes: 24 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,30 @@ Changelog (Pillow)
9.3.0 (unreleased)
------------------

- Copy palette when converting from P to PA #6497
[radarhere]

- Allow RGB and RGBA values for PA image putpixel #6504
[radarhere]

- Removed support for tkinter in PyPy before Python 3.6 #6551
[nulano]

- Do not use CCITTFaxDecode filter if libtiff is not available #6518
[radarhere]

- Fallback to not using mmap if buffer is not large enough #6510
[radarhere]

- Fixed writing bytes as ASCII tag #6493
[radarhere]

- Open 1 bit EPS in mode 1 #6499
[radarhere]

- Removed support for tkinter before Python 1.5.2 #6549
[radarhere]

- Allow default ImageDraw font to be set #6484
[radarhere, hugovk]

Expand Down
Binary file added Tests/images/1.eps
Binary file not shown.
Binary file added Tests/images/mmap_error.bmp
Binary file not shown.
7 changes: 7 additions & 0 deletions Tests/test_file_bmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ def test_invalid_file():
BmpImagePlugin.BmpImageFile(fp)


def test_fallback_if_mmap_errors():
# This image has been truncated,
# so that the buffer is not large enough when using mmap
with Image.open("Tests/images/mmap_error.bmp") as im:
assert_image_equal_tofile(im, "Tests/images/pal8_offset.bmp")


def test_save_to_bytes():
output = io.BytesIO()
im = hopper()
Expand Down
5 changes: 5 additions & 0 deletions Tests/test_file_eps.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ def test_bytesio_object():
assert_image_similar(img, image1_scale1_compare, 5)


def test_1_mode():
with Image.open("Tests/images/1.eps") as im:
assert im.mode == "1"


def test_image_mode_not_supported(tmp_path):
im = hopper("RGBA")
tmpfile = str(tmp_path / "temp.eps")
Expand Down
4 changes: 2 additions & 2 deletions Tests/test_file_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import pytest

from PIL import Image, PdfParser
from PIL import Image, PdfParser, features

from .helper import hopper, mark_if_feature_version

Expand Down Expand Up @@ -44,7 +44,7 @@ def test_monochrome(tmp_path):

# Act / Assert
outfile = helper_save_as_pdf(tmp_path, mode)
assert os.path.getsize(outfile) < 5000
assert os.path.getsize(outfile) < (5000 if features.check("libtiff") else 15000)


def test_greyscale(tmp_path):
Expand Down
16 changes: 16 additions & 0 deletions Tests/test_file_tiff_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,22 @@ def test_iptc(tmp_path):
im.save(out)


def test_writing_bytes_to_ascii(tmp_path):
im = hopper()
info = TiffImagePlugin.ImageFileDirectory_v2()

tag = TiffTags.TAGS_V2[271]
assert tag.type == TiffTags.ASCII

info[271] = b"test"

out = str(tmp_path / "temp.tiff")
im.save(out, tiffinfo=info)

with Image.open(out) as reloaded:
assert reloaded.tag_v2[271] == "test"


def test_undefined_zero(tmp_path):
# Check that the tag has not been changed since this test was created
tag = TiffTags.TAGS_V2[45059]
Expand Down
20 changes: 13 additions & 7 deletions Tests/test_image_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,11 +215,14 @@ def test_signedness(self, mode):
self.check(mode, 2**15 + 1)
self.check(mode, 2**16 - 1)

@pytest.mark.parametrize("mode", ("P", "PA"))
@pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255)))
def test_p_putpixel_rgb_rgba(self, color):
im = Image.new("P", (1, 1), 0)
def test_p_putpixel_rgb_rgba(self, mode, color):
im = Image.new(mode, (1, 1))
im.putpixel((0, 0), color)
assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0)

alpha = color[3] if len(color) == 4 and mode == "PA" else 255
assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha)


@pytest.mark.skipif(cffi is None, reason="No CFFI")
Expand Down Expand Up @@ -340,12 +343,15 @@ def test_reference_counting(self):
# pixels can contain garbage if image is released
assert px[i, 0] == 0

def test_p_putpixel_rgb_rgba(self):
for color in [(255, 0, 0), (255, 0, 0, 255)]:
im = Image.new("P", (1, 1), 0)
@pytest.mark.parametrize("mode", ("P", "PA"))
def test_p_putpixel_rgb_rgba(self, mode):
for color in [(255, 0, 0), (255, 0, 0, 127)]:
im = Image.new(mode, (1, 1))
access = PyAccess.new(im, False)
access.putpixel((0, 0), color)
assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0)

alpha = color[3] if len(color) == 4 and mode == "PA" else 255
assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha)


class TestImagePutPixelError(AccessTest):
Expand Down
6 changes: 6 additions & 0 deletions Tests/test_image_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@ def test_p2pa_alpha():
assert im_a.getpixel((x, y)) == alpha


def test_p2pa_palette():
with Image.open("Tests/images/tiny.png") as im:
im_pa = im.convert("PA")
assert im_pa.getpalette() == im.getpalette()


def test_matrix_illegal_conversion():
# Arrange
im = hopper("CMYK")
Expand Down
2 changes: 1 addition & 1 deletion depends/install_imagequant.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/bash
# install libimagequant

archive=libimagequant-4.0.2
archive=libimagequant-4.0.4

./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz

Expand Down
18 changes: 18 additions & 0 deletions docs/handbook/image-file-formats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,24 @@ Pillow reads and writes TGA images containing ``L``, ``LA``, ``P``,
``RGB``, and ``RGBA`` data. Pillow can read and write both uncompressed and
run-length encoded TGAs.

The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:

**compression**
If set to "tga_rle", the file will be run-length encoded.

.. versionadded:: 5.3.0

**id_section**
The identification field.

.. versionadded:: 5.3.0

**orientation**
If present and a positive number, the first pixel is for the top left corner,
rather than the bottom left corner.

.. versionadded:: 5.3.0

TIFF
^^^^

Expand Down
2 changes: 1 addition & 1 deletion docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ Many of Pillow's features require external libraries:

* **libimagequant** provides improved color quantization

* Pillow has been tested with libimagequant **2.6-4.0.2**
* Pillow has been tested with libimagequant **2.6-4.0.4**
* Libimagequant is licensed GPLv3, which is more restrictive than
the Pillow license, therefore we will not be distributing binaries
with libimagequant support enabled.
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/PixelAccess.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Access using negative indexes is also possible.
Modifies the pixel at x,y. The color is given as a single
numerical value for single band images, and a tuple for
multi-band images. In addition to this, RGB and RGBA tuples
are accepted for P images.
are accepted for P and PA images.

:param xy: The pixel coordinate, given as (x, y).
:param color: The pixel value according to its mode. e.g. tuple (r, g, b) for RGB mode)
Expand Down
13 changes: 8 additions & 5 deletions src/PIL/EpsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,11 +288,14 @@ def _open(self):
# Encoded bitmapped image.
x, y, bi, mo = s[11:].split(None, 7)[:4]

if int(bi) != 8:
break
try:
self.mode = self.mode_map[int(mo)]
except ValueError:
if int(bi) == 1:
self.mode = "1"
elif int(bi) == 8:
try:
self.mode = self.mode_map[int(mo)]
except ValueError:
break
else:
break

self._size = int(x), int(y)
Expand Down
11 changes: 8 additions & 3 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -1839,7 +1839,7 @@ def putpixel(self, xy, value):
Modifies the pixel at the given position. The color is given as
a single numerical value for single-band images, and a tuple for
multi-band images. In addition to this, RGB and RGBA tuples are
accepted for P images.
accepted for P and PA images.
Note that this method is relatively slow. For more extensive changes,
use :py:meth:`~PIL.Image.Image.paste` or the :py:mod:`~PIL.ImageDraw`
Expand All @@ -1864,12 +1864,17 @@ def putpixel(self, xy, value):
return self.pyaccess.putpixel(xy, value)

if (
self.mode == "P"
self.mode in ("P", "PA")
and isinstance(value, (list, tuple))
and len(value) in [3, 4]
):
# RGB or RGBA value for a P image
# RGB or RGBA value for a P or PA image
if self.mode == "PA":
alpha = value[3] if len(value) == 4 else 255
value = value[:3]
value = self.palette.getcolor(value, self)
if self.mode == "PA":
value = (value, alpha)
return self.im.putpixel(xy, value)

def remap_palette(self, dest_map, source_palette=None):
Expand Down
3 changes: 3 additions & 0 deletions src/PIL/ImageFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ def load(self):

with open(self.filename) as fp:
self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ)
if offset + self.size[1] * args[1] > self.map.size():
# buffer is not large enough
raise OSError
self.im = Image.core.map_buffer(
self.map, self.size, decoder_name, offset, args
)
Expand Down
16 changes: 1 addition & 15 deletions src/PIL/ImageTk.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,21 +68,7 @@ def _pyimagingtkcall(command, photo, id):
# may raise an error if it cannot attach to Tkinter
from . import _imagingtk

try:
if hasattr(tk, "interp"):
# Required for PyPy, which always has CFFI installed
from cffi import FFI

ffi = FFI()

# PyPy is using an FFI CDATA element
# (Pdb) self.tk.interp
# <cdata 'Tcl_Interp *' 0x3061b50>
_imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)), 1)
else:
_imagingtk.tkinit(tk.interpaddr(), 1)
except AttributeError:
_imagingtk.tkinit(id(tk), 0)
_imagingtk.tkinit(tk.interpaddr())
tk.call(command, photo, id)


Expand Down
33 changes: 18 additions & 15 deletions src/PIL/PdfImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import os
import time

from . import Image, ImageFile, ImageSequence, PdfParser, __version__
from . import Image, ImageFile, ImageSequence, PdfParser, __version__, features

#
# --------------------------------------------------------------------
Expand Down Expand Up @@ -130,20 +130,23 @@ def _save(im, fp, filename, save_all=False):
width, height = im.size

if im.mode == "1":
filter = "CCITTFaxDecode"
bits = 1
params = PdfParser.PdfArray(
[
PdfParser.PdfDict(
{
"K": -1,
"BlackIs1": True,
"Columns": width,
"Rows": height,
}
)
]
)
if features.check("libtiff"):
filter = "CCITTFaxDecode"
bits = 1
params = PdfParser.PdfArray(
[
PdfParser.PdfDict(
{
"K": -1,
"BlackIs1": True,
"Columns": width,
"Rows": height,
}
)
]
)
else:
filter = "DCTDecode"
colorspace = PdfParser.PdfName("DeviceGray")
procset = "ImageB" # grayscale
elif im.mode == "L":
Expand Down
11 changes: 8 additions & 3 deletions src/PIL/PyAccess.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def __init__(self, img, readonly=False):

# Keep pointer to im object to prevent dereferencing.
self._im = img.im
if self._im.mode == "P":
if self._im.mode in ("P", "PA"):
self._palette = img.palette

# Debugging is polluting test traces, only useful here
Expand Down Expand Up @@ -89,12 +89,17 @@ def __setitem__(self, xy, color):
(x, y) = self.check_xy((x, y))

if (
self._im.mode == "P"
self._im.mode in ("P", "PA")
and isinstance(color, (list, tuple))
and len(color) in [3, 4]
):
# RGB or RGBA value for a P image
# RGB or RGBA value for a P or PA image
if self._im.mode == "PA":
alpha = color[3] if len(color) == 4 else 255
color = color[:3]
color = self._palette.getcolor(color, self._img)
if self._im.mode == "PA":
color = (color, alpha)

return self.set_pixel(x, y, color)

Expand Down

0 comments on commit 69de03b

Please sign in to comment.