comparison 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
comparison
equal deleted inserted replaced
902:89ba3ffcf552 903:7779b37d1d05
1 package digilib.image;
2
3 import java.awt.geom.AffineTransform;
4 import java.awt.geom.Rectangle2D;
5 import java.io.IOException;
6
7 import org.apache.log4j.Logger;
8
9 import digilib.image.DocuImage.ColorOp;
10 import digilib.io.DocuDirCache;
11 import digilib.io.DocuDirectory;
12 import digilib.io.FileOpException;
13 import digilib.io.FileOps;
14 import digilib.io.FileOps.FileClass;
15 import digilib.io.ImageInput;
16 import digilib.io.ImageSet;
17 import digilib.servlet.DigilibConfiguration;
18 import digilib.util.ImageSize;
19 import digilib.util.OptionsSet;
20 import digilib.util.Parameter;
21 import digilib.util.ParameterMap;
22
23 /**
24 * A class for storing the set of parameters necessary for scaling images
25 * with an ImageWorker.
26 *
27 * This contains the functionality formerly found in Scaler.processRequest(),
28 * only factorized.
29 *
30 * @author cmielack, casties
31 *
32 */
33
34 public class ImageJobDescription extends ParameterMap {
35
36 DigilibConfiguration dlConfig = null;
37 protected static Logger logger = Logger.getLogger("digilib.servlet");
38
39 ImageInput input = null;
40 ImageSet imageSet = null;
41 DocuDirectory fileDir = null;
42 String filePath = null;
43 ImageSize expectedSourceSize = null;
44 Float scaleXY = null;
45 Rectangle2D userImgArea = null;
46 Rectangle2D outerUserImgArea = null;
47 Boolean imageSendable = null;
48 String mimeType = null;
49 Integer paramDW = null;
50 Integer paramDH = null;
51
52 /** create empty ImageJobDescription.
53 * @param dlcfg
54 */
55 public ImageJobDescription(DigilibConfiguration dlcfg) {
56 super(30);
57 dlConfig = dlcfg;
58 }
59
60
61 /** set up Parameters
62 * @see digilib.util.ParameterMap#initParams()
63 */
64 @Override
65 protected void initParams() {
66 // url of the page/document (second part)
67 newParameter("fn", "", null, 's');
68 // page number
69 newParameter("pn", new Integer(1), null, 's');
70 // width of client in pixels
71 newParameter("dw", new Integer(0), null, 's');
72 // height of client in pixels
73 newParameter("dh", new Integer(0), null, 's');
74 // left edge of image (float from 0 to 1)
75 newParameter("wx", new Float(0), null, 's');
76 // top edge in image (float from 0 to 1)
77 newParameter("wy", new Float(0), null, 's');
78 // width of image (float from 0 to 1)
79 newParameter("ww", new Float(1), null, 's');
80 // height of image (float from 0 to 1)
81 newParameter("wh", new Float(1), null, 's');
82 // scale factor
83 newParameter("ws", new Float(1), null, 's');
84 // special options like 'fit' for gifs
85 newParameter("mo", this.options, null, 's');
86 // rotation angle (degree)
87 newParameter("rot", new Float(0), null, 's');
88 // contrast enhancement factor
89 newParameter("cont", new Float(0), null, 's');
90 // brightness enhancement factor
91 newParameter("brgt", new Float(0), null, 's');
92 // color multiplicative factors
93 newParameter("rgbm", "0/0/0", null, 's');
94 // color additive factors
95 newParameter("rgba", "0/0/0", null, 's');
96 // display dpi resolution (total)
97 newParameter("ddpi", new Float(0), null, 's');
98 // display dpi X resolution
99 newParameter("ddpix", new Float(0), null, 's');
100 // display dpi Y resolution
101 newParameter("ddpiy", new Float(0), null, 's');
102 // scale factor for mo=ascale
103 newParameter("scale", new Float(1), null, 's');
104 // color conversion operation
105 newParameter("colop", "", null, 's');
106 }
107
108
109 /* (non-Javadoc)
110 * @see digilib.servlet.ParameterMap#initOptions()
111 */
112 @Override
113 protected void initOptions() {
114 String s = this.getAsString("mo");
115 options = new OptionsSet(s);
116 }
117
118
119 /** Creates new ImageJobDescription by merging Parameters from another ParameterMap.
120 * @param pm
121 * @param dlcfg
122 * @return
123 */
124 public static ImageJobDescription getInstance(ParameterMap pm, DigilibConfiguration dlcfg) {
125 ImageJobDescription newMap = new ImageJobDescription(dlcfg);
126 // add all params to this map
127 newMap.params.putAll(pm.getParams());
128 newMap.initOptions();
129 return newMap;
130 }
131
132
133 /** Returns the mime-type (of the input).
134 * @return
135 * @throws IOException
136 */
137 public String getMimeType() throws IOException {
138 if (mimeType == null) {
139 input = getInput();
140 mimeType = input.getMimetype();
141 }
142 return mimeType;
143 }
144
145 /** Returns the ImageInput to use.
146 * @return
147 * @throws IOException
148 */
149 public ImageInput getInput() throws IOException {
150 if(input == null){
151 imageSet = getImageSet();
152
153 /* select a resolution */
154 if (isHiresOnly()) {
155 // get first element (= highest resolution)
156 input = imageSet.getBiggest();
157 } else if (isLoresOnly()) {
158 // enforced lores uses next smaller resolution
159 input = imageSet.getNextSmaller(getExpectedSourceSize());
160 if (input == null) {
161 // this is the smallest we have
162 input = imageSet.getSmallest();
163 }
164 } else {
165 // autores: use next higher resolution
166 input = imageSet.getNextBigger(getExpectedSourceSize());
167 if (input == null) {
168 // this is the highest we have
169 input = imageSet.getBiggest();
170 }
171 }
172 if (input == null || input.getMimetype() == null) {
173 throw new FileOpException("Unable to load "+input);
174 }
175 logger.info("Planning to load: " + input);
176 }
177 return input;
178 }
179
180 /** Returns the DocuDirectory for the input (file).
181 * @return
182 * @throws FileOpException
183 */
184 public DocuDirectory getFileDirectory() throws FileOpException {
185 if(fileDir == null){
186 DocuDirCache dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache");
187 String fp = getFilePath();
188 fileDir = dirCache.getDirectory(fp);
189 if (fileDir == null) {
190 throw new FileOpException("Directory " + getFilePath() + " not found.");
191 }
192 }
193 return fileDir;
194 }
195
196 /** Returns the ImageSet to load.
197 * @return
198 * @throws FileOpException
199 */
200 public ImageSet getImageSet() throws FileOpException {
201 if(imageSet==null){
202 DocuDirCache dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache");
203
204 imageSet = (ImageSet) dirCache.getFile(getFilePath(), getAsInt("pn"), FileClass.IMAGE);
205 if (imageSet == null) {
206 throw new FileOpException("File " + getFilePath() + "("
207 + getAsInt("pn") + ") not found.");
208 }
209 }
210 return imageSet;
211 }
212
213 /** Returns the file path name from the request.
214 * @return
215 */
216 public String getFilePath() {
217 if(filePath == null){
218 String s = this.getAsString("request.path");
219 s += this.getAsString("fn");
220 filePath = FileOps.normalName(s);
221 }
222 return filePath;
223 }
224
225 public boolean isHiresOnly(){
226 return hasOption("clip") || hasOption("hires");
227 }
228
229 public boolean isLoresOnly(){
230 return hasOption("lores");
231 }
232
233 public boolean isScaleToFit() {
234 return !(hasOption("clip") || hasOption("osize") || hasOption("ascale"));
235 }
236
237 public boolean isAbsoluteScale(){
238 return hasOption("osize") || hasOption("ascale");
239 }
240
241
242 /** Returns the minimum size the source image should have for scaling.
243 * @return
244 * @throws IOException
245 */
246 public ImageSize getExpectedSourceSize() throws IOException {
247 if (expectedSourceSize == null){
248 expectedSourceSize = new ImageSize();
249 if (isScaleToFit()) {
250 // scale to fit -- calculate minimum source size
251 float scale = (1 / Math.min(getAsFloat("ww"), getAsFloat("wh"))) * getAsFloat("ws");
252 expectedSourceSize.setSize((int) (getDw() * scale),
253 (int) (getDh() * scale));
254 } else if (isAbsoluteScale() && hasOption("ascale")) {
255 // absolute scale -- apply scale to hires size
256 expectedSourceSize = getHiresSize().getScaled(getAsFloat("scale"));
257 } else {
258 // clip to fit -- source = destination size
259 expectedSourceSize.setSize((int) (getDw() * getAsFloat("ws")),
260 (int) (getDh() * getAsFloat("ws")));
261 }
262 }
263 return expectedSourceSize;
264 }
265
266 /** Returns the size of the highest resolution image.
267 * @return
268 * @throws IOException
269 */
270 public ImageSize getHiresSize() throws IOException {
271 logger.debug("get_hiresSize()");
272
273 ImageSize hiresSize = null;
274 ImageSet fileset = getImageSet();
275 if (isAbsoluteScale()) {
276 ImageInput hiresFile = fileset.getBiggest();
277 hiresSize = hiresFile.getSize();
278 }
279 return hiresSize;
280 }
281
282 /** Returns image scaling factor.
283 * Uses image size and user parameters.
284 * Modifies scaleXY, userImgArea.
285 * @return
286 * @throws IOException
287 * @throws ImageOpException
288 */
289 public float getScaleXY() throws IOException, ImageOpException {
290 //logger.debug("get_scaleXY()");
291 if(scaleXY == null){
292 // coordinates and scaling
293 float areaWidth;
294 float areaHeight;
295 float ws = getAsFloat("ws");
296 ImageSize imgSize = getInput().getSize();
297 // user window area in [0,1] coordinates
298 Rectangle2D relUserArea = new Rectangle2D.Float(getAsFloat("wx"), getAsFloat("wy"),
299 getAsFloat("ww"), getAsFloat("wh"));
300 // transform from relative [0,1] to image coordinates.
301 AffineTransform imgTrafo = AffineTransform.getScaleInstance(imgSize
302 .getWidth(), imgSize.getHeight());
303 // transform user coordinate area to image coordinate area
304 userImgArea = imgTrafo.createTransformedShape(
305 relUserArea).getBounds2D();
306
307 if (isScaleToFit()) {
308 // calculate scaling factors based on inner user area
309 areaWidth = (float) userImgArea.getWidth();
310 areaHeight = (float) userImgArea.getHeight();
311 float scaleX = getDw() / areaWidth * ws;
312 float scaleY = getDh() / areaHeight * ws;
313 scaleXY = (scaleX > scaleY) ? scaleY : scaleX;
314 } else if (isAbsoluteScale()) {
315 // absolute scaling factor
316 if (hasOption("osize")) {
317 // get original resolution from metadata
318 imageSet.checkMeta();
319 float origResX = imageSet.getResX();
320 float origResY = imageSet.getResY();
321 if ((origResX == 0) || (origResY == 0)) {
322 throw new ImageOpException("Missing image DPI information!");
323 }
324 float ddpix = getAsFloat("ddpix");
325 float ddpiy = getAsFloat("ddpiy");
326 if (ddpix == 0 || ddpiy == 0) {
327 float ddpi = getAsFloat("ddpi");
328 if (ddpi == 0) {
329 throw new ImageOpException("Missing display DPI information!");
330 } else {
331 ddpix = ddpi;
332 ddpiy = ddpi;
333 }
334 }
335 // calculate absolute scale factor
336 float sx = ddpix / origResX;
337 float sy = ddpiy / origResY;
338 // currently only same scale -- mean value
339 scaleXY = (sx + sy) / 2f;
340 } else {
341 scaleXY = getAsFloat("scale");
342 }
343 // we need to correct the factor if we use a pre-scaled image
344 ImageSize hiresSize = getHiresSize();
345 if (imgSize.getWidth() != hiresSize.getWidth()) {
346 scaleXY *= (float)hiresSize.getWidth() / (float)imgSize.getWidth();
347 }
348 areaWidth = getDw() / scaleXY * ws;
349 areaHeight = getDh() / scaleXY * ws;
350 // reset user area size
351 userImgArea.setRect(userImgArea.getX(), userImgArea.getY(),
352 areaWidth, areaHeight);
353 } else {
354 // crop to fit -- don't scale
355 areaWidth = getDw() * ws;
356 areaHeight = getDh() * ws;
357 // reset user area size
358 userImgArea.setRect(userImgArea.getX(), userImgArea.getY(),
359 areaWidth, areaHeight);
360 scaleXY = 1f;
361 }
362 }
363 return (float) scaleXY;
364 }
365
366 /** Returns the width of the destination image.
367 * Uses dh parameter and aspect ratio if dw parameter is empty.
368 * @return
369 * @throws IOException
370 */
371 public int getDw() throws IOException {
372 logger.debug("get_paramDW()");
373 if (paramDW == null) {
374
375 paramDW = getAsInt("dw");
376 paramDH = getAsInt("dh");
377
378 float imgAspect = getInput().getAspect();
379 if (paramDW == 0) {
380 // calculate dw
381 paramDW = Math.round(paramDH * imgAspect);
382 setValue("dw", paramDW);
383 } else if (paramDH == 0) {
384 // calculate dh
385 paramDH = Math.round(paramDW / imgAspect);
386 setValue("dh", paramDH);
387 }
388 }
389 return paramDW;
390 }
391
392 /** Returns the height of the destination image.
393 * Uses dw parameter and aspect ratio if dh parameter is empty.
394 * @return
395 * @throws IOException
396 */
397 public int getDh() throws IOException {
398 logger.debug("get_paramDH()");
399 if (paramDH == null) {
400
401 paramDW = getAsInt("dw");
402 paramDH = getAsInt("dh");
403
404 float imgAspect = getInput().getAspect();
405 if (paramDW == 0) {
406 // calculate dw
407 paramDW = Math.round(paramDH * imgAspect);
408 setValue("dw", paramDW);
409 } else if (paramDH == 0) {
410 // calculate dh
411 paramDH = Math.round(paramDW / imgAspect);
412 setValue("dh", paramDH);
413 }
414 }
415 return paramDH;
416 }
417
418 /** Returns image quality as an integer.
419 * @return
420 */
421 public int getScaleQual(){
422 logger.debug("get_scaleQual()");
423 int qual = dlConfig.getAsInt("default-quality");
424 if(hasOption("q0"))
425 qual = 0;
426 else if(hasOption("q1"))
427 qual = 1;
428 else if(hasOption("q2"))
429 qual = 2;
430 return qual;
431 }
432
433 public ColorOp getColOp() {
434 String op = getAsString("colop");
435 if (op == null || op.length() == 0) {
436 return null;
437 }
438 try {
439 return ColorOp.valueOf(op.toUpperCase());
440 } catch (Exception e) {
441 logger.error("Invalid color op: " + op);
442 }
443 return null;
444 }
445
446 /**
447 * Returns the area of the source image that will be transformed into the
448 * destination image.
449 *
450 * @return
451 * @throws IOException
452 * @throws ImageOpException
453 */
454 public Rectangle2D getUserImgArea() throws IOException, ImageOpException{
455 if(userImgArea == null) {
456 // getScaleXY sets userImgArea
457 getScaleXY();
458 }
459 return userImgArea;
460 }
461
462 /** Returns the maximal area of the source image that will be used.
463 * @return
464 * @throws IOException
465 * @throws ImageOpException
466 */
467 public Rectangle2D getOuterUserImgArea() throws IOException, ImageOpException {
468 if(outerUserImgArea == null){
469 outerUserImgArea = getUserImgArea();
470
471 // image size in pixels
472 ImageSize imgSize = getInput().getSize();
473 Rectangle2D imgBounds = new Rectangle2D.Float(0, 0, imgSize.getWidth(),
474 imgSize.getHeight());
475
476 // clip area at the image border
477 outerUserImgArea = outerUserImgArea.createIntersection(imgBounds);
478
479 // check image parameters sanity
480 scaleXY = getScaleXY();
481 logger.debug("outerUserImgArea.getWidth()=" + outerUserImgArea.getWidth());
482 logger.debug("get_scaleXY() * outerUserImgArea.getWidth() = " + (scaleXY * outerUserImgArea.getWidth()));
483
484 if ((outerUserImgArea.getWidth() < 1)
485 || (outerUserImgArea.getHeight() < 1)
486 || (scaleXY * outerUserImgArea.getWidth() < 2)
487 || (scaleXY * outerUserImgArea.getHeight() < 2)) {
488 logger.error("ERROR: invalid scale parameter set!");
489 throw new ImageOpException("Invalid scale parameter set!");
490 }
491 }
492 return outerUserImgArea;
493 }
494
495
496 public float[] getRGBM(){
497 float[] paramRGBM = null;//{0f,0f,0f};
498 Parameter p = params.get("rgbm");
499 if (p.hasValue() && (!p.getAsString().equals("0/0/0"))) {
500 return p.parseAsFloatArray("/");
501 }
502 return paramRGBM;
503 }
504
505 public float[] getRGBA(){
506 float[] paramRGBA = null;//{0f,0f,0f};
507 Parameter p = params.get("rgba");
508 if (p.hasValue() && (!p.getAsString().equals("0/0/0"))) {
509 paramRGBA = p.parseAsFloatArray("/");
510 }
511 return paramRGBA;
512 }
513
514 /** Has send-as-file been requested?
515 * @return
516 */
517 public boolean getSendAsFile(){
518 return hasOption("file")
519 || hasOption("rawfile");
520 }
521
522 /**
523 * Returns if the image can be sent without processing. Takes image type and
524 * additional image operations into account. Does not check requested size
525 * transformation.
526 *
527 * @return
528 * @throws IOException
529 */
530 public boolean isImageSendable() throws IOException {
531 if (imageSendable == null) {
532 String mimeType = getMimeType();
533 imageSendable = (mimeType != null
534 && (mimeType.equals("image/jpeg") || mimeType.equals("image/png")
535 || mimeType.equals("image/gif"))
536 && !(hasOption("hmir")
537 || hasOption("vmir") || (getAsFloat("rot") != 0.0)
538 || (getRGBM() != null) || (getRGBA() != null)
539 || (getAsFloat("cont") != 0.0) || (getAsFloat("brgt") != 0.0)));
540 }
541 return imageSendable;
542 }
543
544
545 /**
546 * Returns if any transformation of the source image (image manipulation or
547 * format conversion) is required.
548 *
549 * @return
550 * @throws IOException
551 */
552 public boolean isTransformRequired() throws IOException {
553 ImageSize is = getInput().getSize();
554 ImageSize ess = getExpectedSourceSize();
555 // nt = no transform required
556 boolean nt = isImageSendable() && (
557 // lores: send if smaller
558 (isLoresOnly() && is.isSmallerThan(ess))
559 // else send if it fits
560 || (!(isLoresOnly() || isHiresOnly()) && is.fitsIn(ess)));
561 return ! nt;
562 }
563 }