mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-31 07:57:27 +03:00 
			
		
		
		
	Merge pull request #7696 from nulano/pfm
This commit is contained in:
		
						commit
						b1f549f60c
					
				
							
								
								
									
										
											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 .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 | ||||
| TEST_FILE = "Tests/images/hopper.ppm" | ||||
|  | @ -84,20 +89,58 @@ def test_16bit_pgm(): | |||
| 
 | ||||
| def test_16bit_pgm_write(tmp_path): | ||||
|     with Image.open("Tests/images/16_bit_binary.pgm") as im: | ||||
|         f = str(tmp_path / "temp.pgm") | ||||
|         im.save(f, "PPM") | ||||
|         filename = str(tmp_path / "temp.pgm") | ||||
|         im.save(filename, "PPM") | ||||
| 
 | ||||
|         assert_image_equal_tofile(im, f) | ||||
|         assert_image_equal_tofile(im, filename) | ||||
| 
 | ||||
| 
 | ||||
| def test_pnm(tmp_path): | ||||
|     with Image.open("Tests/images/hopper.pnm") as im: | ||||
|         assert_image_similar(im, hopper(), 0.0001) | ||||
| 
 | ||||
|         f = str(tmp_path / "temp.pnm") | ||||
|         im.save(f) | ||||
|         filename = str(tmp_path / "temp.pnm") | ||||
|         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( | ||||
|  |  | |||
|  | @ -696,6 +696,25 @@ PCX | |||
| 
 | ||||
| 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 | ||||
| ^^^ | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										49
									
								
								docs/releasenotes/10.3.0.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								docs/releasenotes/10.3.0.rst
									
									
									
									
									
										Normal file
									
								
							|  | @ -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. | ||||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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") | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user