mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-02-11 00:50:51 +03:00
add support for grayscale pfm image format
This commit is contained in:
parent
d329207e62
commit
0d841aab9a
BIN
Tests/images/hopper.pfm
Normal file
BIN
Tests/images/hopper.pfm
Normal file
Binary file not shown.
BIN
Tests/images/hopper_be.pfm
Normal file
BIN
Tests/images/hopper_be.pfm
Normal file
Binary file not shown.
|
@ -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"
|
||||||
|
@ -100,6 +105,44 @@ def test_pnm(tmp_path):
|
||||||
assert_image_equal_tofile(im, f)
|
assert_image_equal_tofile(im, f)
|
||||||
|
|
||||||
|
|
||||||
|
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"))
|
||||||
|
|
||||||
|
f = str(tmp_path / "tmp.pfm")
|
||||||
|
im.save(f)
|
||||||
|
|
||||||
|
assert_image_equal_tofile(im, f)
|
||||||
|
|
||||||
|
|
||||||
|
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"))
|
||||||
|
|
||||||
|
f = str(tmp_path / "tmp.pfm")
|
||||||
|
im.save(f)
|
||||||
|
|
||||||
|
assert_image_equal_tofile(im, f)
|
||||||
|
|
||||||
|
|
||||||
|
@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(
|
||||||
"plain_path, raw_path",
|
"plain_path, raw_path",
|
||||||
(
|
(
|
||||||
|
|
|
@ -696,6 +696,24 @@ 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) 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
|
||||||
^^^
|
^^^
|
||||||
|
|
||||||
|
|
|
@ -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 not math.isfinite(scale) or scale == 0.0:
|
||||||
|
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")
|
||||||
|
|
Loading…
Reference in New Issue
Block a user