Skip to content

Instantly share code, notes, and snippets.

@ge0rg
Created January 5, 2023 20:42
Show Gist options
  • Save ge0rg/5ffe9a5053ab88fd75587bd0af0f5163 to your computer and use it in GitHub Desktop.
Save ge0rg/5ffe9a5053ab88fd75587bd0af0f5163 to your computer and use it in GitHub Desktop.
Script to combine a set of images in "darken" or "lighten" mode, using PIL, rawpy, blend_modes
#!/usr/bin/env python3
import rawpy
from PIL import Image
from blend_modes import darken_only, lighten_only
from argparse import ArgumentParser
import numpy
import re
import sys
RAW_FORMATS = ['SRW']
parser = ArgumentParser()
parser.add_argument("filenames", metavar='FILE', nargs='+',
help="files to blend together")
parser.add_argument("-d", "--darken",
action="store_true", dest="darken", default=False,
help="darken instead of lighten images")
parser.add_argument("-g", "--gamma", dest="gamma", type=float, default=1.0,
help="raw import gamma")
parser.add_argument("-o", "--output", dest="output", default=None,
help="save result to OUTFILE", metavar="OUTFILE")
parser.add_argument("-q", "--quality", dest="quality", type=int, default=95,
help="jpeg file quality")
parser.add_argument("-s", "--silent",
action="store_false", dest="verbose", default=True,
help="don't print status messages to stdout")
args = parser.parse_args()
def extract_number(fn):
try:
return re.findall('\d+', fn)[-1]
except IndexError:
return None
def load_img(fn):
if fn.split('.')[-1] in RAW_FORMATS:
raw = rawpy.imread(fn)
data = raw.postprocess(gamma=(args.gamma, args.gamma), no_auto_bright=True)
image = Image.fromarray(data)
else:
image = Image.open(fn)
return image.convert('RGBA')
f_first = args.filenames[0]
id_first = extract_number(f_first)
id_last = None
# Import background image
if args.verbose:
print(f"Loading {f_first}...")
background_img_raw = load_img(f_first) # RGBA image
background_img = numpy.array(background_img_raw) # Inputs to blend_modes need to be numpy arrays.
merged_float = background_img.astype(float) # Inputs to blend_modes need to be floats.
for f_next in args.filenames[1:]:
id_last = extract_number(f_next)
if args.verbose:
print(f"Adding {f_next}...")
# Import foreground image
foreground_img_raw = load_img(f_next) # RGBA image
foreground_img = numpy.array(foreground_img_raw) # Inputs to blend_modes need to be numpy arrays.
foreground_img_float = foreground_img.astype(float) # Inputs to blend_modes need to be floats.
# Blend images
if args.darken:
merged_float = darken_only(merged_float, foreground_img_float, opacity=1.0)
else:
merged_float = lighten_only(merged_float, foreground_img_float, opacity=1.0)
# Convert blended image back into PIL image
blended_img = numpy.uint8(merged_float) # Image needs to be converted back to uint8 type for PIL handling.
blended_img_raw = Image.fromarray(blended_img) # Note that alpha channels are displayed in black by PIL by default.
# This behavior is difficult to change (although possible).
# If you have alpha channels in your images, then you should give
# OpenCV a try.
if not args.output:
# build name from extracted image number(s)
if id_first and id_last:
args.output = f"blended_{id_first}-{id_last}.jpg"
elif id_first:
args.output = f"blended_{id_first}.jpg"
else:
args.output = f"blended.jpg"
# Display blended image
if args.verbose:
print(f"Saving {args.output}...")
blended_img_raw.convert('RGB').save(args.output, quality=args.quality, optimize=True, progressive=True)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment