def test_cropped_canvas_fixing(): img = builtins.rose.resized((200, 200)) dim = Size(50, 50).at((25, 25)) cropped = img.cropped(dim) assert cropped.size == dim.size assert cropped[0].canvas == dim.at(origin)
def test_multi_frame_resize(): """ImageMagick doesn't handle resizing correctly when a virtual canvas is involved. Make sure we fixed it. """ img = get_image('anim_bgnd.gif') # Original dimensions assert img.size == Size(100, 100) assert img[0].size == Size(32, 32) assert img[0].canvas.position == Vector(5, 10) assert img[1].size == Size(32, 32) assert img[1].canvas.position == Vector(35, 30) assert img[2].size == Size(32, 32) assert img[2].canvas.position == Vector(62, 50) assert img[3].size == Size(32, 32) assert img[3].canvas.position == Vector(10, 55) img = img.resized(img.size * 0.5) # Resized dimensions -- reduced by half assert img.size == Size(50, 50) assert img[0].size == Size(16, 16) assert img[0].canvas.position == Vector(3, 5) assert img[1].size == Size(16, 16) assert img[1].canvas.position == Vector(18, 15) assert img[2].size == Size(16, 16) assert img[2].canvas.position == Vector(31, 25) assert img[3].size == Size(16, 16) assert img[3].canvas.position == Vector(5, 28)
def test_cropped_canvas_fixing_large_crop(): size = Size(20, 20) img = builtins.rose.resized(size) dim = Size(50, 50).at((-25, -25)) cropped = img.cropped(dim) assert cropped.size == size assert cropped[0].canvas == size.at(origin)
def check_original_dimensions(img): assert img.size == Size(100, 100) assert img[0].size == Size(32, 32) assert img[0].canvas.position == Vector(5, 10) assert img[1].size == Size(32, 32) assert img[1].canvas.position == Vector(35, 30) assert img[2].size == Size(32, 32) assert img[2].canvas.position == Vector(62, 50) assert img[3].size == Size(32, 32) assert img[3].canvas.position == Vector(10, 55)
def tiled(self, size): size = Size.coerce(size) new = Image.new(size) # TODO this returns a bool? lib.TextureImage(new._stack, self._frame) magick_raise(self._frame.exception) return new
def size(self): """The image dimensions, as a `Size`. Empty images have zero size. Some image formats support a canvas offset, in which case this value may not match the actual drawable area. See `ImageFrame.canvas`. Note that multi-frame images don't have a notion of intrinsic size for the entire image, though particular formats may enforce that every frame be the same size. If the image has multiple frames, this returns the size of the first frame, which is in line with most image-handling software. """ if self._frames: _frame = self._frames[0]._frame # Note that this doesn't use .rows/.columns, as those are the size # of the pixel area. The "page" is the size of the canvas, which # is the size of the image itself. # TODO the canvas might be different between different frames! see # if this happens on load, try to preserve it with operations return Size(_frame.page.width, _frame.page.height) else: return Size(0, 0)
def new(cls, size, fill=None): """Create a new image (with one frame) of the given size.""" size = Size.coerce(size) image_info = blank_image_info() magick_pixel = blank_magick_pixel() if fill is None: # TODO need a way to explicitly create a certain color fill = RGBColor(0., 0., 0., 0.) fill._populate_magick_pixel(magick_pixel) ptr = lib.NewMagickImage(image_info, size.width, size.height, magick_pixel) magick_raise(ptr.exception) return cls(ptr)
def test_crop_miss(ctx): img = builtins.rose.cropped(Size(40, 30).at((90, 60)), preserve_canvas=True) ctx.compare(img, 'crop_miss.miff')
def test_crop_all(ctx): img = builtins.rose.cropped(Size(90, 60).at((-10, -10)), preserve_canvas=True) ctx.compare(img, 'crop_all.miff')
def test_crop(ctx): img = builtins.rose.cropped(Size(40, 30).at((10, 10)), preserve_canvas=True) ctx.compare(img, 'crop.miff')
def resized(self, size, filter=None): size = Size.coerce(size) # TODO allow picking a filter # TODO allow messing with blur? p = self._stack new_stack_ptr = ffi.new("Image **", ffi.NULL) if filter == "box": c_filter = lib.BoxFilter else: c_filter = lib.UndefinedFilter target_width = size.width target_height = size.height ratio_width = target_width / (self._stack.page.width or self._stack.columns) ratio_height = target_height / (self._stack.page.height or self._stack.rows) while p: # Alrighty, so. ResizeImage takes the given size as the new size # of the FRAME, rather than the CANVAS, which is almost certainly # not what anyone expects. So do the math to fix this manually, # converting from canvas size to frame size. frame_width = int(p.columns * ratio_width + 0.5) frame_height = int(p.rows * ratio_height + 0.5) # ImageMagick, brilliant as it is, does not scale non-linear colorspaces # correctly. This definitely affects sRGB (see Issue-23) and may affect # other colorspaces. The "helpful" solution is to preconvert to linear RGB, # and postconvert to the original value. See also: # http://www.imagemagick.org/script/color-management.php inputImage = p with magick_try() as exc: # Assume ImageMagick will do the dumbest possible thing to non-RGB spaces. # TODO: But maybe we can trust it a bit more? if p.colorspace != lib.RGBColorspace: inputImage = lib.CloneImage(p, 0, 0, 0, exc.ptr) lib.TransformImageColorspace(inputImage, lib.RGBColorspace) if c_filter == lib.BoxFilter: # Use the faster ScaleImage in this special case new_frame = lib.ScaleImage(inputImage, frame_width, frame_height, exc.ptr) else: new_frame = lib.ResizeImage(inputImage, frame_width, frame_height, c_filter, 1.0, exc.ptr) if new_frame.colorspace != p.colorspace: lib.TransformImageColorspace(new_frame, p.colorspace) if inputImage != p: lib.DestroyImage(inputImage) # TODO how do i do this correctly etc? will it ever be non-null?? # except Exception: # lib.DestroyImage(new_frame) # ImageMagick uses new_size/old_size to compute the resized frame's # position. But new_size has already been rounded, so for small # frames in a large image, the double rounding error can place the # new frame a noticable distance from where one might expect. Fix # the canvas manually, too. new_frame.page.width = target_width new_frame.page.height = target_height new_frame.page.x = int(p.page.x * ratio_width + 0.5) new_frame.page.y = int(p.page.y * ratio_height + 0.5) lib.AppendImageToList(new_stack_ptr, new_frame) p = lib.GetNextImageInList(p) return type(self)(new_stack_ptr[0])
def size(self): """Size of the frame, as a `Size`. Shortcut for `frame.canvas.size`. """ return Size(self._frame.columns, self._frame.rows)
def resized(self, size, filter=None): size = Size.coerce(size) # TODO allow picking a filter # TODO allow messing with blur? p = self._stack new_stack_ptr = ffi.new("Image **", ffi.NULL) if filter == 'box': c_filter = lib.BoxFilter else: c_filter = lib.UndefinedFilter target_width = size.width target_height = size.height ratio_width = target_width / (self._stack.page.width or self._stack.columns) ratio_height = target_height / (self._stack.page.height or self._stack.rows) while p: # Alrighty, so. ResizeImage takes the given size as the new size # of the FRAME, rather than the CANVAS, which is almost certainly # not what anyone expects. So do the math to fix this manually, # converting from canvas size to frame size. frame_width = int(p.columns * ratio_width + 0.5) frame_height = int(p.rows * ratio_height + 0.5) # ImageMagick, brilliant as it is, does not scale non-linear colorspaces # correctly. This definitely affects sRGB (see Issue-23) and may affect # other colorspaces. The "helpful" solution is to preconvert to linear RGB, # and postconvert to the original value. See also: # http://www.imagemagick.org/script/color-management.php inputImage = p with magick_try() as exc: # Assume ImageMagick will do the dumbest possible thing to non-RGB spaces. # TODO: But maybe we can trust it a bit more? if p.colorspace != lib.RGBColorspace: inputImage = lib.CloneImage(p, 0, 0, 0, exc.ptr) lib.TransformImageColorspace(inputImage, lib.RGBColorspace) if c_filter == lib.BoxFilter: # Use the faster ScaleImage in this special case new_frame = lib.ScaleImage(inputImage, frame_width, frame_height, exc.ptr) else: new_frame = lib.ResizeImage(inputImage, frame_width, frame_height, c_filter, 1.0, exc.ptr) if new_frame.colorspace != p.colorspace: lib.TransformImageColorspace(new_frame, p.colorspace) if inputImage != p: lib.DestroyImage(inputImage) # TODO how do i do this correctly etc? will it ever be non-null?? #except Exception: # lib.DestroyImage(new_frame) # ImageMagick uses new_size/old_size to compute the resized frame's # position. But new_size has already been rounded, so for small # frames in a large image, the double rounding error can place the # new frame a noticable distance from where one might expect. Fix # the canvas manually, too. new_frame.page.width = target_width new_frame.page.height = target_height new_frame.page.x = int(p.page.x * ratio_width + 0.5) new_frame.page.y = int(p.page.y * ratio_height + 0.5) lib.AppendImageToList(new_stack_ptr, new_frame) p = lib.GetNextImageInList(p) return type(self)(new_stack_ptr[0])
def resized(self, size, filter=None): size = Size.coerce(size) # TODO allow picking a filter # TODO allow messing with blur? p = self._stack new_stack_ptr = ffi.new("Image **", ffi.NULL) if filter == 'box': c_filter = lib.BoxFilter else: c_filter = lib.UndefinedFilter target_width = size.width target_height = size.height ratio_width = target_width / (self._stack.page.width or self._stack.columns) ratio_height = target_height / (self._stack.page.height or self._stack.rows) while p: # Alrighty, so. ResizeImage takes the given size as the new size # of the FRAME, rather than the CANVAS, which is almost certainly # not what anyone expects. So do the math to fix this manually, # converting from canvas size to frame size. frame_width = int(p.columns * ratio_width + 0.5) frame_height = int(p.rows * ratio_height + 0.5) # Imagemagick seems to consider sRGB (nonlinear) to be the default colorspace # for images without colorspace information. In doing so, it might mangle colors # during some operations which expect a linear colorspace. It also seems to prefer # sRGB as output, rather than using the input colorspace. # See also: # http://www.imagemagick.org/script/color-management.php inputImage = p with magick_try() as exc: if c_filter == lib.BoxFilter: # Use the faster ScaleImage in this special case new_frame = lib.ScaleImage( inputImage, frame_width, frame_height, exc.ptr) else: new_frame = lib.ResizeImage( inputImage, frame_width, frame_height, c_filter, 1.0, exc.ptr) if new_frame.colorspace != p.colorspace and p.colorspace != lib.UndefinedColorspace: lib.TransformImageColorspace(new_frame, p.colorspace) if inputImage != p: lib.DestroyImage(inputImage) # TODO how do i do this correctly etc? will it ever be non-null?? #except Exception: # lib.DestroyImage(new_frame) # ImageMagick uses new_size/old_size to compute the resized frame's # position. But new_size has already been rounded, so for small # frames in a large image, the double rounding error can place the # new frame a noticable distance from where one might expect. Fix # the canvas manually, too. new_frame.page.width = target_width new_frame.page.height = target_height new_frame.page.x = int(p.page.x * ratio_width + 0.5) new_frame.page.y = int(p.page.y * ratio_height + 0.5) lib.AppendImageToList(new_stack_ptr, new_frame) p = lib.GetNextImageInList(p) return type(self)(new_stack_ptr[0])
def test_rose_pixel(ctx): img = builtins.rose img = img.cropped(Size(1, 1).at((40, 30))) img = img.resized((100, 100), filter='box') ctx.compare(img, 'canvas_pick.miff')