#! @PERL@
# -*- Mode: Perl -*-
#
# $Id: webmagick.in,v 1.97 2001/12/05 13:52:39 ache Exp $
#
# You are looking at the main PERL script for WebMagick, a package to
# intelligently create HTML and JavaScript index files and imagemaps
# corresponding to a directory tree of image files.
#
# Copyright Bob Friesenhahn (bfriesen@simple.dallas.tx.us) 1996, 1997,
# 1998, 1999, 2000
#
# This work may be used for any purpose, public or private, provided
# that this work or derivations thereof are attributed to its author.
# Full copyrights to this work are maintained by its author to ensure
# that rights so bestowed are not abridged.
#
# NOTE: This program has a substantial manual.  Considerable time has
# been spent writing it so please at least skim through it before
# asking questions.
#
# Requires the ImageMagick, PerlMagick, and PERL 5 packages.  May
# possibly execute correctly (but with reduced performance) with
# package versions as old as:
#  ImageMagick -- 3.8.4
#  PerlMagick  -- 1.12
#  PERL        -- 5.003
#
# But always tested by the author with recent versions.  At the last
# update of these notes (December 19, 1998) the latest versions tested
# were:
#  ImageMagick -- 4.1.6
#  PerlMagick  -- 1.55
#  PERL        -- 5.004_04
#
# While some effort is made to ensure that WebMagick will execute under
# older package versions, the author does not use these versions so
# he will only know to correct incompatabilities if they are brought
# to his attention.
#
# The latest version of WebMagick is always available from the URL
# "ftp://ftp.simplesystems.org/pub/WebMagick". Keep track of
# the latest WebMagick developments by visiting the WebMagick web page
# at "http://webmagick.sourceforge.net/".
#
# Obtain ImageMagick from "ftp://ftp.wizards.dupont.com/pub/ImageMagick"
# or visit the ImageMagick web page at
# "http://www.wizards.dupont.com/cristy/ImageMagick.html".
#
# Obtain PerlMagick from
# "ftp://ftp.wizards.dupont.com/pub/ImageMagick/perl" or visit the
# PerlMagick web page at
# "http://www.wizards.dupont.com/cristy/www/perl.html".
#
# ImageMagick and PerlMagick are written by John Cristy (cristy@dupont.com).
# WebMagick would not be possible without his wonderful software.  The
# author greatly appreciates Cristy's assistance with ironing out PerlMagick's
# (or WebMagick's bugs) during the development of WebMagick.
#
# Suggestions, moral support, and patches were provided by Anthony
# Thyssen (anthony@cit.gu.edu.au). His help is very much appreciated.
#

use locale;
use POSIX qw(strftime _exit);
use Cwd;

# If your PERL barfs, you may need to comment one or more of the following
# lines out:
use strict 'refs';	# Be picky about references
use strict 'subs';	# Be picky about subroutines
use strict 'vars';	# Be picky about variable scope (explicit globals)

#
# Global variable declarations
#
my (
    %RGBDB,		# RGB color database
    @allFiles,		# List of files in current directory
    @allImageFiles,	# List of all source file names in directory
    @cacheFiles,	# List of files in the image cache
    $currentDirectory,	# Current directory (absolute)
    $doMontage,		# Set to true if montage must be generated
    $doPageHtml,	# Set to true if per-page HTML files must be generated
    %dirOrder,		# Order directories are listed in dirindex file
    %dirTitles,		# Directory name to title index
    %fileNames,		# Hash of generated file names
    %frameTargets,	# Frame targets
    @global_option_files, # List of rc files to source before all others
    $haveImages,	# Set to true if there are images in directory
    $haveReadme,	# Set to true if there is a README file in directory
    %htmlOpts,		# Options that effect HTML output
    $htmlParams,	# String form of HTML parameters
    %iconImageSizes,	# Size of icons
    %iconImageUrls,	# URLs for icons
    $icon_base_url,	# URL to icon directory
    $icon_dir_path,	# Path to icon directory
    %icon_paths,	# Paths to icon files
    @imageThumbCoords,	# Thumbnail imagemap coordinates
    $imageFrameLink,	# URL to use as source for image frame
    %imageLabels,	# Labels to use for images (if specified)
    %tableImageLabels, # labels to use in tables
    %imageOrder,	# Order that images occur in imgindex file
    $inChild,		# Set to true if this process is a child
    $currentDate,
    @montageImages,	# List of per-page montage image names
    @montageImageSizes,	# List of per-page montage image sizes
    $montageArguments,	# PerlMagick Montage() arguments
    $montageParameters,	# Montage() arguments plus others that effect montage output
    $netscapeColormap,	# PerlMagick image object representing Netscape color cube
    $numPages,		# Number of pages to represent current directory
    $opt_address,
    $opt_allowconfig,
    $opt_anonymous,
    $opt_cache,
    $opt_cachedir,
    $opt_cacheformat,
    $opt_cachegeom,
    $opt_cachemin,
    $opt_coloralink,
    $opt_colorback,
    $opt_colorfore,
    $opt_colorlink,
    $opt_colorvlink,
    $opt_columns,
    $opt_date,
    $opt_debug,
    $opt_dircoloralink,
    $opt_dircolorback,
    $opt_dircolorfore,
    $opt_dircolorlink,
    $opt_dircolorvlink,
    $opt_dirfmt,
    $opt_dirhtmlext,
    $opt_dirindexname,
    $opt_forcecache,
    $opt_forcegif,
    $opt_forcejpeg,
    $opt_forcehtml,
    $opt_forcemontage,
    $opt_frame_name_dirview,
    $opt_frame_name_imageview,
    $opt_frame_name_thumbview,
    $opt_frame_name_top,
    $opt_frameborder,
    $opt_framebordersize,
    $opt_frameddirfmt,
    %opt_framefmt,
    %opt_framefmt_frames,
    $opt_framemarginheight,
    $opt_framemarginwidth,
    $opt_frames,
    $opt_framestyle,
    $opt_htmlext,
    $opt_header,
    $opt_footer,
    $opt_help,
    $opt_htimage,
    $opt_iconbase,
    $opt_iconpath,
    %opt_icons,
    $opt_ignore,
    $opt_ignorefp,
    $opt_imgindexname,
    $opt_indexname,
    $opt_javascript,
    $opt_jpegquality,
    $opt_lockfile,
    $opt_mapnetscape,
    $opt_maptype,
    $opt_maxgif,
    $opt_metaauthor,
    $opt_metacharset,
    $opt_metaclassification,
    $opt_metadescription,
    $opt_metaexpires,
    $opt_metakeywords,
    $opt_msg_copyright,
    $opt_msg_date_format,
    $opt_msg_directories,
    $opt_msg_directory_navigator,
    $opt_msg_images,
    $opt_msg_index_of_directory,
    $opt_msg_index_of_files,
    $opt_msg_index_through,
    $opt_msg_next,
    $opt_msg_page_navigator,
    $opt_msg_page_updated_on,
    $opt_msg_prev,
    $opt_msg_produced_by,
    $opt_msg_readme,
    $opt_msg_up,
    $opt_pageindexname,
    $opt_prefixpath,
    $opt_prune,
    $opt_readfailhook,
    $opt_readme,
    $opt_readmevisible,
    $opt_recurse,
    $opt_rgbdb,
    $opt_rootpath,
    $opt_rows,
    $opt_serversidemap,
    $opt_skip,
    $opt_srcdir,
    $opt_stylesheet, 	# URL to a CSS to use
    $opt_tables,
    $opt_thumbbackground,
    $opt_thumbbordercolor,
    $opt_thumbborderwidth,
    $opt_thumbcompose,
    $opt_thumbfont,
    $opt_thumbforeground,
    $opt_thumbframe,
    $opt_thumbframebgcolor,
    $opt_thumbframecolor,
    $opt_thumbgeometry,
    $opt_thumbgravity,
    $opt_thumblabel,
    $opt_thumblabelwidth,
    $opt_thumbmattecolor,
    $opt_thumbposthook,
    $opt_thumbprehook,
    $opt_thumbshadow,
    $opt_thumbtexture,
    $opt_thumbtransparent,
    $opt_title,
    $opt_verbose,
    $opt_version,
    $opt_webmagickrc,	# Per-directory WebMagick rc file name
    $opt_zoomfilter,
    $opt_pichtml,
    $opt_pichtmlbottom,
    $opt_pichtmlext,
    $opt_pichtmlnav,
    $opt_pichtmlputtitle,
    $opt_pichtmltarget,
    $opt_pichtmltitleend,
    $opt_pichtmltitlestart,
    $opt_pichtmltop,
    $pageStatusTime,	# Time status file last modified
    $pageNumber,	# Current page number
    $pathSep,		# OS-specific path separator
    $perlVarsLoaded,	# Set to one when directory status file sourced
    $perlVarsVersion,   # version used for last file generation
    $prefix,            # installation directory
    $requiredPerlVarsVersion, # version required. If less than this, regenerate
    %relativePathCache, # Relative path cache
    $sourceDirectory,	# Directory to process
    %thumbImageSizes, # thumbnail image sizes
    @imageNames,	# List of source file names for current page
    $startTime,		# Clock time when program started
    %dirJsLink,		# Link name to use when changing directories via JavaScript
    @dirNames,		# List of directories under this directory
    $pageTitle,		# Title for current page
    %webmagickInfo	# WebMagick version information as a hash
    );


###########################################################################
#                     Internal Default Options
###########################################################################
#
#WebMagickRcTop  -- Don't remove this line
#
# Copy the webmagickrc file to your home directory under the name
# .webmagickrc. Any default definitions you want to override should be
# uncommented in this file and modified.
#
# Navigation Icon Paths and URLs
# Specify the path and file name for the navigation icons.
# !!!MUST EDIT OR OVERRIDE!!!
#
$opt_rootpath	= '@ROOTPATH@';	# Directory Path to top of html tree
				# Needed to determine relative paths to images
$opt_prefixpath	= '@PREFIXPATH@'; # Path or URL to prepend to root URL
				# Not used if local relative paths used
$opt_iconpath	= '@ICONPATH@';
				# Relative path under rootpath / prefixpath
$opt_iconbase   = '';
				# Global icons base URL, if not empty
$prefix = '@prefix@'; # installation directory

#
# Server-side imagemap settings
# !!!MAY NEED TO EDIT OR OVERRIDE!!!
# 
$opt_htimage='@HTIMAGE@';	# Base URL to server-side imagemap CGI
				#  On some systems this is  /cgi-bin/imagemap
				#  Set to '' to use a ".map" URL with relative
                                # URLs (latest NCSA & Apache)
$opt_maptype='@MAPTYPE@';	# Maptype must be 'cern' or 'ncsa'. If you are
				# using Apache, specify 'ncsa'.
$opt_serversidemap = 0;         # Enable server-side maps writting
				
$perlVarsVersion = 0; # default this to 0 for it to be overidden by appropriate status files				
$requiredPerlVarsVersion = 2.2; # need this version to avoid regeneration of files

#
# RC files
#
#
# Name of per-directory rc file
$opt_webmagickrc	= '.webmagickrc';
# global rc files
@global_option_files    = ("@sysconfdir@/webmagickrc", "$ENV{'HOME'}/${opt_webmagickrc}");

#
# Lockfile to keep from doing same directory in two processes
#
$opt_lockfile		= '.webmagick.lockfile';

# File naming
$opt_indexname		= 'index.html';	# Per-directory master index file 
$opt_readme		= 'README.html';# Name of welcome page README file ('' = none)
$opt_pageindexname	= '.index';	# Base name of secondary index files
$opt_dirindexname	= '.dirindex';	# Subdirectory Title cross-reference
					#  dirname   Directory Title
$opt_dirhtmlext         = '.html';      # Use .shtml for SSI
$opt_imgindexname	= '.imgindex';	# Image name to label cross-reference file

if ($^O eq "MSWin32") 
  {$pathSep = "\\"; }
else 
  {$pathSep = "/"; }
  
#
# HTML color and background image related options
#
# X11 RBG color database (rgb.txt) location
# Entries in this file have the form:
#        Red Green Blue  Color-name
# where color intensities are specified in decimal.
#  e.g.  250 240 230     linen
$opt_rgbdb='@RGBDB@';
#
# Page Frame & non-framed pages
$opt_colorback		= '#CCCCCC';	# Color -- Background
$opt_colorfore		= 'black';	# Color -- Foreground

$opt_coloralink		= '#FF0000';	# Color -- Active link
$opt_colorlink		= '#0000EE';	# Color -- Link
$opt_colorvlink		= '#551A8B';	# Color -- Visited link
#
# Directory frame (Leave options empty ('') to use page frame colors
$opt_dircolorback	= '#B2B2B2';	# Color -- Background
$opt_dircolorfore	= 'black';	# Color -- Foreground
$opt_dircoloralink	= '';		# Color -- Active link
$opt_dircolorlink	= '';		# Color -- Link
$opt_dircolorvlink	= '';		# Color -- Visited link

$opt_stylesheet		= '';		# no default stylesheet
$opt_allowconfig = 0; 		# default to not allowing configuration 		


# General options
$opt_anonymous		= 0;	# Don't show address & copyright info on pages
$opt_debug		= 0;	# Debug flag (default off)
$opt_recurse		= 0;	# Recursivally apply webmagick (default off)
$opt_prune		= 0;	# Do Not recurse into subdirectories (off)
$opt_ignore		= 0;	# Do not webmagick this directory
				#    but still recurse into sub-directories
$opt_ignorefp		= 1;	# ignore _* special FrontPage directories (on)
$opt_srcdir		= '.';	# Source directory path (current directory)
$opt_verbose		= 0;	# Verbose flag (default off)
$opt_forcecache		= 0;	# Force update of cached thumbnails
$opt_forcehtml		= 0;	# Force HTML files to be generated (default off)
$opt_forcemontage	= 0;	# Force montage (default off)
$opt_forcegif		= 0;	# Force GIF imagemaps (default off)
$opt_forcejpeg          = 0;    # Force JPEG imagemaps (default off)
$opt_jpegquality        = 70;   # Quality for JPEG imagemaps
$opt_help		= 0;	# Display usage message
$opt_version		= 0;	# Display version info
$opt_htmlext            = '.html'; # Use .shtml for SSI
$opt_header             = '<CENTER>';   # Imagemap extra page header (HTML)
$opt_footer             = '</CENTER>';   # Imagemap extra page footer (HTML)
$opt_javascript         = 0;    # Enable JavaScript output
$opt_readmevisible	= 0;	# Make README.html be first page.
$opt_title		= '';	# Page title (blank provides default title)
$opt_tables		= 0;	# disable tables
$opt_address		= '';	# Additonal address info for bottom of
                                #    imagemap page
$opt_date               = 1;    # Output updates date

$opt_pichtml            = 0;    # Write separate HTML for each picture
$opt_pichtmlbottom      = '</CENTER>';
$opt_pichtmlext         = '.html'; # Use .shtml for SSI
$opt_pichtmlnav         = 0;    # Write navigation into pic's HTML
$opt_pichtmlputtitle    = 1;    # Write picture title above it
$opt_pichtmltarget      = '';
$opt_pichtmltitleend    = '</P>'; # End tags for picture title
$opt_pichtmltitlestart  = '<P>';  # Start tags for picture title
$opt_pichtmltop         = '<CENTER>';

#				
# ImageMagick Montage settings
#
$opt_maxgif		= 30000;	# Maximum GIF imagemap size before
					#  trying JPEG
$opt_columns		= 6;		# Max number of columns in montage grid
$opt_rows		= 4;		# Max number of rows in montage grid
$opt_mapnetscape	= 0;		# Map GIFs to Netscape 216-color cube
$opt_thumbtexture	= 'false';	# Texture to tile onto the image background
$opt_thumbbackground	= '#CCCCCC';	# Montage background color
$opt_thumbborderwidth	= 0;		# Thumbnail border width (pixels)
$opt_thumbframebgcolor	= 'black';	# Inside of Frame color.  Unused if no frame
$opt_thumbframecolor	= '#CCCCCC';	# Color -- Frame Color
$opt_thumbtransparent	= $opt_thumbbackground;	# Color -- Image Transparency
$opt_thumbcompose	= 'Replace';	# Thumbnail image composition operation
$opt_thumbfont		= '@DEFAULTFONT@'; # Label font
$opt_thumbforeground	= 'black';	# Montage foreground color
$opt_thumbframe		= 'false';	# Geometry of frame around thumbnail
					#    (default false)
$opt_thumbgeometry	= '106x80+2+2>'; # Size of thumbnail images (width x height)
$opt_thumbgravity	= 'Center';	# Direction thumbnail gravitates to
					#    (default Center)
$opt_thumblabel		= '%f\n%wx%h\n%b'; # Default format for thumbnail text label
$opt_thumblabelwidth	= 13;		# Label width past which truncation occurs

$opt_thumbshadow	= 'false';	# Enable decorative shadow under thumbnail
					#    (default disabled)
$opt_zoomfilter		= 'Mitchell';	# Zoom (size reduction) filter
					#  (Box/Triangle/Mitchell)

#
# Hooks to add code to key places in WebMagick
#
$opt_thumbprehook	= '';		# PerlMagick commands to execute on original
					#  prior reducing to thumbnail size
$opt_thumbposthook	= '';		# PerlMagick commands to execute on image
					#  after reduction to thumbnail size
$opt_readfailhook	= '';		# PERL commands to execute if reading image
					#  fails due to image corruption

#
# Caching related options
#
# Note: See below about sharing cache with 'xv'

# If caching is turned on, the montage grid will continue to be
# controlled by opt_thumbgeometry but the thumbnail size will be
# controlled by opt_cachegeom

$opt_cache		= 1;		# Cache thumbnails
$opt_cachedir		= '.cache';	# Subdirectory to cache thumbnails in
$opt_cacheformat	= 'JPEG';	# Format to use for thumbnails
$opt_cachegeom		= $opt_thumbgeometry; # Thumbnail geometry
$opt_cachemin		= 300*200;	# Smallest image to cache in total pixels
					#  (width * height).  Images smaller than
					#  this size will not be cached.



# Uncomment these options to share cache with John Bradley's 'xv'
# (Visual Schnauzer format) NOTE: The 'xv' thumbnail format only
# supports 128 absolute colors so expect some montage thumbnail
# degradation when using 'xv' thumbnails.
#
#
# $opt_cacheformat	= 'P7';		# 'xv's thumbnail format
# $opt_cachegeom	= '80x60';	# 'xv's default thumbnail geometry is 80x60
# $opt_cachedir		= '.xvpics';	# 'xv's thumbnail cache directory
# $opt_cachemin		= 0;		# Cache all thumbnails
#
# Uncomment remaining to obtain 'xv's Visual Schnauzer "look and feel"
#
# $opt_thumbgeometry	= '92x72+2+2>';
# $opt_thumblabel	= '%f';
# $opt_thumblabelwidth	= 12;
# $opt_thumbgravity	= 'South';

#
# Frame related options
#
$opt_frames             = 1;            # Use frames
$opt_framemarginwidth	= 1;		# Pixels allocated to frame margin in horizontal direction
$opt_framemarginheight	= 1;		# Pixels allocated to frame margin in vertical direction
$opt_framebordersize	= 3 ;		# Pixels allocated to frame border
$opt_frameborder	= 'YES';	# Enable (YES) or disable (NO) decorative frame borders
$opt_framestyle		= 1;		# Frame style to use (out of those available)

#
# Meta-tag defaults
#
$opt_metaauthor = '';			# Page author
$opt_metacharset = '';                  # Page character set
$opt_metaclassification = '';		# Page classification
$opt_metadescription = '';		# Page description
$opt_metaexpires = '';			# Date page expires (e.g "Tue, 20 Aug 1996 14:25:27 GMT")
$opt_metakeywords = 'WebMagick';	# Key words

#
# Messages customization defaults
#
$opt_msg_copyright = 'Copyright ';
$opt_msg_date_format = '%B %e, %Y';     # See strftime(3)
$opt_msg_directories = 'Directories';
$opt_msg_directory_navigator = 'Directory Navigator';
$opt_msg_images = 'Images';
$opt_msg_index_of_directory = 'Index of directory';
$opt_msg_index_of_files = 'Index of files ';
$opt_msg_index_through = 'through';
$opt_msg_next = 'Next';
$opt_msg_page_navigator = 'Page Navigator';
$opt_msg_page_updated_on = 'Page updated on';
$opt_msg_prev = 'Prev';
$opt_msg_produced_by = 'Produced by';
$opt_msg_readme = 'ReadMe';
$opt_msg_up = 'Up';

#
# Hash table of images used -- image size read internally by webmagick
#

# $opt_icons{'background'}	= 'thumbs.jpg';		# Thumbnail frame background image
# $opt_icons{'dirbackground'}	= 'dir.jpg';		# Directory frame background image
$opt_icons{'config'}		= 'blue_c.gif';	# Configuration button
$opt_icons{'prev'}		= 'blue_prev.gif';	# Previous
$opt_icons{'prev_gray'}		= 'gray_prev.gif';	# Previous (grayed out)
$opt_icons{'next'}		= 'blue_next.gif';	# Next
$opt_icons{'next_gray'}		= 'gray_next.gif';	# Next (grayed out)
$opt_icons{'up'}		= 'blue_up.gif';  	# Up
$opt_icons{'help'}		= 'blue_readme.gif';	# Help Readme File
# $opt_icons{'help'}		= 'blue_help.gif';	# Help Alternative (Question)
# $opt_icons{'dir'}             = 'blue_dir.gif';       # Directory List Icon (See below)
# $opt_icons{'ball'}		= 'blue_ball.gif';	# A ball matching other icons
$opt_icons{'frame-style-1'}	= 'frame-style-1.jpg';
$opt_icons{'frame-style-2'}	= 'frame-style-2.jpg';
$opt_icons{'frame-style-3'}	= 'frame-style-3.jpg';
$opt_icons{'frame-style-4'}	= 'frame-style-4.jpg';


#
# WebMagickRcBottom -- Don't remove this line
#

# Format Templates
#
# WARNING: This is for expert web and perl programmers only do not
# modify unless you know what you are doing.  To learn how frames work,
# read the pages under the URL
# http://home.netscape.com/assist/net_sites/frames.html
#
# Extra Images can be added to the above hash table and then used in
# the following format options. For example the 'dir' icon above can
# be uncommented then the following lines added below.  WARNING: this
# is only useful if $opt_indexname is something else.
#
# 
# <A HREF=\"./\" TARGET=\"thumbnail\">
#    <IMG SRC=\"$iconImageUrls{dir}.gif\" $iconImageSizes{dir} ALT=\"\" BORDER=0></A>
#    <A HREF=\"./\" TARGET=\"thumbnail\">Dir Listing</A><BR>
#

#
# Template for the Frame definition
# This allows adding frames, changing geometry, etc.
#
# Frame Target Names
$opt_frame_name_top="_top";		# Reload frames entirely
#$opt_frame_name_self="_self";		# Link to this (same) frame
#$opt_frame_name_parent="_parent";	# Link to parent frame
$opt_frame_name_dirview="dirview";	# Directory nav frame
$opt_frame_name_thumbview="thumbview";	# Imagemap (image selection) frame
$opt_frame_name_imageview="imageview";	# Image viewing frame (three frames)


#
# Simple two frame screen with directories listed in the left
# frame and imagemap/README displayed in the right frame.
#
#  -------------
# |  |          |
# |  |          |
# |  |          |
# |  |          |
# |  |          |
# |  |          |
#  -------------
$opt_framefmt{1}=
'<FRAMESET COLS=\"132,*\" FRAMEBORDER=${opt_frameborder} BORDER=${opt_framebordersize}>
 <FRAME SRC=\"${dirframelink}\" NAME=\"${opt_frame_name_dirview}\"
   MARGINWIDTH=${opt_framemarginwidth} MARGINHEIGHT=${opt_framemarginheight}>
 <FRAME SRC=\"${pageframelink}\" NAME=\"${opt_frame_name_imageview}\"
   MARGINWIDTH=${opt_framemarginwidth} MARGINHEIGHT=${opt_framemarginheight}>
</FRAMESET>';
$opt_framefmt_frames{1}=2;	# Number of frames expressed by this frame format

# Three frame screen with directories listed in top-left frame,
# imagemap displayed in bottom-left frame, and README/Images
# displayed in full-height right-hand frame.
#  -------------
# |  |          |
# |  |          |
# |--|          |
# |  |          |
# |  |          |
# |  |          |
#  -------------
$opt_framefmt{2}=
'<FRAMESET COLS=\"145,*\" FRAMEBORDER=${opt_frameborder} BORDER=${opt_framebordersize}>
 <FRAMESET ROWS=\"20%,*\" FRAMEBORDER=${opt_frameborder} BORDER=${opt_framebordersize}>
  <FRAME SRC=\"${dirframelink}\" NAME=\"${opt_frame_name_dirview}\"
    MARGINWIDTH=${opt_framemarginwidth} MARGINHEIGHT=${opt_framemarginheight}>
  <FRAME SRC=\"${pageframelink}\" NAME=\"${opt_frame_name_thumbview}\"
    MARGINWIDTH=${opt_framemarginwidth} MARGINHEIGHT=${opt_framemarginheight}>
 </FRAMESET>
 <FRAME SRC=\"${imageFrameLink}\" NAME=\"${opt_frame_name_imageview}\"
   MARGINWIDTH=${opt_framemarginwidth} MARGINHEIGHT=${opt_framemarginheight}>
</FRAMESET>';
$opt_framefmt_frames{2}=3;	# Number of frames expressed by this frame format

# Three frame screen with directories listed in left frame,
# imagemap displayed in top-right frame, and README/Images
# displayed in lower-right frame.
#  -------------
# |  |          |
# |  |----------|
# |  |          |
# |  |          |
# |  |          |
# |  |          |
#  -------------
$opt_framefmt{3}=
'<FRAMESET COLS=\"132,*\" FRAMEBORDER=${opt_frameborder} BORDER=${opt_framebordersize}>
 <FRAME SRC=\"${dirframelink}\" NAME=\"${opt_frame_name_dirview}\"
   MARGINWIDTH=${opt_framemarginwidth} MARGINHEIGHT=${opt_framemarginheight}>
 <FRAMESET ROWS=\"150,*\" FRAMEBORDER=${opt_frameborder} BORDER=${opt_framebordersize}>
   <FRAME SRC=\"${pageframelink}\" NAME=\"${opt_frame_name_thumbview}\"
     MARGINWIDTH=${opt_framemarginwidth} MARGINHEIGHT=${opt_framemarginheight}>
   <FRAME SRC=\"${imageFrameLink}\" NAME=\"${opt_frame_name_imageview}\"
     MARGINWIDTH=${opt_framemarginwidth} MARGINHEIGHT=${opt_framemarginheight}>
 </FRAMESET>
</FRAMESET>';
$opt_framefmt_frames{3}=3;	# Number of frames expressed by this frame format

# Three frame screen with directories listed in lower-left frame,
# imagemap displayed in top frame, and README/Images displayed in
# lower-right frame.
#  -------------
# |             |
# |-- ----------|
# |  |          |
# |  |          |
# |  |          |
# |  |          |
#  -------------
$opt_framefmt{4}=
'<FRAMESET ROWS=\"172,*\" FRAMEBORDER=${opt_frameborder} BORDER=${opt_framebordersize}>
 <FRAME SRC=\"${pageframelink}\" NAME=\"${opt_frame_name_thumbview}\"
   MARGINWIDTH=${opt_framemarginwidth} MARGINHEIGHT=${opt_framemarginheight}>
 <FRAMESET COLS=\"132,*\" FRAMEBORDER=${opt_frameborder} BORDER=${opt_framebordersize}>
   <FRAME SRC=\"${dirframelink}\" NAME=\"${opt_frame_name_dirview}\"
     MARGINWIDTH=${opt_framemarginwidth} MARGINHEIGHT=${opt_framemarginheight}>
   <FRAME SRC=\"${imageFrameLink}\" NAME=\"${opt_frame_name_imageview}\"
     MARGINWIDTH=${opt_framemarginwidth} MARGINHEIGHT=${opt_framemarginheight}>
 </FRAMESET>
</FRAMESET>';
$opt_framefmt_frames{4}=3;	# Number of frames expressed by this frame format

#
# Template for the Directory Index Frame
#
$opt_frameddirfmt='
<P>
<FONT SIZE=\"-1\">
${uphtml}
${helphtml}
${nexthtml}
</FONT>
</P>
${dirhtml}
';

#
# Template for Non-Framed Top Index Page ($opt_indexname)
#
$opt_dirfmt='\n<H3>${opt_msg_directory_navigator} ...</H3>
${uphtml}
${helphtml}
${dirhtml}
${pageindexhtml}
';

# Save revision to variable
$webmagickInfo{'version'} = '@VERSION@';


###########################################################################
#                  End of Internal Default Options
###########################################################################

select(STDERR); $| = 1;		# Make stderr unbuffered
select(STDOUT); $| = 1;		# Make stdout unbuffered

umask( 022 );			# Sets default file mode 644
$startTime = time;		# Save start time

$inChild = 0;

# Set signal handler to gracefully abort (hah!).  Handle signal
# induced exits properly.  Only the parent prints a status message.

sub sig_handler {
    my ($sig) = @_;
    if( ! $inChild ) {
	syswrite (STDERR, "\nCaught signal SIG", 18);
	syswrite (STDERR, "$sig", length("$sig"));
	syswrite (STDERR, " -- aborting ...\n", 17);
    }
    _exit(1);
}
$SIG{'HUP'} = \&sig_handler;
$SIG{'INT'} = \&sig_handler;
$SIG{'QUIT'} = \&sig_handler;


# Allow global options files to override defaults set above (but not
# command line options)

my $goptfile;
foreach $goptfile (@global_option_files) {
    sourceRcFile( $goptfile )
        || die("Error sourcing ${goptfile}\n");
}


# Eval per-directory rc files if they exist.  Rc files are evaluated
# for each directory starting from $opt_rootdir until the current
# directory is reached. This supports "additive" behavior for a branch
# in the tree.
$currentDirectory=cwd();	# Get current (absolute) directory
evaluateRcFiles();

#
# We don't really like command line options but we'll support them anyway. :-)
#
use Getopt::Long;
if ( ! GetOptions(
		'address=s'		=> \$opt_address,
		'anonymous!'		=> \$opt_anonymous,
		'cache!'		=> \$opt_cache,
		'cachedir=s'		=> \$opt_cachedir,
		'cacheformat=s'		=> \$opt_cacheformat,
		'cachegeom=s'		=> \$opt_cachegeom,
		'cachemin=i'		=> \$opt_cachemin,
		'coloralink=s'		=> \$opt_coloralink,
		'colorback=s'		=> \$opt_colorback,
		'colorfore=s'		=> \$opt_colorfore,
		'colorlink=s'		=> \$opt_colorlink,
		'colorvlink=s'		=> \$opt_colorvlink,
		'allowconfig!'          => \$opt_allowconfig,
		'columns=i'		=> \$opt_columns,
		'date!'                 => \$opt_date,
		'debug!'		=> \$opt_debug,
		'dircoloralink=s'	=> \$opt_dircoloralink,
		'dircolorback=s'	=> \$opt_dircolorback,
		'dircolorfore=s'	=> \$opt_dircolorfore,
		'dircolorlink=s'	=> \$opt_dircolorlink,
		'dircolorvlink=s'	=> \$opt_dircolorvlink,
		'dirhtmlext=s'          => \$opt_dirhtmlext,
		'dirindexname=s'	=> \$opt_dirindexname,
		'footer=s'              => \$opt_footer,
		'forcecache!'		=> \$opt_forcecache,
		'forcegif!'		=> \$opt_forcegif,
		'forcehtml!'		=> \$opt_forcehtml,
		'forcejpeg!'            => \$opt_forcejpeg,
		'forcemontage!'		=> \$opt_forcemontage,
		'framemarginwidth=i'	=> \$opt_framemarginwidth,
		'framemarginheight=i'	=> \$opt_framemarginheight,
		'framebordersize=i'	=> \$opt_framebordersize,
		'frameborder=s'		=> \$opt_frameborder,
		'frames!'               => \$opt_frames,
		'framestyle=i'		=> \$opt_framestyle,
		'header=s'		=> \$opt_header,
		'help!'			=> \$opt_help,
		'htimage=s'		=> \$opt_htimage,
		'htmlext=s'             => \$opt_htmlext,
		'iconbase=s'            => \$opt_iconbase,
		'iconpath=s'		=> \$opt_iconpath,
		'ignorefp!'             => \$opt_ignorefp,
		'imgindexname=s'	=> \$opt_imgindexname,
		'indexname=s'		=> \$opt_indexname,
		'javascript!'		=> \$opt_javascript,
		'jpegquality=i'         => \$opt_jpegquality,
		'mapnetscape!'		=> \$opt_mapnetscape,
		'maptype=s'		=> \$opt_maptype,
		'maxgif=i'		=> \$opt_maxgif,
		'msg_copyright=s'       => \$opt_msg_copyright,
		'msg_date_format=s'     => \$opt_msg_date_format,
		'msg_directories=s'     => \$opt_msg_directories,
		'msg_directory_navigator=s' => \$opt_msg_directory_navigator,
		'msg_images=s'          => \$opt_msg_images,
		'msg_index_of_directory=s' => \$opt_msg_index_of_directory,
		'msg_index_of_files=s'  => \$opt_msg_index_of_files,
		'msg_index_through=s'   => \$opt_msg_index_through,
		'msg_next=s'            => \$opt_msg_next,
		'msg_page_navigator=s'  => \$opt_msg_page_navigator,
		'msg_page_updated_on=s' => \$opt_msg_page_updated_on,
		'msg_prev=s'            => \$opt_msg_prev,
		'msg_produced_by=s'     => \$opt_msg_produced_by,
		'msg_readme=s'          => \$opt_msg_readme,
		'msg_up=s'              => \$opt_msg_up,
		'pageindexname=s'	=> \$opt_pageindexname,
		'pichtml!'              => \$opt_pichtml,
		'pichtmlbottom=s'       => \$opt_pichtmlbottom,
		'pichtmlext=s'          => \$opt_pichtmlext,
		'pichtmlnav!'           => \$opt_pichtmlnav,
		'pichtmlputtitle!'      => \$opt_pichtmlputtitle,
		'pichtmltarget=s'       => \$opt_pichtmltarget,
		'pichtmltitleend=s'     => \$opt_pichtmltitleend,
		'pichtmltitlestart=s'   => \$opt_pichtmltitlestart,
		'pichtmltop=s'          => \$opt_pichtmltop,
		'prefixpath=s'		=> \$opt_prefixpath,
		'readme=s'		=> \$opt_readme,
		'readmevisible!'	=> \$opt_readmevisible,
		'recurse!'		=> \$opt_recurse,
		'rootpath=s'		=> \$opt_rootpath,
		'rows=i'		=> \$opt_rows,
		'serversidemap!'        => \$opt_serversidemap,
		'srcdir=s'		=> \$opt_srcdir,
		'stylesheet=s'		=> \$opt_stylesheet,
		'tables!'		=> \$opt_tables,
		'thumbtexture=s'	=> \$opt_thumbtexture,
		'thumbbackground=s'	=> \$opt_thumbbackground,
		'thumbborderwidth=i'	=> \$opt_thumbborderwidth,
		'thumbframebgcolor=s'	=> \$opt_thumbframebgcolor,
		'thumbcompose=s'	=> \$opt_thumbcompose,
		'thumbfont=s'		=> \$opt_thumbfont,
		'thumbforeground=s'	=> \$opt_thumbforeground,
		'thumbframe=s'		=> \$opt_thumbframe,
		'thumbgeometry=s'	=> \$opt_thumbgeometry,
		'thumbgravity=s'	=> \$opt_thumbgravity,
		'thumblabel=s'		=> \$opt_thumblabel,
		'thumblabelwidth=s'	=> \$opt_thumblabelwidth,
		'thumbshadow=s'		=> \$opt_thumbshadow,
		'thumbtransparent=s'	=> \$opt_thumbtransparent,
		'title=s'		=> \$opt_title,
		'verbose!'		=> \$opt_verbose,
		'version!'		=> \$opt_version,
		'zoomfilter=s'		=> \$opt_zoomfilter
		)
    ) {
    help();
    exit(0);
}

#
# Print help message
#
if( $opt_help ) {
    help();
    exit(0);
}

#
# Print version message
#
if( $opt_version ) {
    version();
    exit(0);
}

#
# Check if source directory is valid
#
if ( ! -d "${opt_srcdir}" ) {
    print( STDERR "No ${opt_srcdir} directory\n" );
    exit(1);
}


#
# Open X11 RGB database
#
if ( -f $opt_rgbdb ) {
    open( RGBDB, "<$opt_rgbdb" )
	|| die("Unable to open RGB database $opt_rgbdb");
    while( <RGBDB> ) {
	m/^!/ && next;
        my ( $red, $green, $blue, $color );
        chop;
        s/^[ \t]+//; # Remove any preceding spaces
        ($red, $green, $blue, $color) = split( /[ \t]+/, $_, 4);
        $RGBDB{"\L$color"} = sprintf("#%02X%02X%02X", $red, $green, $blue);
    }
    close( RGBDB );
} else {
    print( STDERR "Warning: X11 RGB database \'$opt_rgbdb\' not found\n" );
}

#
# Minimal 16-color named set as defined by HTML 3.2
#
$RGBDB{'black'}	  = '#000000';
$RGBDB{'green'}   = '#008000';
$RGBDB{'silver'}  = '#C0C0C0';
$RGBDB{'lime'}    = '#00FF00';
$RGBDB{'gray'}    = '#808080';
$RGBDB{'olive'}   = '#808000';
$RGBDB{'white'}   = '#FFFFFF';
$RGBDB{'yellow'}  = '#FFFF00';
$RGBDB{'maroon'}  = '#800000';
$RGBDB{'navy'}    = '#000080';
$RGBDB{'red'}     = '#FF0000';
$RGBDB{'blue'}    = '#0000FF';
$RGBDB{'purple'}  = '#800080';
$RGBDB{'teal'}    = '#008080';
$RGBDB{'fuchsia'} = '#FF00FF';
$RGBDB{'aqua'}    = '#00FFFF';

$currentDate = strftime ($opt_msg_date_format, localtime);

#
# Translate paths to physical paths (avoid symlink problems)
#
$opt_srcdir	= lets_get_physical( $opt_srcdir );
print( "Physical srcdir : $opt_srcdir\n" ) if $opt_debug;
$opt_rootpath 	= lets_get_physical( $opt_rootpath );
print( "Physical rootpath : $opt_rootpath\n" ) if $opt_debug;
$icon_dir_path  = "${opt_rootpath}$pathSep${opt_iconpath}";
print( "Physical icondir : $icon_dir_path\n" ) if $opt_debug;



# html_imgsize.  Obtain image size and return HTML text (HEIGHT=foo
# WIDTH=bar) representing size.

sub html_imgsize {
    my ($file) = @_;
    use Image::Magick;

    my ( 
	$image,
	$height,
	$width,
	@ping,
	$retval,
	$status
	);

    $retval = '';
    if( -f $file ) {
	$image = Image::Magick->new;
	# Try PerlMagick 1.15 Ping method first
	my $newfile = $file;
	$newfile =~ s/\\/\\\\/g;
	eval qq{\@ping = \$image->Ping("$newfile");};
	if ( !$@ ) {
	
		if ( $ping[0] =~ /,/ ) {
			@ping = split (',',$ping[0]);
		}
	    $width = $ping[0];
	    $height = $ping[1];
	} else {
	    # If Ping fails to eval, do things the hard way
	    $status = $image->Read($file);
	    if( "$status" ) {
		undef $image;
		return( $retval );
	    }
	    ($width, $height) = $image->[0]->Get("columns", "rows");
	}
        print( STDERR "Image \"$file\" has dimensions ",
	      "${width}x${height}\n") if $opt_debug;
	$retval="HEIGHT=${height} WIDTH=${width}";
	undef @$image;	# Free up space
	undef $image;	# Ditto
    } else {
        print( STDERR "html_imgsize: no such file \"$file\"\n" );
    }
    return( $retval );
}


if( $opt_recurse && $opt_frames ) {
    # Recurse depth-first under current directory, executing &wanted
    # for each directory ignoring hidden directories
    use File::Find;
    print( "Processing directory tree $opt_srcdir ...\n" ) if $opt_debug;
    find( \&wanted, $opt_srcdir );
} else {
    print( "Processing directory $opt_srcdir ...\n" ) if $opt_debug;
    $sourceDirectory = $opt_srcdir;
    dodir( $sourceDirectory );
}

#
# Print run times if running in verbose mode
#
if( $opt_verbose ) {
    my(
       $user,          # CPU time in user code for this process
       $system,        # CPU time in system code on behalf of this process
       $cuser,         # CPU time in user code of child processes
       $csystem,       # CPU time in system code on behalf of child processes
       $total_user,    # Total user time (parent + children)
       $total_system,  # Total system time (parent + children)
       $total_time     # Total elapsed time (wall clock)
       );
    my(
       $user_m,        # User time in minutes
       $system_m,      # System time in minutes
       $cuser_m,       # Child process user time in minutes
       $csystem_m,     # Child process system time in minutes
       $total_user_m,  # Total user time in minutes
       $total_system_m,# Total system time in minutes
       $total_time_m   # Total elapsed time (wall clock) in minutes
       );
    ($user, $system, $cuser, $csystem) = times;
    $user_m		= elapsedminutes( $user );
    $system_m		= elapsedminutes( $system );
    $cuser_m		= elapsedminutes( $cuser );
    $csystem_m		= elapsedminutes( $csystem );
    $total_user		= $user + $cuser;		# Total user time
    $total_user_m	= elapsedminutes( $total_user );
    $total_system	= $system + $csystem;		# Total system time
    $total_system_m	= elapsedminutes( $total_system );
    $total_time		= time - $startTime;		# Total run time
    $total_time_m	= elapsedminutes( $total_time );
    print( STDERR "Run time statistics:\n" );
    print( STDERR "Detailed times: ${user_m} user, ${system_m} system,",
	  " ${cuser_m} child_user, ${csystem_m} child_system\n" );
    print( STDERR "Summary times : ${total_time_m} total,",
	  " ${total_user_m} user, ${total_system_m} system\n" );
}
print( STDERR "Doing normal exit with code 0\n" ) if $opt_debug; 
exit(0);

#####################
#####################

# Executed for each find operation 
# Want:
# is directory
# not hidden directory
#
# Variables:
#  $File::Find::dir	current directory
#  $File::Find::name	current directory + file name
#  $File::Find::prune	set to true to prune tree
sub wanted {
    my($dev,$ino,$mode,$nlink,$uid,$gid);
    ($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_);
    if ( -d _ && -w _ && !/^\..+/ ) {
        if( $_ ne '.' && get_rc_var('.', 'opt_prune', 0) ) {
            $File::Find::prune=1;
	    # following funny logic is to avoid warnings about $prune
            print( STDERR "Pruning    $File::Find::name\n" ) if $File::Find::prune==1;
            return;
        } 
        #ignore special FrontPage directories (like /_vti_bin)
        # lowercase names only, so _90-95 or _Covers is OK
        if ( $opt_ignorefp && $File::Find::name =~ /\/_[a-z]/ )
        {            
            return;
        }
        forking_dodir($File::Find::name);
    }
}


# Execute dodir with the protection of a fork.  This ensures that
# current directory and global webmagick configuration values are
# preserved between directories.

sub forking_dodir {
    my $sourceDirectory = shift(@_);     # Directory to process
    my $inChild;		# Set to one of we are child
    my ($waitpid,            	# PID returned by wait
	$childstat,          	# Status returned from child
	$pid,			# Child process PID
	$waitpid,		# PID waited on
	$childstat		# Child return status
	);
	
FORK:
    {
	if( $pid = fork ) {
	    # parent here
	    # child process pid is available in $pid
	    $inChild=0;
	    $waitpid=wait();
	    $childstat=$?;
	    # If clean exit, then return 0
	    return 0 if ( $childstat == 0 );
	    syserror( $childstat ); # Print status from error
	    return 1;
	} elsif ( defined $pid ) {  # $pid is zero here if defined
	    # child here
	    # parent process pid is available with getppid
	    $inChild=1;
	    dodir( $sourceDirectory );
	    exit( 0 );
	} elsif ( $! =~ /No more process/ ) {
	    # EAGAIN, supposedly recoverable fork error
	    sleep 5;
	    redo FORK;
	} else {
	    # weird fork error
	    die( "Can't fork: $!\n$@\n" );
	}
    }
    return(0);			# Should never get here!
}

#
# Generate index files for directory specified by $sourceDirectory
#
sub dodir {
    $sourceDirectory = shift(@_);
    my $maxfiles = 0;		# Maximum number of index links per page
    $numPages = 0;		# Number of index pages to be generated
    my $numimages = 0;		# Number of images in directory
    my $numdirectories = 0;	# Number of subdirectories in directory
    $pageNumber = 1;		# Current index page number (1 to N)
    $haveReadme = 0;		# Is there a readme file in this directory?
    my $doIndexHtml=0;		# Set to true if HTML index files must be generated
    my $tmpstr;			# Scratch string
    my @tmplst;			# Scratch list

    #
    # Regular expression to match supported file extensions
    #
    my $includeRegex='(\.avs$)|(\.bie$)|(\.bmp$)|(\.cgm$)|(\.dcm$)|(\.dcx$)' .
        '|(\.dib$)|(\.dot$)|(\.dvi$)|(\.epdf$)|(\.epi$)|(\.eps$)' .
	'|(\.eps2$)|(\.epsf$)|(\.epsi$)|(\.ept$)|(\.fax$)|(\.fig$)' .
        '|(\.fits$)|(\.fpx$)|(\.g3$)|(\.gif$)|(\.gplt$)|(\.hdf$)' .
        '|(\.hpgl$)|(\.ico$)|(\.im1$)|(\.im24$)|(\.im8$)|(\.jbg$)' .
        '|(\.jbig$)|(\.jpeg$)|(\.jpg$)|(\.m2v$)|(\.man$)|(\.mif$)' .
        '|(\.miff$)|(\.mng$)|(\.mpeg$)|(\.mpg$)|(\.mtv$)|(\.p7$)' .
        '|(\.pbm$)|(\.pcd$)|(\.pcds$)|(\.pcx$)|(\.pdf$)|(\.pgm$)' .
        '|(\.pic$)|(\.pict$)|(\.pix$)|(\.png$)|(\.pnm$)|(\.pov$)' .
        '|(\.ppm$)|(\.ps$)|(\.psd$)|(\.rad$)|(\.rla$)|(\.rle$)' .
        '|(\.sgi$)|(\.sun$)|(\.tga$)|(\.tif$)|(\.tiff$)|(\.tim$)' .
        '|(\.ttf$)|(\.vicar$)|(\.viff$)|(\.wmf$)|(\.xbm$)|(\.xpm$)' .
        '|(\.xwd$)';


    #
    # Regular expression for file and directory names we ignore
    #
    my $excludeRegex = '';
    $excludeRegex .= "(^$opt_indexname)|"
	if ("$opt_indexname" ne '');
    $excludeRegex .= "(\\.s?html\$)|(\\.s?htm\$)|(\\.sht\$)|(^\\.)";

    #
    # Change current directory to $sourceDirectory
    #
    chdir( $sourceDirectory )
	|| die("Can't cd to $sourceDirectory\n$@\n");
    
    # Get current (absolute) directory
    $currentDirectory=cwd();

    # Eval per-directory rc files if they exist.  Rc files are
    # evaluated for each directory starting from $opt_rootdir until
    # the current directory is reached. This supports "additive"
    # behavior for a branch in the tree.

    evaluateRcFiles();


    # Decide if we want to process this directory or not based on the
    # value of $opt_ignore.  If not, then just return.

    if( $opt_ignore ) {
        # Skip this directory
        print( STDERR "Skipping   $sourceDirectory\n" );
        return( 0 );
    } else {
        print( STDERR "Processing $sourceDirectory\n" );
    }
    
    # removed until I can find a way to do this
    # that doesn't leave lockfiles on unsuccessful dodir attempts
    #if (-e $opt_lockfile)
    #{
    # 	# Skip this directory because it's currently being processed
    #    print( STDERR "Skipping   $sourceDirectory\n" );
    #    return( 0 );
    #} else {
    #	open (LOCKFILE, ">$opt_lockfile");
    #	close LOCKFILE;
    #}	


    $currentDate = strftime ($opt_msg_date_format, localtime);

    # Translate paths to physical paths (avoid symlink problems)
    # Note: This duplicates code executed during initialization

    $opt_srcdir		= lets_get_physical( $opt_srcdir );
    $opt_rootpath 	= lets_get_physical( $opt_rootpath );
    $icon_dir_path	= "${opt_rootpath}${pathSep}${opt_iconpath}";

    #
    # Set frame targets & frame naming
    #
    if( $opt_framefmt_frames{$opt_framestyle} == 2 ) {
	# Two frame style & naming
	$frameTargets{'default_dirview'}=$opt_frame_name_top;	# Dir nav reloads entirely by default
	$frameTargets{'default_thumbview'}=$opt_frame_name_top;	# Thumbnail nav reloads entirely by default
	$frameTargets{'dirview'}=$opt_frame_name_dirview;	# Directory list displays in dirview frame
	$frameTargets{'readmeview'}=$opt_frame_name_imageview;	# Readme displays in imagemap frame
	$frameTargets{'imageview'}=$opt_frame_name_imageview;	# Images display in imagemap frame
	$frameTargets{'thumbview'}=$opt_frame_name_imageview;	# Imagemap displays in imagemap frame
    } elsif ( $opt_framefmt_frames{$opt_framestyle} == 3 ) {
	# Three frame style & naming
	$frameTargets{'default_dirview'}=$opt_frame_name_top;	# Dir nav reloads entirely by default
	$frameTargets{'default_thumbview'}=$opt_frame_name_top;	# Thumbnail nav reloads entirely by default
	$frameTargets{'dirview'}=$opt_frame_name_dirview;	# Directory list displays in dirview frame
	$frameTargets{'readmeview'}=$opt_frame_name_imageview;	# Readme displays in imageview frame
	$frameTargets{'imageview'}=$opt_frame_name_imageview;	# Images display in imageview frame
	$frameTargets{'thumbview'}=$opt_frame_name_thumbview;	# Imagemap displays in imagemap frame
    } else {
	die("Unsupported number of frames: $opt_framefmt_frames{$opt_framestyle}\n" );
    }

    #
    # Ensure that certain options have correct case
    #
    $opt_cacheformat = "\U${opt_cacheformat}";

    #
    # Default directory frame colors to page colors if not set
    #
    if ($opt_frames) {
	$opt_dircolorback   = $opt_colorback  if ("$opt_dircolorback" eq '');
	$opt_dircolorfore   = $opt_colorfore  if ("$opt_dircolorfore" eq '');
	$opt_dircoloralink  = $opt_coloralink if ("$opt_dircoloralink" eq '');
	$opt_dircolorlink   = $opt_colorlink  if ("$opt_dircolorlink" eq '');
	$opt_dircolorvlink  = $opt_colorvlink if ("$opt_dircolorvlink" eq '');
    }

    #
    # Convert all HTML colors to hex format
    #
    $opt_colorback	= lookupRGBColor( $opt_colorback );
    $opt_colorfore	= lookupRGBColor( $opt_colorfore );
    $opt_coloralink	= lookupRGBColor( $opt_coloralink );
    $opt_colorlink	= lookupRGBColor( $opt_colorlink );
    $opt_colorvlink	= lookupRGBColor( $opt_colorvlink );
    if ($opt_frames) {
	$opt_dircolorback   = lookupRGBColor( $opt_dircolorback );
	$opt_dircolorfore   = lookupRGBColor( $opt_dircolorfore );
	$opt_dircoloralink  = lookupRGBColor( $opt_dircoloralink );
	$opt_dircolorlink   = lookupRGBColor( $opt_dircolorlink );
	$opt_dircolorvlink  = lookupRGBColor( $opt_dircolorvlink );
    }

    # Calculate the maximum number of images per index page

    $maxfiles=$opt_columns*$opt_rows;


    # Convert renamed WebMagick options to new names for backward
    # compatability

    if( defined( $opt_thumbbordercolor ) ) {
	$opt_thumbframebgcolor = $opt_thumbbordercolor;
    }

    if( defined( $opt_thumbmattecolor ) ) {
	$opt_thumbframecolor = $opt_thumbmattecolor;
    }

    #
    # Ensure that maptype is lower case
    # 
    $opt_maptype = "\L${opt_maptype}";

    if ($opt_iconbase) {
	if ($opt_iconbase =~ !/^\//) {
	    die("$0: Iconbase must start with '/', use prefixpath to make it non-root related\n");
	} else {
	    $icon_base_url = "${opt_prefixpath}${opt_iconbase}";
	}
    } else {
	#
	# Compute URL to top of icon directory
	#
	# Attempt to convert to relative URL
	$icon_base_url = relative( $currentDirectory, $icon_dir_path);
	if ( $icon_base_url =~ m\(^/)|(:)\ ) {
	    # If did not convert to relative URL then convert to absolute URL
	    $icon_base_url = escapeurl( abs_path_to_url($icon_dir_path));
	}
    }

    #
    # Source directory status file from last run
    #
    setFileNames(); 		# Set current file names
    $pageStatusTime = 0;
    if ( -f $fileNames{'pageStatus'} ) {
	sourceRcFile($fileNames{'pageStatus'}) ;
	# Obtain last modified date for status file
	$pageStatusTime=fmtime($fileNames{'pageStatus'});
    }


    # Handle a directory name to title index file.  Store alternative
    # names in %dirTitles.
    {
	my $pos = 0;
	my %tmp_dirTitles;
	undef( %tmp_dirTitles );
	if ( $opt_frames && -f $opt_dirindexname ) {
	    open( DIRINDEX, "<$opt_dirindexname" );
	    while( <DIRINDEX> ) {
		chop;
		my($dirname, $dirtitle);
		( $dirname, $dirtitle) = split( /[ \t]+/, $_, 2);
		if(defined($dirname) && defined($dirtitle)) {
		    $tmp_dirTitles{$dirname} = escapehtml($dirtitle);
		    $dirOrder{$dirname} = $pos;
		    $pos++;
		}
	    }
	    close( DIRINDEX );
	}

	if( !defined(%dirTitles) || (  compareHash(\%dirTitles,\%tmp_dirTitles) ) ) {
	    %dirTitles = %tmp_dirTitles;
	    print( STDERR "Directory titles have changed, must re-do HTML indexes\n" )
		if $opt_debug;
	    ++$doIndexHtml;
	}	
    }

    #
    # Handle a image name to thumbnail label index file
    # Store alternative names in %imageLabels
    #
    {
	my $pos = 0;
	my %tmp_imageLabels;
	if ( -f $opt_imgindexname ) {
	    open( IMGINDEX, "<$opt_imgindexname" );
	    while( <IMGINDEX> ) {
		chop;
		my ( $imgname, $imgtitle);
		( $imgname, $imgtitle) = split( /[ \t]+/, $_, 2);
		if(defined($imgname) && defined($imgtitle)) {
		    $tmp_imageLabels{$imgname} = $imgtitle;
		    $imageOrder{$imgname} = $pos;
		    $pos++;
		}
	    }
	    close( IMGINDEX );
	}
	#
	# This code is totally BOGUS since decision should be made on a per-page basis!!
	#	
	if( !defined(%imageLabels) || (  compareHash(\%imageLabels,\%tmp_imageLabels) ) ) {
	    %imageLabels = %tmp_imageLabels;
	    print( STDERR "Image labels have changed, must re-do montages\n" )
		if $opt_debug;
	    $opt_forcemontage=1;
	}
    }

    #
    # Compute individual icon URLs.
    #
    {
	my %tmp_iconImageUrls;
	print( "Icon URLs:\n" )  if $opt_debug;
	my $icon;
	for $icon ( keys %opt_icons ) {
	    if( $opt_icons{$icon} ) { # if defined
		$tmp_iconImageUrls{$icon}  =  "$icon_base_url" . '/' . $opt_icons{$icon};
		printf( " \$tmp_iconImageUrls%-20s = \"%s\"\n", "{'$icon'}", $tmp_iconImageUrls{$icon} )
		    if $opt_debug;
	    }
	}
	if( !defined(%iconImageUrls) || (  compareHash(\%iconImageUrls,\%tmp_iconImageUrls) ) ) {
	    %iconImageUrls = %tmp_iconImageUrls;
	    print( STDERR "Icon URLs have changed, must re-do all HTML\n" )
		if $opt_debug;
	    $opt_forcehtml = 1;;
	}
    }
    

    #
    # Get icon image sizes
    #
    getIconImageSizes();

    #  Read source file names (if any).  Filter out any names matching
    #  the exclude list.

    opendir( SRCDIR, ".")
        || die("$0: Failed to open directory $sourceDirectory\n$@\n");
    @allFiles = grep(!/$excludeRegex/io,readdir( SRCDIR ));
    closedir( SRCDIR );

    #
    # Build list of image files
    #
    @allImageFiles = sort( sortImages grep( /$includeRegex/io, @allFiles));

    # Find subdirectory names (if any) ignoring hidden directories and
    # directories without index files. Directories should have index
    # files since our find goes from the bottom up and we should have
    # already processed the subdirectories.
    #
    # Only test files that are not in the allImageFiles list
    {
        my %tarray;
	my @dirfiles;
	my @tmp_dirNames = ();
	
	if ($opt_frames) {
	    grep($tarray{$_}++, @allImageFiles);
	    foreach $_ ( grep(! $tarray{$_},@allFiles) ) {
		#if( -f "${_}/${opt_indexname}" ) { # If directory & index file
						    #  exists then add to list
		if( -d "${_}" ) {                   # If directory exists
		    unless ($opt_ignorefp && /^_[a-z]/) { #  and we shouldn't ignore it
			    push(@dirfiles, $_);            #  then add it to the list
			    if ($opt_javascript) {
				# Check which index file to use for JavaScript
				if( -f "$_/$fileNames{'jsDirIndex'}" ) {
				    $dirJsLink{"$_"} = $fileNames{'jsDirIndex'};
				} elsif ( -f "$_/$opt_indexname" ){
				    $dirJsLink{"$_"} = $opt_indexname;
				} else {
				    $dirJsLink{"$_"} = '';
				}
			    }
		    }
		}
	    }
	    @tmp_dirNames = sort( sortDir @dirfiles);
	}
	if( "@dirNames" ne "@tmp_dirNames" ) {
	    @dirNames = @tmp_dirNames;
	    if ($opt_frames) {
		print( STDERR "Subdirectories have changed, must re-do HTML indexes\n" )
		    if $opt_debug;
		++$doIndexHtml;
	    }
	}
	if ($opt_javascript) {
	    # Check which parent-directory index file to use for JavaScript
	    if( -f "../$fileNames{'jsDirIndex'}" ) {
		$dirJsLink{".."} = $fileNames{'jsDirIndex'};
	    } elsif ( -f "../$opt_indexname" ){
		$dirJsLink{".."} = $opt_indexname;
	    } else {
		$dirJsLink{".."} = '';
	    }
	}
    }

    #
    # Determine the number of index pages to be generated, etc.
    #
    $numimages=scalar(@allImageFiles);		# Number of images
    $numdirectories=scalar(@dirNames); 		# Number of subdirectories
    $numPages=int($numimages/$maxfiles);
    if ( $numimages%$maxfiles != 0 ) {
        ++$numPages;
    }

    #
    # Check for README file and set haveReadme flag if exists
    #
    if("${opt_readme}" ne '' && -f "${opt_readme}") {
    	$haveReadme = 1;
    }

    # Set haveImages flag if there are images in directory. This
    # effects the way the directory listing appears.

    $haveImages = 0;
    if( $numimages > 0 ) {
    	$haveImages = 1;
    }


    
    #
    # Determine page title
    #
    if( "$opt_title" ne '' ) {
        $pageTitle = $opt_title;
    } else {
        my $dirname=basename($sourceDirectory);
	$pageTitle = "${opt_msg_index_of_directory} \"$dirname\"";
    }


    #
    # Put the PerlMagick Montage options together
    #
    $montageArguments =  ''; #"\n ";

    $montageArguments .= "background=>\'$opt_thumbbackground\',\n "
	if $opt_thumbbackground ne 'false';

    $montageArguments .= "borderwidth=>\'$opt_thumbborderwidth\',\n ";

    $montageArguments .= "compose=>\'$opt_thumbcompose\',\n "
	if $opt_thumbcompose ne 'false';

    $montageArguments .= "font=>\'$opt_thumbfont\',\n "
	if $opt_thumbfont ne 'false';

    $montageArguments .= "foreground=>\'$opt_thumbforeground\',\n "
	if $opt_thumbforeground ne 'false';

    $montageArguments .= "frame=>\'$opt_thumbframe\',\n "
	if $opt_thumbframe ne 'false';

    $montageArguments .= "geometry=>\'${opt_thumbgeometry}\',\n "
	if $opt_thumbgeometry ne 'false';

    $montageArguments .= "gravity=>\'$opt_thumbgravity\',\n "
	if $opt_thumbgravity ne 'false';

    $montageArguments .= "shadow=>\'$opt_thumbshadow\',\n "
	if $opt_thumbshadow ne 'false';

    $montageArguments .= "texture=>\'$opt_thumbtexture\',\n "
	if $opt_thumbtexture ne 'false';

    $montageArguments .= "tile=>\'${opt_columns}x${opt_rows}\',\n ";

    $montageArguments .= "transparent=>\'$opt_thumbtransparent\'\n "
	if $opt_thumbtransparent ne 'false';



    # Compute a conglomeration of all parameters that effect the
    # montage image to use for comparison with the parameters used in
    # the last run.
    {
	my $tmp_montageParameters = "$montageArguments";
	$tmp_montageParameters .= "bordercolor=>\'$opt_thumbframebgcolor\',\n "
	    if $opt_thumbframebgcolor ne 'false';
	$tmp_montageParameters .= "mapnetscape=>\'$opt_mapnetscape\',\n ";
	$tmp_montageParameters .= "mattecolor=>\'$opt_thumbframecolor\',\n "
	    if $opt_thumbframecolor ne 'false';
	$tmp_montageParameters .= "label=>\'$opt_thumblabel\',\n "
	    if $opt_thumblabel ne 'false';
	$tmp_montageParameters .= "labelwidth=>\'$opt_thumblabelwidth\',\n ";
	$tmp_montageParameters .= "\Lzoomfilter=>\'$opt_zoomfilter\'";
	if( !defined($montageParameters) || ($tmp_montageParameters ne $montageParameters)) {
	    $montageParameters = $tmp_montageParameters;
	    print( STDERR "Montage parameters have changed, must re-do montages\n" )
		if $opt_debug;
	    $opt_forcemontage=1;
	}
    }

    #
    # Store HTML options in associative array to make them easier to deal with
    #
    %htmlOpts =
	(
	 'address'		=> $opt_address,	 
	 'anonymous'		=> $opt_anonymous,
	 'backgroundimg'	=> $opt_icons{'background'},
	 'cachedir'             => $opt_cachedir,
	 'coloralink'		=> $opt_coloralink,
	 'colorback'		=> $opt_colorback,
	 'colorfore'		=> $opt_colorfore,
	 'colorlink'		=> $opt_colorlink,
	 'colorvlink'		=> $opt_colorvlink,
	 'columns'              => $opt_columns,
	 'config'               => !$opt_javascript ? 0 : $opt_allowconfig,
	 'dateText'             => !$opt_date ? "" : "${opt_msg_page_updated_on} $currentDate",
	 'dirbackgroundimg'     => !$opt_frames ? "" : $opt_icons{'dirbackground'},
	 'dircoloralink'        => !$opt_frames ? "" : $opt_dircoloralink,
	 'dircolorback'         => !$opt_frames ? "" : $opt_dircolorback,
	 'dircolorfore'         => !$opt_frames ? "" : $opt_dircolorfore,
	 'dircolorlink'         => !$opt_frames ? "" : $opt_dircolorlink,
	 'dircolorvlink'        => !$opt_frames ? "" : $opt_dircolorvlink,
	 'dirfmt'               => !$opt_frames ? "" : $opt_dirfmt,
	 'dirhtmlext'           => !$opt_frames ? "" : $opt_dirhtmlext,
	 'dirindexname'         => !$opt_frames ? "" : $opt_dirindexname,
	 'footer'               => $opt_footer,
	 'frameddirfmt'         => !$opt_frames ? "" : $opt_frameddirfmt,
	 'framemarginwidth'     => !$opt_frames ? 0 : $opt_framemarginwidth,
	 'framemarginheight'    => !$opt_frames ? 0 : $opt_framemarginheight,
	 'framebordersize'      => !$opt_frames ? 0 : $opt_framebordersize,
	 'frameborder'          => !$opt_frames ? "" : $opt_frameborder,
	 'frames'               => $opt_frames,
	 'framestyle'           => !$opt_frames ? 1 : $opt_framestyle,
	 'header'		=> $opt_header,
	 'htimage'              => !$opt_serversidemap ? "" : $opt_htimage,
	 'htmlext'              => !$opt_frames ? "" : $opt_htmlext,
	 'iconbase'             => $opt_iconbase,
	 'imgindexname'		=> $opt_imgindexname,
	 'indexname'            => $opt_indexname,
	 'javascript'           => $opt_javascript,
	 'jsdirindex'           => !$opt_javascript ? "" : $fileNames{'jsDirIndex'},
	 'jsfunctions'          => !$opt_javascript ? "" : $fileNames{'jsFunctions'},
	 'jspageindex'          => !$opt_javascript ? "" : $fileNames{'jsPageIndex'},
	 'jsvariables'          => !$opt_javascript ? "" : $fileNames{'jsVariables'},
	 'metaauthor'		=> $opt_metaauthor,
	 'metacharset'          => $opt_metacharset,
	 'metaclassification'	=> $opt_metaclassification,
	 'metadescription'	=> $opt_metadescription,
	 'metaexpires'		=> $opt_metaexpires,
	 'metakeywords'		=> $opt_metakeywords,
	 'msg_copyright'        => $opt_anonymous ? "" : $opt_msg_copyright,
	 'msg_directories'      => !$opt_frames ? "" : $opt_msg_directories,
	 'msg_directory_navigator' => !$opt_frames ? "" : $opt_msg_directory_navigator,
	 'msg_images'           => !$opt_frames ? "" : $opt_msg_images,
	 'msg_index_of_directory' => $opt_msg_index_of_directory,
	 'msg_index_of_files'   => $opt_msg_index_of_files,
	 'msg_index_through'    => $opt_msg_index_through,
	 'msg_next'             => $opt_msg_next,
	 'msg_page_navigator'   => !$opt_frames ? "" : $opt_msg_page_navigator,
	 'msg_prev'             => $opt_msg_prev,
	 'msg_produced_by'      => $opt_anonymous ? "" : $opt_msg_produced_by,
	 'msg_readme'           => !$haveReadme ? "" : $opt_msg_readme,
	 'msg_up'               => $opt_msg_up,
	 'numpages'		=> $numPages,
	 'pageindexname'        => $opt_pageindexname,
	 'pichtml'              => $opt_pichtml,
	 'pichtmlbottom'        => !$opt_pichtml ? "" : $opt_pichtmlbottom,
	 'pichtmlext'           => !$opt_pichtml ? "" : $opt_pichtmlext,
	 'pichtmlnav'           => !$opt_pichtml ? 0 : $opt_pichtmlnav,
	 'pichtmlputtitle'      => !$opt_pichtml ? 0 : $opt_pichtmlputtitle,
	 'pichtmltarget'        => (!$opt_pichtml || !$opt_frames) ? "" : $opt_pichtmltarget,
	 'pichtmltitleend'      => !$opt_pichtml ? "" : $opt_pichtmltitleend,
	 'pichtmltitlestart'    => !$opt_pichtml ? "" : $opt_pichtmltitlestart,
	 'pichtmltop'           => !$opt_pichtml ? "" : $opt_pichtmltop,
	 'prefixpath'		=> $opt_prefixpath,
	 'readme'               => !$haveReadme ? "" : $opt_readme,
	 'readmepresent'	=> $haveReadme,
	 'readmevisible'        => !$haveReadme ? 0 : $opt_readmevisible,
	 'rows'                 => $opt_rows,
	 'serversidemap'        => $opt_serversidemap,
	 'stylesheet'		=> $opt_stylesheet,
	 'subdirectories'       => !$opt_frames ? "" : join(' ',@dirNames),
	 'tables'		=> $opt_tables,
	 'title'		=> $pageTitle,
	 'version'		=> $webmagickInfo{'version'},
	 );

    # Compute a conglomeration of all parameters that effect the HTML
    # to use for comparison with the parameters used in the last run.
    {
	my $firstparam = 1;
	my $tmp_htmlParams = '';
	my $key;
	foreach $key (sort(keys(%htmlOpts))) {
	    if(defined($htmlOpts{$key})) {
		if( $firstparam ) {
		    $firstparam = 0;
		} else {
		    $tmp_htmlParams .= ",\n ";
		}
		$tmp_htmlParams .= $key . '=>\'' . $htmlOpts{$key} . '\'';
	    }
	}
	if( !defined($htmlParams) || ($tmp_htmlParams ne $htmlParams)) {
	    $htmlParams = $tmp_htmlParams;
	    print( STDERR "HTML parameters have changed, must re-do HTML\n" )
		if $opt_debug;
	    $opt_forcehtml=1;
	}
    }


    #
    # Print statistics message
    #
    print( STDERR "   $numimages Images $numdirectories Directories",
	  " $numPages Pages --- " )
	if $opt_verbose;

    my @imgfiles = @allImageFiles;	# Save full list of files for later
    
    # Loop through file list, placing a maximum of $maxfiles images on
    # each page.  Do at least one page (there might not be any
    # images).
    while (scalar(@allImageFiles) > 0 || $pageNumber == 1) {
	$doMontage=0;	# Set to true if montage must be generated
	$doPageHtml=0;	# Set to true if per-page HTML files must be generated

        
        print(STDERR " $pageNumber" ) if $opt_verbose;

	#
	# Get per-page image names
	# @imageNames is a list of lists
	{
	    my @tmp_imageNames = splice(@allImageFiles,0,$maxfiles);
	    if( !defined($imageNames[$pageNumber - 1]) ||
	       ("@tmp_imageNames" ne "@{$imageNames[$pageNumber - 1]}") ) {
		$imageNames[$pageNumber - 1] = [ @tmp_imageNames ];
		print( STDERR "Need to do both montage and page HTML because",
		      " file list differs\n") if $opt_debug;
		++$doMontage;
		++$doPageHtml;
	    }
	}
	my $numfiles = $#{$imageNames[$pageNumber - 1]} + 1;

        setFileNames(); 	# Set current file names

	#
	# Decide if we need to do HTML & montage
    #
    
    if ( $perlVarsVersion < $requiredPerlVarsVersion) {
    	print ( STDERR "Need to do montage, HTML and cache because",
    		" version has changed\n") if $opt_debug;
    	$opt_forcehtml = 1;
		$opt_forcemontage = 1;
		$opt_forcecache = 1;
    }


	# Montage specific checks
	# Check for missing output files
	# Check for new input files
        if( $numfiles > 0 ) {
            if( ! $opt_tables && (! -f $fileNames{'pageStatus'} ||
	       ( ! -f $fileNames{'montageGIF'} && ! -f $fileNames{'montageJPEG'} ))) {
                # If key file is missing then do montage
                print(STDERR "\nMust do montage because a required output",
		      " file is missing\n")
		    if $opt_debug;
                ++$doMontage;
            } else {
                # If any file in file list is newer than status file,
                # then do montage
		my $file;
                foreach $file (@{$imageNames[$pageNumber - 1]}) {
                    if( fmtime($file) > $pageStatusTime ) {
                        print( STDERR "Need to do both montage and HTML",
			      " because file has been updated\n")
			    if $opt_debug;
                        ++$doMontage;
                        ++$doPageHtml;
                        print(STDERR "\nMust do montage and html: file",
			      " updated\n") if $opt_debug;
                    }
                }
            }
        }

	die("Empty \$opt_indexname set with \$opt_frames\n$@\n")
	    if ($opt_frames && "$opt_indexname" eq '');

	# HTML specific checks
	# Check for missing files
	if( ! -f $fileNames{'pageStatus'} ||
	    ! -f $fileNames{'htmlCurrentIndex'} ||
	    ($opt_frames && ! -f "$opt_indexname")) {
				# If key file is missing then do HTML
            print(STDERR "\n   Must do page and index HTML: output file",
		  " missing\n") if $opt_debug;
            ++$doPageHtml;
	    ++$doIndexHtml if ($opt_frames);
	}
	
	# Overrides
	if( $opt_forcehtml ) {
	    ++$doPageHtml;
	    ++$doIndexHtml if ($opt_frames);
	}
	if( $opt_forcemontage ) {
	    ++$doPageHtml;		# Montage effects HTML output
	    ++$doMontage;
	}

	my $errorstat = 1;	# Cleared for non-error block exit
PAGES:	{

	    #
	    # Build montage for current page
	    #
	    if( $doMontage && ( $numfiles > 0 ) ) {
		doMontage(@{$imageNames[$pageNumber - 1]}) && last PAGES;

		# Determine image name & size to use for imagemap
		{
		    my $montageImage;
		    my $montageImageSize;
		    if( -f $fileNames{'montageGIF'} ) {
			$montageImage = $fileNames{'montageGIF'};	# Use GIF
			$montageImageSize = html_imgsize($fileNames{'montageGIF'});
		    } elsif ( -f $fileNames{'montageJPEG'} ) {
			$montageImage = $fileNames{'montageJPEG'}; # Use JPEG
			$montageImageSize = html_imgsize($fileNames{'montageJPEG'});
		    }
		    if(!defined($montageImages[$pageNumber - 1]) ||
		       ( $montageImages[$pageNumber - 1] ne $montageImage )) {
			$montageImages[$pageNumber - 1] = $montageImage;
			print( STDERR "Need to re-do page HTML because montage image name ",
			      "has changed\n") if $opt_debug;
			++$doPageHtml;
		    }
		    if(!defined($montageImageSizes[$pageNumber - 1]) ||
		       ( $montageImageSizes[$pageNumber - 1] ne $montageImageSize )) {
			$montageImageSizes[$pageNumber - 1] = $montageImageSize;
			print( STDERR "Need to re-do page HTML because montage image size ",
			      "has changed\n") if $opt_debug;
			++$doPageHtml;
		    }
		}
	    }

	    #
	    # Write out page index files for current page
	    #
	    
	    
	    if( $doPageHtml ) {
		writeIndexFiles();
	    }
	    
	    
	    # Write server-side imagemap file
	    if( $opt_serversidemap && $doPageHtml && ( $numfiles > 0 ) ) {
		writeImageMap();
	    }

            if ( $doPageHtml || $doIndexHtml || $doMontage ) {
		#
		# Save run status (source files and montage options)
		#
		writePerlIndexFiles();
		if( $opt_javascript ) {
		    writeJavaScriptIndexFiles();
		}
            }

	    # Clear error flag
	    $errorstat = 0;
	}
	print( STDERR "Error encountered when creating page\n" )
                if $errorstat;
        ++$pageNumber;	# Next page
    }
   
    print( STDERR "\n" ) if $opt_verbose;
    
    #
    # Clean up old files
    #
    setFileNames();
    while(
	  -f $fileNames{'htmlCurrentIndex'} ||
	  -f $fileNames{'montageGIF'} ||
	  -f $fileNames{'montageJPEG'} ||
	  -f $fileNames{'montageServerMap'}
	  ) {
        unlink(
	       $fileNames{'htmlCurrentIndex'},
	       $fileNames{'montageGIF'},
	       $fileNames{'montageJPEG'},
	       $fileNames{'montageServerMap'}
	       );
        ++$pageNumber;	# Next page
        setFileNames();
    }

    #
    # Clean up cached thumbnails
    #
    if( $opt_cache && -d $opt_cachedir ) {
        opendir( CACHEDIR, "$opt_cachedir")
            || die("$0: Failed to open directory $opt_cachedir\n$@\n");
        @cacheFiles = grep(!/$excludeRegex/io,readdir( CACHEDIR ));
        closedir( CACHEDIR );
    }
    {
        my @extra;
        my %tarray;

        grep( $tarray{$_}++, @imgfiles );
        @extra = grep( $_ = "$opt_cachedir/$_", grep( ! $tarray{$_}, @cacheFiles ));
        print( STDERR "Removing extra cache files @extra\n") if $opt_debug;
        unlink( @extra );
    }

    #
    # Write out index files (Both main index and frames index files)
    #
    if( $doIndexHtml ) {
        writeTopIndexes();
    }
    
    # remove lockfile
    # unlink $opt_lockfile;
}

#
# Write out both top index and frame index files
#
sub writeTopIndexes {

    die("Internal error: writeTopIndexes called when \$opt_frames is not set\n$@\n")
	if (!$opt_frames);

    die("Empty \$opt_indexname set with \$opt_frames\n$@\n")
	if ("$opt_indexname" eq '');

    print( STDERR "Writing Index Files ${opt_indexname} & ",
		"${opt_pageindexname}dir${opt_dirhtmlext} ...\n" )
                if $opt_debug;

    #---- Generate the Variables for Format Options ----
    #
    # Generate HTML for up link
    #
    my $uphtml = '';
    # get indexname of parent directory
    my $upindexname =
	     get_rc_var('..', 'opt_indexname', "$opt_indexname");
    unless ( "$upindexname" eq 'NOLINK' ) {
       $uphtml = "<A HREF=\"../${upindexname}\"><IMG\n" .
	  "SRC=\"$iconImageUrls{'up'}\" $iconImageSizes{'up'} ALT=\"^\" BORDER=0></A>" .
	  " <A HREF=\"../${upindexname}\">${opt_msg_up}</A><BR>\n";
    }

    #
    # Generate HTML for help link
    #
    my $helphtml = '';
    if( $haveReadme ) {
	$helphtml .= "<A HREF=\"" . escapeurl(${opt_readme}) . "\" TARGET=\"$frameTargets{'readmeview'}\"><IMG\n" .
	    "SRC=\"$iconImageUrls{'help'}\" $iconImageSizes{'help'} ALT=\"?\" BORDER=0></A>" .
	    " <A HREF=\"" . escapeurl(${opt_readme}) . "\" TARGET=\"$frameTargets{'readmeview'}\">${opt_msg_readme}</A><BR>\n";
    }

    #
    # Compute HTML for link to first image page
    #
    my $nexthtml = '';
    if( $haveReadme && $haveImages && $opt_framefmt_frames{$opt_framestyle}<=2 ) {
	$nexthtml .= "<A HREF=\"${opt_pageindexname}1$opt_htmlext\" TARGET=\"$frameTargets{'thumbview'}\"><IMG\n" .
	    "SRC=\"$iconImageUrls{'next'}\" $iconImageSizes{'next'} ALT=\"*\" BORDER=0></A>" .
	    " <A HREF=\"${opt_pageindexname}1$opt_htmlext\" TARGET=\"$frameTargets{'thumbview'}\">${opt_msg_images}</A><BR>";
    }

    #
    # Compute HTML for directory list
    #
    my $dirhtml = '';
    if( !$opt_prune && scalar(@dirNames) > 0 ) {
	$dirhtml = "<H3>${opt_msg_directories}</H3>\n";
	my $subdir;
	foreach $subdir (@dirNames) {
	
	    # ignore frontpage directories
	    if ($opt_ignorefp && $subdir =~ /^_[a-z]/)
	    {	    	
	    	next;
	    }	    

	    # If an alternative name is defined, then use it
	    my $dirtitle;
	    if( defined( $dirTitles{$subdir} ) ) {
		$dirtitle=$dirTitles{$subdir};
	    } else {
		$dirtitle=$subdir;
	    }

	    # get indexname for sub-directory (default as this directory)
	    my $subindexname =
		get_rc_var($subdir, 'opt_indexname', "$opt_indexname");

	    unless ( "$subindexname" eq 'NOLINK' ) {
		my $url = escapeurl("$subdir/${subindexname}");
		$dirhtml .= '<NOBR>';
		# If a bullet icon is defined, then use it
		# otherwise use plain text only
		if( defined( $iconImageUrls{'ball'} ) ) {
		    $dirhtml .= "<A HREF=\"${url}\"><IMG SRC=\"$iconImageUrls{'ball'}\" $iconImageSizes{'ball'} ALT=\"*\" BORDER=0></A>";
		}
		$dirhtml .= "<A HREF=\"${url}\">$dirtitle</A></NOBR><BR>\n";
	    } 
	}
    }

    #
    # Generate HTML for page index list
    #
    my $pageindexhtml = '';
    if( $haveImages ) {
	$pageindexhtml = "<H3>${opt_msg_page_navigator}</H3>\n";
	my $i;
        for( $i=1; $i <= $numPages; ++$i ) {
            $pageindexhtml .= 
		"  <A HREF=\"${opt_pageindexname}${i}$opt_htmlext\">${i}</A><BR>\n";
        }
    }

    # ----- Evaluate the Format Options -----
    #
    # Evaluate the Top Index File Format Option
    #
    my $indexhtml;
    $indexhtml = eval '"' . $opt_dirfmt . '"';
    die( "Bad Eval of directory page template (\$opt_dirfmt)\n$@\n" )
       if $@;
    
    # Change header to plain bold text for framed directory file
    $dirhtml =~
	s|^<H3>${opt_msg_directories}</H3>\n|<B>${opt_msg_directories}</B><BR>\n|;
    $pageindexhtml =~
	s|^<H3>${opt_msg_page_navigator}</H3>\n|<B>${opt_msg_page_navigator}</B><BR>\n|;

    #
    # Evaluate the Framed Directory File Format Option
    #
    my ($pagedirhtml, $dirframelink, $pageframelink);
    $pagedirhtml = eval '"' . $opt_frameddirfmt . '"';
    die( "Bad Eval for directory page template (\$opt_frameddirfmt)\n$@\n" )
       if $@; 


    #
    # Pull README into thumbnail frame if it exists, and is
    # either marked always visible or there are no images.
    #
    $dirframelink = "${opt_pageindexname}dir${opt_dirhtmlext}";

    if( $opt_framefmt_frames{$opt_framestyle} <= 2 ) {
	if( $haveReadme && ( $opt_readmevisible || ! $haveImages ) ) {
	    # Point to README.html
	    $pageframelink = $opt_readme;
	} else {
	    # Point to first image page
	    $pageframelink = "${opt_pageindexname}1$opt_htmlext";
	}
    } else {
	if( $haveReadme && $opt_readmevisible ) {
	    # Point to README.html
	    $pageframelink = "${opt_pageindexname}1$opt_htmlext";
	    $imageFrameLink = $opt_readme;
	} else {
	    # Point to first image page
	    $pageframelink = "${opt_pageindexname}1$opt_htmlext";
	    $imageFrameLink = $fileNames{'htmlBlankPage'};
	}
    }

    #
    # Evaluate the Frame Format Option
    #
    my $framespechtml = eval '"' . $opt_framefmt{$opt_framestyle} . '"';
    die( "Bad Eval for Frame template (\$opt_framefmt{$opt_framestyle})\n$@\n" )
       if $@; 

    #
    # Evaluate the JavaScript version of the Frame Format
    #
    # Override normal frame targets
    if ($opt_javascript) {
	$dirframelink = q/javascript:parent.returnDirectoryHTML();/;
	$pageframelink = q/javascript:parent.returnThumbNailsHTML();/;
	$imageFrameLink = q/javascript:parent.returnImageHTML(0);/;
	my $framespecjshtml = eval '"' . $opt_framefmt{$opt_framestyle} . '"';
	die( "Bad Eval for Frame template (\$opt_framefmt{$opt_framestyle})\n$@\n" )
	   if $@;
    }

    # ----- Output Top Index File (usually "index.html") -------
    #
    open( INDEX, ">${opt_indexname}")
	|| die("$0: Failed to open file ${opt_indexname} for output\n$@\n");
    print( INDEX "<HTML>\n<HEAD>\n" );
    # Charset is better before title
    print( INDEX "  <META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=${opt_metacharset}\">\n" )
	if( "$opt_metacharset" ne '' );
    print( INDEX "  <TITLE>${pageTitle}</TITLE>\n" );

    # Meta tags
    print( INDEX "  <META NAME=\"GENERATOR\" CONTENT=\"WebMagick/$webmagickInfo{version} [WebMagick]\">\n" );
    print( INDEX "  <META NAME=\"Author\" CONTENT=\"${opt_metaauthor}\">\n" )
	if( "$opt_metaauthor" ne '' );
    print( INDEX "  <META NAME=\"Description\" CONTENT=\"${opt_metadescription}\">\n" )
	if( "$opt_metadescription" ne '' );
    print( INDEX "  <META NAME=\"Keywords\" CONTENT=\"${opt_metakeywords}\">\n" )
	if( "$opt_metakeywords" ne '' );
    print( INDEX "  <META NAME=\"Classification\" CONTENT=\"${opt_metaclassification}\">\n" )
	if( "$opt_metaclassification" ne '' );
    # The value of $opt_metaexpires should be similar to:
    #   "Tue, 20 Aug 1996 14:25:27 GMT"
    print( INDEX "  <META HTTP-EQUIV=\"Expires\" CONTENT=\"${opt_metaexpires}\">\n" )
	if( "$opt_metaexpires" ne '' );
    print( INDEX "  <LINK REL=\"stylesheet\" type=\"text/css\" HREF=\"${opt_stylesheet}\">\n")
	if ("$opt_stylesheet" ne '');
    
    # If in JavaScript mode, output code to switch to JavaScript pages
    # Currently knows about certain browsers.
    if( $opt_javascript ) {
	print( INDEX '<SCRIPT LANGUAGE="JavaScript1.1">' . "\n" );
	print( INDEX '<!--' . "\n" );
	print( INDEX '  var browserName = navigator.appName;', "\n" );
	print( INDEX '  var browserVersion = parseInt(navigator.appVersion);', "\n" );
	print( INDEX '  var filename = "', $fileNames{'jsDirIndex'}, '";', "\n" );
	print( INDEX '  if ( browserName == "Netscape" && browserVersion >= 3 ) {', "\n" );
	print( INDEX '    location.replace(filename);', "\n" );
	print( INDEX '  } else if ( browserName == "Microsoft Internet Explorer" && browserVersion >= 3) {', "\n" );
	print( INDEX '    location = filename;', "\n" );
	print( INDEX '  }', "\n" );
	print( INDEX "// -->\n" );
	print( INDEX "</SCRIPT>\n" );
    }

    print( INDEX "</HEAD>\n" );

    print( INDEX $framespechtml );
    print( INDEX "<NOFRAMES>\n" );
    print( INDEX "<BODY\n" );
    unless ("$opt_stylesheet" ne '')
    {
	    print( INDEX "  TEXT=\"${opt_colorfore}\"\n" );
	    print( INDEX "  BGCOLOR=\"${opt_colorback}\"\n" )
		if $opt_colorback ne 'false';
	    print( INDEX "  BACKGROUND=\"$iconImageUrls{background}\"\n" )
		if defined $iconImageUrls{background};
	    print( INDEX "  LINK=\"${opt_colorlink}\"\n" );
	    print( INDEX "  VLINK=\"${opt_colorvlink}\"\n" );
	    print( INDEX "  ALINK=\"${opt_coloralink}\"");
    }
    print( INDEX ">\n" );
    print( INDEX $indexhtml );
    print( INDEX "</BODY>\n</NOFRAMES>\n</HTML>" );
    close( INDEX );


    # ----- Output Top JavaScript Index File (usually "indexjs.html") -------
    #
    if( $opt_javascript ) {
    open( INDEX, ">$fileNames{'jsDirIndex'}")
	|| die("$0: Failed to open file $fileNames{'jsDirIndex'} for output\n$@\n");
    print( INDEX "<HTML>\n" );
    print( INDEX "<HEAD>\n" );
    print( INDEX "</HEAD>\n" );
    print( INDEX q/<SCRIPT LANGUAGE="JavaScript" SRC="/ . $fileNames{'jsVariables'} . q!"></SCRIPT>! . "\n" );
    print( INDEX q/<SCRIPT LANGUAGE="JavaScript" SRC="/ . $fileNames{'jsFunctions'}    . q!"></SCRIPT>! . "\n" );
    print( INDEX "<SCRIPT LANGUAGE=\"JavaScript\">\n" );
    print( INDEX q/document.write(returnFrameHTML());/ . "\n" );
    print( INDEX q/document.close();/ . "\n" );
    print( INDEX "</SCRIPT>\n" );
    print( INDEX "</HTML>\n" );
    close( INDEX );
    }


    # ----- Output Frame Directory File (usally ".indexdir.html") ------
    #
    open( INDEX, ">${opt_pageindexname}dir${opt_dirhtmlext}")
	|| die("$0: Failed to open file \"${opt_pageindexname}dir${opt_dirhtmlext}\"",
                " for output\n$@\n");
    print( INDEX "<HTML>\n<HEAD>\n" );
    # Charset is better before title
    print( INDEX "  <META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=${opt_metacharset}\">\n" )
	if( "$opt_metacharset" ne '' );
    print( INDEX "  <TITLE>${pageTitle}</TITLE>\n" );

    # Default URL target
    print( INDEX "  <BASE TARGET=\"$opt_frame_name_top\">\n" );

    # Meta tags
    print( INDEX "  <META NAME=\"GENERATOR\" CONTENT=\"WebMagick/$webmagickInfo{version} [WebMagick]\">\n" );
    print( INDEX "  <META NAME=\"Author\" CONTENT=\"${opt_metaauthor}\">\n" )
	if( "$opt_metaauthor" ne '' );
    print( INDEX "  <META NAME=\"Description\" CONTENT=\"${opt_metadescription}\">\n" )
	if( "$opt_metadescription" ne '' );
    print( INDEX "  <META NAME=\"Keywords\" CONTENT=\"${opt_metakeywords}\">\n" )
	if( "$opt_metakeywords" ne '' );
    print( INDEX "  <META NAME=\"Classification\" CONTENT=\"${opt_metaclassification}\">\n" )
	if( "$opt_metaclassification" ne '' );
    # The value of $opt_metaexpires should be similar to:
    #   "Tue, 20 Aug 1996 14:25:27 GMT"
    print( INDEX "  <META HTTP-EQUIV=\"Expires\" CONTENT=\"${opt_metaexpires}\">\n" )
	if( "$opt_metaexpires" ne '' );
    print( INDEX "  <LINK REL=\"stylesheet\" type=\"text/css\" HREF=\"${opt_stylesheet}\">\n")
	if ("$opt_stylesheet" ne '');
    
    print( INDEX "</HEAD>\n" );
    print( INDEX "<BODY\n" );
    unless ("$opt_stylesheet" ne '')
    {
	    print( INDEX "  TEXT=\"${opt_dircolorfore}\"\n" );
	    print( INDEX "  BGCOLOR=\"${opt_dircolorback}\"\n" )
		if $opt_dircolorback ne 'false';
	    print( INDEX "  BACKGROUND=\"$iconImageUrls{dirbackground}\"\n" )
		if defined $iconImageUrls{dirbackground};
	    print( INDEX "  LINK=\"${opt_dircolorlink}\"\n" );
	    print( INDEX "  VLINK=\"${opt_dircolorvlink}\"\n" );
	    print( INDEX "  ALINK=\"${opt_dircoloralink}\"" );
	}
    print( INDEX ">\n");
    print( INDEX $pagedirhtml );
    print( INDEX "</BODY></HTML>\n" );
    close( INDEX );


    return ( 0 );
}


#
# Write out page index files
#
sub writeIndexFiles {
    my $indexbar;		# HTML text representing numeric selection bar
    my $errorstat = 0;

    print( STDERR "Writing file $fileNames{'htmlCurrentIndex'} ...\n" )
        if $opt_debug;

    my $numimages = $#{$imageNames[$pageNumber - 1]} + 1;
    my $pNumber = ($pageNumber == 1) ? "" : "$pageNumber";
    
    # Calculate page index bar
    # No link for current page
    # Nothing at all when there is only one page.
    $indexbar = "<NOBR>";

    # Must be visible at each page, if no frames
    if (!$opt_frames) {

	# --- readme link ---
	if ( $haveReadme ) {
	    $indexbar .= "<A HREF=\"${opt_readme}\"><IMG\n" .
		"SRC=\"$iconImageUrls{'help'}\" $iconImageSizes{'help'} ALT=\"${opt_msg_readme}\" BORDER=0></A>\n";
	}

	# --- up link ---
	my $upindexname = get_rc_var('..', 'opt_indexname', "$opt_indexname");

	if ( "$upindexname" ne 'NOLINK' ) {
	    $indexbar .= "<A HREF=\"../${upindexname}\"><IMG\n" .
		"SRC=\"$iconImageUrls{'up'}\" $iconImageSizes{'up'} ALT=\"${opt_msg_up}\" BORDER=0></A>\n";
	}

    }

    # --- prev link ---
    if( $pageNumber == 1 ) {
	if ($numPages > 1) {
	    # Print a grayed out arrow to maintain alignment
	    $indexbar .= "<IMG SRC=\"$iconImageUrls{'prev_gray'}\" $iconImageSizes{'prev_gray'} ALT=\"\" BORDER=0>\n";
	}
    } else {
        # Go to preceding page
	$indexbar .= "<A HREF=\"$fileNames{'htmlPrevIndex'}\"";
	$indexbar .= " TARGET=\"$frameTargets{'thumbview'}\""
	    if ($opt_frames);
	$indexbar .= "><IMG\n" .
	    "SRC=\"$iconImageUrls{'prev'}\" $iconImageSizes{'prev'} ALT=\"${opt_msg_prev}\" BORDER=0></A>\n";
    }

    # --- next link ---
    if( $numPages > 1 ) {
	if( $pageNumber < $numPages ) {
	    $indexbar .= "<A HREF=\"$fileNames{'htmlNextIndex'}\"";
	    $indexbar .= " TARGET=\"$frameTargets{'thumbview'}\""
		if ($opt_frames);
	    $indexbar .= "><IMG\n" .
		"SRC=\"$iconImageUrls{'next'}\" $iconImageSizes{'next'} ALT=\"${opt_msg_next}\" BORDER=0></A>\n";
	} else {
	    # Print a grayed out arrow to maintain alignment
	    $indexbar .= "<IMG SRC=\"$iconImageUrls{'next_gray'}\" $iconImageSizes{'next_gray'} ALT=\"\" BORDER=0>\n";
        }
    }

    $indexbar .= "</NOBR>";

    # --- page links ---
    if( $numPages > 1 ) {
	my $page;
	for ( $page = 1; $page <= $numPages; ++$page) {
	    if ( $page != $pageNumber ) {
		if ($opt_frames) {
		    $indexbar .= "<A HREF=\"${opt_pageindexname}${page}$opt_htmlext\"";
		    $indexbar .= " TARGET=\"$frameTargets{'thumbview'}\""
		} else {
		    my $pN = ($page == 1) ? "" : "$page";
		    $indexbar .= "<A HREF=\"${pN}${opt_indexname}\"";
		}
		$indexbar .= ">${page}</A>\n";
	    } else {
		$indexbar .= "${page}\n";
	    }
	}
    }
    
    open( INDEX, ">$fileNames{'htmlCurrentIndex'}")
        || die("$0: Failed to open file $fileNames{'htmlCurrentIndex'} for output\n$@\n");
    print( INDEX "<HTML>\n<HEAD>\n" );
    # Charset is better before title
    print( INDEX "  <META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=${opt_metacharset}\">\n" )
	if( "$opt_metacharset" ne '' );
    print( INDEX "  <TITLE>${pageTitle}</TITLE>\n" );
    print( INDEX "  <BASE TARGET=\"$frameTargets{'imageview'}\">\n" )
	if ($opt_frames);

    # Meta tags
    print( INDEX "  <META NAME=\"GENERATOR\" CONTENT=\"WebMagick/$webmagickInfo{version} [WebMagick]\">\n" );
    print( INDEX "  <META NAME=\"Author\" CONTENT=\"${opt_metaauthor}\">\n" )
	if( "$opt_metaauthor" ne '' );
    print( INDEX "  <META NAME=\"Description\" CONTENT=\"${opt_metadescription}\">\n" )
	if( "$opt_metadescription" ne '' );
    print( INDEX "  <META NAME=\"Keywords\" CONTENT=\"${opt_metakeywords}\">\n" )
	if( "$opt_metakeywords" ne '' );
    print( INDEX "  <META NAME=\"Classification\" CONTENT=\"${opt_metaclassification}\">\n" )
	if( "$opt_metaclassification" ne '' );
    # The value of $opt_metaexpires should be similar to:
    #   "Tue, 20 Aug 1996 14:25:27 GMT"
    print( INDEX "  <META HTTP-EQUIV=\"Expires\" CONTENT=\"${opt_metaexpires}\">\n" )
	if( "$opt_metaexpires" ne '' );
    print( INDEX "  <LINK REL=\"stylesheet\" type=\"text/css\" HREF=\"${opt_stylesheet}\">\n")
	if ("$opt_stylesheet" ne '');

    print( INDEX "</HEAD>\n" );
    print( INDEX "<BODY\n" );
    unless ("$opt_stylesheet" ne '')
    {
	    print( INDEX "  TEXT=\"${opt_colorfore}\"\n" );
	    print( INDEX "  BGCOLOR=\"${opt_colorback}\"\n" );
	    print( INDEX "  BACKGROUND=\"$iconImageUrls{background}\"\n" )
		if defined $iconImageUrls{background};
	    print( INDEX "  LINK=\"${opt_colorlink}\"\n" );
	    print( INDEX "  VLINK=\"${opt_colorvlink}\"\n" );
	    print( INDEX "  ALINK=\"${opt_coloralink}\"" );
    }
    print( INDEX ">\n");

    print( INDEX "${opt_header}\n" ) if ("$opt_header" ne '');

    # Leave page blank unless there is something to show
    if( $numimages > 0 ) {
	# Total HACK!!!
	if( !$opt_frames || $opt_framestyle == 1 ) {
	    print( INDEX "<p><FONT SIZE=\"-1\">${opt_msg_index_of_files}\"$imageNames[$pageNumber - 1][0]\" ${opt_msg_index_through}",
		  " \"$imageNames[$pageNumber - 1][$numimages-1]\"</FONT></p>\n" );
	}
	print( INDEX "$indexbar<BR>\n" );

	if ($opt_tables)
	{
		print INDEX "<TABLE WIDTH=\"90%\">";
	}
	else
	{
		#
		# Add image map info to html file
		#
		my $mapname = "thumbnails";
		if ($opt_serversidemap) {
		    if ( "${opt_htimage}" ne '' ) {
			print( INDEX
			      "<A HREF=\"${opt_htimage}$fileNames{'montageServerMap'}\">\n" );
		    } else {
			print( INDEX
			      "<A HREF=\"" . basename($fileNames{'montageServerMap'}) . "\">\n");
		    }
		}
		print( INDEX
		      "<IMG SRC=\"$montageImages[$pageNumber - 1]\" $montageImageSizes[$pageNumber - 1]",
		      " USEMAP=\"#${mapname}\" BORDER=0 ISMAP>" );
		print( INDEX "</A>" ) if ($opt_serversidemap);
		print( INDEX "\n<MAP NAME=\"${mapname}\">\n" );
	} # unless ( $opt_tables )

	my $imageNum;
	for( $imageNum = 0; $imageNum <= $#{$imageNames[$pageNumber - 1]}; ++$imageNum ) {
	
	    my $pic = $imageNames[$pageNumber - 1][$imageNum];
	    my $pichtml;
	    my $target = '';
	    if ($opt_pichtml && $opt_frames && "$opt_pichtmltarget" ne '') {
		$target = " TARGET=\"${opt_pichtmltarget}\"";
	    }

	    if ( $opt_pichtml ) {
			
			$pichtml = $pic . $opt_pichtmlext;

			open( PICHTML, ">$pichtml")
			    || die("$0: Failed to open file $pichtml for output\n$@\n");
			print( PICHTML "<HTML>\n<HEAD>\n" );
			# Charset is better before title
			print( PICHTML "  <META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=${opt_metacharset}\">\n" )
			    if( "$opt_metacharset" ne '' );
			print( PICHTML "  <TITLE>$pic</TITLE>\n" );

			# Meta tags
			print( PICHTML "  <META NAME=\"GENERATOR\" CONTENT=\"WebMagick/$webmagickInfo{version} [WebMagick]\">\n" );
			print( PICHTML "  <META NAME=\"Author\" CONTENT=\"${opt_metaauthor}\">\n" )
			    if( "$opt_metaauthor" ne '' );
			print( PICHTML "  <META NAME=\"Description\" CONTENT=\"${opt_metadescription}\">\n" )
			    if( "$opt_metadescription" ne '' );
			print( PICHTML "  <META NAME=\"Keywords\" CONTENT=\"${opt_metakeywords}\">\n" )
			    if( "$opt_metakeywords" ne '' );
			print( PICHTML "  <META NAME=\"Classification\" CONTENT=\"${opt_metaclassification}\">\n" )
			    if( "$opt_metaclassification" ne '' );
			# The value of $opt_metaexpires should be similar to:
			#   "Tue, 20 Aug 1996 14:25:27 GMT"
			print( PICHTML "  <META HTTP-EQUIV=\"Expires\" CONTENT=\"${opt_metaexpires}\">\n" )
			    if( "$opt_metaexpires" ne '' );
			print( PICHTML "  <LINK REL=\"stylesheet\" type=\"text/css\" HREF=\"${opt_stylesheet}\">\n")
			    if ("$opt_stylesheet" ne '');

			print( PICHTML "</HEAD>\n" );
			print( PICHTML "<BODY\n" );
			unless ("$opt_stylesheet" ne '')
	    		{
				print( PICHTML "  TEXT=\"${opt_colorfore}\"\n" );
				print( PICHTML "  BGCOLOR=\"${opt_colorback}\"\n" );
				print( PICHTML "  BACKGROUND=\"$iconImageUrls{background}\"\n" )
				    if defined $iconImageUrls{background};
				print( PICHTML "  LINK=\"${opt_colorlink}\"\n" );
				print( PICHTML "  VLINK=\"${opt_colorvlink}\"\n" );
				print( PICHTML "  ALINK=\"${opt_coloralink}\"");
			}
			print( PICHTML ">\n" );

			print( PICHTML "$opt_pichtmltop") if ("$opt_pichtmltop" ne '');

			if ($opt_pichtmlputtitle) {
			    print ( PICHTML "${opt_pichtmltitlestart}");
			    if (defined( $imageLabels{$pic})) {
				print( PICHTML "$imageLabels{$pic}");
			    } else {
				print( PICHTML "$pic");
			    }
			    print ( PICHTML "${opt_pichtmltitleend}");
			}

			if ($opt_pichtmlnav) {
				print ( PICHTML "<P><NOBR>");
					
				# Must be visible at each page, if no frames
				if (!$opt_frames) {

				    # --- readme link ---
				    if ( $haveReadme ) {
					print (PICHTML "<A HREF=\"${opt_readme}\"><IMG\n",
					    "SRC=\"$iconImageUrls{'help'}\" $iconImageSizes{'help'} ALT=\"${opt_msg_readme}\" BORDER=0></A>\n");
				    }
				}

				# up
				if (!$opt_frames || $opt_framestyle == 1) {
					if ($opt_frames) {
					    print ( PICHTML "<A HREF=\"${opt_pageindexname}${pageNumber}${opt_htmlext}\"");
					    print ( PICHTML " TARGET=\"$frameTargets{'thumbview'}\"")
					} else {
					    print ( PICHTML "<A HREF=\"${pNumber}${opt_indexname}\"");
					}
					print ( PICHTML "><IMG SRC=\"$iconImageUrls{'up'}\"",
						" $iconImageSizes{'up'} ALT=\"${opt_msg_up}\" BORDER=0></A>\n");
					
				}
				
				# prev
				if (($imageNum == 0) && ($pageNumber == 1)) {
					print ( PICHTML "<IMG SRC=\"$iconImageUrls{'prev_gray'}\"",
						" $iconImageSizes{'prev_gray'} BORDER=0>\n");
				} else {
					print ( PICHTML "<A${target} HREF=\"");
					if ($imageNum == 0 && $pageNumber != 1) {
						print ( PICHTML $imageNames[$pageNumber - 2][${numimages} - 1]);
					} else {
						print ( PICHTML $imageNames[$pageNumber - 1][${imageNum} - 1]);
					}
					print ( PICHTML $opt_pichtmlext);
					print ( PICHTML "\"><IMG SRC=\"$iconImageUrls{'prev'}\"",
						" $iconImageSizes{'prev'} ALT=\"${opt_msg_prev}\" BORDER=0></A>\n");
						
				}
				
				#next
				if (($pageNumber == $numPages) && ($imageNum == $#{$imageNames[$pageNumber - 1]})) {
					print ( PICHTML "<IMG SRC=\"$iconImageUrls{'next_gray'}\"",
						" $iconImageSizes{'next_gray'} BORDER=0>\n");
				} else {
					print ( PICHTML "<A${target} HREF=\"");
					if (($imageNum == $#{$imageNames[$pageNumber - 1]}) && $pageNumber != $numPages) {
						print ( PICHTML $imageNames[$pageNumber][0]);
					} else {
						print ( PICHTML $imageNames[$pageNumber - 1][${imageNum} + 1]);
					}
					print ( PICHTML $opt_pichtmlext);
					print ( PICHTML "\"><IMG SRC=\"$iconImageUrls{'next'}\"",
						" $iconImageSizes{'next'} ALT=\"${opt_msg_next}\" BORDER=0></A>\n");
						
				}
				
				print ( PICHTML "</NOBR><BR>");
			}

			if ( $pic =~ /\.(jpg|jpeg?|gif|xbm|png)$/i ) {
			    print( PICHTML "<IMG SRC=\"$pic\" ALT=\"\">" );
			} else {
			    print( PICHTML "<A HREF=\"", escapeurl($pic), "\"><BIG><B>$pic</B></A> ", fsize($pic), "</BIG>" );
			}

			print( PICHTML "$opt_pichtmlbottom\n") if ("$opt_pichtmlbottom" ne '');

			print( PICHTML "</BODY>\n" );
			print( PICHTML "</HTML>\n" );
			close ( PICHTML );

			unless ($opt_tables)
			{				
				print( INDEX "  <AREA${target} HREF=\"",
				       escapeurl($pichtml), "\" SHAPE=RECT COORDS=", $imageThumbCoords[$pageNumber - 1][$imageNum],">\n" );
			}
	    } else 
	    { 
		    unless ($opt_tables) 
		    {
				print( INDEX "  <AREA HREF=\"", escapeurl($pic),
				       "\" SHAPE=RECT COORDS=", $imageThumbCoords[$pageNumber - 1][$imageNum],">\n" );
		    } 
	    }
	    
	    
	    if ($opt_tables)
		{
			if ($imageNum%$opt_columns == 0)
			{
				print INDEX "<TR ALIGN=CENTER VALIGN=TOP>";
			}
				
			print INDEX "<TD>";
			
			if ($opt_pichtml) {
				print (INDEX "<A${target} HREF=\"", escapeurl($pichtml), "\">");
			} else {
				print (INDEX "<A HREF=\"", escapeurl($pic), "\">");
			}
			
			# TODO: make sure the thumbnails are created, and get some image sizes
			# TODO: save the labels in a new array, maybe same with sizes
			if ( $thumbImageSizes{$pic}) {
				print (INDEX "<IMG SRC=\"", $opt_cachedir, "/", escapeurl($pic), "\" $thumbImageSizes{$pic} BORDER=0>");				
			} else {
				print (INDEX "<IMG SRC=\"", escapeurl($pic), "\" BORDER=0>");
			}
			
			print (INDEX "</A><BR><FONT SIZE=\"-1\">" . $tableImageLabels{$imageNames[$pageNumber - 1][$imageNum]} . "</FONT></TD>\n");
						
			if ($imageNum%$opt_columns == ($opt_columns - 1))
			{
				print INDEX "</TR>";
			}
		} # if ($opt_tables)
	
	} # for each picture
	
	if ($opt_tables) 
	{
		unless ($imageNum%$opt_columns == ($opt_columns - 1))
		{
			print INDEX "</TR>";
		}
		print (INDEX "</TABLE>\n")
	} else {print( INDEX "</MAP>\n" );}
    }

    print( INDEX "${opt_footer}\n" ) if ("$opt_footer" ne '');

    # Print Copyright info on non-blank pages.
    if( $numimages > 0 ) {
       if( "${opt_address}" ne '' ) {
	   print( INDEX "<BR><ADDRESS>${opt_address}</ADDRESS>\n" );
       }
       if( "$htmlOpts{'dateText'}" ne '' || !$opt_anonymous) {
	   print( INDEX "<FONT SIZE=\"-1\">" );
       }
       if( "$htmlOpts{'dateText'}" ne '' ) {
	   print( INDEX "<BR>$htmlOpts{'dateText'}\n" );
       }
       # If anonymous, don't show copyright and address info
       if( ! $opt_anonymous ) {
	   print( INDEX "<HR>\n${opt_msg_produced_by} " );
	   print( INDEX "<NOBR><A HREF=\"http://webmagick.sourceforge.net/\"",
		 " TARGET=\"_top\">WebMagick</A>" );
	   print( INDEX " $webmagickInfo{version}</NOBR>, <NOBR>${opt_msg_copyright}&copy;</NOBR> <NOBR>Bob Friesenhahn</NOBR>\n" );
       }
       if( "$htmlOpts{'dateText'}" ne '' || !$opt_anonymous) {
	   print( INDEX "</FONT>\n" );
       }
    }

    print( INDEX "</BODY>\n" );
    print( INDEX "</HTML>\n" );

    # Close current HTML index file
    close( INDEX );

    return ( $errorstat );
}

#
# Write out JavaScript index files
#
sub writeJavaScriptIndexFiles {

    print("Writing JavaScript status file: $fileNames{'jsPageIndex'}\n") if $opt_debug;

    #
    # Write JavaScript "index" page.
    # This page loads JavaScript source files and ensures that windows are drawn
    #
    open( JSINDEX, ">$fileNames{'jsPageIndex'}")
        || die("$0: Failed to open file $fileNames{'jsPageIndex'} for output\n$@\n");

    print( JSINDEX "<HTML>\n");
    print( JSINDEX "<HEAD>\n");
    print( JSINDEX "<SCRIPT LANGUAGE=\"JavaScript\" SRC=\"$fileNames{'jsVariables'}\"></SCRIPT>\n");
    print( JSINDEX "<SCRIPT LANGUAGE=\"JavaScript\" SRC=\"$fileNames{'jsFunctions'}\"></SCRIPT>\n");
    print( JSINDEX "</HEAD>\n");
    print( JSINDEX "<BODY>\n");
    print( JSINDEX "<SCRIPT LANGUAGE=\"JavaScript\">\n");
    print( JSINDEX "  top.drawWindows();\n");
    print( JSINDEX "</SCRIPT>\n");
    print( JSINDEX "</BODY>\n");
    print( JSINDEX "</HTML>\n");
    close( JSINDEX );    


    #
    # JavaScript Variable definitions
    #
    open( JSVARS, ">$fileNames{'jsVariables'}")
        || die("$0: Failed to open file $fileNames{'jsVariables'} for output\n$@\n");

    print( JSVARS "//\n// WebMagick Run Status File -- JavaScript Format\n//\n" );

    #
    # Directory global variables
    #
    print( JSVARS "//\n// Directory-global definitions\n//\n\n" );

    # Subdirectory names
    print( JSVARS "\n// subdirectory names\n",
	  jsArray( 'dirNames', @dirNames ) );
    
    # Subdirectory titles
    print( JSVARS "\n// subdirectory titles\n",
	  jsHash( 'dirTitles', \%dirTitles ) );

    # Subdirectory links
    print( JSVARS "\n// subdirectory links\n", jsHash( 'dirLinks', \%dirJsLink ) );

    # Image labels
    print( JSVARS "\n// image titles\n", jsHash( 'imageLabels', \%imageLabels ) );

	# Image labels
    #print( JSVARS "\n// table image titles\n", jsHash( 'tableImageLabels', \%tableImageLabels ) );
    print( JSVARS "\n// table image titles\n", 
    	jsHashToDouble( 'imLabels', \%tableImageLabels ) );
    


    # Montage options ($montageParameters)
#    print( JSVARS "\n// montage parameters\n",
#	  jsVariable( 'montageParameters', \$montageParameters) );
    
    # HTML options
    print( JSVARS "\n// HTML options\n",
	  jsHash( 'htmlOpts', \%htmlOpts ) );
    
    # Icon URLs
    print( JSVARS "\n// icon URLs\n",
	  jsHash( 'iconImageUrls', \%iconImageUrls ) );

    # Icon sizes
    print( JSVARS "\n// icon sizes\n", 
	  jsHash( 'iconImageSizes', \%iconImageSizes ) );
	  
	# Thumbnail image sizes
	#print( JSVARS "\n// thumb sizes\n", 
	#  jsHash( 'thumbImageSizes', \%thumbImageSizes ) );
	  
	print ( JSVARS "\n// thumb sizes \n",
	  jsSizes( 'thumb', \%thumbImageSizes ) );
		

    #
    # Page specific variables
    #
    print( JSVARS "\n//\n// Page-Specific variable definitions\n//\n" );

    # Generated File names
#    print( JSVARS "\n// file names\n",
#	  jsHash( 'fileNames', \%fileNames ) );
    
    # Image names
    print( JSVARS "\n// image names (imageNames[page number][image number])\n",
	  jsDoubleArray( 'imageNames', @imageNames) );
        
    # Image thumbnail map (co-ordinate) information (@imageThumbCoords)
    print( JSVARS "\n// thumbnail coordinates (imageThumbCoords[page number])\n",
	  jsDoubleArray( 'imageThumbCoords', @imageThumbCoords ) );

    # Montage image names  (@montageImages) already in per-page array form
    print( JSVARS "\n// montage image names (montageImages[page number])\n",
	  jsArray( 'montageImages', @montageImages) );

    # Montage image sizes  (@montageImageSizes) already in per-page array form
    print( JSVARS "\n// montage image sizes (montageImageSizes[page number])\n",
	  jsArray( 'montageImageSizes', @montageImageSizes) );

    # Set flag to indicate this file completely loaded
    print( JSVARS "\nvar jsVarsLoaded = 1;\n" );

    close( JSVARS );
    print("Done writing JavaScript\n") if $opt_debug;
}

#
# Write out PERL-format run-status file
#
sub writePerlIndexFiles {
    open( PERLVARS, ">$fileNames{'pageStatus'}" )
	|| die( "Unable to open file $fileNames{'pageStatus'}!\n$@\n" );
    print(STDERR "Writing PERL status file: $fileNames{'pageStatus'}\n") if $opt_debug;

    print( PERLVARS "#\n# WebMagick Run Status File -- PERL Format\n#\n\n" );

    #
    # Directory global variables
    #
    print( PERLVARS "#\n# Directory-global definitions\n#\n" );
    
    # Subdirectory names (@dirNames)
    print( PERLVARS "\n# subdirectory names\n",
	  plArray( 'dirNames', @dirNames ) );
    
    # Subdirectory titles
    print( PERLVARS "\n# subdirectory titles\n",
	  plHash( 'dirTitles', \%dirTitles ) );

    # Image labels
    print( PERLVARS "\n# image titles\n", plHash( 'imageLabels', \%imageLabels ) );

	# Table image labels
    print( PERLVARS "\n# table image titles\n", plHash( 'tableImageLabels', \%tableImageLabels ) );


    # Montage options ($montageParameters)
    print( PERLVARS "\n# montage parameters\n",
	  plVariable( 'montageParameters', \$montageParameters) );

    # HTML options ($htmlParams)
    print( PERLVARS "\n# HTML options\n",
	  plVariable( 'htmlParams', \$htmlParams ) );

    # Icon URLs
    print( PERLVARS "\n# icon URLs\n",
	  plHash( 'iconImageUrls', \%iconImageUrls ) );

    # Icon sizes
    print( PERLVARS "\n# icon sizes\n", 
	  plHash( 'iconImageSizes', \%iconImageSizes ) );

    # Thumb sizes
    print( PERLVARS "\n# thumb sizes\n", 
	  plHash( 'thumbImageSizes', \%thumbImageSizes ) );

	#
    # Page-Specific variable definitions
    #
    print( PERLVARS "\n#\n# Page-specific variables\n#\n" );

    # Image names (@imageNames)
    print( PERLVARS "\n# image names (\$imageNames[page number][image number])\n",
	  plDoubleArray( 'imageNames', @imageNames) );

    # Image thumbnail map (co-ordinate) information (@imageThumbCoords)
    print( PERLVARS "\n# thumbnail coordinates (\$imageThumbCoords[page number][thumb number])\n",
	  plDoubleArray( 'imageThumbCoords', @imageThumbCoords ) );

    # Montage image names  (@montageImages) already in per-page array form
    print( PERLVARS "\n# montage image names (\$montageImages[page number])\n",
	  plArray( 'montageImages', @montageImages ) );

    # Montage image sizes  (@montageImageSizes) already in per-page array form
    print( PERLVARS "\n# montage image sizes (\$montageImageSizes[page number])\n",
	  plArray( 'montageImageSizes', @montageImageSizes ) );

    # Indicate the version used for processing
    # This way we can regenerate things if we need a newer version
    print (PERLVARS "\n\$perlVarsVersion = $requiredPerlVarsVersion;\n" );

    # Set flag to indicate this file completely loaded
    print( PERLVARS "\n\$perlVarsLoaded = 1;\n" );    

    close( PERLVARS );
}

#
# Build montage using PerlMagick
#
sub doMontage {
    my $errorstat = 1;	# Started with "failed" status

    use Image::Magick;

    my (
	$imagename,		# Image name
	$image,			# An individual image
	$montage,		# Montage of images
	$newthumb,		# Set to 1 if new thumbnail
	$status,		# Return status
	$thumbs			# Thumbnail array
	);

    # If we need to, then do the expensive stuff Build index
    # files via PerlMagick's Montage operation. Go out of our
    # way to avoid problems with multi-image files. JPEG does not
    # support transparency but we fudge by setting the
    # background color in the JPEG file to the page color.
    # Hopefully browsers that support JPEG also support
    # setting the background

    unlink(
	   $fileNames{'montageGIF'},
	   $fileNames{'montageJPEG'},
	   );

    
    #
    # If caching thumbnails then ensure that directory exists
    #
    mkdir( $opt_cachedir, 0755 ) if ! -d $opt_cachedir;

    # Read images into PerlMagick object
    print( STDERR "\nReading images: ", join(' ', @{$imageNames[$pageNumber - 1]}), "\n" )
	if $opt_debug;

    # Allocate thumbnail image
    $thumbs = Image::Magick->new;
         
    # Allocate scratch image
    $image = Image::Magick->new;

    # Allocate and read Netscape map image (Netscape 216 color cube)
    # No need to do an allocation if we are not doing the mapping.
    if ($opt_mapnetscape) {
	$netscapeColormap = Image::Magick->new;
	$status = $netscapeColormap->Read('netscape:');
	handleMagickError( __FILE__, __LINE__, 'netscape:', $status) if "$status";
    }

MONTAGE:
    {     
READ:       
	foreach $imagename (@{$imageNames[$pageNumber - 1]}) {

	    my (
		$width,		# Image width
		$height,	# Image height
		$base_columns,	# Original width
		$base_rows,	# Original height
		$class,		# Image class
		$comment,	# Image comment
		$depth,		# Image color depth
		$filesize,	# Image file size
		$magick,	# Image magick
		);
	    #
	    # Handle thumbnail cache
	    #
	    my $cachename = "$opt_cachedir/$imagename";

	    $newthumb = 1;	# Start presuming that thumbnail is new
	    # If we are caching, and cache thumbnail exists and is newer then use it
	    # always make cache if doing tables, or if version is not correct
	    if ( ($opt_cache || $opt_tables) && ! $opt_forcecache && -f $cachename 
	    	&& (fmtime($cachename) >= fmtime($imagename))) {
		# Read image
		print( STDERR "Reading $cachename ...\n" ) if $opt_debug;
		$status = $image->Read("$cachename");
		if("$status") {
		    undef @$image; # Only delete image data, not object
		    handleMagickError( __FILE__, __LINE__, $cachename, $status);
		    next READ; # Try to read next image
		}

		# Obtain original image parameters
		$comment = $image->Get("comment");
		if ($comment =~
		    # xv 3.00 & 3.10 format
		    /IMGINFO:(\d+)x(\d+) (\S+) file\s+\((\d+) bytes\)/ ) {
		    $width = $1;
		    $height = $2;
		    $magick = $3;
		    $filesize = $4;
		} else {
		    print( STDERR "Failed to grock image info from thumbnail ${cachename}!\n",
			  "Removing cache file ...\n" );
		    print( STDERR "Run WebMagick again to re-generate the thumbnail.\n" );
		    print( STDERR "If problem continues then your ImageMagick is out of date.\n" );
		    unlink( $cachename );
		}

		# Indicate that thumbnail came from cache
		$newthumb = 0;
	    } else {
		# Otherwise, read and scale image

		# Set desired image read size.  The JPEG library will
		# read and return a reduced image which is at least
		# the size specified (it returns a number of standard
		# scaled sizes) but not smaller.
		# This uses a feature available in PerlMagick 1.12 and beyond
		$status = $image->Set(size=>$opt_thumbgeometry);
		handleMagickError( __FILE__, __LINE__, "$opt_thumbgeometry", $status) if "$status";
		
		# Read image
		print( STDERR "Reading ${imagename}\[0\] ...\n" ) if $opt_debug;
		$status = $image->Read("${imagename}\[0\]");
		if("$status" && handleMagickError( __FILE__, __LINE__, $imagename, $status)) {
		    undef @$image; # Only delete image data, not object
		    print("Trying next image ...\n" );
		    next READ; # Try to read next image		    }
		}

		# Scale image and obtain original parameters if not from cache
		if( $newthumb ) {

		    #
		    # Apply any PerlMagick operations specified by $opt_thumbprehook
		    #
		    if( $opt_thumbprehook ) {
			print("Evaluating thumbnail pre-hook ...\n$opt_thumbprehook\n" )
			    if $opt_debug;
			eval $opt_thumbprehook;
		    }

		    # Obtain image parameters
		    ( $width, $height, $filesize, $magick, $class, $depth ) =
			$image->Get(
				    'width',
				    'height',
				    'filesize',
				    'magick',
				    'class',
				    'depth');
		    
		    if( $opt_debug ) {
		      print("Image: ${width}x${height} $class $filesize bytes $magick $depth bits\n");
		    }

		    # Obtain original image size.  This uses a feature
		    # available in PerlMagick 1.12 and beyond.  If the
		    # feature is not supported then undefined values
		    # should be returned.
		    ($base_columns, $base_rows) = $image->Get('base-columns', 'base-rows');
		    if( defined($base_columns) && defined($base_rows) ) {
			$width = $base_columns;
			$height = $base_rows;
			print("Saving original image size ${base_columns}x${base_rows}\n")
			    if $opt_debug;
		    }

		    my $geometry;
		    if( $opt_cache ) {
			$geometry = $opt_cachegeom;
		    } else {
			$geometry = $opt_thumbgeometry;
		    }

		    my $opt_sampling = 0; # Set to 1 to enable sampling
		    if( $class eq 'PseudoClass' && $opt_sampling ) {
			print( STDERR "Sampling $imagename to geometry \"${geometry}>\" ...\n")
			    if $opt_debug;
			$status = $image->Sample(geometry=>"${geometry}>");
			
		    } else {
			print( STDERR "Zooming $imagename with geometry \"${geometry}>\" ...\n")
			    if $opt_debug;
			$status = $image->Zoom(filter=>"${opt_zoomfilter}",
					       blur=>0.6,
					       geometry=>"${geometry}>" );
		    }
		    
		    if("$status") {
			undef @$image; # Only delete image data, not object
			handleMagickError( __FILE__, __LINE__, $imagename, $status);
			next READ; # Try to read next image
		    }
		}

		#
		# Apply any PerlMagick operations specified by $opt_thumbposthook
		#
		if( $opt_thumbposthook ) {
		    print("Evaluating thumbnail post-hook ...\n$opt_thumbposthook\n" ) if $opt_debug;
		    eval $opt_thumbposthook;
		}

		# If we are caching, thumbnail is new, and image is
		# large enough, then write it to thumbnail cache
		# if we are using tables, then we cache as long as it's new
		if( ($opt_cache || $opt_tables) && $newthumb && ((($width*$height) > $opt_cachemin) || $opt_tables)) {
		

		    my $comment="IMGINFO:${width}x${height} ${magick} file  (${filesize} bytes)";
		    print( STDERR "Applying image comment:\n${comment}\n") if $opt_debug;

		    # Apply comment to thumbnail image
		    $status = $image->Comment( $comment );
		    handleMagickError( __FILE__, __LINE__, $cachename, $status) if "$status";
		    
		    print( STDERR "Writing ${cachename} with format ${opt_cacheformat} ...\n" )
			if $opt_debug;
                    #
                    # Give JPEG files special treatment
                    #
                    if( $opt_cacheformat eq 'JPEG' || $opt_cacheformat eq 'JPG' ) {
		        $status = $image->Write(
                                                  filename=>"${opt_cacheformat}:${cachename}",
					          interlace=>'None',
					          quality=>85
                                                );
                    } else {
		        $status = $image->Write(
                                                  filename=>"${opt_cacheformat}:${cachename}"
                                               );
                    }
		    handleMagickError( __FILE__, __LINE__, $cachename, $status) if "$status";
		    # TODO: for some reason, the output looks like these are getting put in twice, once with .cache/
		    $thumbImageSizes{$imagename} = html_imgsize($cachename);
		    		    
		}

	    }

	    #
	    # Set image label
	    #
	    my $label = '';
	    if( $opt_thumblabel ne 'false' ) {
		if( defined( $imageLabels{$imagename} ) ) {
		    # Set image specific label
		    $label = $imageLabels{$imagename};
		} else {
		    # Set default label
		    $label = $opt_thumblabel;
		}
	    }

	    if ($label ne '') {
		my $sizestr;
		my $kb = 1024;
		my $mb = $kb * $kb;
		if( $filesize <= 9999 ) {
		    # print as bytes
		    $sizestr = "${filesize}b";
		} elsif( $filesize <= 9999999 ) {
		    # print as kilobytes
		    my $size = int($filesize/$kb);
		    $sizestr = "${size}kb";	    
		} else {
		    # print as megabytes
		    my $size = int($filesize/$mb);
		    $sizestr = "${size}Mb";
		}

		#
		# Truncate label down to width $opt_thumblabelwidth
		#
		my $imagebase;
		($imagebase = $imagename) =~ s/\.[^\.]*$//g;
		# %b = file size
		# %d = directory (not implemented)
		# %e = extension (not implemented)
		# %f = full filename
		# %h = height
		# %m = magick
		# %n = filename minus extension
		# %s = scene number (not implemented)
		# %t = top of filename (not implemented)
		# %w = width
		$label =~ s/%b/$sizestr/g;
		$label =~ s/%f/$imagename/g;
		$label =~ s/%h/$height/g;
		$label =~ s/%m/$magick/g;
		$label =~ s/%n/$imagebase/g;
		$label =~ s/%w/$width/g;
		my @llines = split(/\\n/, $label);
		grep($_ = substr( $_, 0, $opt_thumblabelwidth), @llines);
		$label = join("\n", @llines);
		
		# put our label into the table image hash 
		
		$tableImageLabels{$imagename} = $label;
		$tableImageLabels{$imagename} =~ s/\n/<BR>/g;
		
		print( STDERR "Applying image label: \"${label}\"\n" )
		    if $opt_debug;
		$status = $image->Label( $label );
		handleMagickError( __FILE__, __LINE__, $imagename, $status) if "$status";
	    }

	    #
	    # Add thumbnail to thumbs array
	    #
	    push(@$thumbs, @$image);
	     
	    #print( STDERR "Freeing $imagename ...\n") if $opt_debug;
             
	    # Only delete image data, not object
	    undef @$image;
	}
# READ end block

	#
	# Set common image attributes
	#
        if( $opt_thumbframebgcolor ne 'false' ) {
	    $status = $thumbs->Set( bordercolor=>$opt_thumbframebgcolor );
	    if( "$status" ) {
		handleMagickError( __FILE__, __LINE__, "", $status);
	        last MONTAGE;
	    }
        }
        if( $opt_thumbframecolor ne 'false' ) {
	    $status = $thumbs->Set( mattecolor=>$opt_thumbframecolor );
	    if( "$status" ) {
		handleMagickError( __FILE__, __LINE__, "", $status);
	        last MONTAGE;
	    }
        }
	# New-style way to set label text foreground color
	# Somewhere along the way 'foreground' started being ignored by montage
        if( $opt_thumbforeground ne 'false' ) {
	    $status = $thumbs->Set( pen=>$opt_thumbforeground );
	    if( "$status" ) {
		handleMagickError( __FILE__, __LINE__, "", $status);
	        last MONTAGE;
	    }
        }

	unless ($opt_tables)
	{
		#
		# Do the montage
		#
		print( STDERR "Creating montage using options:\n $montageArguments\n" )
		    if $opt_debug;
	
		eval "\$montage = \$thumbs->Montage( $montageArguments ) ;";
		if( $@ ) {
		    handleMagickError( __FILE__, __LINE__, "", $@);
		    last MONTAGE;
		}
		handleMagickError( __FILE__, __LINE__, "", $montage) unless ref($montage);
		
		last MONTAGE unless ref($montage);
	
	
	

		#
		# Obtain imagemap info (thanks to Cristy for the relevant code)
		#
		{
		    my @thumbCoords;
		    my $montageWidth = $montage->Get('width');
		    my $montageHeight = $montage->Get('height');
		    $montage->Get('montage')=~/(\d+)x(\d+)\+(\d+)\+(\d+)/;
		    my $thumbWidth=$1;
		    my $thumbHeight=$2;
		    my $x=$3;
		    my $y=$4;
		    my $directory = $montage->Get('Directory');
		    print( STDERR "Montage directory = $directory\n" ) if $opt_debug;
		    for (split(/\n/,$directory))
		    {
			# Eliminate cache dir from path HACK! HACK!
			s%$opt_cachedir/%% if $opt_cache;
			my $img = $_;
			my $x1 = $x;
			my $y1 = $y;
			my $x2 = $x+$thumbWidth-1;
			my $y2 = $y+$thumbHeight-1;
	
			push(@thumbCoords, "$x1,$y1,$x2,$y2");
	
			#print "  <area href=", escapeurl($_), " shape=rect coords=$x1,$y1,", $x2,',', $y2, ">\n";
			$x+=$thumbWidth;
			if ($x >= $montageWidth)
			{
			    $x=0;
			    $y+=$thumbHeight;
			}
		    }
		    if( !defined($imageThumbCoords[$pageNumber - 1]) ||
		       ("@{$imageThumbCoords[$pageNumber - 1]}" ne "@thumbCoords") ) {
			$imageThumbCoords[$pageNumber - 1] = [ @thumbCoords ];
			print( STDERR "Thumbnail coordinates have changed, must re-do HTML page\n" )
			    if $opt_debug;
			++$doPageHtml;
		    }
		}
	

		#
		# Process & Write GIF file
		#
	
		# Copy image so we can play with it without effecting original
		print( STDERR "Copying montage to scratch ...\n" )
		    if $opt_debug;
		$image = $montage->Copy();
		handleMagickError( __FILE__, __LINE__, "", $image) unless ref($image);
		last MONTAGE unless ref($image);
	
		if ($opt_mapnetscape) {
		    print( STDERR "Mapping $fileNames{'montageGIF'} to Netscape 216 color cube...\n" )
			if $opt_debug;
		    $status = $image->Map(image=>$netscapeColormap, dither=>'True');
		    handleMagickError( __FILE__, __LINE__, "", $status) if "$status";
		}
	
		if( ! $opt_forcejpeg ) {
		print( STDERR "Writing $fileNames{'montageGIF'} ...\n" ) if $opt_debug;
		$status = $image->Write(
					dither=>'True',
					filename=>"GIF:$fileNames{'montageGIF'}",
					interlace=>'Line'
					);
		handleMagickError( __FILE__, __LINE__, $fileNames{'montageGIF'}, $status) if "$status";
		last MONTAGE if "$status";
		undef @$image;
		}
	
		# If not doing GIF only, do JPEG	      
		if( ! $opt_forcegif ) {
		    # Only do JPEG if GIF is large.
		    # Most reasonable GIFs are under 30K
		    if( $opt_forcejpeg || fsize( $fileNames{'montageGIF'} ) > $opt_maxgif ) {
			# Write JPEG file
			print( STDERR "Writing $fileNames{'montageJPEG'} ...\n" )
			    if $opt_debug;
			$status = $montage->Write(
						  filename=>"JPEG:$fileNames{'montageJPEG'}",
						  interlace=>'Plane',
						  quality=>$opt_jpegquality
						  );
			handleMagickError( __FILE__, __LINE__, $fileNames{'montageJPEG'}, $status) if "$status";
			last MONTAGE if "$status";
		    } else {
			print( STDERR "Avoiding JPEG image since GIF is small enough\n" )
			    if $opt_debug;
		    }
		} else {
		    print( STDERR "Avoiding JPEG image due to forcegif option\n" )
			if $opt_debug;
		}
	
		# Decide to use GIF or JPEG version of output depending on size.
		# If there is only one type then no need.
		if( -f $fileNames{'montageGIF'} && -f $fileNames{'montageJPEG'} ) {
		    if( fsize($fileNames{'montageGIF'}) > fsize($fileNames{'montageJPEG'}) ) {
			print( STDERR "Choosing JPEG since it is smaller\n" )
			    if $opt_debug;
			unlink($fileNames{'montageGIF'}); # Use JPEG
		    } else {
			print( STDERR "Choosing GIF since it is smaller\n" )
			    if $opt_debug;
			unlink($fileNames{'montageJPEG'}); # Use GIF
		    }
		}
	} # unless ($opt_tables)
	$errorstat = 0;	# If it made it this far, then no error
    }

# MONTAGE end block


    # Delete montage image
    #print( STDERR "Freeing montage image ...\n" ) if $opt_debug;
    undef @$montage; # FIXME: Can't use string ("Warning 310: No images to montag") as an ARRAY ref while "strict refs" in use
    undef $montage;
     
    # Delete thumbnails
    #print( STDERR "Freeing thumbnail images ...\n" ) if $opt_debug;
    undef @$thumbs;
    undef $thumbs;

    # Delete scratch image
    #print( STDERR "Freeing scratch image ...\n" ) if $opt_debug;
    undef @$image;
    undef $image;

    # Delete Netscape map image
    if ($opt_mapnetscape) {
	#print( STDERR "Freeing Netscape map image ...\n" ) if $opt_debug;
	undef @$netscapeColormap;
	undef $netscapeColormap;
    }
     
    unlink(
	   $fileNames{'montageJPEG'},
	   $fileNames{'montageGIF'}
	   ) if $errorstat;
    return( $errorstat );
}

#
# Write out server-side imagemap data
#
sub writeImageMap {
    my $errorstat = 0;
    my $pindexname;
    my $pNumber;

    # Write out server-side imagemap (CERN or NCSA)
    # This uses the absolute path as the URL or a relative one
    # from the referring URL
    # The server must map URLs specified with filesystem paths
    # or support relative URLs from the referrer (Apache &
    # latest NCSA do).
    print( STDERR "Writing file $fileNames{'montageServerMap'} ...\n" ) if $opt_debug;
    open( IMAGEMAP, ">$fileNames{'montageServerMap'}" )
	|| die("$0: Failed to open file $fileNames{'montageServerMap'} for output\n$@\n");
    # default URL
    if (!$opt_frames) {
	$pNumber = ($pageNumber == 1) ? "" : "$pageNumber";
	$pindexname = "${pNumber}${opt_indexname}";
    } else {
	$pindexname = "${opt_pageindexname}${pageNumber}${opt_htmlext}";
    }
    if ( "${opt_htimage}" ne '' ) {
	print( IMAGEMAP "default "
	      . abs_path_to_url("${sourceDirectory}/${pindexname}")
	      . "\n" );
    } else {
	print( IMAGEMAP "default ${pindexname}\n" );
    }
    my $imageNum;
    for( $imageNum = 0; $imageNum <= $#{$imageNames[$pageNumber - 1]}; ++$imageNum ) {
	my $url = escapeurl($imageNames[$pageNumber - 1][$imageNum]);
	my($x1,$y1,$x2,$y2);
	($x1,$y1,$x2,$y2) = split(',', $imageThumbCoords[$pageNumber - 1][$imageNum]);
	if( $opt_pichtml ) {
	    $url .= $opt_pichtmlext;
	}
	if( $opt_maptype eq 'ncsa' ) {
	    if ( "${opt_htimage}" ne '' ) {
		print( IMAGEMAP "rect "
		      . abs_path_to_url("${sourceDirectory}/${url}")
		      . " $x1,$y1 $x2,$y2\n" );
	    } else {
		print( IMAGEMAP "rect ${url} $x1,$y1 $x2,$y2\n" );
	    }
	} elsif ( $opt_maptype eq 'cern' ) {
	    if ( "${opt_htimage}" ne '' ) {
		print( IMAGEMAP "rect ($x1,$y1) ($x2,$y2) "
		      . abs_path_to_url("${sourceDirectory}/${url}")
		      . "\n" );
	    } else {
		print( IMAGEMAP "rect ($x1,$y1) ($x2,$y2) ${url}\n" );
	    }
	} else {
	    die( "\nError: \$opt_maptype must be \"cern\"",
		" or \"ncsa\".\n" );
	}
    }
    close( IMAGEMAP );
    $errorstat=0;
    return( $errorstat );
}

#
# Return string to represent JavaScript array
# Usage: jsArray('arrayName', @array)
#
sub jsArray {
    my($arrayName, @elements) = @_;

    my($result) = "var ${arrayName} = new Array(\n";
    my $i;
    for $i (0 .. $#elements) {
	$result .= "\t\'" . escapejs($elements[$i]) . "\'";
	$result .= ',' if $i < $#elements;
	$result .= "\n";
    }
    $result .= ");\n";
    return $result;
}
#
# Return string to represent JavaScript double-dimensioned array
# Usage: jsDoubleArray('arrayName', @array)
#                 .
sub jsDoubleArray {
    my($arrayName, @elements) = @_;

    my($result) = "var ${arrayName} = new Array(\n";
    my $i;
    for $i (0 .. $#elements) {
	$result .= "\tnew Array(\n";
	my $j;
	for $j (0 .. $#{$elements[$i]}) {
	    $result .= "\t\t\'" . escapejs($elements[$i][$j]) . "\'";
	    $result .= ',' if $j < $#{$elements[$i]};
	    $result .= "\n";
	}
	$result .= "\t)";
	$result .= ',' if $i < $#elements;
	$result .= "\n";
    }
    $result .= ");\n";
    return $result;
}

#
# Return string to represent Javascript array
# from a hash
# Usage: jsHashToDouble('objectName', \%hashName)
#
sub jsHashToDouble {
    my ( $name, $hash) = @_;
    
    my $result = "var ${name} = new Array(\n";
    my $i;
    for $i (0 .. $#imageNames) {
       $result .= "\tnew Array(\n";
       my $j;
       for $j (0 .. $#{$imageNames[$i]}) {       	  
          $result .= "\t\t'";
          if ($$hash{$imageNames[$i][$j]}) {
          	$result .= escapejs($$hash{$imageNames[$i][$j]});
          } else {
          	$result .= "0";
          }
          $result .= "\'";
          $result .= ',' if $j < $#{$imageNames[$i]};
          $result .= "\n";	   
       }
       $result .= "\t)";
	$result .= ',' if $i < $#imageNames;
	$result .= "\n";
    }
    $result .= ");\n";
    
    return $result;
}


#
# Return string to represent 2 Javascript double
# arrays representing width and height
# Usage: jsSizes('objectNamePrefix', \%hashName)
#
sub jsSizes {
    my ( $nameprefix, $hash) = @_;
    
    my $result = "var ${nameprefix}widths = new Array(\n";
    my $i;
    for $i (0 .. $#imageNames) {
       $result .= "\tnew Array(\n";
       my $j;
       for $j (0 .. $#{$imageNames[$i]}) {
       	  if ($$hash{$imageNames[$i][$j]} =~ /WIDTH=([0-9]+)/) {       	  
	          $result .= "\t\t'" . $1 . "\'";
	          $result .= ',' if $j < $#{$imageNames[$i]};
	          $result .= "\n";
	   } else {
	   	$result .= "\t\t'0\'";
	         $result .= ',' if $j < $#{$imageNames[$i]};
	         $result .= "\n";
	   }
       }
       $result .= "\t)";
	$result .= ',' if $i < $#imageNames;
	$result .= "\n";
    }
    $result .= ");\n";
    
    $result .= "\nvar ${nameprefix}heights = new Array(\n";
    for $i (0 .. $#imageNames) {
       $result .= "\tnew Array(\n";
       my $j;
       for $j (0 .. $#{$imageNames[$i]}) {
          if ($$hash{$imageNames[$i][$j]} =~ /HEIGHT=([0-9]+)/) {       	  
	          $result .= "\t\t'" . $1 . "\'";
	          $result .= ',' if $j < $#{$imageNames[$i]};
	          $result .= "\n";
	   } else {
	   	$result .= "\t\t'0\'";
	         $result .= ',' if $j < $#{$imageNames[$i]};
	         $result .= "\n";
	   }
       }
       $result .= "\t)";
	$result .= ',' if $i < $#imageNames;
	$result .= "\n";
    }
    $result .= ");\n";
    
    
    return $result;

}

#
# Return string to represent JavaScript associative array
# Usage: jsHash('objectName', \%hashName)
#
sub jsHash {
    my( $name, $hash) = @_;

    my $result = '';
    $result .= "var ${name} = new Object();\n";
    my $key;
    foreach $key (sort(keys(%$hash))) {
	if(defined($$hash{$key})) {
	    if($$hash{$key} =~ /^\d+$/) {
		# numeric
		$result .= "${name}" . '["' . $key . '"] = ' . escapejs($$hash{$key}) . ";\n";
	    } else {
		# text string gets quoted
		$result .= "${name}" . '["' . $key . '"] = \'' . escapejs($$hash{$key}) . "\';\n";
	    }
	}
    }
    return $result;
}

#
# Return string to represent JavaScript variable
# Usage: jsVariable('variableName', \$variableName)
#
sub jsVariable {
    my( $name, $variable) = @_;
    my $result = "${name} = \'" . escapejs($$variable) . "\';\n";
    return $result;
}

#
# Return string to represent PERL list
# Usage: plArray('arrayName', @array)
#
sub plArray {
    my($arrayName, @elements) = @_;

    my($result) = "\@${arrayName} = (\n";
    my $elem;
    foreach $elem (@elements) {
      if(defined($elem)) {
	$result .= "\t\'" . escapeperl($elem) . "\',\n";
      } else {
	$result .= "\t\'\',\n";
      }
    }
    $result .= ");\n";
    return $result;
}

#
# Return string to represent PERL list of lists
# Usage: plDoubleArray('arrayName', @array)
#
sub plDoubleArray {
    my($arrayName, @elements) = @_;

    my($result) = "\@${arrayName} = (\n";
    my $slow;
    for $slow (0 .. $#elements) {
	$result .= "    [\n";
	my $fast;
	for $fast (0 .. $#{$elements[$slow]}) {
	    #print( "element $slow $fast is $elements[$slow][$fast]\n");
	    $result .= "\t\'" . escapeperl($elements[$slow][$fast]) . "\',\n";
	}
	$result .= "    ],\n";
    }
    $result .= ");\n";
    return $result;
}

#
# Return string to represent PERL associative array
# Usage: plHash('objectName', \%hashName)
#
sub plHash {
    my( $name, $hash) = @_;

    my $result = "\%${name} = (\n";
    my $key;
    foreach $key (sort(keys(%$hash))) {
	if(defined($$hash{$key})) {
	    $result .= "\t\'" . escapeperl($key) . "\' => \'" . escapeperl($$hash{$key}) . "\',\n";
	}
    }
    $result .= ");\n";
    return $result;
}

#
# Return string to represent PERL variable
# Usage: plVariable('variableName', \$variableName)
#
sub plVariable {
    my( $name, $variable) = @_;
    my $result = "\$${name} = \'" . escapeperl($$variable) . "\';\n";
    return $result;
}

#
# Routine used by 'sort' to sort directories
#    %dirOrder		# Order directories are listed in dirindex file
sub sortDir {
    if(defined($dirOrder{$a}) && defined($dirOrder{$b})) {
	$dirOrder{$a} <=> $dirOrder{$b}
    } else {
	lc($a) cmp lc($b);
    }
}

#
# Routine used by 'sort' to sort images
#    %imageOrder	# Order that images occur in imgindex file
sub sortImages {
    if(defined($imageOrder{$a}) && defined($imageOrder{$b})) {
	$imageOrder{$a} <=> $imageOrder{$b}
    } else {
	lc($a) cmp lc($b);
    }
}

#
# Return current directory
#
sub cwd {
    local($_);
    chomp($_ = getcwd());
    # fix path name for Win32
    if ($^O eq "MSWin32") { s:/:\\:g; };
    return $_;
}

#
# Return size of file in bytes (size)
#
sub fsize {
    my($name) = @_;
    return ((stat($name))[7]);
}

#
# Return file modification time (mtime)
#
sub fmtime {
    my($name) = @_;
    return ((lstat($name))[9]);
}

#
# Return file name portion of path
#
sub basename {
   my($name) = @_;
    $name =~ s:([^\/]*/)+::;
    $name =~ s:([^\\]*\\)+::;
    return($name);
}
 
#
# Return directory name portion of path
#
sub dirname {
    my($name) = @_;
    $name =~ s:(/[^\/]+$)::g;
    return($name);
}

#
# Compare two associative arrays
#
sub compareHash {
    my( $hash1, $hash2) = @_;

    if(scalar(keys(%$hash1)) != scalar(keys(%$hash2))) {
	return( 1 ); # different length
    }
    my $key;
    foreach $key (keys(%$hash1)) {
	if(( defined($$hash1{$key}) && !defined($$hash2{$key})) ||
	   ( !defined($$hash1{$key}) && defined($$hash2{$key}))) {
	    return( 1 ); # different
	}

	if( defined($$hash1{$key}) && defined($$hash2{$key}) ) {
	    if($$hash1{$key} ne $$hash2{$key}) {
		return( 1 ); # different
	    }
	}
    }
    return(0);
}

#
# Generic error handler routine to handle Magick reported errors
# Numeric error codes are currently used to aid backward compatability
# in spite of the fact that Magick.pm exports these constants
#
# Returns 0 when normal processing may continue
# Returns 1 when alternative processing (e.g. skip defective image) is recommended
# Exits process for serious problems
#
# Use similar to:
# $status = $image->Read("${imagename}\[0\]");
# handleMagickError( __FILE__, __LINE__, $imagename, $status) if "$status";
#
sub handleMagickError {
    my ($file, $line, $argument, $error) = @_;
    $error =~ /(\d+)/;
    my $errorCode = $1;

    # Benign warnings
    if (
	( $errorCode == 300 ) || # ResourceLimitWarning
	( $errorCode == 305 ) || # XServerWarning
	( $errorCode == 310 ) || # OptionWarning
	( $errorCode == 315 ) || # PluginWarning
	( $errorCode == 320 ) )  # MissingPluginWarning
    {
	warn('PerlMagick:"', $file, '", line ', $line, ', ', $error, "\n");
	return 0;
    }
    # Corrupt image problem
    elsif ( $errorCode == 325 ) { # CorruptImageWarning
	my $imagename = $argument;
	warn("Warning: Image $imagename is defective\n");
	
	# Evaluate contents of $opt_readfailhook if image fails to
	# read A typical action here would be to unlink the image file
	if( $opt_readfailhook ) {
	    print("Evaluating read-failure hook ...\n$opt_readfailhook\n" )
		if $opt_debug;
	    eval $opt_readfailhook;
	}
	return 1;
    }
    # Errors we consider fatal (until proven otherwise)
    # Presumably the user can rectify the problem
    elsif (
	   ( $errorCode == 330 ) || # FileOpenWarning
	   ( $errorCode == 400 ) || # ResourceLimitError
	   ( $errorCode == 405 ) || # XServerError
	   ( $errorCode == 410 )    # OptionError
	   )
    {
	die('PerlMagick fatal error:"', $file, '", line ', $line, ', ', $error, "\n");
    }
    # Other problems
    warn('PerlMagick:"', $file, '", line ', $line, ', ', $error, "\n");
    return 0;
}

#
# Get icon image sizes
# Sets or updates value of %iconImageSizes hash
# Sets the value of $opt_forcehtml to true if existing hash is altered
#
sub getIconImageSizes {
    my %tmp_iconImageSizes;
    my $icon;
    for $icon ( keys %opt_icons ) {
	if( $opt_icons{$icon} ) { # if defined
	    my $icon_path = $icon_dir_path . ${pathSep}.  $opt_icons{$icon};
	    if( !defined( $icon_paths{$icon} ) || ( $icon_paths{$icon} ne $icon_path )) {	    
		$icon_paths{$icon} = $icon_path;	
		$ tmp_iconImageSizes{$icon} = html_imgsize( $icon_path );
	    }
	}
    }
    
    if( !defined(%iconImageSizes) || (  compareHash(\%iconImageSizes,\%tmp_iconImageSizes) ) ) {
	%iconImageSizes = %tmp_iconImageSizes;
	print( STDERR "Icon sizes have changed, must re-do all HTML\n" )
	    if $opt_debug;
	$opt_forcehtml = 1;;
    }    
}

#
# Get the physical path for a specified directory/file path
#
sub lets_get_physical {
    my( $path ) = @_;
    my $physical=$path;
    if( -d $path ) {
        my( $savedir ) = cwd();
        chdir( $path );
        $physical=cwd();
        chdir( $savedir );
    }
    if( -f $path ) {
        my($dir)=dirname($path);
	my($fname)=basename($path);
        my( $savedir ) = cwd();
        chdir( $dir );
        $physical=cwd();
        chdir( $savedir );
	$physical .= "/$fname";
    }
    return( $physical );
}

#
# Attempt to build a relative path from specified directory
# to a file given the specified absolute physical path
#
# Usage:  relative($dir,$path);
#
# Example:  $relative_icon_path = relative($cwd, $absolute_path);
#
sub relative {
    my($dir, $path) = @_;
    my(@path,@dir);
    my $savepath;
    my $result;
    

    # Return value from cache if defined
    my $index = $dir . '|' . $path;
    if( defined($relativePathCache{$index}) ) {
	return( $relativePathCache{$index} );
    }
    
    $savepath=$path;

    # for Win32, just return absolute for now
    if ($^O eq "MSWin32") { return $savepath; }


    # If not rooted then we are hosed
    if ( ( $path !~ m|^/| ) || ($dir !~ m|^/|) ) {
	print( "ERROR: The path \"${savepath}\" is not absolute.\n" );
	die( "Fix the \$opt_rootpath value and try again.\n" );
    }
    
    if( ! -f $path && ! -d $path ) {
	print( "ERROR: The specified path \"${savepath}\" does not exist.\n" );
	die( "Fix the \$opt_rootpath and \opt_iconpath values and try again.\n" );
    }
    
    @path=split('/', $path);	# Array form
    shift(@path);
    @dir=split('/', $dir);	# Array form
    shift(@dir);
    
    # If roots are not the same, then it is not possible to compute a relative path
    # Just return absolute path
    if ( $path[0] ne $dir[0] ) {
	$result = $savepath;
	# Cache and return result
	$relativePathCache{$index} = $result;
	return( $result );
    }
    
    # If the current dir is not inside the document root, just return 
    # the absolute path 
    if ( index ($dir,$opt_rootpath) == -1 ) { 
	$result = $savepath; 
     	# Cache and return result 
	$relativePathCache{$index} = $result; 
	return( $result ); 
    } 

    
    # Remove common start directories
    while( scalar(@path) && scalar(@dir) ) {
	last if( $path[0] ne $dir[0] );
	shift(@path);
	shift(@dir);
    }
    
    # Prepend any ../ part
    grep($_='..',@dir);
    
    # Return results
    if( scalar(@dir) ) {
	$result = join('/',@dir,@path) ;
    } else {
	if( scalar(@path) ) {
	    $result = './' . join('/',@path);
	} else {
	    $result = '.';
	}
    }
    # Cache and return result
    $relativePathCache{$index} = $result;
    return $result;
}

#
# Build a relative path to a file given the absolute physical path
# Uses the option variables $opt_rootpath and $opt_prefixpath
#
sub abs_path_to_url {
    ($_) = @_;
    
    # Remove root prefix if absolute
    my $newfile = ${opt_rootpath};
    $newfile =~ s/\\/\\\\/g;	
    s|^$newfile||;
    
    # Tack on prefix (if any)
    $_ = "${opt_prefixpath}${_}";
    return( $_ );
}

#
# Subroutine to print version information
#
sub version {
    print( STDOUT
qq|webmagick $webmagickInfo{version}
| );
}

#
# Subroutine to print help message
#
# WARNING: help2man not understand multi-line option description!
#
sub help {
    print( STDOUT
qq|WebMagick `webmagick' recurses through directories of images and builds HTML
pages and image-maps to display those images in a web browser. Options may be
specified on the command line as --option or in .webmagickrc files as \$opt_option.
By default WebMagick processes files in the current directory.  See --srcdir for
a way to process files in a different directory.

Usage: webmagick [OPTIONS]

General:
  --[no]debug        Print debug messages (default off)
  --[no]forcecache   Force cached thumbnails to be generated (default off)
  --[no]forcehtml    Force HTML files to be generated (default off)
  --[no]forcemontage Force montage (default off)
  --[no]ignorefp     Ignore directories with names like _vti (FrontPage directories) (default on)
  --[no]help         Display usage message (default off)
  --[no]recurse      Recurse directory tree (default off)
  --srcdir	     Image directory to process
  --[no]verbose      Tell us more ... (default off)
  --[no]version      Print version and exit (default off)

Paths:
  --iconpath	     Relative path under rootdir to webmagick icons
  --iconbase         Global base URL for webmagick icons
  --prefixpath       Path to prepend to generated URLs (e.g. /~username)
  --rootpath	     Absolute path to server root (NCSA DocumentRoot)

Server-side imagemaps:
  --htimage	     Imagemap CGI program URL (set to '' for none)
  --maptype	     Server-side map type ("ncsa" or "cern")
  --[no]serversidemap Enable server-side map writting (default off)

Filenames:
  --dirindexname     Directory-name to title cross-reference file name
  --imgindexname     Image-name to thumbnail label cross-reference file name
  --indexname	     Name of master index files (default server index)
  --pageindexname    Base name of page-related index files
  --readme	     Name of directory info file

Caching:
  --[no]cache        Cache thumbnails (default on)
  --cachedir         Subdirectory name to cache thumbnails in (default .cache)
  --cacheformat      Format of cached thumbnails (default JPEG)
  --cachegeom        Cache thumbnail geometry (default thumbgeom)
  --cachemin         Smallest image to cache in pixels. (default 300*200)

Montage:
  --[no]forcegif     Force imagemap to be in GIF format (default off)
  --[no]forcejpeg    Force imagemap to be in JPEG format (default off)
  --jpegquality      Quality of JPEG imagemaps
  --maxgif           Maximum size of GIF imagemap before trying JPEG
  --columns	     Montage columns
  --rows	     Montage rows (max)
  --[no]mapnetscape  Map generated image files to Netscape 216-color cube (default off)
  --thumbbackground  Montage background color
  --thumbframebgcolor Background color inside of Frame (unused if no Frame)
  --thumbborderwidth Thumbnail border width (pixels)
  --thumbcompose     Thumbnail image composition operation (default Replace) Over, In, Out, Atop, Xor, Plus, Minus, Add, Subtract, Difference, Bumpmap, Replace, MatteReplace, Mask, Blend, Displace
  --thumbfont	     Thumbnail title font
  --thumbforeground  Montage foreground color (effects label color)
  --thumbframe       Geometry of frame around thumbnail (default no frame)
  --thumbgeometry    Thumbnail geometry (widthxheight)
  --thumbgravity     Direction thumbnail gravitates to (default Center) NorthWest, North, NorthEast, West, Center, East, SouthWest, South, SouthEast. North is up.
  --thumblabel       Format for default thumbnail text label
  --thumblabelwidth  Label width (in characters) to truncate to.
  --thumbframecolor  Frame color (if thumbnail frames enabled)
  --thumbshadow      Enable decorative shadow under thumbnail
  --thumbtexture     Texture to tile onto the image background
  --thumbtransparent Transparent color
  --zoomfilter       Zoom filter algorithm (Box/Triangle/Mitchell)

HTML Colors & Appearance:
  --address          Optional user address info
  --[no]anonymous    Don't show WebMagick address and copyright info on pages (default off)
  --coloralink	     Link (active) color
  --colorback	     Background color (also applied to JPEG montage background)
  --colorfore	     Foreground text color
  --colorlink	     Link (unvisited) color
  --colorvlink	     Link (visited) color
  --dircoloralink    Link (active) color (directory frame)
  --dircolorback     Background color (directory frame)
  --dircolorfore     Foreground color (directory frame)
  --dircolorlink     Link (unvisited) color (directory frame)
  --dircolorvlink    Link (visited) color (directory frame)
  --dirhtmlext       Extension for directory frame
  --stylesheet       URL to stylesheet (overrides other color options)

  --[no]date         Output updates date (default on)
  --footer           Page footer (imagemap frame) (default to </CENTER>)
  --header           Page header (imagemap frame) (default to <CENTER>)
  --[no]javascript   Enable JavaScript output (default off)
  --[no]pichtml      Per-image HTML file generation (default off)
  --pichtmlext       Per-image HTML file extension (default .html)
  --pichtmlbottom    Per-image HTML, extra HTML to display below image (default to </CENTER>)
  --[no]pichtmlnav   Per-image HTML, show navigation buttons (default off)
  --[no]pichtmlputtitle Put per-image HTML picture title (default on)
  --pichtmltarget    Per-image HTML default frame target
  --pichtmltitleend  End tags for per-image HTML picture title (default </P>)
  --pichtmltitlestart Start tags for per-image HTML picture title (default <P>)
  --pichtmltop       Per-image HTML, extra HTML to display above image (default to <CENTER>)
  --[no]readmevisible Show README.html on first page rather than just linking (default off)
  --[no]tables       Use HTML tables instead of imagemaps for thumbnails (default off)
  --title            Page title

Frame Options:
  --[no]frames       Use frames, if no - single directory collection assumed (default on)
  --framemarginwidth Pixels allocated to frame margin in horizontal direction
  --framemarginheight Pixels allocated to frame margin in vertical direction
  --framebordersize  Pixels allocated to frame border
  --frameborder      Enable (YES) or disable (NO) decorative frame borders
  --framestyle       Frame style to use (out of those available)
  --[no]allowconfig  Allow user to configure framestyle, columns and rows (requires javascript and tables) (default off)

Messages Replacement:
  --msg_copyright               "Copyright "
  --msg_date_format             "\%B \%e, \%Y" (see strftime(3))
  --msg_directories             "Directories"
  --msg_directory_navigator     "Directory Navigator"
  --msg_images                  "Images"
  --msg_index_of_directory      "Index of directory"
  --msg_index_of_files          "Index of files "
  --msg_index_through           "through"
  --msg_next                    "Next"
  --msg_page_navigator          "Page Navigator"
  --msg_page_updated_on         "Page updated on"
  --msg_prev                    "Prev"
  --msg_produced_by             "Produced by"
  --msg_readme                  "ReadMe"
  --msg_up                      "Up"

Report bugs to <clindell\@users.sourceforge.net>.
Visit the WebMagick web page at http://webmagick.sourceforge.net/
| );
}

#
# Subroutine to calculate per-page file names
# This is so names can be defined in one place
#
sub setFileNames {
    my $nextPageNumber=$pageNumber + 1;
    my $previousPageNumber=$pageNumber - 1;
    my $pNumber;

    if ($opt_javascript) {
	# HTML file to load JavaScript code into hidden Frame
	$fileNames{'jsPageIndex'} = "${opt_pageindexname}js.html";

	# Javascript master index "indexjs.html"
	$fileNames{'jsDirIndex'} = 'indexjs.html';

	# JavaScript variable definitions
	$fileNames{'jsVariables'} = "${opt_pageindexname}.js";

	# Shared WebMagick JavaScript source file
	$fileNames{'jsFunctions'} = "${icon_base_url}${pathSep}webmagick.js";
    }

    # Run status file
    $fileNames{'pageStatus'} = "${opt_pageindexname}.pl";

    # Generated GIF file
    $fileNames{'montageGIF'} = "${opt_pageindexname}${pageNumber}.gif";

    # Generated JPEG file
    $fileNames{'montageJPEG'} = "${opt_pageindexname}${pageNumber}.jpg";

    # Generated server-side imagemap file
    $fileNames{'montageServerMap'} = "${sourceDirectory}${pathSep}${opt_pageindexname}${pageNumber}.map";

    # Name for current HTML index page
    if (!$opt_frames) {
	$pNumber = ($pageNumber == 1) ? "" : "$pageNumber";
	$fileNames{'htmlCurrentIndex'} = "${pNumber}${opt_indexname}";
    } else {
	$fileNames{'htmlCurrentIndex'} = "${opt_pageindexname}${pageNumber}$opt_htmlext";
    }
    
    # Name for next HTML index page
    if($pageNumber == $numPages) {
	$fileNames{'htmlNextIndex'} = '';
    } else {
	if (!$opt_frames) {
	    $pNumber = ($nextPageNumber == 1) ? "" : "$nextPageNumber";
	    $fileNames{'htmlNextIndex'} = "${pNumber}${opt_indexname}";
	} else {
	    $fileNames{'htmlNextIndex'} = "${opt_pageindexname}${nextPageNumber}$opt_htmlext";
	}
    }
    
    # Name for previous HTML index page
    if($pageNumber == 1) {
	$fileNames{'htmlPrevIndex'} = '';
    } else {
	if (!$opt_frames) {
	    $pNumber = ($previousPageNumber == 1) ? "" : "$previousPageNumber";
	    $fileNames{'htmlPrevIndex'} = "${pNumber}${opt_indexname}";
	} else {
	    $fileNames{'htmlPrevIndex'} = "${opt_pageindexname}${previousPageNumber}$opt_htmlext";
	}
    }

    if ($opt_javascript) {
	# Name for next JavaScript HTML index page
	if($pageNumber == $numPages) {
	    $fileNames{'htmlNextJsIndex'} = '';
	} else {
	    $fileNames{'htmlNextJsIndex'} = "${opt_pageindexname}${nextPageNumber}js.html";
	}
    
	# Name for previous Javascript HTML index page
	if($pageNumber == 1) {
	    $fileNames{'htmlPrevJsIndex'} = '';
	} else {
	    $fileNames{'htmlPrevJsIndex'} = "${opt_pageindexname}${previousPageNumber}js.html";
	}
    }

    # Location of blank HTML page for use in Frame target
    $fileNames{'htmlBlankPage'} = "${icon_base_url}/blank.html";
}

#
# Escape special characters in HTML text
#
sub escapehtml {
    local($_) = @_;
    s/&/&amp;/g;
    s/>/&gt;/g;
    s/</&lt;/g;
    return( $_ );
}

#
# Escape characters in strings so they may be eval'ed later in PERL
# 
sub escapeperl {
    local($_) = @_;
    s/\'/\\'/g;
    return( $_ );
}

#
# Escape characters in strings so they may be evaluated by JavaScript
# 
sub escapejs {
    local($_) = @_;
    s/\'/\\'/g;
    #$_ = join(' + \'', split(/^/));
    #s/\n/\\n\'\n/g;
    s/\n/\\n\'\n + \'/g;
    return( $_ );
}

#
# Escape unsafe characters in URLs
#		
sub escapeurl {
    local($_) = @_;
    s/\%/%25/g;		# % (must substitute first!)
    s/\"/%22/g;		# "
    s/\#/%23/g;		# #
    s/\</%3C/g;		# <
    s/\>/%3E/g;		# >
    s/\[/%5B/g;		# [
    #s/\\/%5C/g;		# \
    s/\]/%5D/g;		# ]
    s/\^/%5E/g;		# ^
    s/\`/%60/g;		# `
    s/\{/%7B/g;		# {
    s/\|/%7C/g;		# |
    s/\}/%7D/g;		# }
    s/\~/%7E/g;		# ~
    s/ /%20/g;          # " "
    s:\\:/:g; # replace \ with / for better URLs
    return( $_ );
}

#
# Convert time in seconds to minutes:seconds.hundreths
#
sub elapsedminutes {
    my($seconds) = @_;
    my $min   = int($seconds/60);
    my $sec   = int($seconds%60);
    my $hund  = ($seconds - int($seconds)) * 100;
    return( "${sec}s" ) if $min == 0;
    return( "${min}:" . sprintf( "%02d", $sec ) ) if $hund == 0;
    return( "${min}:" . sprintf( "%02d.%02d", $sec, $hund ) );
}

#
# PERL-based RC file handlers
#

#
# Search for and return the contents of an rc file
# The file handle will be auto-close for next file.
#
sub get_rc {
   my ($rc) = @_;
   my $result = '';
   my $bytes = 0;
   open( RC, "<${rc}" ) || warn("Failed to open file ${rc}\n") && return '';
   $bytes = read( RC, $result, fsize($rc) );
   #print( STDERR "Read ${bytes} bytes from file ${rc}\n" ) if $opt_debug;
   close( RC );
   return $result;
}

#
# Eval .webmagickrc files with specified path. If the file does not
# exist or is not readable, then return silently. If an error occurs,
# then print message and return zero to caller (who can die if deemed
# necessary). This allows statements like:
# $sourceRcFile( $rcfile) || die( "Failed to source $rcfile\n" );
#
sub sourceRcFile {
    my $rc;
    foreach $rc (@_) {
        next if ( ! -r $rc || ! -f $rc );
	print( "Sourcing ${rc}\n" ) if $opt_debug;
	eval ( get_rc($rc) );
	if( $@ ) {
	    print( STDERR "Bad Eval for file \"${rc}\"...\n$@\n" );
	    return( 0 );
	}
    }
    return( 1 );
}

#
# Look in the .webmagickrc file for the given directory and return
# the variable requested, or the default value given. this tries to
# be a bit more intelligent than previous eval.
#   -- added by Anthony Thyssen <anthony@cit.gu.edu.au>
#
# Use as: get_rc_var( directory, variable name, default value)
sub get_rc_var {
   my($dir, $var, $def) = @_;   
   my($rc) = "$dir/$opt_webmagickrc";
   return $def if ( ! -r $rc || ! -f $rc );
   my($val) = ';' . get_rc( $rc );
   $val =~ s/#.*//g;   # remove comments to avoid confusion
   if ( $val =~ /\$$var\b/ > 1 ) {
     print STDERR "Var \"\$$var\" is not simple in \"$rc\" -- using default.\n";
     return $def;
   }
   # find variable assignment if pressent and remove stuff before it
   unless ( $val =~ s/(.|\n)*;\s*\$$var\s*=\s*// ) {
#     print("DB: \$$var not found in \"$rc\"\n")  if $opt_debug;
     return $def;               # variable assignment was not found
   }
   $val =~ s/;(.|\n)*//;   # remove stuff after assignment expression

   # print("Assignment for \$$var = \"$val\"\n") if $opt_debug;

   $val = eval ( $val );
   if ( $@ ) {
     warn("Bad Eval for variable \"\$$var\" in \"$rc\"...\n$@\n");
     $val = $def;
   }

#   print("DB: \$$var found in \"$rc\" with value of \"$val\"\n")
#               if $opt_debug;
   return  $val;
}

#
# Eval PERL-format rc files in order from $opt_rootpath to
# $currentDirectory directory. Values are added to global variables
# 
sub evaluateRcFiles {
    my($dir)  = $currentDirectory;	  # current directory
    my(@top,@dir);
    my $rcpath ;
    my $top;
    my $direlem;

    # Decide how far to look for .webmagickrc files
    # Support the case where processing outside of the server
    # root directory.  In that case, use the sourceDirectory instead.
    if( $currentDirectory =~ m|^$opt_rootpath| ) {
        $top  = $opt_rootpath; # Use server root directory
    } else {
        $top  = $opt_srcdir; # Use specified source directory
    }

    @top=split('/', $top);	# Array form
    shift(@top);
    
    @dir=split('/', $dir);	# Array form
    shift(@dir);

    splice(@dir, 0, scalar(@top) ); # Leave only subdirectory part

    #
    # Build up path starting at top sourcing any .webmagickrc as we go.
    #
    my $path=$top;
    $direlem='';
    do {
	# Certain values must only be vaild in the last
	# current directory webmagickrc file.
        $opt_ignore=0;          # Ignore -- do not process this directory

        if( $direlem ) {
            $path = "$path/$direlem";
        }
        $rcpath = "$path/$opt_webmagickrc";
        sourceRcFile( $rcpath );
    } while( $direlem = shift(@dir) );

    return( 0 );
}

#
# Lookup color in RGB hash table
#
sub lookupRGBColor {
    my($color) = @_;
 
    # If already in hex format, don't translate
    if( $color =~ /^\#/ ) {
        return( "\U$color" );   # just uppercase the color hex value
    } 

    if( defined($RGBDB{"\L$color"}) ) {
        return( $RGBDB{"\L$color"} );
    } else {
        print( STDERR "No such color \"$color\" found\n" );
        return("#BEBEBE");    # Return grey as default in case of error
    }
}

#
# Print details regarding executing child process
# Takes return from system() command or $? as input
# Borrowed from example provided in the Camel book
# (PERL 5) page 230
#

sub syserror {
    my($rc) = @_;

    print("Process exited: ");

    $rc = 0xffff & $rc;
    
    if($rc == 0) {
        print("normal exit\n");
    }
    elsif ($rc == 0xff00) {
        print("command failed: $!\n");
    }
    elsif ($rc > 0x80) {
        $rc >>= 8;
	print("ran with non-zero exit status $rc\n");
    }
    else {
        print("ran with ");
	if ($rc &   0x80) {
	    $rc &= ~0x80;
	    print("coredump from ");
	}
	print("signal $rc\n");
    }
}


