Last active
May 27, 2023 08:37
-
-
Save Riordan-DC/a47cabe5bd2a4820a0c59217bad9490b to your computer and use it in GitHub Desktop.
Resizing Images Python Script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from PIL import Image, ImageOps | |
import argparse | |
import os | |
""" | |
Resizing Images Python Script | |
Author: Riordan Callil 2023 (riordancallil@outlook.com) | |
Main uses: | |
- resizing a bunch of images for game development. Source images can be very large, i.e. 4K | |
and resizing manually is slow. I use this script to prepare retro game textures, resizing most | |
images down to 128x128 or 256x256 | |
- Outputting a bunch of different splash art. Many platforms (iOS and Steam) require capsule and splash | |
art in multiple resolutions for multiple devices. You can export them all manually for extra fine control | |
but personally I will make a very high res main one and use this script with the crop (-c) argument | |
to cut out all my variants. | |
Example usage: | |
Create three images from japan.jpeg. | |
python3 resize.py input -s 100x50 128x128 50x100 -o output -c | |
iOS splash resizing | |
python3 rips.py landscape_launch_screen.png -s 2048x1536 1024x768 2208x1242 2436x1125 -o . -c -v | |
python3 rips.py portrait_launch_screen.png -s 768x1024 1536x2048 640x960 640x1136 750x1334 1125x2436 1242x2208 -o . -c -v | |
Scaling some high resolution photos from my phone | |
python3 rips.py . -S 0.2 -o resized | |
""" | |
# CLI | |
parser = argparse.ArgumentParser( | |
prog='Image Resize Utility', | |
description='Resizes images', | |
epilog='By Riordan Callil 2023') | |
parser.add_argument('image_dir', type=str, help='The input directory of image or images', default=None) | |
parser.add_argument('-o', '--output', type=str, help='The output directory of images', default='.') | |
parser.add_argument('-s', '--sizes', type=str, nargs='+', help='The new size represented as WxH', action='store', required=False) # option that takes a value | |
parser.add_argument('-e', '--extension', type=str, help='The out image format. e.g: png, jpeg', default='PNG') | |
parser.add_argument('-c', '--crop', action='store_true', help='Non-square images will be cropped, becoming square', default=False) | |
parser.add_argument('-v', '--verbose', action='store_true', default=False) # on/off flag | |
parser.add_argument('-S', '--scale', type=str, help='Float between 0 and 1 representing the scale upon import', required=False) | |
parser.add_argument('-n', '--nearest', action='store_true', help='Nearest pixel image interpolation', default=False) | |
args = parser.parse_args() | |
print('IMAGES: ', args.image_dir) | |
print('OUTPUT: ', args.output) | |
print('SIZES: ', args.sizes) | |
print('SCALE: ', args.scale) | |
# Get images, if we are using on image or multiple | |
image_dirs = [] | |
args.image_dir = args.image_dir.strip() | |
if not os.path.exists(args.image_dir): | |
print('Error: Exiting, image path does not exist.') | |
exit() | |
if not os.path.exists(args.output): | |
print('Error: Exiting, output path does not exist') | |
exit() | |
is_dir = False | |
if os.path.isfile(args.image_dir): | |
image_dirs = [args.image_dir] | |
elif os.path.isdir(args.image_dir): | |
# Some of the dirs wont be images but Pillow wont load them | |
image_dirs = os.listdir(args.image_dir) | |
is_dir = True | |
do_scale = False | |
if args.scale == None: | |
do_scale = False | |
else: | |
args.sizes = ['1x1'] | |
do_scale = True | |
args.scale = float(args.scale) | |
for size in args.sizes: | |
target_size = size.split('x') | |
if len(target_size) != 2: | |
print('Error: invalid size ', size) | |
continue | |
if not target_size[0].isdigit() or not target_size[1].isdigit(): | |
print('Error: invalid digits in size ', size) | |
continue | |
target_size = list(map(int, target_size)) | |
if args.verbose: print('Resizing images to ', size) | |
for image_dir in image_dirs: | |
file, ext = os.path.splitext(image_dir) | |
if is_dir: | |
image_dir = os.path.join(args.image_dir, image_dir) | |
if args.verbose: print('Loading ', image_dir) | |
image = None | |
try: | |
image = Image.open(image_dir) | |
except: | |
print('Cannot load ', image_dir) | |
continue | |
image_resized = None | |
if do_scale == False: | |
# Check if we can resize | |
image_size = image.size | |
if image_size[0] < target_size[0] or image_size[1] < target_size[1]: | |
print(f'Error: image {image_dir} too small to resize') | |
continue | |
box = None | |
if args.crop: | |
if image_size[0] != image_size[1]: | |
# The image will be cropped to the aspect ratio of the target size | |
# The largest side will be cropped by the smaller ratio | |
new_w, new_h = image_size | |
# If the ratio is 1.0 we use the larger side | |
if target_size[0] == target_size[1]: | |
new_w = min(*image_size) | |
new_h = new_w | |
else: | |
h_ratio = target_size[0] / target_size[1] | |
w_ratio = 1 / h_ratio | |
# the ratio means each side is a function of the other side multiplied by the ratio | |
# the issue is that say the ratio is width = 0.6 of height | |
# if height is 1000 therefore we want a width of 600, however our source image has a width of 100 | |
# we cant crop 100 to 600 since it is larger. | |
# so we invert it: height = (1.0/0.6) of width | |
# this means height = 166.666 which is possible! | |
if h_ratio < w_ratio: # We want the smaller size of the ratio | |
# crop the height such that w = h * h_ratio | |
new_w = image_size[1] * h_ratio | |
if new_w > image_size[0]: | |
new_w = image_size[0] | |
new_h = image_size[0] * w_ratio | |
else: | |
# crop the width such that h = w * w_ratio | |
new_h = image_size[0] * w_ratio | |
if new_h > image_size[1]: | |
new_h = image_size[1] | |
new_w = image_size[1] * h_ratio | |
#image = ImageOps.fit(image, (int(new_w), int(new_h))) | |
x = (image_size[0] // 2) - (new_w // 2) | |
y = (image_size[1] // 2) - (new_h // 2) | |
box = [ | |
x, | |
y, | |
x + new_w, | |
y + new_h | |
] | |
#box = None | |
if args.verbose: | |
print(f'Target aspect ratio: {h_ratio}. Image aspect ratio: {new_w/new_h}.') | |
print(box, ' ', new_w, ' ', new_h) | |
else: | |
target_size = [ | |
int(image.size[0] * args.scale), | |
int(image.size[1] * args.scale)] | |
box = None | |
if args.verbose: | |
print(target_size) | |
interpolation = Image.Resampling.BICUBIC | |
if args.nearest: | |
interpolation = Image.Resampling.NEAREST | |
image_resized = image.resize(target_size, interpolation, box) | |
#image_resized = PIL.ImageOps.posterize(image_resized, 2) | |
file_resized = os.path.join(args.output, file + '_' + size + '.' + args.extension) | |
image_resized.save(file_resized, args.extension) | |
if args.verbose: print('Saved ', file_resized) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment