0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Python][Pillow]画像ファイルのリサイズ(コマンドライン)<その1>

Last updated at Posted at 2021-11-17

pillow の resize をコマンドラインから操作できるよう、一般化した。
オプションは、ImageMagick の resize っぽく。
今後、機能追加予定。

resize.py
import os
import sys
import argparse
import glob
import re
from PIL import Image
from decimal import Decimal, getcontext, FloatOperation, ROUND_HALF_UP

getcontext().traps[FloatOperation] = True


def create_parser():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "source",
        type=str,
        help="This is source. (Specify a file or directory. Wildcards cannot be used.)"
    )
    parser.add_argument(
        "dest_dir",
        type=str,
        nargs="?",
        default=None,
        help="This is destination dir."
    )
    parser.add_argument(
        "-s", "--size",
        type=str,
        required=True,
        help="This is size parameter. ex.) 800, 600x400, 350x240!, 450x, x400 etc."
    )
    parser.add_argument(
        "-f", "--filter",
        type=str,
        choices=["NEAREST", "BOX", "BILINEAR", "HAMMING", "BICUBIC", "LANCZOS"],
        help="This is filter parameter."
    )
    parser.add_argument(
        "-t", "--thumbnail",
        action="store_true",
        help="This is thumbnail option. Fixed aspect ratio."
    )
    parser.add_argument(
        "-v", "--verbose",
        action="store_true",
        help="Give more output."
    )
    return parser


def resize(f, arg_size, dest_dir, resample, thumbnail, verbose):
    size_opt = arg_size.lower()
    x_pos = size_opt.find("x")
    width_str = size_opt[:x_pos]
    height_str= size_opt[x_pos + 1:]
    non_ope = False

    try: ######### File open
        img = Image.open(f)
        
        try: ######### Size option check
            w_spec = get_size(width_str, img.width)
            h_spec = get_size(height_str, img.height)

            if not thumbnail:
                if x_pos == -1: ######### Width only
                    w = get_size(size_opt, img.width)
                    width, height = ref_width(w, img)

                else: ############ Width & height

                    if height_str == "": # Fixed aspect ratio
                        width, height = ref_width(w_spec, img)
                    
                    elif width_str == "": # Fixed aspect ratio
                        width, height = ref_height(h_spec, img)

                    elif size_opt[-1] == "!": # Ignore aspect ratio
                        width ,height = w_spec, h_spec

                    elif size_opt[-1] == ">": # Fixed aspect ratio
                        if img.width > w_spec and img.height > h_spec:
                            width, height = adapt_size(size_opt, img)
                        else:
                            non_ope = True

                    elif size_opt[-1] == "<": # Fixed aspect ratio
                        if img.width < w_spec and img.height < h_spec:
                            width, height = adapt_size(size_opt, img)
                        else:
                            non_ope = True

                    elif size_opt[-1] == "^": # Fixed aspect ratio
                        if img.width < img.height:
                            width, height = ref_width(w_spec, img)
                        else:
                            width, height = ref_height(h_spec, img)

                    else: # Fixed aspect ratio
                        width, height = adapt_size(size_opt, img)

        except:
            print("ERROR: Invalid size option.")
            sys.exit(1)

        if non_ope and verbose:
            print("Non operation: ", f)
        
        else:
            root, ext = os.path.splitext(f)
            file_name = os.path.basename(root)

            if thumbnail:
                img.thumbnail((w_spec, h_spec), resample)
                img.save(os.path.join(dest_dir, file_name + ext))
                if verbose:
                    print("Success <Thumbnail> <%sx%s>: " % (w_spec, h_spec) + dest_dir + "/" + file_name + ext)
            else:
                img_resize = img.resize((int(width), int(height)), resample)
                img_resize.save(os.path.join(dest_dir, file_name + ext))
                if verbose:
                    print("Success <Resize> <%sx%s>: " % (width, height) + dest_dir + "/" + file_name + ext)

    except OSError as e:
        print("Error: ", f)
        pass

def adapt_size(size_opt, img):
    x_pos = size_opt.find("x")
    width_str = size_opt[:x_pos]
    height_str= size_opt[x_pos + 1:]
    w_spec = get_size(width_str, img.width)
    h_spec = get_size(height_str, img.height)

    if img.width >= img.height:
        w_temp, h_temp = ref_width(w_spec, img)
        if h_temp > h_spec:
            return ref_height(h_spec, img)

    else:
        w_temp, h_temp = ref_height(h_spec, img)
        if w_temp > w_spec:
            return ref_width(w_spec, img)

    return w_temp, h_temp

def ref_width(w_spec, img):
    try:
        height = round_halfup(img.height * w_spec / img.width)
        return w_spec, height
    except:
        sys.exit(1)

def ref_height(h_spec, img):
    try:
        width = round_halfup(img.width * h_spec / img.height)
        return width, h_spec
    except:
        sys.exit(1)

def get_size(size_str, source_size):
    if size_str == "": return
    p = '\d|.'
    scale = percent(size_str)
    try:
        if scale is None:
            return Decimal("".join(filter(str.isdigit, size_str)))
        else:
            ratio = Decimal("".join(filter(lambda s:re.fullmatch(p, s), scale)))
            return round_halfup(source_size * ratio / 100)
    except:
        sys.exit(1)

def percent(s):
    unit_pos = s.find("%")
    if unit_pos == -1:
        return None
    else:
        return s[:unit_pos]

def round_halfup(decimal_value):
    return decimal_value.quantize(Decimal('0'), rounding=ROUND_HALF_UP)


def main():
    parser = create_parser()
    args = parser.parse_args()

    if args.filter is None:
        resample = Image.NEAREST
    else:
        filter_arg = args.filter.upper()
        if filter_arg == "NEAREST":
            resample = Image.NEAREST
        elif filter_arg == "BOX":
            resample = Image.BOX
        elif filter_arg == "BILINEAR":
            resample = Image.BILINEAR
        elif filter_arg == "HAMMING":
            resample = Image.HAMMING
        elif filter_arg == "BICUBIC":
            resample = Image.BICUBIC
        elif filter_arg == "LANCZOS":
            resample = Image.LANCZOS
        else:
            resample = Image.NEAREST
    
    dest_dir = "." if args.dest_dir is None else args.dest_dir
    try:
        os.makedirs(dest_dir, exist_ok=True)
    except FileExistsError as e:
        print("ERROR: dest_dir is " + e.filename)
        sys.exit(1)

    if os.path.isfile(args.source):
        resize(args.source, args.size, dest_dir, resample, args.thumbnail, args.verbose)
    
    elif os.path.isdir(args.source):
        files = glob.glob(args.source + "/*")
        for f in files:
            resize(f, args.size, dest_dir, resample, args.thumbnail, args.verbose)

    else:
        print("ERROR: Source file or dir does not exist.")
        sys.exit(1)


if __name__ == '__main__':
    main()

github はこちら

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?