Merge branch 'main' into deprecate-cms-constants

This commit is contained in:
Andrew Murray 2024-01-09 07:15:41 +11:00 committed by GitHub
commit 208a34c079
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 103 additions and 27 deletions

View File

@ -65,10 +65,10 @@ As of 2019, Pillow development is
<a href="https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge"><img <a href="https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge"><img
alt="Tidelift" alt="Tidelift"
src="https://tidelift.com/badges/package/pypi/Pillow?style=flat"></a> src="https://tidelift.com/badges/package/pypi/Pillow?style=flat"></a>
<a href="https://pypi.org/project/Pillow/"><img <a href="https://pypi.org/project/pillow/"><img
alt="Newest PyPI version" alt="Newest PyPI version"
src="https://img.shields.io/pypi/v/pillow.svg"></a> src="https://img.shields.io/pypi/v/pillow.svg"></a>
<a href="https://pypi.org/project/Pillow/"><img <a href="https://pypi.org/project/pillow/"><img
alt="Number of PyPI downloads" alt="Number of PyPI downloads"
src="https://img.shields.io/pypi/dm/pillow.svg"></a> src="https://img.shields.io/pypi/dm/pillow.svg"></a>
<a href="https://www.bestpractices.dev/projects/6331"><img <a href="https://www.bestpractices.dev/projects/6331"><img

BIN
Tests/images/hopper.pfm Normal file

Binary file not shown.

BIN
Tests/images/hopper_be.pfm Normal file

Binary file not shown.

View File

@ -6,7 +6,12 @@ import pytest
from PIL import Image, PpmImagePlugin from PIL import Image, PpmImagePlugin
from .helper import assert_image_equal_tofile, assert_image_similar, hopper from .helper import (
assert_image_equal,
assert_image_equal_tofile,
assert_image_similar,
hopper,
)
# sample ppm stream # sample ppm stream
TEST_FILE = "Tests/images/hopper.ppm" TEST_FILE = "Tests/images/hopper.ppm"
@ -84,20 +89,58 @@ def test_16bit_pgm():
def test_16bit_pgm_write(tmp_path): def test_16bit_pgm_write(tmp_path):
with Image.open("Tests/images/16_bit_binary.pgm") as im: with Image.open("Tests/images/16_bit_binary.pgm") as im:
f = str(tmp_path / "temp.pgm") filename = str(tmp_path / "temp.pgm")
im.save(f, "PPM") im.save(filename, "PPM")
assert_image_equal_tofile(im, f) assert_image_equal_tofile(im, filename)
def test_pnm(tmp_path): def test_pnm(tmp_path):
with Image.open("Tests/images/hopper.pnm") as im: with Image.open("Tests/images/hopper.pnm") as im:
assert_image_similar(im, hopper(), 0.0001) assert_image_similar(im, hopper(), 0.0001)
f = str(tmp_path / "temp.pnm") filename = str(tmp_path / "temp.pnm")
im.save(f) im.save(filename)
assert_image_equal_tofile(im, f) assert_image_equal_tofile(im, filename)
def test_pfm(tmp_path):
with Image.open("Tests/images/hopper.pfm") as im:
assert im.info["scale"] == 1.0
assert_image_equal(im, hopper("F"))
filename = str(tmp_path / "tmp.pfm")
im.save(filename)
assert_image_equal_tofile(im, filename)
def test_pfm_big_endian(tmp_path):
with Image.open("Tests/images/hopper_be.pfm") as im:
assert im.info["scale"] == 2.5
assert_image_equal(im, hopper("F"))
filename = str(tmp_path / "tmp.pfm")
im.save(filename)
assert_image_equal_tofile(im, filename)
@pytest.mark.parametrize(
"data",
[
b"Pf 1 1 NaN \0\0\0\0",
b"Pf 1 1 inf \0\0\0\0",
b"Pf 1 1 -inf \0\0\0\0",
b"Pf 1 1 0.0 \0\0\0\0",
b"Pf 1 1 -0.0 \0\0\0\0",
],
)
def test_pfm_invalid(data):
with pytest.raises(ValueError):
with Image.open(BytesIO(data)):
pass
@pytest.mark.parametrize( @pytest.mark.parametrize(

View File

@ -13,7 +13,7 @@ The fork author's goal is to foster and support active development of PIL throug
.. _GitHub Actions: https://github.com/python-pillow/Pillow/actions .. _GitHub Actions: https://github.com/python-pillow/Pillow/actions
.. _AppVeyor: https://ci.appveyor.com/project/Python-pillow/pillow .. _AppVeyor: https://ci.appveyor.com/project/Python-pillow/pillow
.. _GitHub: https://github.com/python-pillow/Pillow .. _GitHub: https://github.com/python-pillow/Pillow
.. _Python Package Index: https://pypi.org/project/Pillow/ .. _Python Package Index: https://pypi.org/project/pillow/
License License
------- -------

View File

@ -696,6 +696,25 @@ PCX
Pillow reads and writes PCX files containing ``1``, ``L``, ``P``, or ``RGB`` data. Pillow reads and writes PCX files containing ``1``, ``L``, ``P``, or ``RGB`` data.
PFM
^^^
.. versionadded:: 10.3.0
Pillow reads and writes grayscale (Pf format) Portable FloatMap (PFM) files
containing ``F`` data.
Color (PF format) PFM files are not supported.
Opening
~~~~~~~
The :py:func:`~PIL.Image.open` function sets the following
:py:attr:`~PIL.Image.Image.info` properties:
**scale**
The absolute value of the number stored in the *Scale Factor / Endianness* line.
PNG PNG
^^^ ^^^

View File

@ -58,11 +58,11 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more <h
:alt: Fuzzing Status :alt: Fuzzing Status
.. image:: https://img.shields.io/pypi/v/pillow.svg .. image:: https://img.shields.io/pypi/v/pillow.svg
:target: https://pypi.org/project/Pillow/ :target: https://pypi.org/project/pillow/
:alt: Latest PyPI version :alt: Latest PyPI version
.. image:: https://img.shields.io/pypi/dm/pillow.svg .. image:: https://img.shields.io/pypi/dm/pillow.svg
:target: https://pypi.org/project/Pillow/ :target: https://pypi.org/project/pillow/
:alt: Number of PyPI downloads :alt: Number of PyPI downloads
.. image:: https://www.bestpractices.dev/projects/6331/badge .. image:: https://www.bestpractices.dev/projects/6331/badge

View File

@ -385,7 +385,7 @@ After navigating to the Pillow directory, run::
python3 -m pip install --upgrade pip python3 -m pip install --upgrade pip
python3 -m pip install . python3 -m pip install .
.. _compressed archive from PyPI: https://pypi.org/project/Pillow/#files .. _compressed archive from PyPI: https://pypi.org/project/pillow/#files
Build Options Build Options
""""""""""""" """""""""""""
@ -602,5 +602,5 @@ Old Versions
------------ ------------
You can download old distributions from the `release history at PyPI You can download old distributions from the `release history at PyPI
<https://pypi.org/project/Pillow/#history>`_ and by direct URL access <https://pypi.org/project/pillow/#history>`_ and by direct URL access
eg. https://pypi.org/project/Pillow/1.0/. eg. https://pypi.org/project/pillow/1.0/.

View File

@ -45,11 +45,6 @@ Deprecated Use instead
:py:data:`sys.version_info`, and ``PIL.__version__`` :py:data:`sys.version_info`, and ``PIL.__version__``
===================================================== ============================================================ ===================================================== ============================================================
TODO
^^^^
TODO
API Changes API Changes
=========== ===========
@ -77,7 +72,8 @@ TODO
Other Changes Other Changes
============= =============
TODO Portable FloatMap (PFM) images
^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TODO Support has been added for reading and writing grayscale (Pf format)
Portable FloatMap (PFM) files containing ``F`` data.

View File

@ -15,6 +15,8 @@
# #
from __future__ import annotations from __future__ import annotations
import math
from . import Image, ImageFile from . import Image, ImageFile
from ._binary import i16be as i16 from ._binary import i16be as i16
from ._binary import o8 from ._binary import o8
@ -35,6 +37,7 @@ MODES = {
b"P6": "RGB", b"P6": "RGB",
# extensions # extensions
b"P0CMYK": "CMYK", b"P0CMYK": "CMYK",
b"Pf": "F",
# PIL extensions (for test purposes only) # PIL extensions (for test purposes only)
b"PyP": "P", b"PyP": "P",
b"PyRGBA": "RGBA", b"PyRGBA": "RGBA",
@ -43,7 +46,7 @@ MODES = {
def _accept(prefix): def _accept(prefix):
return prefix[0:1] == b"P" and prefix[1] in b"0123456y" return prefix[0:1] == b"P" and prefix[1] in b"0123456fy"
## ##
@ -110,6 +113,14 @@ class PpmImageFile(ImageFile.ImageFile):
if magic_number in (b"P1", b"P2", b"P3"): if magic_number in (b"P1", b"P2", b"P3"):
decoder_name = "ppm_plain" decoder_name = "ppm_plain"
for ix in range(3): for ix in range(3):
if mode == "F" and ix == 2:
scale = float(self._read_token())
if scale == 0.0 or not math.isfinite(scale):
msg = "scale must be finite and non-zero"
raise ValueError(msg)
rawmode = "F;32F" if scale < 0 else "F;32BF"
self.info["scale"] = abs(scale)
continue
token = int(self._read_token()) token = int(self._read_token())
if ix == 0: # token is the x size if ix == 0: # token is the x size
xsize = token xsize = token
@ -136,7 +147,8 @@ class PpmImageFile(ImageFile.ImageFile):
elif maxval != 255: elif maxval != 255:
decoder_name = "ppm" decoder_name = "ppm"
args = (rawmode, 0, 1) if decoder_name == "raw" else (rawmode, maxval) row_order = -1 if mode == "F" else 1
args = (rawmode, 0, row_order) if decoder_name == "raw" else (rawmode, maxval)
self._size = xsize, ysize self._size = xsize, ysize
self.tile = [(decoder_name, (0, 0, xsize, ysize), self.fp.tell(), args)] self.tile = [(decoder_name, (0, 0, xsize, ysize), self.fp.tell(), args)]
@ -307,6 +319,7 @@ class PpmDecoder(ImageFile.PyDecoder):
def _save(im, fp, filename): def _save(im, fp, filename):
row_order = 1
if im.mode == "1": if im.mode == "1":
rawmode, head = "1;I", b"P4" rawmode, head = "1;I", b"P4"
elif im.mode == "L": elif im.mode == "L":
@ -315,6 +328,9 @@ def _save(im, fp, filename):
rawmode, head = "I;16B", b"P5" rawmode, head = "I;16B", b"P5"
elif im.mode in ("RGB", "RGBA"): elif im.mode in ("RGB", "RGBA"):
rawmode, head = "RGB", b"P6" rawmode, head = "RGB", b"P6"
elif im.mode == "F":
rawmode, head = "F;32F", b"Pf"
row_order = -1
else: else:
msg = f"cannot write mode {im.mode} as PPM" msg = f"cannot write mode {im.mode} as PPM"
raise OSError(msg) raise OSError(msg)
@ -326,7 +342,9 @@ def _save(im, fp, filename):
fp.write(b"255\n") fp.write(b"255\n")
else: else:
fp.write(b"65535\n") fp.write(b"65535\n")
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]) elif head == b"Pf":
fp.write(b"-1.0\n")
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, row_order))])
# #
@ -339,6 +357,6 @@ Image.register_save(PpmImageFile.format, _save)
Image.register_decoder("ppm", PpmDecoder) Image.register_decoder("ppm", PpmDecoder)
Image.register_decoder("ppm_plain", PpmPlainDecoder) Image.register_decoder("ppm_plain", PpmPlainDecoder)
Image.register_extensions(PpmImageFile.format, [".pbm", ".pgm", ".ppm", ".pnm"]) Image.register_extensions(PpmImageFile.format, [".pbm", ".pgm", ".ppm", ".pnm", ".pfm"])
Image.register_mime(PpmImageFile.format, "image/x-portable-anymap") Image.register_mime(PpmImageFile.format, "image/x-portable-anymap")