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 :)
Related
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.
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.
I am trying to input an image (image1) and flip it horizontally and then save to a file (image2). This works but not the way I want it to
currently this code gives me a flipped image but it just shows the bottom right quarter of the image, so it is the wrong size. Am I overwriting something somewhere? I just want the code to flip the image horizontally and show the whole picture flipped. Where did I go wrong?
and I cannot just use a mirror function or reverse function, I need to write an algorithm
I get the correct window size but the incorrect image size
def Flip(image1, image2):
img = graphics.Image(graphics.Point(0, 0), image1)
X, Y = img.getWidth(), img.getHeight()
for y in range(Y):
for x in range(X):
r, g, b = img.getPixel(x,y)
color = graphics.color_rgb(r, g, b)
img.setPixel(X-x, y, color)
win = graphics.GraphWin(img, img.getWidth(), img.getHeight())
img.draw(win)
img.save(image2)
I think your problem is in this line:
win = graphics.GraphWin(img, img.getWidth(), img.getHeight())
The first argument to the GraphWin constructor is supposed to be the title, but you are instead giving it an Image object. It makes me believe that maybe the width and height you are supplying are then being ignored. The default width and height for GraphWin is 200 x 200, so depending on the size of your image, that may be why only part of it is being drawn.
Try something like this:
win = graphics.GraphWin("Flipping an Image", img.getWidth(), img.getHeight())
Another problem is that your anchor point for the image is wrong. According to the docs, the anchor point is where the center of the image will be rendered (thus at 0,0 you are only seeing the bottom right quadrant of the picture). Here is a possible solution if you don't know what the size of the image is at the time of creation:
img = graphics.Image(graphics.Point(0, 0), image1)
img.move(img.getWidth() / 2, img.getHeight() / 2)
You are editing your source image. It would be
better to create an image copy and set those pixels instead:
create a new image for editing:
img_new = img
Assign the pixel values to that:
img_new.setPixel(X-x, y, color)
And draw that instead:
win = graphics.GraphWin(img_new, img_new.getWidth(), img_new.getHeight())
img_new.draw(win)
img_new.save(image2)
This will also check that your ranges are correct. if they are not, you will see both flipped and unflipped portions in the final image, showing which portions are outside of your ranges.
If you're not opposed to using an external library, I'd recommend the Python Imaging Library. In particular, the ImageOps module has a mirror function that should do exactly what you want.
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.
I am drawing a table using tKinter. I was wondering if there was a way to export the table as an image.
for r in range(numberOfRows):
for c in range(numberOfColumns):
l = tk.Label(root, text=someText, relief="solid", height=5, width=10, bg="white")
l.grid(row=r, column=c)
Thanks.
You can always go the draw-it-yourself way. There are several modules out there that allow you draw an image. Take the coordinates of your table, column width and so on and draw it into the image.
You can take a screen shot of it.
import PIL.ImageGrab as ImageGrab
screenshot = ImageGrab.grab().load()
rgb = screenshot[0, 0]
print rgb
rgb would return the color value of any pixel on screen.