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:
Related
I'm trying to put multiple images and texts on top of the background image. Making use of python PIL image library. The number of images and text printing won't need to be the same always. Text and image printing has to be separate. The text has to be printed outside anywhere image bounding box.
I'm using below code to make this happen
from PIL import Image
import os, random
with open('C:/Users/nike/Desktop/namelist.txt', "r") as word_list:
words = list(word_list)
k=[]
for i in words:
j = i.replace(' ','').replace('\n','')
k.append(j)
folder=r"C:/Users/nike/Desktop/imagefolder"
a=random.choice(os.listdir(folder))
file = folder+'//'+a
random_text=random.choice(k)
img = Image.open(file)
img_w, img_h = img.size
background = Image.open('C:/Users/nike/Desktop/backgroundimages/back.jpeg','r')
bg_w, bg_h = background.size
offset = ((bg_w - img_w) // 2, (bg_h - img_h) // 2)
draw = ImageDraw.Draw(background)
background.paste(img, offset)
font = ImageFont.truetype("C:/Users/nike/Desktop/open-sans/abc.ttf", 16)
draw.text((0, 0),random_text,(255,255,255),font=font)
background.save('out.png')
above code does printing of one image at the center of background image and text at the (0,0) coordinate of background image. How can I make multiple text and images to paste on background images such that text(x,y) do not print on image (x,y). Any suggestion will be helpful.
Example:
expected result
on the background image, I need to copy images and texts such that they won't overlap eachother.
I want to create text to bitmap. the text is considered long string text.
so please tell me how to create a bitmap from a text.
I tried this :
from PIL import Image, ImageFont
img = Image.new('L', (500, 500), color=0)
img_w, img_h = img.size
font = ImageFont.truetype('arial.ttf', 20)
mask = font.getmask('some text related location that is going to write here.'
'this is watermark text', mode='L')
mask_w, mask_h = mask.size
print(mask_w,mask_h)
print(type(mask))
d = Image.core.draw(img.im, 0)
# d = d.rotate(40)
d.draw_bitmap(((img_w - mask_w)/2, (img_h - mask_h)/2), mask, 255)
img = img.rotate(40)
img.show()
img.save('op.jpg')
But text is cut from both side.
here is what i get.
The text is cut from both sides because it was already cut by the horizontal boundary of the image before you rotate it. You should create an image with a width large enough to accommodate the entire text before you rotate it. That is to say, you should create an Image object with a width of mask_w after you obtain it.
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')
So I have a table with image sizes. There are multiple images of different sizes (66x66, 400x400, etc.). I have one example of image (the original) that always has a size of 600x532, and on this image is a product (a TV, a PC, etc.).
I have to resize this image, which isn't a problem. But if I do this with proportion I get something like 66x55. If I don't do this with proportion the image doesn't look good.
So the background of the original is always white. Is there a way to extend the area of the image and filling the rest with white?
So like this: 600x532 -> 600x600 -> 66x66 etc etc.
It should be like a anti-crop.
EDIT: I found out that if I use crop() from PIL and instead of "minimizing" using a value above the actual image-size it creates my extra area. but it is going to be black.
Any idea how I could fill this area white?
EDIT2: I guess it has something to do with ImageDraw.
EDIT3: After finding out that ImageDraw was the solution, my problem was solved. Please close this.
Here my solution:
import Image, ImageDraw
img1 = Image.open("img.jpg")
img2 = img1.crop((0,0,600,600))
draw = ImageDraw.Draw(img2)
draw.rectangle( (0,532,600,600), fill='white' )
del draw
img2.save("img2.jpg","JPEG", quality=75)
The next thing I will do is to make the extra crop above and under. So the picture stays in the middle.
EDIT4: final solution
img1 = Image.open("img1.jpg")
img2 = img1.crop( (0,-34,600,566) )
draw = ImageDraw.Draw(img2)
draw.rectangle( (0,0,600,34), fill="white" )
draw.rectangle( (0,566,600,600), fill="white" )
del draw
img2.save("img2.jpg", "JPEG", quality=75)
Supposing we use PIL to process the image
from PIL import Image
def white_bg_square(img):
"return a white-background-color image having the img in exact center"
size = (max(img.size),)*2
layer = Image.new('RGB', size, (255,255,255))
layer.paste(img, tuple(map(lambda x:(x[0]-x[1])/2, zip(size, img.size))))
return layer
You could resize a PIL Image object, img for example
img.resize((width, height), resample=Image.ANTIALIAS)
Thus in the python shell, it looks like
>>> from PIL import Image
>>> img = Image.open('path/to/image')
>>> square_one = white_bg_square(img)
>>> square_one.resize((100, 100), Image.ANTIALIAS)
>>> square_one.save('path/to/result')
There are nice examples inside PIL document and sorl-thumbnail 3.2.5
http://effbot.org/imagingbook/image.htm
http://pypi.python.org/pypi/sorl-thumbnail/3.2.5
My final solution
img1 = Image.open("img1.jpg")
img2 = img1.crop( (0,-34,600,566) )
draw = ImageDraw.Draw(img2)
draw.rectangle( (0,0,600,34), fill="white" )
draw.rectangle( (0,566,600,600), fill="white" )
del draw
img2.save("img2.jpg", "JPEG", quality=75)
If we use opencv to process the image.
import cv2
import numpy as np
def make_square(self, image_in):
size = image_in.shape[:2]
max_dim = max(size)
delta_w = max_dim - size[1]
delta_h = max_dim - size[0]
top, bottom = delta_h//2, delta_h-(delta_h//2)
left, right = delta_w//2, delta_w-(delta_w//2)
color = [255, 255, 255]
#image_out = cv2.copyMakeBorder(image_in, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)
image_out = cv2.copyMakeBorder(image_in, top, bottom, left, right, cv2.BORDER_REPLICATE, value=color)
return image_out
image_in = cv2.imread(image_path)
I have a transparent png image foo.png and I've opened another image with:
im = Image.open("foo2.png")
Now what I need is to merge foo.png with foo2.png.
(foo.png contains some text and I want to print that text on foo2.png)
from PIL import Image
background = Image.open("test1.png")
foreground = Image.open("test2.png")
background.paste(foreground, (0, 0), foreground)
background.show()
First parameter to .paste() is the image to paste. Second are coordinates, and the secret sauce is the third parameter. It 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.
Check the docs.
Image.paste does not work as expected when the background image also contains transparency. You need to use real Alpha Compositing.
Pillow 2.0 contains an alpha_composite function that does this.
background = Image.open("test1.png")
foreground = Image.open("test2.png")
Image.alpha_composite(background, foreground).save("test3.png")
EDIT: Both images need to be of the type RGBA. So you need to call convert('RGBA') if they are paletted, etc.. If the background does not have an alpha channel, then you can use the regular paste method (which should be faster).
As olt already pointed out, Image.paste doesn't work properly, when source and destination both contain alpha.
Consider the following scenario:
Two test images, both contain alpha:
layer1 = Image.open("layer1.png")
layer2 = Image.open("layer2.png")
Compositing image using Image.paste like so:
final1 = Image.new("RGBA", layer1.size)
final1.paste(layer1, (0,0), layer1)
final1.paste(layer2, (0,0), layer2)
produces the following image (the alpha part of the overlayed red pixels is completely taken from the 2nd layer. The pixels are not blended correctly):
Compositing image using Image.alpha_composite like so:
final2 = Image.new("RGBA", layer1.size)
final2 = Image.alpha_composite(final2, layer1)
final2 = Image.alpha_composite(final2, layer2)
produces the following (correct) image:
One can also use blending:
im1 = Image.open("im1.png")
im2 = Image.open("im2.png")
blended = Image.blend(im1, im2, alpha=0.5)
blended.save("blended.png")
Had a similar question and had difficulty finding an answer. The following function allows you to paste an image with a transparency parameter over another image at a specific offset.
import Image
def trans_paste(fg_img,bg_img,alpha=1.0,box=(0,0)):
fg_img_trans = Image.new("RGBA",fg_img.size)
fg_img_trans = Image.blend(fg_img_trans,fg_img,alpha)
bg_img.paste(fg_img_trans,box,fg_img_trans)
return bg_img
bg_img = Image.open("bg.png")
fg_img = Image.open("fg.png")
p = trans_paste(fg_img,bg_img,.7,(250,100))
p.show()
def trans_paste(bg_img,fg_img,box=(0,0)):
fg_img_trans = Image.new("RGBA",bg_img.size)
fg_img_trans.paste(fg_img,box,mask=fg_img)
new_img = Image.alpha_composite(bg_img,fg_img_trans)
return new_img
Here is my code to merge 2 images of different sizes, each with transparency and with offset:
from PIL import Image
background = Image.open('image1.png')
foreground = Image.open("image2.png")
x = background.size[0]//2
y = background.size[1]//2
background = Image.alpha_composite(
Image.new("RGBA", background.size),
background.convert('RGBA')
)
background.paste(
foreground,
(x, y),
foreground
)
background.show()
This snippet is a mix of the previous answers, blending elements with offset while handling images with different sizes, each with transparency.
the key code is:
_, _, _, alpha = image_element_copy.split()
image_bg_copy.paste(image_element_copy, box=(x0, y0, x1, y1), mask=alpha)
the full function is:
def paste_image(image_bg, image_element, cx, cy, w, h, rotate=0, h_flip=False):
image_bg_copy = image_bg.copy()
image_element_copy = image_element.copy()
image_element_copy = image_element_copy.resize(size=(w, h))
if h_flip:
image_element_copy = image_element_copy.transpose(Image.FLIP_LEFT_RIGHT)
image_element_copy = image_element_copy.rotate(rotate, expand=True)
_, _, _, alpha = image_element_copy.split()
# image_element_copy's width and height will change after rotation
w = image_element_copy.width
h = image_element_copy.height
x0 = cx - w // 2
y0 = cy - h // 2
x1 = x0 + w
y1 = y0 + h
image_bg_copy.paste(image_element_copy, box=(x0, y0, x1, y1), mask=alpha)
return image_bg_copy
the above function supports:
position(cx, cy)
auto resize image_element to (w, h)
rotate image_element without cropping it
horizontal flip