Drawing lines with tkinter - python

I'm working on a project that requires me to show a grid on an image, but my code keeps erroring saying 'numpy.ndarray' object has no attribute 'load'. The error is happening at the line draw = ImageDraw.Draw(cv_img). Why is this happening?
`
import Tkinter
import cv2
from PIL import Image, ImageTk, ImageDraw
# Creates window
window = Tkinter.Tk()
# Load an image using OpenCV
cv_img = cv2.imread("P:\OneSky\United States.png", cv2.COLOR_BGR2RGB)
window.title("United States Map")
# Get the image dimensions (OpenCV stores image data as NumPy ndarray)
height, width, no_channels = cv_img.shape
# Create a canvas that can fit the above image
canvas = Tkinter.Canvas(window, width = width, height = height)
canvas.pack()
# Use PIL (Pillow) to convert the NumPy ndarray to a PhotoImage
photo = ImageTk.PhotoImage(image = Image.fromarray(cv_img))
# Add a PhotoImage to the Canvas
canvas.create_image(0, 0, image=photo, anchor=Tkinter.NW)
# Draws lines on map
draw = ImageDraw.Draw(cv_img)
x = cv_img.width/2
y_start = 0
y_end = cv_img.height
line = ((x,y_start), (x, y_end))
draw.line(line, fill=128)
del draw
# Run the window loop
window.mainloop()`

I don't think you need opencv, try something like this instead:
from PIL import Image, ImageDraw, ImageTk
import tkinter as tk
def main():
root = tk.Tk()
root.title("United States Map")
steps = 25
img = Image.open('P:\OneSky\United States.png').convert('RGB')
draw = ImageDraw.Draw(img)
y_start = 0
y_end = img.height
step_size = int(img.width / steps)
for x in range(0, img.width, step_size):
line = ((x, y_start), (x, y_end))
draw.line(line, fill=128)
x_start = 0
x_end = img.width
for y in range(0, img.height, step_size):
line = ((x_start, y), (x_end, y))
draw.line(line, fill=128)
del draw
display = ImageTk.PhotoImage(img)
label = tk.Label(root, image=display)
label.pack()
root.mainloop()
if __name__ == '__main__':
main()
I got the idea for how to step the grid from here.

Related

image not transparent despite being RGBA PNG format and transparent when opened on preview on mac

Im trying to make a game engine with tkinter but I've encountered a problem. I have an image when opened, clearly shows transparency and, when this code is run:
print(image.mode)
it prints RGBA as the mode. I have googled the necessary elements for transparent images on tkinter and I have all of it. Mode=RGBA format=PNG
and yet, it's still not completely transparent:
and here's the image when opened on preview. It clearly shows transparency:
So why does it show transparency in preview and other apps(like google docs, slides, etc.) but doesn't show transparency in tkinter?
heres the full code:
from tkinter import *
from PIL import ImageTk, Image
class Create(object):
def __init__(self, root, img="default.png", width=None, height=None, x=0, y=0):
self._debug_img = Image.open(img)
self.img = ImageTk.PhotoImage(Image.open(img))
self.img_wdth, self.img_hgt = self._debug_img.size
if width == None and height == None:
self.width = self.img_wdth
self.height = self.img_hgt
elif type(width) != int and type(height) != int:
self.width = self.img_wdth
self.height = self.img_hgt
elif width != self.img_wdth and height != self.img_hgt:
self.copy = self._debug_img.resize((self.width, self.height))
self.img = ImageTk.PhotoImage(Image.open(self.copy))
self.hitbox = (width, height)
self.x = x
self.y = y
self.root = root
self.sprite = Label(self.root, image=self.img)
self.sprite.place(x=self.x, y=self.y, anchor=CENTER)
def update_pos(self):
self.sprite.place(x=self.x, y=self.y)
def update_sprite(self):
self.copy = ImageTk.PhotoImage(Image.open(Image.open(self.img).resize(self.width, self.height)))
self.sprite = Label(self.root, image=self.copy)
def update_hitbox(self):
self.hitbox = (self.width, self.height)
Steps to solve:
Make sure your Image is indeed a PNG and RGBA ,
Then you must put your image inside a tkinter canvas canvas.create_image(150, 150, image=photoimage)
Here is a modified code and its result from How do I make Tkinter support PNG transparency? for Python 3
from tkinter import Tk, Frame, Canvas
from PIL import ImageTk, Image
from PIL import Image
im = Image.open("default.png")
if im.mode in ["RGBA","P"]:
#Displaying:
t = Tk()
t.title("Transparency")
frame = Frame(t)
frame.pack()
canvas = Canvas(frame, bg="black", width=500, height=500, bd=0, highlightthickness=0)
canvas.pack()
photoimage = ImageTk.PhotoImage(file="default.png")
canvas.create_image(150, 150, image=photoimage)#,alpha=0.5)#,bg="black")
t.mainloop()
else:
print("Can't display transparency since image mode is ",im.mode)
input()
Wish you best!

How to display an image with custom size in python with tkinter and pillow

So what I am trying to achieve is to make a class in python that displays a resizeable image with custom width and height. My code so far:
import tkinter as tk
import PIL
from PIL import ImageTk, Image
class closeIcon:
path = ""
width = 0
height = 0
xpos = 0
ypos = 0
onclick = 0
canv = ""
def __init__(self, path, width, height, xpos, ypos, onclick, canv):
self.path = path
self.width = width
self.height = height
self.xpos = xpos
self.ypos = ypos
self.onclick = onclick
self.canv = canv
self.img = Image.open(path).resize((width, height), Image.ANTIALIAS)
self.img_ = ImageTk.PhotoImage(self.img)
self.label = tk.Label(canv, image=self.img_)
self.label.pack()
now the problem is that transparency gets ignored whenever I try to display the image that I resized with pillow as a tkinter label, and every other attempt to display the image with tkinter and have it resized by pillow resulted in an error. Is there any way of simply displaying an image in python as it works in javascript with the following code:
var img = new Image();
img.src = "path to your image";
img.width = 200;
img.height = 200;

Cannot resize list of images on canvas

I have a rectangle and two images on my canvas. When I resize by dragging window corner down and right this happens:
The blue rectangle resizes properly.
The red and green images only have their borders resized properly.
The red and green images stay the same size no matter how I try to regenerate them with ImageTk.PhotoImage().
Here is my python code:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
try:
from Tkinter import *
except ImportError:
from tkinter import *
from PIL import Image, ImageTk
root = Tk()
images = [] # to hold the newly created image
fills = [] # associated list of fill attributes
# a subclass of Canvas for dealing with resizing of windows
class ResizingCanvas(Canvas):
def __init__(self,parent,**kwargs):
Canvas.__init__(self,parent,**kwargs)
self.bind("<Configure>", self.on_resize)
self.height = self.winfo_reqheight()
self.width = self.winfo_reqwidth()
def on_resize(self,event):
# determine the ratio of old width/height to new width/height
wscale = float(event.width)/self.width
hscale = float(event.height)/self.height
# Save new values as old values
self.width = event.width
self.height = event.height
# resize images
for idx, image in enumerate(images):
fill=fills[idx]
dimensions = "image size: %dx%d" % (image.width(), image.height())
events = "event size: %dx%d" % (event.width, event.height)
neww=int(image.width()*wscale)
newh=int(image.height()*hscale)
image = Image.new('RGBA', (neww, newh), fill)
image = ImageTk.PhotoImage(image)
# images[idx] = image
# resize the canvas
self.config(width=self.width, height=self.height)
# rescale all objects with the "all" tag
self.scale("all",0,0,wscale,hscale)
def create_rectangle(x1, y1, x2, y2, **kwargs):
if 'alpha' in kwargs:
alpha = int(kwargs.pop('alpha') * 255)
fill = kwargs.pop('fill')
fill = root.winfo_rgb(fill) + (alpha,)
fills.append(fill)
# print (fill)
image = Image.new('RGBA', (x2-x1, y2-y1), fill)
images.append(ImageTk.PhotoImage(image))
mycanvas.create_image(x1, y1, image=images[-1], anchor='nw')
mycanvas.create_rectangle(x1, y1, x2, y2, **kwargs)
root.title('alpha1.py')
myframe = Frame(root)
myframe.pack(fill=BOTH, expand=YES)
WinWid=1490; WinHgt=860
mycanvas = ResizingCanvas(myframe,width=WinWid, height=WinHgt, \
highlightthickness=0)
mycanvas.pack(fill=BOTH, expand=YES)
create_rectangle(100, 100, 600, 600, fill='blue')
create_rectangle(300, 300, 950, 700, fill='green', alpha=.5)
create_rectangle(200, 500, 850, 820, fill='#800000', alpha=.6)
mycanvas.addtag_all("all")
root.mainloop()
Notice the line:
# images[idx] = image
If I remove the comment # then the red and green images aren't painted whatsoever. Only the image borders appear. I think this is the key because image should be equal to images[idx] prior to resizing.
If you can answer keep in mind the next step in the project will be to grab an image (or rectangle) and move it on the resizable canvas.
Thanks to Stovfl's comment I was able to use:
self.itemconfig(items[idx], image=image)
To change the size of the image. Unfortunately the way the scaling works in tkinter:
# rescale all objects with the "all" tag
self.scale("all",0,0,wscale,hscale)
The image wasn't filling the borders completely and missing a pixel or two. So I had to generate my own scaling factor based on original canvas width and height:
# images use ratio of original width/height to new width/height
wiscale = float(event.width)/self.startw
hiscale = float(event.height)/self.starth
Finally to prevent garbage collector from removing the resized image I assigned it to a list:
resized[idx]=image # stop garbage collector from removing image
TL;DR
Here is the entire Python scrip in case you would like to use it in your own project:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
try:
from Tkinter import *
except ImportError:
from tkinter import *
from PIL import Image, ImageTk
root = Tk()
images = [] # to hold the newly created image
resized = [] # Resized images
fills = [] # associated list of fill attributes
items = [] # associated list of canvass item tags / ids
# a subclass of Canvas for dealing with resizing of windows
class ResizingCanvas(Canvas):
def __init__(self,parent,**kwargs):
Canvas.__init__(self,parent,**kwargs)
self.bind("<Configure>", self.on_resize)
self.height = self.winfo_reqheight()
self.width = self.winfo_reqwidth()
self.startw = self.width
self.starth = self.height
def on_resize(self,event):
# determine the ratio of previous width/height to new width/height
wscale = float(event.width)/self.width
hscale = float(event.height)/self.height
# Save new values as old values
self.width = event.width
self.height = event.height
# images use ratio of original width/height to new width/height
wiscale = float(event.width)/self.startw
hiscale = float(event.height)/self.starth
# resize images
for idx, image in enumerate(images):
fill=fills[idx]
neww=int(image.width()*wiscale)
newh=int(image.height()*hiscale)
image = Image.new('RGBA', (neww, newh), fill)
image = ImageTk.PhotoImage(image)
self.itemconfig(items[idx], image=image)
resized[idx]=image # stop garbage collector from removing image
# resize the canvas
self.config(width=self.width, height=self.height)
# rescale all objects with the "all" tag
self.scale("all",0,0,wscale,hscale)
def create_rectangle(x1, y1, x2, y2, **kwargs):
if 'alpha' in kwargs:
alpha = int(kwargs.pop('alpha') * 255)
fill = kwargs.pop('fill')
fill = root.winfo_rgb(fill) + (alpha,)
fills.append(fill)
image = Image.new('RGBA', (x2-x1, y2-y1), fill)
images.append(ImageTk.PhotoImage(image))
item=mycanvas.create_image(x1, y1, image=images[-1], anchor='nw')
items.append(item)
mycanvas.create_rectangle(x1, y1, x2, y2, **kwargs)
root.title('alpha1.py')
myframe = Frame(root)
myframe.pack(fill=BOTH, expand=YES)
WinWid=1490; WinHgt=860
mycanvas = ResizingCanvas(myframe,width=WinWid, height=WinHgt, \
highlightthickness=0)
mycanvas.pack(fill=BOTH, expand=YES)
create_rectangle(100, 100, 600, 600, fill='blue')
create_rectangle(300, 300, 950, 700, fill='green', alpha=.5)
create_rectangle(200, 500, 850, 820, fill='#800000', alpha=.6)
mycanvas.addtag_all("all")
for image in images:
resized.append(image)
root.mainloop()

Draggable Tkinter Label With Transparent Image Still Covers Image from Parent Canvas

I am working on adding drag-able labels on top of a background image, where the labels are images with a transparent background. The images used for the label themselves are transparent, but the Label itself is not transparent in relation to its parent canvas. Since the labels are drag-able, I can't easily use the parent image for label, and paste the transparent image on top.
The dragon is a drag-able label, with a transparent background, but you see that the label itself is not transparent, and covers the canvas image.
The dragon image itself has a transparent background, as the normal background is blue, so I know the issue is with the label transparency.
The desired behavior would be to allow the label to be transparent, so when the image on the label is transparent, the label should show through to the image below.
Here is a minimal example:
from PIL import Image, ImageTk
import numpy as np
import tkinter as tk
#Creates a blue square with a transparent border
blue_square_transparent_border = [[[0,0,0,0]]*100]*10 + [[[0,0,0,0]]*30 + [[0,0,255,255]]*40 + [[0,0,0,0]]*30]*40 + [[[0,0,0,0]]*100]*10
blue_square_transparent_border = np.array(blue_square_transparent_border, dtype='uint8')
#convert numpy array to PIL image
pil_image = Image.fromarray(blue_square_transparent_border)
root = tk.Tk()
root.configure(background='red')
#convert PIL image to tkinter image
tk_image = ImageTk.PhotoImage(pil_image)
#create label
image_label = tk.Label(root, image=tk_image)
image_label.pack()
root.mainloop()
What I would like to see is a blue square, on a red background, with no border. In the above example though, the border of grey appears, as it is the label being seen through the transparent image; it is easy to see this when you resize the window. I suspect if the label were transparent, this would solve my issues.
Any help would be great, Thanks!
If you want to drag partially transparent images (like the dragon) over a background image, you can do it with a Canvas:
The idea is not to use labels, but the create_image method of the Canvas.
First, display your background image with canvas.create_image(0, 0, image=background_image, anchor='nw'), then display all the draggable images with the tag 'draggable': canvas.create_image(x, y, image=draggable_image, anchor='nw', tag='draggable'). Finally, bind the tag 'draggable' to mouse events.
Here is an example:
import tkinter as tk
import numpy as np
from PIL import Image, ImageTk
# drag callbacks
dragged_item = None
current_coords = 0, 0
def start_drag(event):
global current_coords
global dragged_item
result = canvas.find_withtag('current')
if result:
dragged_item = result[0]
current_coords = canvas.canvasx(event.x), canvas.canvasy(event.y)
else:
dragged_item = None
def stop_drag(event):
dragged_item = None
def drag(event):
global current_coords
xc, yc = canvas.canvasx(event.x), canvas.canvasy(event.y)
dx, dy = xc - current_coords[0], yc - current_coords[1]
current_coords = xc, yc
canvas.move(dragged_item, dx, dy)
#Create pictures
blue_square_transparent_border = [[[0,0,0,0]]*100]*10 + [[[0,0,0,0]]*30 + [[0,0,255,255]]*40 + [[0,0,0,0]]*30]*40 + [[[0,0,0,0]]*100]*10
blue_square_transparent_border = np.array(blue_square_transparent_border, dtype='uint8')
pil_image = Image.fromarray(blue_square_transparent_border)
background_data = np.zeros((200, 400, 4))
background_data[:, :, 0] = 255 * np.ones((200, 400))
background_data[:, :, 3] = 255 * np.ones((200, 400))
background_data = np.array(background_data, dtype='uint8')
pil_image_bg = Image.fromarray(background_data)
# create GUI
root = tk.Tk()
background_image = ImageTk.PhotoImage(pil_image_bg)
tk_image = ImageTk.PhotoImage(pil_image)
canvas = tk.Canvas(root, width=400, height=200)
canvas.pack()
# bind 'draggable' tag to mouse events
canvas.tag_bind('draggable', '<ButtonPress-1>', start_drag)
canvas.tag_bind('draggable', '<ButtonRelease-1>', stop_drag)
canvas.tag_bind('draggable', '<B1-Motion>', drag)
# display pictures
canvas.create_image(0, 0, image=background_image, anchor='nw')
canvas.create_image(0, 0, image=tk_image, anchor='nw', tag='draggable')
root.mainloop()

Tkinter bind widgets below a rectangle widget to a mouse event

I hope I am explaining the problem correctly.
My example below is able to move two images defined on a canvas. The problem is that I want a rectangle, also defined on the canvas, on top of the images. When I do that using .tag_raise, the event triggered by mouse drag is triggered by the rectangle, not the images.
I tried using bing_class but that did not work. I tried to define a separate canvas for the rectangle but it has to overlay the main canvas and I got stuck.
How to keep the rectangle on top but bind the images to my mouse drag event?
import Tkinter as tk # for Python2
import PIL.Image, PIL.ImageTk
win = tk.Tk()
canvas = tk.Canvas(win, height = 500, width = 500)
#Create a rectangle with stipples on top of the images
rectangle = canvas.create_rectangle(0, 0, 400, 300, fill = "gray", stipple = "gray12")
#Create two images
SPRITE = PIL.Image.open("image.jpg")
imagePIL = SPRITE.resize((100, 100))
imagePI = PIL.ImageTk.PhotoImage(imagePIL)
image1 = canvas.create_image(100, 100, image = imagePI, tags = "image")
image2 = canvas.create_image(200, 200, image = imagePI, tags = "image")
#Callback
# Here I select image1 or image2 depending on where I click, and
# drag them on the canvas. The problem is when I put the rectangle
# on top using tag_raise (see below).
def callback(event):
id = canvas.find_withtag(tk.CURRENT)
canvas.coords(id, (event.x, event.y))
#Binding
canvas.bind("<B1-Motion>", callback)
#Place the rectangle on top of all
canvas.pack()
# This is the problem. I want to have the rectangle on top and be able to use the callback
#canvas.tag_raise(rectangle)
canvas.mainloop()
SOLUTION: I enhanced Nehal's answer with the following code. His answer had a glitch, by which images could be switched. In my enhancement I solve it by storing a lock for each image so that, while dragging an image around on the canvas, the same image is dragged. When I move e.g. image1 over image2 I notice that image1 does not completely move over image2, which is fine for me.
import Tkinter as tk # for Python2
import PIL.Image, PIL.ImageTk
win = tk.Tk()
canvas = tk.Canvas(win, height = 500, width = 500)
#Create a rectangle with stipples on top of the images
rectangle = canvas.create_rectangle(0, 0, 400, 300, fill = "gray", stipple = "gray12")
#Create two images
SPRITE = PIL.Image.open("image.jpg")
imagePIL = SPRITE.resize((100, 100))
imagePI = PIL.ImageTk.PhotoImage(imagePIL)
image1 = canvas.create_image(100, 100, image = imagePI, tags = "image")
image2 = canvas.create_image(200, 200, image = imagePI, tags = "image")
images = [image1, image2]
locks = [True, True]
def getImage(x, y):
for image in images:
curr_x, curr_y = canvas.coords(image)
x1 = curr_x - imagePI.width()/2
x2 = curr_x + imagePI.width()/2
y1 = curr_y - imagePI.height()/2
y2 = curr_y + imagePI.height()/2
if (x1 <= x <= x2) and (y1 <= y <= y2):
return image
#Callback
# Here I select image1 or image2 depending on where I click, and
# drag them on the canvas.
def callback(event):
id = getImage(event.x, event.y)
if id:
if locks[images.index(id)] is False: #Hold on to the image on which I originally clicked
canvas.coords(id, (event.x, event.y))
def mouseClick(event):
id = getImage(event.x, event.y)
if id:
locks[images.index(id)] = False
print(locks)
def mouseRelease(event):
id = getImage(event.x, event.y)
if id:
locks[images.index(id)] = True
print(locks)
#Binding
canvas.bind("<ButtonPress-1>", mouseClick) #unlock the image to move it
canvas.bind("<ButtonRelease-1>", mouseRelease) #lock the image
canvas.bind("<B1-Motion>", callback)
#Place the rectangle on top of all
canvas.pack()
# This was the original problem
canvas.tag_raise(rectangle)
canvas.mainloop()
I don't know a tkinter specific way to do this, however, you can try to get the coordinates of the closest image and play with them. Like this:
import Tkinter as tk # for Python2
import PIL.Image, PIL.ImageTk
win = tk.Tk()
canvas = tk.Canvas(win, height = 500, width = 500)
#Create a rectangle with stipples on top of the images
rectangle = canvas.create_rectangle(0, 0, 400, 300, fill = "gray", stipple = "gray12")
#Create two images
SPRITE = PIL.Image.open("image.jpg")
imagePIL = SPRITE.resize((100, 100))
imagePI = PIL.ImageTk.PhotoImage(imagePIL)
image1 = canvas.create_image(100, 100, image = imagePI, tags = "image")
image2 = canvas.create_image(200, 200, image = imagePI, tags = "image")
images = [image1, image2]
def getImage(x, y):
for image in images:
curr_x, curr_y = canvas.coords(image)
x1 = curr_x - imagePI.width()/2
x2 = curr_x + imagePI.width()/2
y1 = curr_y - imagePI.height()/2
y2 = curr_y + imagePI.height()/2
if (x1 <= x <= x2) and (y1 <= y <= y2):
return image
#Callback
# Here I select image1 or image2 depending on where I click, and
# drag them on the canvas. The problem is when I put the rectangle
# on top using tag_raise (see below).
def callback(event):
id = getImage(event.x, event.y)
if id:
canvas.coords(id, (event.x, event.y))
#Binding
canvas.bind("<B1-Motion>", callback)
#Place the rectangle on top of all
canvas.pack()
# This is the problem. I want to have the rectangle on top and be able to use the callback
canvas.tag_raise(rectangle)
canvas.mainloop()

Categories