Skip to content

Commit

Permalink
Merge branch 'main' into hopper-lru-cache
Browse files Browse the repository at this point in the history
  • Loading branch information
radarhere authored Mar 30, 2024
2 parents d131f1c + a4e5dc2 commit 3280527
Show file tree
Hide file tree
Showing 28 changed files with 733 additions and 139 deletions.
15 changes: 15 additions & 0 deletions .github/ISSUE_TEMPLATE/ISSUE_REPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ Thank you.
* Python:
* Pillow:

```text
Please paste here the output of running:
python3 -m PIL.report
or
python3 -m PIL --report
Or the output of the following Python code:
from PIL import report
# or
from PIL import features
features.pilinfo(supported_formats=False)
```

<!--
Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/wheels-dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ ARCHIVE_SDIR=pillow-depends-main

# Package versions for fresh source builds
FREETYPE_VERSION=2.13.2
HARFBUZZ_VERSION=8.3.1
HARFBUZZ_VERSION=8.4.0
LIBPNG_VERSION=1.6.43
JPEGTURBO_VERSION=3.0.2
OPENJPEG_VERSION=2.5.2
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
paths:
- ".ci/requirements-cibw.txt"
- ".github/workflows/wheel*"
- "setup.py"
- "wheels/*"
- "winbuild/build_prepare.py"
- "winbuild/fribidi.cmake"
Expand All @@ -14,6 +15,7 @@ on:
paths:
- ".ci/requirements-cibw.txt"
- ".github/workflows/wheel*"
- "setup.py"
- "wheels/*"
- "winbuild/build_prepare.py"
- "winbuild/fribidi.cmake"
Expand Down
9 changes: 9 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ Changelog (Pillow)
10.3.0 (unreleased)
-------------------

- Determine MPO size from markers, not EXIF data #7884
[radarhere]

- Improved conversion from RGB to RGBa, LA and La #7888
[radarhere]

- Support FITS images with GZIP_1 compression #7894
[radarhere]

- Use I;16 mode for 9-bit JPEG 2000 images #7900
[scaramallion, radarhere]

Expand Down
2 changes: 0 additions & 2 deletions Tests/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,6 @@ def _cached_hopper(mode: str | None = None) -> Image.Image:
return Image.open("Tests/images/hopper.ppm")
if mode == "F":
im = _cached_hopper("L").convert(mode)
elif mode[:4] == "I;16":
im = _cached_hopper("I").convert(mode)
else:
im = _cached_hopper().convert(mode)
return im
Expand Down
Binary file added Tests/images/m13.fits
Binary file not shown.
366 changes: 366 additions & 0 deletions Tests/images/m13_gzip.fits

Large diffs are not rendered by default.

Binary file modified Tests/images/sugarshack_frame_size.mpo
Binary file not shown.
19 changes: 13 additions & 6 deletions Tests/test_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,10 @@ def test_unsupported_module() -> None:
features.version_module(module)


def test_pilinfo() -> None:
@pytest.mark.parametrize("supported_formats", (True, False))
def test_pilinfo(supported_formats) -> None:
buf = io.StringIO()
features.pilinfo(buf)
features.pilinfo(buf, supported_formats=supported_formats)
out = buf.getvalue()
lines = out.splitlines()
assert lines[0] == "-" * 68
Expand All @@ -129,9 +130,15 @@ def test_pilinfo() -> None:
while lines[0].startswith(" "):
lines = lines[1:]
assert lines[0] == "-" * 68
assert lines[1].startswith("Python modules loaded from ")
assert lines[2].startswith("Binary modules loaded from ")
assert lines[3] == "-" * 68
assert lines[1].startswith("Python executable is")
lines = lines[2:]
if lines[0].startswith("Environment Python files loaded from"):
lines = lines[1:]
assert lines[0].startswith("System Python files loaded from")
assert lines[1] == "-" * 68
assert lines[2].startswith("Python Pillow modules loaded from ")
assert lines[3].startswith("Binary Pillow modules loaded from ")
assert lines[4] == "-" * 68
jpeg = (
"\n"
+ "-" * 68
Expand All @@ -142,4 +149,4 @@ def test_pilinfo() -> None:
+ "-" * 68
+ "\n"
)
assert jpeg in out
assert supported_formats == (jpeg in out)
7 changes: 6 additions & 1 deletion Tests/test_file_fits.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from PIL import FitsImagePlugin, Image

from .helper import assert_image_equal, hopper
from .helper import assert_image_equal, assert_image_equal_tofile, hopper

TEST_FILE = "Tests/images/hopper.fits"

Expand All @@ -22,6 +22,11 @@ def test_open() -> None:
assert_image_equal(im, hopper("L"))


def test_gzip1() -> None:
with Image.open("Tests/images/m13_gzip.fits") as im:
assert_image_equal_tofile(im, "Tests/images/m13.fits")


def test_invalid_file() -> None:
# Arrange
invalid_file = "Tests/images/flower.jpg"
Expand Down
2 changes: 1 addition & 1 deletion Tests/test_file_mpo.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def test_exif(test_file: str) -> None:

def test_frame_size() -> None:
# This image has been hexedited to contain a different size
# in the EXIF data of the second frame
# in the SOF marker of the second frame
with Image.open("Tests/images/sugarshack_frame_size.mpo") as im:
assert im.size == (640, 480)

Expand Down
87 changes: 59 additions & 28 deletions Tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,36 +33,38 @@
skip_unless_feature,
)

# name, pixel size
image_modes = (
("1", 1),
("L", 1),
("LA", 4),
("La", 4),
("P", 1),
("PA", 4),
("F", 4),
("I", 4),
("I;16", 2),
("I;16L", 2),
("I;16B", 2),
("I;16N", 2),
("RGB", 4),
("RGBA", 4),
("RGBa", 4),
("RGBX", 4),
("BGR;15", 2),
("BGR;16", 2),
("BGR;24", 3),
("CMYK", 4),
("YCbCr", 4),
("HSV", 4),
("LAB", 4),
)

image_mode_names = [name for name, _ in image_modes]


class TestImage:
@pytest.mark.parametrize(
"mode",
(
"1",
"P",
"PA",
"L",
"LA",
"La",
"F",
"I",
"I;16",
"I;16L",
"I;16B",
"I;16N",
"RGB",
"RGBX",
"RGBA",
"RGBa",
"BGR;15",
"BGR;16",
"BGR;24",
"CMYK",
"YCbCr",
"LAB",
"HSV",
),
)
@pytest.mark.parametrize("mode", image_mode_names)
def test_image_modes_success(self, mode: str) -> None:
Image.new(mode, (1, 1))

Expand Down Expand Up @@ -1042,6 +1044,35 @@ def test_close_graceful(self, caplog: pytest.LogCaptureFixture) -> None:
assert im.fp is None


class TestImageBytes:
@pytest.mark.parametrize("mode", image_mode_names)
def test_roundtrip_bytes_constructor(self, mode: str) -> None:
im = hopper(mode)
source_bytes = im.tobytes()

reloaded = Image.frombytes(mode, im.size, source_bytes)
assert reloaded.tobytes() == source_bytes

@pytest.mark.parametrize("mode", image_mode_names)
def test_roundtrip_bytes_method(self, mode: str) -> None:
im = hopper(mode)
source_bytes = im.tobytes()

reloaded = Image.new(mode, im.size)
reloaded.frombytes(source_bytes)
assert reloaded.tobytes() == source_bytes

@pytest.mark.parametrize(("mode", "pixelsize"), image_modes)
def test_getdata_putdata(self, mode: str, pixelsize: int) -> None:
im = Image.new(mode, (2, 2))
source_bytes = bytes(range(im.width * im.height * pixelsize))
im.frombytes(source_bytes)

reloaded = Image.new(mode, im.size)
reloaded.putdata(im.getdata())
assert_image_equal(im, reloaded)


class MockEncoder(ImageFile.PyEncoder):
pass

Expand Down
12 changes: 12 additions & 0 deletions Tests/test_image_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,14 @@ def test_trns_RGB(tmp_path: Path) -> None:
assert im_l.info["transparency"] == im_l.getpixel((0, 0)) # undone
im_l.save(f)

im_la = im.convert("LA")
assert "transparency" not in im_la.info
im_la.save(f)

im_la = im.convert("La")
assert "transparency" not in im_la.info
assert im_la.getpixel((0, 0)) == (0, 0)

im_p = im.convert("P")
assert "transparency" in im_p.info
im_p.save(f)
Expand All @@ -191,6 +199,10 @@ def test_trns_RGB(tmp_path: Path) -> None:
assert "transparency" not in im_rgba.info
im_rgba.save(f)

im_rgba = im.convert("RGBa")
assert "transparency" not in im_rgba.info
assert im_rgba.getpixel((0, 0)) == (0, 0, 0, 0)

im_p = pytest.warns(UserWarning, im.convert, "P", palette=Image.Palette.ADAPTIVE)
assert "transparency" not in im_p.info
im_p.save(f)
Expand Down
25 changes: 19 additions & 6 deletions Tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@
import subprocess
import sys

import pytest

def test_main() -> None:
out = subprocess.check_output([sys.executable, "-m", "PIL"]).decode("utf-8")

@pytest.mark.parametrize(
"args, report",
((["PIL"], False), (["PIL", "--report"], True), (["PIL.report"], True)),
)
def test_main(args, report) -> None:
args = [sys.executable, "-m"] + args
out = subprocess.check_output(args).decode("utf-8")
lines = out.splitlines()
assert lines[0] == "-" * 68
assert lines[1].startswith("Pillow ")
Expand All @@ -15,9 +22,15 @@ def test_main() -> None:
while lines[0].startswith(" "):
lines = lines[1:]
assert lines[0] == "-" * 68
assert lines[1].startswith("Python modules loaded from ")
assert lines[2].startswith("Binary modules loaded from ")
assert lines[3] == "-" * 68
assert lines[1].startswith("Python executable is")
lines = lines[2:]
if lines[0].startswith("Environment Python files loaded from"):
lines = lines[1:]
assert lines[0].startswith("System Python files loaded from")
assert lines[1] == "-" * 68
assert lines[2].startswith("Python Pillow modules loaded from ")
assert lines[3].startswith("Binary Pillow modules loaded from ")
assert lines[4] == "-" * 68
jpeg = (
os.linesep
+ "-" * 68
Expand All @@ -31,4 +44,4 @@ def test_main() -> None:
+ "-" * 68
+ os.linesep
)
assert jpeg in out
assert report == (jpeg not in out)
51 changes: 16 additions & 35 deletions _custom_build/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,12 @@
class _CustomBuildMetaBackend(backend_class):
def run_setup(self, setup_script="setup.py"):
if self.config_settings:
for key, values in self.config_settings.items():
if not isinstance(values, list):
values = [values]
for value in values:
sys.argv.append(f"--pillow-configuration={key}={value}")

def config_has(key, value):
settings = self.config_settings.get(key)
if settings:
if not isinstance(settings, list):
settings = [settings]
return value in settings

flags = []
for dependency in (
"zlib",
"jpeg",
"tiff",
"freetype",
"raqm",
"lcms",
"webp",
"webpmux",
"jpeg2000",
"imagequant",
"xcb",
):
if config_has(dependency, "enable"):
flags.append("--enable-" + dependency)
elif config_has(dependency, "disable"):
flags.append("--disable-" + dependency)
for dependency in ("raqm", "fribidi"):
if config_has(dependency, "vendor"):
flags.append("--vendor-" + dependency)
if self.config_settings.get("platform-guessing") == "disable":
flags.append("--disable-platform-guessing")
if self.config_settings.get("debug") == "true":
flags.append("--debug")
if flags:
sys.argv = sys.argv[:1] + ["build_ext"] + flags + sys.argv[1:]
return super().run_setup(setup_script)

def build_wheel(
Expand All @@ -54,5 +25,15 @@ def build_wheel(
self.config_settings = config_settings
return super().build_wheel(wheel_directory, config_settings, metadata_directory)

def build_editable(
self, wheel_directory, config_settings=None, metadata_directory=None
):
self.config_settings = config_settings
return super().build_editable(
wheel_directory, config_settings, metadata_directory
)


build_wheel = _CustomBuildMetaBackend().build_wheel
_backend = _CustomBuildMetaBackend()
build_wheel = _backend.build_wheel
build_editable = _backend.build_editable
3 changes: 2 additions & 1 deletion docs/handbook/image-file-formats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1339,7 +1339,8 @@ FITS

.. versionadded:: 9.1.0

Pillow identifies and reads FITS files, commonly used for astronomy.
Pillow identifies and reads FITS files, commonly used for astronomy. Uncompressed and
GZIP_1 compressed images can be read.

FLI, FLC
^^^^^^^^
Expand Down
Loading

0 comments on commit 3280527

Please sign in to comment.