def _get_formats(): formats = dict() num_formats = ffi.new("size_t *") # Snag the list of known supported image formats with magick_try() as exc: magick_infos = ffi.gc(lib.GetMagickInfoList(b"*", num_formats, exc.ptr), lib.RelinquishMagickMemory) # Sometimes this call can generate an exception (such as a module not # being loadable) but then succeed anyway and return a useful value, in # which case we want to ignore the exception if magick_infos != ffi.NULL: exc.clear() for i in range(num_formats[0]): name = ffi.string(magick_infos[i].name).decode("latin-1") formats[name.lower()] = ImageFormat( name=name, description=ffi.string(magick_infos[i].description).decode("latin-1"), can_read=magick_infos[i].decoder != ffi.NULL, can_write=magick_infos[i].encoder != ffi.NULL, supports_frames=magick_infos[i].adjoin != 0, ) return formats
def to_buffer(self, format=None): if not self._frames: raise EmptyImageError image_info = blank_image_info() length = ffi.new("size_t *") # Force writing to a single file image_info.adjoin = lib.MagickTrue # Stupid hack to fix a bug in the rgb codec if format == 'rgba': for frame in self._frames: frame._fix_for_rgba_codec() if format: # If the caller provided an explicit format, pass it along # Make sure not to overflow the char[] # TODO maybe just error out when this happens image_info.magick = format.encode('ascii')[:lib.MaxTextExtent] elif self._stack.magick[0] == b'\0': # Uhoh; no format provided and nothing given by caller raise MissingFormatError with magick_try() as exc: with self._link_frames(self._frames) as ptr: cbuf = ffi.gc( lib.ImagesToBlob(image_info, ptr, length, exc.ptr), lib.RelinquishMagickMemory) return ffi.buffer(cbuf, length[0])
def __init__(self, frame): # TODO typecheck here, maybe? self.frame = frame self.draw_info = ffi.gc( lib.AcquireDrawInfo(), lib.DestroyDrawInfo)
def _get_formats(): formats = dict() formats_by_mime = dict() num_formats = ffi.new("size_t *") # Snag the list of known supported image formats with magick_try() as exc: magick_infos = ffi.gc( lib.GetMagickInfoList(b"*", num_formats, exc.ptr), lib.RelinquishMagickMemory) for i in range(num_formats[0]): imageformat = ImageFormat( name=ffi.string(magick_infos[i].name).decode('latin-1'), description=ffi.string(magick_infos[i].description).decode('latin-1'), can_read=magick_infos[i].decoder != ffi.NULL, can_write=magick_infos[i].encoder != ffi.NULL, supports_frames=magick_infos[i].adjoin != 0, mime_type=ffi.string(magick_infos[i].mime_type).decode('ascii') if magick_infos[i].mime_type else None, ) formats[imageformat.name.lower()] = imageformat formats_by_mime[imageformat.mime_type] = imageformat return formats, formats_by_mime
def coalesced(self): """Returns an image with each frame composited over previous frames.""" with magick_try() as exc: new_image = ffi.gc(lib.CoalesceImages(self._stack, exc.ptr), lib.DestroyImageList) return type(self)(new_image)
def coalesced(self): """Returns an image with each frame composited over previous frames.""" with magick_try() as exc: new_image = ffi.gc( lib.CoalesceImages(self._stack, exc.ptr), lib.DestroyImageList) return type(self)(new_image)
def __call__(self, *frames, **kwargs): channel = kwargs.get('channel', lib.DefaultChannels) c_channel = ffi.cast('ChannelType', channel) steps = ffi.new("sanpera_evaluate_step[]", self.compiled_steps) c_frames = ffi.new("Image *[]", [f._frame for f in frames] + [ffi.NULL]) with magick_try() as exc: new_frame = ffi.gc( lib.sanpera_evaluate_filter(c_frames, steps, c_channel, exc.ptr), lib.DestroyImageList) return Image(new_frame)
def __init__(self, _raw_frame): # This class has ultimate ownership of each Image pointer, so do the gc # stuff here. _frame = self._frame = ffi.gc(_raw_frame, lib.DestroyImage) # New frames need their filenames blanked, lest ImageMagick decide # to ignore our pleas and write to the same file _frame.filename[0] = b'\0' # ...yeah this too #_frame.magick[0] = b'\0' # Sometimes a new image's "page" is 0x0, which is totally bogus if _frame.page.width == 0 or _frame.page.height == 0: _frame.page.width = _frame.columns _frame.page.height = _frame.rows
def cropped(self, rect, preserve_canvas=False): rectinfo = rect.to_rect_info() p = self._stack new_stack_ptr = ffi.new("Image **", ffi.NULL) while p: with magick_try() as exc: new_frame = lib.CropImage(p, rectinfo, exc.ptr) # Only GC the first frame in the stack, since the others will be # in the same list and thus nuked automatically if new_stack_ptr == ffi.NULL: new_frame = ffi.gc(new_frame, lib.DestroyImageList) lib.AppendImageToList(new_stack_ptr, new_frame) p = lib.GetNextImageInList(p) new = type(self)(new_stack_ptr[0]) # Repage by default after a crop; not doing this is unexpected and # frankly insane. Plain old `+repage` behavior would involve nuking # the page entirely, but that would screw up multiple frames; instead, # shift the canvas for every frame so the crop region's upper left # corner is the new origin, and fix the dimensions so every frame fits # (up to the size of the crop area, though ImageMagick should never # return an image bigger than the crop area... right?) if not preserve_canvas: # ImageMagick actually behaves when the crop area extends out # beyond the origin, so don't fix the edges in that case # TODO this is complex enough that i should perhaps just do it # myself left_delta = max(rect.left, 0) top_delta = max(rect.top, 0) # New canvas should be the size of the overlap between the current # canvas and the crop area new_canvas = rect.intersection(self.size.at(origin)) new_height = new_canvas.height new_width = new_canvas.width for frame in new: frame._frame.page.x -= left_delta frame._frame.page.y -= top_delta frame._frame.page.height = new_height frame._frame.page.width = new_width return new
def _get_formats(): formats = dict() num_formats = ffi.new("size_t *") # Snag the list of known supported image formats with magick_try() as exc: magick_infos = ffi.gc( lib.GetMagickInfoList(b"*", num_formats, exc.ptr), lib.RelinquishMagickMemory) for i in range(num_formats[0]): name = ffi.string(magick_infos[i].name).decode('latin-1') formats[name.lower()] = ImageFormat( name=name, description=ffi.string( magick_infos[i].description).decode('latin-1'), can_read=magick_infos[i].decoder != ffi.NULL, can_write=magick_infos[i].encoder != ffi.NULL, supports_frames=magick_infos[i].adjoin != 0, ) return formats
def __init__(self, frame): self._frame = frame self._ptr = ffi.gc(lib.AcquireCacheView(frame._frame), _cache_view_destructor)
def __init__(self): self.ptr = ffi.gc( lib.AcquireExceptionInfo(), lib.DestroyExceptionInfo)
def blank_image_info(): return ffi.gc( lib.CloneImageInfo(ffi.NULL), lib.DestroyImageInfo)
def blank_image_info(): return ffi.gc(lib.CloneImageInfo(ffi.NULL), lib.DestroyImageInfo)
def __init__(self): self.ptr = ffi.gc(lib.AcquireExceptionInfo(), lib.DestroyExceptionInfo)
def __init__(self, frame): self._frame = frame self._ptr = ffi.gc( lib.AcquireCacheView(frame._frame), _cache_view_destructor)
def __init__(self, frame): # TODO typecheck here, maybe? self.frame = frame self.draw_info = ffi.gc(lib.AcquireDrawInfo(), lib.DestroyDrawInfo)