mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-11-01 00:17:27 +03:00 
			
		
		
		
	Allow plugins to specify their supported modes
This commit is contained in:
		
							parent
							
								
									8fab24c8ab
								
							
						
					
					
						commit
						dac567f42b
					
				|  | @ -672,8 +672,7 @@ class TestFileJpeg: | |||
|             img.save(temp_file, convert_mode=True, fill_color="red") | ||||
| 
 | ||||
|         with Image.open(temp_file) as reloaded: | ||||
|             with Image.open("Tests/images/pil123rgba_red.jpg") as target: | ||||
|                 assert_image_similar(reloaded, target, 4) | ||||
|             assert_image_similar_tofile(reloaded, "Tests/images/pil123rgba_red.jpg", 4) | ||||
| 
 | ||||
|     def test_save_tiff_with_dpi(self, tmp_path): | ||||
|         # Arrange | ||||
|  |  | |||
|  | @ -15,8 +15,6 @@ from .helper import ( | |||
|     skip_unless_feature, | ||||
| ) | ||||
| 
 | ||||
| from io import BytesIO | ||||
| 
 | ||||
| try: | ||||
|     from PIL import _webp | ||||
| 
 | ||||
|  | @ -91,7 +89,7 @@ class TestFileWebp: | |||
|             assert_image_similar(image, target, epsilon) | ||||
| 
 | ||||
|     def test_save_convert_mode(self): | ||||
|         out = BytesIO() | ||||
|         out = io.BytesIO() | ||||
|         for mode in ["CMYK", "I", "L", "LA", "P"]: | ||||
|             img = Image.new(mode, (20, 20)) | ||||
|             img.save(out, "WEBP", convert_mode=True) | ||||
|  |  | |||
|  | @ -7,13 +7,7 @@ import warnings | |||
| 
 | ||||
| import pytest | ||||
| 
 | ||||
| from PIL import ( | ||||
|     Image, | ||||
|     ImageDraw, | ||||
|     ImagePalette, | ||||
|     TiffImagePlugin, | ||||
|     UnidentifiedImageError, | ||||
| ) | ||||
| from PIL import Image, ImageDraw, ImagePalette, TiffImagePlugin, UnidentifiedImageError | ||||
| 
 | ||||
| from .helper import ( | ||||
|     assert_image_equal, | ||||
|  | @ -138,8 +132,6 @@ class TestImage: | |||
|             im.size = (3, 4) | ||||
| 
 | ||||
|     def test_invalid_image(self): | ||||
|         import io | ||||
| 
 | ||||
|         im = io.BytesIO(b"") | ||||
|         with pytest.raises(UnidentifiedImageError): | ||||
|             with Image.open(im): | ||||
|  | @ -430,14 +422,67 @@ class TestImage: | |||
|         for ext in [".cur", ".icns", ".tif", ".tiff"]: | ||||
|             assert ext in extensions | ||||
| 
 | ||||
|     def test_no_convert_mode(self, tmp_path): | ||||
|         assert not hasattr(TiffImagePlugin, "_convert_mode") | ||||
|     def test_supported_modes(self): | ||||
|         for format in Image.MIME.keys(): | ||||
|             try: | ||||
|                 save_handler = Image.SAVE[format] | ||||
|             except KeyError: | ||||
|                 continue | ||||
|             plugin = sys.modules[save_handler.__module__] | ||||
|             if not hasattr(plugin, "_supported_modes"): | ||||
|                 continue | ||||
| 
 | ||||
|             # Check that the supported modes list is accurate | ||||
|             supported_modes = plugin._supported_modes() | ||||
|             for mode in [ | ||||
|                 "1", | ||||
|                 "L", | ||||
|                 "P", | ||||
|                 "RGB", | ||||
|                 "RGBA", | ||||
|                 "CMYK", | ||||
|                 "YCbCr", | ||||
|                 "LAB", | ||||
|                 "HSV", | ||||
|                 "I", | ||||
|                 "F", | ||||
|                 "LA", | ||||
|                 "La", | ||||
|                 "RGBX", | ||||
|                 "RGBa", | ||||
|             ]: | ||||
|                 out = io.BytesIO() | ||||
|                 im = Image.new(mode, (100, 100)) | ||||
|                 if mode in supported_modes: | ||||
|                     im.save(out, format) | ||||
|                 else: | ||||
|                     with pytest.raises(Exception): | ||||
|                         im.save(out, format) | ||||
| 
 | ||||
|     def test_no_supported_modes_method(self, tmp_path): | ||||
|         assert not hasattr(TiffImagePlugin, "_supported_modes") | ||||
| 
 | ||||
|         temp_file = str(tmp_path / "temp.tiff") | ||||
| 
 | ||||
|         im = hopper() | ||||
|         im.save(temp_file, convert_mode=True) | ||||
| 
 | ||||
|     def test_convert_mode(self): | ||||
|         for mode, modes in [["P", []], ["P", ["P"]]]:  # no modes, same mode | ||||
|             im = Image.new(mode, (100, 100)) | ||||
|             assert im._convert_mode(modes) is None | ||||
| 
 | ||||
|         for mode, modes in [ | ||||
|             ["P", ["RGB"]], | ||||
|             ["P", ["L"]],  # converting to a non-preferred mode | ||||
|             ["LA", ["P"]], | ||||
|             ["I", ["L"]], | ||||
|             ["RGB", ["L"]], | ||||
|             ["RGB", ["CMYK"]], | ||||
|         ]: | ||||
|             im = Image.new(mode, (100, 100)) | ||||
|             assert im._convert_mode(modes) is not None | ||||
| 
 | ||||
|     def test_effect_mandelbrot(self): | ||||
|         # Arrange | ||||
|         size = (512, 512) | ||||
|  |  | |||
|  | @ -1022,11 +1022,8 @@ def getdata(im, offset=(0, 0), **params): | |||
|     return fp.data | ||||
| 
 | ||||
| 
 | ||||
| def _convert_mode(im): | ||||
|     return { | ||||
|         'LA':'P', | ||||
|         'CMYK':'RGB' | ||||
|     }.get(im.mode) | ||||
| def _supported_modes(): | ||||
|     return ["RGB", "RGBA", "P", "I", "F", "LA", "L", "1"] | ||||
| 
 | ||||
| 
 | ||||
| # -------------------------------------------------------------------- | ||||
|  |  | |||
|  | @ -2277,16 +2277,18 @@ class Image: | |||
| 
 | ||||
|         if format.upper() not in SAVE: | ||||
|             init() | ||||
|         if params.pop('save_all', False): | ||||
|         if params.pop("save_all", False): | ||||
|             save_handler = SAVE_ALL[format.upper()] | ||||
|         else: | ||||
|             save_handler = SAVE[format.upper()] | ||||
| 
 | ||||
|         if params.get('convert_mode'): | ||||
|         if params.get("convert_mode"): | ||||
|             plugin = sys.modules[save_handler.__module__] | ||||
|             converted_im = self._convert_mode(plugin, params) | ||||
|             if converted_im: | ||||
|                 return converted_im.save(fp, format, **params) | ||||
|             if hasattr(plugin, "_supported_modes"): | ||||
|                 modes = plugin._supported_modes() | ||||
|                 converted_im = self._convert_mode(modes, params) | ||||
|                 if converted_im: | ||||
|                     return converted_im.save(fp, format, **params) | ||||
| 
 | ||||
|         self.encoderinfo = params | ||||
|         self.encoderconfig = () | ||||
|  | @ -2315,32 +2317,57 @@ class Image: | |||
|         if open_fp: | ||||
|             fp.close() | ||||
| 
 | ||||
|     def _convert_mode(self, plugin, params): | ||||
|         if not hasattr(plugin, '_convert_mode'): | ||||
|     def _convert_mode(self, modes, params={}): | ||||
|         if not modes or self.mode in modes: | ||||
|             return | ||||
|         new_mode = plugin._convert_mode(self) | ||||
|         if self.mode == 'LA' and new_mode == 'P': | ||||
|             alpha = self.getchannel('A') | ||||
|         if self.mode == "P": | ||||
|             preferred_modes = [] | ||||
|             if "A" in self.im.getpalettemode(): | ||||
|                 preferred_modes.append("RGBA") | ||||
|             preferred_modes.append("RGB") | ||||
|         else: | ||||
|             preferred_modes = { | ||||
|                 "CMYK": ["RGB"], | ||||
|                 "RGB": ["CMYK"], | ||||
|                 "RGBX": ["RGB"], | ||||
|                 "RGBa": ["RGBA", "RGB"], | ||||
|                 "RGBA": ["RGB"], | ||||
|                 "LA": ["RGBA", "P", "L"], | ||||
|                 "La": ["LA", "L"], | ||||
|                 "L": ["RGB"], | ||||
|                 "F": ["I"], | ||||
|                 "I": ["L", "RGB"], | ||||
|                 "1": ["L"], | ||||
|                 "YCbCr": ["RGB"], | ||||
|                 "LAB": ["RGB"], | ||||
|                 "HSV": ["RGB"], | ||||
|             }.get(self.mode, []) | ||||
|         for new_mode in preferred_modes: | ||||
|             if new_mode in modes: | ||||
|                 break | ||||
|         else: | ||||
|             new_mode = modes[0] | ||||
|         if self.mode == "LA" and new_mode == "P": | ||||
|             alpha = self.getchannel("A") | ||||
|             # Convert the image into P mode but only use 255 colors | ||||
|             # in the palette out of 256. | ||||
|             im = self.convert('L') \ | ||||
|                 .convert('P', palette=Palette.ADAPTIVE, colors=255) | ||||
|             im = self.convert("L").convert("P", palette=Palette.ADAPTIVE, colors=255) | ||||
|             # Set all pixel values below 128 to 255, and the rest to 0. | ||||
|             mask = eval(alpha, lambda px: 255 if px < 128 else 0) | ||||
|             # Paste the color of index 255 and use alpha as a mask. | ||||
|             im.paste(255, mask) | ||||
|             # The transparency index is 255. | ||||
|             im.info['transparency'] = 255 | ||||
|             im.info["transparency"] = 255 | ||||
|             return im | ||||
| 
 | ||||
|         elif self.mode == 'I': | ||||
|             im = self.point([i//256 for i in range(65536)], 'L') | ||||
|             return im.convert(new_mode) if new_mode != 'L' else im | ||||
|         elif self.mode == "I": | ||||
|             im = self.point([i // 256 for i in range(65536)], "L") | ||||
|             return im.convert(new_mode) if new_mode != "L" else im | ||||
| 
 | ||||
|         elif self.mode in ('RGBA', 'LA') and new_mode in ('RGB', 'L'): | ||||
|             fill_color = params.get('fill_color', 'white') | ||||
|         elif self.mode in ("RGBA", "LA") and new_mode in ("RGB", "L"): | ||||
|             fill_color = params.get("fill_color", "white") | ||||
|             background = new(new_mode, self.size, fill_color) | ||||
|             background.paste(self, self.getchannel('A')) | ||||
|             background.paste(self, self.getchannel("A")) | ||||
|             return background | ||||
| 
 | ||||
|         elif new_mode: | ||||
|  |  | |||
|  | @ -819,15 +819,8 @@ def jpeg_factory(fp=None, filename=None): | |||
|     return im | ||||
| 
 | ||||
| 
 | ||||
| def _convert_mode(im): | ||||
|     mode = im.mode | ||||
|     if mode == 'P': | ||||
|         return 'RGBA' if 'A' in im.im.getpalettemode() else 'RGB' | ||||
|     return { | ||||
|         'RGBA':'RGB', | ||||
|         'LA':'L', | ||||
|         'I':'L' | ||||
|     }.get(mode) | ||||
| def _supported_modes(): | ||||
|     return ["RGB", "CMYK", "YCbCr", "RGBX", "L", "1"] | ||||
| 
 | ||||
| 
 | ||||
| # --------------------------------------------------------------------- | ||||
|  |  | |||
|  | @ -1420,10 +1420,8 @@ def getchunks(im, **params): | |||
|     return fp.data | ||||
| 
 | ||||
| 
 | ||||
| def _convert_mode(im): | ||||
|     return { | ||||
|         'CMYK':'RGB' | ||||
|     }.get(im.mode) | ||||
| def _supported_modes(): | ||||
|     return ["RGB", "RGBA", "P", "I", "LA", "L", "1"] | ||||
| 
 | ||||
| 
 | ||||
| # -------------------------------------------------------------------- | ||||
|  |  | |||
|  | @ -344,17 +344,22 @@ def _save(im, fp, filename): | |||
|     fp.write(data) | ||||
| 
 | ||||
| 
 | ||||
| def _convert_mode(im): | ||||
|     mode = im.mode | ||||
|     if mode == 'P': | ||||
|         return 'RGBA' if 'A' in im.im.getpalettemode() else 'RGB' | ||||
|     return { | ||||
|         # Pillow doesn't support L modes for webp for now. | ||||
|         'L':'RGB', | ||||
|         'LA':'RGBA', | ||||
|         'I':'RGB', | ||||
|         'CMYK':'RGB' | ||||
|     }.get(mode) | ||||
| def _supported_modes(): | ||||
|     return [ | ||||
|         "RGB", | ||||
|         "RGBA", | ||||
|         "RGBa", | ||||
|         "RGBX", | ||||
|         "CMYK", | ||||
|         "YCbCr", | ||||
|         "HSV", | ||||
|         "I", | ||||
|         "F", | ||||
|         "P", | ||||
|         "LA", | ||||
|         "L", | ||||
|         "1", | ||||
|     ] | ||||
| 
 | ||||
| 
 | ||||
| Image.register_open(WebPImageFile.format, WebPImageFile, _accept) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user