mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-31 16:07:30 +03:00 
			
		
		
		
	Add support for other common pixel formats
This commit is contained in:
		
							parent
							
								
									c6c0749a16
								
							
						
					
					
						commit
						7285b74a61
					
				|  | @ -12,7 +12,7 @@ Full text of the CC0 license: | |||
| 
 | ||||
| import struct | ||||
| from enum import IntEnum, IntFlag | ||||
| from io import BytesIO | ||||
| from io import BytesIO, BufferedIOBase | ||||
| from math import ceil, log | ||||
| from typing import NamedTuple | ||||
| 
 | ||||
|  | @ -125,7 +125,7 @@ VTFHeader = NamedTuple( | |||
|         ("resource_count", int), | ||||
|     ], | ||||
| ) | ||||
| RGB_FORMATS = (VtfPF.RGB888,) | ||||
| RGB_FORMATS = (VtfPF.RGB888, VtfPF.BGR888, VtfPF.UV88,) | ||||
| RGBA_FORMATS = ( | ||||
|     VtfPF.DXT1, | ||||
|     VtfPF.DXT1_ONEBITALPHA, | ||||
|  | @ -139,7 +139,6 @@ L_FORMATS = ( | |||
| ) | ||||
| LA_FORMATS = ( | ||||
|     VtfPF.IA88, | ||||
|     VtfPF.UV88, | ||||
| ) | ||||
| 
 | ||||
| BLOCK_COMPRESSED = (VtfPF.DXT1, VtfPF.DXT1_ONEBITALPHA, VtfPF.DXT3, VtfPF.DXT5) | ||||
|  | @ -153,18 +152,22 @@ def _get_texture_size(pixel_format: VtfPF, width, height): | |||
|     if pixel_format in (VtfPF.DXT1, VtfPF.DXT1_ONEBITALPHA): | ||||
|         return width * height // 2 | ||||
|     elif ( | ||||
|         pixel_format | ||||
|         in ( | ||||
|             VtfPF.DXT3, | ||||
|             VtfPF.DXT5, | ||||
|         ) | ||||
|         + L_FORMATS | ||||
|             pixel_format | ||||
|             in ( | ||||
|                     VtfPF.DXT3, | ||||
|                     VtfPF.DXT5, | ||||
|             ) | ||||
|             + L_FORMATS | ||||
|     ): | ||||
|         return width * height | ||||
|     elif pixel_format == VtfPF.UV88: | ||||
|         return width * height * 2 | ||||
|     elif pixel_format in LA_FORMATS: | ||||
|         return width * height * 2 | ||||
|     elif pixel_format == VtfPF.RGB888: | ||||
|         return width * height * 3 | ||||
|     elif pixel_format == VtfPF.BGR888: | ||||
|         return width * height * 3 | ||||
|     elif pixel_format == VtfPF.RGBA8888: | ||||
|         return width * height * 4 | ||||
|     raise VTFException(f"Unsupported VTF pixel format: {pixel_format}") | ||||
|  | @ -181,9 +184,47 @@ def _get_mipmap_count(width: int, height: int): | |||
|     return mip_count | ||||
| 
 | ||||
| 
 | ||||
| def closest_power(x): | ||||
| def _write_image(fp: BufferedIOBase, im: Image.Image, pixel_format: VtfPF): | ||||
|     extents = (0, 0) + im.size | ||||
|     if pixel_format == VtfPF.DXT1: | ||||
|         encoder = 'bcn' | ||||
|         encoder_args = (1, "DXT1") | ||||
|     elif pixel_format == VtfPF.DXT3: | ||||
|         encoder = 'bcn' | ||||
|         encoder_args = (3, "DXT3") | ||||
|     elif pixel_format == VtfPF.DXT5: | ||||
|         encoder = 'bcn' | ||||
|         encoder_args = (5, "DXT5") | ||||
|     elif pixel_format == VtfPF.RGB888: | ||||
|         encoder = 'raw' | ||||
|         encoder_args = ("RGB", 0, 0) | ||||
|     elif pixel_format == VtfPF.BGR888: | ||||
|         encoder = 'raw' | ||||
|         encoder_args = ("BGR", 0, 0) | ||||
|     elif pixel_format == VtfPF.RGBA8888: | ||||
|         encoder = 'raw' | ||||
|         encoder_args = ("RGBA", 0, 0) | ||||
|     elif pixel_format == VtfPF.I8: | ||||
|         encoder = 'raw' | ||||
|         encoder_args = ("L", 0, 0) | ||||
|     elif pixel_format == VtfPF.IA88: | ||||
|         encoder = 'raw' | ||||
|         encoder_args = ("LA", 0, 0) | ||||
|     elif pixel_format == VtfPF.UV88: | ||||
|         encoder = 'raw' | ||||
|         r, g, *_ = im.split() | ||||
|         im = Image.merge('LA', (r, g)) | ||||
|         encoder_args = ("LA", 0, 0) | ||||
|     else: | ||||
|         raise VTFException(f"Unsupported pixel format: {pixel_format!r}") | ||||
| 
 | ||||
|     tile = [(encoder, extents, fp.tell(), encoder_args)] | ||||
|     ImageFile._save(im, fp, tile, _get_texture_size(pixel_format, *im.size)) | ||||
| 
 | ||||
| 
 | ||||
| def _closest_power(x): | ||||
|     possible_results = round(log(x, 2)), ceil(log(x, 2)) | ||||
|     return 2 ** min(possible_results, key=lambda z: abs(x - 2**z)) | ||||
|     return 2 ** min(possible_results, key=lambda z: abs(x - 2 ** z)) | ||||
| 
 | ||||
| 
 | ||||
| class VtfImageFile(ImageFile.ImageFile): | ||||
|  | @ -268,12 +309,23 @@ class VtfImageFile(ImageFile.ImageFile): | |||
| 
 | ||||
| def _save(im, fp, filename): | ||||
|     im: Image.Image | ||||
|     if im.mode not in ("RGB", "RGBA"): | ||||
|     if im.mode not in ("RGB", "RGBA", 'L', 'LA'): | ||||
|         raise OSError(f"cannot write mode {im.mode} as VTF") | ||||
|     encoderinfo = im.encoderinfo | ||||
|     pixel_format = VtfPF(encoderinfo.get("pixel_format", VtfPF.RGBA8888)) | ||||
|     version = encoderinfo.get("version", (7, 4)) | ||||
|     generate_mips = encoderinfo.get('generate_mips', True) | ||||
| 
 | ||||
|     flags = CompiledVtfFlags(0) | ||||
|     if pixel_format in RGBA_FORMATS: | ||||
|         im = im.convert('RGBA') | ||||
|     if pixel_format in RGB_FORMATS: | ||||
|         im = im.convert('RGB') | ||||
|     if pixel_format in L_FORMATS: | ||||
|         im = im.convert('L') | ||||
|     if pixel_format in LA_FORMATS: | ||||
|         im = im.convert('LA') | ||||
| 
 | ||||
|     if "A" in im.mode: | ||||
|         if pixel_format == VtfPF.DXT1_ONEBITALPHA: | ||||
|             flags |= CompiledVtfFlags.ONEBITALPHA | ||||
|  | @ -282,16 +334,18 @@ def _save(im, fp, filename): | |||
|         else: | ||||
|             flags |= CompiledVtfFlags.EIGHTBITALPHA | ||||
| 
 | ||||
|     im = im.resize((closest_power(im.width), closest_power(im.height))) | ||||
|     im = im.resize((_closest_power(im.width), _closest_power(im.height))) | ||||
|     width, height = im.size | ||||
| 
 | ||||
|     mipmap_count = _get_mipmap_count(width, height) | ||||
|     mipmap_count = 0 | ||||
|     if generate_mips: | ||||
|         mipmap_count = _get_mipmap_count(width, height) | ||||
| 
 | ||||
|     thumb_buffer = BytesIO() | ||||
|     thumb = im.convert("RGB") | ||||
|     thumb.thumbnail(((min(16, width)), (min(16, height)))) | ||||
|     thumb = thumb.resize((closest_power(thumb.width), closest_power(thumb.height))) | ||||
|     ImageFile._save(thumb, thumb_buffer, [("bcn", (0, 0) + thumb.size, 0, (1, "DXT1"))]) | ||||
|     thumb = thumb.resize((_closest_power(thumb.width), _closest_power(thumb.height))) | ||||
|     _write_image(thumb_buffer, thumb, VtfPF.DXT1) | ||||
| 
 | ||||
|     header = VTFHeader( | ||||
|         0, | ||||
|  | @ -338,19 +392,17 @@ def _save(im, fp, filename): | |||
|         fp.write(b"\x00" * (16 - fp.tell() % 16)) | ||||
|     fp.write(thumb_buffer.getbuffer()) | ||||
| 
 | ||||
|     if pixel_format in BLOCK_COMPRESSED: | ||||
|         min_size = 4 | ||||
|     else: | ||||
|         min_size = 1 | ||||
| 
 | ||||
|     for mip_id in range(mipmap_count - 1, 0, -1): | ||||
|         mip_width = max(4, width >> mip_id) | ||||
|         mip_height = max(4, height >> mip_id) | ||||
|         mip_width = max(min_size, width >> mip_id) | ||||
|         mip_height = max(min_size, height >> mip_id) | ||||
|         mip = im.resize((mip_width, mip_height)) | ||||
|         buffer_size = mip_width * mip_height // 2 | ||||
|         extents = (0, 0) + mip.size | ||||
|         ImageFile._save( | ||||
|             mip, fp, [("bcn", extents, fp.tell(), (1, "DXT1"))], buffer_size | ||||
|         ) | ||||
|     buffer_size = im.width * im.height // 2 | ||||
|     ImageFile._save( | ||||
|         im, fp, [("bcn", (0, 0) + im.size, fp.tell(), (1, "DXT1"))], buffer_size | ||||
|     ) | ||||
|         _write_image(fp, mip, pixel_format) | ||||
|     _write_image(fp, im, pixel_format) | ||||
| 
 | ||||
| 
 | ||||
| def _accept(prefix): | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user