Tkinter Canvas postscript saves as 1x1 - python

I am making trying to make a chess game using Canvas. But when i try to get the image that i made using canvas, it returns a 1x1 image.
Heres the code that i am using
from tkinter import *
from PIL import Image, ImageTk, EpsImagePlugin
import io
EpsImagePlugin.gs_windows_binary = r'gs\bin\gswin64c.exe'
def interpret(c, p, x, y):
path = "images/" + c + '/' + c[0] + p + ".png"
win = Tk()
win.geometry("1200x1200")
canvas= Canvas(win, width= 1200, height=1200)
canvas.pack()
table = ImageTk.PhotoImage(Image.open("images/table.png"))
canvas.create_image(0,0,anchor=NW,image=table)
wp = ImageTk.PhotoImage(Image.open(path))
canvas.create_image((150 * x) - 150, (150 * y) - 150,anchor=NW,image=wp)
postscript = canvas.postscript(colormode='color')
img = Image.open(io.BytesIO(postscript.encode('utf-8')))
img.show()
interpret('black', 'p', 4, 5)
I tried saving it as an eps and converting it to a PNG, but still doesnt work. I expected that it saved as a 1200x1200 (table.png resolution) PNG file

The problem is likely that the canvas hasn't had a chance to render yet so tkinter doesn't know the size of the widget, defaulting to its current 1x1 size.
From the official tcl/tk documentation on the postscript method:
If the canvas is freshly created it may still have its initial size of 1x1 pixel so nothing will appear in the Postscript. To get around this problem either invoke the update command to wait for the canvas window to reach its final size, or else use the -width and -height options to specify the area of the canvas to print.

Related

Display an image from an int array in tkinter, with colour

I am aiming to display an image from the data received from a thermal camera connected to a raspberry pi in a Tkinter window. I have got the data from the pi (which is in a single list 768 int values long (32*24)) which I think can be converted into a NumPy array. I have been looking at similar solutions and found some very promising examples however they don't function as I would hope them to. However, I imagine that is because I have entered the data incorrectly.
The ideal outcome would be to have an image displayed in colour (Purple, red, yellow, etc) however I have no idea and cannot find after looking for a while how to achieve this
Hope I have explained what the problem is
# Example code from StackOverflow: #Adrian W
import tkinter as tk
import numpy as np
def _photo_image(image: np.ndarray):
height, width = image.shape
data = f'P5 {width} {height} 255 '.encode() + image.astype(np.uint8).tobytes()
return tk.PhotoImage(width=width, height=height, data=data, format='PPM')
root = tk.Tk()
array = np.array([[1,2,3,4,5,6,7,8,9],[1,2,3,4,5,6,7,8,9],[1,2,3,4,5,6,7,8,9],[1,2,3,4,5,6,7,8,9],[1,2,3,4,5,6,7,8,9],[1,2,3,4,5,6,7,8,9],[1,2,3,4,5,6,7,8,9],[1,2,3,4,5,6,7,8,9],[1,2,3,4,5,6,7,8,9],[1,2,3,4,5,6,7,8,9],[1,2,3,4,5,6,7,8,9]])
img = _photo_image(array)
canvas = tk.Canvas(root, width=300, height=300)
canvas.pack()
canvas.create_image(20, 20, anchor="nw", image=img)
root.mainloop()
The code above displays an example from another question and this displays a black square on a canvas. The reason it is black is because 1 and 9 have basically no difference in shade I think it must go up to 1000.
But that code^ works fine for B&W, the same post showed a colour version but the example code didn't work
def _photo_image(image: np.ndarray):
height, width = image.shape[:2]
ppm_header = f'P6 {width} {height} 255 '.encode()
data = ppm_header + cv2.cvtColor(image, cv2.COLOR_BGR2RGB).tobytes()
return tk.PhotoImage(width=width, height=height, data=data, format='PPM')
^Doesn't work, and even if it did I'm not sure how to turn an array/list of int into the data it would need for colour, possibly HEX codes or something
Any help would be greatly appreciated :)

Python Tkinter how to adjust the x,y default 0,0 coordinates

I've been learning some basic tkinter and I've come across simple code to centre the window in the middle of your monitor, except when I run it, it's off horizontally. It's pedantic but it bothers me a lot.
The code I used
# Imports
from tkinter import *
# tkinter Application
root = Tk()
#Root Geometry
root_Width = 600
root_Length = 600
# Coordinates of top left pixel of application for centred
x_left = int(root.winfo_screenwidth()/2-root_Width/2)
y_top = int(root.winfo_screenheight()/2-root_Length/2)
root_Pos = "+" + str(x_left) + "+" + str(y_top)
# Window size and position
root.geometry(str(root_Width) + "x" + str(root_Length) + root_Pos)
root.mainloop()
Even if I go basic, and just try to open a window the size of my monitor (1920x1080) at 0,0, it's misaligned horizontally by 8px.
from tkinter import *
root = Tk()
root.geometry("1920x1080+0+0")
root.mainloop()
I took a screenshot of the result:
I have a dual monitor set up so I've added a red line where the right monitor starts. I don't know what the issue is or how to fix it. If I change it to,
root.geometry("1920x1080+-8+0")
It opens where it should, but I want to fix this in general preferably. I want 0,0 to be the top left pixel of the monitor. I acknowledge the issue may not be with python, but any advice would be helpful.
Ok, there are two thing that you are missing. The first, in your first example, is that you need to assure tkinter is getting the right values, and to do this you have to use update_idletasks() method before any winfo.
The second thing, which explains why you have to use -8 to center the full screen window, is that tkinter widows have outer-frames. You can determine the size of this frame by checking for the top left coordinate of the window (winfo_rootx()) and of the outer-frame (winfo_x()). Now the frame width is the difference between both: frame_width = root.winfo_rootx() - root.winfo_x() while the real window width is real_width = root.winfo_width() + (2*frame_width), since you have to consider the frame in both sides.
In summary, to center the window horizontally you do:
width = root.winfo_width()
frame_width = root.winfo_rootx() - root.winfo_x()
real_width = root.winfo_width() + (2*frame_width)
x = root.winfo_screenwidth() // 2 - real_width // 2
(here you can print the frame width and you will see it is 8)
and then use geometry method to place the window at position x.
Off course you can do the same for the vertical alignment

What is the relation between font size in tkinter and window size/ pixel density factor/ original screen resolution in Tkinter

This question has stumped me and I can't seem to figure out why its not scaling properly per device.
I have run my code on 3 different computers (2 laptops and one android tablet) and I don't get what is happening.
I am also using a scaling factor (dpi/72). What is shown is the original scaling factor, but I make the scaling factor proportional to my main laptops scaling factor with this code:
dpi = root.winfo_fpixels('1i')
factor = dpi / 72
factor_multiplier = (.40*factor) +.46
factor = factor/factor_multiplier
starting_frame.tk.call('tk', 'scaling', factor)
main computer:
screen size : 1920x1980 , window size : 1280x960, scaling factor: 1.3, font that is correct : 40
others:
screen size : 1366x768, window size : 1912x683, scaling factor: 1.3, font that is correct : 30
screen size : 2560x1600 , window size : 1750x1311, scaling factor: 5.7, font that is correct : 30
Each window size is roughly 88% of the actual screen res, and converted into 4:3 roughly.
I don't understand how my other two laptops can have such different screen resolutions + dpi but still need the same font size. also the dpi is proportional to my main laptops so that might be a factor as well.All the fonts "fit" the window, they don't all fit a box that has been proportionally made (i.e the box is proportionally the same for all screen resolutions as shown in full relevant code bellow).
relevant code is this:
def font_size_calc(): #this is the problematic code that isnt scaling the font size right
global r2_width, r2_height
x = r2_width*int(r2_height)
return str(math.ceil((-(1/106545)*x) + 51.533155004927)) #linear equation I made, slope = fontsize/screen's area
'''
I tried creating a linear equation based on the fonts that worked for
2 of my devices, but a device with a smaller screen resolution than both of my devices needs a font size of 30 like my highest res device
to have the words stay in the box
'''
starting_frame.create_text(adjusted_pixels(2,"x"),adjusted_pixels(2.742857,"y"),
text = "Enter number of laps",fill = "gray", font=("Calibri "+ font_size_calc())) #calls font_size_calc() to get a working font size
#I want the text to fit in the box created by the var border, it works on my laptop and tablet, but not another old laptop with a smaller screen res
#obviously my equation is wrong but im not sure how to scale the font to work in all cases
code for the program (edited so that you can run it too):
import tkinter as tk
from tkinter.ttk import *
import math
from PIL import ImageTk,Image
root = tk.Tk()
dpi = root.winfo_fpixels('1i')
factor = dpi / 72 #gets how many times biger the dpi is than the standard 72, used for making the screen widgets proportional later
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
ratio_1 = width * height
ratio_2 = 1 #init
r2_width = 4 #setting the window to a 4/3 resolution
r2_height = 3
while (ratio_1 / ratio_2) != 1.6875: #making sure that the new window is 88% the area of the old window
r2_height = r2_width / 1.333333333333333333333333333333333333333333333333333333 #gets hieght
ratio_2 = r2_width * r2_height # gets the area of the new screen size
r2_width = r2_width + 1 #increases the width by one
if (ratio_1/ratio_2) <= 1.6875: #makes sure it doesnt go too far over
break
if width + 1 == r2_width: #another check to make sure it doesnt keep going
break
print("{} x {} = {}".format(str(r2_width),str(r2_height),str(ratio_1/ratio_2)))#prints your ending resolution to manually check accuracy
root.geometry(str(r2_width) + "x"+ str(int(r2_height))) #sets window resolution
starting_frame = tk.Canvas(root) #makes a frame
factor_multiplier = (.40*factor) +.46 #makes a multiplier based on an equation I cam up with (im so glad it was linear)
factor = factor/factor_multiplier #sets factor to be this new peoportion
starting_frame.tk.call('tk', 'scaling', factor)#adjusts tkinter scaling factor for the pixels
starting_frame.place(height=int(r2_height), width = r2_width) #places the frame
images = []
def create_rectangle(frame, x1, y1, x2, y2, **kwargs): #this works so dont worry about this function
if 'alpha' in kwargs:
alpha = int(kwargs.pop('alpha') * 255)
fill = kwargs.pop('fill')
fill = root.winfo_rgb(fill) + (alpha,)
image = Image.new('RGBA', (x2-x1, y2-y1), fill)
images.append(ImageTk.PhotoImage(image))
frame.create_image(x1, y1, image=images[-1], anchor='nw')
return frame.create_rectangle(x1, y1, x2, y2, **kwargs)
def adjusted_pixels(number, x_or_y): #this code creates new coordinates based on set numbers I ahve already made and works aswell
global r2_width, r2_height
if x_or_y == 'x':
print(int(r2_width / number))
return(int(r2_width / number))
else:
print(int(r2_height / number))
return(int(r2_height / number))
def font_size_calc(): #this is the problematic code that isnt scaling the font size right
global r2_width, r2_height
x = r2_width*int(r2_height)
return str(math.ceil((-(1/106545)*x) + 51.533155004927)) #linear equation I made, slope = fontsize/screen's area
'''
I tried creating a linear equation based on the fonts that worked for
2 of my devices, but a device with a smaller screen resolution than both of my devices needs a font size of 30 like my highest res device
to have the words stay in the box
'''
boarder = create_rectangle(starting_frame,adjusted_pixels(3.368421,"x"), adjusted_pixels(3.2,'y'),
adjusted_pixels(1.4222222,"x"), adjusted_pixels(1.45454545,"y"), fill='black',alpha = .78,outline = "red") #creates a box thats slightly transparent, works fine
lap_text = starting_frame.create_text(adjusted_pixels(2,"x"),adjusted_pixels(2.742857,"y"),
text = "Enter number of laps",fill = "gray", font=("Calibri "+ font_size_calc())) #calls font_size_calc() to get a working font size
#I want the text to fit in the box created by the var border, it works on my laptop and tablet, but not another old laptop with a smaller screen res
#obviously my equation is wrong but im not sure how to scale the font to work in all cases
root.mainloop()
You might be wondering why I am messing with the scaling factor (dpi/72) instead of just making it 1. I happen to like the way my program looked on my laptop and I wanted it to be proportionally the same across every device, that's why I mess with it.
Again though, I cant seem to get the font size to scale at all based on resolution, I'm not sure why a screen with a lower res (same dpi as main laptop) and screen with a higher res (different dpi than my main laptop) both require the same font size but my main laptop requires a different one.
My problem was this: I was calculating the font based on the screen window, not the full screen size. This is I was getting incorrect sizing.

Transparency bug in PhotoImage

I have two pieces of code, both of them should create test.png containing a black square. The first one does it, but the second returns a transparent square. The difference between them is that the first one has a clear stripe at the left and the second does not.
The first example:
root = Tk()
image = PhotoImage(width = 50, height = 50)
for x in range(1, 50):
for y in range(50):
pixel(image, (x,y), (0,0,0))
image.write('test.png', format='png')
The second example:
root = Tk()
image = PhotoImage(width = 50, height = 50)
for x in range(50):
for y in range(50):
pixel(image, (x,y), (0,0,0))
image.write('test.png', format='png')
I also import tkinter and use function pixel(), which has this code:
def pixel(image, pos, color):
"""Place pixel at pos=(x,y) on image, with color=(r,g,b)."""
r,g,b = color
x,y = pos
image.put("#%02x%02x%02x" % (r,g,b), (x, y))
To make it short: Tkinter's PhotoImage class can't really save PNGs. It does only support GIF, PGM and PPM. You may have noticed that the preview image is correctly colored, but when you open the file, it's blank.
To save PNG images, you have to use the Python Imaging Library or, for Python 3, Pillow.
With this, the image creation is even easier:
from PIL import Image
image = Image.new("RGB", (50, 50), (0,0,0))
image.save('test.png', format='PNG')
If you need, you can convert it to PIL's ImageTk.PhotoImage object that can be used in Tkinter.

Transparency in Tkinter PhotoImage

In my simple game I'm creating I currently have placeholder rectangle objects as graphics. I'm trying to replace them with sprites, but as I understand it Tkinter doesn't have support for PNGs or alpha transparency. I am using Python 3.3, which doesn't work with PIL (and since it is a school project, I am solely trying to use Tkinter as the only external library). Is there a way to use the alpha channel with the supported file formats so that I can have multiple layers of tiles? I just want to filter out the white pixels.
I was able to use an image with transparency. I understand your wish to avoid use of PIL, but the following code works and demonstrates that Tkinter will support formats with transparency.
from Tkinter import Tk, Canvas
import PIL
root = Tk()
tkimg = PIL.ImageTk.PhotoImage('cat1-a.gif')
canvas = Canvas(root, height=600, width=600)
canvas.grid()
def stamp(event):
canvas.create_image(event.x, event.y, image=tkimg)
canvas.bind('<ButtonPress-1>', stamp)
root.mainloop()
To make the white pixels transparent (I am assuming that white means #ffffff) you could use this function below or something like it. This does not require PIL. It has worked for me for pngs, but also will work for gif.
First, make a new blank image the same size as your image.
Second, copy pixel by pixel to the new image (unless the pixel is white).
Set your original image to the new image.
Here is an example of the function being used:
from tkinter import *
def makeTransparent(img, colorToMakeTransparentInHexFormat):
newPhotoImage = PhotoImage(width=img.width(), height=img.height())
for x in range(img.width()):
for y in range(img.height()):
rgb = '#%02x%02x%02x' % img.get(x, y)
if rgb != colorToMakeTransparentInHexFormat:
newPhotoImage.put(rgb, (x, y))
return newPhotoImage
root = Tk()
mycanvas = Canvas(root, width=200, height=200,bg="orange")
mycanvas.pack()
myphotoImage = PhotoImage(file="whitecar.gif")
#set your image to the image returned by the function
myphotoImage = makeTransparent(myphotoImage, "#ffffff")
canvasImage = mycanvas.create_image(100, 100, image=myphotoImage, anchor=CENTER)
root.mainloop()
Here is an example of a white car with a white background:
Here is an example of that car on the canvas using the example program:
So I hope I have answered your question.
I did not use PIL. nothing but the tkinter module.
I only used gif, not png as you asked.
Wherever white is, will now be transparent.
Note:
For whatever reason, processing transparency multiple times with the above function can result in viewing errors in tkinter. Below is a way to remove multiple colors by using a color switching function:
Here is a car:
Here is another function to switch colors, which can be implemented before making a color transparent.
def switchColors(img, currentColor,futureColor):
newPhotoImage = PhotoImage(width=img.width(), height=img.height())
for x in range(img.width()):
for y in range(img.height()):
rgb = '#%02x%02x%02x' % img.get(x, y)
if rgb == currentColor:
newPhotoImage.put(futureColor, (x, y))
else:
newPhotoImage.put(rgb, (x, y))
return newPhotoImage
Here it is in use
root = Tk()
mycanvas = Canvas(root, width=200, height=200,bg="orange")
mycanvas.pack()
myphotoImage = PhotoImage(file="car.png")
myphotoImage = switchColors(myphotoImage,"#db0000","#ffffff") #switch red to white
myphotoImage = switchColors(myphotoImage,"#d9d9d9","#ffffff") #switch greybackground to white
myphotoImage = switchColors(myphotoImage,"#6d6d6d","#ffffff") #switch windshield grey to white
myphotoImage = makeTransparent(myphotoImage,"#ffffff") #make white transparent
canvasImage = mycanvas.create_image(100, 100, image=myphotoImage, anchor=CENTER)
root.mainloop()
And here is the result of that process:
Here is a reference to a similar problem:
How to rotate an image on a canvas without using PIL?
There is a way to use PIL with Python 3 using non-official versions of PIL
Go to http://www.lfd.uci.edu/~gohlke/pythonlibs/ to download it.

Categories

Resources