Here is what i'm trying to achieve:
Resize Multiple Product images into 500 x 500 pixels
Paste all of these resized images individually onto a 800 x 800 image and output as individual images
For now, I have managed to complete Step 1, but have no idea how to proceed to step 2. Here is my code for Step 1:
from PIL import Image
import os, sys
path = "C:\\Users\\User\\Desktop\\Test\\"
dirs = os.listdir( path )
final_size = 500;
def resize_aspect_fit():
for item in dirs:
if item == '.png':
continue
if os.path.isfile(path+item):
im = Image.open(path+item)
f, e = os.path.splitext(path+item)
size = im.size
ratio = float(final_size) / max(size)
new_image_size = tuple([int(x*ratio) for x in size])
im = im.resize(new_image_size, Image.ANTIALIAS)
new_im = Image.new("RGBA", (final_size, final_size), (255,255,255,000))
new_im.paste(im, ((final_size-new_image_size[0])//2, (final_size-new_image_size[1])//2))
new_im.save(f + 'resized.png', 'PNG', quality=100)
resize_aspect_fit()
Thanks!
Edit:
Here's an image illustration for better explanation of what i am trying to achieve. I have 2 smiley faces (500 x 500) which i need to paste over the default 800 x 800 image multiple times (centered) to produce 2 separate images of 800 x 800.
Example
You're nearly there, you can use .paste() with an offset to paste into the middle over your image. a little bit like:
Border_im = Image.open(pathToBorder)
product_im = Image.open(pathToProduct)
x_offset=150;
y_offset=150;
Border_im.paste(product_im, (x_offset,y_offset))
Then save out border_im to a new file
Related
I do three images with different sizes of (72,153,3), (74,153,3), (75,153,3) and (71,153,3).
I am trying to get the max width from these four images which is 75. I have done a bit of coding to read the files from a list using openCV and I don't know how to move forward from there.
image_names = ["POPI_133.png", "POPI_134.png", "POPI_135.png", "POPI_135.png"]
images = []
for name in image_names:
# open all images and find their sizes
images.append(cv2.imread(name))
I really don't know how to forward with these code afterwards. Kindly help please!
You can use this code to obtain the result you need..
import cv2
image_paths = ["POPI_133.png", "POPI_134.png", "POPI_135.png", "POPI_135.png"]
max_width = 0
for img_path in image_paths:
img = cv2.imread(img_path)
width = img.shape[1]
if width > max_width:
max_width = width
print(max_width)
If you wanted to get the max height of all the images you'll need to replace img.shape[1] with img.shape[0] in the code.
In a previous question on the following link Concatenate muliple images with pillow in python, I got the following code that merges each four images together. The code is working very well and I appreciate that to #CrazyChucky
from pathlib import Path
from more_itertools import chunked
from PIL import Image
def concat_images(*images):
"""Generate composite of all supplied images."""
# Get the widest width.
width = max(image.width for image in images)
# Add up all the heights.
height = sum(image.height for image in images)
composite = Image.new('RGB', (width, height))
# Paste each image below the one before it.
y = 0
for image in images:
composite.paste(image, (0, y))
y += image.height
return composite
if __name__ == '__main__':
# Define the folder to operate on (currently set to the current
# working directory).
images_dir = Path('.')
# Define where to save the output (shown here, will be in `output`
# inside the images dir).
output_dir = images_dir / 'output'
# Create the output folder, if it doesn't already exist.
output_dir.mkdir(exist_ok=True)
# Iterate through the .png files in groups of four, using an index
# to name the resulting output.
png_paths = images_dir.glob('*.png')
for i, paths in enumerate(chunked(png_paths, 4), start=1):
images = [Image.open(path) for path in paths]
composite = concat_images(*images)
composite.save(output_dir / f'{i}.png')
My question is how to add padding white space between each image?
I have found this function that helps me a lot (I put it for others to make use of it)
def add_margin(pil_img, top, right, bottom, left, color):
width, height = pil_img.size
new_width = width + right + left
new_height = height + top + bottom
result = Image.new(pil_img.mode, (new_width, new_height), color)
result.paste(pil_img, (left, top))
return result
This seems pretty clear. You just need to pad the height for the image.
def concat_images(*images):
"""Generate composite of all supplied images."""
# Get the widest width.
width = max(image.width for image in images)
# Add up all the heights.
padding = 10
height = sum(image.height+padding for image in images) - padding
composite = Image.new('RGB', (width, height))
# Paste each image below the one before it.
y = 0
for image in images:
composite.paste(image, (0, y))
y += image.height + padding
return composite
In below example there are three images on a white background in order. How to achieve this in python using CV2 or PIL or any working code.
Thank you.
Image must be aligned according to aspect ratio.
Input = 3 images with BG,
Output = single image as shown in above picture
UPDATE !!!!
Each and every loop only one image gets pasted on BG.
from PIL import Image
import cv2
import numpy as np
d=0
folder = 'save'
image_paths = []
for path, subdirs, files in os.walk(folder):
for filename in files:
f = os.path.join(path, filename)
if f.endswith(".jpg"):
image_paths.append(f)
if f.endswith(".png"):
image_paths.append(f)
if f.endswith(".JPG"):
image_paths.append(f)
if f.endswith(".PNG"):
image_paths.append(f)
if f.endswith(".jpeg"):
image_paths.append(f)
if f.endswith(".JPEG"):
image_paths.append(f)
for image in image_paths:
image = cv2.imread(image)
r = 720.0 / image.shape[1]
dim = (720, int(image.shape[0] * r))
resized = cv2.resize(image, dim)
#resized = resized[:,:,0]
h, w, z = resized.shape
back = cv2.imread('template.jpg')
yoff = round((1080-h)/4)
xoff = round((1920-w)/6)
d+=1
result = back.copy()
result[yoff:yoff+h, xoff:xoff+w] = resized
#result = np.stack((result)*3)
cv2.imwrite('saves/resized_centered_%d.jpg'%d, result)
So multiple images in input gets pasted in a background but the thing is, i want three images to paste in the background instead of one image in order.
NOTE: THE IMAGE ON TOP IS JUST TO REPRESENT MY HELP !!! YOU CAN TELL ME WHATEVER POSSIBLE APART FROM THAT !!!
This line of code moves the image towards top-left and seated properly but likewise i need two more image to be seated on top-right and as well as bottom.
yoff = round((1080-h)/4)
xoff = round((1920-w)/6)
I assume some template like this:
The "final image" has dimensions (1920, 1080) (cf. your calculations on xoff and yoff). Since you wrote, you want to keep the aspect ratio for each "single image", you'd need to check both cases: Resize w.r.t. to the single image's width, and if the resulting height is too large, re-resize w.r.t. to the single image's height.
What's left is to track the number of single images per final image inside the loop, and set up proper xoff and yoff values for each of the three cases. Maybe, looking at the code here helps more than long explanations:
import cv2
import numpy as np
import os
folder = 'path/to/your/images'
image_paths = []
for path, subdirs, files in os.walk(folder):
for filename in files:
f = os.path.join(path, filename)
if f.endswith((".jpg", ".png", ".JPG", ".PNG", ".jpeg", ".JPEG")):
image_paths.append(f)
d = 0 # Final image counter
e = 0 # Single image counter
back = np.ones((1080, 1920, 3), np.uint8) * 255 # Background
result = back.copy() # Final image
for i, image in enumerate(image_paths):
# Read image
image = cv2.imread(image)
h, w = image.shape[:2]
# First two single images: Enforce subimage with h_max = 480 and w_max = 900
if e <= 1:
r = 900.0 / w
dim = (900, int(h * r))
if dim[1] > 480:
r = 480.0 / h
dim = (int(w * r), 480)
resized = cv2.resize(image, dim)
hr, wr = resized.shape[:2]
x_off = 40
if e == 0:
y_off = 40
else:
y_off = 560
# Third single image: Enforce subimage with h_max = 1000 and w_max = 900
else:
r = 900.0 / w
dim = (900, int(h * r))
if dim[1] > 1000:
r = 1000.0 / h
dim = (int(w * r), 1000)
resized = cv2.resize(image, dim)
hr, wr = resized.shape[:2]
x_off, y_off = 980, 40
# Add single image to final image
result[y_off:y_off + hr, x_off:x_off + wr] = resized
# Increment single image counter
e += 1
# After three single images: Write final image; start new final image
if (e == 3) or (i == (len(image_paths) - 1)):
cv2.imwrite('resized_centered_%d.jpg' % d, result)
result = back.copy()
d += 1
e = 0
For some random images from my StackOverflow archive, I get the following outputs:
If you want to have different sized boxes or margins around or between the single images, just adapt the corresponding values in the code.
----------------------------------------
System information
----------------------------------------
Platform: Windows-10-10.0.16299-SP0
Python: 3.9.1
PyCharm: 2021.1.1
NumPy: 1.20.2
OpenCV: 4.5.1
----------------------------------------
I'm not familiar with PIL, but I know it's very easy to put a bunch of images into a grid in ImageMagick.
How do I, for example, put 16 images into a 4×4 grid where I can specify the gap between rows and columns?
This is easy to do in PIL too. Create an empty image and just paste in the images you want at whatever positions you need using paste. Here's a quick example:
import Image
#opens an image:
im = Image.open("1_tree.jpg")
#creates a new empty image, RGB mode, and size 400 by 400.
new_im = Image.new('RGB', (400,400))
#Here I resize my opened image, so it is no bigger than 100,100
im.thumbnail((100,100))
#Iterate through a 4 by 4 grid with 100 spacing, to place my image
for i in xrange(0,500,100):
for j in xrange(0,500,100):
#I change brightness of the images, just to emphasise they are unique copies.
im=Image.eval(im,lambda x: x+(i+j)/30)
#paste the image at location i,j:
new_im.paste(im, (i,j))
new_im.show()
Expanding on the great answer by fraxel, I wrote a program which takes in a folder of (.png) images, a number of pixels for the width of the collage, and the number of pictures per row, and does all the calculations for you.
#Evan Russenberger-Rosica
#Create a Grid/Matrix of Images
import PIL, os, glob
from PIL import Image
from math import ceil, floor
PATH = r"C:\Users\path\to\images"
frame_width = 1920
images_per_row = 5
padding = 2
os.chdir(PATH)
images = glob.glob("*.png")
images = images[:30] #get the first 30 images
img_width, img_height = Image.open(images[0]).size
sf = (frame_width-(images_per_row-1)*padding)/(images_per_row*img_width) #scaling factor
scaled_img_width = ceil(img_width*sf) #s
scaled_img_height = ceil(img_height*sf)
number_of_rows = ceil(len(images)/images_per_row)
frame_height = ceil(sf*img_height*number_of_rows)
new_im = Image.new('RGB', (frame_width, frame_height))
i,j=0,0
for num, im in enumerate(images):
if num%images_per_row==0:
i=0
im = Image.open(im)
#Here I resize my opened image, so it is no bigger than 100,100
im.thumbnail((scaled_img_width,scaled_img_height))
#Iterate through a 4 by 4 grid with 100 spacing, to place my image
y_cord = (j//images_per_row)*scaled_img_height
new_im.paste(im, (i,y_cord))
print(i, y_cord)
i=(i+scaled_img_width)+padding
j+=1
new_im.show()
new_im.save("out.jpg", "JPEG", quality=80, optimize=True, progressive=True)
I have calculated that if I want my generated image to be A4 size # 600dpi for print purpose, it needs to be 7016x4961px # 72dpi. So, I generate it programmatically, then test it in Photoshop and it seems to be fine so if I resize it, it gets proper size and resolution
.
What I wonder about is if it's possible to make this resizing programmatically, preferably with PIL, but not necessarily with it. I need to make it higher DPI.
If you have generated your image 7016 x 4961 px, it is already A4 at 600 dpi. So you don't need to resize it, you just have to set resolution information in file.
You can do it with PIL:
from PIL import Image
im = Image.open("test.png")
im.save("test-600.png", dpi=(600,600))
This code will resize a PNG image into 7016x4961 with PIL:
size = 7016, 4961
im = Image.open("my_image.png")
im_resized = im.resize(size, Image.ANTIALIAS)
im_resized.save("my_image_resized.png", "PNG")
Perhaps a better approach would be to make your canvas x times bigger prior to printing, where x is a factor you have to figure out (7016x4961 in size for this particular image).
Here's how you can resize by batch (per folder) and skip other file types and Mac system files like .DS_Store
from PIL import Image
import os
Image.MAX_IMAGE_PIXELS = None
path = "./*your-source-folder*"
resize_ratio = 2 # where 0.5 is half size, 2 is double size
def resize_aspect_fit():
dirs = os.listdir(path)
for item in dirs:
print(item)
if item == '.DS_Store':
continue
if item == 'Icon\r':
continue
if item.endswith(".mp4"):
continue
if item.endswith(".txt"):
continue
if item.endswith(".db"):
continue
if os.path.isfile(path+item):
image = Image.open(path+item)
file_path, extension = os.path.splitext(path+item)
new_image_height = int(image.size[0] / (1/resize_ratio))
new_image_length = int(image.size[1] / (1/resize_ratio))
image = image.resize((new_image_height, new_image_length), Image.ANTIALIAS)
image.save("./*your-output-folder*/" + item)
resize_aspect_fit()