Tkinter canvas fails to update image [duplicate] - image

This question already has answers here:
Why does Tkinter image not show up if created in a function?
(5 answers)
Closed 5 months ago.
According to this tutorial: https://www.tutorialspoint.com/how-to-update-an-image-in-a-tkinter-canvas#:~:text=To%20change%20a%20particular%20image,in%20the%20same%20project%20directory.
this should work: canvas.itemconfig(imageObject, image=newPhotoImage)
But in fact, the following code which seems to follow that pattern, does not work. The first image snap1.jpg appears correctly. But once the update function executes, the canvas goes blank. image.show() reveals that the files were in fact correctly loaded. So I am mystified. What is missing? No exception, no warning message.
from tkinter import *
from PIL import Image, ImageTk
def imagery(root):
global image1
global canvas, log1, snaps2, i1, counter, sizeX, sizeY
image = Image.open("snap1.jpg")
sizeX, sizeY = image.size
canvas= Canvas(root, width= sizeX // 2, height= sizeY // 2)
canvas.grid(column=0, row=0)
image1 = ImageTk.PhotoImage(image.resize((sizeX // 2, sizeY // 2)))
i1 = canvas.create_image(0, 0, anchor='nw',image=image1)
root.after(2000, update)
return canvas
def update():
global counter
image = Image.open("snap2.jpg")
image.show()
image1 = ImageTk.PhotoImage(image.resize((sizeX // 2, sizeY // 2)))
canvas.itemconfig(i1, image=image1)
if __name__ == "__main__":
root = Tk()
root.grid()
canvas = imagery(root)
root.mainloop()

The answer was simple but devious, and highlights a very strange quirk of tkinter. Contrary to all normal Python memory management protocol, tkinter does not keep a reference to the PhotoImage object that it displays. Therefore you must, to prevent it from being garbage collected. As long as the image is intended to be visible, there must be a live reference to the PhotoImage held somewhere in your code. The simplest solution is this tiny modification to hold the reference in a global variable:
def update():
global counter, image1
. . .

Related

setting contrast of image in PyQtGraph GraphicsWindow

I'm building a GUI and I want to allow the user to change the contrast of an image displayed through a PyQtGraph GraphicsWindow (say using a scroll bar or something). Any ideas?
My code looks something like this:
gw = pg.GraphicsWindow(size=(OCT_WIDTH, OCT_HEIGHT), border=True)
gw_layout = gw.addLayout(row=0, col=0)
gw_view = gw_layout.addViewBox(row=1, col=0, lockAspect=True)
img = imread(imgPath)
imgItem = pg.ImageItem(img)
gw_view.addItem(imgItem)
The ImageView widget contains a color bar that can be used to modify the image contrast or intensity. See the ImageView example.
For convenience and future reference I copied the ImageView.py example here below.
# -*- coding: utf-8 -*-
"""
This example demonstrates the use of ImageView, which is a high-level widget for
displaying and analyzing 2D and 3D data. ImageView provides:
1. A zoomable region (ViewBox) for displaying the image
2. A combination histogram and gradient editor (HistogramLUTItem) for
controlling the visual appearance of the image
3. A timeline for selecting the currently displayed frame (for 3D data only).
4. Tools for very basic analysis of image data (see ROI and Norm buttons)
"""
## Add path to library (just for examples; you do not need this)
import initExample
import numpy as np
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
# Interpret image data as row-major instead of col-major
pg.setConfigOptions(imageAxisOrder='row-major')
app = QtGui.QApplication([])
## Create window with ImageView widget
win = QtGui.QMainWindow()
win.resize(800,800)
imv = pg.ImageView()
win.setCentralWidget(imv)
win.show()
win.setWindowTitle('pyqtgraph example: ImageView')
## Create random 3D data set with noisy signals
img = pg.gaussianFilter(np.random.normal(size=(200, 200)), (5, 5)) * 20 + 100
img = img[np.newaxis,:,:]
decay = np.exp(-np.linspace(0,0.3,100))[:,np.newaxis,np.newaxis]
data = np.random.normal(size=(100, 200, 200))
data += img * decay
data += 2
## Add time-varying signal
sig = np.zeros(data.shape[0])
sig[30:] += np.exp(-np.linspace(1,10, 70))
sig[40:] += np.exp(-np.linspace(1,10, 60))
sig[70:] += np.exp(-np.linspace(1,10, 30))
sig = sig[:,np.newaxis,np.newaxis] * 3
data[:,50:60,30:40] += sig
## Display the data and assign each frame a time value from 1.0 to 3.0
imv.setImage(data, xvals=np.linspace(1., 3., data.shape[0]))
## Set a custom color map
colors = [
(0, 0, 0),
(45, 5, 61),
(84, 42, 55),
(150, 87, 60),
(208, 171, 141),
(255, 255, 255)
]
cmap = pg.ColorMap(pos=np.linspace(0.0, 1.0, 6), color=colors)
imv.setColorMap(cmap)
## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()

Overlaying a box on label image using Tkinter

I am using Tkinter and the grid() layout manager to create a GUI. I am showing the image in my GUI using a label, on a tabbed window:
label2 = ttk.Label(tab2)
image2 = PhotoImage(file="lizard.gif")
label2['image'] = image2
label2.grid(column=0, row=0, columnspan=3)
For illustration, let's say the image is 300 x 900. If I know a set of coordinates within the image, how can I overlay a shaded box on the image, defined by the known (A,B,C,D which are shown just for the illustration purpose) coordinates?
Let me give you a step by step solution.
You can use a tkinter.Label() to display your image as you did, you can also choose other widgets. But for situation, let's choose tkinter.Canvas() widget instead (but same reasoning is valid if you choose to use tkinter.Label())
Technical issues:
Your problem contains 2 main sub-problems to resolve:
How to overlay 2 images the way you want.
How to display an image using tkinter.Canvas()
To be able to read an image of jpg format , you need to use a specific PIL (or its Pillow fork) method and a class:
PIL.Image.open()
PIL.ImageTk.PhotoImage()
This is done by 3 lines in the below program:
self.im = Image.open(self.saved_image)
self.photo = ImageTk.PhotoImage(self.im)
And then display self.photo in the self.canvas widget we opted for:
self.canvas.create_image(0,0, anchor=tk.N+tk.W, image = self.photo)
Second, to reproduce the effect you desire, use cv2.addWeighted() OpenCV method. But I feel you have already done that. So I just show you the portion of code of the program that does it:
self.img = cv2.imread(self.image_to_read)
self.overlay = self.img.copy()
cv2.rectangle(self.overlay, (500,50), (400,100), (0, 255, 0), -1)
self.opacity = 0.4
cv2.addWeighted(self.overlay, self.opacity, self.img, 1 - self.opacity, 0, self.img)
cv2.imwrite( self.saved_image, self.img)
Program design:
I use 2 methods:
- __init__(): Prepare the frame and call the GUI initialization method.
- initialize_user_interface(): Draw the GUI and perform the previous operations.
But for scalability reasons, it is better to create a separate method to handle the different operations of the image.
Full program (OpenCV + tkinter)
Here is the source code (I used Python 3.4):
'''
Created on Apr 05, 2016
#author: Bill Begueradj
'''
import tkinter as tk
from PIL import Image, ImageTk
import cv2
import numpy as np
import PIL
class Begueradj(tk.Frame):
'''
classdocs
'''
def __init__(self, parent):
'''
Prepare the frame and call the GUI initialization method.
'''
tk.Frame.__init__(self, parent)
self.parent=parent
self.initialize_user_interface()
def initialize_user_interface(self):
"""Draw a user interface allowing the user to type
"""
self.parent.title("Bill BEGUERADJ: Image overlay with OpenCV + Tkinter")
self.parent.grid_rowconfigure(0,weight=1)
self.parent.grid_columnconfigure(0,weight=1)
self.image_to_read = 'begueradj.jpg'
self.saved_image = 'bill_begueradj.jpg'
self.img = cv2.imread(self.image_to_read)
self.overlay = self.img.copy()
cv2.rectangle(self.overlay, (500,50), (400,100), (0, 255, 0), -1)
self.opacity = 0.4
cv2.addWeighted(self.overlay, self.opacity, self.img, 1 - self.opacity, 0, self.img)
cv2.imwrite( self.saved_image, self.img)
self.im = Image.open(self.saved_image)
self.photo = ImageTk.PhotoImage(self.im)
self.canvas = tk.Canvas(self.parent, width = 580, height = 360)
self.canvas.grid(row = 0, column = 0)
self.canvas.create_image(0,0, anchor=tk.N+tk.W, image = self.photo)
def main():
root=tk.Tk()
d=Begueradj(root)
root.mainloop()
if __name__=="__main__":
main()
Demo:
This is a screenshot of the running program:
You will need to use a canvas widget. That will allow you to draw an image, and then overlay a rectangle on it.
Although the above answers were wonderfully in depth, they did not fit my exact situation (Specifically use of Python 2.7, etc.). However, this solution gave me exactly what I was looking for:
canvas = Canvas(tab2, width=875, height=400)
image2=PhotoImage(file='lizard.gif')
canvas.create_image(440,180,image=image2)
canvas.grid(column=0, row=0, columnspan=3)
The rectangle is added over the canvas using:
x1 = 3, y1 = 10, x2 = 30, y2 = 20
canvas.create_rectangle(x1, y1, x2, y2, fill="blue", stipple="gray12")
stipple comes from this example, to help add transparency to the rectangle.

Is there a max image size (pixel width and height) within wx where png images lose there transparency?

Initially, I loaded in 5 .png's with transparent backgrounds using wx.Image() and every single one kept its transparent background and looked the way I wanted it to on the canvas (it kept the background of the canvas). These png images were about (200,200) in size. I proceeded to load a png image with a transparent background that was about (900,500) in size onto the canvas and it made the transparency a black box around the image. Next, I opened the image up with gimp and exported the transparent image as a smaller size. Then when I loaded the image into python the image kept its transparency. Is there a max image size (pixel width and height) within wx where png images lose there transparency? Any info would help. Keep in mind that I can't resize the picture before it is loaded into wxpython. If I do that, it will have already lost its transparency.
import wx
import os
def opj(path):
return apply(os.path.join, tuple(path.split('/')))
def saveSnapShot(dcSource):
size = dcSource.Size
bmp= wx.EmptyBitmap(size.width, size.height)
memDC = wx.MemoryDC()
memDC.SelectObject(bmp)
memDC.Blit(0, 0, size.width, size.height, dcSource, 0,0)
memDC.SelectObject(wx.NullBitmap)
img = bmp.ConvertToImage()
img.SaveFile('path to new image created', wx.BITMAP_TYPE_JPEG)
def main():
app = wx.App(None)
testImage = wx.Image(opj('path to original image'), wx.BITMAP_TYPE_PNG).ConvertToBitmap()
draw_bmp = wx.EmptyBitmap(1500, 1500)
canvas_dc = wx.MemoryDC(draw_bmp)
background = wx.Colour(208, 11, 11)
canvas_dc.SetBackground(wx.Brush(background))
canvas_dc.Clear()
canvas_dc.DrawBitmap(testImage,0, 0)
saveSnapShot(canvas_dc)
if __name__ == '__main__':
main()
I don't know if I got this right. But if I convert your example from MemoryDC to PaintDC, then I could fix the transparency issue. The key was to pass True to useMask in DrawBitmap method. If I omit useMask parameter, it will default to False and no transparency will be used.
The documentation is here: http://www.wxpython.org/docs/api/wx.DC-class.html#DrawBitmap
I hope this what you wanted to do...
import wx
class myFrame(wx.Frame):
def __init__(self, testImage):
wx.Frame.__init__(self, None, size=testImage.Size)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.testImage = testImage
self.Show()
def OnPaint(self, event):
dc = wx.PaintDC(self)
background = wx.Colour(255, 0, 0)
dc.SetBackground(wx.Brush(background))
dc.Clear()
#dc.DrawBitmap(self.testImage, 0, 0) # black background
dc.DrawBitmap(self.testImage, 0, 0, True) # transparency on, now red
def main():
app = wx.App(None)
testImage = wx.Image(r"path_to_image.png", wx.BITMAP_TYPE_PNG).ConvertToBitmap()
Frame = myFrame(testImage)
app.MainLoop()
if __name__ == '__main__':
main()
(Edit) Ok. I think your original example can be fixed in a similar way
memDC.Blit(0, 0, size.width, size.height, dcSource, 0,0, useMask=True)
canvas_dc.DrawBitmap(testImage,0, 0, useMask=True)
Just making sure that useMask is True was enough to fix the transparency issue in your example, too.

Must be string without null bytes, not str in pygame [duplicate]

This question already has answers here:
Animated sprite from few images
(4 answers)
How do I create animated sprites using Sprite Sheets in Pygame?
(1 answer)
Closed 2 years ago.
So I'm testing out this spritesheet code that I found on this site, and from another python file I wrote, I am trying to go and pass a spritesheet to it, where it will cut it up into a series of images, and allow to animate my monster in my game.
However, when I try to run it, I get this error:
python wormTest.py
Traceback (most recent call last):
File "wormTest.py", line 49, in <module>
worm = Worm()
File "wormTest.py", line 38, in __init__
img = pygame.image.load(outfile.getvalue())
TypeError: must be string without null bytes, not str
I was told to use CStringIO to help handle image input and output, but even after looking at the documentation, it is still a bit fuzzy to me. What is going on here?
The code I wrote for the worm:
import pygame
from sprite_sheet import sprite_sheet #handles sprite sheet stuff
import cStringIO #input output for images
# Define some colors
black = ( 0, 0, 0)
white = ( 255, 255, 255)
red = ( 255, 0, 0)
#initialize pygame
pygame.init()
#set the height and width of the screen
width = 800
height = 480
mainScreen = pygame.display.set_mode([width,height])
#A list of all of the sprites in the game
all_sprites_list = pygame.sprite.Group()
with open("big_worm.png", "rb") as infile:
outfile = cStringIO.StringIO()
outfile.write(infile.read())
class Worm(pygame.sprite.Sprite):
'''class that builds up the worm sprite'''
#constructor function
def __init__(self):
#call up the parent's constructor
pygame.sprite.Sprite.__init__(self)
images =[]
img = pygame.image.load(outfile.getvalue())
img.set_colorkey(white)#sets the color key. any pixel with same color as colorkey will be trasnparent
images = sprite_sheet(img, 40) #make up a sprite sheet
self.image = images[0]
self.rect = self.image.get_rect()
#creates a player object
worm = Worm()
#adds the player object to the all_sprites_list
all_sprites_list.add(worm)
#a conditional for the loop that keeps the game running until the user Xes out
done = False
#clock for the screen updates
clock = pygame.time.Clock()
#
# Game Logic Code
#
while done==False:
for event in pygame.event.get(): #user did something
if event.type == pygame.QUIT: #if the user hit the close button
done=True
mainScreen.fill(white)#makes the background white, and thus, the white part of the images will be invisible
#player.changeImage()
#limit the game to 20 fps
clock.tick(20)
#update the screen on the regular
pygame.display.flip()
pygame.quit()
And the code I'm using for the sprite_sheet:
#!/usr/bin/python
#
# Sprite Sheet Loader - hammythepig
#
# Edited by Peter Kennedy
#
# License - Attribution - hammythepig
#http://stackoverflow.com/questions/10560446/how-do-you-select-a-sprite-image-from-a-sprite-sheet-in-python
#
# Version = '2.0'
import pygame,sys
from pygame.locals import *
import cStringIO
def sprite_sheet(size,file,pos=(0,0)):
#Initial Values
len_sprt_x = size #sprite size
len_sprt_y = size
sprt_rect_x,sprt_rect_y = pos #where to find first sprite on sheet
sheet = pygame.image.load(file).convert_alpha() #Load the sheet
sheet_rect = sheet.get_rect()
sprites = []
print sheet_rect.height, sheet_rect.width
for i in range(0,sheet_rect.height-len_sprt_y,size[1]):#rows
print "row"
for i in range(0,sheet_rect.width-len_sprt_x,size[0]):#columns
print "column"
sheet.set_clip(pygame.Rect(sprt_rect_x, sprt_rect_y, len_sprt_x, len_sprt_y)) #find sprite you want
sprite = sheet.subsurface(sheet.get_clip()) #grab the sprite you want
sprites.append(sprite)
sprt_rect_x += len_sprt_x
sprt_rect_y += len_sprt_y
sprt_rect_x = 0
print sprites
return sprites
#VERSION HISTORY
#1.1 - turned code into useable function
#2.0 - fully functional sprite sheet loader

images in layers with movement and transparency - wxpython

It might sound unproper 'images in layers' like in Photoshop but in fact that is what I have and would like to make working.
I use a set of wx.boxsizer's to have a nice&organized screen after launching my program.
In one row of Horizontal wx.BoxSizer's I have 3 columns done with different wx.Panels and each of that is containing moving wx.StaticBitmaps done by a Timer function.
The second wx.Panel which is in the middle with 0 proportion to mantain original panel size contains 2 images actually, one PNG with transparency and the moving wx.StaticBitmap which should be the background for this PNG.
This is not working out for me. I simply want that the PNG to be over the other image which is moved by the timer. 2 layers if you like.
It is creating a nice very simple and basic action effect (like the object in the PNG is running)
Now I thought of a few ways to go about this but none of them worked:
Figure out how python decides which image to bring in front and which would stay behind. Manipulate that
Keep the moving image in the sizer and take out the PNG and place it over the moving image (than I need to dynamically determine where is that moving image)
Make the moving image the wx.panel background and then probably I can simply call the transparent PNG over it.
I will paste here a part of my script for the brave eyes:
class AnimationPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.loc = wx.Image("intro/runner.png",wx.BITMAP_TYPE_PNG).ConvertToBitmap()
self.locpic = wx.StaticBitmap(self, -1, self.loc, (0, 0), (self.loc.GetWidth(), self.loc.GetHeight()))
self.xer = 3080
self.xer2 = 2310
self.xer3 = 1540
self.env = wx.Image("intro/environ1.png",wx.BITMAP_TYPE_PNG).ConvertToBitmap()
self.env2 = wx.Image("intro/environ2.png",wx.BITMAP_TYPE_PNG).ConvertToBitmap()
self.env3 = wx.Image("intro/environ3.png",wx.BITMAP_TYPE_PNG).ConvertToBitmap()
self.picture = wx.StaticBitmap(self, -1, self.env, (0, 0), (self.env.GetWidth(), self.env.GetHeight()))
self.picture2 = wx.StaticBitmap(self, -1, self.env2, (770, 0), (self.env2.GetWidth(), self.env2.GetHeight()))
self.picture3 = wx.StaticBitmap(self, -1, self.env3, (1540, 0), (self.env3.GetWidth(), self.env3.GetHeight()))
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
self.timer.Start(5)
def OnTimer(self, event):
if self.xer <= 3080:
self.xer += 1
self.picture.Move((self.xer,0))
else:
self.xer = -770
if self.xer2 <= 3080:
self.xer2 += 1
self.picture2.Move((self.xer2,0))
else:
self.xer2 = -770
if self.xer3 <= 3080:
self.xer3 += 1
self.picture3.Move((self.xer3,0))
else:
self.xer3 = -770
This is in the main frame:
ap = AnimationPanel(self)
v2box = wx.BoxSizer(wx.HORIZONTAL)
v2box.Add(someother, 1, wx.EXPAND)
v2box.Add(ap, 0, wx.EXPAND)
v2box.Add(someother, 1, wx.EXPAND)
I did put research in this but I'm quite of a beginner so please help me out with some simple tips or suggestions if it's possible.
Thanks.
In the wxpython demo install there is a samples folder that has a pySketch demo.
In this code it creates dc drawn objects that can be moved in front and behind other objects.
A quick look over this code it looks like the drawn items are stored in a list and then drawn in the list order.
I guess that's how you would implement your layering, you would have a list of layers and in each layer you would store your items for that layer.

Resources