changeset 543:919e008ab1fb digilibPDF

more steps towards more standard java.util.concurrent design
author robcast
date Thu, 14 Oct 2010 14:24:33 +0200
parents e2ff961001af
children 5ff500d6812a
files servlet/.classpath servlet/.settings/org.eclipse.jdt.core.prefs servlet/src/digilib/image/ImageLoaderDocuImage.java servlet/src/digilib/image/JAIDocuImage.java servlet/src/digilib/image/JAIImageLoaderDocuImage.java servlet/src/digilib/servlet/ImageWorker.java servlet/src/digilib/servlet/Initialiser.java servlet/src/digilib/servlet/Scaler.java servlet/src/digilib/servlet/ServletOps.java
diffstat 9 files changed, 423 insertions(+), 228 deletions(-) [+]
line wrap: on
line diff
--- a/servlet/.classpath	Wed Oct 13 18:40:54 2010 +0200
+++ b/servlet/.classpath	Thu Oct 14 14:24:33 2010 +0200
@@ -2,33 +2,37 @@
 <classpath>
 	<classpathentry kind="src" path="src"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/imageinfo">
+	<classpathentry kind="con" path="org.eclipse.jst.server.core.container/org.eclipse.jst.server.tomcat.runtimeTarget/Apache Tomcat v7.0"/>
+	<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.module.container"/>
+	<classpathentry exported="true" kind="con" path="org.eclipse.jdt.USER_LIBRARY/ImageInfo">
 		<attributes>
 			<attribute name="org.eclipse.jst.component.dependency" value="../"/>
 		</attributes>
 	</classpathentry>
-	<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/itext">
+	<classpathentry exported="true" kind="con" path="org.eclipse.jdt.USER_LIBRARY/iText">
 		<attributes>
 			<attribute name="org.eclipse.jst.component.dependency" value="../"/>
 		</attributes>
 	</classpathentry>
-	<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/jai">
+	<classpathentry exported="true" kind="con" path="org.eclipse.jdt.USER_LIBRARY/JAI">
+		<attributes>
+			<attribute name="org.eclipse.jst.component.dependency" value="../"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry exported="true" kind="con" path="org.eclipse.jdt.USER_LIBRARY/JAI-ImageIO">
 		<attributes>
 			<attribute name="org.eclipse.jst.component.dependency" value="../"/>
 		</attributes>
 	</classpathentry>
-	<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/jai-imageio"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/jdom">
+	<classpathentry exported="true" kind="con" path="org.eclipse.jdt.USER_LIBRARY/JDOM">
 		<attributes>
 			<attribute name="org.eclipse.jst.component.dependency" value="../"/>
 		</attributes>
 	</classpathentry>
-	<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/log4j">
+	<classpathentry exported="true" kind="con" path="org.eclipse.jdt.USER_LIBRARY/Log4J">
 		<attributes>
 			<attribute name="org.eclipse.jst.component.dependency" value="../"/>
 		</attributes>
 	</classpathentry>
-	<classpathentry kind="con" path="org.eclipse.jst.server.core.container/org.eclipse.jst.server.tomcat.runtimeTarget/Apache Tomcat v7.0"/>
-	<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.module.container"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
--- a/servlet/.settings/org.eclipse.jdt.core.prefs	Wed Oct 13 18:40:54 2010 +0200
+++ b/servlet/.settings/org.eclipse.jdt.core.prefs	Thu Oct 14 14:24:33 2010 +0200
@@ -1,8 +1,12 @@
-#Fri Oct 08 15:52:22 CEST 2010
+#Tue Oct 12 18:02:03 CEST 2010
 eclipse.preferences.version=1
 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
 org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
 org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
 org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
 org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
 org.eclipse.jdt.core.compiler.source=1.6
--- a/servlet/src/digilib/image/ImageLoaderDocuImage.java	Wed Oct 13 18:40:54 2010 +0200
+++ b/servlet/src/digilib/image/ImageLoaderDocuImage.java	Thu Oct 14 14:24:33 2010 +0200
@@ -179,6 +179,7 @@
 			if (img == null) {
 				throw new FileOpException("Unable to load File!");
 			}
+            mimeType = f.getMimetype();
 		} catch (IOException e) {
 			throw new FileOpException("Error reading image.");
 		}
@@ -236,6 +237,7 @@
 			// read image
 			logger.debug("loading..");
 			img = reader.read(0, readParam);
+			mimeType = f.getMimetype();
 			logger.debug("loaded");
 		} catch (IOException e) {
 			throw new FileOpException("Unable to load File!");
--- a/servlet/src/digilib/image/JAIDocuImage.java	Wed Oct 13 18:40:54 2010 +0200
+++ b/servlet/src/digilib/image/JAIDocuImage.java	Thu Oct 14 14:24:33 2010 +0200
@@ -138,6 +138,7 @@
 		if (img == null) {
 			throw new FileOpException("Unable to load File!");
 		}
+        mimeType = f.getMimetype();
 	}
 
 	/* Load an image file into the Object. */
@@ -168,6 +169,7 @@
 			// scale
 			logger.debug("loadSubimage: scale");
 			img = JAI.create("scale", sp);
+            mimeType = f.getMimetype();
 		}
 	}
 
--- a/servlet/src/digilib/image/JAIImageLoaderDocuImage.java	Wed Oct 13 18:40:54 2010 +0200
+++ b/servlet/src/digilib/image/JAIImageLoaderDocuImage.java	Thu Oct 14 14:24:33 2010 +0200
@@ -90,6 +90,7 @@
 		if (img == null) {
 			throw new FileOpException("Unable to load File!");
 		}
+        mimeType = f.getMimetype();
 	}
 
 	/* Get an ImageReader for the image file. */
@@ -140,6 +141,7 @@
 			throw new FileOpException("Unable to load File!");
 		}
 		imgFile = f.getFile();
+        mimeType = f.getMimetype();
 	}
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/servlet/ImageWorker.java	Thu Oct 14 14:24:33 2010 +0200
@@ -0,0 +1,155 @@
+/** Worker (Callable) that renders an image.
+ * 
+ */
+package digilib.servlet;
+
+import java.awt.Rectangle;
+import java.io.IOException;
+import java.util.concurrent.Callable;
+
+import org.apache.log4j.Logger;
+
+import digilib.image.DocuImage;
+import digilib.image.ImageOpException;
+import digilib.io.FileOpException;
+
+/** Worker that renders an image.
+ * 
+ * @author casties
+ *
+ */
+public class ImageWorker implements Callable<DocuImage> {
+
+    
+    protected static Logger logger = Logger.getLogger(ImageWorker.class);
+    private DigilibConfiguration dlConfig;
+    private ImageJobInformation jobinfo;
+
+    public ImageWorker(DigilibConfiguration dlConfig, ImageJobInformation jobinfo) {
+        super();
+        this.dlConfig = dlConfig;
+        this.jobinfo = jobinfo;
+    }
+
+    /**
+     * render and return the image
+     */
+    @Override
+    public DocuImage call() throws FileOpException, IOException, ImageOpException {
+        
+        logger.debug("image worker starting");
+        long startTime = System.currentTimeMillis();
+
+        /* crop and scale image */
+
+        // new DocuImage instance
+        DocuImage docuImage = dlConfig.getDocuImageInstance();
+        if (docuImage == null) {
+            throw new ImageOpException("Unable to load DocuImage class!");
+        }
+
+        // set interpolation quality
+        docuImage.setQuality(jobinfo.get_scaleQual());
+
+        Rectangle loadRect = jobinfo.get_outerUserImgArea().getBounds();
+        float scaleXY = jobinfo.get_scaleXY();
+        
+        // use subimage loading if possible
+        if (docuImage.isSubimageSupported()) {
+            logger.debug("Subimage: scale " + scaleXY + " = " + (1 / scaleXY));
+            float subf = 1f;
+            float subsamp = 1f;
+            if (scaleXY < 1) {
+                subf = 1 / scaleXY;
+                // for higher quality reduce subsample factor by minSubsample
+                if (jobinfo.get_scaleQual() > 0) {
+                    subsamp = (float) Math.max(Math.floor(subf / dlConfig.getAsFloat("subsample-minimum")), 1d);
+                } else {
+                    subsamp = (float) Math.floor(subf);
+                }
+                scaleXY = subsamp / subf;
+                logger.debug("Using subsampling: " + subsamp + " rest "
+                        + scaleXY);
+            }
+
+            docuImage.loadSubimage(jobinfo.get_fileToLoad(), loadRect, (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(jobinfo.get_fileToLoad());
+            docuImage.crop((int) loadRect.getX(), (int) loadRect.getY(),
+                    (int) loadRect.getWidth(), (int) loadRect.getHeight());
+
+            docuImage.scale(scaleXY, scaleXY);
+        }
+
+        // mirror image
+        // operation mode: "hmir": mirror horizontally, "vmir": mirror
+        // vertically
+        if (jobinfo.get_hmir()) {
+            docuImage.mirror(0);
+        }
+        if (jobinfo.get_vmir()) {
+            docuImage.mirror(90);
+        }
+
+        // rotate image
+        if (jobinfo.getRot() != 0d) {
+            docuImage.rotate(jobinfo.getRot());
+            if (jobinfo.get_wholeRotArea()) {
+                // crop to the inner bounding box
+                float xcrop = (float) (docuImage.getWidth() - jobinfo.get_innerUserImgArea().getWidth()
+                        * scaleXY);
+                float ycrop = (float) (docuImage.getHeight() - jobinfo.get_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
+        float[] paramRGBM = jobinfo.get_paramRGBM();
+        float[] paramRGBA = jobinfo.get_paramRGBA();
+        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, (float) paramRGBM[i]);
+            }
+            docuImage.enhanceRGB(mult, paramRGBA);
+        }
+
+        // contrast and brightness enhancement
+        float paramCONT = jobinfo.getCont();
+        float paramBRGT = jobinfo.getBrgt();
+        if ((paramCONT != 0f) || (paramBRGT != 0f)) {
+            float mult = (float) Math.pow(2, paramCONT);
+            docuImage.enhance(mult, paramBRGT);
+        }
+
+        logger.debug("rendered in " + (System.currentTimeMillis() - startTime) + "ms");
+
+        return docuImage;
+    }
+
+}
--- a/servlet/src/digilib/servlet/Initialiser.java	Wed Oct 13 18:40:54 2010 +0200
+++ b/servlet/src/digilib/servlet/Initialiser.java	Thu Oct 14 14:24:33 2010 +0200
@@ -21,6 +21,8 @@
 package digilib.servlet;
 
 import java.io.File;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletContext;
@@ -134,9 +136,11 @@
                 ImageOps.setDocuImage(di);
 				// worker threads
 				int nt = dlConfig.getAsInt("worker-threads");
-				DigilibWorker1.setSemaphore(nt, true);
+				//DigilibWorker1.setSemaphore(nt, true);
+				ExecutorService imageEx = Executors.newFixedThreadPool(nt);
+                dlConfig.setValue("servlet.worker.imageexecutor", imageEx);				
 				int mt = dlConfig.getAsInt("max-waiting-threads");
-				DigilibWorker1.setMaxWaitingThreads(mt);
+				//DigilibWorker1.setMaxWaitingThreads(mt);
 				// set as the servlets main config
 				context.setAttribute("digilib.servlet.configuration", dlConfig);
 
--- a/servlet/src/digilib/servlet/Scaler.java	Wed Oct 13 18:40:54 2010 +0200
+++ b/servlet/src/digilib/servlet/Scaler.java	Thu Oct 14 14:24:33 2010 +0200
@@ -4,6 +4,9 @@
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
 
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletContext;
@@ -13,6 +16,7 @@
 
 import digilib.auth.AuthOpException;
 import digilib.auth.AuthOps;
+import digilib.image.DocuImage;
 import digilib.image.ImageOpException;
 import digilib.io.DocuDirCache;
 import digilib.io.DocuDirectory;
@@ -27,7 +31,7 @@
 public class Scaler extends RequestHandler {
 
 	/** digilib servlet version (for all components) */
-	public static final String dlVersion = "1.8.0a";
+	public static final String dlVersion = "1.8.1a";
 
 	/** general error code */
 	public static final int ERROR_UNKNOWN = 0;
@@ -44,6 +48,9 @@
 	/** DocuDirCache instance */
 	DocuDirCache dirCache;
 
+    /** Image executor */
+    ExecutorService imageJobCenter;
+
 	/** authentication error image file */
 	File denyImgFile;
 
@@ -92,6 +99,7 @@
 		}
 		return mtime;
 	}
+	
 	/**
 	 * Returns the DocuDirent corresponding to the DigilibRequest.
 	 * 
@@ -139,6 +147,10 @@
 
 		// DocuDirCache instance
 		dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache");
+		
+		// Executor
+		imageJobCenter = (ExecutorService) dlConfig.get("servlet.worker.imageexecutor");
+		
 		denyImgFile = ServletOps.getFile((File) dlConfig.getValue("denied-image"), config);
 		errorImgFile = ServletOps.getFile((File) dlConfig.getValue("error-image"), config);
 		notfoundImgFile = ServletOps.getFile((File) dlConfig.getValue("notfound-image"), config);
@@ -213,15 +225,6 @@
 
 		
 		
-		OutputStream outputstream = null;
-		try {
-			outputstream =  response.getOutputStream();
-		} catch (IOException e1) {
-			// TODO Auto-generated catch block
-			e1.printStackTrace();
-			logger.error(e1.getMessage());
-		}
-		
 		if (! DigilibWorker1.canRun()) {
 			logger.error("Servlet overloaded!");
 			try {
@@ -233,11 +236,15 @@
 		}
 
 		
-		DigilibWorker1 job=null;
+		//DigilibWorker1 job=null;
+		ImageWorker job = null;
 		try {
 			
 			long startTime = System.currentTimeMillis();
 
+	        OutputStream outputstream = null;
+	        outputstream =  response.getOutputStream();
+	        
 			/* check permissions */
 			if (useAuthorization) {
 				// get a list of required roles (empty if no restrictions)
@@ -261,44 +268,35 @@
 			}
 
 			
-			job = new DigilibImageWorker1(dlConfig, outputstream , jobdeclaration);
-
-			job.run();
-
-			
-			if (job.hasError()) {
-				throw new ImageOpException(job.getError().toString());
-			}
+			//job = new DigilibImageWorker1(dlConfig, outputstream , jobdeclaration);
+			//job.run();
 
-			try {
-				outputstream.flush();
-				logger.debug("Job Processing Time: "+ (System.currentTimeMillis()-startTime) + "ms");
-			} catch (IOException e) {
-				e.printStackTrace();
-				logger.error(e.getMessage());
-				response.sendError(1);
-			}
-
+			// create job
+			job = new ImageWorker(dlConfig, jobdeclaration);
+			// submit job
+            Future<DocuImage> jobResult = imageJobCenter.submit(job);
+            // wait for result
+            DocuImage img = jobResult.get();
+            // send image
+            ServletOps.writeImage(img, null, outputstream);
+			
+            logger.debug("Job Processing Time: "+ (System.currentTimeMillis()-startTime) + "ms");
 			
 		} catch (IOException e) {
 			e.printStackTrace();
 			logger.error(e.getClass()+": "+ e.getMessage());
 			//response.sendError(1);
-		} catch (ImageOpException e) {
-			e.printStackTrace();
-			logger.error(e.getClass()+": "+ e.getMessage());
-			//response.sendError(1);
-		}
+		} catch (InterruptedException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+            logger.error(e.getClass()+": "+ e.getMessage());
+        } catch (ExecutionException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+            logger.error(e.getClass()+": "+ e.getMessage());
+            logger.error("caused by: "+ e.getCause().getMessage());
+        }
 
-		
-	/*	boolean errorMsgHtml = false;
-		
-		if(jobdeclaration.hasOption("mo","errtxt")){
-			errorMsgHtml = true;
-		} else if (jobdeclaration.hasOption("mo","errimg")) {
-			errorMsgHtml = true;
-		} */
-		
 	}
 	
 	
--- a/servlet/src/digilib/servlet/ServletOps.java	Wed Oct 13 18:40:54 2010 +0200
+++ b/servlet/src/digilib/servlet/ServletOps.java	Thu Oct 14 14:24:33 2010 +0200
@@ -33,193 +33,217 @@
 
 import org.apache.log4j.Logger;
 
+import digilib.image.DocuImage;
+import digilib.image.ImageOps;
 import digilib.io.FileOpException;
 import digilib.io.FileOps;
 
 public class ServletOps {
 
-	private static Logger logger = Logger.getLogger("servlet.op");
+    private static Logger logger = Logger.getLogger("servlet.op");
 
-	/**
-	 * convert a string with a list of pathnames into an array of strings using
-	 * the system's path seperator string
-	 */
-	public static String[] getPathArray(String paths) {
-		// split list into directories
-		StringTokenizer dirs = new StringTokenizer(paths,
-				java.io.File.pathSeparator);
-		int n = dirs.countTokens();
-		if (n < 1) {
-			return null;
-		}
-		// add directories into array
-		String[] pathArray = new String[n];
-		for (int i = 0; i < n; i++) {
-			pathArray[i] = dirs.nextToken();
-		}
-		return pathArray;
-	}
+    /**
+     * convert a string with a list of pathnames into an array of strings using
+     * the system's path seperator string
+     */
+    public static String[] getPathArray(String paths) {
+        // split list into directories
+        StringTokenizer dirs = new StringTokenizer(paths,
+                java.io.File.pathSeparator);
+        int n = dirs.countTokens();
+        if (n < 1) {
+            return null;
+        }
+        // add directories into array
+        String[] pathArray = new String[n];
+        for (int i = 0; i < n; i++) {
+            pathArray[i] = dirs.nextToken();
+        }
+        return pathArray;
+    }
+
+    /**
+     * get a real File for a web app File.
+     * 
+     * If the File is not absolute the path is appended to the base directory of
+     * the web-app.
+     * 
+     * @param file
+     * @param sc
+     * @return
+     */
+    public static File getFile(File f, ServletConfig sc) {
+        // is the filename absolute?
+        if (!f.isAbsolute()) {
+            // relative path -> use getRealPath to resolve in WEB-INF
+            String fn = sc.getServletContext().getRealPath(f.getPath());
+            f = new File(fn);
+        }
+        return f;
+    }
 
-	/**
-	 * get a real File for a web app File.
-	 * 
-	 * If the File is not absolute the path is appended to the base directory
-	 * of the web-app.
-	 * 
-	 * @param file
-	 * @param sc
-	 * @return
-	 */
-	public static File getFile(File f, ServletConfig sc) {
-		// is the filename absolute?
-		if (!f.isAbsolute()) {
-			// relative path -> use getRealPath to resolve in WEB-INF
-			String fn = sc.getServletContext().getRealPath(f.getPath());
-			f = new File(fn);
-		}
-		return f;
-	}
+    /**
+     * get a real file name for a web app file pathname.
+     * 
+     * If filename starts with "/" its treated as absolute else the path is
+     * appended to the base directory of the web-app.
+     * 
+     * @param filename
+     * @param sc
+     * @return
+     */
+    public static String getFile(String filename, ServletConfig sc) {
+        File f = new File(filename);
+        // is the filename absolute?
+        if (!f.isAbsolute()) {
+            // relative path -> use getRealPath to resolve in WEB-INF
+            filename = sc.getServletContext().getRealPath(filename);
+        }
+        return filename;
+    }
 
-	/**
-	 * get a real file name for a web app file pathname.
-	 * 
-	 * If filename starts with "/" its treated as absolute else the path is
-	 * appended to the base directory of the web-app.
-	 * 
-	 * @param filename
-	 * @param sc
-	 * @return
-	 */
-	public static String getFile(String filename, ServletConfig sc) {
-		File f = new File(filename);
-		// is the filename absolute?
-		if (!f.isAbsolute()) {
-			// relative path -> use getRealPath to resolve in WEB-INF
-			filename = sc.getServletContext()
-					.getRealPath(filename);
-		}
-		return filename;
-	}
+    /**
+     * get a real File for a config File.
+     * 
+     * If the File is not absolute the path is appended to the WEB-INF directory
+     * of the web-app.
+     * 
+     * @param file
+     * @param sc
+     * @return
+     */
+    public static File getConfigFile(File f, ServletConfig sc) {
+        String fn = f.getPath();
+        // is the filename absolute?
+        if (f.isAbsolute()) {
+            // does it exist?
+            if (f.canRead()) {
+                // fine
+                return f;
+            } else {
+                // try just the filename as relative
+                fn = f.getName();
+            }
+        }
+        // relative path -> use getRealPath to resolve in WEB-INF
+        String newfn = sc.getServletContext().getRealPath("WEB-INF/" + fn);
+        f = new File(newfn);
+        return f;
+    }
 
-	/**
-	 * get a real File for a config File.
-	 * 
-	 * If the File is not absolute the path is appended to the WEB-INF directory
-	 * of the web-app.
-	 * 
-	 * @param file
-	 * @param sc
-	 * @return
-	 */
-	public static File getConfigFile(File f, ServletConfig sc) {
-	    String fn = f.getPath();
-		// is the filename absolute?
-		if (f.isAbsolute()) {
-		    // does it exist?
-		    if (f.canRead()) {
-		        // fine
-		        return f;
-		    } else {
-		        // try just the filename as relative
-		        fn = f.getName();
-		    }
-		}
-		// relative path -> use getRealPath to resolve in WEB-INF
-		String newfn = sc.getServletContext().getRealPath(
-		        "WEB-INF/" + fn);
-		f = new File(newfn);
-		return f;
-	}
+    /**
+     * get a real file name for a config file pathname.
+     * 
+     * If filename starts with "/" its treated as absolute else the path is
+     * appended to the WEB-INF directory of the web-app.
+     * 
+     * @param filename
+     * @param sc
+     * @return
+     */
+    public static String getConfigFile(String filename, ServletConfig sc) {
+        File f = new File(filename);
+        // is the filename absolute?
+        if (!f.isAbsolute()) {
+            // relative path -> use getRealPath to resolve in WEB-INF
+            filename = sc.getServletContext()
+                    .getRealPath("WEB-INF/" + filename);
+        }
+        return filename;
+    }
 
-	/**
-	 * get a real file name for a config file pathname.
-	 * 
-	 * If filename starts with "/" its treated as absolute else the path is
-	 * appended to the WEB-INF directory of the web-app.
-	 * 
-	 * @param filename
-	 * @param sc
-	 * @return
-	 */
-	public static String getConfigFile(String filename, ServletConfig sc) {
-		File f = new File(filename);
-		// is the filename absolute?
-		if (!f.isAbsolute()) {
-			// relative path -> use getRealPath to resolve in WEB-INF
-			filename = sc.getServletContext()
-					.getRealPath("WEB-INF/" + filename);
-		}
-		return filename;
-	}
+    /**
+     * print a servlet response and exit
+     */
+    public static void htmlMessage(String msg, HttpServletResponse response)
+            throws IOException {
+        htmlMessage("Scaler", msg, response);
+    }
 
-	/**
-	 * print a servlet response and exit
-	 */
-	public static void htmlMessage(String msg, HttpServletResponse response)
-			throws IOException {
-		htmlMessage("Scaler", msg, response);
-	}
+    /**
+     * print a servlet response and exit
+     */
+    public static void htmlMessage(String title, String msg,
+            HttpServletResponse response) throws IOException {
+        response.setContentType("text/html; charset=iso-8859-1");
+        PrintWriter out = response.getWriter();
+        out.println("<html>");
+        out.println("<head><title>" + title + "</title></head>");
+        out.println("<body>");
+        out.println("<p>" + msg + "</p>");
+        out.println("</body></html>");
+    }
 
-	/**
-	 * print a servlet response and exit
-	 */
-	public static void htmlMessage(String title, String msg,
-			HttpServletResponse response) throws IOException {
-		response.setContentType("text/html; charset=iso-8859-1");
-		PrintWriter out = response.getWriter();
-		out.println("<html>");
-		out.println("<head><title>" + title + "</title></head>");
-		out.println("<body>");
-		out.println("<p>" + msg + "</p>");
-		out.println("</body></html>");
-	}
+    /**
+     * Transfers an image file as-is with the mime type mt.
+     * 
+     * The local file is copied to the <code>OutputStream</code> of the
+     * <code>ServletResponse</code>. If mt is null then the mime-type is
+     * auto-detected with mimeForFile.
+     * 
+     * @param mt
+     *            mime-type of the file.
+     * @param f
+     *            Image file to be sent.
+     * @param res
+     *            ServletResponse where the image file will be sent.
+     * @throws FileOpException
+     *             Exception is thrown for a IOException.
+     */
+    public static void sendFile(File f, String mt, HttpServletResponse response)
+            throws FileOpException {
+        logger.debug("sendRawFile(" + mt + ", " + f + ")");
+        if (mt == null) {
+            // auto-detect mime-type
+            mt = FileOps.mimeForFile(f);
+            if (mt == null) {
+                throw new FileOpException("Unknown file type.");
+            }
+        }
+        response.setContentType(mt);
+        // open file
+        try {
+            if (mt.equals("application/octet-stream")) {
+                response.addHeader("Content-Disposition",
+                        "attachment; filename=\"" + f.getName() + "\"");
+            }
+            FileInputStream inFile = new FileInputStream(f);
+            OutputStream outStream = response.getOutputStream();
+            byte dataBuffer[] = new byte[4096];
+            int len;
+            while ((len = inFile.read(dataBuffer)) != -1) {
+                // copy out file
+                outStream.write(dataBuffer, 0, len);
+            }
+            inFile.close();
+            response.flushBuffer();
+        } catch (IOException e) {
+            throw new FileOpException("Unable to send file.");
+        }
+    }
 
-	/**
-	 * Transfers an image file as-is with the mime type mt.
-	 * 
-	 * The local file is copied to the <code>OutputStream</code> of the
-	 * <code>ServletResponse</code>. If mt is null then the mime-type is
-	 * auto-detected with mimeForFile.
-	 * 
-	 * @param mt
-	 *            mime-type of the file.
-	 * @param f
-	 *            Image file to be sent.
-	 * @param res
-	 *            ServletResponse where the image file will be sent.
-	 * @throws FileOpException
-	 *             Exception is thrown for a IOException.
-	 */
-	public static void sendFile(File f, String mt,
-			HttpServletResponse response) throws FileOpException {
-		logger.debug("sendRawFile(" + mt + ", " + f + ")");
-		if (mt == null) {
-			// auto-detect mime-type
-			mt = FileOps.mimeForFile(f);
-			if (mt == null) {
-				throw new FileOpException("Unknown file type.");
-			}
-		}
-		response.setContentType(mt);
-		// open file
-		try {
-			if (mt.equals("application/octet-stream")) {
-				response.addHeader("Content-Disposition",
-						"attachment; filename=\"" + f.getName() + "\"");
-			}
-			FileInputStream inFile = new FileInputStream(f);
-			OutputStream outStream = response.getOutputStream();
-			byte dataBuffer[] = new byte[4096];
-			int len;
-			while ((len = inFile.read(dataBuffer)) != -1) {
-				// copy out file
-				outStream.write(dataBuffer, 0, len);
-			}
-			inFile.close();
-			response.flushBuffer();
-		} catch (IOException e) {
-			throw new FileOpException("Unable to send file.");
-		}
-	}
+    public static void writeImage(DocuImage img, String mimeType, OutputStream outstream) throws FileOpException,
+            IOException {
+        /* write the resulting image */
+
+        // setup output -- if mime type is set use that otherwise
+        // if source is JPG then dest will be JPG else it's PNG
+        if (mimeType == null) {
+            mimeType = img.getMimetype();
+        }
+        if ((mimeType.equals("image/jpeg")
+                || mimeType.equals("image/jp2") || mimeType.equals("image/fpx"))) {
+            mimeType = "image/jpeg";
+        } else {
+            mimeType = "image/png";
+        }
+
+        // write the image
+        img.writeImage(mimeType, outstream);
+        outstream.flush();
+        
+        logger.debug("write image done");
+        img.dispose();
+    }
 
 }
\ No newline at end of file