def _load_bitmap(self, file, filename): data = file.read() # Create a HGLOBAL with image data hglob = kernel32.GlobalAlloc(GMEM_MOVEABLE, len(data)) ptr = kernel32.GlobalLock(hglob) memmove(ptr, data, len(data)) kernel32.GlobalUnlock(hglob) # Create IStream for the HGLOBAL self.stream = IUnknown() ole32.CreateStreamOnHGlobal(hglob, True, byref(self.stream)) # Load image from stream bitmap = c_void_p() status = gdiplus.GdipCreateBitmapFromStream(self.stream, byref(bitmap)) if status != 0: self.stream.Release() raise ImageDecodeException('GDI+ cannot load %r' % (filename or file)) return bitmap
class GDIPlusDecoder(ImageDecoder): def get_file_extensions(self): return [ '.bmp', '.gif', '.jpg', '.jpeg', '.exif', '.png', '.tif', '.tiff' ] def get_animation_file_extensions(self): # TIFF also supported as a multi-page image; but that's not really an # animation, is it? return ['.gif'] def _load_bitmap(self, file, filename): data = file.read() # Create a HGLOBAL with image data hglob = kernel32.GlobalAlloc(GMEM_MOVEABLE, len(data)) ptr = kernel32.GlobalLock(hglob) memmove(ptr, data, len(data)) kernel32.GlobalUnlock(hglob) # Create IStream for the HGLOBAL self.stream = IUnknown() ole32.CreateStreamOnHGlobal(hglob, True, byref(self.stream)) # Load image from stream bitmap = c_void_p() status = gdiplus.GdipCreateBitmapFromStream(self.stream, byref(bitmap)) if status != 0: self.stream.Release() raise ImageDecodeException('GDI+ cannot load %r' % (filename or file)) return bitmap @staticmethod def _get_image(bitmap): # Get size of image (Bitmap subclasses Image) width = REAL() height = REAL() gdiplus.GdipGetImageDimension(bitmap, byref(width), byref(height)) width = int(width.value) height = int(height.value) # Get image pixel format pf = c_int() gdiplus.GdipGetImagePixelFormat(bitmap, byref(pf)) pf = pf.value # Reverse from what's documented because of Intel little-endianness. fmt = 'BGRA' if pf == PixelFormat24bppRGB: fmt = 'BGR' elif pf == PixelFormat32bppRGB: pass elif pf == PixelFormat32bppARGB: pass elif pf in (PixelFormat16bppARGB1555, PixelFormat32bppPARGB, PixelFormat64bppARGB, PixelFormat64bppPARGB): pf = PixelFormat32bppARGB else: fmt = 'BGR' pf = PixelFormat24bppRGB # Lock pixel data in best format rect = Rect() rect.X = 0 rect.Y = 0 rect.Width = width rect.Height = height bitmap_data = BitmapData() gdiplus.GdipBitmapLockBits(bitmap, byref(rect), ImageLockModeRead, pf, byref(bitmap_data)) # Create buffer for RawImage buffer = create_string_buffer(bitmap_data.Stride * height) memmove(buffer, bitmap_data.Scan0, len(buffer)) # Unlock data gdiplus.GdipBitmapUnlockBits(bitmap, byref(bitmap_data)) return ImageData(width, height, fmt, buffer, -bitmap_data.Stride) def _delete_bitmap(self, bitmap): # Release image and stream gdiplus.GdipDisposeImage(bitmap) self.stream.Release() def decode(self, file, filename): bitmap = self._load_bitmap(file, filename) image = self._get_image(bitmap) self._delete_bitmap(bitmap) return image def decode_animation(self, file, filename): bitmap = self._load_bitmap(file, filename) dimension_count = c_uint() gdiplus.GdipImageGetFrameDimensionsCount(bitmap, byref(dimension_count)) if dimension_count.value < 1: self._delete_bitmap(bitmap) raise ImageDecodeException('Image has no frame dimensions') # XXX Make sure this dimension is time? dimensions = (c_void_p * dimension_count.value)() gdiplus.GdipImageGetFrameDimensionsList(bitmap, dimensions, dimension_count.value) frame_count = c_uint() gdiplus.GdipImageGetFrameCount(bitmap, dimensions, byref(frame_count)) prop_id = PropertyTagFrameDelay prop_size = c_uint() gdiplus.GdipGetPropertyItemSize(bitmap, prop_id, byref(prop_size)) prop_buffer = c_buffer(prop_size.value) prop_item = cast(prop_buffer, POINTER(PropertyItem)).contents gdiplus.GdipGetPropertyItem(bitmap, prop_id, prop_size.value, prop_buffer) n_delays = prop_item.length // sizeof(c_long) delays = cast(prop_item.value, POINTER(c_long * n_delays)).contents frames = [] for i in range(frame_count.value): gdiplus.GdipImageSelectActiveFrame(bitmap, dimensions, i) image = self._get_image(bitmap) delay = delays[i] if delay <= 1: delay = 10 frames.append(AnimationFrame(image, delay / 100.)) self._delete_bitmap(bitmap) return Animation(frames)