def _parse_file(file, filename): if not file: file = open(filename, 'rb') header = AsepriteHeader(file) if header.magic_number != '0xa5e0': raise ImageDecodeException("Does not appear to be a valid ASEprite file.") if header.color_depth not in (8, 16, 32): raise ImageDecodeException("Invalid color depth.") global PALETTE_INDEX PALETTE_INDEX = header.palette_index frames = [] for _ in range(header.num_frames): frame_size = _unpack(DWORD, file) magic_number = hex(_unpack(WORD, file)) if magic_number != '0xf1fa': raise ImageDecodeException("Malformed frame. File may be corrupted.") num_chunks = _unpack(WORD, file) duration = _unpack(WORD, file) _zero = _unpack(BYTE * 6, file) header_size = struct.calcsize(DWORD + WORD * 3 + BYTE * 6) data = file.read(frame_size - header_size) frames.append(Frame(num_chunks, duration, header, data)) # Layers chunk is in the first frame: layers = frames[0].layers pitch = len('RGBA') * header.width file.close() return header, frames, layers, pitch
def _unpack(fmt, file): """Unpack little endian bytes fram a file-like object. """ size = struct.calcsize(fmt) data = file.read(size) if len(data) < size: raise ImageDecodeException('Unexpected EOF') return struct.unpack("<" + fmt, data)[0]
def __init__(self, data): if len(data) < self.get_size(): raise ImageDecodeException('Not a DDS file') items = struct.unpack(self.get_format(), data) for field, value in itertools.zip_longest(self._fields, items, fillvalue=None): setattr(self, field[0], value)
def read_graphic_control_extension(file, stream, graphics_scope): # 23. Graphic control extension (block_size, fields, delay_time, transparent_color_index, terminator) = unpack('BBHBB', file) if block_size != 4: raise ImageDecodeException('Incorrect block size') if delay_time: # Follow Firefox/Mac behaviour: use 100ms delay for any delay # less than 10ms. if delay_time <= 1: delay_time = 10 graphics_scope.delay = float(delay_time) / 100
def pil_open(filename, autocrop=False): with _loader.file(filename) as file: try: image = PIL.Image.open(file) if autocrop: return pil_autocrop(image) image.load() return image except Exception as e: raise ImageDecodeException('PIL cannot read %r: %s' % (filename or file, e))
def _loader_pil_load(filename, autocrop=False): image = pil_open(filename, autocrop) try: image = image.transpose(PIL.Image.FLIP_TOP_BOTTOM) except Exception as e: raise ImageDecodeException('PIL failed to transpose %r: %s' % (filename or file, e)) # Convert bitmap and palette images to component if image.mode in ('1', 'P'): image = image.convert() if image.mode not in ('L', 'LA', 'RGB', 'RGBA'): raise ImageDecodeException('Unsupported mode "%s"' % image.mode) width, height = image.size # tostring is deprecated, replaced by tobytes in Pillow (PIL fork) # (1.1.7) PIL still uses it image_data_fn = getattr(image, "tobytes", getattr(image, "tostring")) return pyglet.image.ImageData(width, height, image.mode, image_data_fn())
def get_image_data(image): """ Retrieve image data from a PIL Image so it can be loaded into a Pyglet image. Returns the data wrapped in a Pyglet ImageData object. """ image = image.transpose(Image.FLIP_TOP_BOTTOM) # Convert bitmap and palette images to component if image.mode in ('1', 'P'): image = image.convert() if image.mode not in ('L', 'LA', 'RGB', 'RGBA'): raise ImageDecodeException('Unsupported mode "%s"' % image.mode) width, height = image.size return ImageData(width, height, image.mode, image.tostring())
def read(file): """Read a GIF file stream. :rtype: GIFStream """ # 17. Header signature = file.read(3) version = file.read(3) if signature != b'GIF': raise ImageDecodeException('Not a GIF stream') stream = GIFStream() # 18. Logical screen descriptor (logical_screen_width, logical_screen_height, fields, background_color_index, pixel_aspect_ratio) = unpack('HHBBB', file) global_color_table_flag = fields & 0x80 global_color_table_size = fields & 0x7 # 19. Global color table if global_color_table_flag: global_color_table = file.read(6 << global_color_table_size) # <Data>* graphics_scope = GraphicsScope() block_type = read_byte(file) while block_type != LABEL_TRAILER: if block_type == LABEL_IMAGE_DESCRIPTOR: read_table_based_image(file, stream, graphics_scope) graphics_scope = GraphicsScope() elif block_type == LABEL_EXTENSION_INTRODUCER: extension_block_type = read_byte(file) if extension_block_type == LABEL_GRAPHIC_CONTROL_EXTENSION: read_graphic_control_extension(file, stream, graphics_scope) else: skip_data_sub_blocks(file) else: # Skip bytes until a valid start character is found print(block_type) pass block_type = read_byte(file) return stream
def _blend_pixels(bottom, top, mode): # Iterate over the arrays in chunks of 4 (RGBA): bottom_iter = _chunked_iter(bottom, 4) top_iter = _chunked_iter(top, 4) if mode == 'Normal': final_array = [] # If RGB values are > 0, use the top pixel. for bottom_pixel, top_pixel in zip(bottom_iter, top_iter): if sum(top_pixel[:3]) > 0: final_array.extend(top_pixel) else: final_array.extend(bottom_pixel) return bytes(final_array) # TODO: implement additional blend modes else: raise ImageDecodeException('Unsupported blend mode.')
def read_byte(file): data = file.read(1) if not len(data): raise ImageDecodeException('Unexpected EOF') return ord(data)
def unpack(format, file): size = struct.calcsize(format) data = file.read(size) if len(data) < size: raise ImageDecodeException('Unexpected EOF') return struct.unpack(format, data)
def decode(self, file, filename): if not file: file = open(filename, 'rb') bytes = file.read() buffer = ctypes.c_buffer(bytes) if bytes[:2] != 'BM': raise ImageDecodeException( 'Not a Windows bitmap file: %r' % (filename or file)) file_header = to_ctypes(buffer, 0, BITMAPFILEHEADER) bits_offset = file_header.bfOffBits info_header_offset = ctypes.sizeof(BITMAPFILEHEADER) info_header = to_ctypes(buffer, info_header_offset, BITMAPINFOHEADER) palette_offset = info_header_offset + info_header.biSize if info_header.biSize < ctypes.sizeof(BITMAPINFOHEADER): raise ImageDecodeException( 'Unsupported BMP type: %r' % (filename or file)) width = info_header.biWidth height = info_header.biHeight if width <= 0 or info_header.biPlanes != 1: raise ImageDecodeException( 'BMP file has corrupt parameters: %r' % (filename or file)) pitch_sign = height < 0 and -1 or 1 height = abs(height) compression = info_header.biCompression if compression not in (BI_RGB, BI_BITFIELDS): raise ImageDecodeException( 'Unsupported compression: %r' % (filename or file)) clr_used = 0 bitcount = info_header.biBitCount if bitcount == 1: pitch = (width + 7) // 8 bits_type = ctypes.c_ubyte decoder = decode_1bit elif bitcount == 4: pitch = (width + 1) // 2 bits_type = ctypes.c_ubyte decoder = decode_4bit elif bitcount == 8: bits_type = ctypes.c_ubyte pitch = width decoder = decode_8bit elif bitcount == 16: pitch = width * 2 bits_type = ctypes.c_uint16 decoder = decode_bitfields elif bitcount == 24: pitch = width * 3 bits_type = ctypes.c_ubyte decoder = decode_24bit elif bitcount == 32: pitch = width * 4 if compression == BI_RGB: decoder = decode_32bit_rgb bits_type = ctypes.c_ubyte elif compression == BI_BITFIELDS: decoder = decode_bitfields bits_type = ctypes.c_uint32 else: raise ImageDecodeException( 'Unsupported compression: %r' % (filename or file)) else: raise ImageDecodeException( 'Unsupported bit count %d: %r' % (bitcount, filename or file)) pitch = (pitch + 3) & ~3 packed_width = pitch // ctypes.sizeof(bits_type) if bitcount < 16 and compression == BI_RGB: clr_used = info_header.biClrUsed or (1 << bitcount) palette = to_ctypes(buffer, palette_offset, RGBQUAD * clr_used) bits = to_ctypes(buffer, bits_offset, bits_type * packed_width * height) return decoder(bits, palette, width, height, pitch, pitch_sign) elif bitcount >= 16 and compression == BI_RGB: bits = to_ctypes(buffer, bits_offset, bits_type * (packed_width * height)) return decoder(bits, None, width, height, pitch, pitch_sign) elif compression == BI_BITFIELDS: if info_header.biSize >= ctypes.sizeof(BITMAPV4HEADER): info_header = to_ctypes(buffer, info_header_offset, BITMAPV4HEADER) r_mask = info_header.bV4RedMask g_mask = info_header.bV4GreenMask b_mask = info_header.bV4BlueMask else: fields_offset = info_header_offset + \ ctypes.sizeof(BITMAPINFOHEADER) fields = to_ctypes(buffer, fields_offset, RGBFields) r_mask = fields.red g_mask = fields.green b_mask = fields.blue class _BitsArray(ctypes.LittleEndianStructure): _pack_ = 1 _fields_ = [ ('data', bits_type * packed_width * height), ] bits = to_ctypes(buffer, bits_offset, _BitsArray).data return decoder(bits, r_mask, g_mask, b_mask, width, height, pitch, pitch_sign)
def to_ctypes(buffer, offset, type): if offset + ctypes.sizeof(type) > len(buffer): raise ImageDecodeException('BMP file is truncated') ptr = ptr_add(ctypes.pointer(buffer), offset) return ctypes.cast(ptr, ctypes.POINTER(type)).contents
def decode(self, file, filename): header = file.read(DDSURFACEDESC2.get_size()) desc = DDSURFACEDESC2(header) if desc.dwMagic != b'DDS ' or desc.dwSize != 124: raise ImageDecodeException('Invalid DDS file (incorrect header).') width = desc.dwWidth height = desc.dwHeight mipmaps = 1 if desc.dwFlags & DDSD_DEPTH: raise ImageDecodeException('Volume DDS files unsupported') if desc.dwFlags & DDSD_MIPMAPCOUNT: mipmaps = desc.dwMipMapCount if desc.ddpfPixelFormat.dwSize != 32: raise ImageDecodeException( 'Invalid DDS file (incorrect pixel format).') if desc.dwCaps2 & DDSCAPS2_CUBEMAP: raise ImageDecodeException('Cubemap DDS files unsupported') if not desc.ddpfPixelFormat.dwFlags & DDPF_FOURCC: raise ImageDecodeException( 'Uncompressed DDS textures not supported.') has_alpha = desc.ddpfPixelFormat.dwRGBAlphaBitMask != 0 selector = (desc.ddpfPixelFormat.dwFourCC, has_alpha) if selector not in _compression_formats: raise ImageDecodeException('Unsupported texture compression %s' % desc.ddpfPixelFormat.dwFourCC) dformat, decoder = _compression_formats[selector] if dformat == GL_COMPRESSED_RGB_S3TC_DXT1_EXT: block_size = 8 else: block_size = 16 datas = [] w, h = width, height for i in range(mipmaps): if not w and not h: break if not w: w = 1 if not h: h = 1 size = ((w + 3) // 4) * ((h + 3) // 4) * block_size data = file.read(size) datas.append(data) w >>= 1 h >>= 1 image = CompressedImageData(width, height, dformat, datas[0], 'GL_EXT_texture_compression_s3tc', decoder) level = 0 for data in datas[1:]: level += 1 image.set_mipmap_data(level, data) return image