Mercurial > hg > digilib-old
diff common/src/main/java/digilib/image/ImageJobDescription.java @ 903:7779b37d1d05
refactored into maven modules per servlet type.
can build servlet-api 2.3 and 3.0 via profile now!
author | robcast |
---|---|
date | Tue, 26 Apr 2011 20:24:31 +0200 |
parents | servlet/src/main/java/digilib/image/ImageJobDescription.java@ba1eb2d821a2 |
children | 28d007673346 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/common/src/main/java/digilib/image/ImageJobDescription.java Tue Apr 26 20:24:31 2011 +0200 @@ -0,0 +1,563 @@ +package digilib.image; + +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.io.IOException; + +import org.apache.log4j.Logger; + +import digilib.image.DocuImage.ColorOp; +import digilib.io.DocuDirCache; +import digilib.io.DocuDirectory; +import digilib.io.FileOpException; +import digilib.io.FileOps; +import digilib.io.FileOps.FileClass; +import digilib.io.ImageInput; +import digilib.io.ImageSet; +import digilib.servlet.DigilibConfiguration; +import digilib.util.ImageSize; +import digilib.util.OptionsSet; +import digilib.util.Parameter; +import digilib.util.ParameterMap; + +/** + * A class for storing the set of parameters necessary for scaling images + * with an ImageWorker. + * + * This contains the functionality formerly found in Scaler.processRequest(), + * only factorized. + * + * @author cmielack, casties + * + */ + +public class ImageJobDescription extends ParameterMap { + + DigilibConfiguration dlConfig = null; + protected static Logger logger = Logger.getLogger("digilib.servlet"); + + ImageInput input = null; + ImageSet imageSet = null; + DocuDirectory fileDir = null; + String filePath = null; + ImageSize expectedSourceSize = null; + Float scaleXY = null; + Rectangle2D userImgArea = null; + Rectangle2D outerUserImgArea = null; + Boolean imageSendable = null; + String mimeType = null; + Integer paramDW = null; + Integer paramDH = null; + + /** create empty ImageJobDescription. + * @param dlcfg + */ + public ImageJobDescription(DigilibConfiguration dlcfg) { + super(30); + dlConfig = dlcfg; + } + + + /** set up Parameters + * @see digilib.util.ParameterMap#initParams() + */ + @Override + protected void initParams() { + // url of the page/document (second part) + newParameter("fn", "", null, 's'); + // page number + newParameter("pn", new Integer(1), null, 's'); + // width of client in pixels + newParameter("dw", new Integer(0), null, 's'); + // height of client in pixels + newParameter("dh", new Integer(0), null, 's'); + // left edge of image (float from 0 to 1) + newParameter("wx", new Float(0), null, 's'); + // top edge in image (float from 0 to 1) + newParameter("wy", new Float(0), null, 's'); + // width of image (float from 0 to 1) + newParameter("ww", new Float(1), null, 's'); + // height of image (float from 0 to 1) + newParameter("wh", new Float(1), null, 's'); + // scale factor + newParameter("ws", new Float(1), null, 's'); + // special options like 'fit' for gifs + newParameter("mo", this.options, null, 's'); + // rotation angle (degree) + newParameter("rot", new Float(0), null, 's'); + // contrast enhancement factor + newParameter("cont", new Float(0), null, 's'); + // brightness enhancement factor + newParameter("brgt", new Float(0), null, 's'); + // color multiplicative factors + newParameter("rgbm", "0/0/0", null, 's'); + // color additive factors + newParameter("rgba", "0/0/0", null, 's'); + // display dpi resolution (total) + newParameter("ddpi", new Float(0), null, 's'); + // display dpi X resolution + newParameter("ddpix", new Float(0), null, 's'); + // display dpi Y resolution + newParameter("ddpiy", new Float(0), null, 's'); + // scale factor for mo=ascale + newParameter("scale", new Float(1), null, 's'); + // color conversion operation + newParameter("colop", "", null, 's'); + } + + + /* (non-Javadoc) + * @see digilib.servlet.ParameterMap#initOptions() + */ + @Override + protected void initOptions() { + String s = this.getAsString("mo"); + options = new OptionsSet(s); + } + + + /** Creates new ImageJobDescription by merging Parameters from another ParameterMap. + * @param pm + * @param dlcfg + * @return + */ + public static ImageJobDescription getInstance(ParameterMap pm, DigilibConfiguration dlcfg) { + ImageJobDescription newMap = new ImageJobDescription(dlcfg); + // add all params to this map + newMap.params.putAll(pm.getParams()); + newMap.initOptions(); + return newMap; + } + + + /** Returns the mime-type (of the input). + * @return + * @throws IOException + */ + public String getMimeType() throws IOException { + if (mimeType == null) { + input = getInput(); + mimeType = input.getMimetype(); + } + return mimeType; + } + + /** Returns the ImageInput to use. + * @return + * @throws IOException + */ + public ImageInput getInput() throws IOException { + if(input == null){ + imageSet = getImageSet(); + + /* select a resolution */ + if (isHiresOnly()) { + // get first element (= highest resolution) + input = imageSet.getBiggest(); + } else if (isLoresOnly()) { + // enforced lores uses next smaller resolution + input = imageSet.getNextSmaller(getExpectedSourceSize()); + if (input == null) { + // this is the smallest we have + input = imageSet.getSmallest(); + } + } else { + // autores: use next higher resolution + input = imageSet.getNextBigger(getExpectedSourceSize()); + if (input == null) { + // this is the highest we have + input = imageSet.getBiggest(); + } + } + if (input == null || input.getMimetype() == null) { + throw new FileOpException("Unable to load "+input); + } + logger.info("Planning to load: " + input); + } + return input; + } + + /** Returns the DocuDirectory for the input (file). + * @return + * @throws FileOpException + */ + public DocuDirectory getFileDirectory() throws FileOpException { + if(fileDir == null){ + DocuDirCache dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache"); + String fp = getFilePath(); + fileDir = dirCache.getDirectory(fp); + if (fileDir == null) { + throw new FileOpException("Directory " + getFilePath() + " not found."); + } + } + return fileDir; + } + + /** Returns the ImageSet to load. + * @return + * @throws FileOpException + */ + public ImageSet getImageSet() throws FileOpException { + if(imageSet==null){ + DocuDirCache dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache"); + + imageSet = (ImageSet) dirCache.getFile(getFilePath(), getAsInt("pn"), FileClass.IMAGE); + if (imageSet == null) { + throw new FileOpException("File " + getFilePath() + "(" + + getAsInt("pn") + ") not found."); + } + } + return imageSet; + } + + /** Returns the file path name from the request. + * @return + */ + public String getFilePath() { + if(filePath == null){ + String s = this.getAsString("request.path"); + s += this.getAsString("fn"); + filePath = FileOps.normalName(s); + } + return filePath; + } + + public boolean isHiresOnly(){ + return hasOption("clip") || hasOption("hires"); + } + + public boolean isLoresOnly(){ + return hasOption("lores"); + } + + public boolean isScaleToFit() { + return !(hasOption("clip") || hasOption("osize") || hasOption("ascale")); + } + + public boolean isAbsoluteScale(){ + return hasOption("osize") || hasOption("ascale"); + } + + + /** Returns the minimum size the source image should have for scaling. + * @return + * @throws IOException + */ + public ImageSize getExpectedSourceSize() throws IOException { + if (expectedSourceSize == null){ + expectedSourceSize = new ImageSize(); + if (isScaleToFit()) { + // scale to fit -- calculate minimum source size + float scale = (1 / Math.min(getAsFloat("ww"), getAsFloat("wh"))) * getAsFloat("ws"); + expectedSourceSize.setSize((int) (getDw() * scale), + (int) (getDh() * scale)); + } else if (isAbsoluteScale() && hasOption("ascale")) { + // absolute scale -- apply scale to hires size + expectedSourceSize = getHiresSize().getScaled(getAsFloat("scale")); + } else { + // clip to fit -- source = destination size + expectedSourceSize.setSize((int) (getDw() * getAsFloat("ws")), + (int) (getDh() * getAsFloat("ws"))); + } + } + return expectedSourceSize; + } + + /** Returns the size of the highest resolution image. + * @return + * @throws IOException + */ + public ImageSize getHiresSize() throws IOException { + logger.debug("get_hiresSize()"); + + ImageSize hiresSize = null; + ImageSet fileset = getImageSet(); + if (isAbsoluteScale()) { + ImageInput hiresFile = fileset.getBiggest(); + hiresSize = hiresFile.getSize(); + } + return hiresSize; + } + + /** Returns image scaling factor. + * Uses image size and user parameters. + * Modifies scaleXY, userImgArea. + * @return + * @throws IOException + * @throws ImageOpException + */ + public float getScaleXY() throws IOException, ImageOpException { + //logger.debug("get_scaleXY()"); + if(scaleXY == null){ + // coordinates and scaling + float areaWidth; + float areaHeight; + float ws = getAsFloat("ws"); + ImageSize imgSize = getInput().getSize(); + // user window area in [0,1] coordinates + Rectangle2D relUserArea = new Rectangle2D.Float(getAsFloat("wx"), getAsFloat("wy"), + getAsFloat("ww"), getAsFloat("wh")); + // transform from relative [0,1] to image coordinates. + AffineTransform imgTrafo = AffineTransform.getScaleInstance(imgSize + .getWidth(), imgSize.getHeight()); + // transform user coordinate area to image coordinate area + userImgArea = imgTrafo.createTransformedShape( + relUserArea).getBounds2D(); + + if (isScaleToFit()) { + // calculate scaling factors based on inner user area + areaWidth = (float) userImgArea.getWidth(); + areaHeight = (float) userImgArea.getHeight(); + float scaleX = getDw() / areaWidth * ws; + float scaleY = getDh() / areaHeight * ws; + scaleXY = (scaleX > scaleY) ? scaleY : scaleX; + } else if (isAbsoluteScale()) { + // absolute scaling factor + if (hasOption("osize")) { + // get original resolution from metadata + imageSet.checkMeta(); + float origResX = imageSet.getResX(); + float origResY = imageSet.getResY(); + if ((origResX == 0) || (origResY == 0)) { + throw new ImageOpException("Missing image DPI information!"); + } + float ddpix = getAsFloat("ddpix"); + float ddpiy = getAsFloat("ddpiy"); + if (ddpix == 0 || ddpiy == 0) { + float ddpi = getAsFloat("ddpi"); + if (ddpi == 0) { + throw new ImageOpException("Missing display DPI information!"); + } else { + ddpix = ddpi; + ddpiy = ddpi; + } + } + // calculate absolute scale factor + float sx = ddpix / origResX; + float sy = ddpiy / origResY; + // currently only same scale -- mean value + scaleXY = (sx + sy) / 2f; + } else { + scaleXY = getAsFloat("scale"); + } + // we need to correct the factor if we use a pre-scaled image + ImageSize hiresSize = getHiresSize(); + if (imgSize.getWidth() != hiresSize.getWidth()) { + scaleXY *= (float)hiresSize.getWidth() / (float)imgSize.getWidth(); + } + areaWidth = getDw() / scaleXY * ws; + areaHeight = getDh() / scaleXY * ws; + // reset user area size + userImgArea.setRect(userImgArea.getX(), userImgArea.getY(), + areaWidth, areaHeight); + } else { + // crop to fit -- don't scale + areaWidth = getDw() * ws; + areaHeight = getDh() * ws; + // reset user area size + userImgArea.setRect(userImgArea.getX(), userImgArea.getY(), + areaWidth, areaHeight); + scaleXY = 1f; + } + } + return (float) scaleXY; + } + + /** Returns the width of the destination image. + * Uses dh parameter and aspect ratio if dw parameter is empty. + * @return + * @throws IOException + */ + public int getDw() throws IOException { + logger.debug("get_paramDW()"); + if (paramDW == null) { + + paramDW = getAsInt("dw"); + paramDH = getAsInt("dh"); + + float imgAspect = getInput().getAspect(); + if (paramDW == 0) { + // calculate dw + paramDW = Math.round(paramDH * imgAspect); + setValue("dw", paramDW); + } else if (paramDH == 0) { + // calculate dh + paramDH = Math.round(paramDW / imgAspect); + setValue("dh", paramDH); + } + } + return paramDW; + } + + /** Returns the height of the destination image. + * Uses dw parameter and aspect ratio if dh parameter is empty. + * @return + * @throws IOException + */ + public int getDh() throws IOException { + logger.debug("get_paramDH()"); + if (paramDH == null) { + + paramDW = getAsInt("dw"); + paramDH = getAsInt("dh"); + + float imgAspect = getInput().getAspect(); + if (paramDW == 0) { + // calculate dw + paramDW = Math.round(paramDH * imgAspect); + setValue("dw", paramDW); + } else if (paramDH == 0) { + // calculate dh + paramDH = Math.round(paramDW / imgAspect); + setValue("dh", paramDH); + } + } + return paramDH; + } + + /** Returns image quality as an integer. + * @return + */ + public int getScaleQual(){ + logger.debug("get_scaleQual()"); + int qual = dlConfig.getAsInt("default-quality"); + if(hasOption("q0")) + qual = 0; + else if(hasOption("q1")) + qual = 1; + else if(hasOption("q2")) + qual = 2; + return qual; + } + + public ColorOp getColOp() { + String op = getAsString("colop"); + if (op == null || op.length() == 0) { + return null; + } + try { + return ColorOp.valueOf(op.toUpperCase()); + } catch (Exception e) { + logger.error("Invalid color op: " + op); + } + return null; + } + + /** + * Returns the area of the source image that will be transformed into the + * destination image. + * + * @return + * @throws IOException + * @throws ImageOpException + */ + public Rectangle2D getUserImgArea() throws IOException, ImageOpException{ + if(userImgArea == null) { + // getScaleXY sets userImgArea + getScaleXY(); + } + return userImgArea; + } + + /** Returns the maximal area of the source image that will be used. + * @return + * @throws IOException + * @throws ImageOpException + */ + public Rectangle2D getOuterUserImgArea() throws IOException, ImageOpException { + if(outerUserImgArea == null){ + outerUserImgArea = getUserImgArea(); + + // image size in pixels + ImageSize imgSize = getInput().getSize(); + Rectangle2D imgBounds = new Rectangle2D.Float(0, 0, imgSize.getWidth(), + imgSize.getHeight()); + + // clip area at the image border + outerUserImgArea = outerUserImgArea.createIntersection(imgBounds); + + // check image parameters sanity + scaleXY = getScaleXY(); + logger.debug("outerUserImgArea.getWidth()=" + outerUserImgArea.getWidth()); + logger.debug("get_scaleXY() * outerUserImgArea.getWidth() = " + (scaleXY * outerUserImgArea.getWidth())); + + 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!"); + } + } + return outerUserImgArea; + } + + + public float[] getRGBM(){ + float[] paramRGBM = null;//{0f,0f,0f}; + Parameter p = params.get("rgbm"); + if (p.hasValue() && (!p.getAsString().equals("0/0/0"))) { + return p.parseAsFloatArray("/"); + } + return paramRGBM; + } + + public float[] getRGBA(){ + float[] paramRGBA = null;//{0f,0f,0f}; + Parameter p = params.get("rgba"); + if (p.hasValue() && (!p.getAsString().equals("0/0/0"))) { + paramRGBA = p.parseAsFloatArray("/"); + } + return paramRGBA; + } + + /** Has send-as-file been requested? + * @return + */ + public boolean getSendAsFile(){ + return hasOption("file") + || hasOption("rawfile"); + } + + /** + * Returns if the image can be sent without processing. Takes image type and + * additional image operations into account. Does not check requested size + * transformation. + * + * @return + * @throws IOException + */ + public boolean isImageSendable() throws IOException { + if (imageSendable == null) { + String mimeType = getMimeType(); + imageSendable = (mimeType != null + && (mimeType.equals("image/jpeg") || mimeType.equals("image/png") + || mimeType.equals("image/gif")) + && !(hasOption("hmir") + || hasOption("vmir") || (getAsFloat("rot") != 0.0) + || (getRGBM() != null) || (getRGBA() != null) + || (getAsFloat("cont") != 0.0) || (getAsFloat("brgt") != 0.0))); + } + return imageSendable; + } + + + /** + * Returns if any transformation of the source image (image manipulation or + * format conversion) is required. + * + * @return + * @throws IOException + */ + public boolean isTransformRequired() throws IOException { + ImageSize is = getInput().getSize(); + ImageSize ess = getExpectedSourceSize(); + // nt = no transform required + boolean nt = isImageSendable() && ( + // lores: send if smaller + (isLoresOnly() && is.isSmallerThan(ess)) + // else send if it fits + || (!(isLoresOnly() || isHiresOnly()) && is.fitsIn(ess))); + return ! nt; + } +} \ No newline at end of file