Mercurial > hg > digilib-old
changeset 339:6d2032b6121d gen2_1
new directory and cache work
line wrap: on
line diff
--- a/servlet/src/digilib/image/ImageLoaderDocuImage.java Thu Jan 17 15:25:46 2002 +0100 +++ b/servlet/src/digilib/image/ImageLoaderDocuImage.java Wed Nov 17 18:17:34 2004 +0100 @@ -1,164 +1,480 @@ /* ImageLoaderDocuImage -- Image class implementation using JDK 1.4 ImageLoader - Digital Image Library servlet components + Digital Image Library servlet components - Copyright (C) 2001, 2002 Robert Casties (robcast@mail.berlios.de) + Copyright (C) 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 + 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 - -*/ + 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.image; -import javax.servlet.*; -import javax.servlet.http.*; -import java.io.*; -import java.util.*; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; +import java.awt.image.ConvolveOp; +import java.awt.image.Kernel; +import java.awt.image.RescaleOp; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.util.Iterator; -import java.awt.*; -import java.awt.image.*; -import java.awt.geom.*; -import java.awt.image.renderable.*; +import javax.imageio.ImageIO; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageReader; +import javax.imageio.stream.FileImageInputStream; +import javax.imageio.stream.ImageInputStream; -import javax.imageio.*; +import digilib.io.FileOpException; +import digilib.io.ImageFile; -import digilib.*; -import digilib.io.*; - +/** Implementation of DocuImage using the ImageLoader API of Java 1.4 and Java2D. */ public class ImageLoaderDocuImage extends DocuImageImpl { - private BufferedImage img; + /** image object */ + protected BufferedImage img; + + /** interpolation type */ + protected RenderingHints renderHint; + + /** ImageIO image reader */ + protected ImageReader reader; + + /** File that was read */ + protected File imgFile; + + /* loadSubimage is supported. */ + public boolean isSubimageSupported() { + return true; + } + + public void setQuality(int qual) { + quality = qual; + renderHint = new RenderingHints(null); + //hint.put(RenderingHints.KEY_ANTIALIASING, + // RenderingHints.VALUE_ANTIALIAS_OFF); + // setup interpolation quality + if (qual > 0) { + logger.debug("quality q1"); + renderHint.put(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BICUBIC); + } else { + logger.debug("quality q0"); + renderHint.put(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + } + } - public ImageLoaderDocuImage() { - } + public int getHeight() { + int h = 0; + try { + if (img == null) { + h = reader.getHeight(0); + } else { + h = img.getHeight(); + } + } catch (IOException e) { + logger.debug("error in getHeight", e); + } + return h; + } - public ImageLoaderDocuImage(Utils u) { - util = u; - } + public int getWidth() { + int w = 0; + try { + if (img == null) { + w = reader.getWidth(0); + } else { + w = img.getWidth(); + } + } catch (IOException e) { + logger.debug("error in getHeight", e); + } + return w; + } + + /* load image file */ + public void loadImage(ImageFile f) throws FileOpException { + logger.debug("loadImage " + f); + //System.gc(); + try { + img = ImageIO.read(f); + if (img == null) { + throw new FileOpException("Unable to load File!"); + } + } catch (IOException e) { + throw new FileOpException("Error reading image."); + } + } - /** - * load image file - */ - public void loadImage(File f) throws FileOpException { - util.dprintln(10, "loadImage!"); - System.gc(); - try { - for (int i = 0; i < ImageIO.getReaderFormatNames().length; i++) { - System.out.println("ImageLoader reader:"+ImageIO.getReaderFormatNames()[i]); - } - for (int i = 0; i < ImageIO.getWriterFormatNames().length; i++) { - System.out.println("ImageLoader writer:"+ImageIO.getWriterFormatNames()[i]); - } - img = ImageIO.read(f); - if (img == null) { - util.dprintln(3, "ERROR(loadImage): unable to load file"); - throw new FileOpException("Unable to load File!"); - } - } - catch (IOException e) { - throw new FileOpException("Error reading image."); - } - } + /** + * Get an ImageReader for the image file. + * + * @return + */ + public ImageReader getReader(ImageFile f) throws IOException { + logger.debug("preloadImage " + f); + if (reader != null) { + logger.debug("Reader was not null!"); + // clean up old reader + dispose(); + } + //System.gc(); + RandomAccessFile rf = new RandomAccessFile(f, "r"); + ImageInputStream istream = new FileImageInputStream(rf); + //Iterator readers = ImageIO.getImageReaders(istream); + String mt = f.getMimetype(); + logger.debug("File type:" + mt); + Iterator readers = ImageIO.getImageReadersByMIMEType(mt); + if (!readers.hasNext()) { + throw new FileOpException("Unable to load File!"); + } + reader = (ImageReader) readers.next(); + /* are there more readers? */ + logger.debug("ImageIO: this reader: " + reader.getClass()); + while (readers.hasNext()) { + logger.debug("ImageIO: next reader: " + readers.next().getClass()); + } + //*/ + reader.setInput(istream); + imgFile = f; + return reader; + } + + /* Load an image file into the Object. */ + public void loadSubimage(ImageFile f, Rectangle region, int prescale) + throws FileOpException { + logger.debug("loadSubimage"); + //System.gc(); + try { + if ((reader == null) || (imgFile != f)) { + getReader(f); + } + // set up reader parameters + ImageReadParam readParam = reader.getDefaultReadParam(); + readParam.setSourceRegion(region); + if (prescale > 1) { + readParam.setSourceSubsampling(prescale, prescale, 0, 0); + } + // read image + logger.debug("loading.."); + img = reader.read(0, readParam); + logger.debug("loaded"); + } catch (IOException e) { + throw new FileOpException("Unable to load File!"); + } + if (img == null) { + throw new FileOpException("Unable to load File!"); + } + } + + /* write image of type mt to Stream */ + public void writeImage(String mt, OutputStream ostream) + throws FileOpException { + logger.debug("writeImage"); + try { + // setup output + String type = "png"; + if (mt == "image/jpeg") { + type = "jpeg"; + } else if (mt == "image/png") { + type = "png"; + } else { + // unknown mime type + throw new FileOpException("Unknown mime type: " + mt); + } + + /* + * JPEG doesn't do transparency so we have to convert any RGBA image + * to RGB :-( *Java2D BUG* + */ + if ((type == "jpeg") && (img.getColorModel().hasAlpha())) { + logger.debug("BARF: JPEG with transparency!!"); + int w = img.getWidth(); + int h = img.getHeight(); + // BufferedImage.TYPE_INT_RGB seems to be fastest (JDK1.4.1, + // OSX) + int destType = BufferedImage.TYPE_INT_RGB; + BufferedImage img2 = new BufferedImage(w, h, destType); + img2.createGraphics().drawImage(img, null, 0, 0); + img = img2; + } + + // render output + logger.debug("writing"); + if (ImageIO.write(img, type, ostream)) { + // writing was OK + return; + } else { + throw new FileOpException( + "Error writing image: Unknown image format!"); + } + } catch (IOException e) { + throw new FileOpException("Error writing image."); + } + } - /** - * write image of type mt to Stream - */ - public void writeImage(String mt, ServletResponse res) - throws FileOpException { - util.dprintln(10, "writeImage!"); - try { - // setup output - String type = "png"; - if (mt == "image/jpeg") { - type = "jpeg"; - } else if (mt == "image/png") { - type = "png"; - } else { - // unknown mime type - util.dprintln(2, "ERROR(writeImage): Unknown mime type "+mt); - throw new FileOpException("Unknown mime type: "+mt); - } - res.setContentType(mt); - // render output - if (ImageIO.write(img, type, res.getOutputStream())) { - // writing was OK - return; - } else { - throw new FileOpException("Error writing image: Unknown image format!"); - } - } catch (IOException e) { - // e.printStackTrace(); - throw new FileOpException("Error writing image."); - } - } + public void scale(double scale, double scaleY) throws ImageOpException { + logger.debug("scale"); + /* for downscaling in high quality the image is blurred first */ + if ((scale <= 0.5) && (quality > 1)) { + int bl = (int) Math.floor(1 / scale); + blur(bl); + } + /* then scaled */ + AffineTransformOp scaleOp = new AffineTransformOp(AffineTransform + .getScaleInstance(scale, scale), renderHint); + BufferedImage scaledImg = null; + // enforce destination image type (*Java2D BUG*) + int type = img.getType(); + // FIXME: which type would be best? + if ((quality > 0) && (type != 0)) { + logger.debug("creating destination image"); + Rectangle2D dstBounds = scaleOp.getBounds2D(img); + scaledImg = new BufferedImage((int) dstBounds.getWidth(), + (int) dstBounds.getHeight(), type); + } + logger.debug("scaling..."); + scaledImg = scaleOp.filter(img, scaledImg); + logger.debug("destination image type " + scaledImg.getType()); + if (scaledImg == null) { + throw new ImageOpException("Unable to scale"); + } + //DEBUG + logger.debug("SCALE: " + scale + " ->" + scaledImg.getWidth() + "x" + + scaledImg.getHeight()); + img = scaledImg; + scaledImg = null; + } - public int getWidth() { - if (img != null) { - return img.getWidth(); - } - return 0; - } + public void blur(int radius) throws ImageOpException { + //DEBUG + logger.debug("blur: " + radius); + // minimum radius is 2 + int klen = Math.max(radius, 2); + // FIXME: use constant kernels for most common sizes + int ksize = klen * klen; + // kernel is constant 1/k + float f = 1f / ksize; + float[] kern = new float[ksize]; + for (int i = 0; i < ksize; i++) { + kern[i] = f; + } + Kernel blur = new Kernel(klen, klen, kern); + // blur with convolve operation + ConvolveOp blurOp = new ConvolveOp(blur, ConvolveOp.EDGE_NO_OP, + renderHint); + // blur needs explicit destination image type for color *Java2D BUG* + BufferedImage blurredImg = null; + if (img.getType() == BufferedImage.TYPE_3BYTE_BGR) { + blurredImg = new BufferedImage(img.getWidth(), img.getHeight(), img + .getType()); + } + blurredImg = blurOp.filter(img, blurredImg); + if (blurredImg == null) { + throw new ImageOpException("Unable to scale"); + } + img = blurredImg; + } + + public void crop(int x_off, int y_off, int width, int height) + throws ImageOpException { + // setup Crop + BufferedImage croppedImg = img.getSubimage(x_off, y_off, width, height); + logger.debug("CROP:" + croppedImg.getWidth() + "x" + + croppedImg.getHeight()); + //DEBUG + // util.dprintln(2, " time + // "+(System.currentTimeMillis()-startTime)+"ms"); + if (croppedImg == null) { + throw new ImageOpException("Unable to crop"); + } + img = croppedImg; + } + + public void enhance(float mult, float add) throws ImageOpException { + /* + * Only one constant should work regardless of the number of bands + * according to the JDK spec. Doesn't work on JDK 1.4 for OSX and Linux + * (at least). RescaleOp scaleOp = new RescaleOp( (float)mult, + * (float)add, null); scaleOp.filter(img, img); + */ + + /* The number of constants must match the number of bands in the image. */ + int ncol = img.getColorModel().getNumComponents(); + float[] dm = new float[ncol]; + float[] da = new float[ncol]; + for (int i = 0; i < ncol; i++) { + dm[i] = (float) mult; + da[i] = (float) add; + } + RescaleOp scaleOp = new RescaleOp(dm, da, null); + scaleOp.filter(img, img); + } + + public void enhanceRGB(float[] rgbm, float[] rgba) throws ImageOpException { + + /* + * The number of constants must match the number of bands in the image. + * We do only 3 (RGB) bands. + */ + + int ncol = img.getColorModel().getNumColorComponents(); + if ((ncol != 3) || (rgbm.length != 3) || (rgba.length != 3)) { + logger + .debug("ERROR(enhance): unknown number of color bands or coefficients (" + + ncol + ")"); + return; + } + RescaleOp scaleOp = new RescaleOp(rgbOrdered(rgbm), rgbOrdered(rgba), + null); + scaleOp.filter(img, img); + } - public int getHeight() { - if (img != null) { - return img.getHeight(); - } - return 0; - } - - /** - * crop and scale image - * take rectangle width,height at position x_off,y_off - * and scale by scale - */ - public void cropAndScale(int x_off, int y_off, int width, int height, - float scale, int qual) throws ImageOpException { - util.dprintln(10, "cropAndScale!"); - - int scaleInt = 0; - // setup interpolation quality - if (qual > 0) { - util.dprintln(4, "quality q1"); - scaleInt = AffineTransformOp.TYPE_BILINEAR; - } else { - util.dprintln(4, "quality q0"); - scaleInt = AffineTransformOp.TYPE_NEAREST_NEIGHBOR; - } + /** + * Ensures that the array f is in the right order to map the images RGB + * components. (not shure what happens + */ + public float[] rgbOrdered(float[] fa) { + /* + * TODO: this is UGLY, UGLY!! + */ + float[] fb; + int t = img.getType(); + if (img.getColorModel().hasAlpha()) { + fb = new float[4]; + if ((t == BufferedImage.TYPE_INT_ARGB) + || (t == BufferedImage.TYPE_INT_ARGB_PRE)) { + // RGB Type + fb[0] = fa[0]; + fb[1] = fa[1]; + fb[2] = fa[2]; + fb[3] = 1f; + } else { + // this isn't tested :-( + fb[0] = 1f; + fb[1] = fa[0]; + fb[2] = fa[1]; + fb[3] = fa[2]; + } + } else { + fb = new float[3]; + if (t == BufferedImage.TYPE_3BYTE_BGR) { + // BGR Type (actually it looks like RBG...) + fb[0] = fa[0]; + fb[1] = fa[2]; + fb[2] = fa[1]; + } else { + fb[0] = fa[0]; + fb[1] = fa[1]; + fb[2] = fa[2]; + } + } + return fb; + } - // setup Crop - BufferedImage croppedImg = img.getSubimage(x_off, y_off, width, height); - - img = null; // free img - util.dprintln(3, "CROP:"+croppedImg.getWidth()+"x"+croppedImg.getHeight()); //DEBUG -// util.dprintln(2, " time "+(System.currentTimeMillis()-startTime)+"ms"); - - if (croppedImg == null) { - util.dprintln(2, "ERROR(cropAndScale): error in crop"); - throw new ImageOpException("Unable to crop"); - } + public void rotate(double angle) throws ImageOpException { + // setup rotation + double rangle = Math.toRadians(angle); + // create offset to make shure the rotated image has no negative + // coordinates + double w = img.getWidth(); + double h = img.getHeight(); + AffineTransform trafo = new AffineTransform(); + // center of rotation + double x = (w / 2); + double y = (h / 2); + trafo.rotate(rangle, x, y); + // try rotation to see how far we're out of bounds + AffineTransformOp rotOp = new AffineTransformOp(trafo, renderHint); + Rectangle2D rotbounds = rotOp.getBounds2D(img); + double xoff = rotbounds.getX(); + double yoff = rotbounds.getY(); + // move image back in line + trafo + .preConcatenate(AffineTransform.getTranslateInstance(-xoff, + -yoff)); + // transform image + rotOp = new AffineTransformOp(trafo, renderHint); + BufferedImage rotImg = rotOp.filter(img, null); + // calculate new bounding box + //Rectangle2D bounds = rotOp.getBounds2D(img); + if (rotImg == null) { + throw new ImageOpException("Unable to rotate"); + } + img = rotImg; + // crop new image (with self-made rounding) + /* + * img = rotImg.getSubimage( (int) (bounds.getX()+0.5), (int) + * (bounds.getY()+0.5), (int) (bounds.getWidth()+0.5), (int) + * (bounds.getHeight()+0.5)); + */ + } - // setup scale - AffineTransformOp scaleOp = new AffineTransformOp( - AffineTransform.getScaleInstance(scale, scale), - scaleInt); - BufferedImage scaledImg = scaleOp.filter(croppedImg, null); - croppedImg = null; // free opCrop + public void mirror(double angle) throws ImageOpException { + // setup mirror + double mx = 1; + double my = 1; + double tx = 0; + double ty = 0; + if (Math.abs(angle - 0) < epsilon) { // 0 degree + mx = -1; + tx = getWidth(); + } else if (Math.abs(angle - 90) < epsilon) { // 90 degree + my = -1; + ty = getHeight(); + } else if (Math.abs(angle - 180) < epsilon) { // 180 degree + mx = -1; + tx = getWidth(); + } else if (Math.abs(angle - 270) < epsilon) { // 270 degree + my = -1; + ty = getHeight(); + } else if (Math.abs(angle - 360) < epsilon) { // 360 degree + mx = -1; + tx = getWidth(); + } + AffineTransformOp mirOp = new AffineTransformOp(new AffineTransform(mx, + 0, 0, my, tx, ty), renderHint); + BufferedImage mirImg = mirOp.filter(img, null); + if (mirImg == null) { + throw new ImageOpException("Unable to mirror"); + } + img = mirImg; + } - if (scaledImg == null) { - util.dprintln(2, "ERROR(cropAndScale): error in scale"); - throw new ImageOpException("Unable to scale"); - } - img = scaledImg; - } + /* + * (non-Javadoc) + * + * @see java.lang.Object#finalize() + */ + protected void finalize() throws Throwable { + dispose(); + super.finalize(); + } + + public void dispose() { + // we must dispose the ImageReader because it keeps the filehandle + // open! + if (reader != null) { + reader.dispose(); + reader = null; + } + img = null; + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/servlet/src/digilib/image/ImageOps.java Wed Nov 17 18:17:34 2004 +0100 @@ -0,0 +1,99 @@ +/* ImageOps -- convenience methods for images + + Digital Image Library servlet components + + Copyright (C) 2004 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 + + * Created on 13.10.2004 + */ +package digilib.image; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.Iterator; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; + +import org.apache.log4j.Logger; +import org.marcoschmidt.image.ImageInfo; + +import digilib.io.FileOpException; +import digilib.io.FileOps; +import digilib.io.ImageFile; + +/** + * convenience methods for images + * + * @author casties + */ +public class ImageOps { + + private static Logger logger = Logger.getLogger(ImageOps.class); + + /** Check and store image size and type of image in ImageFile */ + public static boolean checkFile(ImageFile imgf) throws IOException { + // fileset to store the information + RandomAccessFile raf = new RandomAccessFile(imgf, "r"); + // set up ImageInfo object + ImageInfo iif = new ImageInfo(); + iif.setInput(raf); + iif.setCollectComments(false); + iif.setDetermineImageNumber(false); + logger.debug("identifying (ImageInfo) " + imgf); + // try with ImageInfo first + if (iif.check()) { + ImageSize d = new ImageSize(iif.getWidth(), iif.getHeight()); + imgf.setSize(d); + imgf.setMimetype(iif.getMimeType()); + //logger.debug(" format:"+iif.getFormatName()); + raf.close(); + iif = null; + } else { + iif = null; + logger.debug("identifying (ImageIO) " + imgf); + /* + * else use ImageReader + */ + ImageInputStream istream = ImageIO.createImageInputStream(raf); + Iterator readers = ImageIO.getImageReaders(istream); + if ((readers == null) || (!readers.hasNext())) { + throw new FileOpException("ERROR: unknown image file format!"); + } + ImageReader reader = (ImageReader) readers.next(); + /* are there more readers? */ + logger.debug("ImageIO: this reader: " + reader.getClass()); + while (readers.hasNext()) { + logger.debug("ImageIO: next reader: " + + readers.next().getClass()); + } + reader.setInput(istream); + ImageSize d = new ImageSize(reader.getWidth(0), reader.getHeight(0)); + imgf.setSize(d); + //String t = reader.getFormatName(); + String t = FileOps.mimeForFile(imgf); + imgf.setMimetype(t); + //logger.debug(" format:"+t); + // dispose the reader to free resources + reader.dispose(); + raf.close(); + reader = null; + } + logger.debug("image size: " + imgf.getSize()); + return true; + } + +}
--- a/servlet/src/digilib/image/JAIDocuImage.java Thu Jan 17 15:25:46 2002 +0100 +++ b/servlet/src/digilib/image/JAIDocuImage.java Wed Nov 17 18:17:34 2004 +0100 @@ -2,7 +2,7 @@ Digital Image Library servlet components - Copyright (C) 2001, 2002 Robert Casties (robcast@mail.berlios.de) + 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 @@ -20,149 +20,402 @@ package digilib.image; -import javax.servlet.*; -import javax.servlet.http.*; -import java.io.*; -import java.util.*; +import java.awt.RenderingHints; +import java.awt.image.RenderedImage; +import java.awt.image.renderable.ParameterBlock; +import java.io.IOException; +import java.io.OutputStream; -import java.awt.*; -import java.awt.image.*; -import java.awt.image.renderable.*; -import javax.media.jai.*; +import javax.media.jai.BorderExtender; +import javax.media.jai.Interpolation; +import javax.media.jai.JAI; +import javax.media.jai.KernelJAI; +import javax.media.jai.ParameterBlockJAI; +import javax.media.jai.operator.TransposeDescriptor; +import javax.media.jai.operator.TransposeType; -import digilib.*; -import digilib.io.*; +import digilib.io.ImageFile; +import digilib.io.FileOpException; - +/** A DocuImage implementation using Java Advanced Imaging Library. */ public class JAIDocuImage extends DocuImageImpl { - private RenderedImage img; + protected RenderedImage img; + protected Interpolation interpol = null; + + /* Load an image file into the Object. */ + public void loadImage(ImageFile f) throws FileOpException { + System.gc(); + img = JAI.create("fileload", f.getAbsolutePath()); + if (img == null) { + throw new FileOpException("Unable to load File!"); + } + } - public JAIDocuImage() { - } + /* Write the current image to an OutputStream. */ + public void writeImage(String mt, OutputStream ostream) + throws FileOpException { + try { + // setup output + ParameterBlock pb3 = new ParameterBlock(); + pb3.addSource(img); + pb3.add(ostream); + if (mt == "image/jpeg") { + pb3.add("JPEG"); + } else if (mt == "image/png") { + pb3.add("PNG"); + } else { + // unknown mime type + throw new FileOpException("Unknown mime type: " + mt); + } + // render output + JAI.create("encode", pb3); - public JAIDocuImage(Utils u) { - util = u; - } + } catch (IOException e) { + throw new FileOpException("Error writing image."); + } + } - /** - * load image file - */ - public void loadImage(File f) throws FileOpException { - System.gc(); - img = JAI.create("fileload", f.getAbsolutePath()); - if (img == null) { - util.dprintln(3, "ERROR(loadImage): unable to load file"); - throw new FileOpException("Unable to load File!"); - } - } + /* Real setQuality implementation. + * Creates the correct Interpolation. + */ + public void setQuality(int qual) { + quality = qual; + // setup interpolation quality + if (qual > 1) { + logger.debug("quality q2"); + interpol = Interpolation.getInstance(Interpolation.INTERP_BICUBIC); + } else if (qual == 1) { + logger.debug("quality q1"); + interpol = Interpolation.getInstance(Interpolation.INTERP_BILINEAR); + } else { + logger.debug("quality q0"); + interpol = Interpolation.getInstance(Interpolation.INTERP_NEAREST); + } + } + + /** The width of the curent image in pixel. + * @return Image width in pixels. + */ + public int getWidth() { + if (img != null) { + return img.getWidth(); + } + return 0; + } + + /** The height of the curent image in pixel. + * @return Image height in pixels. + */ + public int getHeight() { + if (img != null) { + return img.getHeight(); + } + return 0; + } - /** - * write image of type mt to Stream - */ - public void writeImage(String mt, ServletResponse res) - throws FileOpException { - try { - // setup output - ParameterBlock pb3 = new ParameterBlock(); - pb3.addSource(img); - pb3.add(res.getOutputStream()); - if (mt == "image/jpeg") { - pb3.add("JPEG"); - } else if (mt == "image/png") { - pb3.add("PNG"); - } else { - // unknown mime type - util.dprintln(2, "ERROR(writeImage): Unknown mime type "+mt); - throw new FileOpException("Unknown mime type: "+mt); - } - res.setContentType(mt); - // render output - JAI.create("encode", pb3); + /* scales the current image */ + public void scale(double scale, double scaleY) throws ImageOpException { + logger.debug("scale"); + if ((scale < 1) + && (img.getColorModel().getPixelSize() == 1) + && (quality > 0)) { + /* + * "SubsampleBinaryToGray" for downscaling BW + */ + scaleBinary((float) scale); + } else if ((scale <= 0.5) && (quality > 1)) { + /* + * blur and "Scale" for downscaling color images + */ + int subsample = (int) Math.floor(1 / scale); + blur(subsample); + scaleAll((float) scale); + } else { + /* + * "Scale" for the rest + */ + scaleAll((float) scale); + } + + //DEBUG + logger.debug("SCALE: " + scale + " ->" + img.getWidth() + "x" + img.getHeight()); + + } + + public void scaleAll(float scale) throws ImageOpException { + RenderedImage scaledImg; + //DEBUG + logger.debug("scaleAll: " + scale); + ParameterBlockJAI param = new ParameterBlockJAI("Scale"); + param.addSource(img); + param.setParameter("xScale", scale); + param.setParameter("yScale", scale); + param.setParameter("interpolation", interpol); + // hint with border extender + RenderingHints hint = + new RenderingHints( + JAI.KEY_BORDER_EXTENDER, + BorderExtender.createInstance(BorderExtender.BORDER_COPY)); + // scale + scaledImg = JAI.create("Scale", param, hint); + + if (scaledImg == null) { + throw new ImageOpException("Unable to scale"); + } + img = scaledImg; + } - } catch (IOException e) { - throw new FileOpException("Error writing image."); - } - } + public void blur(int radius) throws ImageOpException { + RenderedImage blurredImg; + //DEBUG + logger.debug("blur: " + radius); + int klen = Math.max(radius, 2); + int ksize = klen * klen; + float f = 1f / ksize; + float[] kern = new float[ksize]; + for (int i = 0; i < ksize; i++) { + kern[i] = f; + } + KernelJAI blur = new KernelJAI(klen, klen, kern); + ParameterBlockJAI param = new ParameterBlockJAI("Convolve"); + param.addSource(img); + param.setParameter("kernel", blur); + // hint with border extender + RenderingHints hint = + new RenderingHints( + JAI.KEY_BORDER_EXTENDER, + BorderExtender.createInstance(BorderExtender.BORDER_COPY)); + blurredImg = JAI.create("Convolve", param, hint); + if (blurredImg == null) { + throw new ImageOpException("Unable to scale"); + } + img = blurredImg; + } - public int getWidth() { - if (img != null) { - return img.getWidth(); - } - return 0; - } + public void scaleBinary(float scale) throws ImageOpException { + RenderedImage scaledImg; + //DEBUG + logger.debug("scaleBinary: " + scale); + ParameterBlockJAI param = + new ParameterBlockJAI("SubsampleBinaryToGray"); + param.addSource(img); + param.setParameter("xScale", scale); + param.setParameter("yScale", scale); + // hint with border extender + RenderingHints hint = + new RenderingHints( + JAI.KEY_BORDER_EXTENDER, + BorderExtender.createInstance(BorderExtender.BORDER_COPY)); + // scale + scaledImg = JAI.create("SubsampleBinaryToGray", param, hint); + if (scaledImg == null) { + throw new ImageOpException("Unable to scale"); + } + img = scaledImg; + } - public int getHeight() { - if (img != null) { - return img.getHeight(); - } - return 0; - } - + /* crops the current image */ + public void crop(int x_off, int y_off, int width, int height) + throws ImageOpException { + // setup Crop + ParameterBlock param = new ParameterBlock(); + param.addSource(img); + param.add((float) x_off); + param.add((float) y_off); + param.add((float) width); + param.add((float) height); + RenderedImage croppedImg = JAI.create("crop", param); - /** - * crop and scale image - * take rectangle width,height at position x_off,y_off - * and scale by scale - */ - public void cropAndScale(int x_off, int y_off, int width, int height, - float scale, int qual) throws ImageOpException { + logger.debug("CROP: " + + x_off + + "," + + y_off + + ", " + + width + + "," + + height + + " ->" + + croppedImg.getWidth() + + "x" + + croppedImg.getHeight()); + //DEBUG + + if (croppedImg == null) { + throw new ImageOpException("Unable to crop"); + } + img = croppedImg; + } + + /* rotates the current image */ + public void rotate(double angle) throws ImageOpException { + RenderedImage rotImg; + // convert degrees to radians + double rangle = Math.toRadians(angle); + double x = img.getWidth() / 2; + double y = img.getHeight() / 2; - Interpolation scaleInt = null; - // setup interpolation quality - if (qual > 1) { - util.dprintln(4, "quality q2"); - scaleInt = Interpolation.getInstance(Interpolation.INTERP_BICUBIC); - } else if (qual == 1) { - util.dprintln(4, "quality q1"); - scaleInt = Interpolation.getInstance(Interpolation.INTERP_BILINEAR); - } else { - util.dprintln(4, "quality q0"); - scaleInt = Interpolation.getInstance(Interpolation.INTERP_NEAREST); - } + // optimize rotation by right angles + TransposeType rotOp = null; + if (Math.abs(angle - 0) < epsilon) { + // 0 degree + return; + } else if (Math.abs(angle - 90) < epsilon) { + // 90 degree + rotOp = TransposeDescriptor.ROTATE_90; + } else if (Math.abs(angle - 180) < epsilon) { + // 180 degree + rotOp = TransposeDescriptor.ROTATE_180; + } else if (Math.abs(angle - 270) < epsilon) { + // 270 degree + rotOp = TransposeDescriptor.ROTATE_270; + } else if (Math.abs(angle - 360) < epsilon) { + // 360 degree + return; + } + if (rotOp != null) { + // use Transpose operation + ParameterBlock pb = new ParameterBlock(); + pb.addSource(img); + pb.add(rotOp); + rotImg = JAI.create("transpose", pb); + } else { + // setup "normal" rotation + ParameterBlock param = new ParameterBlock(); + param.addSource(img); + param.add((float) x); + param.add((float) y); + param.add((float) rangle); + param.add(interpol); - // setup Crop - ParameterBlock pb1 = new ParameterBlock(); - pb1.addSource(img); - pb1.add((float)x_off); - pb1.add((float)y_off); - pb1.add((float)width); - pb1.add((float)height); - RenderedImage croppedImg = JAI.create("crop", pb1); - img = null; // free img + rotImg = JAI.create("rotate", param); + } + + logger.debug("ROTATE: " + + x + + "," + + y + + ", " + + angle + + " (" + + rangle + + ")" + + " ->" + + rotImg.getWidth() + + "x" + + rotImg.getHeight()); + //DEBUG + + if (rotImg == null) { + throw new ImageOpException("Unable to rotate"); + } + img = rotImg; + } - util.dprintln(3, "CROP:"+croppedImg.getWidth()+"x"+croppedImg.getHeight()); //DEBUG + /* mirrors the current image + * works only horizontal and vertical + */ + public void mirror(double angle) throws ImageOpException { + RenderedImage mirImg; + // only mirroring by right angles + TransposeType rotOp = null; + if (Math.abs(angle) < epsilon) { + // 0 degree + rotOp = TransposeDescriptor.FLIP_HORIZONTAL; + } else if (Math.abs(angle - 90) < epsilon) { + // 90 degree + rotOp = TransposeDescriptor.FLIP_VERTICAL; + } else if (Math.abs(angle - 180) < epsilon) { + // 180 degree + rotOp = TransposeDescriptor.FLIP_HORIZONTAL; + } else if (Math.abs(angle - 270) < epsilon) { + // 270 degree + rotOp = TransposeDescriptor.FLIP_VERTICAL; + } else if (Math.abs(angle - 360) < epsilon) { + // 360 degree + rotOp = TransposeDescriptor.FLIP_HORIZONTAL; + } + // use Transpose operation + ParameterBlock param = new ParameterBlock(); + param.addSource(img); + param.add(rotOp); + mirImg = JAI.create("transpose", param); - if (croppedImg == null) { - util.dprintln(2, "ERROR(cropAndScale): error in crop"); - throw new ImageOpException("Unable to crop"); - } + if (mirImg == null) { + throw new ImageOpException("Unable to flip"); + } + img = mirImg; + } + + /* contrast and brightness enhancement */ + public void enhance(float mult, float add) throws ImageOpException { + RenderedImage enhImg; + double[] ma = { mult }; + double[] aa = { add }; + // use Rescale operation + ParameterBlock param = new ParameterBlock(); + param.addSource(img); + param.add(ma); + param.add(aa); + enhImg = JAI.create("rescale", param); - // setup scale - ParameterBlock pb2 = new ParameterBlock(); - pb2.addSource(croppedImg); - pb2.add(scale); - pb2.add(scale); - pb2.add(0f); - pb2.add(0f); - pb2.add(scaleInt); - // the following is nice but way too slow... - //if (opCrop.getColorModel().getPixelSize() < 8) { - // change color model if necessary - // util.dprintln("converting color model..."); - // BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY); - // ImageLayout lay = new ImageLayout(bi); - // rh = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, lay); - //} - RenderedImage scaledImg = JAI.create("scale", pb2); - croppedImg = null; // free opCrop + logger.debug("ENHANCE: *" + + mult + + ", +" + + add + + " ->" + + enhImg.getWidth() + + "x" + + enhImg.getHeight()); + //DEBUG + + if (enhImg == null) { + throw new ImageOpException("Unable to enhance"); + } + img = enhImg; + } - if (scaledImg == null) { - util.dprintln(2, "ERROR(cropAndScale): error in scale"); - throw new ImageOpException("Unable to scale"); - } + /* (non-Javadoc) + * @see digilib.image.DocuImage#enhanceRGB(float[], float[]) + */ + public void enhanceRGB(float[] rgbm, float[] rgba) + throws ImageOpException { + RenderedImage enhImg; + int nb = rgbm.length; + double[] ma = new double[nb]; + double[] aa = new double[nb]; + for (int i = 0; i < nb; i++) { + ma[i] = rgbm[i]; + aa[i] = rgba[i]; + } + // use Rescale operation + ParameterBlock param = new ParameterBlock(); + param.addSource(img); + param.add(ma); + param.add(aa); + enhImg = JAI.create("rescale", param); - img = scaledImg; - } + logger.debug("ENHANCE_RGB: *" + + rgbm + + ", +" + + rgba + + " ->" + + enhImg.getWidth() + + "x" + + enhImg.getHeight()); + //DEBUG + + if (enhImg == null) { + throw new ImageOpException("Unable to enhanceRGB"); + } + img = enhImg; + } + + /* (non-Javadoc) + * @see digilib.image.DocuImage#dispose() + */ + public void dispose() { + img = null; + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/servlet/src/digilib/image/JAIImageLoaderDocuImage.java Wed Nov 17 18:17:34 2004 +0100 @@ -0,0 +1,184 @@ +/* JAIImageLoaderDocuImage -- Image class implementation using JAI's ImageLoader Plugin + + Digital Image Library servlet components + + Copyright (C) 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.image; + +import java.awt.Rectangle; +import java.awt.image.renderable.ParameterBlock; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.util.Iterator; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageReader; +import javax.imageio.stream.FileImageInputStream; +import javax.imageio.stream.ImageInputStream; +import javax.media.jai.JAI; + +import digilib.io.FileOpException; +import digilib.io.ImageFile; + +/** DocuImage implementation using the Java Advanced Imaging API and the ImageLoader + * API of Java 1.4. + */ +public class JAIImageLoaderDocuImage extends JAIDocuImage { + + /** ImageIO image reader */ + protected ImageReader reader; + /** current image file */ + protected File imgFile; + + /* loadSubimage is supported. */ + public boolean isSubimageSupported() { + return true; + } + + public int getHeight() { + int h = 0; + try { + if (img == null) { + h = reader.getHeight(0); + } else { + h = img.getHeight(); + } + } catch (IOException e) { + logger.debug("error in getHeight", e); + } + return h; + } + + public int getWidth() { + int w = 0; + try { + if (img == null) { + w = reader.getWidth(0); + } else { + w = img.getWidth(); + } + } catch (IOException e) { + logger.debug("error in getHeight", e); + } + return w; + } + + /* Load an image file into the Object. */ + public void loadImage(ImageFile f) throws FileOpException { + logger.debug("loadImage: "+f); + //System.gc(); + img = JAI.create("ImageRead", f.getAbsolutePath()); + if (img == null) { + throw new FileOpException("Unable to load File!"); + } + } + + /* Get an ImageReader for the image file. */ + public ImageReader getReader(ImageFile f) throws IOException { + logger.debug("preloadImage: "+f); + //System.gc(); + RandomAccessFile rf = new RandomAccessFile(f, "r"); + ImageInputStream istream = new FileImageInputStream(rf); + //Iterator readers = ImageIO.getImageReaders(istream); + Iterator readers = ImageIO.getImageReadersByMIMEType(f.getMimetype()); + if (! readers.hasNext()) { + throw new FileOpException("Unable to load File!"); + } + reader = (ImageReader) readers.next(); + logger.debug("JAIImageIO: this reader: " + reader.getClass()); + while (readers.hasNext()) { + logger.debug(" next reader: " + readers.next().getClass()); + } + reader.setInput(istream); + return reader; + } + + /* Load an image file into the Object. */ + public void loadSubimage(ImageFile f, Rectangle region, int prescale) + throws FileOpException { + logger.debug("loadSubimage: "+f); + //System.gc(); + try { + if ((reader == null) || (imgFile != f)) { + getReader(f); + } + ImageReadParam readParam = reader.getDefaultReadParam(); + readParam.setSourceRegion(region); + readParam.setSourceSubsampling(prescale, prescale, 0, 0); + img = reader.read(0, readParam); + /* JAI imageread seems to ignore the readParam :-( + ImageInputStream istream = (ImageInputStream) reader.getInput(); + ParameterBlockJAI pb = new ParameterBlockJAI("imageread"); + pb.setParameter("Input", istream); + pb.setParameter("ReadParam", readParam); + pb.setParameter("Reader", reader); + img = JAI.create("imageread", pb); + */ + } catch (IOException e) { + throw new FileOpException("Unable to load File!"); + } + if (img == null) { + throw new FileOpException("Unable to load File!"); + } + imgFile = f; + } + + + /* Write the current image to an OutputStream. */ + public void writeImage(String mt, OutputStream ostream) + throws FileOpException { + logger.debug("writeImage"); + try { + // setup output + ParameterBlock pb3 = new ParameterBlock(); + pb3.addSource(img); + pb3.add(ostream); + if (mt == "image/jpeg") { + pb3.add("JPEG"); + } else if (mt == "image/png") { + pb3.add("PNG"); + } else { + // unknown mime type + throw new FileOpException("Unknown mime type: " + mt); + } + // render output + JAI.create("ImageWrite", pb3); + } catch (IOException e) { + throw new FileOpException("Error writing image."); + } + } + + /* (non-Javadoc) + * @see java.lang.Object#finalize() + */ + protected void finalize() throws Throwable { + dispose(); + super.finalize(); + } + + public void dispose() { + // we must dispose the ImageReader because it keeps the filehandle open! + reader.dispose(); + reader = null; + img = null; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/servlet/src/digilib/io/AliasingDocuDirCache.java Wed Nov 17 18:17:34 2004 +0100 @@ -0,0 +1,91 @@ +/* + * AliasingDocuDirCache -- DocuDirCache using alias entries from config file + * + * Digital Image Library servlet components + * + * Copyright (C) 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 + * + * Created on 04.11.2003 + */ + +package digilib.io; + +import java.io.File; +import java.util.Iterator; +import java.util.Map; + +/** + * @author casties + * + */ +public class AliasingDocuDirCache extends DocuDirCache { + + /** + * @param baseDirs + * @param fileClasses + * @param confFileName + * @throws FileOpException + */ + public AliasingDocuDirCache(File confFile) throws FileOpException { + // create standard DocuDirCache + super(); + Map pathMap = null; + // read alias config file + try { + // load into pathMap + XMLListLoader mapLoader = new XMLListLoader("digilib-aliases", + "mapping", "link", "dir"); + pathMap = mapLoader.loadURL(confFile.toURL().toString()); + } catch (Exception e) { + throw new FileOpException("ERROR loading mapping file: " + e); + } + if (pathMap == null) { + throw new FileOpException("ERROR: unable to load mapping file!"); + } + + /* + * load map entries into cache + */ + + for (Iterator i = pathMap.keySet().iterator(); i.hasNext();) { + String link = FileOps.normalName((String) i.next()); + String dn = (String) pathMap.get(link); + File dir = FileOps.getRealFile(dn); + if (dir.isDirectory()) { + logger.debug("Aliasing dir: " + link); + DigiDirectory destDir = new DigiDirectory(dir, dn, null); + // add the alias name + putName(link, destDir); + // add the real dir + putDir(destDir); + } + } + } + + /** + * Adds a DocuDirectory under another name to the cache. + * + * @param name + * @param newdir + */ + public void putName(String name, DigiDirectory newdir) { + Object oldkey = map.put(name, newdir); + if (oldkey != null) { + logger + .warn("Duplicate key in AliasingDocuDirCache.put -- replaced!"); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/servlet/src/digilib/io/DigiDirectory.java Wed Nov 17 18:17:34 2004 +0100 @@ -0,0 +1,380 @@ +/* DigiDirectory.java -- digilib directory object + * + * Digital Image Library servlet components + * + * Copyright (C) 2004 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 + * + * Created on 03.11.2004 + */ +package digilib.io; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Map; + +import org.xml.sax.SAXException; + +/** + * digilib directory object + * + * @author casties + * + */ +public class DigiDirectory extends DigiDirent { + + protected static boolean isFile = false; + + protected static boolean isDirectory = true; + + protected DigiDirent[] entries; + + protected int[][] indexes; + + protected File dir; + + /** directory name (digilib canonical form) */ + protected String dlPath = null; + + protected long mtime = 0; + + protected boolean isDirRead = false; + + protected Map unresolvedMeta; + + /** + * @param parent + * @param dir + */ + public DigiDirectory(String dlPath) { + super(FileOps.dlName(dlPath), null); + } + + /** + * @param dir + * @param parent + */ + public DigiDirectory(File dir, DigiDirectory parent) { + super(dir.getName(), parent); + this.dir = dir; + this.dlPath = parent.getDLPath() + "/" + dir.getName(); + } + + /** + * @param dir + * @param dlpath + * @param parent + */ + public DigiDirectory(File dir, String dlpath, DigiDirectory parent) { + super(dir.getName(), parent); + this.dlPath = dlpath; + this.dir = dir; + } + + public boolean exists() { + return ((dir != null) && (dir.isDirectory())); + } + + /** + * Returns the file of the class at the index. + * + * @param index + * @param fc + * fileClass + * @return + */ + public DigiDirent get(int index, int fc) { + if (!isDirRead) { + // read directory now + if (readDir() < 1) { + return null; + } + } + try { + return (DigiDirent) entries[indexes[fc][index]]; + } catch (Exception e) { + } + return null; + } + + /** + * Returns the file with the name <code>fn</code>. + * + * @param fn + * filename + * @return + */ + public DigiDirent get(String fn) { + if (!isDirRead) { + // read directory now + if (readDir() < 1) { + return null; + } + } + // search for exact match + int idx = Arrays.binarySearch(entries, fn); + if (idx >= 0) { + return entries[idx]; + } else { + // try closest matches without extension + idx = -idx - 1; + int imax = entries.length; + String fb = FileOps.basename(fn); + if ((idx < imax) + && (FileOps.basename(entries[idx].getName()).equals(fb))) { + // idx matches + return entries[idx]; + } else if ((idx > 0) + && (FileOps.basename(entries[idx - 1].getName()).equals(fb))) { + // idx-1 matches + return entries[idx - 1]; + } else if ((idx + 1 < imax) + && (FileOps.basename(entries[idx + 1].getName()).equals(fb))) { + // idx+1 matches + return entries[idx + 1]; + } + } + return null; + } + + + /** + * Reads the names of the files in the directory. Fills the filenames array. + * Returns the number of files. + * + * @return + */ + public int readDir() { + if (!exists()) { + return -1; + } + File[] allFiles = null; + // list all files in the directory + allFiles = dir.listFiles(); + if (allFiles == null) { + // not a directory + isDirRead = true; + return -1; + } + Arrays.sort(allFiles); + int nfiles = allFiles.length; + entries = new DigiDirent[nfiles]; + // create new index lists for all file classes + int nfc = FileOps.NUM_CLASSES; + indexes = new int[nfc][nfiles]; + // index pointers for the file classes + int[] lastidx = new int[nfc]; + Map hints = FileOps.newHints(); + // go through all files + for (int dirIdx = 0; dirIdx < nfiles; dirIdx++) { + File f = allFiles[dirIdx]; + String fn = f.getName(); + int fc = FileOps.classForFilename(fn); + // create the right kind of Dirent + DigiDirent df = FileOps.fileForClass(fc, f, this, hints); + // add the file to our list + entries[dirIdx] = df; + // add to the indexes + if (fc >= 0) { + indexes[fc][lastidx[fc]++] = dirIdx; + } + } + // copy out the index arrays + for (int i = 0; i < nfc; i++) { + int[] idxs = new int[lastidx[i]]; + System.arraycopy(indexes[i], 0, idxs, 0, lastidx[i]); + indexes[i] = idxs; + } + // update modification time + mtime = dir.lastModified(); + // read metadata as well + readMeta(); + isDirRead = true; + return nfiles; + } + + /** + * Check to see if the directory has been modified and reread if necessary. + * + * @return boolean the directory is valid + */ + public void check() { + if (isDirRead && (dir.lastModified() > mtime)) { + // on-disk modification time is more recent + readDir(); + } else if (!isDirRead) { + readDir(); + } + } + + /** + * Read directory metadata. + * + */ + public void readMeta() { + // check for directory metadata... + File mf = new File(dir, "index.meta"); + if (mf.canRead()) { + XMLMetaLoader ml = new XMLMetaLoader(); + try { + // read directory meta file + Map fileMeta = ml.loadURL(mf.getAbsolutePath()); + if (fileMeta == null) { + throw new IOException("XMLMetaloader returned no data!"); + } + // meta for the directory itself is in the "" bin + meta = (Map) fileMeta.remove(""); + // read meta for files in this directory + storeFileMeta(fileMeta, null); + // is there meta for other files left? + if (fileMeta.size() > 0) { + unresolvedMeta = fileMeta; + } + } catch (SAXException e) { + logger.warn("error parsing index.meta", e); + } catch (IOException e) { + logger.warn("error reading index.meta", e); + } + } + readParentMeta(); + isMetaRead = true; + } + + /** + * Read metadata from all known parent directories. + * + */ + public void readParentMeta() { + // check the parent directories for additional file meta + DigiDirectory dd = getParent(); + String path = dir.getName(); + while (dd != null) { + if (dd.hasUnresolvedMeta()) { + storeFileMeta(dd.getUnresolvedMeta(), path); + } + // prepend parent dir path + path = dd.getDir().getName() + "/" + path; + // become next parent + dd = dd.getParent(); + } + } + + /** + * Stores metadata in the files in this directory. + * + * Takes a Map with meta-information, adding the relative path before the + * lookup. + * + * @param fileMeta + * @param relPath + * @param fc + * fileClass + */ + protected void storeFileMeta(Map fileMeta, String relPath) { + if (entries == null) { + // there are no files + return; + } + String path = (relPath != null) ? (relPath + "/") : ""; + // iterate through the list of files in this directory + for (int i = 0; i < entries.length; i++) { + DigiDirent f = entries[i]; + // prepend path to the filename + String fn = path + f.getName(); + // look up meta for this file + if (relPath == null) { + // remove from map the same directory + f.addMeta((Map) fileMeta.remove(fn)); + } else { + // copy from map in other directories + f.addMeta((Map) fileMeta.get(fn)); + } + } + } + + /* + * boring getters and setters + */ + + public boolean hasUnresolvedMeta() { + return ((unresolvedMeta != null) && unresolvedMeta.isEmpty()); + } + + /** + * @return Returns the unresolvedMeta. + */ + public Map getUnresolvedMeta() { + return unresolvedMeta; + } + + /** + * @return Returns the dir. + */ + public File getDir() { + return dir; + } + + /** + * @param dir + * The dir to set. + */ + public void setDir(File dir) { + this.dir = dir; + } + + /** + * @return Returns the dlPath. + */ + public String getDLPath() { + return dlPath; + } + + /** + * @param dlPath + * The dlPath to set. + */ + public void setDlPath(String dlPath) { + this.dlPath = dlPath; + } + + /** + * The number of files in this directory. + * + * @return + */ + public int getSize() { + return (entries != null) ? entries.length : 0; + } + + /** + * The number of files of a file class in this directory. + * + * @return + */ + public int getSize(int fc) { + try { + return indexes[fc].length; + } catch (Exception e) { + } + return 0; + } + + /** + * @return Returns the mtime. + */ + public long getMtime() { + return mtime; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/servlet/src/digilib/io/DigiDirent.java Wed Nov 17 18:17:34 2004 +0100 @@ -0,0 +1,191 @@ +/* DigiDirent.java -- an entry in a directory + * + * Digital Image Library servlet components + * + * Copyright (C) 2004 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 + * + * Created on 03.11.2004 + */ + +package digilib.io; + +import java.io.File; +import java.util.Map; + +import org.apache.log4j.Logger; + +/** + * an entry in a directory + * + * @author casties + * + */ +public class DigiDirent implements Comparable { + + protected static Logger logger = Logger.getLogger(DigiDirent.class); + + protected static int fileClass = FileOps.CLASS_NONE; + + protected static boolean isFile = true; + + protected static boolean isDirectory = false; + + protected String name; + + protected DigiDirectory parent; + + protected Map meta; + + protected boolean isMetaRead = false; + + /** + * + */ + public DigiDirent() { + super(); + } + + /** + * @param name + * @param parent + */ + public DigiDirent(String name) { + super(); + this.name = name; + } + + /** + * @param name + * @param parent + */ + public DigiDirent(String name, DigiDirectory parent) { + super(); + this.name = name; + this.parent = parent; + } + + public boolean isFile() { + return isFile; + } + + public boolean isDirectory() { + return isDirectory; + } + + /** + * @return Returns the file class. + */ + public int getFileClass() { + return fileClass; + } + + public String getName() { + return name; + } + + /** + * @return Returns the parent. + */ + public DigiDirectory getParent() { + return parent; + } + + /** + * @param parent + * The parent to set. + */ + public void setParent(DigiDirectory parent) { + this.parent = parent; + } + + /** + * @return Returns the meta. + */ + public Map getMeta() { + return meta; + } + + /** + * @param meta + * The meta to set. + */ + public void setMeta(Map meta) { + this.meta = meta; + } + + /** + * Adds metadata to this files metadata. + * + * @param meta + */ + public void addMeta(Map meta) { + if (meta == null) { + return; + } + if ((this.meta == null) || (this.meta.isEmpty())) { + this.meta = meta; + } else { + this.meta.putAll(meta); + } + } + + /** + * Reads meta-data for this file if there is any. + * + */ + public void readMeta() { + if (isMetaRead || (parent == null) || (!parent.exists())) { + // there is already metadata or there is no file + return; + } + // metadata is in the file {filename}.meta + String fn = parent.getDir().getAbsolutePath(); + File mf = new File(fn, name + ".meta"); + if (mf.canRead()) { + XMLMetaLoader ml = new XMLMetaLoader(); + try { + // read meta file + Map fileMeta = ml.loadURL(mf.getAbsolutePath()); + // meta for this file is in an entry with its name + addMeta((Map)fileMeta.get(name)); + } catch (Exception e) { + Logger.getLogger(this.getClass()).warn( + "error reading file .meta", e); + } + } + isMetaRead = true; + } + + /** + * Checks metadata. + * + */ + public void check() { + if (isMetaRead) { + return; + } + // read the metadata file + readMeta(); + } + + /* (non-Javadoc) + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public int compareTo(Object o) { + // TODO Auto-generated method stub + return (getName().compareTo(o)); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/servlet/src/digilib/io/DocuDirCache.java Wed Nov 17 18:17:34 2004 +0100 @@ -0,0 +1,240 @@ +/* + * DocuDirCache.java + * + * Digital Image Library servlet components + * + * Copyright (C) 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 + * + * Created on 03.03.2003 + */ + +package digilib.io; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import org.apache.log4j.Logger; + +/** + * Cache of digilib directories. + * + * @author casties + */ +public class DocuDirCache { + + /** general logger for this class */ + protected static Logger logger = Logger.getLogger(DocuDirCache.class); + + /** Map of directories */ + Map map = null; + + /** number of files in the whole cache (approximate) */ + long numFiles = 0; + + /** number of cache hits */ + long hits = 0; + + /** number of cache misses */ + long misses = 0; + + /** the root directory element */ + public DigiDirectory rootDir = null; + + /** + * Default constructor. + */ + public DocuDirCache() { + map = new HashMap(); + // add root directory + rootDir = new DigiDirectory(FileOps.getBaseDirs()[0], "", null); + map.put("", rootDir); + } + + /** + * The number of directories in the cache. + * + * @return + */ + public int size() { + return (map != null) ? map.size() : 0; + } + + /** + * Add a DocuDirectory to the cache. + * + * @param newdir + */ + public void put(DigiDirectory newdir) { + String s = newdir.getDLPath(); + Object oldkey = map.put(s, newdir); + if (oldkey != null) { + logger.warn("Duplicate key in DocuDirCache.put -- overwritten!"); + } + numFiles += newdir.getSize(); + } + + /** + * Add a directory to the cache and check its parents. + * + * @param newDir + */ + public synchronized void putDir(DigiDirectory newDir) { + put(newDir); + } + + /** + * Returns a DigiDirectory from the cache. + * + * @param path + * @return + */ + public DigiDirectory get(String path) { + return (DigiDirectory) map.get(path); + } + + /** + * Returns the DigiDirectory indicated by the pathname <code>fn</code>. + * + * If <code>fn</code> is a file then its parent directory is returned. + * + * @param fn + * digilib pathname + * @return + */ + public DigiDirectory getDirectory(String pn) { + /* + * first, assume pn is a directory and look in the cache + */ + DigiDirectory dd = (DigiDirectory) map.get(pn); + if (dd != null) { + // cache hit + hits++; + return dd; + } else { + /* + * maybe it's a file? try the parent directory + */ + String dn = FileOps.dlParent(pn); + // try it in the cache + dd = (DigiDirectory) map.get(dn); + if (dd != null) { + // cache hit + hits++; + return dd; + } else { + // cache miss + misses++; + /* + * try to read from disk + */ + File f = FileOps.getRealFile(pn); + /* + * is it a directory? + */ + if (f.isDirectory()) { + dd = new DigiDirectory(f, pn, null); + // add to the cache + putDir(dd); + return dd; + } else { + /* + * then maybe a file? try the parent as a directory + */ + File d = FileOps.getRealFile(dn); + if (d.isDirectory()) { + dd = new DigiDirectory(d, dn, null); + // add to the cache + putDir(dd); + return dd; + } + } + } + } + /* + * otherwise it's crap + */ + return null; + } + + /** + * Returns the DigiDirent with the pathname <code>fn</code> and the index + * <code>in</code> and the class <code>fc</code>. + * + * If <code>fn</code> is a file then the corresponding DocuDirent is + * returned and the index is ignored. + * + * @param pn + * digilib pathname + * @param in + * file index + * @param fc + * file class + * @return + */ + public DigiDirent getFile(String pn, int in, int fc) { + // file number is 1-based, vector index is 0-based + int n = in - 1; + // get the (parent) directory + DigiDirectory dd = getDirectory(pn); + if (dd != null) { + // get the directory's name + String dn = dd.getDLPath(); + if (dn.equals(pn)) { + // return the file at the index + return dd.get(n, fc); + } else { + // then the last part must be the filename + String fn = FileOps.dlName(pn); + return dd.get(fn); + } + } + return null; + } + + /** + * @return long + */ + public long getNumFiles() { + return numFiles; + } + + /** + * @return long + */ + public long getHits() { + return hits; + } + + /** + * @return long + */ + public long getMisses() { + return misses; + } + + /** + * @return Returns the rootDir. + */ + public DigiDirectory getRootDir() { + return rootDir; + } + /** + * @param rootDir The rootDir to set. + */ + public void setRootDir(DigiDirectory rootDir) { + this.rootDir = rootDir; + } +}
--- a/servlet/src/digilib/io/FileOps.java Thu Jan 17 15:25:46 2002 +0100 +++ b/servlet/src/digilib/io/FileOps.java Wed Nov 17 18:17:34 2004 +0100 @@ -1,188 +1,675 @@ -/* FileOps -- Utility class for file operations - - Digital Image Library servlet components - - Copyright (C) 2001, 2002 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 - -*/ +/* + * FileOps -- Utility class for file operations + * + * Digital Image Library servlet components + * + * Copyright (C) 2001, 2002 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.io; -import java.io.*; -import java.util.*; +import java.io.File; +import java.io.FileFilter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; -import digilib.*; - +import org.apache.log4j.Logger; public class FileOps { - private Utils util = null; - public static String[] fileTypes = { - "jpg", "image/jpeg", - "jpeg", "image/jpeg", - "png", "image/png", - "gif", "image/gif", - "tif", "image/tiff", - "tiff", "image/tiff"}; + private static Logger logger = Logger.getLogger(FileOps.class); + + /** + * Array of file extensions and corresponding mime-types. + */ + private static String[][] ft = { { "jpg", "image/jpeg" }, + { "jpeg", "image/jpeg" }, { "jp2", "image/jp2" }, + { "png", "image/png" }, { "gif", "image/gif" }, + { "tif", "image/tiff" }, { "tiff", "image/tiff" }, + { "txt", "text/plain" }, { "html", "text/html" }, + { "htm", "text/html" }, { "xml", "text/xml" }, + { "svg", "image/svg+xml" } }; + + public static Map fileTypes; + + public static List imageExtensions; + + public static List textExtensions; + + public static List svgExtensions; + + public static final int CLASS_NONE = -1; + + public static final int CLASS_IMAGE = 0; + + public static final int CLASS_TEXT = 1; + + public static final int CLASS_SVG = 2; + + public static final int NUM_CLASSES = 3; - public FileOps() { - util = new Utils(); - } + public static int[] fileClasses = {}; + + public static int[] fcIndexes = {}; + + public static final Integer HINT_BASEDIRS = new Integer(1); + + public static final Integer HINT_FILEEXT = new Integer(2); + + public static final Integer HINT_DIRS = new Integer(3); + + public static File[] baseDirs = {}; + + public static DocuDirCache cache; - public FileOps(Utils u) { - util = u; - } + /** + * static initializer for FileOps + */ + static { + fileTypes = new HashMap(); + imageExtensions = new ArrayList(); + textExtensions = new ArrayList(); + svgExtensions = new ArrayList(); + // iterate through file types in ft and fill the Map and Lists + for (int i = 0; i < ft.length; i++) { + String ext = ft[i][0]; + String mt = ft[i][1]; + fileTypes.put(ext, mt); + if (classForMimetype(mt) == CLASS_IMAGE) { + imageExtensions.add(ext); + } else if (classForMimetype(mt) == CLASS_TEXT) { + textExtensions.add(ext); + } else if (classForMimetype(mt) == CLASS_SVG) { + svgExtensions.add(ext); + } + } + } - public void setUtils(Utils u) { - util = u; - } + /** sets the array of actually used file classes */ + public static void setFileClasses(int[] fc) { + fileClasses = fc; + fcIndexes = new int[NUM_CLASSES]; + for (int i = 0; i < fc.length; i++) { + fcIndexes[fc[i]] = i; + } + } + /** returns the array of actually used file classes */ + public static int[] getFileClasses() { + return fileClasses; + } + + /** returns an element from the array of actally used file classes */ + public static int getFileClass(int idx) { + try { + return fileClasses[idx]; + } catch (Exception e) { + } + return CLASS_NONE; + } - /** - * get the mime type for a file format (by extension) - */ - public static String mimeForFile(File f) { - String fn = f.getName(); - for (int i = 0; i < fileTypes.length; i += 2) { - if (fn.toLowerCase().endsWith(fileTypes[i])) { - return fileTypes[i+1]; - } - } - return null; - } + /** Returns the index number for the given file class. + * @param fc + * @return + */ + public static int getFCindex(int fc) { + try { + return fcIndexes[fc]; + } catch (Exception e) { + } + return -1; + } + + /** + * returns the file class for a mime-type + * + * @param mt + * @return + */ + public static int classForMimetype(String mt) { + if (mt == null) { + return CLASS_NONE; + } + if (mt.startsWith("image/svg")) { + return CLASS_SVG; + } else if (mt.startsWith("image")) { + return CLASS_IMAGE; + } else if (mt.startsWith("text")) { + return CLASS_TEXT; + } + return CLASS_NONE; + } + + /** + * get the mime type for a file format (by extension) + */ + public static String mimeForFile(File f) { + return (String) fileTypes.get(extname(f.getName().toLowerCase())); + } + + /** + * get the file class for the filename (by extension) + * + * @param fn + * @return + */ + public static int classForFilename(String fn) { + String mt = (String) fileTypes.get(extname(fn).toLowerCase()); + return classForMimetype(mt); + } - /** - * get a filehandle for a file or directory name - * returns File number n if fn is directory (starts with 1) - */ - public File getFile(String fn, int n) throws FileOpException { - util.dprintln(4, "getFile ("+fn+", "+n+")"); + /** + * get the file class for the file (by extension) + * + * @param fn + * @return + */ + public static int classForFile(File f) { + return classForFilename(f.getName()); + } + + public static Iterator getImageExtensionIterator() { + return imageExtensions.iterator(); + } + + public static Iterator getTextExtensionIterator() { + return textExtensions.iterator(); + } + + public static Iterator getSVGExtensionIterator() { + return svgExtensions.iterator(); + } + + /** + * convert a string with a list of pathnames into an array of strings using + * the system's path separator string + */ + public static String[] pathToArray(String paths) { + // split list into directories + StringTokenizer dirs = new StringTokenizer(paths, 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++) { + String s = dirs.nextToken(); + // make shure the dir name ends with a directory separator + if (s.endsWith(File.separator)) { + pathArray[i] = s; + } else { + pathArray[i] = s + File.separator; + } + } + return pathArray; + } + + /** + * Extract the base of a file name (sans extension). + * + * Returns the filename without the extension. The extension is the part + * behind the last dot in the filename. If the filename has no dot the full + * file name is returned. + * + * @param fn + * @return + */ + public static String basename(String fn) { + if (fn == null) { + return null; + } + int i = fn.lastIndexOf('.'); + if (i > 0) { + return fn.substring(0, i); + } + return fn; + } - File f = new File(fn); - // if fn is a file name then return file - if (f.isFile()) { - return f; - } - // if fn is a directory name then open directory - if (f.isDirectory()) { - File[] fl = f.listFiles(new ImgFileFilter()); - Arrays.sort(fl); - if ((n > 0) && (n <= fl.length)) { - return fl[n - 1]; - } - } - throw new FileOpException("Unable to find file: "+fn); - } + /** + * Extract the base of a file name (sans extension). + * + * Returns the filename without the extension. The extension is the part + * behind the last dot in the filename. If the filename has no dot the full + * file name is returned. + * + * @param f + * @return + */ + public static String basename(File f) { + if (f == null) { + return null; + } + return basename(f.getName()); + } + + /** + * Extract the extension of a file name. + * + * Returns the extension of a file name. The extension is the part behind + * the last dot in the filename. If the filename has no dot the empty string + * is returned. + * + * @param fn + * @return + */ + public static String extname(String fn) { + if (fn == null) { + return null; + } + int i = fn.lastIndexOf('.'); + if (i > 0) { + return fn.substring(i + 1); + } + return ""; + } - /** - * get the number of files in a directory - * (almost the same as getFile) - * returns 0 in case of problems - */ - public int getNumFiles(String fn) throws FileOpException { - util.dprintln(4, "getNumFiles ("+fn+")"); + /** + * Extract the parent directory of a (digilib) path name. + * + * Returns the parent directory of a path name. The parent is the part + * before the last slash in the path name. If the path name has no slash the + * empty string is returned. + * + * @param fn + * @return + */ + public static String dlParent(String fn) { + if (fn == null) { + return null; + } + int i = fn.lastIndexOf('/'); + if (i > 0) { + return fn.substring(0, i); + } + return ""; + } - File f = new File(fn); - // if fn is a file name then return 1 - if (f.isFile()) { - return 1; - } - // if fn is a directory name then return the number of files - if (f.isDirectory()) { - return f.listFiles(new ImgFileFilter()).length; - } - // then fn must be something strange... - return 0; - } + /** + * Extract the dir/file name of a (digilib) path name. + * + * The file/dir name is the part after the last slash in the path name. If + * the path name has no slash the same string is returned. + * + * @param path + * @return + */ + public static String dlName(String path) { + if (path == null) { + return null; + } + int i = path.lastIndexOf('/'); + if (i > 0) { + return path.substring(i+1); + } + return path; + } + /** + * Normalize a path name. + * + * Removes leading and trailing slashes. Returns null if there is other + * unwanted stuff in the path name. + * + * @param pathname + * @return + */ + public static String normalName(String pathname) { + if (pathname == null) { + return null; + } + // upper-dir references are unwanted + if (pathname.indexOf("../") >= 0) { + return null; + } + int a = 0; + int e = pathname.length() - 1; + if (e < 0) { + return pathname; + } + // leading and trailing "/" are removed + while ((a <= e) && (pathname.charAt(a) == '/')) { + a++; + } + while ((a < e) && (pathname.charAt(e) == '/')) { + e--; + } + return pathname.substring(a, e + 1); + } - /** - * get a filehandle for a file or directory name out of a list - * dirs is a list of base directories, fn is the appended file/dirname - * searches dirs until fn exists (backwards if fwd is false) - * returns File number n if fn is directory (starts with 1) - */ - public File getFileVariant(String[] dirs, String fn, int n, boolean fwd) throws FileOpException { - util.dprintln(4, "getVariantFile ("+dirs+", "+fn+", "+n+")"); + public static StringTokenizer dlPathIterator(String path) { + return new StringTokenizer(path, "/"); + } + + + /** + * FileFilter for general files + */ + static class ReadableFileFilter implements FileFilter { + + public boolean accept(File f) { + return f.canRead(); + } + } + + /** + * FileFilter for image types (helper class for getFile) + */ + static class ImageFileFilter implements FileFilter { + + public boolean accept(File f) { + return (classForFilename(f.getName()) == CLASS_IMAGE); + } + } + + /** + * FileFilter for text types (helper class for getFile) + */ + static class TextFileFilter implements FileFilter { + + public boolean accept(File f) { + return (classForFilename(f.getName()) == CLASS_TEXT); + } + } + + /** + * FileFilter for svg types (helper class for getFile). + * + */ + static class SVGFileFilter implements FileFilter { - File f = null; - int start = 0; - int inc = 1; - int end = dirs.length; - if (fwd == false) { - start = dirs.length - 1; - inc = -1; - end = 0; - } + public boolean accept(File f) { + return (classForFilename(f.getName()) == CLASS_SVG); + } + } + + /** + * Factory for FileFilters (image or text). + * + * @param fileClass + * @return + */ + public static FileFilter filterForClass(int fileClass) { + if (fileClass == CLASS_IMAGE) { + return new ImageFileFilter(); + } + if (fileClass == CLASS_TEXT) { + return new TextFileFilter(); + } + if (fileClass == CLASS_SVG) { + return new SVGFileFilter(); + } + return null; + } - for (int i = start; i != end; i += inc) { - try { - f = getFile(dirs[i]+fn, n); - } catch (FileOpException e) { - f = null; - } - if (f != null) { - return f; - } - } - throw new FileOpException("Unable to find file: "+fn); - } + /** + * Factory for DocuDirents based on file class. + * + * Returns an ImageFileset, TextFile or SVGFile. + * + * @param fileClass + * @param file + * @param parent + * @param hints + * optional additional parameters + * @return + */ + public static DigiDirent fileForClass(int fileClass, File file, + DigiDirectory parent, Map hints) { + // what class of file do we have? + if (fileClass == CLASS_IMAGE) { + // image file + return new ImageFileset(file, parent, hints); + } else if (fileClass == CLASS_TEXT) { + // text file + return new TextFile(file, parent); + } else if (fileClass == CLASS_SVG) { + // text file + return new SVGFile(file, parent); + } + // anything else is a generic dir or file + if (file.isDirectory()) { + return getCachedDirectory(file, null, parent); + } + return new DigiDirent(file.getName(), parent); + } + + /** + * Filters a list of Files through a FileFilter. + * + * @param files + * @param filter + * @return + */ + public static File[] listFiles(File[] files, FileFilter filter) { + if (files == null) { + return null; + } + File[] ff = new File[files.length]; + int ffi = 0; + for (int i = 0; i < files.length; i++) { + if (filter.accept(files[i])) { + ff[ffi] = files[i]; + ffi++; + } + } + File[] fff = new File[ffi]; + System.arraycopy(ff, 0, fff, 0, ffi); + return fff; + } - /** - * get the number of files in a directory - * (almost the same as getFileVariant) - * returns 0 in case of problems - */ - public int getNumFilesVariant(String[] dirs, String fn, boolean fwd) throws FileOpException { - util.dprintln(4, "getNumFilesVariant ("+dirs+", "+fn+")"); + /** + * Returns the closest matching file out of an array. + * + * Compares the files sans extensions if no direct match is found. Returns + * null if no match is found. + * + * @param fn + * @param files + * @return + */ + public static File findFile(File fn, File[] files) { + // try the same filename as the original + int fileIdx = Arrays.binarySearch(files, fn); + if (fileIdx >= 0) { + return files[fileIdx]; + } else { + // try closest matches without extension + String fb = FileOps.basename(fn); + fileIdx = -fileIdx - 1; + if ((fileIdx < files.length) + && (FileOps.basename(files[fileIdx]).equals(fb))) { + // idx ok + return files[fileIdx]; + } else if ((fileIdx > 0) + && (FileOps.basename(files[fileIdx - 1]).equals(fb))) { + // idx-1 ok + return files[fileIdx - 1]; + } else if ((fileIdx + 1 < files.length) + && (FileOps.basename(files[fileIdx + 1]).equals(fb))) { + // idx+1 ok + return files[fileIdx + 1]; + } + } + // unknown + return null; + } - int nf = 0; - int start = 0; - int inc = 1; - int end = dirs.length; - if (fwd == false) { - start = dirs.length - 1; - inc = -1; - end = 0; - } + /** + * Returns the closest matching file out of an array. + * + * Compares the files sans extensions if no direct match is found. Returns + * null if no match is found. + * + * @param fn + * @param files + * @return + */ + public static String findFilename(String fn, String[] files) { + // try the same filename as the original + int fileIdx = Arrays.binarySearch(files, fn); + if (fileIdx >= 0) { + return files[fileIdx]; + } else { + // try closest matches without extension + String fb = FileOps.basename(fn); + fileIdx = -fileIdx - 1; + if ((fileIdx < files.length) + && (FileOps.basename(files[fileIdx]).equals(fb))) { + // idx ok + return files[fileIdx]; + } else if ((fileIdx > 0) + && (FileOps.basename(files[fileIdx - 1]).equals(fb))) { + // idx-1 ok + return files[fileIdx - 1]; + } else if ((fileIdx + 1 < files.length) + && (FileOps.basename(files[fileIdx + 1]).equals(fb))) { + // idx+1 ok + return files[fileIdx + 1]; + } + } + // unknown + return null; + } + /** + * Returns a File for a base directory and a digilib-path. + * + * @param basedir + * @param dlpath + * @return + */ + public static File getRealFile(File basedir, String dlpath) { + // does this work on all platforms?? + return new File(basedir, dlpath); + } + + /** Returns a File for a digilib-path. + * + * The file is assumed to be in the first base directory. + * + * @param dlpath + * @return + */ + public static File getRealFile(String dlpath) { + // does this work on all platforms?? + return new File(baseDirs[0], dlpath); + } - for (int i = start; i != end; i += inc) { - try { - nf = getNumFiles(dirs[i]+fn); - } catch (FileOpException e) { - nf = 0; - } - if (nf > 0) { - return nf; - } - } - return 0; - } + /** + * Creates a new empty hints Map. + * + * @return + */ + public static Map newHints() { + Map m = new HashMap(); + return m; + } + + /** + * Creates a new hints Map with the given first element. + * + * @param type + * @param value + * @return + */ + public static Map newHints(Integer type, Object value) { + Map m = new HashMap(); + if (type != null) { + m.put(type, value); + } + return m; + } + + /** + * @return Returns the baseDirs. + */ + public static File[] getBaseDirs() { + return baseDirs; + } + + /** + * @param baseDirs + * The baseDirs to set. + */ + public static void setBaseDirs(File[] baseDirs) { + FileOps.baseDirs = baseDirs; + } - /** - * FileFilter for image types (helper class for getFile) - */ - private class ImgFileFilter implements FileFilter { - - public boolean accept(File f) { - if (f.isFile()) { - return (mimeForFile(f) != null); - } else { - return false; - } - } - } - + + /** + * Returns a DigiDirectory instance that is guaranteed to be unique in the + * cache. + * + * @param dir + * @param parent + * @return + */ + public static DigiDirectory getCachedDirectory(File dir, String dlpath, DigiDirectory parent) { + if (dir == null) { + dir = FileOps.getRealFile(dlpath); + } + DigiDirectory dd = null; + if (parent == null) { + // create a new parent by starting at the root + StringBuffer ps = new StringBuffer(); + DigiDirectory p = cache.getRootDir(); + // walk the path + for (StringTokenizer i = dlPathIterator(dlpath); i.hasMoreTokens();) { + p.check(); + String dn = i.nextToken(); + ps.append("/"); + ps.append(dn); + DigiDirectory d = cache.get(dn); + if (d == null) { + dd = new DigiDirectory(FileOps.getRealFile(dn), ps.toString(), p); + } + if (d.getParent() != p) { + logger.warn("digidirectory "+d.getDLPath()+" has wrong parent: "+p.getDLPath()); + } + p = d; + } + } else { + if (dlpath == null) { + dlpath = parent.getDLPath() + "/" + dir.getName(); + } + dd = cache.get(dlpath); + if (dd == null) { + dd = new DigiDirectory(dir, dlpath, parent); + } else { + logger.debug("reusing directory:" + dlpath); + } + } + return dd; + } + + /** + * @return Returns the cache. + */ + public static DocuDirCache getCache() { + return cache; + } + /** + * @param cache The cache to set. + */ + public static void setCache(DocuDirCache cache) { + FileOps.cache = cache; + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/servlet/src/digilib/io/ImageFile.java Wed Nov 17 18:17:34 2004 +0100 @@ -0,0 +1,99 @@ +/* ImageFile.java -- digilib image file class. + + Digital Image Library servlet components + + Copyright (C) 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 + + * Created on 25.02.2003 + */ + +package digilib.io; + +import java.io.File; +import java.io.IOException; + +import digilib.image.ImageOps; +import digilib.image.ImageSize; + +/** + * @author casties + */ +public class ImageFile extends File { + + private static final long serialVersionUID = 1L; + + /** file mime-type */ + private String mimetype = null; + + /** image size in pixels */ + private ImageSize pixelSize = null; + + /** + * @param pathname + */ + public ImageFile(String pathname) { + super(pathname); + } + + /** + * @param file + */ + public ImageFile(File file) { + super(file.getPath()); + } + + /** + * @return ImageSize + */ + public ImageSize getSize() { + return pixelSize; + } + + /** + * Sets the imageSize. + * @param imageSize The imageSize to set + */ + public void setSize(ImageSize imageSize) { + this.pixelSize = imageSize; + } + + /** + * @return String + */ + public String getMimetype() { + return mimetype; + } + + /** + * Sets the mimetype. + * @param mimetype The mimetype to set + */ + public void setMimetype(String filetype) { + this.mimetype = filetype; + } + + /** + * Checks image size. + * @throws IOException + * + */ + public void check() throws IOException { + if (pixelSize != null) { + return; + } + ImageOps.checkFile(this); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/servlet/src/digilib/io/ImageFileset.java Wed Nov 17 18:17:34 2004 +0100 @@ -0,0 +1,326 @@ +/* ImageFileset -- digilib image file info class. + * Digital Image Library servlet components + * Copyright (C) 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.io; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import digilib.image.ImageOps; +import digilib.image.ImageSize; + +/** + * @author casties + */ +public class ImageFileset extends DigiDirent { + + /** this is an image file */ + protected static int FILE_CLASS = FileOps.CLASS_IMAGE; + + /** list of files (ImageFile) */ + private List files; + + /** resolution of the biggest image (DPI) */ + private float resX = 0; + + /** resolution of the biggest image (DPI) */ + private float resY = 0; + + /** + * Constructor with a file and hints. + * + * The hints are expected to contain 'basedirs' and 'scaledfilext' keys. + * + * @param file + * @param hints + */ + public ImageFileset(File file, DigiDirectory parent, Map hints) { + super(file.getName(), parent); + files = new ArrayList(FileOps.getBaseDirs().length); + fill(file, hints); + } + + /** + * Gets the default File. + * + */ + public ImageFile getFile() { + return (files != null) ? (ImageFile) files.get(0) : null; + } + + /** + * Get the ImageFile at the index. + * + * + * @param index + * @return + */ + public ImageFile get(int index) { + return (files != null) ? (ImageFile) files.get(index) : null; + } + + /** + * Get the next smaller ImageFile than the given size. + * + * Returns the ImageFile from the set that has a width and height smaller or + * equal the given size. Returns null if there isn't any smaller image. + * Needs DocuInfo instance to checkFile(). + * + * + * @param size + * @param info + * @return + */ + public ImageFile getNextSmaller(ImageSize size) { + for (Iterator i = getHiresIterator(); i.hasNext();) { + ImageFile f = (ImageFile) i.next(); + try { + ImageOps.checkFile(f); + if (f.getSize().isTotallySmallerThan(size)) { + return f; + } + } catch (IOException e) { + } + } + return null; + } + + /** + * Get the next bigger ImageFile than the given size. + * + * Returns the ImageFile from the set that has a width or height bigger or + * equal the given size. Returns null if there isn't any bigger image. Needs + * DocuInfo instance to checkFile(). + * + * + * @param size + * @param info + * @return + */ + public ImageFile getNextBigger(ImageSize size) { + for (ListIterator i = getLoresIterator(); i.hasPrevious();) { + ImageFile f = (ImageFile) i.previous(); + try { + ImageOps.checkFile(f); + if (f.getSize().isBiggerThan(size)) { + return f; + } + } catch (IOException e) { + } + } + return null; + } + + /** + * Returns the biggest ImageFile in the set. + * + * + * @return + */ + public ImageFile getBiggest() { + return (ImageFile) files.get(0); + } + + /** + * Returns the biggest ImageFile in the set. + * + * + * @return + */ + public ImageFile getSmallest() { + return (ImageFile) files.get(files.size() - 1); + } + + /** + * Get an Iterator for this Fileset starting at the highest resolution + * images. + * + * + * @return + */ + public ListIterator getHiresIterator() { + return files.listIterator(); + } + + /** + * Get an Iterator for this Fileset starting at the lowest resolution + * images. + * + * The Iterator starts at the last element, so you have to use it backwards + * with hasPrevious() and previous(). + * + * + * @return + */ + public ListIterator getLoresIterator() { + return files.listIterator(files.size()); + } + + /** + * Fill the ImageFileset with files from different base directories. + * + * + * @param dirs + * list of base directories + * @param imf + * file (from first base dir) + * @param hints + * + */ + void fill(File imf, Map hints) { + File[][] scaledirs = (File[][]) hints.get(FileOps.HINT_BASEDIRS); + File[] bd = FileOps.getBaseDirs(); + int nb = bd.length; + if (scaledirs == null) { + // read all scaled directories + scaledirs = new File[nb][]; + for (int i = 1; i < nb; i++) { + // check basedir + digilib path + File d = FileOps.getRealFile(bd[i], parent.getDLPath()); + scaledirs[i] = d.listFiles(); + } + hints.put(FileOps.HINT_BASEDIRS, scaledirs); + } + // add the first ImageFile to the ImageFileset + files.add(new ImageFile(imf)); + // iterate the remaining base directories + for (int dirIdx = 1; dirIdx < nb; dirIdx++) { + if (scaledirs[dirIdx] == null) { + continue; + } + // find the file in the directory + File fn = FileOps.findFile(imf, scaledirs[dirIdx]); + if (fn == null) { + continue; + } + if (FileOps.classForFile(fn) == FileOps.CLASS_IMAGE) { + // add to the fileset + files.add(new ImageFile(fn)); + } + } + } + + /** + * Reads metadata and sets resolution in resX and resY. + * + */ + public void readMeta() { + if (isMetaRead) { + return; + } + // read the metadata file + super.readMeta(); + if (meta == null) { + // try directory metadata + meta = parent.getMeta(); + if (meta == null) { + // no metadata available + isMetaRead = true; + return; + } + } + isMetaRead = true; + String s; + float dpi = 0; + float dpix = 0; + float dpiy = 0; + float sizex = 0; + float sizey = 0; + float pixx = 0; + float pixy = 0; + // DPI is valid for X and Y + if (meta.containsKey("original-dpi")) { + try { + dpi = Float.parseFloat((String) meta.get("original-dpi")); + } catch (NumberFormatException e) { + } + if (dpi != 0) { + resX = dpi; + resY = dpi; + return; + } + } + // DPI-X and DPI-Y + if (meta.containsKey("original-dpi-x") + && meta.containsKey("original-dpi-y")) { + try { + dpix = Float.parseFloat((String) meta.get("original-dpi-x")); + dpiy = Float.parseFloat((String) meta.get("original-dpi-y")); + } catch (NumberFormatException e) { + } + if ((dpix != 0) && (dpiy != 0)) { + resX = dpix; + resY = dpiy; + return; + } + } + // SIZE-X and SIZE-Y and PIXEL-X and PIXEL-Y + if (meta.containsKey("original-size-x") + && meta.containsKey("original-size-y") + && meta.containsKey("original-pixel-x") + && meta.containsKey("original-pixel-y")) { + try { + sizex = Float.parseFloat((String) meta.get("original-size-x")); + sizey = Float.parseFloat((String) meta.get("original-size-y")); + pixx = Float.parseFloat((String) meta.get("original-pixel-x")); + pixy = Float.parseFloat((String) meta.get("original-pixel-y")); + } catch (NumberFormatException e) { + } + if ((sizex != 0) && (sizey != 0) && (pixx != 0) && (pixy != 0)) { + resX = pixx / (sizex * 100 / 2.54f); + resY = pixy / (sizey * 100 / 2.54f); + return; + } + } + } + + /** + * Returns the aspect ratio of the images. + * + * @return + */ + public float getAspect() { + for (Iterator i = files.iterator(); i.hasNext();) { + ImageFile f = (ImageFile) i.next(); + ImageSize s = f.getSize(); + if (s != null) { + return s.getAspect(); + } + } + return 0f; + } + + /** + * @return + */ + public float getResX() { + return resX; + } + + /** + * @return + */ + public float getResY() { + return resY; + } + +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/servlet/src/digilib/io/SVGFile.java Wed Nov 17 18:17:34 2004 +0100 @@ -0,0 +1,71 @@ +/* SVGFile -- Class for SVG files + + Digital Image Library servlet components + + Copyright (C) 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 + + * Created on 25.11.2003 by casties + * + */ +package digilib.io; + +import java.io.File; + + +/** Class for SVG files. + * + * @author casties + * + */ +public class SVGFile extends DigiDirent { + /** this is a SVG file */ + protected static final int FILE_CLASS = FileOps.CLASS_SVG; + + protected File file; + + /** + * @param name + * @param parent + * @param file + */ + public SVGFile(File file, DigiDirectory parent) { + super(file.getName(), parent); + this.file = file; + } + + /** + * @param name + * @param parent + */ + public SVGFile(String name, File file, DigiDirectory parent) { + super(name, parent); + this.file = file; + } + + /** + * @return Returns the file. + */ + public File getFile() { + return file; + } + + /** + * @param file The file to set. + */ + public void setFile(File file) { + this.file = file; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/servlet/src/digilib/io/TextFile.java Wed Nov 17 18:17:34 2004 +0100 @@ -0,0 +1,70 @@ +/* TextFile.java -- Class for text files + + Digital Image Library servlet components + + Copyright (C) 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 + + * Created on 15.09.2003 by casties + * + */ +package digilib.io; + +import java.io.File; + +/** + * Class for text files. + * + * @author casties + * + */ +public class TextFile extends DigiDirent { + /** this is a text file */ + protected static final int fileClass = FileOps.CLASS_TEXT; + + protected File file; + + /** + * @param name + * @param parent + * @param file + */ + public TextFile(File file, DigiDirectory parent) { + super(file.getName(), parent); + this.file = file; + } + + /** + * @param name + * @param parent + */ + public TextFile(String name, File file, DigiDirectory parent) { + super(name, parent); + this.file = file; + } + + /** + * @return Returns the file. + */ + public File getFile() { + return file; + } + + /** + * @param file The file to set. + */ + public void setFile(File file) { + this.file = file; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/servlet/src/digilib/servlet/DigilibConfiguration.java Wed Nov 17 18:17:34 2004 +0100 @@ -0,0 +1,257 @@ +/* + * DigilibConfiguration -- Holding all parameters for digilib servlet. + * + * Digital Image Library servlet components + * + * Copyright (C) 2001, 2002 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.io.File; +import java.util.Iterator; +import java.util.Map; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; + +import org.apache.log4j.BasicConfigurator; +import org.apache.log4j.Logger; + +import digilib.image.DocuImage; +import digilib.image.DocuImageImpl; +import digilib.io.FileOps; +import digilib.io.XMLListLoader; + +/** + * Class to hold the digilib servlet configuration parameters. The parameters + * can be read from the digilib-config file and be passed to other servlets or + * beans. <br>errorImgFileName: image file to send in case of error. <br> + * denyImgFileName: image file to send if access is denied. <br>baseDirs: + * array of base directories in order of preference (prescaled versions first). + * <br>useAuth: use authentication information. <br>authConfPath: + * authentication configuration file. <br>... <br> + * + * @author casties + * + */ +public class DigilibConfiguration extends ParameterMap { + + private static final long serialVersionUID = -6630487070791637120L; + + /** DocuImage class instance */ + private Class docuImageClass = null; + + /** Log4J logger */ + private Logger logger = Logger.getLogger("digilib.config"); + + /** + * Default constructor defines all parameters and their default values. + * + */ + public DigilibConfiguration() { + // create HashMap(20) + super(20); + // we start with a default logger config + BasicConfigurator.configure(); + + /* + * Definition of parameters and default values. System parameters that + * are not read from config file have a type 's'. + */ + + // digilib servlet version + newParameter( + "servlet.version", + digilib.servlet.Scaler.dlVersion, + null, + 's'); + // configuration file location + newParameter("servlet.config.file", null, null, 's'); + // DocuDirCache instance + newParameter("servlet.dir.cache", null, null, 's'); + // DocuImage class instance + newParameter( + "servlet.docuimage.class", + digilib.image.JAIDocuImage.class, + null, + 's'); + // AuthOps instance for authentication + newParameter("servlet.auth.op", null, null, 's'); + + /* + * parameters that can be read from config file have a type 'f' + */ + + // image file to send in case of error + newParameter( + "error-image", + new File("/docuserver/images/icons/scalerror.gif"), + null, + 'f'); + // image file to send if access is denied + newParameter( + "denied-image", + new File("/docuserver/images/icons/denied.gif"), + null, + 'f'); + // base directories in order of preference (prescaled versions last) + String[] bd = { "/docuserver/images", "/docuserver/scaled/small" }; + newParameter("basedir-list", bd, null, 'f'); + // use authentication information + newParameter("use-authorization", Boolean.FALSE, null, 'f'); + // authentication configuration file + newParameter("auth-file", new File("digilib-auth.xml"), null, 'f'); + // sending image files as-is allowed + newParameter("sendfile-allowed", Boolean.TRUE, null, 'f'); + // Debug level + newParameter("debug-level", new Integer(5), null, 'f'); + // Type of DocuImage instance + newParameter( + "docuimage-class", + "digilib.image.JAIDocuImage", + null, + 'f'); + // part of URL used to indicate authorized access + newParameter("auth-url-path", "authenticated/", null, 'f'); + // degree of subsampling on image load + newParameter("subsample-minimum", new Float(2f), null, 'f'); + // default scaling quality + newParameter("default-quality", new Integer(1), null, 'f'); + // use mapping file to translate paths + newParameter("use-mapping", Boolean.FALSE, null, 'f'); + // mapping file location + newParameter("mapping-file", new File("digilib-map.xml"), null, 'f'); + // log4j config file location + newParameter("log-config-file", new File("log4j-config.xml"), null, 'f'); + // maximum destination image size (0 means no limit) + newParameter("max-image-size", new Integer(0), null, 'f'); + // use safe (but slower) directory indexing + newParameter("safe-dir-index", Boolean.FALSE, null, 'f'); + // number of working threads + newParameter("worker-threads", new Integer(1), null, 'f'); + + } + + /** + * Constructor taking a ServletConfig. Reads the config file location from + * an init parameter and loads the config file. Calls <code>readConfig()</code>. + * + * @see readConfig() + */ + public DigilibConfiguration(ServletConfig c) throws Exception { + this(); + readConfig(c); + } + + /** + * read parameter list from the XML file in init parameter "config-file" + */ + public void readConfig(ServletConfig c) throws Exception { + + /* + * Get config file name. The file name is first looked for as an init + * parameter, then in a fixed location in the webapp. + */ + if (c == null) { + // no config no file... + return; + } + String fn = c.getInitParameter("config-file"); + if (fn == null) { + fn = ServletOps.getConfigFile("digilib-config.xml", c); + if (fn == null) { + logger.fatal("readConfig: no param config-file"); + throw new ServletException("ERROR: no digilib config file!"); + } + } + File f = new File(fn); + // setup config file list reader + XMLListLoader lilo = + new XMLListLoader("digilib-config", "parameter", "name", "value"); + // read config file into HashMap + Map confTable = lilo.loadURL(f.toURL().toString()); + + // set config file path parameter + setValue("servlet.config.file", f.getCanonicalPath()); + + /* + * read parameters + */ + + for (Iterator i = confTable.keySet().iterator(); i.hasNext();) { + String key = (String) i.next(); + String val = (String) confTable.get(key); + Parameter p = get(key); + if (p != null) { + if (p.getType() == 's') { + // type 's' Parameters are not overwritten. + continue; + } + if (!p.setValueFromString(val)) { + /* + * automatic conversion failed -- try special cases + */ + + // basedir-list + if (key.equals("basedir-list")) { + // split list into directories + String[] sa = FileOps.pathToArray(val); + if (sa != null) { + p.setValue(sa); + } + } + } + } else { + // parameter unknown -- just add + newParameter(key, null, val, 'f'); + } + } + + } + + /** + * Creates a new DocuImage instance. + * + * The type of DocuImage is specified by docuImageType. + * + * @return DocuImage + */ + public DocuImage getDocuImageInstance() { + DocuImageImpl di = null; + try { + if (docuImageClass == null) { + docuImageClass = Class.forName(getAsString("docuimage-class")); + } + di = (DocuImageImpl) docuImageClass.newInstance(); + } catch (Exception e) { + } + return di; + } + + /** + * @return Returns the docuImageClass. + */ + public Class getDocuImageClass() { + return docuImageClass; + } + /** + * @param docuImageClass The docuImageClass to set. + */ + public void setDocuImageClass(Class docuImageClass) { + this.docuImageClass = docuImageClass; + } +}
--- a/servlet/src/digilib/servlet/DocumentBean.java Thu Jan 17 15:25:46 2002 +0100 +++ b/servlet/src/digilib/servlet/DocumentBean.java Wed Nov 17 18:17:34 2004 +0100 @@ -1,198 +1,296 @@ -/* DocumentBean -- Access control bean for JSP - - Digital Image Library servlet components - - Copyright (C) 2001, 2002 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 - -*/ +/* + * DocumentBean -- Access control bean for JSP + * + * 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.util.List; -import java.util.*; -import javax.servlet.*; -import javax.servlet.http.*; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.log4j.Logger; -import digilib.*; -import digilib.io.*; -import digilib.auth.*; +import digilib.auth.AuthOpException; +import digilib.auth.AuthOps; +import digilib.io.DigiDirectory; +import digilib.io.DocuDirCache; +import digilib.io.FileOps; +import digilib.io.ImageFileset; -public class DocumentBean implements AuthOps { +public class DocumentBean { + + // general logger + private static Logger logger = Logger.getLogger("digilib.docubean"); + + // AuthOps object to check authorization + private AuthOps authOp; - // Utils object for logging - private Utils util = new Utils(5); - // AuthOps object to check authorization - private AuthOps authOp; - // FileOps object - private FileOps fileOp = new FileOps(util); + // use authorization database + private boolean useAuthentication = true; + + // path to add for authenticated access + private String authURLPath = ""; + + // DocuDirCache + private DocuDirCache dirCache = null; + + // DigilibConfiguration object + private DigilibConfiguration dlConfig; + + // DigilibRequest object + private DigilibRequest dlRequest = null; - // base directories in order of preference (prescaled versions first) - private String[] baseDirs = {"/docuserver/scaled/small", "/docuserver/images", "/docuserver/scans/quellen"}; - // part of URL path to prepend for authenticated access - private String authURLpath = "authenticated/"; + /** + * Constructor for DocumentBean. + */ + public DocumentBean() { + super(); + } - - public DocumentBean() { - } + public DocumentBean(ServletConfig conf) { + try { + setConfig(conf); + } catch (Exception e) { + logger.fatal("ERROR: Unable to read config: ", e); + } + } - public void setConfig(ServletConfig conf) throws ServletException { - util.dprintln(10, "setConfig"); - // servletOps takes a ServletConfig to get the config file name - ServletOps servletOp = new ServletOps(util, conf); - /** - * basedir-list : List of document directories - */ - String bl = servletOp.tryToGetInitParam("basedir-list", null); - if ((bl != null)&&(bl.length() > 0)) { - // split list into directories - StringTokenizer dirs = new StringTokenizer(bl, ":"); - int n = dirs.countTokens(); - if (n > 0) { - // add directories into array - baseDirs = new String[n]; - for (int i = 0; i < n; i++) { - baseDirs[i] = dirs.nextToken(); - } - } - util.dprintln(3, "basedir-list: "+bl); - } - /** - * auth-url-path : part of URL to indicate authenticated access - */ - String au = servletOp.tryToGetInitParam("auth-url-path", null); - if ((au != null)&&(au.length() > 0)) { - authURLpath = au; - util.dprintln(3, "auth-url-path: "+au); - } - /** - * authentication - */ - try { - // DB version - //private AuthOps authOp = new DBAuthOpsImpl(util); - // XML version - String cp = servletOp.tryToGetInitParam("auth-file", "/docuserver/www/digitallibrary/WEB-INF/digilib-auth.xml"); - util.dprintln(3, "auth-file: "+cp); - authOp = new XMLAuthOps(util, cp); - } catch (AuthOpException e) { - throw new ServletException(e); - } - } + public void setConfig(ServletConfig conf) throws ServletException { + logger.debug("setConfig"); + // get our ServletContext + ServletContext context = conf.getServletContext(); + // see if there is a Configuration instance + dlConfig = (DigilibConfiguration) context + .getAttribute("digilib.servlet.configuration"); + if (dlConfig == null) { + // create new Configuration + throw new ServletException("ERROR: No configuration!"); + } + + // get cache + dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache"); + + /* + * authentication + */ + useAuthentication = dlConfig.getAsBoolean("use-authorization"); + authOp = (AuthOps) dlConfig.getValue("servlet.auth.op"); + authURLPath = dlConfig.getAsString("auth-url-path"); + if (useAuthentication && (authOp == null)) { + throw new ServletException( + "ERROR: use-authorization configured but no AuthOp!"); + } + } + + /** + * check if the request must be authorized to access filepath + */ + public boolean isAuthRequired(DigilibRequest request) + throws AuthOpException { + logger.debug("isAuthRequired"); + return useAuthentication ? authOp.isAuthRequired(request) : false; + } + + /** + * check if the request is allowed to access filepath + */ + public boolean isAuthorized(DigilibRequest request) throws AuthOpException { + logger.debug("isAuthorized"); + return useAuthentication ? authOp.isAuthorized(request) : true; + } + + /** + * return a list of authorization roles needed for request to access the + * specified path + */ + public List rolesForPath(DigilibRequest request) throws AuthOpException { + logger.debug("rolesForPath"); + return useAuthentication ? authOp.rolesForPath(request) : null; + } + + /** + * check request authorization against a list of roles + */ + public boolean isRoleAuthorized(List roles, DigilibRequest request) { + logger.debug("isRoleAuthorized"); + return useAuthentication ? authOp.isRoleAuthorized(roles, request) + : true; + } + + /** + * check for authenticated access and redirect if necessary + */ + public boolean doAuthentication(HttpServletResponse response) + throws Exception { + return doAuthentication(dlRequest, response); + } - public String getDocuPath(HttpServletRequest request) { - util.dprintln(10, "getDocuPath"); - // fetch query string - String qs = request.getQueryString(); - String fn = ""; - if (qs != null && qs.length() > 0) { - // the file name is in the request before the first "+" - int endfn = qs.indexOf("+"); - if (endfn > 0) { - fn = qs.substring(0, endfn); - } else { - fn = qs; - } - } - util.dprintln(4, "docuPath: "+fn); - return fn; - } - - /** - * check if the request must be authorized to access filepath - */ - public boolean isAuthRequired(HttpServletRequest request) throws AuthOpException { - util.dprintln(10, "isAuthRequired"); - return authOp.isAuthRequired(getDocuPath(request), request); - } + /** + * check for authenticated access and redirect if necessary + */ + public boolean doAuthentication(DigilibRequest request, + HttpServletResponse response) throws Exception { + logger.debug("doAuthentication"); + if (!useAuthentication) { + // shortcut if no authentication + return true; + } + // check if we are already authenticated + if (((HttpServletRequest) request.getServletRequest()).getRemoteUser() == null) { + logger.debug("unauthenticated so far"); + // if not maybe we must? + if (isAuthRequired(request)) { + logger.debug("auth required, redirect"); + // we are not yet authenticated -> redirect + response.sendRedirect(authURLPath + + ((HttpServletRequest) request.getServletRequest()) + .getServletPath() + + "?" + + ((HttpServletRequest) request.getServletRequest()) + .getQueryString()); + } + } + return true; + } - public boolean isAuthRequired(String filepath, HttpServletRequest request) throws AuthOpException { - util.dprintln(10, "isAuthRequired"); - return authOp.isAuthRequired(filepath, request); - } - - /** - * check if the request is allowed to access filepath - */ - public boolean isAuthorized(HttpServletRequest request) throws AuthOpException { - util.dprintln(10, "isAuthorized"); - return authOp.isAuthorized(getDocuPath(request), request); - } + /** + * Sets the current DigilibRequest. Also completes information in the request. + * + * @param dlRequest + * The dlRequest to set. + */ + public void setRequest(DigilibRequest dlRequest) throws Exception { + this.dlRequest = dlRequest; + if (dirCache == null) { + return; + } + String fn = dlRequest.getFilePath(); + // get information about the file + ImageFileset fileset = (ImageFileset) dirCache.getFile(fn, dlRequest + .getAsInt("pn"), FileOps.CLASS_IMAGE); + if (fileset == null) { + return; + } + // add file name + dlRequest.setValue("img.fn", fileset.getName()); + // add dpi + fileset.check(); + dlRequest.setValue("img.dpix", new Double(fileset.getResX())); + dlRequest.setValue("img.dpiy", new Double(fileset.getResY())); + // get number of pages in directory + DigiDirectory dd = dirCache.getDirectory(fn); + if (dd != null) { + dlRequest.setValue("pt", dd.getSize(FileOps.CLASS_IMAGE)); + } + } - public boolean isAuthorized(String filepath, HttpServletRequest request) throws AuthOpException { - util.dprintln(10, "isAuthorized"); - return authOp.isAuthorized(filepath, request); - } + /** + * get the first page number in the directory (not yet functional) + */ + public int getFirstPage(DigilibRequest request) { + logger.debug("getFirstPage"); + return 1; + } - /** - * return a list of authorization roles needed for request - * to access the specified path - */ - public List rolesForPath(String filepath, HttpServletRequest request) throws AuthOpException { - util.dprintln(10, "rolesForPath"); - return authOp.rolesForPath(filepath, request); - } + /** + * get the number of pages/files in the directory + */ + public int getNumPages() throws Exception { + return getNumPages(dlRequest); + } - /** - * check request authorization against a list of roles - */ - public boolean isRoleAuthorized(List roles, HttpServletRequest request) { - util.dprintln(10, "isRoleAuthorized"); - return authOp.isRoleAuthorized(roles, request); - } + /** + * get the number of pages/files in the directory + */ + public int getNumPages(DigilibRequest request) throws Exception { + logger.debug("getNumPages"); + DigiDirectory dd = (dirCache != null) ? dirCache.getDirectory(request + .getFilePath()) : null; + if (dd != null) { + return dd.getSize(FileOps.CLASS_IMAGE); + } + return 0; + } - /** - * check for authenticated access and redirect if necessary - */ - public boolean doAuthentication(HttpServletRequest request, HttpServletResponse response) throws Exception { - util.dprintln(10, "doAuthentication"); - // check if we are already authenticated - if (request.getRemoteUser() == null) { - util.dprintln(3, "unauthenticated so far"); - // if not maybe we must? - if (isAuthRequired(request)) { - util.dprintln(3, "auth required, redirect"); - // we are not yet authenticated -> redirect - response.sendRedirect(authURLpath+request.getServletPath()+"?"+request.getQueryString()); - } - } - return true; - } + /** + * Returns the dlConfig. + * + * @return DigilibConfiguration + */ + public DigilibConfiguration getDlConfig() { + return dlConfig; + } + + /** + * returns if the zoom area in the request can be moved + * + * @return + */ + public boolean canMoveRight() { + float ww = dlRequest.getAsFloat("ww"); + float wx = dlRequest.getAsFloat("wx"); + return (ww + wx < 1.0); + } - /** - * get the first page number in the directory - * (not yet functional) - */ - public int getFirstPage(HttpServletRequest request) { - return getFirstPage(getDocuPath(request), request); - } - - public int getFirstPage(String filepath, HttpServletRequest request) { - util.dprintln(10, "getFirstPage"); - return 1; - } + /** + * returns if the zoom area in the request can be moved + * + * @return + */ + public boolean canMoveLeft() { + float ww = dlRequest.getAsFloat("ww"); + float wx = dlRequest.getAsFloat("wx"); + return ((ww < 1.0) && (wx > 0)); + } - /** - * get the number of pages/files in the directory - */ - public int getNumPages(HttpServletRequest request) throws Exception { - return getNumPages(getDocuPath(request), request); - } + /** + * returns if the zoom area in the request can be moved + * + * @return + */ + public boolean canMoveUp() { + float wh = dlRequest.getAsFloat("wh"); + float wy = dlRequest.getAsFloat("wy"); + return ((wh < 1.0) && (wy > 0)); + } - public int getNumPages(String filepath, HttpServletRequest request) throws Exception { - util.dprintln(10, "getNumPages"); - return fileOp.getNumFilesVariant(baseDirs, "/"+filepath, true); - } + /** + * returns if the zoom area in the request can be moved + * + * @return + */ + public boolean canMoveDown() { + float wh = dlRequest.getAsFloat("wh"); + float wy = dlRequest.getAsFloat("wy"); + return (wh + wy < 1.0); + } -} + /** + * @return Returns the dlRequest. + */ + public DigilibRequest getRequest() { + return dlRequest; + } + +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/servlet/src/digilib/servlet/Initialiser.java Wed Nov 17 18:17:34 2004 +0100 @@ -0,0 +1,175 @@ +/* Initialiser.java -- initalisation servlet for setup tasks + * + * Digital Image Library servlet components + * + * Copyright (C) 2004 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 + * + * Created on 18.10.2004 + */ +package digilib.servlet; + +import java.io.File; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; + +import org.apache.log4j.Logger; +import org.apache.log4j.xml.DOMConfigurator; + +import EDU.oswego.cs.dl.util.concurrent.FIFOSemaphore; +import EDU.oswego.cs.dl.util.concurrent.Semaphore; +import digilib.auth.AuthOps; +import digilib.auth.XMLAuthOps; +import digilib.io.AliasingDocuDirCache; +import digilib.io.DocuDirCache; +import digilib.io.FileOps; + +/** + * Initalisation servlet for setup tasks. + * + * @author casties + * + */ +public class Initialiser extends HttpServlet { + + private static final long serialVersionUID = -5126621114382549343L; + + /** servlet version */ + public static final String iniVersion = "0.1b1"; + + /** gengeral logger for this class */ + private static Logger logger = Logger.getLogger("digilib.init"); + + /** AuthOps instance */ + AuthOps authOp; + + /** DocuDirCache instance */ + DocuDirCache dirCache; + + /** DigilibConfiguration instance */ + DigilibConfiguration dlConfig; + + /** use authorization database */ + boolean useAuthentication = false; + + /** + * Initialisation on first run. + * + * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig) + */ + public void init(ServletConfig config) throws ServletException { + super.init(config); + + System.out + .println("***** Digital Image Library Initialisation Servlet (version " + + iniVersion + ") *****"); + + // 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); + + /* + * further initialization + */ + + // set up the logger + File logConf = ServletOps.getConfigFile((File) dlConfig + .getValue("log-config-file"), config); + DOMConfigurator.configure(logConf.getAbsolutePath()); + dlConfig.setValue("log-config-file", logConf); + // say hello in the log file + logger + .info("***** Digital Image Library Initialisation Servlet (version " + + iniVersion + ") *****"); + // base directories + String[] bd = (String[]) dlConfig.getValue("basedir-list"); + File[] dirs = new File[bd.length]; + int ndirs = 0; + for (int i = 0; i < bd.length; i++) { + File d = new File(bd[i]); + if (d.isDirectory()) { + dirs[ndirs++] = d; + } + } + File[] bdirs = new File[ndirs]; + System.arraycopy(dirs, 0, bdirs, 0, ndirs); + FileOps.setBaseDirs(bdirs); + // file classes + int[] fcs = { FileOps.CLASS_IMAGE, FileOps.CLASS_TEXT, + FileOps.CLASS_SVG }; + FileOps.setFileClasses(fcs); + // directory cache + if (dlConfig.getAsBoolean("use-mapping")) { + // with mapping file + File mapConf = ServletOps.getConfigFile((File) dlConfig + .getValue("mapping-file"), config); + dirCache = new AliasingDocuDirCache(mapConf); + dlConfig.setValue("mapping-file", mapConf); + } else { + // without mapping + dirCache = new DocuDirCache(); + } + dlConfig.setValue("servlet.dir.cache", dirCache); + FileOps.setCache(dirCache); + // useAuthentication + if (dlConfig.getAsBoolean("use-authorization")) { + // DB version + //authOp = new DBAuthOpsImpl(util); + // XML version + File authConf = ServletOps.getConfigFile((File) dlConfig + .getValue("auth-file"), config); + authOp = new XMLAuthOps(authConf); + dlConfig.setValue("servlet.auth.op", authOp); + dlConfig.setValue("auth-file", authConf); + } + // DocuImage class + Class cl = Class.forName(dlConfig + .getAsString("docuimage-class")); + dlConfig.setDocuImageClass(cl); + dlConfig.setValue("servlet.docuimage.class", cl.getName()); + // worker threads + int nt = dlConfig.getAsInt("worker-threads"); + Semaphore lck = new FIFOSemaphore(nt); + DigilibWorker.setLock(lck); + // set as the servlets main config + context.setAttribute("digilib.servlet.configuration", dlConfig); + + } catch (Exception e) { + throw new ServletException(e); + } + } else { + // say hello in the log file + logger + .info("***** Digital Image Library Initialisation Servlet (version " + + iniVersion + ") *****"); + logger.warn("Already initialised?"); + // 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"); + } + } + +}
--- a/servlet/src/digilib/servlet/Scaler.java Thu Jan 17 15:25:46 2002 +0100 +++ b/servlet/src/digilib/servlet/Scaler.java Wed Nov 17 18:17:34 2004 +0100 @@ -1,377 +1,701 @@ -/* Scaler -- Scaler servlet main class - - Digital Image Library servlet components - - Copyright (C) 2001, 2002 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 - -*/ +/* + * Scaler -- Scaler servlet main class + * + * Digital Image Library servlet components + * + * Copyright (C) 200-2004 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 javax.servlet.*; -import javax.servlet.http.*; -import java.io.*; -import java.util.*; +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.*; -import digilib.io.*; -import digilib.image.*; -import digilib.auth.*; +import digilib.auth.AuthOpException; +import digilib.auth.AuthOps; +import digilib.image.ImageOpException; +import digilib.image.ImageSize; +import digilib.io.DigiDirectory; +import digilib.io.DigiDirent; +import digilib.io.DocuDirCache; +import digilib.io.FileOpException; +import digilib.io.FileOps; +import digilib.io.ImageFile; +import digilib.io.ImageFileset; - +/** + * @author casties + */ //public class Scaler extends HttpServlet implements SingleThreadModel { public class Scaler extends HttpServlet { - // Utils instance with debuglevel - Utils util; - // ServletOpss instance - ServletOps servletOp; - // FileOps instance - FileOps fileOp; - // AuthOps instance - AuthOps authOp; - // global DocuImage instance (don't reuse inside a request!) - DocuImage globalImage; + private static final long serialVersionUID = -325080527268912852L; + + /** digilib servlet version (for all components) */ + public static final String dlVersion = "1.6.0a"; + + /** logger for accounting requests */ + private static Logger accountlog = Logger.getLogger("account.request"); + + /** gengeral logger for this class */ + private static Logger logger = Logger.getLogger("digilib.servlet"); + + /** logger for authentication related */ + private static Logger authlog = Logger.getLogger("digilib.auth"); + + /** general error code */ + public static final int ERROR_UNKNOWN = 0; + + /** error code for authentication error */ + public static final int ERROR_AUTH = 1; + + /** error code for file operation error */ + public static final int ERROR_FILE = 2; + + /** error code for image operation error */ + public static final int ERROR_IMAGE = 3; + + /** servlet start time */ + long starttime = 0; + + /** DocuDirCache instance */ + DocuDirCache dirCache; + + /** authentication error image file */ + File denyImgFile; + + /** image 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 useAuthorization = true; + + /** AuthOps instance */ + AuthOps authOp; + + // 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); + + System.out + .println("***** Digital Image Library Image Scaler Servlet (version " + + dlVersion + ") *****"); + // say hello in the log file + logger + .info("***** Digital Image Library Image Scaler Servlet (version " + + dlVersion + ") *****"); - // use authorization database - boolean useAuthentication = true; - // image file to send in case of error - File errorImgFile = new File("/docuserver/images/icons/scalerror.gif"); - // image file to send if access is denied - File denyImgFile = new File("/docuserver/images/icons/denied.gif"); - // base directories in order of preference (prescaled versions first) - String[] baseDirs = {"/docuserver/scaled/small", "/docuserver/images", "/docuserver/scans/quellen"}; + starttime = System.currentTimeMillis(); + // get our ServletContext + ServletContext context = config.getServletContext(); + // see if there is a Configuration instance + dlConfig = (DigilibConfiguration) context + .getAttribute("digilib.servlet.configuration"); + if (dlConfig == null) { + // no Configuration + throw new ServletException("No Configuration!"); + } + // set our AuthOps + useAuthorization = dlConfig.getAsBoolean("use-authorization"); + authOp = (AuthOps) dlConfig.getValue("servlet.auth.op"); + // DocuDirCache instance + dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache"); + denyImgFile = (File) dlConfig.getValue("denied-image"); + errorImgFile = (File) dlConfig.getValue("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); + } - /********************************************************* - * Initialize global variables - *********************************************************/ - public void init(ServletConfig config) throws ServletException { - super.init(config); + /** 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); + } - // first we need an Utils to setup ServletOps UGLY!! - util = new Utils(5); - // servletOps takes a ServletConfig to get the config file name - servletOp = new ServletOps(util, config); - // then we can start reading parameters UGLY!! + /* + * (non-Javadoc) + * + * @see javax.servlet.http.HttpServlet#getLastModified(javax.servlet.http.HttpServletRequest) + */ + protected long getLastModified(HttpServletRequest request) { + accountlog.debug("GetLastModified from " + request.getRemoteAddr() + + " for " + request.getQueryString()); + long mtime = -1; + // create new request with defaults + DigilibRequest dlReq = new DigilibRequest(); + // set with request parameters + dlReq.setWithRequest(request); + // find the requested file + DigiDirent f = findFile(dlReq); + if (f != null) { + DigiDirectory dd = f.getParent(); + mtime = dd.getMtime() / 1000 * 1000; + } + // limit time to servlet start time + if (mtime < starttime) { + mtime = starttime; + } + return mtime; + } + + /** main request handler. */ + void processRequest(HttpServletRequest request, HttpServletResponse response) + throws ServletException { - // Utils instance with debuglevel - int debugLevel = servletOp.tryToGetInitParam("debug-level", 10); - util = new Utils(debugLevel); - // reset Util for ServletOps instance - servletOp.setUtils(util); - // image file to send in case of error - String errorImgFileName = servletOp.tryToGetInitParam("error-image", "/docuserver/images/icons/scalerror.gif"); - errorImgFile = new File(errorImgFileName); - // image file to send if access is denied - String denyImgFileName = servletOp.tryToGetInitParam("denied-image", "/docuserver/images/icons/denied.gif"); - denyImgFile = new File(denyImgFileName); - // base directories in order of preference (prescaled versions first) - String baseDirList = servletOp.tryToGetInitParam("basedir-list", "/docuserver/scaled/small:/docuserver/images:/docuserver/scans/quellen"); - // split list into directories - StringTokenizer dirs = new StringTokenizer(baseDirList, ":"); - int n = dirs.countTokens(); - // add directories into array - baseDirs = new String[n]; - for (int i = 0; i < n; i++) { - baseDirs[i] = dirs.nextToken(); - } - // use authentication information - String useAuth = servletOp.tryToGetInitParam("use-authorization", "true"); - if ((useAuth.indexOf("false") > 0)||(useAuth.indexOf("FALSE") > 0)) { - useAuthentication = false; - } else { - useAuthentication = true; - try { - // DB version - //authOp = new DBAuthOpsImpl(util); - // XML version - String cnfPath = servletOp.tryToGetInitParam("auth-file", "/docuserver/www/digitallibrary/WEB-INF/digilib-auth.xml"); - authOp = new XMLAuthOps(util, cnfPath); - } catch (AuthOpException e) { - throw new ServletException(e); - } - } - // FileOps instance - fileOp = new FileOps(util); - // global DocuImage instance (don't reuse inside a request!) - globalImage = new JAIDocuImage(util); -// globalImage = new JIMIDocuImage(util); - //globalImage = new ImageLoaderDocuImage(util); + if (dlConfig == null) { + throw new ServletException("ERROR: No Configuration!"); + } + + 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; + // send the image always as JPEG + boolean forceJPEG = 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 + float mirrorAngle = 0; + // original (hires) image resolution + float origResX = 0; + float origResY = 0; + + /* request parameters */ + + DigilibRequest dlRequest = (DigilibRequest) request + .getAttribute("digilib.servlet.request"); - /**Process the HTTP Get request*/ - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - util.dprintln(1, "The servlet has received a GET!"); - processRequest(request, response); - } - - /**Process the HTTP Post request*/ - public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - util.dprintln(1, "The servlet has received a POST!"); - processRequest(request, response); - } - - /**Clean up resources*/ - public void destroy() { - } - -/********************************************************************** - * main request handler - **********************************************************************/ - - void processRequest(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + // destination image width + int paramDW = dlRequest.getAsInt("dw"); + // destination image height + int paramDH = dlRequest.getAsInt("dh"); + // relative area x_offset (0..1) + float paramWX = dlRequest.getAsFloat("wx"); + // relative area y_offset + float paramWY = dlRequest.getAsFloat("wy"); + // relative area width (0..1) + float paramWW = dlRequest.getAsFloat("ww"); + // relative area height + float paramWH = dlRequest.getAsFloat("wh"); + // scale factor (additional to dw/width, dh/height) + float paramWS = dlRequest.getAsFloat("ws"); + // rotation angle + float 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; + } - // time for benchmarking - long startTime = System.currentTimeMillis(); - // output mime/type - String mimeType = "image/png"; - - /** - * parameters for a session - */ + /* + * 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; + } + // operation mode: "jpg": always use JPEG + if (dlRequest.hasOption("mo", "jpg")) { + forceJPEG = true; + } - // scale the image file to fit window size - boolean scaleToFit = true; - // use heuristics (GIF?) to scale or not - boolean forcedScale = false; - // try prescaled images first - boolean preScaledFirst = true; - // interpolation to use for scaling - int scaleQual = 0; - // send html error message (or image file) - boolean errorMsgHtml = false; + // 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; + } - /** - * request parameter - */ + //"big" try for all file/image actions + try { + + // ImageFileset of the image to load + ImageFileset fileset = null; + + /* find the file to load/send */ + + // get PathInfo + String loadPathName = dlRequest.getFilePath(); - // file/dir to load - String param_fn = servletOp.tryToGetParam("fn", "", request); - // page number - int param_pn = servletOp.tryToGetParam("pn", 1, request); - // destination image width - int param_dw = servletOp.tryToGetParam("dw", 300, request); - // destination image height - int param_dh = servletOp.tryToGetParam("dh", 400, request); - // relative area x_offset (0..1) - float param_wx = servletOp.tryToGetParam("wx", 0f, request); - // relative area y_offset - float param_wy = servletOp.tryToGetParam("wy", 0f, request); - // relative area width (0..1) - float param_ww = servletOp.tryToGetParam("ww", 1f, request); - // relative area height - float param_wh = servletOp.tryToGetParam("wh", 1f, request); - // scale factor (additional to dw/width, dh/height) - float param_ws = servletOp.tryToGetParam("ws", 1f, request); - // operation mode: flags separated by "+" - String param_mo = servletOp.tryToGetParam("mo", "", request); - // operation mode: "fit": always fit to page, "file": send as-is - if (param_mo.indexOf("fit") >= 0) { - scaleToFit = true; - forcedScale = true; - } else if (param_mo.indexOf("file") >= 0) { - scaleToFit = false; - forcedScale = true; - } - // operation mode: "errtxt": error message in html, "errimg": error image - if (param_mo.indexOf("errtxt") >= 0) { - errorMsgHtml = true; - } else if (param_mo.indexOf("errimg") >= 0) { - errorMsgHtml = false; - } - // operation mode: "q0" - "q2": interpolation quality - if (param_mo.indexOf("q0") >= 0) { - scaleQual = 0; - } else if (param_mo.indexOf("q1") >= 0) { - scaleQual = 1; - } else if (param_mo.indexOf("q2") >= 0) { - scaleQual = 2; - } - // operation mode: "lores": try to use scaled image, "hires": unscaled image - if (param_mo.indexOf("lores") >= 0) { - preScaledFirst = true; - } else if (param_mo.indexOf("hires") >= 0) { - preScaledFirst = false; - } + /* check permissions */ + if (useAuthorization) { + // get a list of required roles (empty if no restrictions) + List rolesRequired = authOp.rolesForPath(loadPathName, request); + if (rolesRequired != null) { + authlog.debug("Role required: " + rolesRequired); + authlog.debug("User: " + request.getRemoteUser()); + // is the current request/user authorized? + if (!authOp.isRoleAuthorized(rolesRequired, request)) { + // send deny answer and abort + throw new AuthOpException(); + } + } + } + + // find the file + fileset = (ImageFileset) findFile(dlRequest); + if (fileset == null) { + throw new FileOpException("File " + loadPathName + "(" + + dlRequest.getAsInt("pn") + ") not found."); + } + + /* calculate expected source image size */ + ImageSize expectedSourceSize = new ImageSize(); + if (scaleToFit) { + float scale = (1 / Math.min(paramWW, paramWH)) * paramWS; + expectedSourceSize.setSize((int) (paramDW * scale), + (int) (paramDH * scale)); + } else { + expectedSourceSize.setSize((int) (paramDW * paramWS), + (int) (paramDH * paramWS)); + } - Utils.dprintln(1, "Parameter values: fn:"+param_fn+" pn:"+param_pn+" dw:"+param_dw+" dh:"+param_dh+" wx:"+param_wx+" wy:"+param_wy+" ww:"+param_ww+" wh:"+param_wh+" ws:"+param_ws+" mo:"+param_mo); + ImageFile fileToLoad; + /* 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); + if (fileToLoad == null) { + // this is the smallest we have + fileToLoad = fileset.getSmallest(); + } + } else { + // autores: use next higher resolution + fileToLoad = fileset.getNextBigger(expectedSourceSize); + if (fileToLoad == null) { + // this is the highest we have + fileToLoad = fileset.getBiggest(); + } + } + logger.info("Planning to load: " + fileToLoad); - //"big" try for all file/image actions - try { + /* + * 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"; + } + logger.debug("Sending RAW File as is."); + ServletOps.sendFile(fileToLoad, mt, response); + logger.info("Done in " + + (System.currentTimeMillis() - startTime) + "ms"); + return; + } + } - // DocuImage instance - DocuImage docuImage = new JAIDocuImage(util); -// DocuImage docuImage = new JIMIDocuImage(util); - //DocuImage docuImage = new ImageLoaderDocuImage(util); + // check the source image + fileToLoad.check(); + // get the source image type + mimeType = fileToLoad.getMimetype(); + // get the source image size + ImageSize imgSize = fileToLoad.getSize(); - - /** - * find the file to load/send - */ + // 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; - String loadPathName = ""; - // if there's PathInfo, append - if (request.getPathInfo() != null) { - loadPathName += request.getPathInfo(); - } - // append fn parameter - loadPathName += param_fn; - // if it's zoomed, try hires version (to be optimized...) - if ((param_ww < 1f) || (param_wh < 1f)) { - preScaledFirst = false; - } + /* + * 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, null, response); + + logger.info("Done in " + + (System.currentTimeMillis() - startTime) + "ms"); + return; + } - if (useAuthentication) { - // check permissions - List rolesRequired = authOp.rolesForPath(loadPathName, request); - if (rolesRequired != null) { - Utils.dprintln(1, "Role required: "+rolesRequired); - Utils.dprintln(2, "User: "+request.getRemoteUser()); - if (! authOp.isRoleAuthorized(rolesRequired, request)) { - Utils.dprintln(1, "ERROR: access denied!"); - if (errorMsgHtml) { - servletOp.htmlMessage("ERROR: Unauthorized access!", response); - } else { - docuImage.sendFile(denyImgFile, response); - } - return; - } - } - } + // set missing dw or dh from aspect ratio + float imgAspect = imgSize.getAspect(); + if (paramDW == 0) { + paramDW = (int) Math.round(paramDH * imgAspect); + } else if (paramDH == 0) { + paramDH = (int) Math.round(paramDW / imgAspect); + } + + /* + * prepare resolution for original size + */ + if (absoluteScale) { + // get original resolution from metadata + fileset.check(); + origResX = fileset.getResX(); + origResY = fileset.getResY(); + if ((origResX == 0) || (origResY == 0)) { + throw new ImageOpException("Missing image DPI information!"); + } - // find the file - File fileToLoad = fileOp.getFileVariant(baseDirs, loadPathName, param_pn, preScaledFirst); + if ((paramDDPIX == 0) || (paramDDPIY == 0)) { + throw new ImageOpException( + "Missing display DPI information!"); + } + } - Utils.dprintln(1, "Loading: "+fileToLoad); + /* crop and scale the image */ - // get the source image type (if it's known) - mimeType = fileOp.mimeForFile(fileToLoad); + logger.debug("IMG: " + imgSize.getWidth() + "x" + + imgSize.getHeight()); + logger.debug("time " + (System.currentTimeMillis() - startTime) + + "ms"); - // if not forced and source is GIF/PNG then send-as-is if not zoomed - if((!forcedScale && (mimeType == "image/gif" || mimeType == "image/png") - && (param_ww == 1f) && (param_wh == 1f)) || (forcedScale && !scaleToFit)) { - - Utils.dprintln(1, "Sending File as is."); + // coordinates and scaling + float areaXoff; + float areaYoff; + float areaWidth; + float areaHeight; + float scaleX; + float scaleY; + float scaleXY; - docuImage.sendFile(fileToLoad, response); - - Utils.dprintln(1, "Done in "+(System.currentTimeMillis()-startTime)+"ms"); - return; - } - - // load file - docuImage.loadImage(fileToLoad); - - /** - * crop and scale the image - */ - - // get size - int imgWidth = docuImage.getWidth(); - int imgHeight = docuImage.getHeight(); - - util.dprintln(2, "IMG: "+imgWidth+"x"+imgHeight); - util.dprintln(2, "time "+(System.currentTimeMillis()-startTime)+"ms"); + // coordinates using Java2D + // image size in pixels + Rectangle2D imgBounds = new Rectangle2D.Float(0, 0, imgSize + .getWidth(), imgSize.getHeight()); + // user window area in [0,1] coordinates + Rectangle2D relUserArea = new Rectangle2D.Float(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 absolute from relative coordinates - float areaXoff = param_wx * imgWidth; - float areaYoff = param_wy * imgHeight; - float areaWidth = param_ww * imgWidth; - float areaHeight = param_wh * imgHeight; - // calculate scaling factors - float scaleX = param_dw / areaWidth * param_ws; - float scaleY = param_dh / areaHeight * param_ws; - float scaleXY = (scaleX > scaleY) ? scaleY : scaleX; - - util.dprintln(1, "Scale "+scaleXY+"("+scaleX+","+scaleY+") on "+areaXoff+","+areaYoff+" "+areaWidth+"x"+areaHeight); + // calculate scaling factors based on inner user area + if (scaleToFit) { + areaWidth = (float) userImgArea.getWidth(); + areaHeight = (float) 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; + } - // fit area to image - areaWidth = (areaXoff + areaWidth > imgWidth) ? imgWidth - areaXoff : areaWidth; - areaHeight = (areaYoff + areaHeight > imgHeight) ? imgHeight - areaYoff : areaHeight; - - util.dprintln(2, "cropped: "+areaXoff+","+areaYoff+" "+areaWidth+"x"+areaHeight); + // 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 + logger.error(e1); + } + } + } - // check image parameters - if ((areaWidth < 1)||(areaHeight < 1) - ||(scaleXY * areaWidth < 2)||(scaleXY * areaHeight < 2)) { - Utils.dprintln(1, "ERROR: invalid scale parameter set!"); - throw new ImageOpException("Invalid scale parameter set!"); - } + logger.debug("Scale " + scaleXY + "(" + scaleX + "," + scaleY + + ") on " + outerUserImgArea); + + // clip area at the image border + outerUserImgArea = outerUserImgArea.createIntersection(imgBounds); - // crop and scale image - docuImage.cropAndScale((int)areaXoff, (int)areaYoff, (int)areaWidth, (int)areaHeight, - scaleXY, scaleQual); - - util.dprintln(2, "time "+(System.currentTimeMillis()-startTime)+"ms"); + // check image parameters sanity + if ((outerUserImgArea.getWidth() < 1) + || (outerUserImgArea.getHeight() < 1) + || (scaleXY * outerUserImgArea.getWidth() < 2) + || (scaleXY * outerUserImgArea.getHeight() < 2)) { + logger.error("ERROR: invalid scale parameter set!"); + throw new ImageOpException("Invalid scale parameter set!"); + } - /** - * write the resulting image - */ + /* + * submit the image worker job + */ + + DigilibWorker job = new DigilibImageWorker(dlConfig, response, + mimeType, scaleQual, dlRequest, paramROT, paramCONT, + paramBRGT, paramRGBM, paramRGBA, fileToLoad, scaleXY, + outerUserImgArea, innerUserImgArea, minSubsample, + wholeRotArea, forceJPEG); - // setup output -- if source is JPG then dest will be JPG else it's PNG - if (mimeType != "image/jpeg") { - mimeType="image/png"; - } + job.run(); + if (job.hasError()) { + throw new ImageOpException(job.getError().toString()); + } + + logger.debug("servlet done in " + + (System.currentTimeMillis() - startTime)); + + /* error handling */ - // write the image - docuImage.writeImage(mimeType, response); - - util.dprintln(1, "Done in "+(System.currentTimeMillis()-startTime)+"ms"); - - /** - * error handling - */ + } // end of "big" try + catch (IOException e) { + logger.error("ERROR: File IO Error: " + e); + digilibError(errorMsgHtml, ERROR_FILE, + "ERROR: File IO Error: " + e, response); + } catch (AuthOpException e) { + logger.error("ERROR: Authorization error: " + e); + digilibError(errorMsgHtml, ERROR_AUTH, + "ERROR: Authorization error: " + e, response); + } catch (ImageOpException e) { + logger.error("ERROR: Image Error: " + e); + digilibError(errorMsgHtml, ERROR_IMAGE, + "ERROR: Image Operation Error: " + e, response); + } catch (RuntimeException e) { + // JAI likes to throw RuntimeExceptions ;-( + logger.error("ERROR: Other Image Error: " + e); + digilibError(errorMsgHtml, ERROR_IMAGE, + "ERROR: Other Image Operation Error: " + e, response); + } + } - }//"big" try - catch (FileOpException e) { - util.dprintln(1, "ERROR: File IO Error: "+e); - try { - if (errorMsgHtml) { - servletOp.htmlMessage("ERROR: File IO Error: "+e, response); - } else { - globalImage.sendFile(errorImgFile, response); - } - } catch (FileOpException ex) {} // so we don't get a loop - return; - } - catch (AuthOpException e) { - Utils.dprintln(1, "ERROR: Authorization error: "+e); - try { - if (errorMsgHtml) { - servletOp.htmlMessage("ERROR: Authorization error: "+e, response); - } else { - globalImage.sendFile(errorImgFile, response); - } - } catch (FileOpException ex) {} // so we don't get a loop - return; - } - catch (ImageOpException e) { - Utils.dprintln(1, "ERROR: Image Error: "+e); - try { - if (errorMsgHtml) { - servletOp.htmlMessage("ERROR: Image Operation Error: "+e, response); - } else { - globalImage.sendFile(errorImgFile, response); - } - } catch (FileOpException ex) {} // so we don't get a loop - return; - } + /** + * Returns the DocuDirent corresponding to the DigilibRequest. + * + * @param dlRequest + * @return + */ + public DigiDirent findFile(DigilibRequest dlRequest) { + // find the file(set) + DigiDirent f = dirCache.getFile(dlRequest.getFilePath(), dlRequest + .getAsInt("pn"), FileOps.CLASS_IMAGE); + return f; + } - } + /** + * Sends an error to the client as text or image. + * + * @param asHTML + * @param type + * @param msg + * @param response + */ + public void digilibError(boolean asHTML, int type, String msg, + HttpServletResponse response) { + try { + File img = null; + if (type == ERROR_AUTH) { + if (msg == null) { + msg = "ERROR: Unauthorized access!"; + } + img = denyImgFile; + } else { + if (msg == null) { + msg = "ERROR: Other image error!"; + } + img = this.errorImgFile; + } + if (asHTML && (img != null)) { + ServletOps.htmlMessage(msg, response); + } else { + ServletOps.sendFile(img, null, response); + } + } catch (IOException e) { + logger.error("Error sending error!", e); + } -}//Scaler class + } + +} //Scaler class