I need to create semi-transparent 1x1 images which I can zoom on the canvas.
Currently, I have a directory full of prerendered transparent images for each possible alpha value, however I would like to avoid that.
Also, I cannot use anything outside of the standard library - this is for a school project and the computers don't have admin access (cannot use PIP).
Using PhotoImage, you can create a blank image and place pixels in the desired coordinates, however when specifying the color used, I can't figure out how to pass in alpha values. The Tcl Tk documentation (https://www.tcl.tk/man/tcl8.7/TkCmd/photo.htm#M53) states you can specify alpha values in colors.
From my understanding, a red color with an alpha value of 0.5 would look like this: #ff0000#0.5
Here's the code I have right now:
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, width = 500, height = 500, bg = "black", highlightthickness = 0)
canvas.pack()
image = tk.PhotoImage(width = 1, height = 1)
image.put("#ff0000#0.5", (0, 0))
image = image.zoom(30)
canvas.create_image(
0,
0,
image = image,
anchor = "nw"
)
root.mainloop()
Can I place transparent pixels/regions using the put method in Tkinter's PhotoImage? I cannot use PIL.
transparent = "#000000" # color that will be considered as "transparent"
if color != transparent:
image.put(color, (0, 0))
Related
I'm trying to make a program to display a single color, fullscreen. The idea is to use it on a large screen to create environment in the room, the screen changing from color to color after certain time.
Displaying the color fullscreen is not a problem but how can I change said color smoothly?
For the fullscreen display I've used a Tkinter window with the same size as the screen: imagesprite = canvas.create_image(w/2, h/2, image=image) where image is a certain color. But to change the color I need to destroy the window using root.after(2000, root.destroy) and then create a new one. This is not smooth as the desktop can be seen for a brief moment.
How can I change the image displayed inside a Tkinter window on the go, or, how can I close one window and open another one smoothly?
An option you have is instead of using an image is to have a background for the Canvas object. Here is the minimum code to have a single colour background.
from tkinter import Tk, Canvas
root = Tk()
root.attributes("-fullscreen",True)#Makes the window fullscreen
canvas = Canvas(root, width=root.winfo_width(),height=root.winfo_height(), background="red") #Makes a canvas with a red coloured background
#The width and height of the Canvas are taken from the root object
canvas.pack()
root.mainloop()
From here, instead of deleting the window constantly, it is possible to just change the attributes of Tkinter widgets. This is done using the config method.
canvas.config(background="green")
A great thing about tkinter is that you can give it a hex code for the colour and it will draw use that. It needs to be in a string formatted like this:
"#RRGGBB" where each group is a hexadecimal number from 0 to FF.
With this in mind, you can increase the hexadecimal number each frame or however many frames you want between two colours. To have a good transition, you may want to use Hue,Saturation,Value (HSV) colours, and only change the Hue value.
You can store the HSV in a list:
hsv = [0,0.7,0.7]
To convert, you first want to convert to 0 to 255 RGB and then to Hexadecimal.
import colorsys
rgb = colorsys.hsv_to_rgb(*hsv) #Uses list unpacking to give it as arguments
Next, you use the rgb and turn it into Hexcode form.
def getHexCode(rgb):
r = hex(int(rgb[0]*255))[2:] #converts to hexadecimal
#With the hex() function, it returns a number in "0xFE" format (0x representing hex).
#To ignore this, we can take the substring using [2:]
if len(r) < 2: #If the value is a 1-digit number, then we want to add a zero at the front for hexcode form
r = "0"+r
g = hex(int(rgb[1]*255))[2:]
if len(g) < 2:
g = "0"+g
b = hex(int(rgb[2]*255))[2:]
if len(b) < 2:
b = "0"+b
return "#" + r + g + b
Finally, we actually call the change method.
changeSpeed = 200
def changeColor():
rgb = colorsys.hsv_to_rgb(*hsv)
hexCode = getHexCode(rgb)
canvas.config(background = hexCode)
hsv[0]+=0.01
root.after(changeSpeed,changeColor)
root.after(changeSpeed, changeColor)
(EDITED)
Two things that were previously a problem were the root.winfo_width() and root.winfo_height(), as well as the fullscreen giving a border.
To solve the first problem, we have to somehow update the root object, since by default it's 1x1. What we can do for that is make the Canvas object and then update it. It looks like this:
canvas = Canvas(root, width=100,height=100, background="white",highlightthickness=0) #Makes a canvas with a white coloured background
canvas.pack()
canvas.update()
canvas.config(width = root.winfo_width(), height = root.winfo_height())
The second problem is also solved by making the canvas object with a specific attribute, highlightthickness=0. If you notice, the canvas object initialization is now:
canvas = Canvas(root, width=100,height=100, background="white",highlightthickness=0)
Another thing that I thought was useful is if a button closes the program. I bound the "Escape" key to the closing using the following:
def quit(event):
root.destroy()
root.bind("<Escape>", quit)
As a full program, it looks like this:
import colorsys
from tkinter import Tk, Canvas
hsv = [0,1,0.8]
changeSpeed = 200
root = Tk()
root.attributes("-fullscreen",True)
canvas = Canvas(root, width=100,height=100, background="white",highlightthickness=0) #Makes a canvas with a white coloured background
canvas.pack()
canvas.update()
canvas.config(width = root.winfo_width(), height = root.winfo_height())
def getHexCode(rgb):
r = hex(int(rgb[0]*255))[2:]
if len(r) < 2:
r = "0"+r
g = hex(int(rgb[1]*255))[2:]
if len(g) < 2:
g = "0"+g
b = hex(int(rgb[2]*255))[2:]
if len(b) < 2:
b = "0"+b
return "#" + r + g + b
def changeColor():
rgb = colorsys.hsv_to_rgb(*hsv)
hexCode = getHexCode(rgb)
canvas.config(background = hexCode)
hsv[0]+=0.01
root.after(changeSpeed,changeColor)
def quit(event):
root.destroy()
root.after(changeSpeed, changeColor)
root.bind("<Escape>", quit)
root.mainloop()
Some variables you can change in this is the changeSpeed, the original hsv list, and the 0.01 that is added to the hue each increase
I have made a function that change the black colour of an image in png (a black icon with transparent background) to the colour of the accent theme in windows.
I use this function to made all my icons match the colour interface of my window, but with this function, I need to manually call the function to the image, and then, pick the image and define as a PhotoImage to put it as a Label in tkinter.
The goal of this, is to make a method to define the main png (the black icon) as a dynamic colour image that can be used as a PhotoImage, but even with the Image.TkPhotoImage method of the PIL library, I haven't done it.
The code of my function is this:
def changeImageColorToAccentColor(imagename):
imagename = str(imagename)
accent = str(getAccentColor().lstrip('#'))
rcolor = int(str(accent[0:2]),16)
gcolor = int(str(accent[2:4]),16)
bcolor = int(str(accent[4:6]),16)
im = Image.open(str(imagename))
im = im.convert('RGBA')
data = np.array(im) # "data" is a height x width x 4 numpy array
red, green, blue, alpha = data.T # Temporarily unpack the bands for readability
# Replace white with red... (leaves alpha values alone...)
white_areas = (red == 0) & (blue == 0) & (green == 0) & (alpha == 255)
data[..., :-1][white_areas.T] = (rcolor, gcolor, bcolor) # Transpose back needed
im2 = Image.fromarray(data)
image1 = ImageTk.PhotoImage(im2)
return(image1)
And then, I define my Label in tkinter giving the image option the function that returns the PhotoImage object.
icon = Label(image=changeImageColorToAccentColor('file.png'))
But it doesn't work for me, so, if this proof doesn't work, I won't be able to make the object.
You need ta save a reference to the PhotoImage object. If it gets garbage collected the image won't show. Passing it ti the Label as image parameter does not save a reference automatically. If you do
im = changeImageColorToAccentColor('image2.png')
icon = Label(root, image=im)
the PhotoImage object is saved as im and the image will show.
I have a ttk.Separator widget and I would like it to be all black. I used ttk.Style() as follows
import Tkinter as TK
import ttk
self.line_style = ttk.Style()
self.line_style.configure("Line.TSeparator", background="#000000")
self.line = ttk.Separator(self.tk, orient=TK.VERTICAL, style="Line.TSeparator")
self.line.place(x = 1250,y = 0, height = self.tk.winfo_screenheight(), width = 8)
And the separator is black, 8 pixels wide, but it has a 1 pixel white line on the left side. Plase, do you know how could I get rid of it?
Unfortunately you can't get rid of the 1 pixel white line, the only available option to configure for a ttk Separator is the background option.
You can see this by finding the layout of the Separator and then listing all of its configuration options as follows
s = ttk.Style()
print(s.layout('TSeparator')) # [('Separator.separator', {'sticky': 'nswe'})]
print(s.element_options('Separator.separator')) # ('-orient', '-background')
If you set the background color to something other than black you'd see that this 1 pixel border is not always white but somehow related to chosen background color, either a lighter or darker shade.
The most viable workaround is to simply insert a styled Frame (ttk or regular tkinter) of the desired width instead of using a separator
I know that this is an old post but I had the same issue and figured out a work around. When I create a 1 pixel high tkinter.Frame and stretch it out along the x-axis, I get something that looks like a ttk.Separator. Example:
import tkinter as tk
root = tk.Tk()
separator = tk.Frame(root, bg="blue", height=1, bd=0)
separator.pack(fill="x")
root.mainloop()
The colour of the separator is controlled by its bg keyword.
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.
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.