wand: How to assemble transparent gif / clear background each frame - python

So I have a series of transparent pngs and append them to a new Image()
with Image() as new_gif:
for img_path in input_images:
with Image(filename=img_path) as inimg:
# create temp image with transparent background to composite
with Image(width=inimg.width, height=inimg.height, background=None) as new_img:
new_img.composite(inimg, 0, 0)
new_gif.sequence.append(new_img)
new_gif.save(filename=output_path)
unfortunately the background is not "cleared" when the new image is appended. They'll have the last image there as well:
But how do I clear the background? I though I do exactly that by compositing into a new image upfront.. `:| HALP!!
I see there is a similar thing with commandline ImageMagick but wand doesn't have anything like that. So far I have to workaround with a fitting background color.

Without seeing the source images, I can assume the -set dispose background is what's needed. For wand, you'll need to call wand.api.library.MagickSetOption method.
from wand.image import Image
from wand.api import library
with Image() as new_gif:
# Tell new gif how to manage background
library.MagickSetOption(new_gif.wand, 'dispose', 'background')
for img_path in input_images:
library.MagickReadImage(new_gif.wand, img_path)
new_gif.save(filename=output_path)
Or alternatively...
You can extent wand to manage Background Dispose behavior. This approach would give you the benefit of alter/generate each frame programmatically. But the down side would include a lot more work with ctypes. For example.
import ctypes
from wand.image import Image
from wand.api import library
# Tell python about library method
library.MagickSetImageDispose.argtypes = [ctypes.c_void_p, # Wand
ctypes.c_int] # DisposeType
# Define enum DisposeType
BackgroundDispose = ctypes.c_int(2)
with Image() as new_gif:
for img_path in input_images:
with Image(filename=img_path) as inimg:
# create temp image with transparent background to composite
with Image(width=inimg.width, height=inimg.height, background=None) as new_img:
new_img.composite(inimg, 0, 0)
library.MagickSetImageDispose(new_img.wand, BackgroundDispose)
new_gif.sequence.append(new_img)
# Also rebuild loop and delay as ``new_gif`` never had this defined.
new_gif.save(filename=output_path)
<- still needs delay correction

Related

How to resize ImageDraw object?

I am following these instructions: https://www.geeksforgeeks.org/python-pil-imagedraw-draw-line/ .
from PIL import Image, ImageDraw
w, h = 220, 190
shape = [(40, 40), (w - 10, h - 10)]
# creating new Image object
img = Image.new("RGB", (w, h))
# create line image
img1 = ImageDraw.Draw(img)
img1.line(shape, fill ="red", width = 0)
img.show()
I then tried to add this line immediately before img.show():
img1 = img1.resize((1024, 1024), Image.BOX)
Which is what I usually do to resize Image objects (I know, if this worked it would distort the image since it's a square, but I don't care about that right now).
When I run the code I get the AttributeError: 'ImageDraw' object has no attribute 'resize'.
So, either there is a different method to resize ImageDraw objects or I need to convert the ImageDraw object back into an Image object. In both cases I couldn't find a solution, can you help me out?
Using
img1 = img1.resize((1024, 1024), Image.BOX)
you're trying to call a resize method on some ImageDraw object. And, the error message tells you, that ImageDraw objects don't have such a method.
Let's have a look at the different modules, classes, and objects involved:
Pillow has an Image module providing an Image class, that "represents an image object". Instances of that class, i.e. Image objects, have a resize method.
Also, there is an ImageDraw module, that "provides simple 2D graphics for Image objects". From the documentation on ImageDraw.Draw:
Creates an object that can be used to draw in the given image.
Note that the image will be modified in place.
The first sentence tells you, that the created ImageDraw object is linked to your actual Image object, and that any draw operation is performed in that image (Image object). The second sentence tells you, that any modification is instantly performed. There's no need to explicitly "update" the Image object, or to somehow "convert" the ImageDraw object (back) to some Image object. (It's also simply not possible.)
So, fixing your code is very easy now. Simply call resize on your actual Image object img, and not on your ImageDraw object img1:
img = img.resize((1024, 1024), Image.BOX)

Difficulties with .scale method with Canvas when using tkinter with python

I am attempting to load a .gif image into a canvas. Once loaded the picture is to be resized using the .scale method on the canvas. The image reloads but does not resize.
I am using python 3.6. I tried it in python 2.7 with the same result although on the full implementation rather that the simplified code shown below.
import tkinter as tk
f = "C:\Fanny Poer.gif"
root = tk.Tk()
dis = tk.Canvas(root)
dis.grid()
new_image = tk.PhotoImage(file=f)
id = dis.create_image(500, 500, image = new_image)
dis.scale(id, 0, 0, .1, .1)
root.mainloop()
The image loads but does not resize. What am I doing wrong?
Once loaded the picture is to be resized using the .scale method on the canvas.
You can't do that with the scale method. That method only affects the coordinates of an object. It won't resize an image.
To resize an image you can use the subsample and zoom methods on the PhotoImage instance, or you can use some other library (eg: pillow) to resize the image.

Make a GIF play exactly once with Python Wand

I can create an animated GIF like this:
from wand.image import Image
with Image() as im:
while i_need_to_add_more_frames():
im.sequence.append(Image(blob=get_frame_data(), format='png'))
with im.sequence[-1] as frame:
frame.delay = calculate_how_long_this_frame_should_be_visible()
im.type = 'optimize'
im.format = 'gif'
do_something_with(im.make_blob())
However, an image created like this loops indefinitely. This time, I want it to loop once, and then stop. I know that I could use convert's -loop parameter if I were using the commandline interface. However, I was unable to find how to do this using the Wand API.
What method should I call, or what field should I set, to make the generated GIF loop exactly once?
You'll need to use ctypes to bind the wand library to the correct C-API method.
Luckily this is straightforward.
import ctypes
from wand.image import Image
from wand.api import library
# Tell Python about the C-API method.
library.MagickSetImageIterations.argtypes = (ctypes.c_void_p, ctypes.c_size_t)
with Image() as im:
while i_need_to_add_more_frames():
im.sequence.append(Image(blob=get_frame_data(), format='png'))
with im.sequence[-1] as frame:
frame.delay = calculate_how_long_this_frame_should_be_visible()
im.type = 'optimize'
im.format = 'gif'
# Set the total iterations of the animation.
library.MagickSetImageIterations(im.wand, 1)
do_something_with(im.make_blob())

Save the contents of a Gtk.DrawingArea or Cairo pattern to an image on disk

I've got a small PyGI project which uses a Cairo image surface, which I then scale with a surface pattern and render on a Gtk.DrawingArea.
I'd like to write the scaled version to a PNG file. I've tried to write from the original surface with Surface.write_to_png(), but it only writes in the original (i.e. non-scaled) size, so I'm stuck in there.
Then I thought I could perhaps fetch the rendered image from the Gtk.DrawingArea and write that to disk, but I haven't found out how to do that in PyGI (this seems to be only possible in GTK+ 2 - save gtk.DrawingArea to file). So I'm trying to figure out how I can write my scaled image to disk.
Here's the code that creates the surface, scales it up and renders it:
def on_drawingarea1_draw (self, widget, ctx, data=None):
# 'widget' is a Gtk.DrawingArea
# 'ctx' is the Cairo context
text = self.ui.entry1.get_text()
if text == '':
return
# Get the data and encode it into the image
version, size, im = qrencode.encode(text)
im = im.convert('RGBA') # Cairo expects RGB
# Create a pixel array from the PIL image
bytearr = array.array('B', im.tostring())
height, width = im.size
# Convert the PIL image to a Cairo surface
self.surface = cairo.ImageSurface.create_for_data(bytearr,
cairo.FORMAT_ARGB32,
width, height,
width * 4)
# Scale the image
imgpat = cairo.SurfacePattern(self.surface)
scaler = cairo.Matrix()
scaler.scale(1.0/self.scale_factor, 1.0/self.scale_factor)
imgpat.set_matrix(scaler)
ctx.set_source(imgpat)
# Render the image
ctx.paint()
And here's the code to write the surface to a PNG file:
def on_toolbuttonSave_clicked(self, widget, data=None):
if not self.surface:
return
# The following two lines did not seem to work
# ctx = cairo.Context(self.surface)
# ctx.scale(self.scale_factor, self.scale_factor)
self.surface.write_to_png('/tmp/test.png')
So writing the surface creates an non-scaled image, and there is no write method in the cairo.SurfacePattern either.
My last resort is to fetch the scaled image as rendered in the gtk.DrawingArea, put it in a GtkPixbuf.Pixbuf or in a new surface, and then write that to disk. The pixbuf approach seemed to work in GTK+ 2, but not in GTK+ 3.
So does anyone know how I can write the scaled image to disk?
Ok, I found a way:
Remembering that Gtk.DrawingArea derives from Gtk.Window, I could use the Gdk.pixbuf_get_from_window() function to get the contents of the drawing area into a GdkPixbuf.Pixbuf and then use the GdkPixbuf.Pixbuf.savev() function to write the pixbuf as an image on disk.
def drawing_area_write(self):
# drawingarea1 is a Gtk.DrawingArea
window = self.ui.drawingarea1.get_window()
# Some code to get the coordinates for the image, which is centered in the
# in the drawing area. You can ignore it for the purpose of this example
src_x, src_y = self.get_centered_coordinates(self.ui.drawingarea1,
self.surface)
image_height = self.surface.get_height() * self.scale_factor
image_width = self.surface.get_width() * self.scale_factor
# Fetch what we rendered on the drawing area into a pixbuf
pixbuf = Gdk.pixbuf_get_from_window(window, src_x, src_y,
image_width, image_height)
# Write the pixbuf as a PNG image to disk
pixbuf.savev('/tmp/testimage.png', 'png', [], [])
While this works, it'd still be nice to see if someone could confirm this is the right way or to see if there is any other alternative.
I found another approach, using the Cairo context passed to the handler of draw events, but it resulted in capturing a region of the parent window that was larger than the DrawingArea.
What worked for me was to use the PixBuf as you have shown, but first calling the queue_draw() method for the DrawingArea, to force a full rendering, and waiting for the event to be processed (easy enough, I already had a draw handler). Otherwise, the resulting images can be partially undrawn.

Antialiasing PNG resize in Qt?

In a PyQt based GUI program, I'm drawing a few PNG file as QPixmap after resize. So here is basically what happens:
bitmap = QPixmap( "foo.png" )
bitmap.scaleToHeight(38) # original is larger than this
scene.addItem(QGraphicsPixmapItem(bitmap)) # Add to graphics scene
The problem is: afterwards, the bitmap is rather ugly. Is there a way to do this in a antialiasing way?
See the documentation.
bitmap = bitmap.scaledToHeight(38, Qt.SmoothTransformation)

Categories