view servlet/src/digilib/servlet/Scaler.java @ 233:e40d8b2e3978

Servlet version 1.19b1 -- with context metadata - reads and caches context tags from index meta file - for use in dlContext-xml.jsp
author robcast
date Sat, 17 Jul 2004 19:25:26 +0200
parents c4ee5f0c3a77
children aaf6eace011d
line wrap: on
line source

/*
 * Scaler -- Scaler servlet main class
 * 
 * Digital Image Library servlet components
 * 
 * Copyright (C) 2001, 2002, 2003 Robert Casties (robcast@mail.berlios.de)
 * 
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 * 
 * Please read license.txt for the full details. A copy of the GPL may be found
 * at http://www.gnu.org/copyleft/lgpl.html
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA
 *  
 */

package digilib.servlet;

import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.IOException;
import java.util.List;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

import digilib.auth.AuthOpException;
import digilib.auth.AuthOps;
import digilib.image.DocuImage;
import digilib.image.DocuInfo;
import digilib.image.ImageLoaderImageInfoDocuInfo;
import digilib.image.ImageOpException;
import digilib.image.ImageSize;
import digilib.io.DocuDirCache;
import digilib.io.FileOpException;
import digilib.io.FileOps;
import digilib.io.ImageFile;
import digilib.io.ImageFileset;

//import tilecachetool.*;

/**
 * @author casties
 */
//public class Scaler extends HttpServlet implements SingleThreadModel {
public class Scaler extends HttpServlet {

	// digilib servlet version (for all components)
	public static final String dlVersion = "1.19b1";

	// logger for accounting requests
	Logger accountlog = Logger.getLogger("account.request");
	// gengeral logger for this class
	Logger logger = Logger.getLogger("digilib.servlet");
	// logger for authentication related
	Logger authlog = Logger.getLogger("digilib.auth");
	
	
	// AuthOps instance
	AuthOps authOp;
	// DocuDirCache instance
	DocuDirCache dirCache;

	// deny image file
	File denyImgFile;
	// error image file
	File errorImgFile;
	// subsampling before scaling
	float minSubsample = 2f;
	// send files as is?
	boolean sendFileAllowed = true;
	// default scaling quality
	int defaultQuality = 1;

	// DigilibConfiguration instance
	DigilibConfiguration dlConfig;

	// use authorization database
	boolean useAuthentication = true;

	// EXPRIMENTAL
	// try to enlarge cropping area for "oblique" angles
	boolean wholeRotArea = false;

	/**
	 * Initialisation on first run.
	 * 
	 * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
	 */
	public void init(ServletConfig config) throws ServletException {
		super.init(config);

		// Debuggin!
		//TCTool tctool = new TCTool();

		System.out.println(
			"***** Digital Image Library Image Scaler Servlet (version "
				+ dlVersion
				+ ") *****");

		// get our ServletContext
		ServletContext context = config.getServletContext();
		// see if there is a Configuration instance
		dlConfig =
			(DigilibConfiguration) context.getAttribute(
				"digilib.servlet.configuration");
		if (dlConfig == null) {
			// create new Configuration
			try {
				dlConfig = new DigilibConfiguration(config);
				context.setAttribute("digilib.servlet.configuration", dlConfig);
			} catch (Exception e) {
				throw new ServletException(e);
			}
		}
		// say hello in the log file
		logger.info("***** Digital Image Library Image Scaler Servlet (version "
				+ dlVersion
				+ ") *****");
		// set our AuthOps
		useAuthentication = dlConfig.getAsBoolean("use-authorization");
		// AuthOps instance
		authOp = (AuthOps) dlConfig.getValue("servlet.auth.op");

		// DocuDirCache instance
		dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache");
		denyImgFile = new File(dlConfig.getAsString("denied-image"));
		errorImgFile = new File(dlConfig.getAsString("error-image"));
		sendFileAllowed = dlConfig.getAsBoolean("sendfile-allowed");
		minSubsample = dlConfig.getAsFloat("subsample-minimum");
		defaultQuality = dlConfig.getAsInt("default-quality");
	}

	/** Process the HTTP Get request */
	public void doGet(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
		accountlog.info("GET from "+request.getRemoteAddr());
		// create new request with defaults
		DigilibRequest dlReq = new DigilibRequest();
		// set with request parameters
		dlReq.setWithRequest(request);
		// add DigilibRequest to ServletRequest
		request.setAttribute("digilib.servlet.request", dlReq);
		// do the processing
		processRequest(request, response);
	}

	/** Process the HTTP Post request */
	public void doPost(
		HttpServletRequest request,
		HttpServletResponse response)
		throws ServletException, IOException {
		accountlog.info("POST from "+request.getRemoteAddr());
		// create new request with defaults
		DigilibRequest dlReq = new DigilibRequest();
		// set with request parameters
		dlReq.setWithRequest(request);
		// add DigilibRequest to ServletRequest
		request.setAttribute("digilib.servlet.request", dlReq);
		// do the processing
		processRequest(request, response);
	}

	/** main request handler. */
	void processRequest(
		HttpServletRequest request,
		HttpServletResponse response)
		throws ServletException, IOException {

		accountlog.debug("request: "+request.getQueryString());
		logger.debug("request: "+request.getQueryString());
		
		// time for benchmarking
		long startTime = System.currentTimeMillis();
		// output mime/type
		String mimeType = "image/png";

		/* parameters for a session */

		// scale the image file to fit window size i.e. respect dw,dh
		boolean scaleToFit = true;
		// scale the image by a fixed factor only
		boolean absoluteScale = false;
		// only crop the image to fit
		boolean cropToFit = false;
		// send the file as is
		boolean sendFile = false;
		// use low resolution images only
		boolean loresOnly = false;
		// use hires images only
		boolean hiresOnly = false;
		// interpolation to use for scaling
		int scaleQual = defaultQuality;
		// send html error message (or image file)
		boolean errorMsgHtml = false;
		// mirror the image
		boolean doMirror = false;
		// angle of mirror axis
		double mirrorAngle = 0;
		// original (hires) image resolution
		double origResX = 0;
		double origResY = 0;

		/* request parameters */

		DigilibRequest dlRequest =
			(DigilibRequest) request.getAttribute("digilib.servlet.request");

		// destination image width
		int paramDW = dlRequest.getAsInt("dw");
		// destination image height
		int paramDH = dlRequest.getAsInt("dh");
		// relative area x_offset (0..1)
		double paramWX = dlRequest.getAsFloat("wx");
		// relative area y_offset
		double paramWY = dlRequest.getAsFloat("wy");
		// relative area width (0..1)
		double paramWW = dlRequest.getAsFloat("ww");
		// relative area height
		double paramWH = dlRequest.getAsFloat("wh");
		// scale factor (additional to dw/width, dh/height)
		double paramWS = dlRequest.getAsFloat("ws");
		// rotation angle
		double paramROT = dlRequest.getAsFloat("rot");
		// contrast enhancement
		float paramCONT = dlRequest.getAsFloat("cont");
		// brightness enhancement
		float paramBRGT = dlRequest.getAsFloat("brgt");
		// color modification
		float[] paramRGBM = null;
		Parameter p = dlRequest.get("rgbm");
		if (p.hasValue() && (!p.getAsString().equals("0/0/0"))) {
			paramRGBM = p.parseAsFloatArray("/");
		}
		float[] paramRGBA = null;
		p = dlRequest.get("rgba");
		if (p.hasValue() && (!p.getAsString().equals("0/0/0"))) {
			paramRGBA = p.parseAsFloatArray("/");
		}
		// destination resolution (DPI)
		float paramDDPIX = dlRequest.getAsFloat("ddpix");
		float paramDDPIY = dlRequest.getAsFloat("ddpiy");
		if ((paramDDPIX == 0) || (paramDDPIY == 0)) {
			// if X or Y resolution isn't set, use DDPI
			paramDDPIX = dlRequest.getAsFloat("ddpi");
			paramDDPIY = paramDDPIX;
		}

		/*
		 * operation mode: "fit": always fit to page, "clip": send original
		 * resolution cropped, "file": send whole file (if allowed)
		 */
		if (dlRequest.hasOption("mo", "clip")) {
			scaleToFit = false;
			absoluteScale = false;
			cropToFit = true;
			sendFile = false;
			hiresOnly = true;
		} else if (dlRequest.hasOption("mo", "fit")) {
			scaleToFit = true;
			absoluteScale = false;
			cropToFit = false;
			sendFile = false;
			hiresOnly = false;
		} else if (dlRequest.hasOption("mo", "osize")) {
			scaleToFit = false;
			absoluteScale = true;
			cropToFit = false;
			sendFile = false;
			hiresOnly = true;
		}
		// operation mode: "lores": try to use scaled image, "hires": use
		// unscaled image
		//   "autores": try best fitting resolution
		if (dlRequest.hasOption("mo", "lores")) {
			loresOnly = true;
			hiresOnly = false;
		} else if (dlRequest.hasOption("mo", "hires")) {
			loresOnly = false;
			hiresOnly = true;
		} else if (dlRequest.hasOption("mo", "autores")) {
			loresOnly = false;
			hiresOnly = false;
		}
		// operation mode: "errtxt": error message in html, "errimg": error
		// image
		if (dlRequest.hasOption("mo", "errtxt")) {
			errorMsgHtml = true;
		} else if (dlRequest.hasOption("mo", "errimg")) {
			errorMsgHtml = false;
		}
		// operation mode: "q0" - "q2": interpolation quality
		if (dlRequest.hasOption("mo", "q0")) {
			scaleQual = 0;
		} else if (dlRequest.hasOption("mo", "q1")) {
			scaleQual = 1;
		} else if (dlRequest.hasOption("mo", "q2")) {
			scaleQual = 2;
		}

		// check with the maximum allowed size (if set)
		int maxImgSize = dlConfig.getAsInt("max-image-size");
		if (maxImgSize > 0) {
			paramDW = (paramDW * paramWS > maxImgSize) ? (int)(maxImgSize / paramWS) : paramDW;
			paramDH = (paramDH * paramWS > maxImgSize) ? (int)(maxImgSize / paramWS) : paramDH;
		}
		
		//"big" try for all file/image actions
		try {

			// ImageFileset of the image to load
			ImageFileset fileset = null;

			// new DocuInfo instance
			DocuInfo docuInfo = new ImageLoaderImageInfoDocuInfo();

			/* find the file to load/send */

			// get PathInfo
			String loadPathName = dlRequest.getFilePath();

			/* check permissions */
			if (useAuthentication) {
				// get a list of required roles (empty if no restrictions)
				List rolesRequired = authOp.rolesForPath(loadPathName, request);
				if (rolesRequired != null) {
					authlog.info("Role required: " + rolesRequired);
					authlog.info("User: " + request.getRemoteUser());
					// is the current request/user authorized?
					if (!authOp.isRoleAuthorized(rolesRequired, request)) {
						// send deny answer and abort
						authlog.error("ERROR: access denied!");
						if (errorMsgHtml) {
							ServletOps.htmlMessage(
								"ERROR: Unauthorized access!",
								response);
						} else {
							ServletOps.sendFile(denyImgFile, null, response);
						}
						return;
					}
				}
			}

			// find the file(set)
			ImageFile fileToLoad;
			fileset =
				(ImageFileset) dirCache.getFile(
					loadPathName,
					dlRequest.getAsInt("pn"),
					FileOps.CLASS_IMAGE);
			if (fileset == null) {
				throw new FileOpException(
					"File "
						+ loadPathName
						+ "("
						+ dlRequest.getAsInt("pn")
						+ ") not found.");
			}

			/* calculate expected source image size */
			ImageSize expectedSourceSize = new ImageSize();
			if (scaleToFit) {
				double scale = (1 / Math.min(paramWW, paramWH)) * paramWS;
				expectedSourceSize.setSize(
					(int) (paramDW * scale),
					(int) (paramDH * scale));
			} else {
				expectedSourceSize.setSize(
					(int) (paramDW * paramWS),
					(int) (paramDH * paramWS));
			}

			/* select a resolution */
			if (hiresOnly) {
				// get first element (= highest resolution)
				fileToLoad = fileset.getBiggest();
			} else if (loresOnly) {
				// enforced lores uses next smaller resolution
				fileToLoad =
					fileset.getNextSmaller(expectedSourceSize, docuInfo);
				if (fileToLoad == null) {
					// this is the smallest we have
					fileToLoad = fileset.getSmallest();
				}
			} else {
				// autores: use next higher resolution
				fileToLoad =
					fileset.getNextBigger(expectedSourceSize, docuInfo);
				if (fileToLoad == null) {
					// this is the highest we have
					fileToLoad = fileset.getBiggest();
				}
			}
			logger.info("Loading: " + fileToLoad.getFile());

			/*
			 * send the image if its mo=(raw)file
			 */
			if (dlRequest.hasOption("mo", "file")
				|| dlRequest.hasOption("mo", "rawfile")) {
				if (sendFileAllowed) {
					String mt = null;
					if (dlRequest.hasOption("mo", "rawfile")) {
						mt = "application/octet-stream";
					}
					ServletOps.sendFile(fileToLoad.getFile(), mt, response);
				}
			}

			/*
			 * prepare resolution for original size
			 */
			if (absoluteScale) {
				// get original resolution from metadata
				fileset.checkMeta();
				origResX = fileset.getResX();
				origResY = fileset.getResY();
				if ((origResX == 0) || (origResY == 0)) {
					throw new ImageOpException("Missing image DPI information!");
				}

				if ((paramDDPIX == 0) || (paramDDPIY == 0)) {
					throw new ImageOpException("Missing display DPI information!");
				}
			}

			// check the source image
			if (!fileToLoad.isChecked()) {
				docuInfo.checkFile(fileToLoad);
			}
			// get the source image type
			mimeType = fileToLoad.getMimetype();
			// get the source image size
			ImageSize imgSize = fileToLoad.getSize();

			// decide if the image can be sent as is
			boolean mimetypeSendable =
				mimeType.equals("image/jpeg")
					|| mimeType.equals("image/png")
					|| mimeType.equals("image/gif");
			boolean imagoOptions =
				dlRequest.hasOption("mo", "hmir")
					|| dlRequest.hasOption("mo", "vmir")
					|| (paramROT != 0)
					|| (paramRGBM != null)
					|| (paramRGBA != null)
					|| (paramCONT != 0)
					|| (paramBRGT != 0);
			boolean imageSendable = mimetypeSendable && !imagoOptions;

			/*
			 * if not autoRes and image smaller than requested size then send
			 * as is. if autoRes and image has requested size then send as is.
			 * if not autoScale and not scaleToFit nor cropToFit then send as
			 * is (mo=file)
			 */
			if (imageSendable
				&& ((loresOnly
					&& fileToLoad.getSize().isSmallerThan(expectedSourceSize))
					|| (!(loresOnly || hiresOnly)
						&& fileToLoad.getSize().fitsIn(expectedSourceSize)))) {

				logger.debug("Sending File as is.");

				ServletOps.sendFile(fileToLoad.getFile(), null, response);

				logger.info("Done in "
						+ (System.currentTimeMillis() - startTime)
						+ "ms");
				return;
			}

			// new DocuImage instance
			DocuImage docuImage = dlConfig.getDocuImageInstance();
			if (docuImage == null) {
				throw new ImageOpException("Unable to load DocuImage class!");
			}

			// set interpolation quality
			docuImage.setQuality(scaleQual);

			// set missing dw or dh from aspect ratio
			double imgAspect = fileToLoad.getAspect();
			if (paramDW == 0) {
				paramDW = (int) Math.round(paramDH * imgAspect);
			} else if (paramDH == 0) {
				paramDH = (int) Math.round(paramDW / imgAspect);
			}

			/* crop and scale the image */

			logger.debug("IMG: " + imgSize.getWidth() + "x" + imgSize.getHeight());
			logger.debug("time " + (System.currentTimeMillis() - startTime) + "ms");

			// coordinates and scaling
			double areaXoff;
			double areaYoff;
			double areaWidth;
			double areaHeight;
			double scaleX;
			double scaleY;
			double scaleXY;

			// coordinates using Java2D
			// image size in pixels
			Rectangle2D imgBounds =
				new Rectangle2D.Double(
					0,
					0,
					imgSize.getWidth(),
					imgSize.getHeight());
			// user window area in [0,1] coordinates
			Rectangle2D relUserArea =
				new Rectangle2D.Double(paramWX, paramWY, paramWW, paramWH);
			// transform from relative [0,1] to image coordinates.
			AffineTransform imgTrafo =
				AffineTransform.getScaleInstance(
					imgSize.getWidth(),
					imgSize.getHeight());
			// transform user coordinate area to image coordinate area
			Rectangle2D userImgArea =
				imgTrafo.createTransformedShape(relUserArea).getBounds2D();

			// calculate scaling factors based on inner user area
			if (scaleToFit) {
				areaWidth = userImgArea.getWidth();
				areaHeight = userImgArea.getHeight();
				scaleX = paramDW / areaWidth * paramWS;
				scaleY = paramDH / areaHeight * paramWS;
				scaleXY = (scaleX > scaleY) ? scaleY : scaleX;
			} else if (absoluteScale) {
				// absolute scale
				scaleX = paramDDPIX / origResX;
				scaleY = paramDDPIY / origResY;
				// currently only same scale :-(
				scaleXY = scaleX;
				areaWidth = paramDW / scaleXY * paramWS;
				areaHeight = paramDH / scaleXY * paramWS;
				// reset user area size
				userImgArea.setRect(
					userImgArea.getX(),
					userImgArea.getY(),
					areaWidth,
					areaHeight);
			} else {
				// crop to fit
				areaWidth = paramDW * paramWS;
				areaHeight = paramDH * paramWS;
				// reset user area size
				userImgArea.setRect(
					userImgArea.getX(),
					userImgArea.getY(),
					areaWidth,
					areaHeight);
				scaleX = 1f;
				scaleY = 1f;
				scaleXY = 1f;
			}

			// enlarge image area for rotations to cover additional pixels
			Rectangle2D outerUserImgArea = userImgArea;
			Rectangle2D innerUserImgArea = userImgArea;
			if (wholeRotArea) {
				if (paramROT != 0) {
					try {
						// rotate user area coordinates around center of user
						// area
						AffineTransform rotTrafo =
							AffineTransform.getRotateInstance(
								Math.toRadians(paramROT),
								userImgArea.getCenterX(),
								userImgArea.getCenterY());
						// get bounds from rotated end position
						innerUserImgArea =
							rotTrafo
								.createTransformedShape(userImgArea)
								.getBounds2D();
						// get bounds from back-rotated bounds
						outerUserImgArea =
							rotTrafo
								.createInverse()
								.createTransformedShape(innerUserImgArea)
								.getBounds2D();
					} catch (NoninvertibleTransformException e1) {
						// this shouldn't happen anyway
						e1.printStackTrace();
					}
				}
			}

			logger.debug("Scale "
					+ scaleXY
					+ "("
					+ scaleX
					+ ","
					+ scaleY
					+ ") on "
					+ outerUserImgArea);

			// clip area at the image border
			outerUserImgArea = outerUserImgArea.createIntersection(imgBounds);

			areaWidth = outerUserImgArea.getWidth();
			areaHeight = outerUserImgArea.getHeight();
			areaXoff = outerUserImgArea.getX();
			areaYoff = outerUserImgArea.getY();

			logger.debug("crop: "
					+ areaXoff
					+ ","
					+ areaYoff
					+ " "
					+ areaWidth
					+ "x"
					+ areaHeight);

			// check image parameters sanity
			if ((areaWidth < 1)
				|| (areaHeight < 1)
				|| (scaleXY * areaWidth < 2)
				|| (scaleXY * areaHeight < 2)) {
				logger.error("ERROR: invalid scale parameter set!");
				throw new ImageOpException("Invalid scale parameter set!");
			}

			/* crop and scale image */

			// use subimage loading if possible
			if (docuImage.isSubimageSupported()) {
				logger.debug("Subimage: scale " + scaleXY + " = " + (1 / scaleXY));
				double subf = 1d;
				double subsamp = 1d;
				if (scaleXY < 1) {
					subf = 1 / scaleXY;
					// for higher quality reduce subsample factor by
					// minSubsample
					if (scaleQual > 0) {
						subsamp = Math.max(Math.floor(subf / minSubsample), 1d);
					} else {
						subsamp = Math.floor(subf);
					}
					scaleXY = subsamp / subf;
					logger.debug("Using subsampling: " + subsamp + " rest " + scaleXY);
				}

				docuImage.loadSubimage(
					fileToLoad,
					outerUserImgArea.getBounds(),
					(int) subsamp);

				logger.debug("SUBSAMP: "
						+ subsamp
						+ " -> "
						+ docuImage.getWidth()
						+ "x"
						+ docuImage.getHeight());

				docuImage.scale(scaleXY, scaleXY);

			} else {
				// else load and crop the whole file
				docuImage.loadImage(fileToLoad);
				docuImage.crop(
					(int) areaXoff,
					(int) areaYoff,
					(int) areaWidth,
					(int) areaHeight);

				docuImage.scale(scaleXY, scaleXY);
			}

			// mirror image
			// operation mode: "hmir": mirror horizontally, "vmir": mirror
			// vertically
			if (dlRequest.hasOption("mo", "hmir")) {
				docuImage.mirror(0);
			}
			if (dlRequest.hasOption("mo", "vmir")) {
				docuImage.mirror(90);
			}

			// rotate image
			if (paramROT != 0d) {
				docuImage.rotate(paramROT);
				if (wholeRotArea) {
					// crop to the inner bounding box
					double xcrop =
						docuImage.getWidth()
							- innerUserImgArea.getWidth() * scaleXY;
					double ycrop =
						docuImage.getHeight()
							- innerUserImgArea.getHeight() * scaleXY;
					if ((xcrop > 0) || (ycrop > 0)) {
						// only crop smaller
						xcrop = (xcrop > 0) ? xcrop : 0;
						ycrop = (ycrop > 0) ? ycrop : 0;
						// crop image
						docuImage.crop(
							(int) (xcrop / 2),
							(int) (ycrop / 2),
							(int) (docuImage.getWidth() - xcrop),
							(int) (docuImage.getHeight() - ycrop));
					}
				}

			}

			// color modification
			if ((paramRGBM != null) || (paramRGBA != null)) {
				// make shure we actually have two arrays
				if (paramRGBM == null) {
					paramRGBM = new float[3];
				}
				if (paramRGBA == null) {
					paramRGBA = new float[3];
				}
				// calculate "contrast" values (c=2^x)
				float[] mult = new float[3];
				for (int i = 0; i < 3; i++) {
					mult[i] = (float) Math.pow(2, (double) paramRGBM[i]);
				}
				docuImage.enhanceRGB(mult, paramRGBA);
			}

			// contrast and brightness enhancement
			if ((paramCONT != 0f) || (paramBRGT != 0f)) {
				double mult = Math.pow(2, paramCONT);
				docuImage.enhance((float) mult, (float) paramBRGT);
			}

			logger.debug("time " + (System.currentTimeMillis() - startTime) + "ms");

			/* write the resulting image */

			// setup output -- if source is JPG then dest will be JPG else it's
			// PNG
			if (mimeType.equals("image/jpeg")
				|| mimeType.equals("image/jp2")) {
				mimeType = "image/jpeg";
			} else {
				mimeType = "image/png";
			}
			response.setContentType(mimeType);

			// write the image
			docuImage.writeImage(mimeType, response.getOutputStream());

			logger.info("Done in " + (System.currentTimeMillis() - startTime) + "ms");
			
			docuImage.dispose();

			/* error handling */

		} // end of "big" try
		catch (IOException e) {
			logger.fatal("ERROR: File IO Error: "+e);
			try {
				if (errorMsgHtml) {
					ServletOps.htmlMessage(
						"ERROR: File IO Error: " + e,
						response);
				} else {
					ServletOps.sendFile(errorImgFile, null, response);
				}
			} catch (FileOpException ex) {
			} // so we don't get a loop
		} catch (AuthOpException e) {
			logger.fatal("ERROR: Authorization error: "+e);
			try {
				if (errorMsgHtml) {
					ServletOps.htmlMessage(
						"ERROR: Authorization error: " + e,
						response);
				} else {
					ServletOps.sendFile(errorImgFile, null, response);
				}
			} catch (FileOpException ex) {
			} // so we don't get a loop
		} catch (ImageOpException e) {
			logger.fatal("ERROR: Image Error: "+e);
			try {
				if (errorMsgHtml) {
					ServletOps.htmlMessage(
						"ERROR: Image Operation Error: " + e,
						response);
				} else {
					ServletOps.sendFile(errorImgFile, null, response);
				}
			} catch (FileOpException ex) {
			} // so we don't get a loop
		} catch (RuntimeException e) {
			// JAI likes to throw RuntimeExceptions ;-(
			logger.fatal("ERROR: Other Image Error: "+e);
			try {
				if (errorMsgHtml) {
					ServletOps.htmlMessage(
						"ERROR: Other Image Operation Error: " + e,
						response);
				} else {
					ServletOps.sendFile(errorImgFile, null, response);
				}
			} catch (FileOpException ex) {
			} // so we don't get a loop
		}
	}

} //Scaler class