astc_encoder.pil_codec
PIL codec for ASTC images.
Importing this module provides an ASTC codec for PIL for encoding and decoding ASTC images.
1"""PIL codec for ASTC images. 2 3Importing this module provides an ASTC codec for PIL for encoding and decoding ASTC images. 4""" 5 6import struct 7from typing import Any, List, Union 8 9from PIL import Image, ImageFile 10 11from astc_encoder import ( 12 ASTCConfig, 13 ASTCConfigFlags, 14 ASTCContext, 15 ASTCImage, 16 ASTCProfile, 17 ASTCSwizzle, 18 ASTCType, 19) 20 21 22class ASTCEncoder(ImageFile.PyEncoder): # noqa: D101 23 _pushes_fd: bool = True 24 context: ASTCContext 25 26 def init( # noqa: D102 27 self, 28 args: List[Any], 29 ): 30 assert len(args) in (4, 5), "Invalid number of arguments" 31 profile: int = args[0] 32 quality: float = args[1] 33 block_width: int = args[2] 34 block_height: int = args[3] 35 block_depth: int = args[4] if len(args) > 4 else 1 36 37 assert block_depth == 1, "Cannot handle 3D textures" 38 39 config = ASTCConfig( 40 ASTCProfile(profile), 41 block_width, 42 block_height, 43 block_depth, 44 quality=quality, 45 ) 46 self.context = ASTCContext(config) 47 48 def encode(self, bufsize: int) -> tuple[int, int, bytes]: # noqa: D102 49 assert self.im is not None, "No image set" # type: ignore 50 51 mode: str = self.mode 52 pack_struct: struct.Struct 53 if mode == "RGBA": 54 swizzle = ASTCSwizzle.from_str("rgba") 55 pack_struct = struct.Struct("4B") 56 elif mode == "RGB": 57 swizzle = ASTCSwizzle.from_str("rgb1") 58 pack_struct = struct.Struct("3Bx") 59 else: 60 raise ValueError(f"Unsupported mode: {mode}") 61 62 pa = self.im.pixel_access() 63 data = b"".join( 64 pack_struct.pack(*pa[x, y]) # type: ignore 65 for y in range(self.state.ysize) 66 for x in range(self.state.xsize) 67 ) 68 69 astc_img = ASTCImage( 70 ASTCType.U8, 71 self.state.xsize, 72 self.state.ysize, 73 data=data, 74 ) 75 comp = self.context.compress(astc_img, swizzle) 76 return len(comp), 1, comp 77 78 79Image.register_encoder("astc", ASTCEncoder) 80 81 82class ASTCDecoder(ImageFile.PyDecoder): # noqa: D101 83 _pull_fd: bool = True 84 context: ASTCContext 85 86 def init(self, args: List[Any]): # noqa: D102 87 assert len(args) in (3, 4), "Invalid number of arguments" 88 profile: int = ASTCProfile(args[0]) 89 block_width: int = args[1] 90 block_height: int = args[2] 91 block_depth: int = args[3] if len(args) > 3 else 1 92 assert block_depth == 1, "Cannot handle 3D textures" 93 94 config = ASTCConfig( 95 profile, 96 block_width, 97 block_height, 98 block_depth, 99 flags=ASTCConfigFlags.DECOMPRESS_ONLY, 100 ) 101 self.context = ASTCContext(config) 102 103 def decode( 104 self, buffer: Union[bytes, Image.SupportsArrayInterface] 105 ) -> tuple[int, int]: # noqa: D102 106 assert self.state.xoff == 0 and self.state.yoff == 0, "Cannot handle offsets" 107 108 config = self.context.config 109 assert self.state.xsize % config.block_x == 0, ( 110 "Invalid width, must be multiple of block width" 111 ) 112 assert self.state.ysize % config.block_y == 0, ( 113 "Invalid height, must be multiple of block height" 114 ) 115 116 block_count_x = (self.state.xsize + config.block_x - 1) // config.block_x 117 block_count_y = (self.state.ysize + config.block_y - 1) // config.block_y 118 expected_size = block_count_x * block_count_y * 16 119 120 if len(buffer) != expected_size and self.fd is not None: 121 buffer = self.fd.read(expected_size) 122 123 if len(buffer) != expected_size: 124 raise ValueError("Not enough data") 125 126 astc_img = ASTCImage( 127 ASTCType.U8, 128 self.state.xsize, 129 self.state.ysize, 130 ) 131 132 mode: str = self.mode 133 if mode == "RGBA": 134 swizzle = ASTCSwizzle.from_str("rgba") 135 elif mode == "RGB": 136 swizzle = ASTCSwizzle.from_str("rgb1") 137 else: 138 # TODO: LA, L 139 raise ValueError(f"Unsupported mode: {mode}") 140 141 self.context.decompress(buffer, astc_img, swizzle) 142 143 self.set_as_raw(astc_img.data, "RGBA") 144 return -1, 0 145 146 147Image.register_decoder("astc", ASTCDecoder)
class
ASTCEncoder(PIL.ImageFile.PyEncoder):
23class ASTCEncoder(ImageFile.PyEncoder): # noqa: D101 24 _pushes_fd: bool = True 25 context: ASTCContext 26 27 def init( # noqa: D102 28 self, 29 args: List[Any], 30 ): 31 assert len(args) in (4, 5), "Invalid number of arguments" 32 profile: int = args[0] 33 quality: float = args[1] 34 block_width: int = args[2] 35 block_height: int = args[3] 36 block_depth: int = args[4] if len(args) > 4 else 1 37 38 assert block_depth == 1, "Cannot handle 3D textures" 39 40 config = ASTCConfig( 41 ASTCProfile(profile), 42 block_width, 43 block_height, 44 block_depth, 45 quality=quality, 46 ) 47 self.context = ASTCContext(config) 48 49 def encode(self, bufsize: int) -> tuple[int, int, bytes]: # noqa: D102 50 assert self.im is not None, "No image set" # type: ignore 51 52 mode: str = self.mode 53 pack_struct: struct.Struct 54 if mode == "RGBA": 55 swizzle = ASTCSwizzle.from_str("rgba") 56 pack_struct = struct.Struct("4B") 57 elif mode == "RGB": 58 swizzle = ASTCSwizzle.from_str("rgb1") 59 pack_struct = struct.Struct("3Bx") 60 else: 61 raise ValueError(f"Unsupported mode: {mode}") 62 63 pa = self.im.pixel_access() 64 data = b"".join( 65 pack_struct.pack(*pa[x, y]) # type: ignore 66 for y in range(self.state.ysize) 67 for x in range(self.state.xsize) 68 ) 69 70 astc_img = ASTCImage( 71 ASTCType.U8, 72 self.state.xsize, 73 self.state.ysize, 74 data=data, 75 ) 76 comp = self.context.compress(astc_img, swizzle) 77 return len(comp), 1, comp
Python implementation of a format encoder. Override this class and
add the decoding logic in the encode() method.
See :ref:Writing Your Own File Codec in Python<file-codecs-py>
def
init(self, args: List[Any]):
27 def init( # noqa: D102 28 self, 29 args: List[Any], 30 ): 31 assert len(args) in (4, 5), "Invalid number of arguments" 32 profile: int = args[0] 33 quality: float = args[1] 34 block_width: int = args[2] 35 block_height: int = args[3] 36 block_depth: int = args[4] if len(args) > 4 else 1 37 38 assert block_depth == 1, "Cannot handle 3D textures" 39 40 config = ASTCConfig( 41 ASTCProfile(profile), 42 block_width, 43 block_height, 44 block_depth, 45 quality=quality, 46 ) 47 self.context = ASTCContext(config)
Override to perform codec specific initialization
Parameters
- args: Tuple of arg items from the tile entry :returns: None
def
encode(self, bufsize: int) -> tuple[int, int, bytes]:
49 def encode(self, bufsize: int) -> tuple[int, int, bytes]: # noqa: D102 50 assert self.im is not None, "No image set" # type: ignore 51 52 mode: str = self.mode 53 pack_struct: struct.Struct 54 if mode == "RGBA": 55 swizzle = ASTCSwizzle.from_str("rgba") 56 pack_struct = struct.Struct("4B") 57 elif mode == "RGB": 58 swizzle = ASTCSwizzle.from_str("rgb1") 59 pack_struct = struct.Struct("3Bx") 60 else: 61 raise ValueError(f"Unsupported mode: {mode}") 62 63 pa = self.im.pixel_access() 64 data = b"".join( 65 pack_struct.pack(*pa[x, y]) # type: ignore 66 for y in range(self.state.ysize) 67 for x in range(self.state.xsize) 68 ) 69 70 astc_img = ASTCImage( 71 ASTCType.U8, 72 self.state.xsize, 73 self.state.ysize, 74 data=data, 75 ) 76 comp = self.context.compress(astc_img, swizzle) 77 return len(comp), 1, comp
Override to perform the encoding process.
Parameters
- bufsize: Buffer size.
:returns: A tuple of
(bytes encoded, errcode, bytes). If finished with encoding return 1 for the error code. Err codes are from.ImageFile.ERRORS.
class
ASTCDecoder(PIL.ImageFile.PyDecoder):
83class ASTCDecoder(ImageFile.PyDecoder): # noqa: D101 84 _pull_fd: bool = True 85 context: ASTCContext 86 87 def init(self, args: List[Any]): # noqa: D102 88 assert len(args) in (3, 4), "Invalid number of arguments" 89 profile: int = ASTCProfile(args[0]) 90 block_width: int = args[1] 91 block_height: int = args[2] 92 block_depth: int = args[3] if len(args) > 3 else 1 93 assert block_depth == 1, "Cannot handle 3D textures" 94 95 config = ASTCConfig( 96 profile, 97 block_width, 98 block_height, 99 block_depth, 100 flags=ASTCConfigFlags.DECOMPRESS_ONLY, 101 ) 102 self.context = ASTCContext(config) 103 104 def decode( 105 self, buffer: Union[bytes, Image.SupportsArrayInterface] 106 ) -> tuple[int, int]: # noqa: D102 107 assert self.state.xoff == 0 and self.state.yoff == 0, "Cannot handle offsets" 108 109 config = self.context.config 110 assert self.state.xsize % config.block_x == 0, ( 111 "Invalid width, must be multiple of block width" 112 ) 113 assert self.state.ysize % config.block_y == 0, ( 114 "Invalid height, must be multiple of block height" 115 ) 116 117 block_count_x = (self.state.xsize + config.block_x - 1) // config.block_x 118 block_count_y = (self.state.ysize + config.block_y - 1) // config.block_y 119 expected_size = block_count_x * block_count_y * 16 120 121 if len(buffer) != expected_size and self.fd is not None: 122 buffer = self.fd.read(expected_size) 123 124 if len(buffer) != expected_size: 125 raise ValueError("Not enough data") 126 127 astc_img = ASTCImage( 128 ASTCType.U8, 129 self.state.xsize, 130 self.state.ysize, 131 ) 132 133 mode: str = self.mode 134 if mode == "RGBA": 135 swizzle = ASTCSwizzle.from_str("rgba") 136 elif mode == "RGB": 137 swizzle = ASTCSwizzle.from_str("rgb1") 138 else: 139 # TODO: LA, L 140 raise ValueError(f"Unsupported mode: {mode}") 141 142 self.context.decompress(buffer, astc_img, swizzle) 143 144 self.set_as_raw(astc_img.data, "RGBA") 145 return -1, 0
Python implementation of a format decoder. Override this class and
add the decoding logic in the decode() method.
See :ref:Writing Your Own File Codec in Python<file-codecs-py>
def
init(self, args: List[Any]):
87 def init(self, args: List[Any]): # noqa: D102 88 assert len(args) in (3, 4), "Invalid number of arguments" 89 profile: int = ASTCProfile(args[0]) 90 block_width: int = args[1] 91 block_height: int = args[2] 92 block_depth: int = args[3] if len(args) > 3 else 1 93 assert block_depth == 1, "Cannot handle 3D textures" 94 95 config = ASTCConfig( 96 profile, 97 block_width, 98 block_height, 99 block_depth, 100 flags=ASTCConfigFlags.DECOMPRESS_ONLY, 101 ) 102 self.context = ASTCContext(config)
Override to perform codec specific initialization
Parameters
- args: Tuple of arg items from the tile entry :returns: None
def
decode( self, buffer: Union[bytes, PIL.Image.SupportsArrayInterface]) -> tuple[int, int]:
104 def decode( 105 self, buffer: Union[bytes, Image.SupportsArrayInterface] 106 ) -> tuple[int, int]: # noqa: D102 107 assert self.state.xoff == 0 and self.state.yoff == 0, "Cannot handle offsets" 108 109 config = self.context.config 110 assert self.state.xsize % config.block_x == 0, ( 111 "Invalid width, must be multiple of block width" 112 ) 113 assert self.state.ysize % config.block_y == 0, ( 114 "Invalid height, must be multiple of block height" 115 ) 116 117 block_count_x = (self.state.xsize + config.block_x - 1) // config.block_x 118 block_count_y = (self.state.ysize + config.block_y - 1) // config.block_y 119 expected_size = block_count_x * block_count_y * 16 120 121 if len(buffer) != expected_size and self.fd is not None: 122 buffer = self.fd.read(expected_size) 123 124 if len(buffer) != expected_size: 125 raise ValueError("Not enough data") 126 127 astc_img = ASTCImage( 128 ASTCType.U8, 129 self.state.xsize, 130 self.state.ysize, 131 ) 132 133 mode: str = self.mode 134 if mode == "RGBA": 135 swizzle = ASTCSwizzle.from_str("rgba") 136 elif mode == "RGB": 137 swizzle = ASTCSwizzle.from_str("rgb1") 138 else: 139 # TODO: LA, L 140 raise ValueError(f"Unsupported mode: {mode}") 141 142 self.context.decompress(buffer, astc_img, swizzle) 143 144 self.set_as_raw(astc_img.data, "RGBA") 145 return -1, 0
Override to perform the decoding process.
Parameters
- buffer: A bytes object with the data to be decoded.
:returns: A tuple of
(bytes consumed, errcode). If finished with decoding return -1 for the bytes consumed. Err codes are from.ImageFile.ERRORS.