#!/bin/bash 
# B9 June 2017

# mktrans 
# This is similar to ImageMagick's bg_removal script, but much higher
# quality. (It's also faster and simpler to use.) 
#
# For a sample, run these commands:
#    convert logo: logo.png
#    mktrans logo.png
#    display logo-transparent.png


# Fuzz is how far off the background color can be (in percent).
# This is important for getting good antialiasing.
defaultfuzz=20

usage() {
    cat <<EOF

mktrans: Convert images into shaped transparent pngs by floodfilling
	 the background with transparency (antialiased alpha channel).
	 Unless a different starting pixel is specified, the top left
	 pixel is used as the "background" color to remove and
	 floodfill starts from all four image edges,

Typical usage: 

	mktrans foo.jpg		(creates foo-transparent.png)

Usage: mktrans  [-f <fuzz>]  [-s|-S]  [-p <x>,<y>]  [-v]  <files ... >

	-f <fuzz>: How loosely to match the background color (default $defaultfuzz%)

    	   	   Advanced options:
	       -s: Use speedy antialiasing (much faster, slightly less acurate) 
	       -S: Supress antialiasing completely. (Useful for repeated runs)
	p <x>,<y>: Floodfill from pixel at x,y instead of 0,0
	       -v: Verbose
EOF

# * Side note: This creates an antialiased (blurred) alpha channel
#   that is also eroded by half a pixel to avoid halos. ImageMagick's
#   morphological operations don't (yet?) work at the subpixel level,
#   so I'm blowing up the alpha channel to 200% before eroding. Since
#   this can be slow on large images, consider using the '-s' option
#   which uses a faster, lower quality antialiasing.

# * Running this script on an image that already has transparency will
#   erode the image due to the antialiasing. Using -S is a workaround,
#   but is not very satisfactory. Perhaps this script should remove any
#   existing transparency before manipulating the image and then add it
#   back in at the end. But then again, how often are people going to
#   want to do that? The only use I can think of is when using -p.

# * Because of the previous bug, if you do use -p to fill lots of
#   lagoons, you'll probably want to use -A at the same time.

# * Finding the coordinates for -p is a pain. It'd be nice if there was
#   a -P option which let the user click on a point (or multiple points)
#   in the image to start the floodfill.

    exit 0
}

fuzz=$defaultfuzz
pixelcomma="0,0"
pixelplus="+0+0"

while getopts f:sAShp:v name; do
    case $name in
        f) fuzz=$OPTARG
           ;;
        s) sflag=True
           ;;
        S|A) noantialias=True
           ;;
        v) vflag=True
           ;;
        h) usage
           ;;
        p) pixelcomma=$OPTARG
           pixelplus=+${OPTARG%,*}+${OPTARG#*,}
           pflag=True
           ;;
        *) usage
           ;;
    esac
done

shift $((OPTIND-1))
[[ "$#" != 0 ]] || usage


for filename; do
    # Get color of 0,0 (top left) pixel
    color=$(convert "$filename" -format "%[pixel:p{$pixelcomma}]" info:-)
    if [[ "$color" == *rgba*",0)" ]]; then
	color="${color%,0)},1)"	# Floodfill only works with opaque colors.
    fi
    if [[ "$color" == "none" ]]; then
	echo "Error: $filename: pixel at $pixelcomma is completely transparent. Cannot floodfill." >&2
	continue
    fi

    options=""
    if [ -z "$pflag" ]; then
	# Add a 1 pixel border so we'll fill from the bottom and sides as well.
	options+=" -bordercolor $color -border 1 "
    fi
    # In a new stack, make a copy of the image
    options+=" ( +clone "
    # [copy] floodfill with transparency ("none") starting at top-left
    options+="		-fuzz $fuzz% -fill none -floodfill $pixelplus $color"
    # [copy] extract just the transparency (alpha channel)
    options+="		-alpha extract"

    if [ -z "$noantialias" ]; then
	if [ -z "$sflag" ]; then
            # [copy] blow up the alpha channel so we can do sub-pixel morphology
            options+="	-geometry 200%"
            # [copy] blur the alpha channel to make it antialiased
            options+="	-blur 0x0.5"
            # [copy] shrink the region that is opaque by half a pixel.
            options+="	-morphology erode square:1"
            # [copy] scale the alpha channel back to normal size.
            options+="	-geometry 50%"
	else	# sflag: speedy antialias
            # [copy] blur the alpha channel to make it antialiased
            options+="	-blur 0x1"
            # [copy] only antialias inside the figure (<50% opacity becomes 0%)
            options+="	-level 50%,100%"
	fi
    fi
    # [copy] end the stack.
    options+=" ) "
    # Compose the original image and the copy's alpha channel.
    options+=" -compose CopyOpacity -composite"
    if [ -z "$pflag" ]; then
	# Remove the 1 pixel border we added
	options+=" -shave 1"
    fi
    
    [ "$vflag" ] && echo convert "$filename" $options "${filename%.*}-transparent.png"

    convert "$filename" $options "${filename%.*}-transparent.png"
done
