def magick_raise(exc, force=False): """If the given `ExceptionInfo` pointer contains an exception, convert it to a Python one and throw it. Set `force` to True to throw a generic error even if the pointer doesn't seem to indicate an exception. This is useful in cases where a function uses both exceptions and `NULL` returns, which are sadly all too common in ImageMagick. """ if exc == ffi.NULL or exc.severity == lib.UndefinedException: if force: raise MysteryError return # TODO have more exception classes # An exception's description tends to be blank; the actual message # is in `reason` if exc.reason == ffi.NULL: message = b'' else: message = ffi.string(exc.reason) severity_name = ffi.string(ffi.cast("ExceptionType", exc.severity)) # severity_name is an enum name and thus a string, not bytes message = message + b' ' + severity_name.encode('ascii') if exc.severity < lib.ErrorException: # This is a warning, so do a warning cls = magick_exception_map.get(exc.severity, GenericMagickWarning) warnings.warn(cls(message)) else: # ERROR ERROR cls = magick_exception_map.get(exc.severity, GenericMagickError) raise cls(message)
def read(cls, filename): with open(filename, "rb") as fh: image_info = blank_image_info() image_info.file = ffi.cast("FILE *", fh) with magick_try() as exc: ptr = lib.ReadImage(image_info, exc.ptr) exc.check(ptr == ffi.NULL) return cls(ptr)
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 __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: # TODO can this raise an exception /but also/ return a new value? # is that a thing i should be handling better new_frame = lib.sanpera_evaluate_filter( c_frames, steps, c_channel, exc.ptr) return Image(new_frame)
def write(self, filename, format=None): if not self._frames: raise EmptyImageError with open(filename, "wb") as fh: image_info = blank_image_info() image_info.file = ffi.cast("FILE *", fh) # Force writing to a single file image_info.adjoin = lib.MagickTrue 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._frames[0]._frame.magick[0] == b'\0': # Uhoh; no format provided and nothing given by caller raise MissingFormatError # TODO detect format from filename if explicitly asked to do so with self._link_frames(self._frames) as ptr: lib.WriteImage(image_info, ptr) magick_raise(ptr.exception)
def op_number(value): return dict( op=lib.SANPERA_OP_LOAD_NUMBER, color=ffi.NULL, number=ffi.cast("double", value), )
def __call__(self, *frames, **kwargs): channel = kwargs.get('channel', lib.DefaultChannels) # TODO force_python should go away and this should become a wrapper for # evaluate_python # We're gonna be using this a lot, so let's cast it to a C int just # once (and get the error early if it's a bogus type) c_channel = ffi.cast('ChannelType', channel) # TODO any gc concerns in this? for f in frames: assert isinstance(f, ImageFrame) # XXX how to handle frames of different sizes? gravity? scaling? first # frame as the master? hm frame = frames[0] # TODO does this create a blank image or actually duplicate the pixels?? docs say it actually copies with (0, 0) but the code just refs the same pixel cache? # TODO could use an inplace version for, e.g. the SVG-style compose operators # TODO also might want a different sized clone! with magick_try() as exc: new_stack = lib.CloneImage(frame._frame, 0, 0, lib.MagickTrue, exc.ptr) exc.check(new_stack == ffi.NULL) # TODO: set image to full-color. # TODO: work out how this works, how different colorspaces work, and waht the ImageType has to do with anything # QUESTION: this doesn't actually do anything. how does it work? does it leave indexes populated? what happens if this isn't done? # if (SetImageStorageClass(fx_image,DirectClass) == MagickFalse) { # InheritException(exception,&fx_image->exception); # fx_image=DestroyImage(fx_image); # return((Image *) NULL); # } out_view = lib.AcquireCacheView(new_stack) # TODO i need to be a list in_view = lib.AcquireCacheView(frame._frame) state = FilterState() for y in range(frame._frame.rows): with magick_try() as exc: q = lib.GetCacheViewAuthenticPixels(out_view, 0, y, frame._frame.columns, 1, exc.ptr) exc.check(q == ffi.NULL) # TODO is this useful who knows #fx_indexes=GetCacheViewAuthenticIndexQueue(fx_view); for x in range(frame._frame.columns): # TODO per-channel things # TODO for usage: see line 1453 #GetMagickPixelPacket(image,&pixel); #(void) InterpolateMagickPixelPacket(image,fx_info->view[i],image->interpolate, point.x,point.y,&pixel,exception); # Set up state object # TODO document that this is reused, or somethin state._color = BaseColor._from_pixel(q) ret = self.impl(state) #q.red = c_api.RoundToQuantum(<c_api.MagickRealType> ret.c_struct.red * c_api.QuantumRange) #q.green = c_api.RoundToQuantum(<c_api.MagickRealType> ret.c_struct.green * c_api.QuantumRange) #q.blue = c_api.RoundToQuantum(<c_api.MagickRealType> ret.c_struct.blue * c_api.QuantumRange) # TODO black, opacity? # TODO seems like this should apply to any set of channels, but # IM's -fx only understands RGB # TODO this is a little invasive, but given that this inner # loop runs for every f*****g pixel, i'd like to avoid method # calls as much as possible. even that rgb() can add up rgb = ret.rgb() lib.sanpera_pixel_from_doubles_channel(q, rgb._array, c_channel) # XXX this is actually black # if (((channel & IndexChannel) != 0) && (fx_image->colorspace == CMYKColorspace)) { # (void) FxEvaluateChannelExpression(fx_info[id],IndexChannel,x,y, &alpha,exception); # SetPixelIndex(fx_indexes+x,RoundToQuantum((MagickRealType) QuantumRange*alpha)); # } q += 1 # q++ with magick_try() as exc: lib.SyncCacheViewAuthenticPixels(in_view, exc.ptr) # TODO check exception, return value # XXX destroy in_view # XXX destroy out_view s return Image(new_stack)