# HG changeset patch # User robcast # Date 1287059073 -7200 # Node ID 919e008ab1fb416b96fa26262031484078de9509 # Parent e2ff961001afcd1e2fc3c7eacd465480fbd7c7c6 more steps towards more standard java.util.concurrent design diff -r e2ff961001af -r 919e008ab1fb servlet/.classpath --- 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 @@ - + + + - + - + + + + + + - - + - + - - diff -r e2ff961001af -r 919e008ab1fb servlet/.settings/org.eclipse.jdt.core.prefs --- 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 diff -r e2ff961001af -r 919e008ab1fb servlet/src/digilib/image/ImageLoaderDocuImage.java --- 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!"); diff -r e2ff961001af -r 919e008ab1fb servlet/src/digilib/image/JAIDocuImage.java --- 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(); } } diff -r e2ff961001af -r 919e008ab1fb servlet/src/digilib/image/JAIImageLoaderDocuImage.java --- 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(); } diff -r e2ff961001af -r 919e008ab1fb servlet/src/digilib/servlet/ImageWorker.java --- /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 { + + + 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; + } + +} diff -r e2ff961001af -r 919e008ab1fb servlet/src/digilib/servlet/Initialiser.java --- 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); diff -r e2ff961001af -r 919e008ab1fb servlet/src/digilib/servlet/Scaler.java --- 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 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; - } */ - } diff -r e2ff961001af -r 919e008ab1fb servlet/src/digilib/servlet/ServletOps.java --- 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(""); + out.println("" + title + ""); + out.println(""); + out.println("

" + msg + "

"); + out.println(""); + } - /** - * 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(""); - out.println("" + title + ""); - out.println(""); - out.println("

" + msg + "

"); - out.println(""); - } + /** + * Transfers an image file as-is with the mime type mt. + * + * The local file is copied to the OutputStream of the + * ServletResponse. 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 OutputStream of the - * ServletResponse. 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