Additive color with Tkinter - python

I'm trying to reproduce additive color with Tkinter.
My function :
def synthese(red,green,blue):
win2 = Tk()
win2.title("ADDITIVE COLOR")
win2.geometry("500x500")
win2.resizable(0,0)
hred = "#%02x%02x%02x" % (red, 0, 0) #RGB to Hexadecimal
hgreen = "#%02x%02x%02x" % (0, green, 0)
hblue = "#%02x%02x%02x" % (0, 0, blue)
r = 50
Width = 450
Height = 450
win3 = Canvas(win2, width = Width, height = Height, bg = 'white')
win3.pack(padx=5,pady=5)
win3.create_oval(10,150,300,440, outline=hred, fill=hred)
win3.create_oval(150,150,440,440, outline=hblue, fill=hblue)
win3.create_oval(75,10,375,300, outline=hgreen, fill=hgreen)
win2.mainloop()
What I get :
And what I would like :
It is possible to merge the colors or I need to find the collision zones?

You can use ImageChops to add images.
So you can do something like this:
from Tkinter import Tk, Canvas, Label
import ImageDraw, ImageChops, Image, ImageTk
image1 = Image.new("RGBA", (500, 500), color=0)
image2 = Image.new("RGBA", (500, 500), color=0)
image3 = Image.new("RGBA", (500, 500), color=0)
draw1 = ImageDraw.Draw(image1)
draw2 = ImageDraw.Draw(image2)
draw3 = ImageDraw.Draw(image3)
draw1.ellipse([10, 150, 300, 440], (128,0,0))
draw2.ellipse([150, 150, 440, 440], (0,0,128))
draw3.ellipse([75, 10, 375, 300], (0,128,0))
out = ImageChops.add(image1,image2,0.5)
out = ImageChops.add(out,image3,0.5)
win2 = Tk()
photo = ImageTk.PhotoImage(out)
label = Label(win2, image=photo)
label.pack()
win2.mainloop()
output:

Here's a way to draw additive RGB circles using Numpy. It converts the Numpy data to a Tkinter PhotoImage object using PIL (Pillow), and displays the results in a Tkinter Label. I use a black background because we're doing additive color mixing.
import numpy as np
from PIL import Image, ImageTk
import tkinter as tk
width, height = 400, 360
# Make RGB colors
red, grn, blu = np.eye(3, dtype=np.uint8) * 255
class GUI:
def __init__(self, width, height):
self.root = root = tk.Tk()
root.title('Circles')
root.geometry('%dx%d' % (width, height))
self.img_label = tk.Label(self.root)
self.img_label.pack(fill='both', expand=True)
gui = GUI(width, height)
# Increase the scale for smoother circles
scale = 4
width *= scale
height *= scale
screen = np.zeros((height, width, 3), dtype=np.uint8)
def show(fname=None):
img = Image.fromarray(screen, 'RGB')
img = img.resize((width // scale, height // scale), resample=Image.BILINEAR)
gui.photo = ImageTk.PhotoImage(image=img)
gui.img_label.config(image=gui.photo)
gui.root.update()
if fname is not None:
img.save(fname)
def disc(radius):
diameter = 2 * radius
yy, xx = np.mgrid[:diameter, :diameter] - radius
c = xx * xx + yy * yy < radius * radius
return c.reshape(diameter, diameter, 1)
def get_region(cx, cy, radius):
ylo = cy - radius
yhi = cy + radius
xlo = cx - radius
xhi = cx + radius
return screen[ylo:yhi, xlo:xhi]
radius = 120 * scale
circle = disc(radius)
cx = width // 2
cy = 130 * scale
region = get_region(cx, cy, radius)
region |= circle * red
show()
cy += 97 * scale
cx -= 56 * scale
region = get_region(cx, cy, radius)
region |= circle * grn
show()
cx += 112 * scale
region = get_region(cx, cy, radius)
region |= circle * blu
show('rgb.png')
gui.root.mainloop()
output

Using PIL you can create three grayscale layers, draw circles and use them to create expected circles but on black background.
If you use inverted layers then you get white background but with wrong circles.
With PIL you can even display it or save in file.
from PIL import Image, ImageDraw
def synthese(red=255, green=255, blue=255):
background = 0 # black
# layers in greyscale
layer_R = Image.new('L', (450, 450), background)
layer_G = Image.new('L', (450, 450), background)
layer_B = Image.new('L', (450, 450), background)
# draw circle on red layer
draw_R = ImageDraw.Draw(layer_R)
draw_R.ellipse((10,150,300,440), red)
# draw circle on green layer
draw_G = ImageDraw.Draw(layer_G)
draw_G.ellipse((150,150,440,440), green)
# draw circle on blue layer
draw_B = ImageDraw.Draw(layer_B)
draw_B.ellipse((75,10,375,300), blue)
#layer_R.show()
#layer_G.show()
#layer_B.show()
#layer_R.save('layer_r.png')
#layer_G.save('layer_g.png')
#layer_B.save('layer_b.png')
# create RGB image using greyscale layers
image_RGB = Image.merge('RGB', (layer_R, layer_G, layer_B))
# show it
image_RGB.show()
#image_RGB.save('rgb.png')
synthese(255, 255, 255)

Related

Draw circle with PIL (Old ones doesn't works)

I am trying to draw a circle with PIL, but I get an attribute error.
Current code is for square.
Part of code to draw:
youtube = Image.open(f"cache/thumb{videoid}.png")
image1 = changeImageSize(1280, 720, youtube)
image2 = image1.convert("RGBA")
background = image2.filter(filter=ImageFilter.BoxBlur(30))
enhancer = ImageEnhance.Brightness(background)
background = enhancer.enhance(0.6)
Xcenter = youtube.width / 2
Ycenter = youtube.height / 2
x1 = Xcenter - 250
y1 = Ycenter - 250
x2 = Xcenter + 250
y2 = Ycenter + 250
logo = youtube.crop((x1, y1, x2, y2))
logo.thumbnail((520, 520), Image.ANTIALIAS)
logo = ImageOps.expand(logo, border=15, fill="pink")
background.paste(logo, (50, 100))
draw = ImageDraw.Draw(background)
My whole code:
Code
You can use either ImageDraw.arc() or ImageDraw.ellipse.
from PIL import Image, ImageDraw
# Image size
W, H = 100, 100
# Bounding box points
X0 = int(W / 4)
X1 = int(X0 * 3)
Y0 = int(H / 4)
Y1 = int(X0 * 3)
# Bounding box
bbox = [X0, Y0, X1, Y1]
# Set up
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
# Draw a circle
draw.arc(bbox, 0, 360)
# Show the image
im.show()
Or:
# Draw a circle
draw.ellipse(bbox)

ImageDraw - adapt Font Size dynamically to text length

The font size has to dynamically adapt to different the text length, which can vary a lot.
Currently I have a ton of code, basically manually checking how long the incoming text is and manually giving a font size for that.
There has to be a better solution. I don't think I can use ImageDraw.textsize.
Has anyone a solution for this kinda problem?
I don't know other method then ImageDraw.textsize() in for-loop which checks it for different font sizes.
Now I found that in version 8.0.0 they add ImageDraw.textbbox() which gives better results because it calculates also top margin which can be use to better calcualte position.
But it still needs for-loop to check it for different font sizes.
Result with textsize:
Result with textbbox (center vertically):
It is my example which calculate font size and box size to use it directly to calculate
from PIL import Image, ImageDraw, ImageFont
width = 300
height = 100
text = "Hello World"
font_name = 'Ubuntu-M'
# --- create image for text ---
img = Image.new('RGB', (width, height), (255, 255, 255))
draw = ImageDraw.Draw(img)
# --- calculate font size, box ---
# default values at start
font_size = None # for font size
font = None # for object truetype with correct font size
box = None # for version 8.0.0
# test for different font sizes
for size in range(1, 500):
# create new font
new_font = ImageFont.truetype(font_name, size)
# calculate bbox for version 8.0.0
new_box = draw.textbbox((0, 0), text, new_font) # need 8.0.0
# `bbox` may have top/left margin so calculate real width/height
new_w = new_box[2] - new_box[0] # bottom-top
new_h = new_box[3] - new_box[1] # right-left
#print(size, '|', new_w, new_h, '|', new_box)
# if too big then exit with previous values
if new_w > width or new_h > height:
break
# set new current values as current values
font_size = size
font = new_font
box = new_box
w = new_w
h = new_h
# --- use it ---
print('font size:', font_size)
print('box:', box)
print('w <= width :', w, '<=', width)
print('h <= height:', h, '<=', height)
# calculate position (minus margins in box)
x = (width - w)//2 - box[0] # minus left margin
y = (height - h)//2 - box[3] # minus top margin
print('w,h (without margins):', w, h)
print('x,y (without margins):', x, y)
# draw it
draw.text((x, y), text, (0, 0, 0), font)
# display result
img.show()
img.save('result-textbbox.png', 'png')
EDIT:
The same as function
from PIL import Image, ImageDraw, ImageFont
def get_font(img, text, font_name, width, height):
# default values at start
font_size = None # for font size
font = None # for object truetype with correct font size
box = None # for version 8.0.0
# test for different font sizes
for size in range(1, 500):
# create new font
new_font = ImageFont.truetype(font_name, size)
# calculate bbox for version 8.0.0
new_box = draw.textbbox((0, 0), text, new_font) # need 8.0.0
# `bbox` may have top/left margin so calculate real width/height
new_w = new_box[2] - new_box[0] # bottom-top
new_h = new_box[3] - new_box[1] # right-left
#print(size, '|', new_w, new_h, '|', new_box)
# if too big then exit with previous values
if new_w > width or new_h > height:
break
# set new current values as current values
font_size = size
font = new_font
box = new_box
w = new_w
h = new_h
# calculate position (minus margins in box)
x = (width - w)//2 - box[0] # minus left margin
y = (height - h)//2 - box[1] # minus top margin
return font, font_size, box, w, h, x, y
# --- main ---
width = 300
height = 100
text = "World"
font_name = 'Ubuntu-M'
# --- create image for text ---
img = Image.new('RGB', (width, height), (200, 255, 255))
draw = ImageDraw.Draw(img)
# --- calculate font size, box ---
font, font_size, box, w, h, x, y = get_font(img, text, font_name, width, height)
# --- use it ---
print('font size:', font_size)
print('box:', box)
print('w <= width :', w, '<=', width)
print('h <= height:', h, '<=', height)
print('w,h (without margins):', w, h)
print('x,y (without margins):', x, y)
# draw it
draw.text((x, y), text, (0, 0, 0), font)
# display result
img.show()
img.save('result-textbbox.png', 'png')

Convert Current Image to Black and White

The goal is to convert the current image in GUI window to black and white
Below is my code:
def BlackAndWhite(self):
from images import Image
LoadAFile = self.inputText.getText()
CurrentImage = open(LoadAFile)
image = self.image = PhotoImage(file = LoadAFile)
image.draw()
BlackAndWhite(image)
image.draw()
self.imageLabel["image"] = self.image
blackPixel = (0,0,0)
whitePixel = (255,255,255)
for y in range(image.getHeight()):
for x in range(image.getWidth()):
(r,g,b) = image.getPixel(x,y)
average = (r+b+g) /3
if average < 128:
image.setPixel(x,y,blackPixel)
else:
image.setPixel(x,y, whitePixel)
I am getting this error message:
image.draw()
AttributeError: 'PhotoImage' object has no attribute 'draw'
Here's working code, you should be able to tweak it to work with your work:
from tkinter import Tk, Canvas, NW
from PIL import ImageTk, Image
root = Tk()
canvas = Canvas(root, width=1000, height=1000)
canvas.pack()
img = Image.open("PATH_TO_AN_IMAGE")
blackPixel = (0, 0, 0)
whitePixel = (255, 255, 255)
for y in range(img.height):
for x in range(img.width):
pixelVal = img.getpixel((x, y))
# Unpacking in this way in case the pixel contains more than R, G, B (ex: a png)
r, g, b = pixelVal[0:3]
average = (r + b + g) / 3
if average < 128:
img.putpixel((x, y), blackPixel)
else:
img.putpixel((x, y), whitePixel)
photoimage = ImageTk.PhotoImage(img)
canvas.create_image((20, 20), anchor=NW, image=photoimage, state="normal")
root.mainloop()

Merging a transparent image to another background

I have 2 images as below, both are in RGBA format
I want to overlay the object into a fixed region in the background. However, my codes produces a weird result:
Can someone have a look at my code to see where I was wrong. Thanks in advance
import numpy as np
import matplotlib.pyplot as plt
import cv2
import glob
from PIL import Image
bg = np.array(Image.open('background.png'))
obj = np.array(Image.open('object.png'))
#RESIZE BACKGROUND
scale_percent = 50 # percent of original size
width = int(bg.shape[1] * scale_percent / 100)
height = int(bg.shape[0] * scale_percent / 100)
dim = (width, height)
bg = resized = cv2.resize(bg, dim, interpolation = cv2.INTER_AREA)
#RESIZE OBJECT
scale_percent = 80 # percent of original size
width = int(obj.shape[1] * scale_percent / 100)
height = int(obj.shape[0] * scale_percent / 100)
dim = (width, height)
# resize image
obj = resized = cv2.resize(obj, dim, interpolation=cv2.INTER_AREA)
#create ROI
rows, cols, channels = obj.shape
bg_rows, bg_cols, bg_channels = bg.shape
roi = bg[bg_rows-rows:bg_rows:, bg_cols-cols:bg_cols:]
img2gray = cv2.cvtColor(obj, cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(img2gray, 220, 255, cv2.THRESH_BINARY_INV)
mask_inv = cv2.bitwise_not(mask)
img1_bg = cv2.bitwise_and(roi, roi, mask=mask_inv)
img2_fg = cv2.bitwise_and(obj, obj, mask=mask)
dst = cv2.add(img1_bg, img2_fg)
bg[bg_rows-rows:bg_rows:, bg_cols-cols:bg_cols:] = dst
Image.fromarray(bg)
You should use ImageDraw() function from the pillow library. It will solve your problem by giving the mode same as the 2nd image.

Pillow, how to put the text in the center of the image

I use the Pillow (PIL) 6.0 and add text in the image. And I want to put the text in the center of the image. Here is my code,
import os
import string
from PIL import Image
from PIL import ImageFont, ImageDraw, ImageOps
width, height = 100, 100
text = 'H'
font_size = 100
os.makedirs('./{}'.format(text), exist_ok=True)
img = Image.new("L", (width, height), color=0) # "L": (8-bit pixels, black and white)
font = ImageFont.truetype("arial.ttf", font_size)
draw = ImageDraw.Draw(img)
w, h = draw.textsize(text, font=font)
draw.text(((width-w)/2, (height-h)/2), text=text, fill='white', font=font)
img.save('H.png')
Here is the output:
Question:
The text is in the center horizontally, but not in the center vertically. How can I put it in the center horizontally and vertically?
Text always have some added space around characters, e.g. if we create a box that is the exact size reported for your 'H'
img = Image.new("L", (width, height), color=0) # "L": (8-bit pixels, black and white)
font = ImageFont.truetype("arial.ttf", font_size)
draw = ImageDraw.Draw(img)
w, h = draw.textsize(text, font=font)
# draw.text(((width-w)/2, (height-h)/2), text=text, fill='white', font=font)
# img.save('H.png')
img2 = Image.new("L", (w, h), color=0) # "L": (8-bit pixels, black and white)
draw2 = ImageDraw.Draw(img2)
draw2.text((0, 0)), text=text, fill='white', font=font)
img2.save('H.png')
gives the bounding box:
Knowing that line height is normally ~20% larger than the glyphs/characters (+ some trial and error), and we can figure out the extent of the extra space. (The extra space for width is equally distributed so not interesting for centering).
draw2.text((0, 0 - int(h*0.21)), text=text, fill='white', font=font)
which moves the 'H' to the top:
Plugging this back into your original code:
img = Image.new("L", (width, height), color=0) # "L": (8-bit pixels, black and white)
font = ImageFont.truetype("arial.ttf", font_size)
draw = ImageDraw.Draw(img)
w, h = draw.textsize(text, font=font)
h += int(h*0.21)
draw.text(((width-w)/2, (height-h)/2), text=text, fill='white', font=font)
img.save('H.png')
gives:
The 0.21 factor usually works well for a large range of font sizes for the same font. E.g. just plugging in font size 30:
Use of anchors can help with this
import os
import string
from PIL import Image
from PIL import ImageFont, ImageDraw, ImageOps
width, height = 100, 100
text = 'H'
font_size = 100
os.makedirs('./{}'.format(text), exist_ok=True)
img = Image.new("L", (width, height), color=0) # "L": (8-bit pixels, black and white)
font = ImageFont.truetype("arial.ttf", font_size)
draw = ImageDraw.Draw(img)
draw.text(((width)/2, (height)/2), text=text, fill='white', font=font, anchor="mm", align='center')
img.save('H.png')
It works fine without w and h
P.S.: I've tested it, and it can work well with non-English characters also

Categories

Resources