Skip to content

Instantly share code, notes, and snippets.

@3lpsy
Last active July 30, 2020 16:43
Show Gist options
  • Save 3lpsy/cbe6accfac7ec3cbdc2aea989e92850d to your computer and use it in GitHub Desktop.
Save 3lpsy/cbe6accfac7ec3cbdc2aea989e92850d to your computer and use it in GitHub Desktop.
Add a drop shadow effect to one or multiple images
#!/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()
@3lpsy
Copy link
Author

3lpsy commented Jul 30, 2020

usage: shadowify.py [-h] [-s SRC] [-S SRCDIR] [-d DEST] [-p POSTFIX] [-D DESTDIR]

optional arguments:
  -h, --help            show this help message and exit
  -s SRC, --src SRC     A single source file
  -S SRCDIR, --srcdir SRCDIR
                        A directory to recursively convert
  -d DEST, --dest DEST  A single destination location
  -p POSTFIX, --postfix POSTFIX
                        string to add to source base name if no dest is specified
  -D DESTDIR, --destdir DESTDIR
                        A directory to output recrusively converted files to

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment