Last active
July 30, 2020 16:43
-
-
Save 3lpsy/cbe6accfac7ec3cbdc2aea989e92850d to your computer and use it in GitHub Desktop.
Add a drop shadow effect to one or multiple images
This file contains hidden or 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
#!/usr/bin/env python3 | |
from PIL import Image, ImageFilter | |
import argparse | |
from pathlib import Path | |
from typing import Optional | |
DEFAULT_POSTFIX = "-ds" | |
class BadArgsException(Exception): | |
pass | |
# yoinked code | |
""" | |
Drop shadows with PIL. | |
Author: Kevin Schluff | |
License: Python license | |
""" | |
def dropShadow( | |
image, offset=(5, 5), background=0xFFFFFF, shadow=0x444444, border=8, iterations=3 | |
): | |
""" | |
Add a gaussian blur drop shadow to an image. | |
image - The image to overlay on top of the shadow. | |
offset - Offset of the shadow from the image as an (x,y) tuple. Can be | |
positive or negative. | |
background - Background colour behind the image. | |
shadow - Shadow colour (darkness). | |
border - Width of the border around the image. This must be wide | |
enough to account for the blurring of the shadow. | |
iterations - Number of times to apply the filter. More iterations | |
produce a more blurred shadow, but increase processing time. | |
""" | |
# Create the backdrop image -- a box in the background colour with a | |
# shadow on it. | |
totalWidth = image.size[0] + abs(offset[0]) + 2 * border | |
totalHeight = image.size[1] + abs(offset[1]) + 2 * border | |
back = Image.new(image.mode, (totalWidth, totalHeight), background) | |
# Place the shadow, taking into account the offset from the image | |
shadowLeft = border + max(offset[0], 0) | |
shadowTop = border + max(offset[1], 0) | |
back.paste( | |
shadow, | |
[shadowLeft, shadowTop, shadowLeft + image.size[0], shadowTop + image.size[1]], | |
) | |
# Apply the filter to blur the edges of the shadow. Since a small kernel | |
# is used, the filter must be applied repeatedly to get a decent blur. | |
n = 0 | |
while n < iterations: | |
back = back.filter(ImageFilter.BLUR) | |
n += 1 | |
# Paste the input image onto the shadow backdrop | |
imageLeft = border - min(offset[0], 0) | |
imageTop = border - min(offset[1], 0) | |
back.paste(image, (imageLeft, imageTop)) | |
return back | |
# end of code thievery | |
def single(src: Path, dest: Optional[Path], postfix: Optional[str]): | |
print(f"Targeting: {src}") | |
if not postfix: | |
postfix = DEFAULT_POSTFIX | |
if not src.exists() or not src.is_file(): | |
print(f"{src} is not a file") | |
raise BadArgsException() | |
if not dest: | |
if "." not in str(src.name): | |
dest_path = str(src.resolve().parent.joinpath(src.name + postfix)) | |
else: | |
partition = src.name.rsplit(".", 1) | |
dest_path = str( | |
src.resolve().parent.joinpath( | |
partition[0] + postfix + "." + partition[1] | |
) | |
) | |
print(f"Determined destination: {dest_path}") | |
else: | |
dest_path = str(dest) | |
print(f"Using destination: {dest_path}") | |
image = Image.open(str(src)) | |
print("Computing...") | |
new_image = dropShadow(image) | |
print("Saving...") | |
new_image.save(dest_path, "PNG") | |
def recurse(srcdir: Path, destdir: Optional[Path], postfix: Optional[str]): | |
if not postfix: | |
postfix = DEFAULT_POSTFIX | |
if not srcdir.exists() or not srcdir.is_dir(): | |
print(f"{srcdir} is not a directory") | |
raise BadArgsException() | |
if not destdir: | |
destdir = srcdir | |
if destdir.exists() and destdir.is_file(): | |
print(f"{destdir} already exists and is a file, not a directory") | |
else: | |
if not destdir.exists(): | |
destdir.mkdir() | |
print(f"Destination directory: {destdir}") | |
print(f"Finding images to modify:") | |
for src in srcdir.iterdir(): | |
print("") | |
if not src.is_file(): | |
print(f"Skipping sub directory: {src.name}") | |
continue | |
if "." in src.name: | |
partition = src.name.rsplit(".", 1) | |
if partition[0].endswith(postfix): | |
print(f"Skipping already converted: {partition[0]}") | |
continue | |
if src.suffix.lower() in [".png"]: | |
# postfix has to be preapplied | |
partition = src.name.rsplit(".", 1) | |
dest_path = src.resolve().parent.joinpath( | |
partition[0] + postfix + "." + partition[1] | |
) | |
single(src.resolve(), dest_path, None) | |
else: | |
# TODO: ability to customize png | |
print(f"Unsupported file type: {src.name}. skipping") | |
if __name__ == "__main__": | |
import sys | |
parser = argparse.ArgumentParser() | |
parser.add_argument("-s", "--src", type=str, help="A single source file") | |
parser.add_argument( | |
"-S", "--srcdir", type=str, help="A directory to recursively convert" | |
) | |
parser.add_argument("-d", "--dest", type=str, help="A single destination location") | |
parser.add_argument( | |
"-p", | |
"--postfix", | |
type=str, | |
help="string to add to source base name if no dest is specified", | |
) | |
parser.add_argument( | |
"-D", | |
"--destdir", | |
type=str, | |
help="A directory to output recrusively converted files to", | |
) | |
args = parser.parse_args() | |
if args.dest: | |
dest = Path(args.dest) | |
else: | |
dest = None | |
if args.destdir: | |
destdir = Path(args.destdir) | |
else: | |
destdir = None | |
try: | |
if args.src: | |
single(Path(args.src), dest, args.postfix) | |
elif args.srcdir: | |
recurse(Path(args.srcdir), destdir, args.postfix) | |
else: | |
parser.print_help() | |
except BadArgsException as e: | |
parser.print_help() | |
sys.exit(1) | |
# dropShadow(image, background=0xEEEEEE, shadow=0x444444, offset=(0, 5)).show() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
As of now, only .png files are supported as the save format is hardcoded. This can be easily modified.by changing lines 103 and 131