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.
Related
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 want to make something like this python.
I have the image in background and write text with transparent fill, so that image shows up.
Here's one way I found to do it using the Image.composite() function which is documented here and here.
The approach used is described (very) tersely in this answer to the question Is it possible to mask an image in Python Imaging Library (PIL)? by #Mark Ransom…the following is just an illustration of applying it to accomplish what you want do.
from PIL import Image, ImageDraw, ImageFont
BACKGROUND_IMAGE_FILENAME = 'cookie_cutter_background_cropped.png'
RESULT_IMAGE_FILENAME = 'cookie_cutter_text_result.png'
THE_TEXT = 'LOADED'
FONT_NAME = 'arialbd.ttf' # Arial Bold
# Read the background image and convert to an RGB image with Alpha.
with open(BACKGROUND_IMAGE_FILENAME, 'rb') as file:
bgr_img = Image.open(file)
bgr_img = bgr_img.convert('RGBA') # Give iamge an alpha channel.
bgr_img_width, bgr_img_height = bgr_img.size
cx, cy = bgr_img_width//2, bgr_img_height//2 # Center of image.
# Create a transparent foreground to be result of non-text areas.
fgr_img = Image.new('RGBA', bgr_img.size, color=(0, 0, 0, 0))
font_size = bgr_img_width//len(THE_TEXT)
font = ImageFont.truetype(FONT_NAME, font_size)
txt_width, txt_height = font.getsize(THE_TEXT) # Size of text w/font if rendered.
tx, ty = cx - txt_width//2, cy - txt_height//2 # Center of text.
mask_img = Image.new('L', bgr_img.size, color=255)
mask_img_draw = ImageDraw.Draw(mask_img)
mask_img_draw.text((tx, ty), THE_TEXT, fill=0, font=font, align='center')
res_img = Image.composite(fgr_img, bgr_img, mask_img)
res_img.save(RESULT_IMAGE_FILENAME)
res_img.show()
Which, using the following BACKGROUND_IMAGE:
produced the image shown below, which is it being viewed in Photoshop so the transparent background it has would be discernible (not to scale):
Here's an enlargement, showing the smoothly rendered edges of the characters:
I am creating a program which must change the color of individual pixels in a pyglet window. I am unable to find any way to do this in the docs. Is there a way to do this?
For funsies, I'll add another answer that is more along the lines of what you might need. Because the window itself will be whatever "clear" color buffer you decide via:
window = pyglet.window.Window(width=width, height=height)
pyglet.gl.glClearColor(0.5,0,0,1) # Note that these are values 0.0 - 1.0 and not (0-255).
So changing the background is virtually impossible because it's "nothing".
You can however draw pixels on the background via the .draw() function.
import pyglet
from random import randint
width, height = 500, 500
window = pyglet.window.Window(width=width, height=height)
#window.event
def on_draw():
window.clear()
for i in range(10):
x = randint(0,width)
y = randint(0,height)
pyglet.graphics.draw(1, pyglet.gl.GL_POINTS,
('v2i', (x, y)),
('c3B', (255, 255, 255))
)
pyglet.app.run()
This will create 10 randomly placed white dots on the background.
To add anything above that simply place your .blit() or .draw() features after the pyglet.graphics.draw() line.
You could use the magic function SolidColorImagePattern and modify the data you need.
R,G,B,A = 255,255,255,255
pyglet.image.SolidColorImagePattern((R,G,B,A).create_image(width,height)
This is a .blit():able image. It's white, and probably not what you want.
So we'll do some more wizardry and swap out all the pixels for random ones (War of the ants):
import pyglet
from random import randint
width, height = 500, 500
window = pyglet.window.Window(width=width, height=height)
image = pyglet.image.SolidColorImagePattern((255,255,255,255)).create_image(width, height)
data = image.get_image_data().get_data('RGB', width*3)
new_image = b''
for i in range(0, len(data), 3):
pixel = bytes([randint(0,255)]) + bytes([randint(0,255)]) + bytes([randint(0,255)])
new_image += pixel
image.set_data('RGB', width*3, new_image)
#window.event
def on_draw():
window.clear()
image.blit(0, 0)
pyglet.app.run()
For educational purposes, I'll break it down into easier chunks.
image = pyglet.image.SolidColorImagePattern((255,255,255,255)).create_image(width, height)
Creates a solid white image, as mentioned. It's width and height matches the window-size.
We then grab the image data:
data = image.get_image_data().get_data('RGB', width*3)
This bytes string will contain width*height*<format>, meaning a 20x20 image will be 1200 bytes big because RGB takes up 3 bytes per pixel.
new_image = b''
for i in range(0, len(data), 3):
pixel = bytes([randint(0,255)]) + bytes([randint(0,255)]) + bytes([randint(0,255)])
new_image += pixel
This whole block loops over all the pixels (len(data) is just a convenience thing, you could do range(0, width*height*3, 3) as well, but meh.
The pixel contists of 3 randint(255) bytes objects combined into one string like so:
pixel = b'xffxffxff'
That's also the reason for why we step 3 in our range(0, len(data), 3). Because one pixel is 3 bytes "wide".
Once we've generated all the pixels (for some reason the bytes object image can't be modified.. I could swear I've modified bytes "strings" before.. I'm tired tho so that's probably a utopian dream or something.
Anyhow, once all that sweet image building is done, we give the image object it's new data by doing:
image.set_data('RGB', width*3, new_image)
And that's it. Easy as butter in sunshine on a -45 degree winter day.
Docs:
https://pyglet.readthedocs.io/en/pyglet-1.2-maintenance/programming_guide/quickstart.html
https://github.com/Torxed/PygletGui/blob/master/gui_classes_generic.py
https://pythonhosted.org/pyglet/api/pyglet.image.ImageData-class.html#get_image_data
https://pythonhosted.org/pyglet/api/pyglet.image.ImageData-class.html#set_data
You can also opt in to get a region, and just modify a region.. But I'll leave the tinkering up to you :)
You can blit pixels into background 'image'. You can look at this Stack Overflow question.
If you mean background color, I can help. There is one option that I know of, the pyglet.gl.glClearColor function.
for example,:
import pyglet
from pyglet.gl import glClearColor
win = pyglet.window.Window(600, 600, caption = "test")
glClearColor(255, 255, 255, 1.0) # red, green, blue, and alpha(transparency)
def on_draw():
win.clear()
That will create a window with a white background(as opposed to the default, black)
I want to create a simple Python script that let's me create an image file and place a word dead-center on that canvas. However, while I can get the word to align horizontally, I just can't get it to the center vertically. I am on MacOS btw. I have tried this so far:
import os
from PIL import Image, ImageDraw, ImageFont
font_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'fonts')
font = ImageFont.truetype(os.path.join(font_path, "Verdana.ttf"), 80)
W, H = (1200,200)
msg = "hola"
im = Image.new("RGB",(W,H),(255,255,255))
draw = ImageDraw.Draw(im)
w, h = draw.textsize(msg, font)
draw.text(((W-w)/2,(H-h)/2), msg, font=font, fill="black")
im.save("output_script.png", "PNG")
I already considered the font in the textsize calculation. But the word still appears roughly 5-10% below the middle. Any ideas?
textsize seems to return the size of the actual text, not that of the line. So it's smaller for ooo than for XXX!
I think you should just use 80 — the size you gave PIL — for the height, although I can't guarantee that it's correct.
ImageFont.getmask(txt) returns the alpha mask bitmap with which text can be centered in the image.
import Image, ImageFont
img = Image.new('L', (32, 32), color=0)
img_w, img_h = img.size
font = ImageFont.truetype('/path/to/font.ttf', 16)
mask = font.getmask('hola') # your text here
mask_w, mask_h = mask.size
d = Image.core.draw(img.im, 0)
d.draw_bitmap(((img_w - mask_w)/2, (img_h - mask_h)/2), mask, 255) # last arg is pixel intensity of text
img.save('test.png')
I have a small background image like this:
it's a smaller than my image size, so I need to draw it repeatedly. (think about background-repeat in css)
I searched a lot and can't find a solution to this....thanks a lot.
Based on the code linked to by Marcin, this will tile a background image on a larger one:
from PIL import Image
# Opens an image
bg = Image.open("NOAHB.png")
# The width and height of the background tile
bg_w, bg_h = bg.size
# Creates a new empty image, RGB mode, and size 1000 by 1000
new_im = Image.new('RGB', (1000,1000))
# The width and height of the new image
w, h = new_im.size
# Iterate through a grid, to place the background tile
for i in xrange(0, w, bg_w):
for j in xrange(0, h, bg_h):
# Change brightness of the images, just to emphasise they are unique copies
bg = Image.eval(bg, lambda x: x+(i+j)/1000)
#paste the image at location i, j:
new_im.paste(bg, (i, j))
new_im.show()
Produces this:
Or removing the Image.eval() line: