diff --git a/README.md b/README.md index 6982676f5..6ca870166 100644 --- a/README.md +++ b/README.md @@ -65,10 +65,10 @@ As of 2019, Pillow development is Tidelift - Newest PyPI version - Number of PyPI downloads `_ and by direct URL access -eg. https://pypi.org/project/Pillow/1.0/. +`_ and by direct URL access +eg. https://pypi.org/project/pillow/1.0/. diff --git a/docs/releasenotes/10.3.0.rst b/docs/releasenotes/10.3.0.rst new file mode 100644 index 000000000..34afbe4b8 --- /dev/null +++ b/docs/releasenotes/10.3.0.rst @@ -0,0 +1,49 @@ +10.3.0 +------ + +Backwards Incompatible Changes +============================== + +TODO +^^^^ + +Deprecations +============ + +TODO +^^^^ + +TODO + +API Changes +=========== + +TODO +^^^^ + +TODO + +API Additions +============= + +TODO +^^^^ + +TODO + +Security +======== + +TODO +^^^^ + +TODO + +Other Changes +============= + +Portable FloatMap (PFM) images +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Support has been added for reading and writing grayscale (Pf format) +Portable FloatMap (PFM) files containing ``F`` data. diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index d8034853c..e86f8082b 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -14,6 +14,7 @@ expected to be backported to earlier versions. .. toctree:: :maxdepth: 2 + 10.3.0 10.2.0 10.1.0 10.0.1 diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index 25dbfa5b0..d43e21e14 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -15,6 +15,8 @@ # from __future__ import annotations +import math + from . import Image, ImageFile from ._binary import i16be as i16 from ._binary import o8 @@ -35,6 +37,7 @@ MODES = { b"P6": "RGB", # extensions b"P0CMYK": "CMYK", + b"Pf": "F", # PIL extensions (for test purposes only) b"PyP": "P", b"PyRGBA": "RGBA", @@ -43,7 +46,7 @@ MODES = { 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"): decoder_name = "ppm_plain" 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()) if ix == 0: # token is the x size xsize = token @@ -136,7 +147,8 @@ class PpmImageFile(ImageFile.ImageFile): elif maxval != 255: 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.tile = [(decoder_name, (0, 0, xsize, ysize), self.fp.tell(), args)] @@ -307,6 +319,7 @@ class PpmDecoder(ImageFile.PyDecoder): def _save(im, fp, filename): + row_order = 1 if im.mode == "1": rawmode, head = "1;I", b"P4" elif im.mode == "L": @@ -315,6 +328,9 @@ def _save(im, fp, filename): rawmode, head = "I;16B", b"P5" elif im.mode in ("RGB", "RGBA"): rawmode, head = "RGB", b"P6" + elif im.mode == "F": + rawmode, head = "F;32F", b"Pf" + row_order = -1 else: msg = f"cannot write mode {im.mode} as PPM" raise OSError(msg) @@ -326,7 +342,9 @@ def _save(im, fp, filename): fp.write(b"255\n") else: 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_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")