#!/bin/sh
#echo START $0 $* 1>&2
# Extract hero (and similar) image HTML in various forms for the document.
# Default to mobile/lite outputs unless eg explicitly for desktop.
# This may generate (and cache) smaller efficient images on the fly.
# HTML generated may include img tags with src and maybe srcset, etc,
# or larger composite elements such as divs.
#
# This may also perform various supporting tasks,
# such as extracting alt text from an image name,
# or extracting a CCS-based representative colour or gradient.
#
#   $1 must be the operation to perform.
#   $2 must be the declared (external) hero image path name,
#      or if starting with a "." is the HTML source to discover it from.
#      The declared name must be HTML attr value safe, ie no embedded [ ='"].
#   $3 must be the source document that this HTML is being created for.
#      Mainly but not only used for error messages.
#      May also be used to cap the image size against (eg for insTop).
#   $4 must be the static host prefix ($4$2 should be external URL for img).
#   $5 must be "false" if desktop, "true" if HTML5 mobile,
#      "offline" for offline, "amp" for AMP.
#
# Additional parameters may be supplied for some operations.
#
#   $6 Can be target width of image.
#   $7 Can be target height of image.
#   $8 Can be image class.
#   $9 Can be alt text.
#   $10 Can be list of key-value pairs, eg "ic=3,ll=true,x=0".
#
# This may recurse using its own name in $0.
#
# Note that $2, the HEROIMG, may be of the form:
#   * img/xxx
#   * out/xxx
#   * http://gallery.hd.org/_tn/std/xxx
# and in future possibly:
#   * http://gallery.hd.org/_exhibits/xxx
#
if [ $# -lt 5 ]; then
    echo "ERROR: $0: missing parameter(s), have: $@" 1>&2
    exit 2
fi

OP="$1"
HEROIMG="$2"
ARTBODY="$3"
STATICCDNPREFIX="$4"
MOBILE="$5"
# Extra parameters for some calls.
TARGETWIDTH="$6" # Can be target width of image.
TARGETHEIGHT="$7" # Can be target height of image in some cases.
ARGCLASS="$8" # Can be class.
ARGALT="$9" # Can be alt.
ARGKVP="${10}" # Can be kvp list.

# echo "INFO: $0 OP=$OP HEROIMG=$HEROIMG ARTBODY=$ARTBODY STATICCDNPREFIX=$STATICCDNPREFIX MOBILE=$MOBILE TARGETWIDTH=$TARGETWIDTH TARGETHEIGHT=$TARGETHEIGHT ARGCLASS=$ARGCLASS ARGALT=$ARGALT ARGKVP=$ARGKVP" 1>&2

# Some simple parameter validation.
if [ "" = "$OP" ]; then
    echo "ERROR: missing OP" 1>&2
    exit 2
fi
if [ "" = "$STATICCDNPREFIX" ]; then
    echo "ERROR: missing STATICCDNPREFIX" 1>&2
    exit 2
fi

# Load any soft site build parameters.
. .work/softparams.txt

# Enable performance timestamps with debug setting...
PERFTIMESTAMPS=false
if [ "" != "$DEBUGPGBUILDPERF" ]; then
    PERFTIMESTAMPS=true
fi
if [ "true" = "$PERFTIMESTAMPS" ]; then
    # Default to portable seconds timestamp.
    PERFTIMESTAMPCMD="date +%s"
    # Try for milliseconds if available.
    if [ "`date +%3N`" != "3N" ]; then
        PERFTIMESTAMPCMD="date +%S.%3N";
    elif [ "`gdate +%3N`" != "" -a "`gdate +%3N`" != "3N" ]; then
        PERFTIMESTAMPCMD="gdate +%S.%3N";
    fi
fi
$PERFTIMESTAMPS && echo "INFO: TS: GHII: START: ${OP}:" `$PERFTIMESTAMPCMD` 1>&2

# Hero image as filename (defaults to be same as HEROIMG).
HEROIMGASFILE="$HEROIMG"

# If nominal hero image name starts with a "."
# then it is instead the name of the HTML page source to extract hero from.
# This will select the primary hero image.
if [ "" != "`echo $HEROIMG | egrep '^[.]'`" ]; then
    #echo "Finding hero image $HEROIMG for $ARTBODY." 1>&2
    $PERFTIMESTAMPS && echo "INFO: TS: GHII: GHID:" `$PERFTIMESTAMPCMD` 1>&2
    HEROIMG="`script/get_hero_img_declared $HEROIMG $ARTBODY`"
fi

# Verify that the source image is in a valid location and 
# has a valid format.  Abort with an error if not.
# (This script checks earlier for existence as appropriate.)
#
#     img/* for static images
#     out/* for dynamic images (whose size is assumed to stay fixed)
#     http://gallery.hd.org/* from the CMS, mainly intended for body images
#         (HEROIMG should be rewritten to a local file copy from the CMS)
#         pointing to a thumbnail, /_tn/std/<exhibitpath>, eg:
#         http://gallery.hd.org/_tn/std/places-and-sights/England-London-Kennington-The-Prince-of-Wales-frontage-1-DHD.jpg
IMAGELOC=""
    case $HEROIMG in
    img/*) IMAGELOC="img";; # Static (the common case).
    out/*) IMAGELOC="out";; # Dynamic.
    http://gallery.hd.org/_tn/std/*)
        if [ "" = "$GALLERYCMSPATH" ]; then
            # If no Gallery CMS path is yet set, use the default.
            GALLERYCMSPATH=/rw/galleryDB/photos/
        fi
        if [ ! -d "$GALLERYCMSPATH" ]; then 
            echo "ERROR: GALLERYCMSPATH $GALLERYCMSPATH not a directory for $ARTBODY." 1>&2
            exit 1;
        fi
        # Convert HEROIMG to local file path.
        # Note that GALLERYCMSPATH ends with '/'.
        IMGGALPATH="`echo $HEROIMG | sed -e 's/^http:\/\/gallery[.]hd[.]org\/_tn\/std\///'`"
        HEROIMGASFILE="$GALLERYCMSPATH$IMGGALPATH"
        #echo "INFO: rewritten Gallery $HEROIMG -> $HEROIMGASFILE for $ARTBODY." 1>&2
        # URL for Gallery image catalogue page.
        # Can be switched to https in future.
        IMAGEGALCATPAGE="http://gallery.hd.org/_c/$IMGGALPATH.html"
        IMAGEGALEXHIBIT="http://gallery.hd.org/_exhibits/$IMGGALPATH"
        IMAGELOC="gallery";; # From CMS.
    *) echo "ERROR: $0: $OP invalid image location $HEROIMG for $ARTBODY." 1>&2
       exit 1;;
    esac

# Check that hero image exists and is a plain file and non-zero length...
# DUMMYIMGASFILE is a single-pixel placeholder image.
DUMMYIMGASFILE=img/p.png
if [ "" = "$HEROIMG" -o "" = "$HEROIMGASFILE" -o ! -f "$HEROIMGASFILE" -o ! -s "$HEROIMGASFILE" ]; then
    # If not, but is a dynamic image, warn but carry on with placeholder.
    # This is pragmatic, since dynamic images will not always be ready.
    if [ "out" = "$IMAGELOC" ]; then
        HEROIMGASFILE=$DUMMYIMGASFILE
        if [ "" != "$SOFTPPRODTESTFILE" -a -s "$SOFTPPRODTESTFILE" ]; then
            echo "ERROR: production server missing dynamic source image $HEROIMG for ${ARTBODY}" 1>&2
            exit 2
        fi
        echo "INFO: non-server missing dynamic source image $HEROIMG for ${ARTBODY}" 1>&2
    else
        echo "ERROR: missing static source image $HEROIMG ($HEROIMGASFILE) for $ARTBODY." 1>&2
        exit 1
    fi
fi


# Load in default URLs/locations.
. script/SITE.loc
# Prefix to use to fetch image without redirect for non-desktop pages.
# Note that external absolute references should never be prefixed.
CDNPREFIX="$STATICCDNPREFIX"
CDNWRAPPREFIX="$CDNPREFIX"
if [ "false" = "$MOBILE" ]; then
    # Desktop does not need any prefix for static or dynamic images.
    CDNPREFIX=""
    CDNWRAPPREFIX=""
elif [ "offline" = "$MOBILE" ]; then
    # Offline does not need a prefix for static or dynamic images.
    CDNPREFIX=""
    # The click-through image wrapper link prefix is needed though.
    #CDNWRAPPREFIX="$STATICCDNPREFIX"
elif [ "out" = "$IMAGELOC" ]; then
    # Use dynamic CDN not static CDN for out/ images for mobile.
    # Use protocol-relative version to do the right thing for http and https.
    CDNPREFIX="${DEFAULTPRELDYNAMICCDNPREFIX}"
    CDNWRAPPREFIX="$CDNPREFIX"
    if [ "" = "$CDNPREFIX" ]; then
        echo "ERROR: $0: unable to create main-site dynamic image URL prefix from $DEFAULTPRELDYNAMICCDNPREFIX for $HEROIMG for $ARTBODY; MOBILE=$MOBILE." 1>&2
        exit 1
    fi
else
    # Use protocol-relative version of static CDN for all non-desktop variants.
    CDNPREFIX="`echo $STATICCDNPREFIX | sed -e 's/^http[a-z]*://'`"
    CDNWRAPPREFIX="$CDNPREFIX"
    if [ "" = "$CDNPREFIX" ]; then
        echo "ERROR: $0: unable to create main-site static image URL prefix from $STATICCDNPREFIX for $HEROIMG for $ARTBODY; MOBILE=$MOBILE." 1>&2
        exit 1
    fi
fi
#echo "INFO: $0: using CDNPREFIX=\"$CDNPREFIX\" (IMAGELOC=$IMAGELOC) for $HEROIMG for $ARTBODY; MOBILE=$MOBILE." 1>&2


# Location of ImageMagick 'convert' and other utilities.
CONVERTDIR=""
CONVERT="$(sh script/IMPathConvert.sh)"
IDENTIFY=""
COMPARE=""
if [ "" != "$CONVERT" ] && [ -x "$CONVERT" ]; then
    CONVERTDIR="$(dirname "$CONVERT")"
    IDENTIFY=$CONVERTDIR/identify
    COMPARE=$CONVERTDIR/compare
fi

# ImageMagick seems not to work well as a wrapper on RPi with cwebp 0.5.2
# DHD20210802: use locally-built (cut-down) cwebp if available, not distro.
CWEBP=cwebp
if [ -x /usr/local/bin/cwebp ]; then CWEBP=/usr/local/bin/cwebp; fi

# Location of image compression utilities.
ZOPFLIPNG=/usr/local/bin/zopflipng
if [ -x /usr/bin/zopflipng ]; then ZOPFLIPNG=/usr/bin/zopflipng; fi
if [ ! -x $ZOPFLIPNG ]; then
    echo "WARNING: missing zopfliping" 1>&2
fi
LOSSLESSJPEGCOMP=script/lossless_JPEG_compress
if [ ! -x script/lossless_JPEG_compress ]; then
    echo "WARNING: missing $LOSSLESSJPEGCOMP" 1>&2
fi


# Returns on stdout the HTML to insert in the document; empty if none.
# There may be non-error cases where an empty value is returned,
# eg we may not always want to generate a hero image for mobile.
# In case of error this returns a non-zero value.
#
# Supported operations:
#
#  chooseImg
#     Choose largest (bytes and px) version of declared hero image,
#     within the display byte weight limits.
#     If no suitable image is available then this will return an empty result,
#     and a zero (non-error) return code, or may build and cache an image.
#
#     The optional first extra arg ($6) is the target width in pixels.
#     MAY BE IGNORED.
#
#  chooseBannerImg
#     Choose largest (bytes and px) version of declared hero image,
#     of at least given width (possibly with a tolerance)
#     and with a suitable 'letterbox' aspect ratio,
#     within the display byte weight limits.
#     If no suitable image is available then this will return an empty result,
#     and a zero (non-error) return code, or may build and cache an image.
#
#     The optional first extra arg ($6) is the target minimum width in pixels.
#
#  insTop
#     Generate HTML to insert hero code at top of wrapped version of page.
#     If no suitable image is available then this may return an empty result,
#     and a zero (non-error) return code, or may build and cache an image.
#     The img tag may be wrapped in a link to the full image
#     and a div for a banner image.
#
#  carouselEntryImg (or carouselEntryImgBig for larger header image)
#     Generate responsive img tag suitable to slot into a column
#     (single column for narrow screen, double/triple for desktop)
#     with hero image and title/description.
#     Returns nothing if no suitable content.
#     Can (eg) be tried with each of most popular/newest in turn
#     to find first one(s) suitable to generate carousel content from.
#     The img tag is not wrapped in any way.
#
#  autogenBannerImg
#     Return any autogenerated banner image for the specified hero,
#     creating it if necessary/possible, or an empty result if not possible.
#     Any auto-generated hero banner image is under img/a/h.
#
#     Can also be used to generate column banners, rather than page-wide.
#
#     The final (extra) arg is the target width.
#
#  autogenImg
#     As for autogenBannerImg but images may be informational (non-hero)
#     so higher-fi than hero images.
#
#     Allowed to generate images up to a multiple of HEROIMGABSMAXBYTES.
#
#  floatImg
#     Return a responsive (usually float) informational image tag suitable for
#     the specified class and body width,
#     else empty output (and error on stderr) in case of error.
#
#     Image must usually be a static image under /img
#     and resized (pre-rendered) versions may be created as necessary
#     and all will load the image without redirect from the static
#     (primary) server.
#
#     Images may also be dynamic and under /out
#     in which case they are referenced as-is, with no resizing.
#     (Though support for srcset with "IMGx Xw, IMGy Yw" will be created.)
#
#     The image used may be the original or autogenerated if under /img.
#
#     If the src image is of the form X/Y-tn.Z and an image exists
#     of the form X/Y.Z then the src (thumbnail) image will be used
#     for display but the X/Y.Z image will be linked to.
#
#     The form of the tag may change entirely for (say) MOBILE==amp.
#
#     These images are informational rather than decorative heros,
#     and are likely relatively large and in the body of the page.
#     Such images will often not be in the initial viewport on mobile.
#
#  autogenHiresHeroImgs
#     Generate ImageObject HTML5 microdata for
#     search-engine-friendly 16:9, 4:3, 1:1 cropped (and space-efficient)
#     versions of the supplied (JPG/PNG raster) hero image.
#     (May add to page as thumbnailUrl rather then image itemprop if small.)
#
#     The centre of the image is always preserved and cropping is symmetric.
#
#     This metadata would generally be added to the end of a page
#     (so not impacting the Critical Rendering Path)
#     to allow an SE to select a suitable version for a SERPS result.
#
#     At least contentUrl and url will be provided for ImageObject,
#     with caption also from alt text where possible.
#
#     Accepts as TARGETWIDTH the minimum recommended image width.
#     images narrower than this will not be generated as image itemprops,
#     though may be base thumbnailUrls down to DRFRSPX.
#
#  repCol|repColOpt
#     Extract a single representative hex colour for the entire image.
#     This may build a minimal-sized image to extract that colour from
#     and/or cache the computed colour.
#
#     repColOpt is optimised eg omitting common web-page background colours
#     (white and black, eg light mode and dark mode)
#     and if image is not opaque.
#
#     Format is # followed by 6 or 3 digits.  (Latter 216 "Web-safe" palette.)
#
#     Intended to be reasonably fast.
#
#  inline-<OP>
#     Return the inline (data URL) form of the image if allowed
#     (source image is a local file, small enough, old enough if out/*)
#     else nothing is returned.
#
#     OP is the original operation requested at an outer level.
#
#     Can be passed the intermediate resized image (eg under img/a)
#     rather than the source image if required.

# Some key parameters, that should match with page layout, eg CSS.

# Hi-res screen width for common desktops and smartphone ~2020.
# Can be used to drive crisper displays for (say) hero images.
HBODYPX=1440
# Target desktop body max-width (px), eg for page-wide (100vw) images.
DBODYPX=800
# Target mobile body width (px), eg for page-wide (100vw) images.
# Should match main container width, eg from CSS.
# Should match (max) size targeted for 'alternate' page media max-width:
#   <link rel=alternate href="http://m.earth.org.uk/" media="only screen and (max-width:640px)"/>
MBODYPX=640
# Target minimum body width (px), eg for page-wide (100vw) images.
# As of 2017Q1 most popular viewport width ~360 (CSS) pixels.
# This targets a 1.5x density at that minimum viewport size,
# thus 540px effective real pixel width.
MINBODYPX=540

# Common viewport width for smartphones, eg cf tablets.
# Creating page-width hero banners at this width or just below may
# save bandwidth when used with srcset.
# Can also be used as larger feature image size if need be,
# though at risk of ambiguity of appropriate bits-per-pixel.
CBODYPX=360
# Target image widths for respfloatrsml and carousel (hero) image.
# Target desktop respfloatrsml (hero) image with body 800px and max-width:33%.
#DRFRSPX=264 # Up to 2019/11/05
# Carousel desktop hero image width (nominally just < respfloatrsml).
# Rounded down to a multiple of 8 pixels to help (JPEG) encoding efficiency.
# Downsizing also actual available width of 3 cols (31%) with whitespace.
DRFRSPX=248 # Up to 2019/11/05
# Target mobile respfloatrsml (hero) image with body 640px and max-width:33%.
#MRFRSPX=211
# Carousel mobile hero image width (nominally just < respfloatrsml).
# Rounded down to a multiple of 8 pixels to help (JPEG) encoding efficiency.
# Also note that as of 2017Q1 most popular viewport width ~360 (CSS) pixels,
# and devices directed to the mobile pages will often be << exact 640px.
MRFRSPX=200
# Widest image (px) that can be non-responsive (to support faster layout).
WMAXFIXEDPX=80

# Desktop banner minimum fraction of full width ]0.0,0.1]
DHBMINFRAC=0.9
# Mobile banner minimum fraction of full width ]0.0,0.1]
# Allow a lot greater under-width tolerance for mobile.
MHBMINFRAC=0.5
HBMINFRAC="$DHBMINFRAC"
if [ "false" != "$MOBILE" ]; then
    HBMINFRAC="$MHBMINFRAC"
fi

# Attempt to select hero image for default 'respfloatrsml' display.
# Target pixel width to display type.
# Likewise for full-width banners.
FHIPX="$DRFRSPX"
FWPX="$DBODYPX"
if [ "false" != "$MOBILE" ]; then
    # Somewhat reduced target image size for mobile.
    FHIPX="$MRFRSPX"
    FWPX="$MBODYPX"
fi

# Min aspect ratio (x:y or w/h as a decimal) for full-width hero images.
# Selected to not eat too much vertical space.
# Aim for about half this if auto-generating.
MINHWAR=2.0
# Target (min) aspect ratio (w/h as a decimal) for column-width hero images.
# Selected to provide a decent narrower view, maybe even just portrait.
# Aim for about half this if auto-generating.
MINHCAR=0.7

# Maximum size in bytes of image to inline with data URL.
# Below this should save traffic overheads and latency esp on first visit.
# Also keeps (Base64) encoded form within nominal 1kB as per RFC.
# May only apply to subset of, eg header, hero images.
# May only apply to 'lite' version of site where img request latency is key.
# Note that inlined images can be several times slower to decode than separate.
# At/below ~750 bytes almost certainly data saving for typical HTTP overheads.
# Above ~4kB almost never worthwhile, especially if not ATF.
MAXINLINEIMG=750
# At/below ~300B likely a win on desktops but CPU may be too much for lite.
MAXINLINEIMGDESKUNCOND=320
# Allowance for (inlineable, mobile) hero images to avoid RTT on slow links.
MAXINLINEIMGATF=3000
# Minimum age in days to inline an 'out' image, ie regarding it as fixed.
MINOUTINLINED=366

# Abs maximum desktop hero image size (bytes) to avoid excessive page weight.
# Body images can be larger since they should be displaying useful info.
DHEROIMGABSMAXBYTES=60000
DBODYIMGABSMAXBYTES=99000
# Preferred maximum desktop hero image size to avoid excessive page weight.
DHEROIMGMAXBYTES=40000
DBODYIMGMAXBYTES=80000
# Much reduced image size/weight limits for mobile.
MHEROIMGABSMAXBYTES=30000
MBODYIMGABSMAXBYTES=50000
MHEROIMGMAXBYTES=10000
MBODYIMGMAXBYTES=40000
# Default to desktop sizes.
HEROIMGABSMAXBYTES=$DHEROIMGABSMAXBYTES
BODYIMGABSMAXBYTES=$DBODYIMGABSMAXBYTES
HEROIMGMAXBYTES=$DHEROIMGMAXBYTES
BODYIMGMAXBYTES=$DBODYIMGMAXBYTES
if [ "false" != "$MOBILE" ]; then
    # Much reduced image size/weight limits for mobile.
    HEROIMGABSMAXBYTES=$MHEROIMGABSMAXBYTES
    BODYIMGABSMAXBYTES=$MBODYIMGABSMAXBYTES
    HEROIMGMAXBYTES=$MHEROIMGMAXBYTES
    BODYIMGMAXBYTES=$MBODYIMGMAXBYTES
fi

# Target image bits per pixel after compression (primarily for JPEG).
# Targeting 40kByte (320kbit) for 800x200 desktop image.
DCBPP=2.0
# Significantly reduced hero image by default for mobile/lite targets.
# Hero images are assumed mainly decorative, so lo-fi is OK.
MCBPP=1.0

# Approx typical overheads (minimum file sizes) for various formats.
# Allows a fairer bits-per-pixel calculation.
OHMINJPG=160
OHMINPNG=68
# Quick-and-dirty cross-format fixed overhead to allow.
# This also acknowledges that there will be network overheads, etc.
OHMIN=200

# Increasing supported decimal pixel densities > 1, space separated.
# Should match common device density ratios and not be too widely spaced.
# 4x density is probably about the limit for most people's vision.
DENSITIES="1.5 2 3 4"

# Alternate image format that may be more compact, by file extension.
# Note: also assumed to be second part of MIME type following "image/".
# Preferred formats (likely more compact) first.
# Note that more compact formats are generally newer and less supported.
# This list is expected to include webp, jxl and avif at least, in due course.
# Place likely-most-compact formats first.
ALTERNATESALL="jxl avif webp"
# Popular non-JPEG/non-PNG format(s) understood by >>90% of browsers.
ALTERNATESALLPOP=webp

# AUTOGEN cache locations.
CACHEDIRAG=img/a
# If the rebuild flags exists and is newer than any cached result flag,
# unconditionally try to rebuild/refresh everything (carefully, under a lock).
# Else return immediately on cached positive or negative results.
# Assumes that a cached positive result is the most likely.
# Should be touched after any substantive change to image selection/generation.
REBUILDFLAG=$CACHEDIRAG/.rebuild.flag
# Cache sub-dir for primarily-decorative low-ish-fi 'hero' images,
CACHEDIRHERO=h
# Cache sub-dir for primarily-informative high-ish-fi 'body' images,
CACHEDIRBODY=b
# Cache sub-dir for relocated originals and derivatives (alongside video).
CACHEDIRREL=v
AUTOGENFILEDIR=$CACHEDIRAG/$CACHEDIRREL
# Path from top of EOU to auto-gen image cache dir.
# Set max image sizes to match...
# (DHD20210820: FIXME: autogenImg also used for body images?)
AGHBDIR=$CACHEDIRAG/$CACHEDIRHERO
IMGABSMAXBYTES=$HEROIMGABSMAXBYTES
IMGMAXBYTES=$HEROIMGMAXBYTES
if [ "autogenImg" = "$OP" ]; then
    AGHBDIR=$CACHEDIRAG/$CACHEDIRBODY
    IMGABSMAXBYTES=$BODYIMGABSMAXBYTES
    IMGMAXBYTES=$BODYIMGMAXBYTES
fi
mkdir -p $AGHBDIR/.flags || exit 1


#============================
#$PERFTIMESTAMPS && echo "INFO: TS: GHII: case: ${OP}:" `$PERFTIMESTAMPCMD` 1>&2


case $OP in


autogenBannerImg|autogenImg) #-------------------------------------------
#  autogenBannerImg
#     Return any autogenerated banner image for the specified hero,
#     creating it if necessary/possible, or an empty result if not possible.
#     Any auto-generated hero banner image is under img/a/h.
#
#     Can also be used to generate column banners, rather than page-wide.
#
#     The final (extra) arg is the target width.
#
#  autogenImg
#     As for autogenBannerImg but images may be informational (non-hero)
#     so higher-fi than hero images.
#
#     Allowed to generate images up to a multiple of HEROIMGABSMAXBYTES.

# Get source image dimensions.
HEROIMGWH="`script/get_img_XxY $HEROIMGASFILE`"
if [ "" = "$HEROIMGWH" -o "1x1" = "$HEROIMGWH" ]; then
    echo "ERROR: $0: failed to get img dimensions of $HEROIMG ($HEROIMGASFILE) for $ARTBODY; class=$Iclass." 1>&2
    exit 1
fi

# Compute the target cache filename.
# See $AGHBDIR/README.txt for canonical format.
#
# Auto-generated hero images, named as:
#   * the declared hero source image basename (no suffix) for human id,
#     may be truncated and catenated a sha256 hash of the name if too long
#   * then a '.' then a hash of the source image to avoid clashes
#   * then a '.' then the geometry in pixels w then "x" then y, eg ".800x200"
#   * then an optional ".l" for a 'lite' (lower-fi) version of the image,
#     or an optional .h' for a hi-fi (possibly cropped) version.
#   * then a '.' suffix suitable for the generated image type,
#     typically ".png" or ".jpg" which will in turn depend on the source type,
#     and is generally identical to it.
# 
# The defined hash types are:
#   * 'l' then the original image length in bytes in decimal (portable, easy)
#   * 'm' then the hex MD5 original image hash (robust)
AGHBSUFFIX="`echo $HEROIMGASFILE | sed -n -e 's/^.*\([.][a-z][a-z0-9]*\)$/\1/p'`"
AGHBHASH="l`wc -c $HEROIMGASFILE | awk '{print$1}'`"
# Make the best-looking banner that we can within the allowed byte weight.
# Compute target dimensions using limit dimensions.
# Allow slimmer banners for mobile for better images for bytes available.
# Since these banners may be shared between multiple pages,
# don't limit size by current page, but to well below ceiling size instead.
# AGHBGEOM is of form WxH eg 800x200.
AGHBGEOM="`echo $TARGETWIDTH | awk '{
        divisor=MINHWAR; # Default letterbox aspect ratio for page-wide banner.
        if($1 < MBODYPX) { divisor = MINHCAR; } # Taller for columns/floats.
        # DHD20210803: keep smaler banner same aspect ratio to reduce CWV CLS.
        #else if("false" != MOBILE) { divisor *= 2; } # Else thinner for mobile.
        divisor *= 2; # Aim for twice the minimum-allowed aspect ratio.
        printf("%dx%d", $1, int($1/divisor));
        }' MINHWAR=$MINHWAR MINHCAR=$MINHCAR MBODYPX=$MBODYPX MOBILE=$MOBILE`"
# If building a body (float) image, preserve the original aspect ratio instead.
if [ "autogenImg" = "$OP" ]; then
    AGHBGEOM="`echo $HEROIMGWH | awk -Fx '{
    HX=$1; HY=$2;
    # If image is narrower than target, leave geometry exactly as-is.
    if(TARGETWIDTH >= HX) { print $0; }
    else {
        SCALEFACTOR = TARGETWIDTH / HX;
        printf("%dx%d", TARGETWIDTH, int(HY * SCALEFACTOR));
        }
    }' TARGETWIDTH=$TARGETWIDTH`"
fi

# If the raw base name is too long, replace with a crypto hash.
# This need only be long and good enough to avoid collisions.
# MD5 is fine, SHA256 at double the length might be a reasonable upgrade.
AGHBRAWBASE=`basename $HEROIMGASFILE $AGHBSUFFIX`
AGHBBASE="$AGHBRAWBASE"
AGHBMAXBASENAME=42
if [ "${#AGHBRAWBASE}" -gt "$AGHBMAXBASENAME" ]; then
    # Try to preserve first word from the original name if not too long.
    NPREFIX=`echo $AGHBRAWBASE |awk -F- '{if(length($1)<10){printf("%s-",$1)}}'`
    NHASH=`echo $AGHBRAWBASE | md5sum | awk '{printf("%s",$1)}'`
    AGHBBASE="$NPREFIX$NHASH"
    #echo "INFO: shortened base to $AGHBBASE for $HEROIMG." 1>&2
fi

# Desktop default.
AGHBFILE=$AGHBDIR/$AGHBBASE.$AGHBHASH.$AGHBGEOM$AGHBSUFFIX

if [ "false" != "$MOBILE" ]; then
    # Mobile-constrained images (eg capped byte weight) potentially different.
    AGHBFILE=$AGHBDIR/$AGHBBASE.$AGHBHASH.$AGHBGEOM.m$AGHBSUFFIX
fi

CBPP=$DCBPP
if [ "false" != "$MOBILE" -a "autogenImg" != "$OP" ]; then
    # Select lower-fi generation for mobile hero (decorative-only) images.
    CBPP=$MCBPP
    # Mobile constrained and with lower bpp potentially different again.
    AGHBFILE=$AGHBDIR/$AGHBBASE.$AGHBHASH.$AGHBGEOM.l$AGHBSUFFIX
fi

$PERFTIMESTAMPS && echo "INFO: TS: GHII: ${OP}: $AGHBFILE" `$PERFTIMESTAMPCMD` 1>&2

# TODO: after generation, return core version path in place of .m/.l version if identical?

# Subdir and file prefix for done/failed markers and timestamps.
FLAGSSDIR=.flags
FLAGSPREF=`basename $AGHBFILE`

# Success marker and timestamp (in flags subdir).
AGHBDONE=$AGHBDIR/$FLAGSSDIR/$FLAGSPREF.DONE
# If the success file exists and is new enough, then succeed immediately,
# *IFF* the target image actually exists too!
if [ -e $AGHBDONE -a $AGHBDONE -nt $REBUILDFLAG -a -s $AGHBFILE ]; then
    echo $AGHBFILE
    exit 0
fi

# Failure marker and timestamp (in flags subdir).
AGHBFAIL=$AGHBDIR/$FLAGSSDIR/$FLAGSPREF.FAIL
# Don't try if the 'fail' negative cache marker is present and new enough.
# We can assume that it won't be created under us.
if [ -e $AGHBFAIL -a $AGHBFAIL -nt $REBUILDFLAG ]; then
    exit 1
fi

# About to build or rebuild the target image, which may take a while.
echo "INFO: $0: (re)building image $AGHBFILE" 1>&2

# Info on the image build.
AGHBINFO=$AGHBDIR/$FLAGSSDIR/$FLAGSPREF.info

# Try to avoid concurrent attempts to (re)build same (common/popular) files.
# Attempt to sleep here long enough to let a racing task complete
# so that this doesn't fail incorrectly returning an empty result.
# CONTINUE ANYWAY IF LOCK CANNOT BE TAKEN BUT THEN DON'T REMOVE LOCK AT THE END!
AGLOCKFILE=$AGHBFILE.lock
if lockfile -s 10 -r 50 -l 1301 $AGLOCKFILE 1>&2; then
    #echo "INFO: lock acquired: $AGLOCKFILE" 1>&2
    # Recheck to see if task was completed while lock was held to avoid rework!
    if [ -e $AGHBDONE -a $AGHBDONE -nt $REBUILDFLAG -a -s $AGHBFILE ]; then
        echo "INFO: build done before lock acquired: $AGLOCKFILE" 1>&2
        rm -f $AGLOCKFILE
        echo $AGHBFILE
        exit 0
    fi
    if [ -e $AGHBFAIL -a $AGHBFAIL -nt $REBUILDFLAG ]; then
        echo "INFO: build failed before lock acquired: $AGLOCKFILE" 1>&2
        rm -f $AGLOCKFILE
        exit 0
    fi
    # Clear info file if about to build image.
    rm -f $AGHBINFO
else
    # Replace lock with dummy name so that rm at end doesn't break anything.
    # Replace the info file too.
    AGLOCKFILE=$AGHBFILE.dummy.lock
    AGHBINFO=$AGHBINFO.dup
    echo "INFO: lock NOT acquired so possibly doing redundant work though safe: $AGLOCKFILE" | tee -a $AGHBINFO 1>&2
fi

# If ImageMagick 'convert' utility cannot be found then give up immediately.
if [ "" = "$CONVERT" ] || [ ! -x $CONVERT ]; then
    echo "ERROR: ImageMagick 'convert' utility not found." | tee -a $AGHBINFO 1>&2
    rm -f $AGLOCKFILE
    exit 1
fi


# Once into the process of trying to create an autogen image,
# and within the lock scope to reduce contention a little bit,
# in the background remove any very old temp files for all images,
# to get some cleaning up done whenever new work is happening.
# Assume all such temporary files end with .tmp or .tmpL*.
# DO THIS ONLY ON (less often requested) MOBILE BUILDS TO SAVE EFFORT.
if [ "true" = "$MOBILE" ]; then
    find $AGHBDIR/ -maxdepth 1 \( -name '*.tmp' -o -name '*.tmpL*' \) -mtime +1 -exec rm -f {} \; 1>&2 &
fi


echo "INFO: attempting to auto-generate image for ${ARTBODY} for mobile=$MOBILE: ${AGHBFILE}" >> $AGHBINFO


# True if generating an image for a high-density (eg 'retina') display.
# Don't generate L (lo-fi) since bandwidth may be cheap for these clients.
HIGHDENSITY=false
# Assumed true when target image is wider than the outer container.
if [ "$FWPX" -lt "`echo $AGHBGEOM | awk -Fx '{print$1}'`" ]; then
    HIGHDENSITY=true
fi
# TODO: other cases.


# Refuse to expand banners from narrower images; will generally look bad.
HEROIMGW="`echo $HEROIMGWH | awk -Fx '{print $1}'`"
if [ "$HEROIMGW" -lt "`echo $AGHBGEOM | awk -Fx '{print$1}'`" ]; then
    echo "INFO: refusing to expand to target geom ${AGHBGEOM}: is wider than source ${HEROIMG} @${HEROIMGW}w, for ${ARTBODY} for mobile=$MOBILE: ${AGHBFILE}" >> $AGHBINFO
    touch $AGHBFAIL # Avoid re-trying.
    rm -f $AGHBDONE $AGLOCKFILE
    exit 0
fi
#HEROIMGH="`echo $HEROIMGWH | awk -Fx '{print $1}'`"

# Constrain the maximum file size as for any hero image.
MAXSIZE="$HEROIMGMAXBYTES"
if [ "autogenImg" = "$OP" ]; then
    # Allow building of larger information-rich images where needed.
    MAXSIZE="$BODYIMGMAXBYTES"
fi
# The generated banner image must be smaller than the source.
HEROIMGB="`wc -c < ${HEROIMGASFILE} | awk '{printf("%d", $1)}'`"
if [ "$MAXSIZE" -gt "$HEROIMGB" ]; then MAXSIZE="`expr $HEROIMGB - 1`"; fi
# Aim well undersize for mobile.
if [ "false" != "$MOBILE" ]; then MAXSIZE="`expr \( 3 \* $MAXSIZE \) / 4`"; fi
# Cap the compressed bits per pixel.
HEROIMGPIXMAXBYTES="`echo $AGHBGEOM | awk -Fx '{print OHMIN+int(($1*$2*CBPP)/8)}' CBPP=$CBPP OHMIN=$OHMIN`"
echo "INFO: max bits-per-pixel $CBPP so max bytes $HEROIMGPIXMAXBYTES vs $MAXSIZE" >> $AGHBINFO
if [ "$HEROIMGPIXMAXBYTES" -lt "$MAXSIZE" ]; then
    echo "INFO: capping size by target bits-per-pixel: $CBPP $AGHBGEOM $MAXSIZE $HEROIMGPIXMAXBYTES" >> $AGHBINFO
    MAXSIZE="$HEROIMGPIXMAXBYTES"
fi
# Target lower-quality 'L' version for Save-Data at about half the file size.
MAXSIZEL="`expr $MAXSIZE / 2`"
# Constrain to make 'L' image version potentially inlineable for mobile.
if [ "false" != "$MOBILE" -a "$MAXSIZEL" -gt "$MAXINLINEIMGATF" ]; then
    MAXSIZEL=$MAXINLINEIMGATF;
fi
# Attempt to generate the banner (safely without a lock)...
AGTMP=$AGHBFILE.$$.tmp

echo "INFO: target result image file size: $MAXSIZE (L $MAXSIZEL)" >> $AGHBINFO

# Suggested flags for JPEG and PNG from:
#     https://www.smashingmagazine.com/2015/06/efficient-image-resizing-with-imagemagick/
# FLAGSJP="-filter Triangle -define filter:support=2 -thumbnail OUTPUT_WIDTH -unsharp 0.25x0.25+8+0.065 -dither None -posterize 136 -quality 82 -define jpeg:fancy-upsampling=off -define png:compression-filter=5 -define png:compression-level=9 -define png:compression-strategy=1 -define png:exclude-chunk=all -interlace none -colorspace sRGB -strip"
# Suggested by:
#     https://stackoverflow.com/questions/7261855/recommendation-for-compressing-jpg-files-with-imagemagick
#     -strip -interlace Plane -gaussian-blur 0.05 -quality 85% source.jpg result.jpg
# or:
#    -sampling-factor 4:2:0 instead of gaussian-blur
#

# Pre-tidy!
rm -f $AGHBFILE.$$.tmp* $AGHBDONE $AGHBFAIL
# Hook in handler to tidy up temp files on forced exit.
trap "rm -f $AGHBFILE.$$.tmp*; exit 1" 1 2 15

# If true then enable creation of 'LL' minimal placeholder images.
ENABLELL="false"

# ALTERNATE COMPACT FORMATS
# Ceiling size (%) of alternate format image to use.
MAXALTIMGPC=95
# Approximate uncompressed HTML overhead of an extra picture source tag.
PICTURESOURCEOH=101


$PERFTIMESTAMPS && echo "INFO: TS: GHII: ${OP}: scaling START" `$PERFTIMESTAMPCMD` 1>&2


# Deal with images by type.

# JPEG (.jpg)
if [ ".jpg" = "$AGHBSUFFIX" ]; then

    # On slow connections it may be good to use interlace/progressive
    # to get as much of the image rendered (coarsely) as possible in the first
    # network packet, eg ~1kB or so.  On the other hand interlacing may
    # (a) annoy users and (b) reduce perceived image 'quality' per byte for
    # small images and (c) be hard work for low-powered devices.
    #
    # So use it only for desktop-targeted images, which may also benefit
    # from improved compression with progressive being larger (~>10kB).
    #
    # Note that order of operations is important:
    #   * Any -trim should be before resizing.
    #   * Any colour reduction shoule be after resizing.
    #
    # Google suggests -sampling-factor 4:2:0
    #     https://developers.google.com/speed/docs/insights/OptimizeImages
    #
    # Hand-picked parameters:
    MAXQUALJP=82
    MINQUALJP=1
    FLAGSJ="-limit memory 200MB -sampling-factor 4:2:0 -strip"
    if [ "false" = "$MOBILE" -a "$MAXSIZE" -gt 10000 ]; then
        # Not mobile and >10kB: allow progressive.
        # This may be turned off again for lo-fi image versions.
        FLAGSJ="$FLAGSJ -interlace Plane"
    fi

    # Alternate (newer, potentially more compact, maybe not universal) formats.
    # In each case a lossless intermediate is used as input for the alternate.
    # Expected to be eventually JXL, AVIF and WEBP.
    ALTERNATESJPG="webp"
    # Most widely-supported (>>90% of browsers) JPG alternative, eg safe to inline.
    ALTERNATESJPGPOP=webp

    # When generating high-density images put a floor under quality.
    if [ "true" = "$HIGHDENSITY" ]; then
        MINQUALJP=65
    fi

# Lots of great ideas here:
#     https://medium.com/@duhroach/reducing-jpg-file-size-e5b27df3257c
# such as:
#   * (median) filtering chroma channels (eg in YCbCr space)
#   * use Butteraugli to test for the Psychovisual Error Threshold
#     (could be applied just to 'best' image, since remainder must be smaller)

#    # Versions of IM older than 7 may hang with -define jpeg:extent=$MAXSIZE
#    # so a manual loop reduing quality may need to be run in this case...
#    CONVER="`$CONVERT -version | awk '{print substr($3,1,1); exit}'`"
#
#    if [ "$CONVER" -gt 6 ]; then
#        # Let IM do everything.
#        $CONVERT $HEROIMG $FLAGSJ -define jpeg:extent=$MAXSIZE -resize "$AGHBGEOM"'^' -gravity center -extent "$AGHBGEOM" -quality $MAXQUALJP JPG:${AGTMP} 1>&2
#        MAXSIZELTHIS=$MAXSIZEL
#        AGTMPSIZE="`wc -c < $AGTMP`"
#        if [ "$AGTMPSIZE" -le "$MAXSIZELTHIS" ]; then
#            # Ensure that the L version is significantly smaller.
#            MAXSIZELTHIS="`expr \( $AGTMPSIZE \* 3 \) / 4`"
#        fi
#        $CONVERT $HEROIMGASFILE $FLAGSJ -define jpeg:extent=$MAXSIZELTHIS -resize "$AGHBGEOM"'^' -gravity center -extent "$AGHBGEOM" -quality $MAXQUALJP JPG:${AGTMP}L 1>&2
#    else
#        # Avoid poor compression and hanging in older versions...
#        echo "INFO: old ImageMagick $CONVERT: V$CONVER" 1>&2

    MAXHIFISIZE=$MAXSIZE

    # Make lossless reference image.
    NOLOSSTMP=$AGTMP.nlr.png.tmp
    # Aim is to create realistic 'low loss' source and reference for PSNR.
    # Ensure no optional (eg EXIF) metadata in this image.
    $CONVERT $HEROIMGASFILE -limit memory 200MB -strip -resize $AGHBGEOM'^' -gravity center -extent $AGHBGEOM PNG:$NOLOSSTMP | tee -a $AGHBINFO 1>&2
    # Make target reference image with unforced 'quality'.
    # Aim is to create realistic 'low loss' reference for size, etc.
    LOWLOSSTMP=$AGTMP.llr.tmp
    rm -f $LOWLOSSTMP
    $CONVERT $HEROIMGASFILE $FLAGSJ -resize $AGHBGEOM'^' -gravity center -extent $AGHBGEOM JPG:$LOWLOSSTMP | tee -a $AGHBINFO 1>&2
    LLASIZE="`wc -c < $LOWLOSSTMP`"
    #echo "INFO: LOSSLESSAPRX: $LLASIZE vs MAXSIZE=$MAXSIZE `ls -al $LOWLOSSTMP`" 1>&2
    if [ "$LLASIZE" -lt "$MAXHIFISIZE" ]; then MAXHIFISIZE=$LLASIZE; fi

    # Make target 'hi-fi' image.
    Q="$MAXQUALJP"
    while [ $Q -ge $MINQUALJP ];
    do
        rm -f $AGTMP
$PERFTIMESTAMPS && [ "$DEBUGPGBUILDPERF" -gt 1 ] && echo "INFO: $CONVERT $HEROIMGASFILE $FLAGSJ -resize $AGHBGEOM^ -gravity center -extent $AGHBGEOM -quality $Q JPG:$AGTMP" | tee -a $AGHBINFO 1>&2
        $CONVERT $HEROIMGASFILE $FLAGSJ -resize $AGHBGEOM'^' -gravity center -extent $AGHBGEOM -quality $Q JPG:$AGTMP | tee -a $AGHBINFO 1>&2

        if [ -s $AGTMP -a "`wc -c < $AGTMP`" -le "$MAXHIFISIZE" ]; then break; fi
        # Don't keep oversize image.
        rm -f $AGTMP
        # Take smaller (non-zero) steps when closer to 0.
        STEP="`expr 1 + \( $Q / 16 \)`"
        #if [ "$Q" -le "$STEP" ]; then break; fi
        Q="`expr $Q - $STEP`"
    done
    # Attempt final lossless shink of image.
    if [ -x $LOSSLESSJPEGCOMP -a -s $AGTMP ]; then
        $LOSSLESSJPEGCOMP $AGTMP 2>&1 | tee -a $AGHBINFO 1>&2
    fi

    if [ "true" != "$HIGHDENSITY" ]; then
        # Make target lo-fi image.
        # Smooth out in the YCbCr colourspace.
        # Force progressive off (more of these images will be used for mobile).
        # Force quality under 25 or at least down by 1 if any headroom left.
        # TODO: limit perceptual difference (abort if too high):
        #    ImageMagick V7 support SSIM [0,1], V6 best probably PSNR [1,[
        if [ "$Q" -gt 25 ]; then Q="25";
        elif [ "$Q" -gt 1 ]; then Q="`expr $Q - 1`";
        fi
        while [ $Q -gt 0 ];
        do
            rm -f ${AGTMP}L
$PERFTIMESTAMPS && [ "$DEBUGPGBUILDPERF" -gt 1 ] && echo "INFO: $CONVERT $HEROIMGASFILE $FLAGSJ -resize $AGHBGEOM^ -gravity center -extent $AGHBGEOM -colorspace YCbCr -median 8x8 -colorspace sRGB -interlace None -quality $Q JPG:${AGTMP}L" | tee -a $AGHBINFO 1>&2
            $CONVERT $HEROIMGASFILE $FLAGSJ -resize $AGHBGEOM'^' -gravity center -extent $AGHBGEOM -colorspace YCbCr -median 8x8 -colorspace sRGB -interlace None -quality $Q JPG:${AGTMP}L | tee -a $AGHBINFO 1>&2
            if [ -s ${AGTMP}L -a "`wc -c < ${AGTMP}L`" -le "$MAXSIZEL" -a "`wc -c < ${AGTMP}L`" -lt "`wc -c < $AGTMP`" ]; then break; fi
            # Don't keep oversize image.
            rm -f ${AGTMP}L
            STEP="`expr 1 + \( $Q / 8 \)`"
            #if [ "$Q" -le "$STEP" ]; then break; fi
            Q="`expr $Q - $STEP`"
        done
        # Attempt final lossless shink of image.
        if [ -x $LOSSLESSJPEGCOMP -a -s ${AGTMP}L ]; then
            $LOSSLESSJPEGCOMP ${AGTMP}L 2>&1 | tee -a $AGHBINFO 1>&2
        fi
    fi

    if [ "true" = "$ENABLELL" -a "true" != "$HIGHDENSITY" ]; then 
        # Make target minimum-weight lo-lo-fi image.
        # Make the image (no dither) monochrome.
        # Apply a lot of smoothing/filtering.
        rm -f ${AGTMP}LL
$PERFTIMESTAMPS && [ "$DEBUGPGBUILDPERF" -gt 1 ] && echo "INFO: $CONVERT $HEROIMGASFILE $FLAGSP -resize $AGHBGEOM'^' -gravity center -extent $AGHBGEOM -colorspace gray -median 8x8 -normalize -quality 1 -interlace None JPG:${AGTMP}LL" | tee -a $AGHBINFO 1>&2
        $CONVERT $HEROIMGASFILE $FLAGSP -resize $AGHBGEOM'^' -gravity center -extent $AGHBGEOM -colorspace gray -median 8x8 -normalize -quality 1 -interlace None JPG:${AGTMP}LL | tee -a $AGHBINFO 1>&2
    fi
    # Attempt final lossless shink of image.
    if [ -x $LOSSLESSJPEGCOMP -a -s ${AGTMP}LL ]; then
        $LOSSLESSJPEGCOMP ${AGTMP}LL 2>&1 | tee -a $AGHBINFO 1>&2
    fi

    # Attempt to create alternate image formats for JPG input.
    for fmt in $ALTERNATESJPG;
        do
        for q in . L LL;
            do
            if [ "." = "$q" ]; then qual=""; else qual=$q; fi
            imain=${AGTMP}${qual}
            itmp=${AGTMP}.${fmt}${qual}
            # If (temp version of) main image not present, skip.
            if [ ! -s "$imain" ]; then continue; fi
#echo "INFO: generating alternate $fmt for $imain main as tmp $itmp" | tee -a $AGHBINFO 1>&2
            if [ "webp" = "$fmt" ]; then
                # Attempt to build a temporary alternate
                # to move into place later.
                # TARGETPSNR includes a fudge factor from ImageMagick to WEBP.
                TARGETPSNR=`$COMPARE -metric PSNR $NOLOSSTMP ${imain} null: 2>&1 | awk '{print ($1 + 4.5)}'`
$PERFTIMESTAMPS && [ "$DEBUGPGBUILDPERF" -gt 1 ] && echo "INFO: TARGETPSNR $TARGETPSNR" | tee -a $AGHBINFO 1>&2
                # If it is zero length or not enough smaller, zap it.
                # NOTE: always generate from the *lossless* version.
$CWEBP -quiet -mt -psnr $TARGETPSNR -pass 10 -m 6 -af $NOLOSSTMP -o $itmp
                if [ ! -f "$itmp" ]; then continue; fi
                if [ ! -s "$itmp" ]; then rm -f "$itmp"; continue; fi
                imainsize="`wc -c < $imain | awk '{print $1}'`"
                itmpsize="`wc -c < $itmp | awk '{print $1}'`"
                # Allow for the HTML overhead of a new source tag.
                # Then enforce a mimimum % saving.
                maxtmpsize="`expr \( $imainsize \* $MAXALTIMGPC \) / 100`"
                #echo "INFO: imainsize=$imainsize maxtmpsize=$maxtmpsize itmpsize=$itmpsize" | tee -a $AGHBINFO 1>&2
                if [ "$itmpsize" -gt "$maxtmpsize" ]; then rm -f "$itmp"; continue; fi
echo "INFO: MADE smaller alternate $itmp $itmpsize cf $imainsize" | tee -a $AGHBINFO 1>&2
            fi
            # Skip formats not supported for automatic generation.
            done
        done

    rm -f $NOLOSSTMP $LOWLOSSTMP
fi


# PNG (.png)
# Control output size with -posterize NNN or -colors NNN
# Note that order of operations is important:
#   * Any -trim should be before resizing.
#   * Any colour reduction should be after resizing.
#   * For lo-fi images force removal of any alpha channel.
# For >6 bits per channel don't attempt to posterise/quantize.
#
# Force output to generic PNG rather than PNG8,
# eg to allow preserving of greyscale colourmaps, or of alpha/transparency.
# Note: png:compression-level 8 to save time since postprocessed w/ zopflipng.
FLAGSP="-limit memory 200MB -strip -define png:compression-level=8 -define png:exclude-chunk=all -interlace none -dither None"

# Alternate (newer, potentially more compact, maybe not universal) formats.
# In each case the generated PNG is used as input for the alternate,
# and the alternate is assumed to be a lossless (or very nearly so) copy.
ALTERNATESPNG="jxl webp"
# Most widely-supported (>>90% of browsers) PNG alternative, eg safe to inline.
ALTERNATESPNGPOP=webp
# Flags for when using WEBP as a PNG alternate.
# (Note: sometimes better without "-quality 100" flag.)
ALTERNATESPNGWEBPFLAGS="-limit memory 200MB -strip -quality 100 -define webp:method=6"

MAXDPNG=7
MINDPNG=2

    # When generating high-density images put a floor under quality.
    if [ "true" = "$HIGHDENSITY" ]; then
        MINDPNG=6
    fi

# Trim borders of hero images to make best use of space.
PNGTRIMCMD="-trim"
if [ "autogenImg" = "$OP" ]; then
    # Preserve body images untrimmed eg to avoid messing with info near edges.
    PNGTRIMCMD=""
fi

if [ ".png" = "$AGHBSUFFIX" ]; then
    # Make target image.
    D="$MAXDPNG"
    while [ $D -ge $MINDPNG ];
    do
        rm -f $AGTMP
        POSTFLAG="-colorspace sRGB -posterize $D"
        if [ $D -ge 7 ]; then POSTFLAG=""; fi
$PERFTIMESTAMPS && [ "$DEBUGPGBUILDPERF" -gt 1 ] && echo "INFO: $CONVERT $HEROIMGASFILE $FLAGSP $PNGTRIMCMD -resize $AGHBGEOM'^' -gravity center -extent $AGHBGEOM $POSTFLAG PNG:$AGTMP" | tee -a $AGHBINFO 1>&2
        $CONVERT $HEROIMGASFILE $FLAGSP $PNGTRIMCMD -resize $AGHBGEOM'^' -gravity center -extent $AGHBGEOM $POSTFLAG PNG:$AGTMP | tee -a $AGHBINFO 1>&2
        if [ -s $AGTMP -a "`wc -c < $AGTMP`" -le "$MAXSIZE" ]; then break; fi
        # Don't keep oversize image.
        rm -f $AGTMP
        D=`expr $D - 1`
    done
    # Attempt further compression (slow, ~8%).
    if [ -s $AGTMP -a -x $ZOPFLIPNG ]; then
        rm -f $AGTMP.zop
        # Hi-fi versions should not be lossy.
        $ZOPFLIPNG -m $AGTMP $AGTMP.zop >/dev/null 2>&1
        if [ -s $AGTMP.zop -a "`wc -c < $AGTMP.zop`" -lt "`wc -c < $AGTMP`" ]; then
            #echo "INFO: zopflipng compressed $AGTMP" | tee -a $AGHBINFO 1>&2
            mv $AGTMP.zop $AGTMP
        else
            rm $AGTMP.zop
        fi
    fi

    if [ "true" != "$HIGHDENSITY" ]; then
        # Make target lo-fi image.
        # Limit perceptual difference (abort if image too degraded),
        # because posterising can accidentally wash out all detail, for example.
        #   * ImageMagick V7 support SSIM [0,1], V6 best probably PSNR [1,[
        D=`expr $D - 1`
        if [ $D -gt 5 ]; then D=5; fi
        rm -f ${AGTMP}L ${AGTMP}Lt
        while [ $D -gt 1 ];
        do
            POSTFLAG="-colorspace sRGB -posterize $D"
$PERFTIMESTAMPS && [ "$DEBUGPGBUILDPERF" -gt 1 ] && echo "INFO: $CONVERT $HEROIMGASFILE $FLAGSP -alpha Remove $PNGTRIMCMD -resize $AGHBGEOM'^' -gravity center -extent $AGHBGEOM $POSTFLAG PNG:${AGTMP}L" | tee -a $AGHBINFO 1>&2
            $CONVERT $HEROIMGASFILE $FLAGSP $PNGTRIMCMD -resize $AGHBGEOM'^' -gravity center -extent $AGHBGEOM $POSTFLAG PNG:${AGTMP}Lt | tee -a $AGHBINFO 1>&2
            # If trial image is too large then abort this iteration without
            # using this trial image at all.
            SIZE="`wc -c < ${AGTMP}`"
            SIZELt="`wc -c < ${AGTMP}Lt`"
            if [ "$SIZELt" -lt "$SIZE" ]; then
                # Abort if size diffence too huge to be believable
                # (ie it is likely that most info has been thrown away).
                if [ "$SIZELt" -lt "`expr $SIZE / 10`" ]; then
                    #echo "INFO: giving up at image with size $SIZELt down from $SIZE: `ls -al ${AGTMP}Lt`" | tee -a $AGHBINFO 1>&2
                    break;
                fi
                # If perceptual difference too high then abort loop,
                # leaving previous lo-fi version (if any) in place,
                # on the grounds that the perceptual difference will get worse
                # as compression rises on successive rounds.
                PSNR=`$COMPARE -metric PSNR $AGTMP ${AGTMP}Lt null: 2>&1 | awk '{print int($1)}'`
                #echo "INFO: PSNR $PSNR (size $SIZELt vs $SIZE)" | tee -a $AGHBINFO 1>&2
                # Minimum acceptable PSNR value, determined empirically.
                PSNRMIN=16
                if [ "$PSNR" != "inf" -a "$PSNR" -lt "$PSNRMIN" ]; then
                    # Further compression will make for even worse images.
                    #echo "INFO: giving up at image with PSNR: $PSNR `ls -al ${AGTMP}Lt`" | tee -a $AGHBINFO 1>&2
                    break;
                fi

                # This image seems of acceptable fidelity: move into place.
                mv ${AGTMP}Lt ${AGTMP}L
                # On last-available level accept L image if any smaller.
                SIZEL="`wc -c < ${AGTMP}L`"
                if [ -s ${AGTMP}L -a \( $D -le 2 -o "$SIZEL" -le "$MAXSIZEL" \) ]; then
                    # Done.
                    break;
                fi
                # Don't keep oversize image.
                rm -f ${AGTMP}L
            fi
            D=`expr $D - 1`
        done
        rm -f ${AGTMP}Lt
        # Attempt further compression (slow, ~8%).
        if [ -s ${AGTMP}L -a -x $ZOPFLIPNG ]; then
            rm -f ${AGTMP}L.zop
            # Lo-fi versions can be lossy.
            $ZOPFLIPNG -m --lossy_8bit --lossy_transparent ${AGTMP}L ${AGTMP}L.zop >/dev/null 2>&1
            if [ -s ${AGTMP}L.zop -a "`wc -c < ${AGTMP}L.zop`" -lt "$SIZEL" ]; then
                #echo "INFO: zopflipng compressed ${AGTMP}L" | tee -a $AGHBINFO 1>&2
                mv ${AGTMP}L.zop ${AGTMP}L
            else
                rm ${AGTMP}L.zop
            fi
        fi
    fi

    if [ "true" = "$ENABLELL" -a "true" != "$HIGHDENSITY" ]; then 
        # Make target minimum-weight lo-lo-fi image.
        # Make the image 1-bit (no dither) monochrome.
        rm -f ${AGTMP}LL
$PERFTIMESTAMPS && [ "$DEBUGPGBUILDPERF" -gt 1 ] && echo "INFO: $CONVERT $HEROIMGASFILE $FLAGSP -alpha Remove $PNGTRIMCMD -resize $AGHBGEOM'^' -gravity center -extent $AGHBGEOM -colorspace gray -dither None -colors 2 -normalize -depth 1 -define png:color-type=0 -define png:bit-depth=1 PNG:${AGTMP}LL" | tee -a $AGHBINFO 1>&2
        $CONVERT $HEROIMGASFILE $FLAGSP -alpha Remove $PNGTRIMCMD -resize $AGHBGEOM'^' -gravity center -extent $AGHBGEOM -colorspace gray +dither -colors 2 -normalize -depth 1 -define png:color-type=0 -define png:bit-depth=1 PNG:${AGTMP}LL | tee -a $AGHBINFO 1>&2
        if [ -x $ZOPFLIPNG ]; then
            # Lo-fi versions can be lossy.
            $ZOPFLIPNG -m --lossy_8bit --lossy_transparent ${AGTMP}LL ${AGTMP}LL.zop >/dev/null 2>&1
            if [ -e ${AGTMP}LL.zop -a "`wc -c < ${AGTMP}LL.zop`" -lt "`wc -c < ${AGTMP}LL`" ]; then mv ${AGTMP}LL.zop ${AGTMP}LL; else rm ${AGTMP}LL.zop; fi
        fi
    fi

    # Attempt to create alternate image formats for PNG input.
    for fmt in $ALTERNATESPNG;
        do
        for q in . L LL;
            do
            if [ "." = "$q" ]; then qual=""; else qual=$q; fi
            imain=${AGTMP}${qual}
            itmp=${AGTMP}.${fmt}${qual}
            # If (temp version of) main image not present, skip.
            if [ ! -s "$imain" ]; then continue; fi
#echo "INFO: generating alternate $fmt for $imain main as tmp $itmp" | tee -a $AGHBINFO 1>&2
            if [ "webp" = "$fmt" ]; then
                # Attempt to build a temporary alternate
                # to move into place later.
                # If it is zero length or not enough smaller, zap it.
                #$CONVERT "$imain" $ALTERNATESPNGWEBPFLAGS $itmp
# ImageMagick seems not to work well as a wrapper on RPi with cwebp 0.5.2
$CWEBP -quiet -mt -q 100 -lossless -m 6 $imain -o $itmp
                if [ ! -f "$itmp" ]; then continue; fi
                if [ ! -s "$itmp" ]; then rm -f "$itmp"; continue; fi
                imainsize="`wc -c < $imain | awk '{print $1}'`"
                itmpsize="`wc -c < $itmp | awk '{print $1}'`"
                # Allow for the HTML overhead of a new source tag.
                # Then enforce a mimimum % saving.
                maxtmpsize="`expr \( $imainsize \* $MAXALTIMGPC \) / 100`"
#echo "INFO: imainsize=$imainsize maxtmpsize=$maxtmpsize itmpsize=$itmpsize" | tee -a $AGHBINFO 1>&2
                if [ "$itmpsize" -gt "$maxtmpsize" ]; then rm -f "$itmp"; continue; fi
#echo "INFO: MADE smaller alternate $itmp $itmpsize cf $imainsize" | tee -a $AGHBINFO 1>&2
            fi
            # Skip formats not supported for automatic generation.
            done
        done

fi

$PERFTIMESTAMPS && echo "INFO: TS: GHII: ${OP}: atomic update" `$PERFTIMESTAMPCMD` 1>&2

# If generated images exist, and are a sensible size, and are new/changed,
# then atomically move into place.
# (Leave unchanged images and their timestamps alone for good cacheing.)
# The file will also need appropriate permissions for publishing.
# Note mop-up failure case end.
if [ -s $AGTMP ]; then
if [ "`wc -c < $AGTMP`" -le "$MAXSIZE" ]; then
    if cmp -s "${AGTMP}" "${AGHBFILE}"; then
        echo "INFO: leaving alone unchanged ${AGHBFILE}" | tee -a $AGHBINFO 1>&2
    else
        chmod 444 "$AGTMP"
        if [ -e $AGHBFILE ]; then chmod -f u+w "$AGHBFILE"; fi
        mv -f "$AGTMP" "$AGHBFILE"
        touch "$AGHBDONE"
        echo "INFO: generated hero from hero image (target $AGHBGEOM) for ${ARTBODY} for mobile=$MOBILE: `ls -l $AGHBFILE`" | tee -a $AGHBINFO 1>&2
    fi
    if [ ! -s ${AGTMP}L ]; then
        # Avoid leaving a possibly-broken file in place.
        rm -f ${AGHBFILE}L
    else
        if [ "`wc -c < ${AGTMP}L`" -ge "`wc -c < $AGHBFILE`" ]; then
            # Avoid leaving a possibly-broken file in place.
            rm -f ${AGHBFILE}L ${AGTMP}L
        else
            # Move the lo-fi image into place if created and smaller.
            #
            # As this file is not used for timestamping
            # then leave it alone if byte-for-byte identical
            # to help preserve caches on low-bandwidth devices!
            if cmp -s ${AGTMP}L ${AGHBFILE}L; then
                echo "INFO: leaving alone unchanged ${AGHBFILE}L" | tee -a $AGHBINFO 1>&2
            else
                chmod 444 "${AGTMP}L"
                if [ -e "${AGHBFILE}L" ]; then chmod -f u+w "${AGHBFILE}L"; fi
                mv -f "${AGTMP}L" "${AGHBFILE}L"
                echo "INFO: generated Save-Data lite hero from hero image (target $AGHBGEOM) for ${ARTBODY} for mobile=$MOBILE: `ls -l ${AGHBFILE}L`" | tee -a $AGHBINFO 1>&2
            fi
            if [ ! -s "${AGTMP}LL" ]; then
                # Avoid leaving a possibly-broken file in place.
                rm -f "${AGHBFILE}LL"
            else
                if [ "`wc -c < ${AGTMP}LL`" -ge "`wc -c < ${AGHBFILE}L`" ]; then
                    # Avoid leaving a possibly-broken file in place.
                    rm -f "${AGHBFILE}LL" "${AGTMP}LL"
                else
                    # Move the minimal lo-lo-fi image into place if created and smaller.
                    #
                    # As this file is not used for timestamping
                    # then leave it alone if byte-for-byte identical
                    # to help preserve caches on low-bandwidth devices!
                    if cmp -s ${AGTMP}LL ${AGHBFILE}LL; then
                        echo "INFO: leaving alone unchanged ${AGHBFILE}LL" | tee -a $AGHBINFO 1>&2
                    else
                        chmod 444 "${AGTMP}LL"
                        if [ -f "${AGHBFILE}LL" ]; then chmod -f u+w "${AGHBFILE}LL"; fi
                        mv -f "${AGTMP}LL" "${AGHBFILE}LL"
                        echo "INFO: generated minimal stand-in hero from hero image (target $AGHBGEOM) for ${ARTBODY} for mobile=$MOBILE: `ls -l ${AGHBFILE}LL`" | tee -a $AGHBINFO 1>&2
                    fi
                fi
            fi
        fi
    fi

    # Deal with alternate more-compact image formats.
    # Atomically move into place, or remove.
    #
    for fmt in $ALTERNATESALL;
        do
        # Move into place atomically if sufficiently smaller,
        # else remove.
        for q in . L LL;
            do
            if [ "." = "$q" ]; then qual=""; else qual=$q; fi
            imain="${AGHBFILE}${qual}"
            ialt="${AGHBFILE}.${fmt}${qual}"
            itmp="${AGTMP}.${fmt}${qual}"
            # If the main file exists AND
            # if the temporary alternate exists and is enough smaller than main,
            # then move it into place atomically.
            if [ -s "${imain}" ] && [ -s "${itmp}" ]; then
                imainsize="$(wc -c < "$imain" | awk '{print $1}')"
                itmpsize="$(wc -c < "$itmp" | awk '{print $1}')"
#echo "INFO: considering $imain main $imainsize vs $ialt alternate tmp $itmp $itmpsize" | tee -a $AGHBINFO 1>&2
                maxtmpsize="$(expr $imainsize - $PICTURESOURCEOH)"
                if [ "$itmpsize" -lt "$maxtmpsize" ]; then
                    # If unchanged, simply discard the temporary.
                    if cmp -s "${itmp}" "${ialt}"; then
                        rm -f "${itmp}";
                        echo "INFO: leaving alone unchanged alternate ${ialt}" | tee -a $AGHBINFO 1>&2
                        continue;
                    fi
                    chmod 444 "${itmp}"
                    if [ -f "${ialt}" ]; then chmod -f u+w "${ialt}"; fi
                    mv -f "${itmp}" "${ialt}"
                    continue
                fi
            fi
            # Else remove the alternate and its temporary if any.
            rm -f "${ialt}" "${itmp}"
            done
        done

$PERFTIMESTAMPS && echo "INFO: TS: GHII: ${OP}: scaling END" `$PERFTIMESTAMPCMD` 1>&2

    # Mark as done, tidy up.
    touch "$AGHBDONE"
    rm -f $AGHBFILE.$$.tmp* "$AGHBFAIL" "$AGLOCKFILE"

    # Echo the target file and exit.
    echo "$AGHBFILE"
    exit 0
fi
fi

# Error/failure exit.
echo "INFO: failed to generate hero from hero image (target $AGHBGEOM) for ${ARTBODY} for mobile=$MOBILE: $AGHBFILE" | tee -a $AGHBINFO 1>&2
# Make the build attempt as failed so that it need not be retried.
touch $AGHBFAIL
chmod go-rwx "$AGHBFAIL"
# Avoid leaving a possibly-broken result file and/or lock in place.
rm -f $AGHBFILE $AGHBDONE $AGLOCKFILE
rm -f $AGHBFILE.$$.tmp* 
exit 1;;


insTop|carouselEntryImg|carouselEntryImgBig) #--------------------------------
# insTop
#     Generate HTML to insert hero code at top of wrapped version of page.
#     If no suitable image is available then this may return an empty result,
#     and a zero (non-error) return code, or may build and cache an image.
#     The img tag may be wrapped in a link to the full image
#     and a div for a banner image.
#
#     May use alternate version of image varying only in a trailing -NNNw
#     so long as it is no larger in filesize and pixels,
#     and is at least the target size for the style of image and display.
#     May use smaller or no hero image for mobile.
#
# carouselEntryImg (or carouselEntryImgBig for larger header image)
#     Generate responsive img tag suitable to slot into a column
#     (single column for narrow screen, double/triple for desktop)
#     with hero image and title/description.
#     Returns nothing if no suitable content.
#     Can (eg) be tried with each of most popular/newest in turn
#     to find first one(s) suitable to generate carousel content from.
#     The img tag is not wrapped in any way.
#
# Note that carouselEntryImg generates default-style image insTop
# except of a different class.
#
# All of these may depend only on the hero image, $MOBILE and $OP.
# In particular per-page customisations must not happen
# and this in turn allows one cached header per hero/MOBILE/OP.

# PARALLEL SAFE
# Success marker flag and result cache setup.
# Dir and file prefix for insTop (and related) done/failed markers.
FLAGSSDIR=$CACHEDIRAG/$CACHEDIRHERO/.flags-insTop
# Note that the header generated depends on the hero selected.
FLAGSPREF="`basename $HEROIMG`"
if [ "false" != "$MOBILE" -o "insTop" != "$OP" ]; then
    # Common case is desktop insTop, but this allows for others, unambiguously.
    FLAGSPREF="$OP=$MOBILE=$FLAGSPREF"
fi
mkdir -p $FLAGSSDIR || exit 1
# Result HTML fragment and success marker.
AGHBRESULT=$FLAGSSDIR/$FLAGSPREF.html
# If the result file exists and is new enough, then succeed immediately.
# It must be newer than the force-rebuild flag.
if [ -f $AGHBRESULT -a $AGHBRESULT -nt $REBUILDFLAG ]; then
#$PERFTIMESTAMPS && echo "INFO: TS: GHII: cached: ${OP}:" `$PERFTIMESTAMPCMD` 1>&2
    cat $AGHBRESULT
    exit 0
fi
# Pre-tidy!
rm -f $AGHBRESULT.*.tmp

$PERFTIMESTAMPS && echo "INFO: TS: GHII: uncached: START: ${OP}:" `$PERFTIMESTAMPCMD` 1>&2

# Fake ARTBODY to avoid accidental dependencies on HTML source file.
FAKEARTBODY=.FAKE.html

SELHEROIMG=""
SELHEROIMGISBANNER="false"
SELHEROIMGHEAVY=false
# If generating a hero image insert (primarily for desktop pages)
# then try an alternate algorithm first to find a page-wide banner image.
if [ "insTop" = "$OP" -o "carouselEntryImgBig" = "$OP" ]; then
    SELHEROIMG="`$0 chooseBannerImg $HEROIMG $FAKEARTBODY $STATICCDNPREFIX $MOBILE $FWPX`"

    SELHEROIMGHEAVY=false
    if [ "" != "$SELHEROIMG" ]; then
        if [ "`wc -c < $SELHEROIMG`" -gt $HEROIMGMAXBYTES ]; then
            SELHEROIMGHEAVY=true
        fi
    fi

    # If no suitable manually-created hero image was found,
    # or appears to be somewhat over-weight,
    # then try for an autogenerated version.
    if [ "" = "$SELHEROIMG" -o "true" = "$SELHEROIMGHEAVY" ]; then
        :
        AGSELHEROIMG="`$0 autogenBannerImg $HEROIMG $FAKEARTBODY $STATICCDNPREFIX $MOBILE $FWPX`"
        # Don't override a manually-selected image unless autogen made one.
        if [ "" != "$AGSELHEROIMG" ]; then
            SELHEROIMG="$AGSELHEROIMG"
            SELHEROIMGHEAVY=false; # Assume autogen image weight OK.
        fi
    fi

    if [ "" != "$SELHEROIMG" ]; then
        SELHEROIMGISBANNER="true"
    fi
fi

# Get fallback/default hero image, eg to use as float right or as column header.
if [ "" = "$SELHEROIMG" ]; then
    # By default aim for standard float-right or multi-column-header size.
    WIDTH=$FHIPX
    if [ "carouselEntryImgBig" = "$OP" ]; then
        # For desktop 'big' carousel / column header aim for full page width.
        WIDTH=$FWPX
        if [ "false" != "$MOBILE" ]; then
            # For mobile have a slightly-larger-than-column-head image.
            WIDTH=$DRFRSPX
        fi
    fi

    SELHEROIMG="`$0 chooseImg $HEROIMG $FAKEARTBODY $STATICCDNPREFIX $MOBILE $WIDTH`"

    SELHEROIMGHEAVY=false
    if [ "" != "$SELHEROIMG" ]; then
        if [ "`wc -c < $SELHEROIMG`" -gt $HEROIMGMAXBYTES ]; then
            SELHEROIMGHEAVY=true
        fi
    fi

    # If no suitable manually-created hero image was found,
    # then try for an autogenerated small squarish one.
    # If (informational) column header, eg for front page, avoid very-lo-fi image.
    IMGALGO=autogenBannerImg
    #if [ "carouselEntryImgBig" = "$OP" -o "carouselEntryImg" = "$OP" ]; then
    #    # Higher-fi images for front page, etc.
    #    IMGALGO=autogenImg
    #fi
    if [ "" = "$SELHEROIMG" -o "true" = "$SELHEROIMGHEAVY" ]; then
        AGSELHEROIMG="`$0 $IMGALGO $HEROIMG $FAKEARTBODY $STATICCDNPREFIX $MOBILE $WIDTH`"
    fi
    # Don't overwrite a manually-selected image unless autogen made one.
    if [ "" != "$AGSELHEROIMG" ]; then
        SELHEROIMG="$AGSELHEROIMG"
        SELHEROIMGHEAVY=false; # Assume autogen image weight OK.
    fi
fi

if [ "" = "$SELHEROIMG" -o ! -f "$SELHEROIMG" ]; then
    echo "INFO: hero image(s) [$SELHEROIMG] NOT AVAILABLE for ${ARTBODY} for mobile=$MOBILE from declared ${HEROIMG}" 1>&2
    # No suitable image found, so empty result, but not an error.
    # Mark as done, tidy up.
    printf "" > $AGHBRESULT
    exit 0
# Warn (but still use) if image heavier than desirable.
elif [ "`wc -c < $SELHEROIMG`" -gt "$HEROIMGMAXBYTES" ]; then
    echo "WARNING: hero image(s) OVER-WEIGHT for ${ARTBODY} mobile=$MOBILE `wc -c < $SELHEROIMG` bytes vs suggested max $HEROIMGMAXBYTES, abs max $HEROIMGABSMAXBYTES bytes: ${SELHEROIMG}" 1>&2
fi

# Extract selected hero image width and height (or nothing in case of error).
SELHEROIMGW=""
SELHEROIMGH=""
SELHEROIMGWH="`script/get_img_XxY $SELHEROIMG`"
if [ "" != "$SELHEROIMGWH" -a "1x1" != "$SELHEROIMGWH" ]; then
    SELHEROIMGW="`echo $SELHEROIMGWH | awk -Fx '{print $1}'`"
    SELHEROIMGH="`echo $SELHEROIMGWH | awk -Fx '{print $2}'`"
else
    echo "ERROR: $0 ${OP}: unable to extract XxY from $HEROIMG / $SELHEROIMG." 1>&2
    rm -f $AGHBRESULT
    exit 1
fi

# Auto-construct alt text from hero image name.
ALT="`script/altTextFromFilename $HEROIMG`"
ALTVALUE="\"$ALT\""
if [ "" != "`echo "$ALT" | egrep '^[A-Za-z0-9]{1,}$'`" ]; then
    ALTVALUE="$ALT"
fi
# If there is a non-empty ALT text then generate a title attribute too.
# Give this a leading space if non-empty to allow it to be injected efficently.
# Omit for non-desktop target as there is no hover and so no tooltip.
TITLESAT=""
if [ "" != "$ALT" -a "false" = "$MOBILE" ]; then
    TITLESAT=" title=$ALTVALUE"
fi

# Adjust the class as appropriate.
# Also, wrap in a div if not to be floated.
# Note that if multiple classes are needed then " should be inserted.
# Carousel images get rounded corners on desktop too.
IMGCLASS="respfloatrsml"
if [ "carouselEntryImg" = "$OP" -o "carouselEntryImgBig" = "$OP" ]; then
    IMGCLASS='"resp himg"';
    DIVWRPS="<div>"
    DIVWRPE="</div>"
elif [ "true" = "$SELHEROIMGISBANNER" ]; then
    IMGCLASS="resp";
    DIVWRPS="<div>"
    DIVWRPE="</div>"
fi

# Image URL defaults to selected image on static server.
IMGURL="$CDNPREFIX$SELHEROIMG"
if [ "false" = "$MOBILE" ]; then IMGURL="$SELHEROIMG"; fi
# On the mobile/'lite' site, to help overcome RTT to fetch the ATF hero image
# (and to save bandwidth for tiny non-ATF hero images such as column headers),
# if the image (or lo-fi version) is small enough then inline it.
# For desktop no lo-fi images are considered.
# This does not consider any place-holder 'LL' or worse variants.
INLINED="false"
# Per-image styling, eg to blur very lo-fi images.
# By default empty, else with leading space.
IMGSTYLE=""
CANDIDATEINILINEIMGS="${SELHEROIMG}"
if [ "false" != "$MOBILE" ]; then
    # The low-fi image is a candidate for all non-desktop users.
    CANDIDATEINILINEIMGS="${CANDIDATEINILINEIMGS} ${SELHEROIMG}L"
fi
for f in ${CANDIDATEINILINEIMGS}; do
    if [ ! -s $f ]; then continue; fi
    #echo "INFO: ${OP}: possible inline $f ($IMGSIZE) for ${ARTBODY} for mobile=$MOBILE" 1>&2
    DATAURL="$($0 "inline-$1" "$f" "$FAKEARTBODY" "$STATICCDNPREFIX" "$MOBILE" "" "" "" "" "$ARGKVP")"

    # Use the dataurl, if created, as the img src.
    if [ "" != "$DATAURL" ]; then
        #echo "INFO: inlined non-body img ($(echo $DATAURL | wc -c)B) $f" 1>&2
        IMGURL='"'"$DATAURL"'"';
        INLINED="true"
        break
    fi
done

# Try to expand a carousel image to full width of its container.
# (Don't do this if IMGSTYLE already in use.)
# (Don't do this for AMP; it breaks..)
if [ "" = "$IMGSTYLE" -a "amp" != "$MOBILE" ]; then
    case $OP in
        carouselEntryImg*) IMGSTYLE=' style=width:100%';;
    esac
fi

# Force a banner hero image into its own div.
# Mobile pages may be viewed on a desktop screen and vice versa.
# If the image is undersize for the *max* container width, stretch to fit.
# Desktop banner can be wrapped in a picture element
# to allow use of lower-weight mobile banner when viewed on small screen,
# and more compact alternate image formats.
DIVWRPS=""
DIVWRPE=""
PICWRPS=""
PICWRPE=""
if [ "true" = "$SELHEROIMGISBANNER" ]; then
    DIVWRPS="<div>"
    DIVWRPE="</div>"
    if [ "$SELHEROIMGW" -lt "$FWPX" ]; then
        IMGCLASS="respF";
    fi
fi
if [ "insTop" = "$OP" -o "carouselEntryImgBig" = "$OP" -o "carouselEntryImg" = "$OP" ]; then
    # For desktop banners, recursively try to locate mobile header image.
    # (Actually sharing/reusing the mobile image version may improve cacheing.)
    # This uses a much-simplified search algorthm,
    # only using auto-generated images,
    # as likely optimally small and backed by lo-fi 'L' versions too.
    # Don't do this if the main image is being inlined.
    SMLHEROIMG=""
    if [ "true" = "$SELHEROIMGISBANNER" -a "false" = "$MOBILE" -a "true" != "$INLINED" ]; then
        #echo "INFO: $0: get mobile header $OP for $HEROIMG for $ARTBODY." 1>&2
        SMLHEROIMG="`$0 autogenBannerImg $HEROIMG $FAKEARTBODY $STATICCDNPREFIX true $MBODYPX`"
    fi

    # By default don't wrap as a picture element.
    WRAPHEROIMGASPICTURE=false
    # Wrap as picture if a smaller (lite) image exists.
    if [ "" != "$SMLHEROIMG" ]; then
         WRAPHEROIMGASPICTURE=true
    fi

    # Wrap as picture if more-compact alternate formats exist.
    # Don't look for them when inlining the primary image anyway.
    SELCOMPACT=""
    if [ "true" != "$INLINED" ]; then
        for format in $ALTERNATESALL;
            do
                f=$SELHEROIMG.$format
                if [ -s $f ]; then
                    if [ "`wc -c < $f`" -lt "`wc -c < $SELHEROIMG`" ]; then
                        SELCOMPACT="$SELCOMPACT $f";
                        # Need not deal with special as-is case below.
                        continue;
                    fi
                fi
                # SPECIAL CASE: image is being directly used as-is
                # If there isn't this alternate format in the usual place
                # and the SELHEROIMG is being used as-is (not autogenerated)
                # then look in the autogenerated area for alternate instead.
                # Note: small risk of name clash with resized images...
                if [ "" != "`echo $SELHEROIMG | egrep '^'$CACHEDIRAG/`" ]; then
                    continue;
                fi
                f2=$AGHBDIR/`basename $SELHEROIMG`.$format
#echo "INFO: $0: looking for alternate format hero of as-is $SELHEROIMG as $f2 for $ARTBODY." 1>&2
                # Attempt to (re)create alternate format.
                script/build_alternate_format_compact_image $SELHEROIMG $f2 $SELHEROIMG | tee -a $AGHBINFO 1>&2
                if [ -s $f2 ]; then
                    if [ "`wc -c < $f2`" -lt "`wc -c < $SELHEROIMG`" ]; then
                        SELCOMPACT="$SELCOMPACT $f2";
echo "INFO: $0: found alternate format hero of as-is $SELHEROIMG as $f2 for $ARTBODY." 1>&2
                        continue;
                    fi
                fi
            done
    fi
    SMLCOMPACT=""
    if [ "" != "$SMLHEROIMG" -a "true" != "$INLINED" ]; then
        for format in $ALTERNATESALL;
            do
                f=$SMLHEROIMG.$format
                if [ -s $f ]; then
                    if [ "`wc -c < $f`" -lt "`wc -c < $SMLHEROIMG`" ]; then
                        SMLCOMPACT="$SMLCOMPACT $f";
                    fi
                fi
            done
    fi
    if [ "" != "$SELCOMPACT" -o "" != "$SMLCOMPACT" ]; then
         WRAPHEROIMGASPICTURE=true
    fi

    if [ "true" = "$WRAPHEROIMGASPICTURE" ]; then
        #echo "INFO: $0: wrapping hero as picture for $OP for $HEROIMG for $ARTBODY." 1>&2
        # STATICCDNPREFIX not needed for desktop.
        PICWRPS='<picture>'

        # Insert any compact versions of lite/small image, smallest first.
        if [ "" != "$SMLCOMPACT" ]; then
            for f in `ls -1Sr $SMLCOMPACT`
                do
                    SMLHEROIMGALTERNATE=$f;
                    SMLHEROIMGALTERNATEFMT=`echo $f | awk -F. '{print $NF}'`
                    PICWRPS="$PICWRPS"'<source media="(max-width:'$MBODYPX'px)" type=image/'$SMLHEROIMGALTERNATEFMT' srcset='${SMLHEROIMGALTERNATE}'>'
                    # Can stop once popular format added and rest are bigger.
                    if [ "$ALTERNATESALLPOP" = "$SMLHEROIMGALTERNATEFMT" ]; then break; fi
                done
        fi

        # Insert lite/small image if available.
        if [ "" != "$SMLHEROIMG" ]; then
            PICWRPS="$PICWRPS"'<source media="(max-width:'$MBODYPX'px)" srcset='${SMLHEROIMG}'>'
        fi

        # Insert any compact versions of full hero image, smallest first.
        if [ "" != "$SELCOMPACT" ]; then
$PERFTIMESTAMPS && [ "$DEBUGPGBUILDPERF" -gt 1 ] && echo "INFO: $0: SELCOMPACT = $SELCOMPACT for $HEROIMG for $ARTBODY." 1>&2
            for f in `ls -1Sr $SELCOMPACT`
                do
                    SELHEROIMGALTERNATE=$f;
                    SELHEROIMGALTERNATEFMT=`echo $f | awk -F. '{print $NF}'`
                    PICWRPS="$PICWRPS"'<source type=image/'$SELHEROIMGALTERNATEFMT' srcset='${SELHEROIMGALTERNATE}'>'
                    # Can stop once popular format added and rest are bigger.
                    if [ "$ALTERNATESALLPOP" = "$SELHEROIMGALTERNATEFMT" ]; then break; fi
                done
        fi

        PICWRPE="</picture>"
    fi
fi

# For desktop insTop hero page-top inserts link to full declared image,
# providing that it is not the image already displayed.
HREFS=""
HREFE=""
if [ "insTop" = "$OP" -a "false" = "$MOBILE" -a "$HEROIMG" != "$SELHEROIMG" ]; then
    # STATICCDNPREFIX not needed for desktop.
    HREFS="<a href=${HEROIMG}>"
    HREFE="</a>"
fi

# Width and height attributes with leading space if available, else "".
# Don't bother with width and height if image is being inlined, unless on AMP.
ATTRWH=" width=${SELHEROIMGW} height=${SELHEROIMGH}"
if [ "amp" != "$MOBILE" -a "true" = "$INLINED" ]; then
    ATTRWH=""
fi

# Support img pre-render cssgip-like style="background:#xxx" here.
# Hero images are decorative, so showing a helpful placeholder ASAP is good.
# Almost certainly no point if the image is inlined.
# Don't bother for AMP, which has its own placeholder scheme.
# Don't bother for offline, as there in no network latency.
# Cache the result as it is somewhat expensive to compute.
# Or it could be computed and cached along with the image itself,
# and used if available and pre-computed.
# STYLECSSBG is either empty or a style attr/value with a leading space.
# The STYLECSSBG colour value is effectively a hash over the image,
# so should probably go at the end of the img tag.
#
# If there is an existing IMGSTYLE then style info is appended to that instead.
STYLECSSBG=""
if [ "amp" != "$MOBILE" -a "offline" != "$MOBILE" -a "true" != "$INLINED" ]; then
    # Encode hex colour in lowercase to help compression.
    BGCOLHEX="`$0 repColOpt $SELHEROIMG $FAKEARTBODY $STATICCDNPREFIX $MOBILE`"
    #echo "INFO: $0: BGCOLHEX=$BGCOLHEX for $HEROIMG for $ARTBODY." 1>&2
    if [ "" != "$BGCOLHEX" ]; then
        if [ "" != "$IMGSTYLE" ]; then
            IMGSTYLE="$IMGSTYLE;background:$BGCOLHEX"
        else
            STYLECSSBG=" style=background:$BGCOLHEX"
        fi
    fi
fi

# Default to eager load and decode.
LOADING=""
# Set flag to L if image is set to lazy load.
LAZYFLAG=""

# Full-width banner is the default for a page-top hero image.
# Nominal default was to insert the page-top hero image as a small right float.
# Will omit width and height attributes if their values are unavailable,
# to keep the HTML valid even at the cost of page rendering delays/wobbles.
# The src attr comes after esp width/height to help data url page layout.
# NOTE: generates void HTML tag without trailing "/>".  Not XHTML-friendly.
AGHBRESULTTMP=$AGHBRESULT.$$.tmp
if [ "amp" != "$MOBILE" ]; then
    # Loading of prominent hero image should possibly be eager,
    # and if inline with synchronous decoding to get image dimensions.
    # In part this may reduce visible page 'jank'
    # (and some spurious network traffic),
    # even if the image content is not of itself necessarily vital information.
    # Decoding may be best eager or left as default (which saves some HTML).
    # If the image is inlined then it is pointless to add loading=eager!
    if [ "true" = "$INLINED" ]; then
        # Make eager for inlined to extract width and height ASAP.
        LOADING=""
    elif [ "insTop" = "$OP" -o "carouselEntryImgBig" = "$OP" ]; then
        # Big (banner, leading, key?) hero image loading not lazy.
        LOADING=" decoding=async"
    elif [ "true" != "$INLINED" ]; then
        # Else lazy if not inlined.
        LOADING=" loading=lazy"
        LAZYFLAG="L"
    fi

    # Normal HTML5, desktop or mobile...
    #echo "INFO: $0: $OP: wrapped $HEROIMG @$ATTRWH for for $ARTBODY." 1>&2
    #if [ "offline" = "$MOBILE" -a "true" != "$INLINED" ]; then
    #    # If common (smaller) alternative found, use it instead.
    #    for f in $ALTERNATESALLPOP;
    #        do if [ -s $IMGURL.$f ]; then IMGURL=$IMGURL.$f; break; fi; done
    #    # Used to capture offline dependencies.
    #    echo "INFO: IMGSRC: $IMGURL" 1>&2
    #fi
    # In case of long inlined src, put it after all other attributes.
    CLASSATTR="";if [ "raw" != "$IMGCLASS" ];then CLASSATTR=" class=$IMGCLASS";fi
    echo "$DIVWRPS$HREFS$PICWRPS"'<img'"$CLASSATTR$ATTRWH"' alt='$ALTVALUE$TITLESAT$STYLECSSBG$IMGSTYLE$LOADING' src='"$IMGURL"'>'"$PICWRPE$HREFE$DIVWRPE" | tee $AGHBRESULTTMP
    # Atomic update for parallel/thread safety.
    mv -f $AGHBRESULTTMP $AGHBRESULT
else
    # Well, AMP is a little different!
    if [ "" = "$ATTRWH" ]; then
        echo "ERROR: $0: could not get dimensions of $HEROIMG for AMP page for $ARTBODY." 1>&2
        rm -f $AGHBRESULT
        exit 1
    fi
    # Always lazy-loaded for AMP.
    LAZYFLAG="L"
    # In case of long inlined src, put it after all other attributes.
    echo "$DIVWRPS$HREFS"'<amp-img layout=intrinsic class='"$IMGCLASS$ATTRWH"' alt='$ALTVALUE$TITLESAT$IMGSTYLE' src='"$IMGURL"'></amp-img>'"$HREFE$DIVWRPE" | tee $AGHBRESULTTMP
    # Atomic update for parallel/thread safety.
    mv -f $AGHBRESULTTMP $AGHBRESULT
fi

## Help computation of total page weight without lazy loading or user action.
#if [ "true" != "$INLINED" -a -f "$SELHEROIMG" ]; then
#    echo "INFO: SRCBYTES: IMG: `wc -c < $SELHEROIMG`" $LAZYFLAG 1>&2
#fi

$PERFTIMESTAMPS && echo "INFO: TS: GHII: uncached: END: ${OP} for ${ARTBODY}:" `$PERFTIMESTAMPCMD` 1>&2

exit 0;;


chooseImg|chooseBannerImg) #-----------------------------------------------
#  chooseImg
#     Choose largest (bytes and px) version of declared hero image,
#     within the display byte weight limits.
#     If no suitable image is available then this will return an empty result,
#     and a zero (non-error) return code, or may build and cache an image.
#
#     The optional first extra arg ($6) is the target width in pixels.
#     MAY BE IGNORED.
#
#  chooseBannerImg
#     Choose largest (bytes and px) version of declared hero image,
#     of at least given width (possibly with a tolerance)
#     and with a suitable 'letterbox' aspect ratio,
#     within the display byte weight limits.
#     If no suitable image is available then this will return an empty result,
#     and a zero (non-error) return code, or may build and cache an image.
#
#     The optional first extra arg ($6) is the target minimum width in pixels.
#
# chooseBannerImg may depend only on the hero image and $MOBILE
# In particular per-page customisations must not happen
# and this in turn allows one cached header per hero/MOBILE.

# Extract declared hero image size in bytes.
HEROIMGB="`wc -c < ${HEROIMG}`"

# Assume that any image with the same stem and image type/suffix,
# ie differing only in and optional trailing -NNNw width indicator.
# is interchangeable as a hero image, subject to size constraints.
# This will not slect anything heavier than the passed-in hero image.
#
# Compute the name stem and the suffix (without dot).
# The base name is stripped of the .jpg/.png type
# and of any trailing -NNNw part too.
HEROIMGSUFFIX="`echo $HEROIMG | sed -n -e 's/.*[.]\([^.]*\)$/\1/p'`"
#echo "HEROIMGSUFFIX: $HEROIMGSUFFIX" 1>&2
HEROIMGSTEM="`echo $HEROIMG | sed -e 's/-[0-9]*w[.][^.]*$//' -e 's/[.][^.]*$//'`"
#echo "HEROIMGSTEM: $HEROIMGSTEM" 1>&2
# Compute candiate images, with a mixture of shell globbing and regex horror.
# Listed in descending size order
# (this assumes that images with more bytes have more pixels/detail)
# so that the first suitable one can be used in the simplest case.
HEROIMGCANDIDATES="`ls -1S ${HEROIMGSTEM}*${HEROIMGSUFFIX} | \
    egrep ${HEROIMGSTEM}'(-[0-9]*w){0,1}[.]'${HEROIMGSUFFIX}`"
#echo "HEROIMGCANDIDATES: $HEROIMGCANDIDATES" 1>&2

# By default no hero image is accepted.
SELHEROIMG=""

for IMG in $HEROIMGCANDIDATES;
    do
    # Extract declared hero image size in bytes.
    IMGB="`wc -c < ${IMG}`"
    #echo "INFO: $IMG $IMGB" 1>&2

    # Do not use if image is much too big to use for given display.
    if [ $IMGB -gt $HEROIMGABSMAXBYTES ]; then
        # Skip, but with no error.
        continue;
    fi

    # Do not use if image bigger than the passed-in (reference) image.
    if [ $IMGB -gt $HEROIMGB ]; then
        # Skip, but with no error.
        continue;
    fi

    # Do not use unless small enough to inline for mobile banner.
    if [ "chooseBannerImg" = "$OP" -a "false" != "$MOBILE" -a "${IMGB}" -gt "$MAXINLINEIMGATF" ]; then
        #echo "INFO: banner hero image too big to inline for MOBILE for ${ARTBODY} at $IMGB bytes against max $MAXINLINEIMGATF: ${IMG}" 1>&2
        # Skip, but with no error.
        continue;
    fi

    # DHD20210815: dependency on target HTML page not permitted.
    ## Do not use for mobile if image heavier than the containing core HTML.
    #if [ "false" != "$MOBILE" -a "${IMGB}" -ge "`wc -c < ${ARTBODY}`" ]; then
    #    #echo "WARNING: hero image(s) OVER-WEIGHT for MOBILE for ${ARTBODY} at $IMGB bytes against max $HEROIMGMAXBYTES: ${IMG}" 1>&2
    #    # Skip, but with no error.
    #    continue;
    #fi

    # Extra filtering by target width and aspect ratio if for a banner.
    # This width is a (near) minimum for a banner, rough bounds otherwise.
    IMGWH="`script/get_img_XxY $IMG`"
    # Skip this image if the width and height cannot be extracted.
    if [ "" = "$IMGWH" -o "1x1" = "$IMGWH" ]; then continue; fi
    #echo "INFO: $IMG $IMGWH $TARGETWIDTH" 1>&2
    if [ "chooseBannerImg" = "$OP" ]; then
        ISOK=`echo $IMGWH | awk -Fx '{
            if(($1 >= minfrac*TARGETWIDTH) && ($1/$2 >= MINHWAR)) { print "true"; }
            }' MINHWAR=$MINHWAR minfrac=$HBMINFRAC TARGETWIDTH=$TARGETWIDTH MOBILE=$MOBILE`
        # Skip this image if criteria are not met.
        if [ "" = "$ISOK" ]; then continue; fi
    # Skip this image if way too wide (wasting bandwidth)
    # or if way too narrow (so may look strange)
    # and not even vaguely square (not good for top-float or carousel header).
    # The bounds need not be entirely symmetric.
    else
        ISOK=`echo $IMGWH | awk -Fx '{
            if(($1 < TARGETWIDTH*1.25) && ($1 > TARGETWIDTH/2) && ($2 >= $1/2) && ($2 <= $1*2)) { print "true"; }
            }' TARGETWIDTH=$TARGETWIDTH`
        # Skip this image if criteria are not met.
        if [ "" = "$ISOK" ]; then continue; fi
    fi

    # Image seems acceptable, so remember it.
    SELHEROIMG="$IMG"

    # Keep looking if image is too big to use for given display.
    if [ $IMGB -gt $HEROIMGMAXBYTES ]; then
        continue;
    fi

    # Else stop searching for more candidates immediately.
    break;
    done

# Exit if no suitable candidate found.
if [ "" = "$SELHEROIMG" ]; then
    exit 0;
fi

# Return path of successfully-selected image, and exit without error.
echo $SELHEROIMG
exit 0;;


floatImg) #-----------------------------------------------
#     Return a responsive (usually float) informational image tag suitable for
#     the specified class and body width,
#     else empty output (and error on stderr) in case of error.
#
#     Image must usually be a static image under /img
#     and resized (pre-rendered) versions may be created as necessary
#     and all will load the image without redirect from the static
#     (primary) server.
#
#     Images may also be dynamic and under /out
#     in which case they are referenced as-is, with no resizing.
#     (Though support for srcset with "IMGx Xw, IMGy Yw" will be created.)
#
#     The image used may be the original or autogenerated if under /img.
#
#     If the src image is of the form X/Y-tn.Z and an image exists
#     of the form X/Y.Z then the src (thumbnail) image will be used
#     for display but the X/Y.Z image will be linked to.
#
#     The form of the tag may change entirely for (say) MOBILE==amp.
#
#     These images are informational rather than decorative heros,
#     and are likely relatively large and in the body of the page.
#     Such images will often not be in the initial viewport on mobile.
#
# If a width attribute was provided it is in TARGETWIDTH.
# Image class is ARGCLASS.
# Any alt text is in ARGALT (else text extracted from image filename is used).
#
# Note: nominally allowing respF to scale to twice body width for desktop,
# eg to spill outside text container for fullw.
#
# Potentially supportable image classes and max width (% of FWPX):
# respF         200  (not float, note: this expands to full-width as required)
# respW         200  (not float, note: natural width wider than resp)
# resp          100  (not float)
# resp90        90   (not float, for figures with wide margins etc)
# respsml       33   (not float, width as respfloatXsml)
# respfloatr    50
# respfloatrsml 33
# respfloatlsml 33
#
#
# raw           100  (not float, leaves image as-is, does not wrap in href)
# floatrsml     100  (laid out like respfloatrsml, v small but not responsive)
    Iclass=$ARGCLASS
    ImaxwidthPC=100; # Default to full-width.
    case $Iclass in
    resp) ;; # Full width...
    respF|respW) if [ "false" = "$MOBILE" ]; then ImaxwidthPC=200; fi;;
    resp90) ImaxwidthPC=90;;
    respfloatr) ImaxwidthPC=50;;
    respfloatrsml|respfloatlsml|respsml) ImaxwidthPC=33;;
    raw) ;;        # Don't rescale.
#    floatrsml) ;;  # Don't rescale. # DHD20190831: DISABLED
    *)  echo "ERROR: $0: $OP class=$Iclass not supported for $ARTBODY." 1>&2
        exit 1;;
    esac

# Compute target (maximum) width for (static) body image.
Itargetwidth="`expr \( $FWPX \* $ImaxwidthPC \) / 100`"
#echo "INFO: target width $HEROIMG = $Itargetwidth." 1>&2

HEROIMGW=""
HEROIMGH=""
HEROIMGWH="`script/get_img_XxY $HEROIMGASFILE`"
if [ "" != "$HEROIMGWH" -a "1x1" != "$HEROIMGWH" ]; then
    HEROIMGW="`echo $HEROIMGWH | awk -Fx '{print $1}'`"
    HEROIMGH="`echo $HEROIMGWH | awk -Fx '{print $2}'`"
fi

# If narrower explicit target width requested, then use that.
if [ "" != "$TARGETWIDTH" ]; then
    if [ "$TARGETWIDTH" -le 1 ]; then
        echo "ERROR: invalid TARGETWIDTH=$TARGETWIDTH for $HEROIMG." 1>&2
        exit 1
    fi
    if [ "$TARGETWIDTH" -lt "$Itargetwidth" ]; then
        Itargetwidth="$TARGETWIDTH"
    fi
fi

# Handle vector images specially (eg don't try to scale them).
IMGSUFFIX="`echo $HEROIMG | sed -n -e 's/\([.][a-z][a-z]*\)$/\1/p'`"
ISVECTOR=false
if [ ".svg" = "$IMGSUFFIX" ]; then ISVECTOR=true; fi

# If specified image already meets size limits then use as-is.
# That implies that sizes might not be as tight as when auto-generated,
# and that the 'L' Save-Data version does not get created either.
SELHEROIMG=""
# Extract declared source image size in bytes.
HEROIMGB="$(wc -c < ${HEROIMGASFILE})"
# Extract declared source image width in pixels.
if [ "$HEROIMGB" -le "$BODYIMGABSMAXBYTES" ]; then
    # Small enough (in bytes) to use as-is.
    # Try very hard to allow use of the original image, unfiddled with.
    SELHEROIMG=$HEROIMG
    # If rescaling might be an option (neither dynamic nor vector nor raw image)
    # then try it iff the source image is wider than required.
    if [ "out" != "$IMAGELOC" -a "true" != "$ISVECTOR" -a "raw" != "$Iclass" ]; then
        if [ "" != "$HEROIMGH" ]; then
            if [ $HEROIMGW -gt $Itargetwidth ]; then
                # Wider than wanted...
                # Attempt to generate image at specified width.
                SELHEROIMG="`$0 autogenImg $HEROIMG $ARTBODY $STATICCDNPREFIX $MOBILE $Itargetwidth`"
                # If that fails, use source image as-is.
                if [ "" = "$SELHEROIMG" ]; then SELHEROIMG=$HEROIMG; fi
            fi
        fi
    fi
elif [ "out" = "$IMAGELOC" ]; then
    # If this is a dynamic image then it is a warning but not fatal
    # if the image is too heavy (file size is too large).
    echo "WARNING: $0: $OP LARGE dynamic body image $HEROIMG used as-is for $ARTBODY." 1>&2
    # For now the client will have to scale it if necessary.
    # TODO: may be able to find smaller dynamic image automagically.
    SELHEROIMG=$HEROIMG
elif [ "raw" = "$Iclass" ]; then
    # If this is a raw image then it is a warning but not fatal
    # if the image is too heavy (file size is too large).
    echo "WARNING: $0: $OP LARGE raw body image $HEROIMG used as-is for $ARTBODY." 1>&2
    SELHEROIMG=$HEROIMG
fi

# Stop if there is no usable image.
if [ "" = "$SELHEROIMG" -a "out" != "$IMAGELOC" -a "raw" != "$Iclass" ]; then
    # Attempt to generate image at specified width.
    SELHEROIMG="`$0 autogenImg $HEROIMG $ARTBODY $STATICCDNPREFIX $MOBILE $Itargetwidth`"
    if [ "" = "$SELHEROIMG" ]; then
        echo "ERROR: $0: unable to create img insert for $HEROIMG ($HEROIMGB bytes, $BODYIMGABSMAXBYTES max) for $ARTBODY; class=$Iclass MOBILE=mobile." 1>&2
        exit 1
    fi
fi


# For gallery image small enough to use as-is, take copy locally to serve.
if [ "$SELHEROIMG" = "$HEROIMG" -a "gallery" = "$IMAGELOC" ]; then
    # COPY from source gallery image to autogen directory (if not already done).
    # TODO: break this out into subroutine / recursive call.
    # Should be idempotent and atomic (without needing locks).
    # Copy should be image lossless, but lossless size reduction is good.
    # Name of copied file similar to but distinct from autogen shrunk images.
    AGHBDIR=img/a/$CACHEDIRBODY
    AGHBSUFFIX="`echo $HEROIMGASFILE | sed -n -e 's/^.*\([.][a-z][a-z0-9]*\)$/\1/p'`"
    AGHBHASH="l`wc -c $HEROIMGASFILE | awk '{print$1}'`"
    #HEROIMGWH="`script/get_img_XxY $HEROIMGASFILE`" # Computed above.
    if [ "" = "$HEROIMGWH" -o "1x1" = "$HEROIMGWH" ]; then
        echo "ERROR: $0: failed to get img dimensions of $HEROIMG ($HEROIMGASFILE) for $ARTBODY; class=$Iclass." 1>&2
        exit 1
    fi
    AGHBGEOM="$HEROIMGWH"
    AGHBFILE=$AGHBDIR/`basename $HEROIMGASFILE $AGHBSUFFIX`.$AGHBHASH.$AGHBGEOM$AGHBSUFFIX
    #echo "INFO: using copy $AGHBFILE for $ARTBODY; class=$Iclass MOBILE=mobile." 1>&2
    # Copy if missing or too old.
    if [ ! -s $AGHBFILE -o ! $AGHBFILE -nt $REBUILDFLAG ]; then
        echo "INFO: losslessly copying $HEROIMG to $AGHBFILE for $ARTBODY; class=$Iclass MOBILE=mobile." 1>&2
        case $AGHBSUFFIX in
        .png)
            echo "INFO: $ZOPFLIPNG -m $HEROIMGASFILE $AGHBFILE.$$.tmp" 1>&2
            $ZOPFLIPNG -m $HEROIMGASFILE $AGHBFILE.$$.tmp
            ;;
        .jpg)
            cp $HEROIMGASFILE $AGHBFILE.$$.tmp
            if [ -x $LOSSLESSJPEGCOMP ]; then
                echo "INFO: $LOSSLESSJPEGCOMP $AGHBFILE.$$.tmp" 1>&2
                $LOSSLESSJPEGCOMP $AGHBFILE.$$.tmp
            fi
            ;;
        *)  # Default byte-by-byte copy.
            echo "INFO: default byte-by-byte copy" 1>&2
            cp $HEROIMGASFILE $AGHBFILE.$$.tmp;;
        esac
        # Set perms for readability and atomically move into place.
        chmod a+r "$AGHBFILE.$$.tmp"
        mv -f "$AGHBFILE.$$.tmp" "$AGHBFILE"
    fi
    # Use local copy.
    SELHEROIMG="$AGHBFILE"
fi

# Block any attempt to use gallery (cat page) URL directly for hero!
if [ "$SELHEROIMG" = "$HEROIMG" -a "gallery" = "$IMAGELOC" ]; then
    echo "ERROR: $0 ${OP}: unable to YET directly use from Gallery small $HEROIMG for $ARTBODY; class=$Iclass MOBILE=mobile." 1>&2
    exit 1
fi

ALT="$ARGALT"
if [ "" = "$ALT" ]; then
    # Auto-construct alt text from image name.
    ALT="`script/altTextFromFilename $HEROIMG`"
fi
ALTVALUE="\"$ALT\""
if [ "" != "`echo "$ALT" | egrep '^[A-Za-z0-9]{1,}$'`" ]; then
    ALTVALUE="$ALT"
fi
# If there is a non-empty ALT text then generate a title attribute too.
# Give this a leading space if non-empty to allow it to be injected efficently.
# Omit for non-desktop target as there is no hover and so no tooltip.
TITLESAT=""
if [ "" != "$ALT" -a "false" = "$MOBILE" ]; then
    TITLESAT=" title=$ALTVALUE"
fi

# Extract selected hero image width and height (or nothing in case of error).
# If the SELHERO is the same as the HERO use latter's dimensions from file,
# which saves some mucking about if not a local file.
SELHEROIMGW=""
SELHEROIMGH=""
if [ "$DUMMYIMGASFILE" = "$HEROIMGASFILE" -a "" != "$TARGETWIDTH" -a "" != "$TARGETHEIGHT" ]; then
    # Take supplied width/height as supplied for a dummied out/ image.
    SELHEROIMGWH="${TARGETWIDTH}x${TARGETHEIGHT}"
elif [ "$SELHEROIMG" = "$HEROIMG" ]; then
    SELHEROIMGWH="`script/get_img_XxY $HEROIMGASFILE`"
else
    SELHEROIMGWH="`script/get_img_XxY $SELHEROIMG`"
fi
if [ "" != "$SELHEROIMGWH" -a "1x1" != "$SELHEROIMGWH" ]; then
    SELHEROIMGW="`echo $SELHEROIMGWH | awk -Fx '{print $1}'`"
    SELHEROIMGH="`echo $SELHEROIMGWH | awk -Fx '{print $2}'`"
else
    echo "ERROR: $0 ${OP}: unable to extract XxY from $HEROIMG / $SELHEROIMG." 1>&2
    exit 1
fi

# Width and height attributes with leading space if available, else "".
ATTRWH=""
if [ "" != "${SELHEROIMGW}" -a "" != "${SELHEROIMGH}" ]; then
    ATTRWH=" width=${SELHEROIMGW} height=${SELHEROIMGH}"
fi


# Check explicit width/height, if any, supplied in the IMG tag.
# This sometimes may be used to override an automatically-derived size.
# In particular, where the explicit width is *less* than the auto value,
# the smaller one may be used (with an appropriate matching height)
# either in generating a smaller (body) image or at least in setting
# output img width/height to constrain the shown size in some cases.
if [ "" != "$TARGETWIDTH" ]; then
    #echo "INFO: $0 ${OP}: explicit width=$TARGETWIDTH supplied for $HEROIMG for $ARTBODY; class=$Iclass MOBILE=mobile." 1>&2
    if [ "$TARGETWIDTH" -lt "${SELHEROIMGW}" ]; then
        echo "ERROR: $0 ${OP}: explicit width=$TARGETWIDTH narrower than default ${SELHEROIMGW} for $HEROIMG for $ARTBODY; class=$Iclass MOBILE=mobile." 1>&2
        exit 1
    fi
fi
if [ "" != "$TARGETHEIGHT" ]; then
    #echo "INFO: $0 ${OP}: explicit height=$TARGETHEIGHT supplied for $HEROIMG for $ARTBODY; class=$Iclass MOBILE=mobile." 1>&2
    if [ "$TARGETHEIGHT" -lt "${SELHEROIMGH}" ]; then
        echo "ERROR: $0 ${OP}: explicit height=$TARGETHEIGHT shorter than default ${SELHEROIMGH} for $HEROIMG for $ARTBODY; class=$Iclass MOBILE=mobile." 1>&2
        exit 1
    fi
fi


# Optimisation for faster (lazy) layout by browser by fixing small image sizes.
# Check if a nominally-responsive small image class can be fixed instead.
# Requires some intimate knowledge about available base CSS classes.
# NOTE: cannot actually do without communicating to CSS optimiser
#     substitution of class...
#if [ "$SELHEROIMGW" -le "$WMAXFIXEDPX" -a "" != "$SELHEROIMGH" ]; then
#    newIclass=""
#    case $Iclass in
#        respfloatr*) newIclass=floatrsml;;
#        #resp) newIclass=raw;;
#    esac
#    if [ "" != "$newIclass" ]; then
#        # Could upgrade to WARNING if browsers actually pay attention.
#        echo "INFO: $0 ${OP}: consider converting image class $Iclass to $newIclass for layout speed (${SELHEROIMGW}x${SELHEROIMGH} vs ${WMAXFIXEDPX}w px limit) $HEROIMG for $ARTBODY; class=$Iclass MOBILE=mobile." 1>&2
#        #Iclass=$newIclass
#    fi
#fi

# For pseudo-responsive (small) images, check that they are small enough.
# Small enough to fit easily on even narrowest-expected screen.
# Non-responsive to allow browser to to use width/height ATTRWH for layout
# without needing to fetch image metadata.
if [ "floatrsml" = "$Iclass" ]; then
    if [ "$SELHEROIMGW" -gt "$WMAXFIXEDPX" ]; then
        echo "ERROR: $0 ${OP}: image too wide to be $Iclass ($SELHEROIMGW vs $WMAXFIXEDPX px) $HEROIMG for $ARTBODY; class=$Iclass MOBILE=mobile." 1>&2
        exit 1
    fi
fi


# Try to inline the image, to reduce page weight and/or load time.
DATAURL="$($0 "inline-$1" "$SELHEROIMG" "$ARTBODY" "$STATICCDNPREFIX" "$MOBILE" "" "" "" "" "$ARGKVP")"
# Use the dataurl, if created, as the img src.
if [ "" != "$DATAURL" ]; then
$PERFTIMESTAMPS && [ "$DEBUGPGBUILDPERF" -gt 1 ] && echo "INFO: inlined body img $SELHEROIMG ($(echo "$DATAURL" | wc -c)B) $f" 1>&2
    SELHEROIMG='"'"$DATAURL"'"';
    INLINED="true"
    break
fi


# Try to locate/generate smaller image versions (including sub-lite viewport).
# (Actually sharing/reusing the mobile image version may improve cacheing.)
# In any case try to locate/generate tweaked minimal-size srcset member.
# Don't generate smaller/larger versions for dynamic or vector or raw images.
# Don't generate smaller/larger versions if selected image is being inlined.
# If available, create srcset/sizes to allow responsive resolution switching.
# Only offer one (likely lowish-fi) image, as src, for offline.
# May be able to find smaller dynamic image automagically.
# SRCSETSIZES is empty else contains a leading space then srcset/sizes.
# This is ready to insert directly into the <img> tag.
SRCSETSIZES=""

if [ "true" != "$INLINED" -a "out" != "$IMAGELOC" -a "true" != "$ISVECTOR" -a "raw" != "$Iclass" -a "offline" != "$MOBILE" ]; then

$PERFTIMESTAMPS && echo "INFO: TS: GHII: START: ${OP}: SRCSET" `$PERFTIMESTAMPCMD` 1>&2
#$PERFTIMESTAMPS && [ "$DEBUGPGBUILDPERF" -gt 2 ] && echo "INFO: $0 ${OP}: START: SRCSET/sizes for $HEROIMG; class=$Iclass MOBILE=mobile." 1>&2

    # Stylised srcset and sizes attributes, or empty if none.
    # Cacheable.
    # CDN prefix is replaced with @@ to substitute when used from cache.
    STSRCSETSIZES=""

    # Flags area for cached img components, and done/failed markers.
    FLAGSSDIR=$CACHEDIRAG/$CACHEDIRBODY/.flags-floatImg
    mkdir -p $FLAGSSDIR || exit 1
    # Note that the header generated depends on the hero selected.
    FLAGSPREF="`basename $HEROIMG`"
    # If the flag raw base name would be too long, replace with a crypto hash.
    # This need only be long and good enough to avoid collisions.
    # MD5 is fine, SHA256 at double the length might be a reasonable upgrade.
    # Similar algorithm as used to shorten names of resized images,
    # but with a higher threshold to preserve more hero names as-is.
    # Trimming long names a bit will save some directory space.
    FLAGSPREFMAXBASENAME=150
    if [ "`echo $FLAGSPREF | wc -c`" -gt $FLAGSPREFMAXBASENAME ]; then
        # Try to preserve first word from the original name if not too long.
        NPREFIX=`echo $FLAGSPREF|awk -F- '{if(length($1)<15){printf("%s-",$1)}}'`
        NHASH=`echo $FLAGSPREF | md5sum | awk '{printf("%s",$1)}'`
        FLAGSPREF="$NPREFIX$NHASH"
    fi
    FLAGSPREF="$MOBILE=$Iclass=$FLAGSPREF"
    # Result HTML fragment and success marker.
    AGHBRESULT=$FLAGSSDIR/$FLAGSPREF.txt
    # If the result file exists use it!
    # It must be newer than the force-rebuild flag.
    if [ -f $AGHBRESULT -a $AGHBRESULT -nt $REBUILDFLAG ]; then
        STSRCSETSIZES="`cat $AGHBRESULT`"
    else
        # COMPUTE STSRCSETSIZES

        # Stylised attribute value srcset only.
        STSRCSET=""

        # Minimal image size.  Derived from minimal supported mobile viewport.
        MINHEROIMG=""
        MINHEROIMGW=""
        # Small image size.  Derived from normal mobile viewport.
        SMLHEROIMG=""
        SMLHEROIMGW=""
        # Normal image size (for desktop, only where ImaxwidthPC > 100).
        NMLHEROIMG=""
        NMLHEROIMGW=""

        SELIMAGEBYTES="`wc -c < $SELHEROIMG | awk '{print $1}'`"

        # NML: up to desktop full container width.
        # Only applicable where non-mobile and ImaxwidthPC > 100.
        # At desktop bits-per-pixel.
        if [ "false" = "$MOBILE" -a "$ImaxwidthPC" -gt "100" ]; then
            # Cap ImaxwidthPC to 100% for fallback where max-width <= FWPX.
            NMLtargetwidth="$DBODYPX"
            if [ "$NMLtargetwidth" -lt "$SELHEROIMGW" ]; then
                NMLHEROIMG="`$0 autogenImg $HEROIMG $ARTBODY $STATICCDNPREFIX false $NMLtargetwidth`"
                # Drop if this results in a repeat/larger image for some reason,
                # else create additional sizes and srcset attributes.
                # Use leading space if non-empty to allow to be injected efficently.
                if [ "" = "$NMLHEROIMG" -o "$SELHEROIMG" = "$NMLHEROIMG" ]; then
                    NMLHEROIMG=""
                elif [ "`wc -c < $NMLHEROIMG`" -ge "$SELIMAGEBYTES" ]; then
                    NMLHEROIMG=""
                else
                    # Assume target size is hit (or is at least no larger).
                    NMLHEROIMGW="$NMLtargetwidth"
        #echo "INFO: $0 $OP: NMLHEROIMG=$NMLHEROIMG NMLHEROIMGW=$NMLHEROIMGW $HEROIMG for $ARTBODY; MOBILE=$MOBILE." 1>&2
                fi
            fi
        fi

        # SML: up to mobile container full width, so less than desktop full-width.
        # Also, at lower bits-per-pixel than desktop.
        # For desktop, next smaller image is mobile size.
        if [ "false" = "$MOBILE" ]; then
            SMLtargetwidth="`expr \( $MBODYPX \* $ImaxwidthPC \) / 100`"
            if [ "$ImaxwidthPC" -gt "100" ]; then
                # Cap ImaxwidthPC to 100% in case viewed on mobile.
                SMLtargetwidth="$MBODYPX"
            fi
            if [ "$SMLtargetwidth" -lt "$SELHEROIMGW" ]; then
                SMLHEROIMG="`$0 autogenImg $HEROIMG $ARTBODY $STATICCDNPREFIX true $SMLtargetwidth`"
                # Drop if this results in same/larger image for some reason,
                # else create additional sizes and srcset attributes.
                # Use leading space if non-empty to allow to be injected efficently.
                if [ "" = "$SMLHEROIMG" -o "$SELHEROIMG" = "$SMLHEROIMG" ]; then
                    SMLHEROIMG=""
                elif [ "`wc -c < $SMLHEROIMG`" -ge "$SELIMAGEBYTES" ]; then
                    SMLHEROIMG=""
                else
                    #SMLHEROIMGW="`script/get_img_XxY $SMLHEROIMG | awk -Fx '{print $1}'`"
                    # Assume target size is hit (or is at least no larger).
                    SMLHEROIMGW="$SMLtargetwidth"
    #echo "INFO: $0 $OP: SMLHEROIMG=$SMLHEROIMG SMLHEROIMGW=$SMLHEROIMGW $HEROIMG for $ARTBODY; MOBILE=$MOBILE." 1>&2
                fi
            fi
        fi

        # MIN: mobile minimum width, so less than desktop and mobile full-width.
        # Also, at lower bits-per-pixel than desktop.
        # Minimal (autogen) image width targets smallest supported viewport.
        # For any variant, if MRFRSPX << SELHEROIMGW and << any SMLHEROIMGW,
        # then make minimal version.
        # First check that minimal version is narrower.
        # TODO: check that generated file signficantly smaller than SEL/SML also.
        MINtargetwidth="`expr \( $MINBODYPX \* $ImaxwidthPC \) / 100`"
        if [ "$ImaxwidthPC" -gt "100" ]; then
            # Cap ImaxwidthPC to 100% in case viewed on mobile.
            MINtargetwidth="$MINBODYPX"
        fi
        if [ "$MINtargetwidth" -lt "$SELHEROIMGW" -a "$MINtargetwidth" -lt "${SMLHEROIMGW:-${SELHEROIMGW}}" ]; then
            MINHEROIMG="`$0 autogenImg $HEROIMG $ARTBODY $STATICCDNPREFIX true $MINtargetwidth`"
            # Drop if this results in a repeat/larger image for some reason.
            if [ "" = "$MINHEROIMG" -o "$SELHEROIMG" = "$MINHEROIMG" -o "$SMLHEROIMG" = "$MINHEROIMG" ]; then
                MINHEROIMG=""
            elif [ "`wc -c < $MINHEROIMG`" -ge "$SELIMAGEBYTES" ]; then
                MINHEROIMG=""
            elif [ "" != "$SMLHEROIMG" ] && [ "`wc -c < $MINHEROIMG`" -ge "`wc -c < $SMLHEROIMG`" ]; then
                # Ensure no larger than SML image bytes, if extant.
                MINHEROIMG=""
            else
                #MINHEROIMGW="`script/get_img_XxY $MINHEROIMG | awk -Fx '{print $1}'`"
                # Assume target size is hit (or is at least no larger).
                MINHEROIMGW="$MINtargetwidth"
        #echo "INFO: $0 $OP: MINHEROIMG=$MINHEROIMG MINHEROIMGW=$MINHEROIMGW $HEROIMG for $ARTBODY; MOBILE=$MOBILE." 1>&2
            fi
        fi

        # Construct a srcset if at least one of MIN/SML/NML HEROIMG available.
        if [ "" != "$NMLHEROIMG" -o "" != "$SMLHEROIMG" -o "" != "$MINHEROIMG" ]; then
            STSRCSET="@@$SELHEROIMG ${SELHEROIMGW}w"
            if [ "" != "$NMLHEROIMG" -a "$NMLHEROIMGW" != "$SELHEROIMGW" ]; then
                STSRCSET="$STSRCSET, @@$NMLHEROIMG ${NMLHEROIMGW}w"
            fi
            if [ "" != "$SMLHEROIMG" ]; then
                STSRCSET="$STSRCSET, @@$SMLHEROIMG ${SMLHEROIMGW}w"
            fi
            if [ "" != "$MINHEROIMG" ]; then
                STSRCSET="$STSRCSET, @@$MINHEROIMG ${MINHEROIMGW}w"
            fi
            # To support pixel density > 1 without penalising desktops
            # while viewport >> ${FWPX}px, rather than sizes="${MAXPC}vw",
            # better is:
            #   "(min-width:${FWPX}px) ${(${MAXPC) * ${FWPX})/100}px, ${MAXPC}vw"
            #
            # The slot size doesn't (usually) exceed container width, nor 100vw,
            # even for container-busting full-width images.
            #
            # Reduce it still further when space for image, or max available image,
            # much narrower even than nominal target maximum width.
            #
            CALCMAXW=`expr \( ${ImaxwidthPC} \* ${FWPX} \) / 100`

    # Prefix srcset with xx ..., 2x, 1.5x desktop bpp versions if light enough.
    # These should be driven by *target* natural size.
    # ONLY IF:
    #   * targeting desktop
    #   * SEL image is significantly (10%?) below the byte weight limit.
    # SKIP IF:
    #   * putative generated width less than current SEL image.
    # STOP AS SOON AS (starting at lowest density version):
    #   * larger image cannot be generated
    #   * larger image not significantly below the byte weight limit.
    #   * putative generated width greater than source (can do equal)
    # TODO: ensure that higher-density items have high minimum quality.
            if [ "false" = "$MOBILE" ]; then
                NEARBODYIMGMAXBYTES="`expr \( $BODYIMGMAXBYTES \* 19 \) / 20`"
                #echo "INFO: $0: NEARBODYIMGMAXBYTES=$NEARBODYIMGMAXBYTES for higher-densisty images for for $HEROIMG for $ARTBODY." 1>&2
                if [ "$NEARBODYIMGMAXBYTES" -le "$SELIMAGEBYTES" ]; then
                    #echo "INFO: $0: not enough headroom: default SEL ($SELIMAGEBYTES) already at or over NEARBODYIMGMAXBYTES=$NEARBODYIMGMAXBYTES (limit $BODYIMGMAXBYTES) for higher-density images for $HEROIMG for $ARTBODY." 1>&2
                    break;
                fi
                CURRENTLARGEST=-1
                for density in $DENSITIES;
                    do
                    if [ "$NEARBODYIMGMAXBYTES" -le "$CURRENTLARGEST" ]; then
                        #echo "INFO: $0: not enough headroom: current largest ($CURRENTLARGEST) already at or over NEARBODYIMGMAXBYTES=$NEARBODYIMGMAXBYTES (limit $BODYIMGMAXBYTES) for higher-density images for $HEROIMG for $ARTBODY." 1>&2
                        break;
                    fi
                    EXPANDEDW="`echo $CALCMAXW $density | awk '{print int($1 * $2)}'`"
                    #echo "INFO: $0: putative higher-density ${EXPANDEDW}w for $HEROIMG for $ARTBODY." 1>&2
                    # Do not generate image smaller than current SEL.
                    if [ "$EXPANDEDW" -le "$SELHEROIMGW" ]; then
                        #echo "INFO: $0: skipping putative higher-density ${EXPANDEDW}w narrower than SEL ${SELHEROIMGW}w for $HEROIMG for $ARTBODY." 1>&2
                        continue;
                    fi
                    # Prepare to generate the expanded higher-density image.
                    ORGHEROIMG=""
                    ORGHEROIMGBYTES="-1"
                    # Do not generate image larger than original source dimensions.
                    if [ "$EXPANDEDW" -ge "$HEROIMGW" ]; then
                        #echo "INFO: $0: putative higher-density ${EXPANDEDW}w wider than original ${HEROIMGW}w for $HEROIMG for $ARTBODY." 1>&2
                        # Try limiting width to exactly that of the original.
                        # But if not larger than the natural (SEL) image size, stop.
                        if [ "$HEROIMGW" -le "$SELHEROIMGW" ]; then break; fi
                        EXPANDEDW=$HEROIMGW
                        if [ "img" = "$IMAGELOC" ]; then
                            ORGHEROIMGBYTES="`wc -c < $HEROIMGASFILE`"
                            if [ "$ORGHEROIMGBYTES" -le "$BODYIMGMAXBYTES" ]; then
                                #echo "INFO: $0: can use original (at ${HEROIMGW}w) in srcset for $HEROIMG for $ARTBODY." 1>&2
                                ORGHEROIMG=$HEROIMG
                            fi
                        fi
                    fi
                    # Generate an expanded higher-density image.
                    # This will be at target bit-per-pixel.
                    # This will have Save-Data (L) versions etc generated too.
                    EXPHEROIMG="`$0 autogenImg $HEROIMG $ARTBODY $STATICCDNPREFIX false $EXPANDEDW`"
                    if [ "" = "$EXPHEROIMG" -a "" != "$ORGHEROIMG" ]; then
                        # Use original as limited and possibly-expensive fallback.
                        #echo "INFO: $0: using original (at ${HEROIMGW}w) in srcset for $HEROIMG for $ARTBODY." 1>&2
                        EXPHEROIMG=$ORGHEROIMG
                    elif [ "" = "$EXPHEROIMG" ]; then
                        #echo "INFO: $0: failed to generate putative higher-density ${EXPANDEDW}w for $HEROIMG for $ARTBODY." 1>&2
                        # Probably will not be able to make even larger one so stop.
                        break;
                    fi
                    EXPIMAGEBYTES="`wc -c < $EXPHEROIMG | awk '{print $1}'`"
                    if [ "$EXPIMAGEBYTES" -lt "$CURRENTLARGEST" ]; then
                        #echo "INFO: $0: skipping putative higher-density ${EXPANDEDW}w smaller than previous largest for $HEROIMG for $ARTBODY." 1>&2
                        continue;
                    fi
                    if [ "$EXPIMAGEBYTES" -gt "$CURRENTLARGEST" ]; then
                        CURRENTLARGEST="$EXPIMAGEBYTES"
                    fi
                    # Prepend expanded image to srcset.
                    STSRCSET="@@$EXPHEROIMG ${EXPANDEDW}w, $STSRCSET"
                    if [ "$EXPANDEDW" -ge "$HEROIMGW" ]; then
                        # Stop when no smaller than original.
                        break;
                    fi
                    done
            fi

            STSRCSETSIZES="srcset=\"$STSRCSET\" sizes=\"(min-width:${FWPX}px) ${CALCMAXW}px, ${ImaxwidthPC}vw\""
            if [ "${CALCMAXW}" -gt "${SELHEROIMGW}" ]; then
                # Main (x1) image natural size narrower than slot available.
                # Correct sizes to reflect that.
                ADJSMW=`expr \( ${FWPX} \* ${SELHEROIMGW} \) / ${CALCMAXW}`
                STSRCSETSIZES="srcset=\"$STSRCSET\" sizes=\"(min-width:${ADJSMW}px) ${SELHEROIMGW}px, ${ImaxwidthPC}vw\""
            fi
            # For images that can escape the container, call for full 100vw.
            if [ "${ImaxwidthPC}" -gt 100 ]; then
                # Even when actually 100vw as here, 'sizes' may not be omitted.
                STSRCSETSIZES="srcset=\"$STSRCSET\" sizes=100vw"
            fi
        fi

        # Cache the computed srcset/sizes atomically.
        printf "%s" "$STSRCSETSIZES" > $AGHBRESULT.$$.tmp
        mv -f $AGHBRESULT.$$.tmp $AGHBRESULT
    fi

#echo "INFO: $0 ${OP}: END: SRCSET/sizes for $HEROIMG; class=$Iclass MOBILE=mobile: $STSRCSETSIZES" 1>&2

    if [ "" != "$STSRCSETSIZES" ]; then
        SRCSETCDNPREFIX="$CDNPREFIX"
        if [ "false" = "$MOBILE" ]; then SRCSETCDNPREFIX=""; fi
        # Note: leading space inserted here...
        SRCSETSIZES=`echo " $STSRCSETSIZES"|sed -e 's|@@|'$SRCSETCDNPREFIX'|g'`
    fi
$PERFTIMESTAMPS && echo "INFO: TS: GHII: END: ${OP}: SRCSET" `$PERFTIMESTAMPCMD` 1>&2
$PERFTIMESTAMPS && [ "$DEBUGPGBUILDPERF" -gt 1 ] && echo "INFO: $0 ${OP}: END: SRCSET/sizes for $HEROIMG; class=$Iclass MOBILE=mobile: $SRCSETSIZES" 1>&2

fi

 
# Support img pre-render cssgip-like style="background:#xxxxxx" here.
# (Almost certainly no point if the image is inlined, if that is possible.)
# Don't bother for AMP, which has its own placeholder scheme.
# Don't bother for 'out' images, which may change after HTML has been generated.
# Don't bother for offline images, which may change after HTML has been generated.
# STYLECSSBG is either empty or a style attr/value with a leading space.
# The STYLECSSBG colour value is effectively a hash over the image,
# so should probably go at the end of the img tag.
STYLECSSBG=""
if [ "amp" != "$MOBILE" -a "offline" != "$MOBILE" -a "out" != "$IMAGELOC" -a "true" != "$INLINED" ]; then
    BGCOLHEX="`$0 repColOpt $SELHEROIMG $ARTBODY $STATICCDNPREFIX $MOBILE`"
    #echo "INFO: $0: BGCOLHEX=$BGCOLHEX for $HEROIMG for $ARTBODY." 1>&2
    if [ "" != "$BGCOLHEX" ]; then
        STYLECSSBG=" style=background:$BGCOLHEX"
    fi
fi

# Create wrapping link to original if an alternate is being provided,
# or if original image (much) wider than target width.
# If original comes from the Gallery CMS, link to its catalogue page.
# Wrapping *may* include ImageObject with link to highest-res source image.
# DO NOT WRAP in a link/ImageObject if 'raw' class type.
#
# Alternatively, if this image ends with -tn.XXX,
# wrap with a link to the non-tn version of the image if extant.
NONTNIMG="$HEROIMG"
PUTATIVENONTNIMG="`echo $HEROIMG | sed -e 's/-tn\([.][a-z][a-z]*\)$/\1/'`"
if [ "$PUTATIVENONTNIMG" != "$HEROIMG" -a -s "$PUTATIVENONTNIMG" ]; then
    NONTNIMG=$PUTATIVENONTNIMG
    #echo "INFO: $0: discovered non-tn $NONTNIMG for $HEROIMG for $ARTBODY; MOBILE=$MOBILE." 1>&2
fi
WRAPSTART=""
WRAPEND=""
if [ "raw" = "$Iclass" ]; then
    : Do NOT wrap raw image in a link or ImageObject.
elif [ "gallery" = "$IMAGELOC" ]; then
    WRAPURL="$IMAGEGALCATPAGE"
    #LINKURL="$IMAGEGALEXHIBIT"
    #WRAPSTART="<span itemprop=image itemscope itemtype=http://schema.org/ImageObject><link href=$LINKURL itemprop=\"contentUrl url\"><a href=$WRAPURL>"
    #WRAPEND="</a><meta itemprop=caption content=$ALTVALUE></span>"
    WRAPSTART="<a href=$WRAPURL>"
    WRAPEND="</a>"
elif [ "$NONTNIMG" != "$SELHEROIMG" -o "" != "SRCSETSIZES" -o "$SELHEROIMGW" -gt "$Itargetwidth" ]; then
    WRAPURL="$CDNWRAPPREFIX$NONTNIMG"
    # For desktop no prefix is needed.
    if [ "false" = "$MOBILE" ]; then
        WRAPURL="$NONTNIMG";
    fi
    #WRAPSTART="<span itemprop=image itemscope itemtype=http://schema.org/ImageObject><a href=$WRAPURL itemprop=\"contentUrl url\">"
    #WRAPEND="</a><meta itemprop=caption content=$ALTVALUE></span>"
    WRAPSTART="<a href=$WRAPURL>"
    WRAPEND="</a>"
fi

# Set flag to L if image is set to lazy load.
LAZYFLAG=""

# Generate (body) image tag with all required attributes.
# Put most important attributes first.
# Try to order attributes to maximise compressibility.
# The src attr comes after esp width/height to help data url page layout.
# Set default image (where no srcset or not recognised by browser).
IMGURL="$CDNPREFIX$SELHEROIMG"
if [ "true" = "$INLINED" ]; then IMGURL="$SELHEROIMG"; fi
# Generated normal HTML5 or AMP tag.
if [ "amp" != "$MOBILE" ]; then
    # HTML5 simple (often floating) image.
    # These images are content, but should probably not block text rendering.
    # If (non-first) dbtf=1 kvp present (deep below the fold), or is mobile,
    # or some body images have already been emitted,
    # or the image is heavy (in bytes),
    # then image can be lazily loaded and async decoded to improve UX.
    # As of end 2019 in Chrome, decoding=async is overridden by loading=lazy.
    # If the image is inlined then it is pointless to add loading=XXX;
    # decoding=async may cause content movement from width/height delay.
    LOADING=" decoding=async"
    if [ "true" = "$INLINED" ]; then
        # Don't bother with width and height if image is being inlined.
        ATTRWH=""
        # Turn off decoding=async for inlined to extract width and height ASAP.
        LOADING=""
    elif [ "false" != "$MOBILE" ]; then
        LOADING=" loading=lazy"
        LAZYFLAG="L"
    elif [ "" != "`echo $ARGKVP | egrep '(^n=([3-9]|([1-9][^,])))|(,dbtf=1)'`" ]; then
        LOADING=" loading=lazy"
        LAZYFLAG="L"
    elif [ "out" = "$IMAGELOC" -o ! -f "$SELHEROIMG" ]; then
        # Non-static image, size and cacheability unknown...
        LOADING=" loading=lazy"
        LAZYFLAG="L"
    elif [ "`wc -c < $SELHEROIMG`" -gt "`expr $BODYIMGMAXBYTES / 2`" ]; then
        #echo "INFO: $0: forcing loading=lazy for large $SELHEROIMG for $HEROIMG for $ARTBODY." 1>&2
        LOADING=" loading=lazy"
        LAZYFLAG="L"
    fi
    if [ "false" = "$MOBILE" ]; then
        IMGURL="$SELHEROIMG"; # Root relative, no prefix.
        if [ "true" != "$INLINED" ]; then
            # Ensure that source image is (made) globally readable.
            # This applies to sources images in the img/ tree too,
            # so is slightly unusual, though should almost always be a no-op.
            if [ -s $SELHEROIMG ]; then chmod a+r "$SELHEROIMG"; fi
        fi
    fi
    # Set non-empty if a local alternate found.
    IMGURLalternate=""
    if [ "offline" = "$MOBILE" -a "true" != "$INLINED" ]; then
        IMGURL=$SELHEROIMG # No prefix/CDN for offline.
        IMGURLsmallest=$IMGURL
        IMGURLsmallestbytes="`wc -c < $IMGURLsmallest`"
        # If widely-supported (smaller) alternative available, use it instead.
        for f in $ALTERNATESALLPOP;
            #do if [ -s $IMGURL.$f ]; then IMGURL=$IMGURL.$f; break; fi; done
            do
                if [ -s $IMGURL.$f ]; then
                    IMGURLfbytes="`wc -c < $IMGURL.$f`"
#echo "INFO: found $IMGURL.$f of $IMGURLfbytes bytes..." 1>&2
                    if [ $IMGURLfbytes -lt $IMGURLsmallestbytes ]; then
                        IMGURLsmallestbytes=$IMGURLfbytes
                        IMGURLsmallest=$IMGURL.$f
                    fi
                fi;
            done
        if [ "$IMGURLsmallest" != "$IMGURL" ]; then
            #echo "INFO: substituting $IMGURLsmallest for $IMGURL" 1>&2
            IMGURL=$IMGURLsmallest
            IMGURLalternate=$IMGURLsmallest
        fi
        # Used to capture offline dependencies.
        echo "INFO: IMGSRC: $IMGURL `if [ -s $IMGURL ]; then wc -c < $IMGURL; fi`" 1>&2
    fi
    # In case of long inlined src, put it after all other attributes.
    CLASSATTR="";if [ "raw" != "$Iclass" ];then CLASSATTR=" class=$Iclass";fi
    echo "$WRAPSTART"'<img'"$CLASSATTR$ATTRWH"' alt='$ALTVALUE$TITLESAT$STYLECSSBG$LOADING$SRCSETSIZES' src='"$IMGURL"'>'"$WRAPEND"
else
    # Well, AMP is a little different!
    # Always lazy-loaded for AMP.
    LAZYFLAG="L"
    # In case of long inlined src, put it after all other attributes.
    echo "$WRAPSTART"'<amp-img layout=intrinsic class='"$Iclass$ATTRWH"' alt='$ALTVALUE$TITLESAT$SRCSETSIZES' src='"$IMGURL"'></amp-img>'"$WRAPEND"
fi

## Help computation of total page weight without lazy loading or user action.
#if [ "true" != "$INLINED" -a -f "$SELHEROIMG" ]; then
#    echo "INFO: SRCBYTES: IMG: `wc -c < $SELHEROIMG`" $LAZYFLAG 1>&2
#fi

exit 0;;


repCol|repColOpt) #-----------------------------------------------
#     Extract a single representative hex colour for the entire image.
#     This may build a minimal-sized image to extract that colour from
#     and/or cache the computed colour.
#
#     repColOpt is optimised eg omitting common web-page background colours
#     (white and black, eg light mode and dark mode)
#     and if image is not opaque.
#
#     Format is # followed by 6 or 3 digits.  (Latter 216 "Web-safe" palette.)
#
#     Intended to be reasonably fast.
#
# Returns an empty result if no colour can be extracted
# (and on dummy/placeholder image).
if [ "$DUMMYIMGASFILE" = "$HEROIMGASFILE" ]; then exit 0; fi
#
# Call on the image to be shown, or a reduced-size version of it.
# This may be slow for large images.
#
# Should probably cache the result as is somewhat expensive to compute.
# Or it could be computed and cached along with the image itself,
# and used if available and pre-computed.
#
# Compute and extract hex 'average' #xxx colour using ImageMagick (fast).
# Encode hex colour in lowercase to help compression.
#
# repColOpt: Don't compute background colour for non-opaque image.
# Otherwise overall appearance may be changed if this background shows through,
# rather than what was originally behind the image.
ColCACHE=
if [ "repColOpt" = "$OP" ]; then
    ColCACHE=$HEROIMGASFILE.rgbcol.txt
    # Check for cached result, for auto-gen images.
    case $HEROIMGASFILE in
    ${CACHEDIRAG}/*)
        if [ -f $ColCACHE -a $ColCACHE -nt $HEROIMGASFILE ]; then
            # Cached value exists and is not stale; return it.
            # (An empty file/value is valid.)
            cat $ColCACHE
            exit 0
        fi
        # Ensure no stale value retained.
        rm -f $ColCACHE;;
    *)
        #echo "INFO: $0: cannot cache repColOpt for $HEROIMG for $ARTBODY." 1>&2
        # Simply refuse to expensively (re)make the bgcol if uncacheable.
        exit 0;;
    esac

    # Avoid even testing (common) image types that cannot have transparency.
    case $HEROIMG in
    *.jpg) ;; # JPEGs are always opaque, and there are lots of them.
    *)  # Image types that may be fully or partially transparent.
        OPAQUE="`$IDENTIFY -format '%[opaque]' $HEROIMGASFILE`"
        if [ "False" = "$OPAQUE" -o "false" = "$OPAQUE" ]; then
            # Return (and cache) an empty value.
            touch $ColCACHE
            exit 0;
        fi
        ;;
    esac
fi
# Compute image 'average' colour.
#
# Note: could take dominant color (most common of ~5) instead with:
#     +dither -colors 5 -define histogram:unique-colors=true -format "%c" histogram:info:
# eg see:
#     https://legacy.imagemagick.org/discourse-server/viewtopic.php?t=28963
BGCOLHEX="`$CONVERT $HEROIMGASFILE +dither -colors 1 -remap netscape: -unique-colors txt: | \
    sed -n -e 's/^.* #\([0-9A-Z]\)[0-9A-Z]\([0-9A-Z]\)[0-9A-Z]\([0-9A-Z]\)[0-9A-Z] .*$/#\1\2\3/p' | \
    tr '[:upper:]' '[:lower:]'`"
echo "INFO: $0: computed BGCOLHEX=$BGCOLHEX for $HEROIMG for $ARTBODY." 1>&2
# repColOpt: Don't return background colour same as normal page colours
# Allow for light and dark mode (white/black).
if [ "repColOpt" = "$OP" -a \( "#fff" = "$BGCOLHEX" -o "#000" = "$BGCOLHEX" \) ]; then
    # Return (and cache) an empty value.
    touch "$ColCACHE"
    exit 0
    fi
# Cache value carefully.
if [ "" != "$ColCACHE.tmp" ]; then
    echo "$BGCOLHEX" > "$ColCACHE.tmp" && mv -f "$ColCACHE.tmp" "$ColCACHE"
    # Make it public...
    chmod a+r "$ColCACHE"
fi
# Return value.
echo "$BGCOLHEX"
exit 0;;

autogenHiresHeroImgs) #-----------------------------------------------
#     Generate ImageObject HTML5 microdata for
#     search-engine-friendly 16:9, 4:3, 1:1 cropped (and space-efficient)
#     versions of the supplied (JPG/PNG raster) hero image.
#     (May add to page as thumbnailUrl rather then image itemprop if small.)
#
#     The centre of the image is always preserved and cropping is symmetric.
#
#     This metadata would generally be added to the end of a page
#     (so not impacting the Critical Rendering Path)
#     to allow an SE to select a suitable version for a SERPS result.
#
#     At least contentUrl and url will be provided for ImageObject,
#     with caption also from alt text where possible.
#
#     Accepts as TARGETWIDTH the minimum recommended image width.
#     images narrower than this will not be generated as image itemprops,
#     though may be base thumbnailUrls down to DRFRSPX.

# This assumes that any letterbox crop of the source image will be useful.
# This assumes that the very centre of the source image is interesting.
# This may refuse to crop images already too small in some way.
# This may refuse to crop if that would reduce the width to hit target aspect.
# This may refuse to use images 'too small' in some way.
# This may refuse to create very large images even though not page-embedded.

# This may omit generating an image version for an aspect ratio
# that the source image already meets.

# The recommended minimum width.
RECLIMGW=$TARGETWIDTH

# Result is empty if, for example, source image is not a raster.
case $HEROIMG in
*.jpg|*.png) ;; # Raster images good for ImageObject SERP snippets.
*) exit 0;; # Other types may not be good for snippets.
esac

# Get source image dimensions.
HEROIMGWH="`script/get_img_XxY $HEROIMGASFILE`"
if [ "" = "$HEROIMGWH" -o "1x1" = "$HEROIMGWH" ]; then
    echo "ERROR: $0: failed to get img dimensions of $HEROIMG ($HEROIMGASFILE) for $ARTBODY; class=$Iclass." 1>&2
    exit 1
fi

# Aspect ratios (x/y as decimal) for 1:1, 4:3, 16:9.
# All aspect ratios expected to be >= 1 (W>=H).
ASPECTRATIOS="1.0 1.3333 1.7777"

# Use hero file naming pattern, but 'high-res'.
AGHBSUFFIX="`echo $HEROIMGASFILE | sed -n -e 's/^.*\([.][a-z][a-z0-9]*\)$/\1/p'`"
AGHBHASH="l`wc -c $HEROIMGASFILE | awk '{print$1}'`"

# Extract "alt" text from file name for caption.
ALT="`script/altTextFromFilename $HEROIMG`"

for ar in $ASPECTRATIOS;
    do
$PERFTIMESTAMPS && echo "INFO: TS: GHII: AR: ${OP}: $ar:" `$PERFTIMESTAMPCMD` 1>&2

    # Target geometry XxY pixels, or "" if not appropriate to crop.
    AGHBGEOM="`echo $HEROIMGWH | awk -Fx '{
        HX=$1; HY=$2;
        #if(HX < TW) { exit; } # Refuse to make too narrow.
        if(HX < TNMINW) { exit; } # Refuse to make too narrow.
        srcAR=HX/HY;
        if(srcAR > AR) { # Potentially narrowing the image.
            adjHX = int(0.5 + HY * AR);
            #if(adjHX < TW) { exit; } # Refuse to make too narrow.
            if(adjHX < TNMINW) { exit; } # Refuse to make too narrow.
            printf("%dx%d", adjHX, HY);
        } else {
            adjHY = int(0.5 + HX / AR);
            printf("%dx%d", HX, adjHY);
        }
        }' AR=$ar TW=$TARGETWIDTH TNMINW=$MBODYPX`"

    # If not appropriate to make image of this ratio, then skip.
    if [ "" = "$AGHBGEOM" ]; then continue; fi 
    # If exactly reproduced source image ratio, then skip.
    if [ "$HEROIMGWH" = "$AGHBGEOM" ]; then continue; fi 

    # Use hero file naming pattern, but 'high-res'.
    AGHBFILEBASENAME=`basename $HEROIMGASFILE $AGHBSUFFIX`.$AGHBHASH.$AGHBGEOM.h$AGHBSUFFIX
    AGHBFILE=$AGHBDIR/$AGHBFILEBASENAME
    AGHBFAIL=$AGHBDIR/.flags/$AGHBFILEBASENAME.FAIL
    #echo "INFO: $0: AGHBFILE=$AGHBFILE" 1>&2

    # Assume that these images will not be referenced from our HTML pages,
    # but will be used only in less weight-sensitive ways by search engines.
    # HOWEVER, AVOID MAKING/RETAINING HUGE IMAGES.
    # Assume that rebuild of these images is relatively fast.
    # Assume that rebuild of these images is very unlikely to fail.
    # Assume that an extant file won't be removed from under us.
    # Can avoid locking and can move cropped image into place atomically.

    # If the FAIL flag exists and is new enough, then fail/skip immediately.
    if [ -f $AGHBFAIL -a $AGHBFAIL -nt $REBUILDFLAG ]; then continue; fi

    # Check for previous sins: built overlarge images just for search engines.
    if [ -s $AGHBFILE ]; then
        if [ "`wc -c < $AGHBFILE`" -gt "$BODYIMGABSMAXBYTES" ]; then
            echo "INFO: $0: removed extant over-large $AGHBFILE for $ARTBODY." 1>&2
            rm -f $AGHBFILE
        fi
    fi

    # If the target file exists and is new enough, then succeed immediately.
    if [ ! -s $AGHBFILE -o ! $AGHBFILE -nt $REBUILDFLAG ]; then
        : Rebuild required.
        echo "INFO: $0: (re)build required for $AGHBFILE" 1>&2

        TMPOUT=$AGHBFILE.$$.tmp
        # Crop assumed to be relatively quick.
        #echo "INFO: $0: building/cacheing $AGHBFILE for $ARTBODY." 1>&2
        $CONVERT $HEROIMGASFILE -gravity center -crop ${AGHBGEOM}+0+0 +repage $TMPOUT | tee -a $AGHBINFO 1>&2
        if [ -s $TMPOUT ]; then
            if [ "`wc -c < $TMPOUT`" -gt "$BODYIMGABSMAXBYTES" ]; then
                echo "INFO: $0: cannot use over-large $TMPOUT ($AGHBFILE) for $ARTBODY." 1>&2
                rm -f $TMPOUT
                touch  $AGHBFAIL
            else
                rm -f $AGHBFAIL
                chmod a+r,go-wx "$TMPOUT"
                mv -f $TMPOUT $AGHBFILE
                echo "INFO: $0: (re)build done for $AGHBFILE" 1>&2
            fi
        else
            touch $AGHBFAIL
            echo "WARNING: $0: not able to build/insert $AGHBFILE for $ARTBODY." 1>&2
        fi
        # Clean up unconditionally.
        rm -f $TMPOUT
    fi

    # If cropped image created successfully then spit out metadata.
    # If cropped image large enough make ImageObject, else bare thumbnailUrl.
    if [ -s $AGHBFILE ]; then
        CDNPREFIX="$STATICCDNPREFIX"
        if [ "false" = "$MOBILE" ]; then CDNPREFIX=""; fi
        IMG="$CDNPREFIX$AGHBFILE"
$PERFTIMESTAMPS && [ "$DEBUGPGBUILDPERF" -gt 1 ] && echo "INFO: adding metadata hero $AGHBFILE" | tee -a $AGHBINFO 1>&2
        if [ "`echo $AGHBGEOM | awk -Fx '{print $1}'`" -ge "$RECLIMGW" ]; then
            # Emit ImageObject...
            printf "<span itemprop=image itemscope itemtype=http://schema.org/ImageObject><link href=%s itemprop=\"contentUrl url\"><meta itemprop=caption content=\"%s\"><link href=%s itemprop=isBasedOn></span>" "$IMG" "$ALT" "$CDNPREFIX$HEROIMG"
        else
            # Emit bare thumbnailUrl...
            printf "<link href=%s itemprop=thumbnailUrl>" "$IMG"
        fi
    fi

    done

exit 0;;


inline-*) #-----------------------------------------------
#
#  inline-<OP>
#     Return the inline (data URL) form of the image if allowed
#     (source image is a local file, small enough, old enough if out/*)
#     else nothing is returned.
#
#     OP is the original operation requested at an outer level.
#
#     Can be passed the intermediate resized image (eg under img/a)
#     rather than the source image if required.

    INLINESRC=$HEROIMG

    case $IMAGELOC in
    # Permit inlining of local image, including auto-resized versions.
    img) ;;
    # Permit inlining of generated images only if unchanged for a long time.
    out) if [ ! -s "$INLINESRC" ]; then exit 0; elif [ "" = "`find $HEROIMG -mtime +$MINOUTINLINED`" ]; then exit 0; fi;;
    # Do not inline Gallery/CMS images.
    gallery) exit 0;;
    *) echo "ERROR: $0: $OP invalid image location $IMAGELOC for $ARTBODY." 1>&2
       exit 1;;
    esac

    # Stop if not valid local image.
    if [ ! -s "$INLINESRC" ]; then exit 0; fi

    # Stop if image is too large to ever inline.
    IMGSIZE="`wc -c < $INLINESRC`"
    if [ "$IMGSIZE" -gt ${MAXINLINEIMGATF} ]; then
        #echo "INFO: ${OP}: $INLINESRC too large to ever inline: ($IMGSIZE) for ${ARTBODY} for mobile=$MOBILE" 1>&2
        exit 0;
    fi

    # Stop unless unconditionally small enough to inline,
    # or lite and is 1st hero image to avoid an RTT for first content paint.
    if [ "$IMGSIZE" -gt ${MAXINLINEIMG} -a \( "false" = "$MOBILE" -o \( "inline-insTop" != "$OP" -a "inline-carouselEntryImgBig" != "$OP" \) \) ]; then
        #echo "INFO: ${OP}: $INLINESRC rejected for inlining: not tiny and not first hero on mobile: ($IMGSIZE) for ${ARTBODY} for mobile=$MOBILE" 1>&2
        exit 0;
    fi

    # Avoid inlining if deep below the fold
    # (look for (non-first) dbtf=1 kvp),
    # unless really tiny (smaller than the likely URL) so very likely a win,
    # or for desktop and really really tiny!
    # Note: this assumes that 4/3 Base64 expansion likely reclaimed by gzip
    #    or better (eg brotli) content encoding.
    # Note that smallest possible PNG is ~67 bytes, JPEG ~107B, GIF ~14B.
    # Assume that some attribute text is dropped when inlining such as
    # 'width=XX height=XX style=background:#XXX ' more than compensating for
    # 'data:image/jpeg;base64,' prefixing the inlined data.
    # Note that for SVG careful URL encoding likely yields better compression
    # with 'data:image/svg+xml,' prefixing the inlined data.
    # This can be strict URL encoding or slightly relaxed and more compact.
    IMGURL="$STATICCDNPREFIX$INLINESRC"
    if [ "false" = "$MOBILE" ]; then IMGURL="$INLINESRC"; fi
    #echo "INFO: ${OP}: INLINE: body vs URL: $IMGSIZE vs `echo $IMGURL | wc -c` for $IMGURL mobile=$MOBILE" 1>&2

    if [ "$IMGSIZE" -lt `echo $IMGURL | wc -c` ]; then
$PERFTIMESTAMPS && [ "$DEBUGPGBUILDPERF" -gt 1 ] && echo "INFO: ${OP}: INLINED because $INLINESRC is smaller than its URL: ($IMGSIZE) for ${ARTBODY} for mobile=$MOBILE" 1>&2
        : OK to inline!
    elif [ "$IMGSIZE" -le ${MAXINLINEIMGDESKUNCOND} -a "false" = "$MOBILE" ]; then
$PERFTIMESTAMPS && [ "$DEBUGPGBUILDPERF" -gt 1 ] && echo "INFO: ${OP}: INLINED because $INLINESRC is tiny and this is desktop full-fat page: ($IMGSIZE) for ${ARTBODY} for mobile=$MOBILE" 1>&2
        : OK to inline!
    elif [ "" != "`echo $ARGKVP | egrep '(,dbtf=1)'`" ]; then
        #echo "INFO: ${OP}: $INLINESRC rejected for inlining: deep below the fold: ($IMGSIZE) for ${ARTBODY} for mobile=$MOBILE" 1>&2
        exit 0;
    fi

    # Attempt to create the inline image version for non-desktop.
    DATAURL=""
    MIMETYPE=""
    # INLINEENCODING empty means Base64, else urlencoding.
    INLINEENCODING=""
    SELHEROIMAGESUFFIX="$(echo $INLINESRC | sed -n -e 's/^.*\([.][a-z][a-z0-9]*\)L*$/\1/p')"
    case $SELHEROIMAGESUFFIX in
	.jpg) MIMETYPE="image/jpeg"; INLINEENCODING=";base64";;
	.png) MIMETYPE="image/png"; INLINEENCODING=";base64";;
	.svg) MIMETYPE="image/svg+xml";;
	*) echo "ERROR: unexpected suffix $SELHEROIMAGESUFFIX for inline data URL for $INLINESRC" 1>&2;;
    esac
    # URLENCODE where appropriate.
    # A slightly relaxed and more compact encoding should be widely acceptable.
    URLENCSCRIPT="script/urlencode-dataURL.sh"
    if [ "" = "$INLINEENCODING" ] && [ -f "$URLENCSCRIPT" ]; then
        DATAURL="data:$MIMETYPE,$(sh "$URLENCSCRIPT" < "$INLINESRC")"
    fi
    # FOR EVERYTHING ELSE DO BASE64...
    if [ "" = "$DATAURL" ]; then
	# Use ImageMagick convert if present and new enough (V7+).
	# Does not work with ImageMagic 6.7.
	CONVER=""
	if [ "" != "$CONVERT" ] && [ -x "$CONVERT" ]; then
	    CONVER="$($CONVERT -version| awk '{print substr($3,1,1); exit}')"
	    if [ "$CONVER" -ge 7 ]; then
		DATAURL="$($CONVERT "$INLINESRC" inline: 2>/dev/null)"
	    fi
	fi
    fi
    # If ImageMagick cannot be used, then try the base64 utility.
    if [ "" = "$DATAURL" ]; then
        # Generate the data URL, wrapped in quotes for safety for now.
        B64="$(base64 -w0 < "$INLINESRC")"
        if [ "" != "$MIMETYPE" -a "" != "$B64" ]; then
            DATAURL="data:$MIMETYPE;base64,$B64"
        fi
    fi

    echo "$DATAURL"

exit 0;;


*) #---------------------------------------------------------
echo "ERROR: $0: bad operation $OP for $ARTBODY." 1>&2
exit 2;;

esac

echo "Unable to create hero image insert for for $HEROIMG..." 1>&2
exit 1
