Skip to content

Instantly share code, notes, and snippets.

@Riordan-DC
Last active May 27, 2023 08:37
Show Gist options
  • Save Riordan-DC/a47cabe5bd2a4820a0c59217bad9490b to your computer and use it in GitHub Desktop.
Save Riordan-DC/a47cabe5bd2a4820a0c59217bad9490b to your computer and use it in GitHub Desktop.
Resizing Images Python Script
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