#!/bin/ksh
# @(#) brandimg.ksh 1.0 94/04/17
# 94/04/17 john h. dubois iii (john@armory.com)

# Usage: pnminfo File
# Puts the width of File in InfX, and the height in InfY
typeset -i InfX InfY
function pnminfo {
    typeset j1 j2 j3 j4

    if [ $# -gt 0 ]; then
	pnmfile "$1" | read j1 type j2 InfX j3 InfY j4
    else
	pnmfile | read j1 type j2 InfX j3 InfY
    fi
}

# Finds the width and height of the piece to cut out of the image for branding,
# and stores them in globals Width and Height.
# Also sets ExtractProc and InsertProc to pipelines that should be used in
# extracting material from image and inserting modified material back into
# image, if it is neccessary to flip the material or truncate the brand.
# Usage: FindCutParms filename brand-width brand-height
# filename is the image file to cut from.
# brand-width and brand-height are the dimensions of the branding files.
# Other global vars used: Debug
typeset -i Width Height
function FindCutParms {
    typeset File=$1 Flip=false
    typeset -i BrandWidth=$2 BrandHeight=$3 tm

    ExtractProc=
    InsertProc=
    Width=BrandWidth
    Height=BrandHeight
    pnminfo $File
    $Debug && print -u2 "Image $File: width=$InfX height=$InfY"
    # -le is used instead of -lt because image must be one pixel larger
    # than brand due to bug in some pnm utilities
    if [ InfX -le BrandWidth ]; then
	if [ InfY -gt BrandWidth ]; then
	    Flip=true
	else
	    $Debug && print -u2 "Brand won't fit; truncating."
	    let Width=Width-1
	    ExtractProc="|Expand 0 $((BrandWidth-Width)) 0 0"
	    InsertProc="pnmcut 0 0 $Width $Height|"
	    print -u2 \
"Truncated brand for $File to $(((100*Width)/BrandWidth))% of original length."
	    [ InfX -lt InfY ] && Flip=true
	fi
    fi
    if $Flip; then
	tm=Width
	Width=Height
	Height=tm
	ExtractProc="|pnmflip -ccw $ExtractProc"
	InsertProc="$InsertProc pnmflip -cw|"
	$Debug && print -u2 "Inserting brand vertically."
    else
	$Debug && print -u2 "Inserting brand horizontally."
    fi
    $Debug && print -u2 \
    "Addtl extraction pipe: $ExtractProc\nAddtl insertion pipe: $InsertProc"
    return 0
}

# Usage: Invert conversion-type source-material mask
# Operations are performed on the source material to make it suitable for
# branding.
# Material is then cut out using mask and sent to the standard output.
# Conversion is one of [iIbBmM]
# iI Invert only	bB Quantize to 8 colors        mM Render in black/white
# IBM Convolve first
# Source is inverted, then convolved if required, 
# then quantized or reduced to black/white if required.
# Global vars used: ConvolTmp, QuantTmp
function Invert {
    typeset Conversion=$1 Source=$2 Mask=$3 Cmd=

    if [[ "$Conversion" = [IBM] ]]; then
	# This convolution replaces each pixel with the average of its
	# neighbors, with no weight given to the pixel itself.
	# Note that the edges pixels won't be convolved since it's only
	# done on pixels that have neighbors all around.
	Cmd="|pnmconvol $ConvolTmp"
	[ ! -f $ConvolTmp ] && echo \
"P2
3 3
16
9 9 9
9 8 9
9 9 9" > $ConvolTmp
    fi

    case "$Conversion" in
    [bB]) Cmd="$Cmd | ppmquant -map $QuantTmp"
	# This will push colors to 0 or full value
	[ ! -f $QuantTmp ] && echo \
"P3
8 1
255
  0   0   0
255   0   0
  0 255   0
  0   0 255
255 255   0
255   0 255
  0 255 255
255 255 255" > $QuantTmp
	;;
    [mM])
	Cmd="$Cmd | ppmtopgm | pgmtopbm -threshold -value 0.5"
	;;
    esac
    # Convert source to the form that we want to use,
    # and then cut what we want out of it
    $Debug && print -u2 \
    "Inversion type: $Conversion\nInversion pipe extra commands: $Cmd
Inversion source file: $Source\nInversion mask: $Mask"
    eval pnminvert $Source $Cmd | pnmarith -mult $Mask -
    return 0
}

# Usage: MakeText "text" output-file border-width [fontfile]
# Makes pbm text, using fontfile if given, and puts output in output-file.
# The text is cropped to remove margins.
# If border-width is nonzero, a margin of the given thickness is left.
# Sets globals TextWidth and TextHeight to width and height of output file.
# Other global variables: CroppedTmp, Debug
function MakeText {
    typeset Text=$1 OutputFile=$2 FontOpt FontFile TmpOutput
    typeset -i BorderWidth=$3

    if [ $# -eq 4 ]; then
	FontFile=$4
	$Debug && print -u2 "Font file is $FontFile"
	if [ ! -r "$FontFile" ]; then
	    print -u2 "Cannot read font file $FontFile.  Exiting."
	    exit 1
	fi
	FontOpt="-font $FontFile"
    fi
    [ $BorderWidth -eq 0 ] && TmpOutput=$OutputFile || TmpOutput=$CroppedTmp
    # Fonts are black on white.
    # Crop white off, then invert to give white on black.
    pbmtext $FontOpt "$Text" | pnmcrop -white | pnminvert > $TmpOutput
    pnminfo $TmpOutput
    if [ $BorderWidth -ne 0 ]; then
	# Add margin around text.
	TextWidth="InfX+(2*BorderWidth)"
	TextHeight="InfY+(2*BorderWidth)"
	pbmmake -black $TextWidth $TextHeight |
	pnmpaste -replace $TmpOutput $BorderWidth $BorderWidth > $OutputFile
    else
	TextWidth=InfX
	TextHeight=InfY
    fi
    $Debug && print -u2 "Text width=$TextWidth; height=$TextHeight"
    return 0 
}

# Usage: 
# MakeMasks text width height border-width text-color allmask-file bmask-file
# Make a white-on-black masks for the border and for all of the fixed material
# (the text mask is the text itself).  
# Textfile should be white on black.  Its size is given by width & height.
# Border mask is written to bmask-file.
# All-fixed-mask is written to allmask-file.
# Text is grown by border-width (1 or more) pixels.  The grown part is the
# border mask; the text and grown part is the all-mask.
# If text-color is n (indicating that only the border will be masked into the
# image), the grown part only is used for the all-mask.
# Global vars used: FullMaskInTmp, FullMaskOutTmp
function MakeMasks {
    typeset GrowConv tm TextFile=$1 Cmd= FullTee= BOnlyTee=
    typeset -i TextWidth=$2 TextHeight=$3 BorderWidth=$4
    typeset TextColor=$5 AllMaskFile=$6 BorderMaskFile=$7

    GrowConv="P2
3 3
2
2 2 2
2 2 2
2 2 2"
    # First, make a mask that is one pixel larger, because pnmconvol won't
    # operate on the outermost pixels given a 3x3 convolution matrix
    pbmmake -black $((TextWidth+2)) $((TextHeight+2)) |
    pnmpaste -replace $TextFile 1 1 > $FullMaskInTmp
    # Run as many iterations as neccessary.
    # Use pnmconv & pgmtopbm instead of using pbmmask because pbmmask will
    # decide the text is the background if it gets too fat.  Also, pbmmask
    # fills in enclosed areas, which we don't want.
    while [ BorderWidth -gt 1 ]; do
	echo "$GrowConv" | pnmconvol - $FullMaskInTmp | 
	pgmtopbm -threshold -value 0.1 > $FullMaskOutTmp
	# Swap names of input & output
	tm=$FullMaskOutTmp
	FullMaskOutTmp=$FullMaskInTmp
	FullMaskInTmp=$tm
	let BorderWidth-=1
    done

    # Insert the tee that writes the all-mask-file either before or after
    # the text is XORed out.
    [ "$TextColor" = n ] && BOnlyTee="| tee $AllMaskFile" ||
    FullTee="| tee $AllMaskFile"

    $Debug && print -u2 "No-text-cmd: $Cmd"
    # Do last grow step.
    # Cut off the temporary margin that was added.
    # If image will be left where original text is, subtract it from mask.
    echo "$GrowConv" | pnmconvol - $FullMaskInTmp | 
    pgmtopbm -threshold -value 0.1 | 
    eval pnmcut 1 1 $TextWidth $TextHeight - $FullTee |
    eval pnmpaste -xor $TextFile 0 0 - $BOnlyTee > $BorderMaskFile
}

# Make fixed-color text and/or border & write it to stdout
# Usage: MakeFixed text-color border-color text-mask border-mask
# text-mask and border-mask are white-on-black masks to colorize.
# Globals used: BorderTmp, Debug
function MakeFixed {
    typeset TextColor=$1 BorderColor=$2 TextMask=$3 BorderMask=$4
    typeset RealBorder RealText BorderCmd

    # Make text/border black if they won't included or if their color will
    # be derived from image material
    [[ "$BorderColor" = [niIbBmM] ]] && RealBorder=black ||
    RealBorder=$BorderColor
    [[ "$TextColor" = [niIbBmM] ]] && RealText=black || RealText=$TextColor
    $Debug && print -u2 "Real colors: border=$RealBorder, text=$RealText"

    if [ $RealBorder = black ]; then
	# BorderMask will not exist if no border
	pgmtoppm $RealText $TextMask
    else
    	pgmtoppm $RealBorder $BorderMask > $BorderTmp
	$Debug && print -u2 "Adding border and text."
	pgmtoppm $RealText $TextMask | pnmarith -add $BorderTmp -
    fi
    return 0
}

name=${0##*/}
Usage=\
"Usage: $name [-hoPx] [-b text-border-type] [-t text-type] [-e extension]
       [-w border-width] [-f pbm-font-file] \"text\" pnm-image-file ..."

BorderColor=blue TextColor=white Extension=.B
Debug=false Preserve=false Stdout=false
typeset -i BorderWidth=1

while getopts :e:f:hb:t:w:oxP opt; do
    case $opt in
    h)
	echo \
"$name: imprint text on an image.
$name masks the given text into the bottom of an image, right adjusted and
surrounded by a one-pixel-wide border to ensure that the text can be seen
regardless of the color of the area it is inserted into.  The border is not a
box; it flows around the text itself.
If the text is too long to fit, it is put along the right of the image, bottom
adjusted.  If it is still too long to fit, it is put along the longer dimension
with the right of the text truncated and a warning is printed.
Output is written to a file with name of the source file with .B appended.
ni color
$Usage
Options:
-h: Print this help.
-b, -t: These options specify alternate material to use for the border and text
    respectively.  The default is $TextColor text with a $BorderColor border.
    A value of 'n' turns off that component.  -bn removes the border; -tn makes
    the image show through inside the border.  If either is given and the
    material inserted is the same color as the insertion area, it won't show
    up.  A value of 'i' makes that component consist of the image material
    with red, green, and blue each inverted separately.  The closer the 
    insertion area is to 50% intensity gray, the less the text will show up.
    A value of 'b' is like i, except that the results of the inversion are
    thresholded such that each color has either 0 or full intensity.
    A value of 'm' is like b, except that only black and white are used.
    I, B, and M are the like i, b, and m except that the image material that
    will be used for the text is first run through a convolution such that each
    pixel is replaced with an average of its neighbors, with 0 weight given to
    the pixel itself, so that the color of the text will be derived from the
    pixels that will be next to it rather than the pixels that are replaced.
    If any other value is given with -b or -t, it is taken to be a color to set
    that part to.  See the pgmtoppm man page for a description of how colors
    may be specified.
-o: Write output to standard output.  Use only if one file will be processed.
-x: Turn on debugging.
-P: Preserve (do not remove) tmp files.
-e: Write output to files with the given extension appended, instead of \".B\".
-w: Set the border width (thickness).  The default is 1 pixel.
-f: Use a font other than the default.  The font file should be created by
    the means described in the pbmtext man page."
	exit 0
	;;
    b)  BorderColor=$OPTARG
	[ "$BorderColor" = n ] && BorderWidth=0
	;;
    t)  TextColor=$OPTARG
	;;
    o)	Stdout=true; unset OutFile;;
    x)  Debug=true;;
    P)  Preserve=true;;
    e)	Extension=$OPTARG;;
    w)	BorderWidth=$OPTARG;;
    f)  FontFile=$OPTARG;;
    +?)
	print -u2 "$name: options should not be preceded by a '+'."
	exit 1
	;;
    ?) 
	print -u2 "$name: $OPTARG: bad option.  Use -h for help."
	exit 1
	;;
    esac
done
 
if [ "$BorderColor" = n -a "$TextColor" = n ]; then
    print -u2 "No border or text to be inserted.  Aborting."
    exit 1
fi

# remove args that were options
let OPTIND=OPTIND-1
shift $OPTIND

# Need at least text and one image file
if [ $# -lt 2 ]; then
    print -u2 "$Usage\nUse -h for help."
    exit
fi

# Quit if anything goes wrong
set -e

Text=$1
shift

Tmp=${TMP:-/tmp}/b
BorderTmp=$Tmp.fixbdr.$$	# Fixed border material
ImageBdrTmp=$Tmp.imgbdr.$$	# Image-derived border material
BorderMaskTmp=$Tmp.brdmsk.$$	# Mask for border material
ConvolTmp=$Tmp.imgcnv.$$	# Image convolution matrix
CroppedTmp=$Tmp.rawtxt.$$	# pbm text, after cropping & before expansion
TextMaskTmp=$Tmp.exptxt.$$	# White-on-black expanded text
FullMaskInTmp=$Tmp.bdr-in.$$	# Used in generating border
FullMaskOutTmp=$Tmp.bdrout.$$	# Used in generating border
ImagePieceTmp=$Tmp.img-pc.$$	# Text-size piece cut from image
ImgMaskTmp=$Tmp.imgmsk.$$	# Mask used to cut space in image for brand
AllBrandTmp=$Tmp.albrnd.$$	# Fixed and image-derived brand to add to image
FixedMtlTmp=$Tmp.fxbrnd.$$	# Fixed branding material (text and/or border)
QuantTmp=$Tmp.quantz.$$		# Quantization colors

$Preserve ||
trap "rm -f $FixedMtlTmp $ImgMaskTmp $BorderTmp $TextMaskTmp $CroppedTmp "\
"$FullMaskInTmp $FullMaskOutTmp $ImagePieceTmp $AllBrandTmp $QuantTmp "\
"$ConvolTmp $BorderMaskTmp $ImageBdrTmp" EXIT 1 2 3 15

typeset -i TextWidth TextHeight

# Make raw text & put in $TextMaskTmp
MakeText "$Text" $TextMaskTmp $BorderWidth $FontFile

# BorderMaskTmp is set up only if there is a border
if [ $BorderWidth -gt 0 ]; then
    # Make mask for fixed material & put it in $ImgMaskTmp
    MakeMasks $TextMaskTmp $TextWidth $TextHeight $BorderWidth $TextColor \
    $ImgMaskTmp $BorderMaskTmp
else
    ImgMaskTmp=$TextMaskTmp
fi

# Put the fixed-color part of the material to be masked in in $FixedMtlTmp.
MakeFixed $TextColor $BorderColor $TextMaskTmp $BorderMaskTmp > $FixedMtlTmp

# FixedMtlTmp is the fixed-color material.
# ImgMaskTmp is the mask, white where image should be removed.
# TextMaskTmp is the white-on-black text bitmap.
# BorderTmp & CroppedTmp vars are no longer used but may refer to the same
# files as the other vars.

for file; do
    # Set Width & Height to dimensions of piece to cut out, etc.
    FindCutParms $file $TextWidth $TextHeight

    # Do not rely in existance of /dev/stdout et al
    $Stdout || {
	if [ "$file" -ef $file$Extension ]; then
	    print -u2 "Input and output are the same file."
	    exit 1
	fi
	OutFile="> '$file$Extension'"
    }
    $Debug && print -u2 "Stdout=$Stdout.  Output is $OutFile"

    # Get image piece
    eval pnmcut -$Width -$Height $Width $Height '"$file"' $ExtractProc \
    > $ImagePieceTmp

    # Make image-derived brand components, if neccessary
    if [[ "$TextColor" = [iIbBmM] || "$BorderColor" = [iIbBmM] ]]; then
	# Don't bother adding fixed color material if it's just black
	if [[ "$TextColor" != [niIbBmM] || "$BorderColor" != [niIbBmM] ]]; then
	    FixedCmd="| pnmarith -add - $FixedMtlTmp" # Add fixed part of brand
	    $Debug && print -u2 "Fixed material add command: $FixedCmd"
	else
	    $Debug && print -u2 "No fixed material to add."
	fi
	TextInvCmd="Invert $TextColor $ImagePieceTmp $TextMaskTmp"
	BorderInvCmd="Invert $BorderColor $ImagePieceTmp $BorderMaskTmp"
	if [[ "$TextColor" != [iIbBmM] ]]; then	# Only border is image derived
	    eval $BorderInvCmd $FixedCmd
	elif [[ "$BorderColor" != [iIbBmM] ]]; then  # Only text image derived
	    eval $TextInvCmd $FixedCmd
	else	# Both are image derived
	    eval $BorderInvCmd > $ImageBdrTmp
	    eval $TextInvCmd $FixedCmd | pnmarith -add - $ImageBdrTmp
	fi > $AllBrandTmp
    else
	AllBrandTmp=$FixedMtlTmp
    fi

    # Put it all back together
    pnmarith -sub $ImagePieceTmp $ImgMaskTmp | # Cut mask out of image
    pnmarith -add - $AllBrandTmp | # Add brand
    eval $InsertProc pnmpaste -replace - -$Width -$Height '"$file"' $OutFile
done
