What I'm trying to do: Since I'm still quite new to image generation using the PIL library, I decided to experiment with putting images on top of gifs. There were not a lot of proper tutorials or references I could use.
What's going wrong: More often than not, the gif would not be generated. This would give the error IndexError: bytearray index out of range which I'm not sure how to fix. However, sometimes the gif would be generated, but there would be some errors. I have included some of these gifs below.
The code:
#client.command()
async def salt(ctx, user:discord.Member=None):
if user == None:
user = ctx.author
animated_gif = Image.open("salty.gif")
response = requests.get(user.avatar_url)
background = Image.open(BytesIO(response.content))
all_frames = []
# background = background.resize((500, 500))
for gif_frame in ImageSequence.Iterator(animated_gif):
# duplicate background image
new_frame = background.copy()
# need to convert from `P` to `RGBA` to use it in `paste()` as mask for transparency
gif_frame = gif_frame.convert('RGBA')
# paste on background using mask to get transparency
new_frame.paste(gif_frame, mask=gif_frame)
all_frames.append(new_frame)
# save all frames as animated gif
all_frames[0].save("image.gif", save_all=True, append_images=all_frames[1:], duration=50, loop=0)
This is the gif I am using:
Unfortunately animated GIF support in PIL is faulty, and hard to work with at all. The images you are showing suffer from the layers sharing the palette information with the background layer, so they have some of their colors distorted.
I don't know if there is a way to control the palette information for each frame using PIL.
If you want to generate GIFs progamaticaly using Python, I'd, for now, recommend that you use the GIMP Image editor - there you can build your image, either interactively using the program, or programaticaly, using the Python console, and just call the "save as gif" function (pdb.file_gif_save2).
(I will take a look at PILs exact capabilities, and check if I can extend the answer on proper handling of transparency - otherwise, GIMP is the way to go)
Related
I'm using latest PyQt5 5.12.2 and I'm getting a weird message for every JPG picture that I'm showing in my script using QPixmap or QIcon.
qt.gui.icc: fromIccProfile: failed minimal tag size sanity
It isn't causing anything and the script works as it should. The problem is that I'm trying to display a huge amount of jpg pictures at the same time (as a photo gallery) so the window gets unresponsive until all messages are printed for each photo.
I tried for hours to find something useful online but unfortunately, it seems like nearly no one had the same issue. I'm using some PNG files too and they don't raise this error so I'm assuming that the problem is with jpg format. I tried using older pyqt5 versions but the only difference is that they are not printing the message but the problem remains.
Meanwhile, I tried to use this command to mute those messages since there is no use of them but the problem with unresponsive window for a few seconds remains even when it's not printing in the console.
def handler(*args):
pass
qInstallMessageHandler(handler)
EDIT: I tried converting these images to PNG but the error remains. So the JPG format wasn't the problem
I dug more deeply into ICC profiles and colour spaces and it seems like the colour space that your pictures are using is somehow non-standard for PyQt.
My solution is to convert these pictures to an ICC profile that is classical such as sRGB.
Here's an example function:
import io
from PIL import Image, ImageCms
def convert_to_srgb(file_path):
'''Convert PIL image to sRGB color space (if possible)'''
img = Image.open(file_path)
icc = img.info.get('icc_profile', '')
if icc:
io_handle = io.BytesIO(icc) # virtual file
src_profile = ImageCms.ImageCmsProfile(io_handle)
dst_profile = ImageCms.createProfile('sRGB')
img_conv = ImageCms.profileToProfile(img, src_profile, dst_profile)
icc_conv = img_conv.info.get('icc_profile','')
if icc != icc_conv:
# ICC profile was changed -> save converted file
img_conv.save(file_path,
format = 'JPEG',
quality = 50,
icc_profile = icc_conv)
Using PIL library is a fast and effective way how to properly resolve that error.
I am making a GUI Image Viewer with Pyside2 and was having a similar issue.
The images were loading fine and for my case there was no performances issues, but I keep getting these ICC warnings.
And I didn't want to fix the original files, because my app is supposed to be only a viewer.
I don't know it will help for your case, but my solution is to first load the image with pillow ImageQT module
from pathlib import Path
from PIL.ImageQt import ImageQt
def load_image(path):
if Path(path).is_file():
return ImageQt(path)
Then in my QT Widget class that display the image, I load this image on a empty QPixmap:
def on_change(self, path):
pixmap = QtGui.QPixmap()
image = load_image(path)
if image:
pixmap.convertFromImage(image)
if pixmap.isNull():
self.display_area_label.setText('No Image')
else:
self.display_area_label.setPixmap(pixmap)
So I have been having problems trying to write the function to change the size of an image if to big and saving it as a thumbnail. I have how to retrieve the image just lost after that. I know about pillow but cant use for the class any help would be appreciated.
Update: So far I have gotten the code to resize the image and make it a thumbnail. The next part that I am on is having it save if resized to thumbnail2, but if it stays the same save as thumbnail1. Here is my code so far without the next step.
import urllib
url ="https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstra ion_1.png"
src = "C:\Users\laramie\Pictures\PNG_transparency_demonstration_1.png"
connect = urllib.urlretrieve(url, src)
def scalePicture(src):
newWidth = getWidth(src)/2
newHeight = getHeight(src)/2
canvas = makeEmptyPicture(newWidth, newHeight)
for x in range(newWidth):
for y in range(newHeight):
setColor(getPixel(canvas, x,y), getColor(getPixel(src, x*2, y*2)))
return canvas
def thumbNail():
srcPic = makePicture(src)
destWidth = getWidth(srcPic) / 2
destHeight = getHeight(srcPic) / 2
destPic = makeEmptyPicture(destWidth, destHeight)
destPic = scalePicture(srcPic)
show(srcPic)
show(destPic)
thumbNail()
There are a bunch of strange things going on in your code:
destPic = makeEmptyPicture(destWidth, destHeight)
destPic = scalePicture(srcPic)
the first line here is not required, because the destPic is overwritten immediately.
for x in range(newWidth):
for y in range(newHeight):
setColor(getPixel(canvas, x,y), getColor(getPixel(src, x*2, y*2)))
Ths is a very inefficient way to scale an image, that gives inferior results, unless the scale factor is an integer, and even then there are faster and better approaches.
I would recommend you to import PIL (Python Image Library) and use it to work with images. Things like loadng, saving, scaling or flipping images are easily done. However, you may need to install this library if it did not come with your python installation.
I am currently trying to write some kind of mapping tool in python using both the PIL and Tkinter module. So far almost everything works quite fine. During the setup process of the ui a virtual image is created (based upon several input png files), which also seems to have worked well. However, when trying to display this picture inside a canvas using ImageTk.Photo the display shows something that is definitly not the picture. For debugging reasons I had my picture be output by using the show method on the PIL.Image itself.
However what the show method shows and the canvas displays somehow differ - I could not figure out the solution and would appreciate help.
First of all the code excerts (I only show the initialization of the canvas and the displaying of the picture, since the whole code would be extremly long and not provide any more inside I guess).
This is the code excert of the initialization of the canvas (I display a completly black picture until the user loaded all resources neccessary for displaying the picture):
self.tileset_canvas = tkinter.Canvas(root, width=1024, height=1024)
self.tileset_canvas.create_line(0, 0, 80, 80)
self.tileset_raw_image = ImageTk.PhotoImage(PImage.new("RGB", (1024, 1024), "black"))
self.tileset_canvas_image = self.tileset_canvas.create_image(0, 0, image=self.tileset_raw_image)
self.tileset_canvas.grid(row=4, column=6, sticky=tkinter.W)
Furthermore inside another method I change the display of the image:
merged_img = PImage.new("RGB", (128, 512))
merged_img.paste(tsp_img, (0, 0))
merged_img.paste(tss_img, (0, 320))
merged_img = merged_img.resize((1024, 1024)) #Double the view for a better mapping expierience
merged_img = merged_img.convert(mode="RGB")
self.tileset_raw_image = ImageTk.PhotoImage(merged_img)
merged_img.show()
self.tileset_canvas.itemconfig(self.tileset_canvas_image, image=self.tileset_raw_image)
As you can see I also call the show method to see what the picture actually looks like.
This is what the show method outputs, when called on the picture:
However this is what is shown inside the ui:
For anyone that cares, it seems that the image was not just correctly anchored but instead fixed in the middle of the canvas.
I'm trying to convert from PDF to JPG using PythonMagick, but I can't find a way to set the background color, which by default is changed from transparent to black. I can get the desired result using os.system and the -flatten parameter as shown below.
import os
os.system('convert -flatten -background \#ffffff -density 400 -adaptive-resize 1900x infile.pdf outfile.jpg')
However, PythonMagick does not seem to have a flatten method and the following snippet produces an image with a black background.
import PythonMagick
import os
img = PythonMagick.Image("infile.pdf")
img.backgroundColor('#ffffff')
img.density('400')
img.resize('1900x')
img.magick('JPG')
img.quality(60)
img.write("outfile.jpg")
There is also a transparent() method that takes a color. I'm not quite sure what it's for, but img.transparent('#ffffff') did not help. Is there another way to achieve the same result? I'd rather not do it using os.system, since it seems to take quite alot longer.
If you look at the documentation for the -flatten command-line option, you will see it is an alias for -layers flatten.
The -layers flatten command is itself a combination command, which comprises creating a layer of the current background colour the size of the first images canvas, and then composing each layer in turn on top of it.
PythonMagick is essentially just a binding layer to the Magick++ C++ interface. The advanced commands that convert provides, are not necessarily replicated in the lower level libraries, as they are really a sequence of commands as described above. So whilst there is no single command for it in the PythonMagick library, the functionality can be replicated.
The method you are after is .composite(), the PythonMagick documentation is so limited ( or indeed non-existent), most people stay clear of the library. But I think the usage is something like this, if there was only one layer in the PDF (totally untested):
import PythonMagick
img = PythonMagick.Image("infile.pdf")
img.density('400')
bgColour = PythonMagick.ColorRGB(1.0, 1.0, 1.0)
size = "%sx%s" % (img.columns(), img.rows())
flattened = PythonMagick.Image(size, bgColour)
flattened.type = img.type
flattened.composite(img, 0, 0, PythonMagick.CompositeOperator.SrcOverCompositeOp)
flattened.resize('1900x')
flattened.magick('JPG')
flattened.quality(60)
flattened.write("outfile.jpg")
NB. The composition operator could be PythonMagick.CompositeOperator.DstOverCompositeOp, I'm not sure which way round it is handling that.
Though PDFs are a special case with ImageMagick, as they are usually passed off to ghostscript to rasterize. Which means you might need to give ghostscript (gs) some odd parameters to handle the alpha channel properly. Try adding verbose options to the command that works to see what delegate commands it issues and consider doing the PDF rasterisation yourself via an os.system('gs ...') command and then doing the resize. Though I doubt that would be faster than just calling convert.
I want to save gtk.DrawingArea() object contents to jpeg file using PIL. Particularly I want add to this script possibility to make photo. I found how to save image to jpeg. All I need - get pixbuf object from gtk.DrawingArea() object. How can I do this?
If you're not married to the idea of using gstreamer, you can use OpenCV instead. This post gives a great example using pygame.
However, you can get the pixbuf this way (and you don't even need PIL):
def get_picture(self, event, data):
drawable = self.movie_window.window
colormap = drawable.get_colormap()
pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, *drawable.get_size())
pixbuf = pixbuf.get_from_drawable(drawable, colormap, 0,0,0,0, *drawable.get_size())
pixbuf.save(r'somefile.png', 'png')
pixbuf.save(r'somefile.jpeg', 'jpeg')
and then just create a button and bind the command to get_picture.
Of course, if I were writing this program I would actually grab the image to an Image in the first place and then draw it on the gtk.DrawingArea - that would allow you to do all sorts of fun things.