Python: Image resizing: keep proportion - add white background - python

I would like to create a Python script to resize images, but not changing its proportions, just by adding a white background
(So, a : 500*700 px image would transform to a 700*700 px image by adding 100 px of a white band on each side)
The three image types I use are .PNG, .JPG and .GIF. I am not even sure it is possible for Gifs, PNG and JPG would already be awesome.
In my case, they have to be squares. But if any of you manage to do it for adaptable to any proportion, it would benefit the maximum number of people that see this thread and you would be even more awesome !
I saw same threads for other languages but not python, do you guys know how you do this ?
PS : I am using Python 3
What I tried :
Combining 3 images together.
If we take our 500*700 px image :
Creating two white images of 100*700px and put one on each side of the image. Inspired by :
Combine several images horizontally with Python
But, I am kind of new on python, and I haven't succeded.

Finally did it :
def Reformat_Image(ImageFilePath):
from PIL import Image
image = Image.open(ImageFilePath, 'r')
image_size = image.size
width = image_size[0]
height = image_size[1]
if(width != height):
bigside = width if width > height else height
background = Image.new('RGBA', (bigside, bigside), (255, 255, 255, 255))
offset = (int(round(((bigside - width) / 2), 0)), int(round(((bigside - height) / 2),0)))
background.paste(image, offset)
background.save('out.png')
print("Image has been resized !")
else:
print("Image is already a square, it has not been resized !")
Thanks to #Blotosmetek for the suggestion, pasting a centered image is definitely simpler than creating images and combining them !
PS : If you don't have PIL yet, the library's name to install it with pip is "pillow", not PIL. But still, you use it as PIL in the code.

Thanks #Jay D., here a bit more general version:
from PIL import Image
def resize(image_pil, width, height):
'''
Resize PIL image keeping ratio and using white background.
'''
ratio_w = width / image_pil.width
ratio_h = height / image_pil.height
if ratio_w < ratio_h:
# It must be fixed by width
resize_width = width
resize_height = round(ratio_w * image_pil.height)
else:
# Fixed by height
resize_width = round(ratio_h * image_pil.width)
resize_height = height
image_resize = image_pil.resize((resize_width, resize_height), Image.ANTIALIAS)
background = Image.new('RGBA', (width, height), (255, 255, 255, 255))
offset = (round((width - resize_width) / 2), round((height - resize_height) / 2))
background.paste(image_resize, offset)
return background.convert('RGB')

The accepted answer is great, I am just happy not to use OpenCV.
As #Nemanja mentioned, if you want to make it work for any aspect ration. Here is the snippet to use. I just twisted the code a bit.
from PIL import Image
def Reformat_Image_With_Ratio(ImageFilePath, desired_aspect_ratio):
image = Image.open(ImageFilePath, 'r')
width = image.width
height = image.height
img_aspect_ratio = width/height
if (img_aspect_ratio != desired_aspect_ratio):
bigside = width if width > height else height
other_side = int(bigside * desired_aspect_ratio)
background = Image.new('RGBA', (other_side, bigside), (255, 0, 0, 255))
offset = (int(round(((bigside - width) / 2), 0)), int(round(((bigside - height) / 2),0)))
background.paste(image, offset)
background.save('out4.png')
print("Image has been resized !")
else:
print("Image is already a valid aspect ratio, it has not been resized !")
Reformat_Image_With_Ratio('test.png', 9/16)

The other answer didn't work for me, I rewrote it and this worked:
def resize_with_pad(im, target_width, target_height):
'''
Resize PIL image keeping ratio and using white background.
'''
target_ratio = target_height / target_width
im_ratio = im.height / im.width
if target_ratio > im_ratio:
# It must be fixed by width
resize_width = target_width
resize_height = round(resize_width * im_ratio)
else:
# Fixed by height
resize_height = target_height
resize_width = round(resize_height / im_ratio)
image_resize = im.resize((resize_width, resize_height), Image.ANTIALIAS)
background = Image.new('RGBA', (target_width, target_height), (255, 255, 255, 255))
offset = (round((target_width - resize_width) / 2), round((target_height - resize_height) / 2))
background.paste(image_resize, offset)
return background.convert('RGB')

Related

Kerning text in Python PIL

I have some code to produce an image with text on it, using PIL:
from PIL import Image, ImageDraw, ImageFont
width = 2480
height = 3071
message = "Hello"
font = ImageFont.truetype("Arial.ttf", size=900)
img = Image.new('RGB', (width, height), color='black')
imgDraw = ImageDraw.Draw(img)
textWidth, textHeight = imgDraw.textsize(message, font=font)
xText = (width - textWidth) / 2
yText = (height - textHeight) / 2
imgDraw.text((152, 2100), message, font=font, fill=(255, 255, 255))
img.save('result.png')
I get a result like this: Example
But I need the text to have less space between letters. I intend to use this code for a batch of 40+ words located in a CSV file. How can I automatically adjust the kerning? I've seen other articles pointing to a related problem but they haven't helped.

How Can I create an image with python PIL where there are two colors?

Here is an example of the output I want to generate. I'm able to create an image with one color, but I don't have idea of how can use two colors, and how to color only certain parts of the image .
I solved in this way. I created two image with two different colors, and then paste them in another one image.
width = 400
height = 300
img = Image.new( mode = "RGB", size = (width, height), color = (209, 123, 193) )
#First IMG
img2 = Image.new( mode = "RGB", size = (width, height + 400), color = (255, 255, 255) )
#Second IMG
img3 = Image.new('RGB', (img.width, img.height + img2.height)) img3.paste(img, (0, 0)) img3.paste(img2, (img.width, 0))
#IMG + IMG2
I got my result.

How can i create frame with python that i can put a picture into frame

I want to put my pic in my frame.I used this cod :
from PIL import Image
img = Image.open('Pic.jpg')
frame = Image.open('Frame.jpg')
size1 = 354,362
paste_point = 69,339
Pic = img.resize((size1))
frame.paste(img, (paste_point))
frame.show()
When i run the program, my pic doesn't put in my frame.
How can i create a frame in python
your question solution depends always on your pic size and frame size ,so code must be adjusted according to pixel size of your pic
i am giving generic code for your problem maynot fit to your image standards
from PIL import Image
img = Image.open('Pic.jpg')
img_w, img_h = img.size
frame = Image.new('RGBA', (1440, 900), (255, 255, 255, 255))
bg_w, bg_h = frame.size
offset = ((bg_w - img_w) / 2, (bg_h - img_h) / 2)
frame.paste(img, offset)
frame.save('out.png')

OpenCV & Python - Image too big to display

I have an image that is 6400 × 3200, while my screen is 1280 x 800. Therefore, the image needs to be resized for display only. I am using Python and OpenCV 2.4.9.
According to OpenCV Documentation,
If you need to show an image that is bigger than the screen resolution, you will need to call namedWindow("", WINDOW_NORMAL) before the imshow.
That is what I am doing, but the image is not fitted to the screen, only a portion is shown because it's too big. I've also tried with cv2.resizeWindow, but it doesn't make any difference.
import cv2
cv2.namedWindow("output", cv2.WINDOW_NORMAL) # Create window with freedom of dimensions
# cv2.resizeWindow("output", 400, 300) # Resize window to specified dimensions
im = cv2.imread("earth.jpg") # Read image
cv2.imshow("output", im) # Show image
cv2.waitKey(0) # Display the image infinitely until any keypress
Although I was expecting an automatic solution (fitting to the screen automatically), resizing solves the problem as well.
import cv2
cv2.namedWindow("output", cv2.WINDOW_NORMAL) # Create window with freedom of dimensions
im = cv2.imread("earth.jpg") # Read image
imS = cv2.resize(im, (960, 540)) # Resize image
cv2.imshow("output", imS) # Show image
cv2.waitKey(0) # Display the image infinitely until any keypress
The other answers perform a fixed (width, height) resize. If you wanted to resize to a specific size while maintaining aspect ratio, use this
def ResizeWithAspectRatio(image, width=None, height=None, inter=cv2.INTER_AREA):
dim = None
(h, w) = image.shape[:2]
if width is None and height is None:
return image
if width is None:
r = height / float(h)
dim = (int(w * r), height)
else:
r = width / float(w)
dim = (width, int(h * r))
return cv2.resize(image, dim, interpolation=inter)
Example
image = cv2.imread('img.png')
resize = ResizeWithAspectRatio(image, width=1280) # Resize by width OR
# resize = ResizeWithAspectRatio(image, height=1280) # Resize by height
cv2.imshow('resize', resize)
cv2.waitKey()
Use this for example:
cv2.namedWindow('finalImg', cv2.WINDOW_NORMAL)
cv2.imshow("finalImg",finalImg)
The only way resizeWindow worked for me was to have it after imshow. This is the order I'm using:
# Create a Named Window
cv2.namedWindow(win_name, cv2.WINDOW_NORMAL)
# Move it to (X,Y)
cv2.moveWindow(win_name, X, Y)
# Show the Image in the Window
cv2.imshow(win_name, image)
# Resize the Window
cv2.resizeWindow(win_name, width, height)
# Wait for <> miliseconds
cv2.waitKey(wait_time)
In OpenCV, cv2.namedWindow() just creates a window object, but doesn't resize the original image. You can use cv2.resize(img, resolution) to solve the problem.
Here's what it displays, a 740 * 411 resolution image.
image = cv2.imread("740*411.jpg")
cv2.imshow("image", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
Here, it displays a 100 * 200 resolution image after resizing. Remember the resolution parameter use column first then is row.
image = cv2.imread("740*411.jpg")
image = cv2.resize(image, (200, 100))
cv2.imshow("image", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
This code will resize the image so that it can retain it's aspect ratio and only ever take up a specified fraction of the screen area.
It will automatically adjust depending on your screen size and the size of the image.
Use the area variable to change the max screen area you want the image to be able to take up. The example shows it displayed at quarter the screen size.
import cv2
import tkinter as tk
from math import *
img = cv2.imread("test.jpg")
area = 0.25
h, w = img.shape[:2]
root = tk.Tk()
screen_h = root.winfo_screenheight()
screen_w = root.winfo_screenwidth()
vector = sqrt(area)
window_h = screen_h * vector
window_w = screen_w * vector
if h > window_h or w > window_w:
if h / window_h >= w / window_w:
multiplier = window_h / h
else:
multiplier = window_w / w
img = cv2.resize(img, (0, 0), fx=multiplier, fy=multiplier)
cv2.imshow("output", img)
cv2.waitKey(0)
I've also made a similar function where area is still a parameter but so is window height and window width.
If no area is input then it will use a defined height and width (window_h, window_w) of the window size you would like the image to fit inside.
If an input is given for all parameters then 'area' is prioritised.
import cv2
import tkinter as tk
from math import *
def resize_image(img, area=0.0, window_h=0, window_w=0):
h, w = img.shape[:2]
root = tk.Tk()
screen_h = root.winfo_screenheight()
screen_w = root.winfo_screenwidth()
if area != 0.0:
vector = math.sqrt(area)
window_h = screen_h * vector
window_w = screen_w * vector
if h > window_h or w > window_w:
if h / window_h >= w / window_w:
multiplier = window_h / h
else:
multiplier = window_w / w
img = cv2.resize(img, (0, 0), fx=multiplier, fy=multiplier)
return img
# using area
initial_image = cv2.imread("test.jpg")
resized_image = resize_image(initial_image, area=0.25))
cv2.imshow("output", resized_image)
cv2.waitKey(0)
# using window height and width
initial_image = cv2.imread("test.jpg")
resized_image = resize_image(initial_image, window_h = 480, window_w = 270))
cv2.imshow("output", resized_image)
cv2.waitKey(0)
Looks like opencv lib is pretty sensitive to parameters passed to the methods. The following code worked for me using opencv 4.3.0:
win_name = "visualization" # 1. use var to specify window name everywhere
cv2.namedWindow(win_name, cv2.WINDOW_NORMAL) # 2. use 'normal' flag
img = cv2.imread(filename)
h,w = img.shape[:2] # suits for image containing any amount of channels
h = int(h / resize_factor) # one must compute beforehand
w = int(w / resize_factor) # and convert to INT
cv2.resizeWindow(win_name, w, h) # use variables defined/computed BEFOREHAND
cv2.imshow(win_name, img)
Try this:
image = cv2.imread("img/Demo.jpg")
image = cv2.resize(image,(240,240))
The image is now resized. Displaying it will render in 240x240.
The cv2.WINDOW_NORMAL option works correctly but the first time it displays the window in an standard size.
If you resize the window like any other windows in your computer, by position the mouse over the edge of the window you want to resize and then drag the mouse to the position you want. If you do this to both width and height of the window to the size you want to obtain.
The following times you refresh the window, by executing the code, OpenCV will generate the window with the size of the last time it was shown or modified.
Try this code:
img = cv2.imread("Fab2_0.1 X 1.03MM GRID.jpg", cv2.IMREAD_GRAYSCALE)
image_scale_down = 3
x = (int)(img.shape[0]/image_scale_down)
y = (int)(img.shape[1]/image_scale_down)
image = cv2.resize(img, (x,y))
cv2.imshow("image_title", image)
cv2.waitKey(5000)
cv2.destroyAllWindows()
The most upvote answer is perfect !
I just add my code for those who want some "dynamic" resize handling depending of the ratio.
import cv2
from win32api import GetSystemMetrics
def get_resized_for_display_img(img):
screen_w, screen_h = GetSystemMetrics(0), GetSystemMetrics(1)
print("screen size",screen_w, screen_h)
h,w,channel_nbr = img.shape
# img get w of screen and adapt h
h = h * (screen_w / w)
w = screen_w
if h > screen_h: #if img h still too big
# img get h of screen and adapt w
w = w * (screen_h / h)
h = screen_h
w, h = w*0.9, h*0.9 # because you don't want it to be that big, right ?
w, h = int(w), int(h) # you need int for the cv2.resize
return cv2.resize(img, (w, h))
Try this code
img = cv2.resize(img,(1280,800))
Try with this code:
from PIL import Image
Image.fromarray(image).show()

PIL: Thumbnail and end up with a square image

Calling
image = Image.open(data)
image.thumbnail((36,36), Image.NEAREST)
will maintain the aspect ratio. But I need to end up displaying the image like this:
<img src="/media/image.png" style="height:36px; width:36px" />
Can I have a letterbox style with either transparent or white around the image?
PIL already has a function to do exactly that:
from PIL import Image, ImageOps
thumb = ImageOps.fit(image, size, Image.ANTIALIAS)
Paste the image into a transparent image with the right size as a background
from PIL import Image
size = (36, 36)
image = Image.open(data)
image.thumbnail(size, Image.ANTIALIAS)
background = Image.new('RGBA', size, (255, 255, 255, 0))
background.paste(
image, (int((size[0] - image.size[0]) / 2), int((size[1] - image.size[1]) / 2))
)
background.save("output.png")
EDIT: fixed syntax error
from PIL import Image
import StringIO
def thumbnail_image():
image = Image.open("image.png")
image.thumbnail((300, 200))
thumb_buffer = StringIO.StringIO()
image.save(thumb_buffer, format=image.format)
fp = open("thumbnail.png", "w")
fp.write(thumb_buffer.getvalue())
fp.close()
Update of Cesar Canassa's answer.
This will create a thumbnail of image.jpg as image_thumb.jpg:
from PIL import Image, ImageOps
fname = 'image.jpg'
size = (48,48)
thumb = ImageOps.fit(Image.open(fname), size, Image.ANTIALIAS)
thumb.save('{}_thumb.jpg'.format(fname[:fname.rfind('.')]), "JPEG")
Or this, maybe... (forgive spaghetti)
from PIL import Image
def process_image(image, size):
if image.size[0] > size[0] or image.size[1] > size[1]:
#preserve original
thumb = image.copy()
thumb.thumbnail(size,Image.ANTIALIAS)
img = thumb.copy()
img_padded = Image.new("RGBA",size)
img_padded.paste(image,(int((size[0]-image.size[0])/2),int((size[1]-image.size[1])/2)))
return img_padded
Why not simply use the resize method ?
from PIL import Image
image = Image.open('/path/to/img.png')
image = image.resize((36,36), Image.ANTIALIAS)
See recommendations for image resizing in this release note:
https://pillow.readthedocs.io/en/stable/releasenotes/5.3.0.html
you can wrap Nadia's answer in this function, which gives you control over size and background.
def make_square(im, min_size=36, fill_color=(255, 255, 255, 0)):
x, y = im.size
size = min(min_size, x, y)
new_im = Image.new('RGBA', (size, size), fill_color)
im.thumbnail((256, 256))
new_im.paste(im, (int((x - size) / 2), int((y -size) / 2))
return new_im

Categories

Resources