PIL Drawing a semi-transparent square overlay on image - python

from PIL import Image
from PIL import ImageDraw
from io import BytesIO
from urllib.request import urlopen
url = "https://i.ytimg.com/vi/W4qijIdAPZA/maxresdefault.jpg"
file = BytesIO(urlopen(url).read())
img = Image.open(file)
img = img.convert("RGBA")
draw = ImageDraw.Draw(img, "RGBA")
draw.rectangle(((0, 00), (img.size[0], img.size[1])), fill=(0,0,0,127))
img.save('dark-cat.jpg')
This is giving me a giant black square. I want it to be a semi transparent black square with a cat. Any Ideas?

Sorry, the comment I made about it being a bug was incorrect, so...
You can do it by creating a temporary image and using Image.alpha_composite() as shown in the code below. Note that it supports semi-transparent squares other than black.
from PIL import Image, ImageDraw
from io import BytesIO
from urllib.request import urlopen
TINT_COLOR = (0, 0, 0) # Black
TRANSPARENCY = .25 # Degree of transparency, 0-100%
OPACITY = int(255 * TRANSPARENCY)
url = "https://i.ytimg.com/vi/W4qijIdAPZA/maxresdefault.jpg"
with BytesIO(urlopen(url).read()) as file:
img = Image.open(file)
img = img.convert("RGBA")
# Determine extent of the largest possible square centered on the image.
# and the image's shorter dimension.
if img.size[0] > img.size[1]:
shorter = img.size[1]
llx, lly = (img.size[0]-img.size[1]) // 2 , 0
else:
shorter = img.size[0]
llx, lly = 0, (img.size[1]-img.size[0]) // 2
# Calculate upper point + 1 because second point needs to be just outside the
# drawn rectangle when drawing rectangles.
urx, ury = llx+shorter+1, lly+shorter+1
# Make a blank image the same size as the image for the rectangle, initialized
# to a fully transparent (0% opaque) version of the tint color, then draw a
# semi-transparent version of the square on it.
overlay = Image.new('RGBA', img.size, TINT_COLOR+(0,))
draw = ImageDraw.Draw(overlay) # Create a context for drawing things on it.
draw.rectangle(((llx, lly), (urx, ury)), fill=TINT_COLOR+(OPACITY,))
# Alpha composite these two images together to obtain the desired result.
img = Image.alpha_composite(img, overlay)
img = img.convert("RGB") # Remove alpha for saving in jpg format.
img.save('dark-cat.jpg')
img.show()
Here's the result of applying it to your test image:

Given that I keep coming back to this issue whenever I want to draw a transparent rectangle with PIL, I decided to give an update.
Your code is pretty much working for me if I just change one thing: Save the image in the PNG format instead of JPEG.
So when I'm running
from io import BytesIO
from urllib.request import urlopen
from PIL import Image
from PIL import ImageDraw
url = "https://i.ytimg.com/vi/W4qijIdAPZA/maxresdefault.jpg"
file = BytesIO(urlopen(url).read())
img = Image.open(file)
draw = ImageDraw.Draw(img, "RGBA")
draw.rectangle(((280, 10), (1010, 706)), fill=(200, 100, 0, 127))
draw.rectangle(((280, 10), (1010, 706)), outline=(0, 0, 0, 127), width=3)
img.save('orange-cat.png')
I get this wonderful image:

If you just want to dim the entire image, there's a simpler way:
img = Image.eval(img, lambda x: x/2)

Related

How to generate an alpha image with a color range with PIL?

I have a grayscale image and I want to create an alpha layer based on a range of pixel values. I want to know how can I create a fall-off function to generate such image.
The original image is the following:
I can use the color range in photoshop to select the shadows with fuzziness of 20%
And the resultant alpha channel is the following:
With fuzziness of 100%:
How can I generate such alpha channels in python with PIL?
I thought that maybe a subtract, but it does not generates a
The code to generate the image with Numpy and PIL:
from PIL import Image
import numpy as np
img = np.arange(0,256, 0.1).astype(np.uint8)
img = np.reshape(img, (img.shape[0], 1))
img = np.repeat((img), 500, axis=1)
img = Image.fromarray(img.T)
I tried to create a fall-off function from the distance of the pixel values but it does not have the same gradient. Maybe there is a different way?
def gauss_falloff(distance, c=0.2, alpha=255):
new_value = alpha * np.exp(-1 * ((distance) ** 2) / (c**2))
new_value = new_value.clip(0,255)
return new_value.astype(np.uint8)
test = img.T / 255
test = np.abs(test - pixel)
test = gauss_falloff(test, c=0.2, alpha=255)
test = Image.fromarray(test)
With my code:
Here's how you could do that
from PIL import Image, ImageDraw
# Create a new image with a transparent background
width, height = 300, 300
image = Image.new('RGBA', (width, height), (255, 255, 255, 0))
# Create a drawing context for the image
draw = ImageDraw.Draw(image)
# Set the starting and ending colors for the gradient
start_color = (255, 0, 0)
end_color = (0, 0, 255)
# Draw a gradient line with the specified color range
for x in range(width):
color = tuple(int(start_color[i] + (end_color[i] - start_color[i]) * x / width)
for i in range(3))
draw.line((x, 0, x, height), fill=color)
# Save the image
image.save('gradient.png')
This code creates a new image with a transparent background and a drawing context for that image. Then it draws a gradient line on the image with the specified color range. Finally, it saves the image as a PNG file.
Note: The Python Imaging Library (PIL) has been replaced by the Pillow library, which is a fork of PIL. If you are using Pillow, you can use the same code as above, but you need to import the Image and ImageDraw modules from the Pillow package instead of the PIL package.

How would I use PIL to "extend" an image, and then draw a black rectangle with text on it?

Basically, what I want to do with Pillow is:
I want to get an image, and then extend the size of the image from the bottom so I'm able to fit a black rectangle with a four digit code on it. How would I do this? I tried to, but my text ended up being, for some reason, extremely small and unreadable and my rectangle wasn't perfect.
If it makes it easier, here's my image: https://i.stack.imgur.com/o9eYr.jpg
And here's what I want the end result to be: https://i.stack.imgur.com/GZ4uu.jpg (take a look at the bottom of the image)
I would suggest ImageOps.expand to expand your canvas:
#!/usr/bin/env python3
from PIL import Image, ImageDraw, ImageFont, ImageOps
# Load image
im = Image.open('o9eYr.jpg')
# Define font size, and annotation and height of padding above and below annotation
fontSize = 130
annotation = "GVVL"
padding = 20
# Load font and work out size of annotation
font = ImageFont.truetype("/System/Library/Fonts//Menlo.ttc", fontSize)
tw, th = font.getsize(annotation)
# Extend image at bottom and get height and width of new canvas
extended = ImageOps.expand(im, border=(0,0,0,th+2*padding), fill=(0,0,0))
w, h = extended.size
# Get drawing context and annotate
draw = ImageDraw.Draw(extended)
draw.text(((w-tw)//2, h-th-padding), annotation,(255,255,255),font=font)
extended.save('result.jpg')
You could create a new black image, paste the desired image and add text.
from PIL import Image, ImageFont, ImageDraw
base_img = Image.open('tmp.jpg')
base_size = base_img.size
new_size = (base_size[0], base_size[1] + 150)
img = Image.new("RGB", new_size)
img.paste(base_img, (0, 0))
draw = ImageDraw.Draw(img)
font = ImageFont.truetype("microsoftsansserif.ttf", 145) # (<font-file>, <font-size>)d
draw.text((base_size[0] // 2 - 150, base_size[1]),"GVVL",(255,255,255),font=font) # (x, y),"text",(r,g,b)
img.save('out.jpg')
Result:

How to add image on gif using Pillow?

Actually I am doing some experiments with python but I came to the point where I want to add an image on a transparent GIF with dimensions of the image.
I am getting an error of bad transparency mask.
Code -
from PIL import Image, ImageSequence
background = Image.open(...)
animated_gif = Image.open(...)
frames = []
for frame in ImageSequence.Iterator(animated_gif):
frame = frame.copy()
frame.paste(background, mask=bg)
frames.append(frame)
frames[0].save('output.gif', save_all=True, append_images=frames[1:])
Here is the answer of my question...
from PIL import Image, ImageSequence
background = Image.open("img.jpg")
animated_gif = Image.open("GIFF.gif")
frames = []
for frame in ImageSequence.Iterator(animated_gif):
output = background.copy()
frame_px = frame.load()
output_px = output.load()
transparent_foreground = frame.convert('RGBA')
transparent_foreground_px = transparent_foreground.load()
for x in range(frame.width):
for y in range(frame.height):
if frame_px[x, y] in (frame.info["background"], frame.info["transparency"]):
continue
output_px[x, y] = transparent_foreground_px[x, y]
frames.append(output)
frames[0].save('output.gif', save_all=True, append_images=frames[1:-1])
import Image
background = Image.open("test1.png")
foreground = Image.open("test2.png")
background.paste(foreground, (0, 0), foreground)
background.show()
I will explain the parameters for .paste() function.
first - the image to paste
second - coordinates
third - This indicates a mask that will be used to paste the image. If you pass a image with transparency, then the alpha channel is used as mask.
If this is not what you want to do, please add a comment for your need.

Crop region of interest from binary image using python

Requirement is to crop region of interest from binary image.
I need a rectangle image from a binary image by removing the extra space around the region of interest.
For example:
From this Original image i want only the region of interest marked with yellow color rectangle.
Note: Yellow color rectangle is just for the reference and it is not present in the image that will be processed.
I tried the following python code but it is not giving the required output.
from PIL import Image
from skimage.io import imread
from skimage.morphology import convex_hull_image
import numpy as np
from matplotlib import pyplot as plt
from skimage import io
from skimage.color import rgb2gray
im = imread('binaryImageEdited.png')
plt.imshow(im)
plt.title('input image')
plt.show()
# create a binary image
im1 = 1 - rgb2gray(im)
threshold = 0.8
im1[im1 <= threshold] = 0
im1[im1 > threshold] = 1
chull = convex_hull_image(im1)
plt.imshow(chull)
plt.title('convex hull in the binary image')
plt.show()
imageBox = Image.fromarray((chull*255).astype(np.uint8)).getbbox()
cropped = Image.fromarray(im).crop(imageBox)
cropped.save('L_2d_cropped.png')
plt.imshow(cropped)
plt.show()
Thank you.
Your image is not actually binary on account of two things:
firstly, it has 26 colours, and
secondly it has an (entirely unnecessary) alpha channel.
You can trim it like this:
#!/usr/bin/env python3
from PIL import Image, ImageOps
# Open image and ensure greysale and discard useless alpha channel
im = Image.open("thing.png").convert('L')
# Threshold and invert image as not actually binary
thresh = im.point(lambda p: p < 64 and 255)
# Get bounding box of thresholded image
bbox1 = thresh.getbbox()
crop1 = thresh.crop(bbox1)
# Invert and crop again
crop1n = ImageOps.invert(crop1)
bbox2 = crop1n.getbbox()
crop2 = crop1.crop(bbox2) # You don't actually need this - it's just for debug
# Trim original, unthresholded, uninverted image to the two bounding boxes
result = im.crop(bbox1).crop(bbox2)
result.save('result.png')
even i have similar problem. Also it would be helpful if image saved is in 32X32 px.

Draw text on an angle (rotated) in Python

I am drawing text onto a numpy array image in Python (using a custom font). Currently I am converting the image to PIL, drawing the text and then converting back to a numpy array.
import numpy as np
import cv2
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
char_image = np.zeros((200, 300, 3), np.uint8)
# convert to pillow image
pillowImage = Image.fromarray(char_image)
draw = ImageDraw.Draw(pillowImage)
# add chars to image
font = ImageFont.truetype("arial.ttf", 32)
draw.text((50, 50), 'ABC', (255, 255, 255), font=font)
# convert back to numpy array
char_image = np.array(pillowImage, np.uint8)
# show image on screen
cv2.imshow('myImage', char_image)
cv2.waitKey(0)
Is there anyway to draw the text on a given angle, ie. 33 degrees?
Rotating the image once the text has been drawn is not an option
You can use PIL to draw rotated text. I suggest drawing the text onto a blank image, rotating that image, and then pasting the rotated image into the main image. Something like:
Code:
def draw_rotated_text(image, angle, xy, text, fill, *args, **kwargs):
""" Draw text at an angle into an image, takes the same arguments
as Image.text() except for:
:param image: Image to write text into
:param angle: Angle to write text at
"""
# get the size of our image
width, height = image.size
max_dim = max(width, height)
# build a transparency mask large enough to hold the text
mask_size = (max_dim * 2, max_dim * 2)
mask = Image.new('L', mask_size, 0)
# add text to mask
draw = ImageDraw.Draw(mask)
draw.text((max_dim, max_dim), text, 255, *args, **kwargs)
if angle % 90 == 0:
# rotate by multiple of 90 deg is easier
rotated_mask = mask.rotate(angle)
else:
# rotate an an enlarged mask to minimize jaggies
bigger_mask = mask.resize((max_dim*8, max_dim*8),
resample=Image.BICUBIC)
rotated_mask = bigger_mask.rotate(angle).resize(
mask_size, resample=Image.LANCZOS)
# crop the mask to match image
mask_xy = (max_dim - xy[0], max_dim - xy[1])
b_box = mask_xy + (mask_xy[0] + width, mask_xy[1] + height)
mask = rotated_mask.crop(b_box)
# paste the appropriate color, with the text transparency mask
color_image = Image.new('RGBA', image.size, fill)
image.paste(color_image, mask)
How does it work:
Create a transparency mask.
Draw the text onto the mask.
Rotate the mask, and crop to proper size.
Paste the desired color into the image, using the rotated transparency mask containing the text.
Test Code:
import numpy as np
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
char_image = np.zeros((100, 150, 3), np.uint8)
# convert to pillow image
pillowImage = Image.fromarray(char_image)
# draw the text
font = ImageFont.truetype("arial.ttf", 32)
draw_rotated_text(pillowImage, 35, (50, 50), 'ABC', (128, 255, 128), font=font)
pillowImage.show()
Results:
Using matplotlib, first visualize array and draw on it, get the raw data from the figure back.
Pro: both tools are quite high level and let you deal with many details of the process. ax.annotate() offers flexibility for where and how to draw and set font properties, and plt.matshow() offers flexibility that lets you deal with aspects of array visualization.
import matplotlib.pyplot as plt
import scipy as sp
# make Data array to draw in
M = sp.zeros((500,500))
dpi = 300.0
# create a frameless mpl figure
fig, axes = plt.subplots(figsize=(M.shape[0]/dpi,M.shape[1]/dpi),dpi=dpi)
axes.axis('off')
fig.subplots_adjust(bottom=0,top=1.0,left=0,right=1)
axes.matshow(M,cmap='gray')
# set custom font
import matplotlib.font_manager as fm
ttf_fname = '/usr/share/fonts/truetype/ubuntu-font-family/Ubuntu-B.ttf'
prop = fm.FontProperties(fname=ttf_fname)
# annotate something
axes.annotate('ABC',xy=(250,250),rotation=45,fontproperties=prop,color='white')
# get fig image data and read it back to numpy array
fig.canvas.draw()
w,h = fig.canvas.get_width_height()
Imvals = sp.fromstring(fig.canvas.tostring_rgb(),dtype='uint8')
ImArray = Imvals.reshape((w,h,3))

Categories