#!/bin/bash # # jigsaw [options] photo_image mask_image destination # # This program takes a image like a photo and mask image (same size as photo), # and cut out that shaped piece from the original image. The piece can then # be rotated, thickness added, and shadowed. # # The returned image will, by default, be as small as posible, with the input # mask trimmed down, unless preserved or set to a specific size. In all cases # the offset of the resulting mask, and cutout pieces will be preserved # relative to the source photo image. # # By using this script it should be posible to generate a complete set of # jigsaw pieces, with multiple rotations. # # Image arguments may be pipes as they will only be used once. # # OPTIONS # -o offset Mask offset, overriding any existing offset in mask image # -d offset Displace the jigsaw piece by this offset from cut position # # -m Return the mask that will be used, ignore photo argument # -mp Preserve the input mask as is, don't trim or adjust its size # -ms size Trim/expand mask to fit the size given, with peice centered # Warning: mask should include enough space for any effects # # -l Add an outline along egde of piece # -r angle Rotate piece by this angle # -h Add highlight to piece # -t pixels Add thickness to piece # -s Add a soft shadow to piece # #### # # WARNING: Input arguments are NOT tested for correctness. # This script represents a security risk if used ONLINE. # I accept no responsiblity for misuse. Use at own risk. # # Anthony Thyssen 26 May 2006 # PROGNAME=`type $0 | awk '{print $3}'` # search for executable on path PROGDIR=`dirname $PROGNAME` # extract directory of program PROGNAME=`basename $PROGNAME` # base name of program Usage() { # output the script comments as docs echo >&2 "$PROGNAME:" "$@" sed >&2 -n '/^###/q; /^#/!q; s/^#//; s/^ //; 3s/^/Usage: /; 2,$ p' \ "$PROGDIR/$PROGNAME" exit 10; } # Controls for shape enhancements shade_angle=120 # lighting angle of the highlight is to use shade_blur=2 # highlight blurring (smoothing) # 0.001 none 2 good 10 very rounded edge shade_surface=5 # How shiny are bright spots (reduce non-spot area) # 1 matte 5 good 10 shiny spots only shade_contrast=10 # Overall reduction of highlight intensity # 2 strong highlights 50 near nothing # t_color=DarkSlateGrey # color of added thickness shadow=40x4+4+4 # shadow argument # --------------------- # Add the fixed option parts (do not adjust) shade_angle="${shade_angle}x21.78" # shade azmuth angle shade_blur="0x$shade_blur" # rounding blur radius limit shade_surface="${shade_surface}x50%" # sigmodial-cotrast midpoint shade_contrast="${shade_contrast}%" # reduce highlight intensity # Temporary working images (with auto-clean-up on exit) tmp1=`mktemp "${TMPDIR:-/tmp}/$PROGNAME.XXXXXXXXXX.miff"` || { echo >&2 "$PROGNAME: Unable to create temporary file"; exit 10;} tmp2=`mktemp "${TMPDIR:-/tmp}/$PROGNAME.XXXXXXXXXX.miff"` || { echo >&2 "$PROGNAME: Unable to create temporary file"; exit 10;} trap "rm -f $tmp1 $tmp2" 0 trap "exit 2" 1 2 3 15 # read in commandline options while [ $# -gt 0 ]; do case "$1" in --help|--doc*) Usage ;; -o) shift; INIT_OFFSET="$1" ;; # an initial offset for the mask -d) shift; DISPLACE="$1" ;; # offset displacement of piece -m) MASK_ONLY=true ;; # return trimmed mask (photo ignored) -mp) PRESERVE=true ;; # preserve the image mask and results as cut -ms) shift; MASK_SIZE="$1" ;; # Crop mask to fit the sized image -l) OUTLINE=true ;; # add an outline along egde of piece -r) shift; ROTATE=$1 ;; # rotate piece by this angle -h) HIGHLIGHT=true ;; # add highlight to piece -t) shift; THICKNESS="$1" ;; # add thickness to piece -s) SHADOW=true ;; # add soft shadow to piece --) shift; break ;; # end of user options -*) Usage "Unknown option \"$1\"" ;; *) break ;; # end of user options esac shift # next option done [ $# -lt 3 ] && Usage "Too few arguments." [ $# -gt 3 ] && Usage "Too many arguments." # Now the image files (may be '-' for pipelined commands) photo_input="$1" mask_input="$2" destination="$3" # FUTURE: check input arguments are in correct format # This can not be done securly in shell, a perl version is needed # before this script can use online (web) supplied user arguments. # if [ "X$MASK_SIZE" != 'X' -a "$PRESERVE" ]; then echo >&2 \ "$PROGNAME: Mask size \"-ms\" ignored due to mask preserve \"-mp\" option" fi if [ "$MASK_ONLY" ] && \ [ "$OUTLINE" -o "$HIGHLIGHT" -o "$SHADOW" \ -o "X$ROTATE" != 'X' -o "X$THICKNESS" != 'X' ]; then echo >&2 \ "$PROGNAME: Piece enhancment ignored, when mask only \"-m\" otpion given" fi # ----------------------------------------------------- # Subroutine to add the given offset to the current known offset offset_add() { set -$- -- `echo $1 | sed 's/[+-]/ &/g;s/+//g'` x=`expr $x + $1` y=`expr $y + $2` offset=`printf '%+d%+d' $x $y` } x=0; y=0; offset=+0+0 # current offset of piece over original image # ----------------------------------------------------- # Read in the mask (once only) magick "$mask_input" miff:$tmp2 if [ ! -s $tmp2 ]; then echo >&2 "$PROGNAME: Mask Image "$mask_input" read failed -- aborting" exit 1 fi # if no starting offset given, see if mask has one if [ "X$INIT_OFFSET" = 'X' ]; then # first lets see if mask has a page offset OFFSET=`magick identify -format %O $tmp2` # If no page offset, look for a offset comment (of the right format) # This is depreciated method of saving the images offset if [ "X$OFFSET" = 'X+0+0' ]; then OFFSET=`magick identify -format %c $tmp2 | grep '^[-+][0-9][0-9]*[-+][0-9][0-9]*$'` fi else OFFSET="$INIT_OFFSET" fi # Now save any starting offset (either from image, or command line option) if [ "X$OFFSET" != 'X' ]; then offset_add "$OFFSET" fi if [ -z "$PRESERVE" ]; then # Trim the mask and adjust offset due to trim (assumes mask is greyscale!) offset_add `magick $tmp2 +repage \ -bordercolor black -border 1x1 -repage -1-1 \ -trim -format %O -identify +repage $tmp2` if [ "X$MASK_SIZE" != 'X' ]; then # Center the mask on an image of the size given magick $tmp2 -bordercolor black -border $MASK_SIZE \ -gravity center -crop $MASK_SIZE+0+0\! +repage \ -background black -flatten $tmp1 # adjust mask offset after trimming the mask. set -$- -- `magick identify -format "%w %h " $tmp1 $tmp2` offset_add "`expr \( $3 - $1 \) / 2` `expr \( $4 - $2 \) / 2`" mv $tmp1 $tmp2 # replace original mask input with new one PRESERVE=true # do not resize mask or image again from this point fi fi if [ "$MASK_ONLY" ]; then # Return the trimmed mask, with adjusted offset (for later re-use). magick -page $offset $tmp2 "$destination" exit 0 fi # -------------- # Read the Source Photo Image (once only) magick "$photo_input" +repage miff:$tmp1 if [ ! -s $tmp1 ]; then echo >&2 "$PROGNAME: Photo Image "$photo_input" read failed -- aborting" exit 1 fi # Cut out the Source image at the current offset (add outline) crop=`magick identify -format %wx%h$offset $tmp2` if [ "$OUTLINE" ]; then magick $tmp1 -crop $crop\! -background none -flatten +repage \ \( $tmp2 -edge 1 -blur 0x.5 -normalize -negate \) \ -compose multiply -composite \ \( $tmp2 +matte \) -compose CopyOpacity -composite \ $tmp1 else magick $tmp1 -crop $crop\! -background none -flatten +repage \ \( $tmp2 +matte \) -compose CopyOpacity -composite \ $tmp1 fi # Rotate the piece just cut out if [ "$ROTATE" ]; then if [ -z "$PRESERVE" ]; then # Trimmed rotate, adjust offset as a result of rotate magick $tmp1 -matte -background none -rotate "$ROTATE" \ -bordercolor none -border 1x1 -trim +repage $tmp1 set -$- -- `magick identify -format "%w %h " $tmp1 $tmp2` offset_add "`expr \( $3 - $1 \) / 2` `expr \( $4 - $2 \) / 2`" else # Rotate but preserve original masked size (no offset adjust needed) magick $tmp1 -matte \( +clone -background none -rotate "$ROTATE" \) \ -gravity center -compose Src -composite $tmp1 fi fi rm -f $tmp2 # original mask is now no longer needed # Adjust location of the piece just cut out if [ "X$DISPLACE" != 'X' ]; then offset_add "$DISPLACE" fi if [ "$HIGHLIGHT" ]; then # Add highlights to the cutout piece magick $tmp1 \ \( +clone -channel A -separate +channel -negate \ -background black -virtual-pixel background \ -blur $shade_blur -shade $shade_angle -normalize \ -contrast-stretch 0% -fill grey50 -colorize $shade_contrast \ +sigmoidal-contrast $shade_surface \ +clone -compose Overlay -composite \ \) -compose In -composite $tmp1 fi if [ "$THICKNESS" ]; then if [ -z "$PRESERVE" ]; then # first add some extra space magick $tmp1 -background none -gravity SouthEast \ -splice $[(${THICKNESS}+1)/2]x${THICKNESS} $tmp1 fi # now generate a 'variable' thickness command. magick $tmp1 \( +clone -fill DarkSlateGrey -colorize 100% -repage +0+1 \) \ $( for i in $( seq 2 "$THICKNESS" ); do printf '( +clone -repage %+d%+d ) ' $[ ($i+1)/2 ] $i; done ) -background none -compose DstOver -flatten $tmp1 fi if [ "$SHADOW" ]; then if [ -z "$PRESERVE" ]; then # first add some extra space magick $tmp1 -background none -gravity SouthEast -splice 8x8 $tmp1 fi # now add shadow without resizing the resulting image magick $tmp1 \( +clone -background Black -shadow 50x3+4+4 \) \ -background none -compose DstOver -flatten $tmp1 fi # Output the result, adding offset to image format (for PNG, GIF, MIFF) magick -page $offset $tmp1 "$destination"