Create text mask using Python+PIL or Imagemagick - python

I need to programmatically create a text mask over a solid colour with the text being transparent.
eg.
Can this be done with either Imagemagick or the Python imaging library?
This works for the inverse of what I want, so solid text over a transparent background.
convert -size 720x405 xc:transparent -fill pink -gravity center -pointsize 150 -font font.ttf -draw "text 0,0 CUT" output.png
I would prefer to use PIL/Pillow if possible.
UPDATED:
This is what I have so far...
font = ImageFont.truetype('font.ttf', 200, encoding='unic')
bg = Image.new('RGBA', (720, 404), '#0099ff')
mask = Image.new('1', (720, 404))
draw = ImageDraw.Draw(mask)
draw.text((0, 0), '#HELLO', font=font, fill='#ffffff')
bg.paste('#00000000', (0, 0), text)
bg.save('text.png', 'PNG')
However this throws an error:
ValueError: unknown color specifier: '#00000000'
If I set to a valid colour it works as expected so I know I'm close, just can't reference the transparency.
UPDATED:
In the end I just created another blank image and used that rather than defining the colour. So:
trans = Image.new('RGBA', (720, 404))
bg.paste(trans, (0, 0), mask)

This is relatively simple in PIL.
Here's a pretty good PIL reference.
There are many ways to do it - here's one:
Create an image with the size and background color you want in RGBA mode.
Create a blank image of the same size in mode "1" to be used as a mask
Write the text on the mask image using the ImageDraw module
Use Image.paste(colour, box, mask) to paste the color (0, 0, 0, 0) everywhere the text exists in the mask.

Related

Writing text on a grayscale low resolution image

I have been trying to write text to an 80x80 16-bit grayscale image and have been having some trouble getting it to work.
I am currently using:
image = im[0]/255.0 #where im is just an np array of images (which are 80x80 np arrays)
# font
font = cv2.FONT_HERSHEY_SIMPLEX
# org
org = (40, 15)
# fontScale
fontScale = 0.3
# Blue color in BGR
color = (255.0)
# Line thickness of 2 px
thickness = 1
# Using cv2.putText() method
image = cv2.putText(image, 'Out:16', org, font, fontScale, color, thickness, cv2.LINE_AA)
# Displaying the image
cv2.imshow(window_name, image)
However, not only does the text look pretty awe full and take up a lot of space (I cant go lower without it not being legible), the images becomes all black except of the text which is white.
Would there be a better way to write text to a low resolution image (make the text smaller)? And why is the image turned to all black?
EDIT:
I tried using ImageDraw() and the result is all greyed
from PIL import Image, ImageFont, ImageDraw
# creating a image object
image = Image.fromarray(im[0]/255.0)
draw = ImageDraw.Draw(image)
# specified font size
font = ImageFont.truetype('./arial.ttf', 10)
text = 'fyp:16'
# drawing text size
draw.text((5, 5), text, font = font, align ="left")
It looks like the main issue is converting image type to float.
Assume (please verify it):
im[0] is 16-bit grayscale, and im[0].dtype is dtype('uint16').
image = im[0]/255.0 implies that you want to convert the range from 16-bit grayscale to the the range of uint8.
Note: for converting the range from [0, 2^16-1] to [0, 255] you need to divide by (2**16-1)/255 = 257.0. But this is not the main issue.
The main issue is converting the type to float.
The valid range of float images in OpenCV is [0, 1].
All values above 1.0 are white pixels, and 0.5 is a gray pixel.
You can keep the image type uint16 - you don't have to convert it to uint8.
A white text color for uint16 type is 2**16-1 = 65535 (not 255).
Here is code sample that works with 16-bit grayscale (and uint16 type):
import numpy as np
import cv2
im = np.full((80, 80), 10000, np.uint16) # 16 bits grayscale synthetic image - set all pixels to 10000
cv2.circle(im, (40, 40), 10, 0, 20, cv2.LINE_8) # draw black cicle - synthetic image
#image = im[0]/255.0 #where im is just an np array of images (which are 80x80 np arrays)
image = im #where im is just an np array of images (which are 80x80 np arrays)
color = 2**16-1 # 65535 is white color for 16 bis image
# Using cv2.putText() method
image = cv2.putText(image, 'Out:16', (40, 15), cv2.FONT_HERSHEY_SIMPLEX, 0.3, color, 1, cv2.LINE_AA)
# Displaying the image
cv2.imshow("image", image)
cv2.waitKey()
The above code creates synthetic 16-bit grayscale for testing.
Converting from 16-bit grayscale to 8-bit grayscale:
# https://stackoverflow.com/questions/11337499/how-to-convert-an-image-from-np-uint16-to-np-uint8
uint8_image = cv2.convertScaleAbs(image, alpha=255.0/(2**16-1)) # Convent uint16 image to uint8 image (2**16-1 scaled to 255)
The above conversion assumes image is full range 16 bits (pixel range [0, 65535]).
About the font:
OpenCV is computer vision oriented, and text drawing limited.
Why is the image black?
It's hard to answer without knowing the values of im[0].
It could be that im[0] is not a 16-bit grayscale at all.
It could be that the values of im[0] are very small.
It could be that the type of im[0] is not uint16.
Drawing text using Pillow (PIL):
The quality of the small text is much better compared to OpenCV.
You can find for about quality text rendering here.
Continue with the uint8 image:
pil_image = Image.fromarray(uint8_image)
draw = ImageDraw.Draw(pil_image)
# specified font size
font = ImageFont.truetype('./arial.ttf', 10)
text = 'fyp:16'
# drawing text size
draw.text((5, 5), text, 255, font = font, align ="left")
pil_image.show()
Result:
I don't really know the reason your text looks wired compared to above result.

How do I add a transparent overlay to an image using pillow?

I'm trying to add an transparent overlay on an jpeg image. In the example below, the desired result would be a red image with a pieslice where the it is light red.
My input is an jpeg Image. Since jpeg doesn't have an alpha channel, I though I could convert it to an 'RGBA' image and paste the overlay on it:
from PIL import Image, ImageDraw
# img = Image.open('input.jpg').convert('RGBA')
img = Image.new('RGBA', (400,400), (255,0,0))
img2 = Image.new('RGBA', (400,400))
draw2 = ImageDraw.Draw(img2)
draw2.pieslice([0,0,400,400], 90, 180, fill='white')
img.putalpha(128)
img.save('img.png')
img2.save('img2.png')
img.paste(img2)
img.save('img1+2.png')
However, this doesn't have the desired effect, and windows Photos cannot even open it correctly.
I saw the blend and alpha_composite functions but it doesn't have the desired effect for me. I don't want to lower the alpha values of the background outside of the overlay.

How can I change the brightness of an image in pygame?

I need a bright version of some of my images in pygame. However, I don't want to make a bright version of every single one. Can I change this through pygame?
I've found a similar queation here (How can you edit the brightness of images in PyGame?), but I don't know how to multiply the color of an image. Can someone explain how I can do that?
If you want to brighten an image, then I recommend to add a constant color to the surface. This can be achieved by .fill(), wby the use of the special parameter BLEND_RGB_ADD. If the fill color is black (0, 0, 0) then the image won't change at all. If the fill color is white (255, 255, 255), then the entire image will become white. e.g.:
image = pygame.image.load(my_imagename)
brighten = 128
image.fill((brighten, brighten, brighten), special_flags=pygame.BLEND_RGB_ADD)
[...] I want the image to be more transparent.
If you want to increase the transparency of the image, then yo can "blend" the image with a transparent color, by the use of the special flag BLEND_RGBA_MULT . Of course you've to ensure that the image format provides an alpha channel (e.g. .convert_alpha())
image = pygame.image.load(my_imagename).convert_alpha()
transparency = 128
image.fill((255, 255, 255, transparency), special_flags=pygame.BLEND_RGBA_MULT)

Extending an image (PIL/Pillow)

I can use the following code to draw a black strip (or rectangle) atop a base image:
base_width, base_height = img.size
background = Image.new('RGBA', (base_width, base_height/3),(0,0,0,146))
offset = (0,base_height/2)
img.paste(background,offset,mask=background)
Result:
But how do I extend the height of the image such that the said black strip appears attached below the image's bottom border, outside the image itself?
If I move the offset in my code above, the black strip can't move beyond the borders of the base image, so that's not a viable solution.
Here's one way:
Create a new_img that is (base_width, base_height + background.height) in size
Paste the original img into new_img at (0, 0)
Paste the background into new_img at (0, base_height)

getbbox method from python image library (PIL) not working

I want to crop an image to its smaller size, by cutting the white areas on the borders. I tried the solution suggested in this forum Crop a PNG image to its minimum size but the getbbox() method of pil is returning a bounding box of the same size of the image, i.e., it seems that it doesn't recognize the blank areas around. I tried the following:
>>>import Image
>>>im=Image.open("myfile.png")
>>>print im.format, im.size, im.mode
>>>print im.getbbox()
PNG (2400,1800) RGBA
(0,0,2400,1800)
I checked that my image has truly white croppable borders by cropping the image with the GIMP auto-crop. I also tried with ps and eps versions of the figure, without luck.
Any help would be highly appreciated.
Trouble is getbbox() crops off the black borders, from the docs: Calculates the bounding box of the non-zero regions in the image.
import Image
im=Image.open("flowers_white_border.jpg")
print im.format, im.size, im.mode
print im.getbbox()
# white border output:
JPEG (300, 225) RGB
(0, 0, 300, 225)
im=Image.open("flowers_black_border.jpg")
print im.format, im.size, im.mode
print im.getbbox()
# black border output:
JPEG (300, 225) RGB
(16, 16, 288, 216) # cropped as desired
We can do an easy fix for white borders, by first inverting the image using ImageOps.invert, and then use getbbox():
import ImageOps
im=Image.open("flowers_white_border.jpg")
invert_im = ImageOps.invert(im)
print invert_im.getbbox()
# output:
(16, 16, 288, 216)

Categories