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.
Related
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)
I'm working on a Kivy app that lets the user position images and text labels on a template. When they're done, the resulting image is cropped to fit the template.
I need all the empty space in the image to be clear, as I will be printing the image and don't want to waste ink. Kivy, however, fills all the empty space with black. Is there a way for Kivy's export_to_png() function to use a transparent background instead of a black one?
I could be wrong, but it looks like the transparent colour is hardcoded.
Looking at the source code it seems that ClearColor is hard-coded as black.
def export_to_png(self, filename, *args):
'''Saves an image of the widget and its children in png format at the
specified filename. Works by removing the widget canvas from its
parent, rendering to an :class:`~kivy.graphics.fbo.Fbo`, and calling
:meth:`~kivy.graphics.texture.Texture.save`.
.. note::
The image includes only this widget and its children. If you want
to include widgets elsewhere in the tree, you must call
:meth:`~Widget.export_to_png` from their common parent, or use
:meth:`~kivy.core.window.WindowBase.screenshot` to capture the whole
window.
.. note::
The image will be saved in png format, you should include the
extension in your filename.
.. versionadded:: 1.9.0
'''
if self.parent is not None:
canvas_parent_index = self.parent.canvas.indexof(self.canvas)
self.parent.canvas.remove(self.canvas)
fbo = Fbo(size=self.size, with_stencilbuffer=True)
with fbo:
ClearColor(0, 0, 0, 1)
ClearBuffers()
Scale(1, -1, 1)
Translate(-self.x, -self.y - self.height, 0)
I guess you could try updating the Widget module code to accept a parameter to set the ClearColor.
I'm drawing small black and white video frames to a Tkinter canvas using this code (at 10Hz)
self.image.buf = bytearray(header.width * header.height);
self.image.buf[:] = image
self.image.im = Image.frombuffer("L", (header.width, header.height), self.image.buf).resize((320, 240)).transpose(Image.ROTATE_180)
self.image.tkimage = ImageTk.PhotoImage(self.image.im)
if (self.image.id): self.image.delete(self.image.id);
self.image.id = self.image.create_image((0, 0), image=self.image.tkimage, anchor=NW)
Everytime a frame gets drawn, the widget flickers. Isn't the Tk canvas supposed to be double buffered? what can I do to avoid this?
So i figured out the problem -- it seems you have to create your tkimage from the same thread that tk is running in or bad things happen. Thanks to anyone who looked at this!
I don't think there's enough detail in your question to say for certain what the problem is. It's possible to swap images in and out without flicker and your code doesn't look too unusual, so there may be something else in your code that is causing the problem.
Here's one thing to try: instead of deleting and re-creating the canvas item each iteration, try using one canvas item that you reconfigure to use the new image using the itemconfig method.
For example:
if self.image.id is None:
self.image.id = self.image.create_image(...)
else:
self.image.itemconfig(self.image.id, image=self.image.tkimage)
Also, if you're not using the canvas for anything else you might want to consider using a label widget rather than a canvas and an image item.
using google (and this site) i have seen some similar questions but my problem is still here:
"i want to draw an image (without reading a file) , being able to manipulate every single pixel's colour in that image."
i have seen another question where was suggested to do something like this:
from tkinter import *
A=Tk()
B=Canvas(A)
B.place(x=0,y=0,height=256,width=256)
for a in range(256):
for b in range(256):
B.create_line(a,b,a+1,b+1,fill=pyList[a][b])#where pyList is a matrix of hexadecimal strings
A.geometry("256x256")
mainloop()
in fact this answers my question but... it is extremely slow.
what should i do with a 1920x1080 image ? wait for my death?
so i am asking something to perform the same as the above code but in a faster way
i have found a way to improve the method suggested by jsbueno , it is explained in the page linked :
Why is Photoimage put slow?
It is indeed tricky --
I thought you had to use a Canvas widget, but that has no access to Pixels either.
Image items embedded in the Canvas do have, though. The Tkinter.PhotoImage class
does have a "put" method that accepts a color in hex format and pixel coordinates:
from tkinter import Tk, Canvas, PhotoImage, mainloop
from math import sin
WIDTH, HEIGHT = 640, 480
window = Tk()
canvas = Canvas(window, width=WIDTH, height=HEIGHT, bg="#000000")
canvas.pack()
img = PhotoImage(width=WIDTH, height=HEIGHT)
canvas.create_image((WIDTH/2, HEIGHT/2), image=img, state="normal")
for x in range(4 * WIDTH):
y = int(HEIGHT/2 + HEIGHT/4 * sin(x/80.0))
img.put("#ffffff", (x//4,y))
mainloop()
The good news is that even it being done this way, the updates are "live":
you set pixels on the image, and see them showing up on screen.
This should be much faster than the way drawing higher level lines on screen -
but for lots of pixels it still will be slow, due to a Python function call needed for
every pixel. Any other pure python way of manipulating pixels directly will suffer from that - the only way out is calling primitives that manipulate several pixels at a time in native code from your Python code.
A nice cross-platform library for getting 2d drawing, however poorly documented as well
is Cairo - it would should have much better primitives than Tkinter's Canvas or PhotoImage.
Don't forget to save a reference after canvas.create_image. In some cases, especially when working with the PIL module, python will garbage-collect the image, even though it is being displayed!
Syntax is something like
canvas.create_image((WIDTH/2, HEIGHT/2), image=img)
canvas.image = img
I have three questions that I could really use some help on. Hope I'm not asking too much.
1) I am designing a simple GUI that contains one frame and one panel. Let's say I have two images that I draw on the panel using dc. One image will be continually fade in and out (on a timer), and the second is stationary (doesn't change). The fading is accomplished by changing the opacity of the image and use dc.Clear() before redrawing the new version of the image.
My question is this: how would I draw the fading in/out image without affecting the second image which does not change? It seems like this causes unnecessary drawing as the stationary image will be redrawn alongside the fading image. Could I selectively clear just the first image without affecting the second? This is my drawing function:
def on_paint(self, event):
dc = wx.PaintDC(self)
dc = wx.BufferedDC(dc)
brush = wx.Brush('#3B3B3B')
dc.SetBackground(brush)
dc.Clear()
# Draw the first image (stationary)
dc.DrawBitmap(stationaryBitmap, 120, 0, True)
# Draw the second image (fading)
image = self.image.AdjustChannels(1, 1, 1, self.factoralpha)
fadingBitmap = wx.BitmapFromImage(image)
dc.DrawBitmap(fadingBitmap, 120, 0, True)
2) How can I bind an event to a wx.Image object? I would like to be able to click on the fading in/out image, though I can't seem to assign it an id. The goal is to bind an event similar to what I could do with a wx.StaticBitmap.
self.image = wx.Image("C:\image.png", wx.BITMAP_TYPE_PNG)
# Trying to bind an event, but no ID is assigned
self.Bind(wx.EVT_BUTTON, self.go_button, id=self.image.GetId())
3) Is it possible to place wx.DrawBitmap in a sizer? It appears that it only takes an x,y coordinate pair.
dc.DrawBitmap(bitmap, 120, 0, True)
Thanks everyone.
1) For the Performance, I would recommend using a MemoryDC and update the Drawing only it is required. See here: BufferedCanvas. You may want to use more than 2 buffers because you are using 2 images (see example).
2) I don't know about this, but have you tried to do the binding to a panel and fade the panel in/out?
You can directly paint on a wx.Panel.
Regards