Overwriting a border on an image in Python PIL - python

I have a gallery application where the users upload photos and my code gives it a border, writes some of the photo attributes on the border and stores it.
image2 = Image.open('media/' + str(image.file))
width, height = image2.size;
image2 = ImageOps.expand(image2, border=(int(width/25),int(height/20),int(width/25),int(height/10)), fill='rgb(0,0,0)')
(Note that here my bottom border is longer than the top because I am writing attributes on the bottom border.)
Now I'm building an edit feature for the uploaded images where the user can change the attributes of the uploaded images. But the attributes that are already written on the border have to be overwritten.
So here, my approach is to put a black patch on the bottom border and re-write the new attributes without changes the top and side borders and without changing the aspect ratio. All of this has to be done using PIL.
Question is how do I put a black box on the bottom border?
I tried ImageOps.fit() as mentioned here https://pillow.readthedocs.io/en/3.3.x/reference/ImageOps.html#PIL.ImageOps.fit, but the aspect ratio doesn't seem to be right and I want to overwrite on the black border a black box and not crop the photo.

To me it seems like the easiest solution is just quickly draw the black pixels in the area that you want using a couple loops and Image.putpixel
from PIL import Image
img = Image.open('red.png')
for x in range(img.width):
for y in range(img.height - 40, img.height):
img.putpixel((x, y), (0, 0, 0))
img.save('red2.png')

The simplest way in my opinion is to create a new black image and paste onto your existing image -
from PIL import Image
im = Image.open('test.png')
blackBox = Image.new(im.mode, (im.width, 50), '#000')
im.paste(blackBox, (0, im.height - blackBox.height))
Alternatively, you could use ImageDraw - http://pillow.readthedocs.io/en/5.2.x/reference/ImageDraw.html - which you could use to draw rectangles and other shapes.
from PIL import Image, ImageDraw
im = Image.open('test.png')
d = ImageDraw.Draw(im)
d.rectangle((0, im.height - 50, im.width, im.height), fill='#000')

Related

ValueError: images do not match when blending pictures in PIL

I have been messing around in python to see if I could "mix" two pictures together. What I mean by that is so that the image is transparent and you can see two pictures together. If that still does not make sense check out this link: (only I would mix a picture and a picture not a gif)
https://cdn.discordapp.com/attachments/652564556211683363/662770085844221963/communism.gif
Here is my code:
from PIL import Image
im1 = Image.open('oip.jpg')
im2 = Image.open('star.jpg')
bg = Image.blend(im1, im2, 0)
bg.save('star_oip_paste.jpg', quality=95)
and I get the error:
line 6, in <module> bg = Image.blend(im1, im2, 0) ValueError: images do not match
I'm not even sure if I'm using the right function for "mixing" two images together — so if I'm not, let me know.
There are several things going on here:
Your input images are both JPEG which doesn't support transparency, so you can only get a fixed blending throughout your image. I mean you can't see one image at one point and the other image at another. You will only see the same proportion of each image at each point. Is that what you want?
For example, if I take Paddington and Buckingham Palace and take 50% of each:
I get this:
If that's what you want, you need to resize the images to a common size and change this line:
bg = Image.blend(im1, im2, 0)
to
bg = Image.blend(im1, im2, 0.5) # blend half and half
If you want to paste something with transparency, so it only shows up in certain places, you need to load the overlay from a GIF or PNG with transparency and use:
background.paste(overlay, box=None, mask=overlay)
Then you can do this - note you can see different amounts of the two images at each point:
So, as a concrete example of overlaying a transparent image onto an opaque background, and starting with Paddington (400x400) and this star (500x500):
#!/usr/bin/env python3
from PIL import Image
# Open background and foreground and ensure they are RGB (not palette)
bg = Image.open('paddington.png').convert('RGB')
fg = Image.open('star.png').convert('RGBA')
# Resize foreground down from 500x500 to 100x100
fg_resized = fg.resize((100,100))
# Overlay foreground onto background at top right corner, using transparency of foreground as mask
bg.paste(fg_resized,box=(300,0),mask=fg_resized)
# Save result
bg.save('result.png')
If you want to grab an image from a website, use this:
from PIL import Image
import requests
from io import BytesIO
# Grab the star image from this answer
response = requests.get('https://i.stack.imgur.com/wKQCT.png')
# Make it into a PIL image
img = Image.open(BytesIO(response.content))
As an alternative, you could try with OpenCV (depending on your desired output)
import cv2
# Read the images
foreground = cv2.imread("puppets.png")
background = cv2.imread("ocean.png")
alpha = cv2.imread("puppets_alpha.png")
# Convert uint8 to float
foreground = foreground.astype(float)
background = background.astype(float)
# Normalize the alpha mask to keep intensity between 0 and 1
alpha = alpha.astype(float)/255
# Multiply the foreground with the alpha matte
foreground = cv2.multiply(alpha, foreground)
# Multiply the background with ( 1 - alpha )
background = cv2.multiply(1.0 - alpha, background)
# Add the masked foreground and background.
outImage = cv2.add(foreground, background)
# Display image
cv2.imshow("outImg", outImage/255)
cv2.waitKey(0)

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)

HTML5 canvas source-atop alternative in PIL

I cannot find the right function to add color overlay on another image when transparent areas should remain untouched.
For example on source image there is a semi-transparent white circle and around it everything is fully transparent. When I apply the operation with red color, I'd like to get white/red circle with fully transparent area around it.
On HTML5 canvas, it is achieved by:
layerContext.globalCompositeOperation = "source-atop";
layerContext.fillStyle = color;
layerContext.fillRect(0, 0, w, h);
I tried alpha_composite, blend and composite functions, but maybe misused them.
EDIT: here is what I get using:
overlay = Image.new('RGB', im1.size, (255, 0, 0))
im1.paste(overlay, mask=im1)
Canvas:
PIL:
PIL result is a little bit lighter, but color is the same. Any ideas?

Python: Combining images using paste with overlapping pixels and areas with alpha channel=0

I am trying to combine three images together. The image I want on the bottom is a 700x900 image with all black pixels. On top of that I want to paste an image that is 400x400 with an offset of 100,200. On top of that I want to paste an image border that is 700x900. The image border has alpha=0 in the inside of it and alpha=0 around it because it doesn't have straight edges. When I run the code I have pasted below I encounter 2 problems:
1) Everywhere on the border image where the alpha channel = 0, the alpha channel has been set to 255 and the color white shows instead of the black background and the image I am putting the border around.
2) The border image's quality has been significantly reduced and looks a lot different than it should.
Also: part of the border image will cover part of the Image I am putting the border around. So I can't just switch the order that I am pasting.
Thanks in advance for any help.
#!/usr/bin/python -tt
from PIL import ImageTk, Image
old_im2 = Image.open('backgroundImage1.jpg') # size = 400x400
old_im = Image.open('topImage.png') # size = 700x900
new_size = (700,900)
new_im = Image.new("RGBA", new_size) # makes the black image
new_im.paste(old_im2, (100, 200))
new_im.paste(old_im,(0,0))
new_im.show()
new_im.save('final.jpg')
I think you have a misconception about images - the border image does have pixels everywhere. It's not possible for it to be "missing" pixels. It is possible to have an image with an alpha channel, which is a channel like the R, G, and B channels, but indicates transparency.
Try this:
1. Make sure that topImage.png has a transparency channel, and that the pixels that you want to be "missing" are transparent (i.e. have a maximum alpha value). You can double check this way:
print old_im.mode # This should print "RGBA" if it has an alpha channel.
2. Create new_im in "RGBA" mode:
new_im = Image.new("RGBA", new_size) # makes the black image
# Note the "A" --------^
3. Try this paste statement instead:
new_im.paste(old_im,(0,0), mask=old_im) # Using old_im as the mask argument should tell the paste function to use old_im's alpha channel to combine the two images.

PIL selection of coordinates to make an image

I am wanting to create an image from a selection of coordinates i have. So I want each coordinate to be set a particular size and colour say black and 2X2, and then place it at the particular pixel it represents.
How will i go about this?
Will the function putpixel, work for what I want to do?
Thanks in advance
Doing this with putpixel will be inconvenient but not impossible. Since you say you want to make dots of more than a single pixel, it would be better to use ImageDraw.rectangle() or ellipse() instead.
For example:
import Image
import ImageDraw
img = Image.new("RGB", (400,400), "white")
draw = ImageDraw.Draw(img)
coords = [(100,70), (220, 310), (200,200)]
dotSize = 2
for (x,y) in coords:
draw.rectangle([x,y,x+dotSize-1,y+dotSize-1], fill="black")
img.show()

Categories