001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.jsp.util; 029 030import org.opencms.ade.galleries.shared.CmsPoint; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsResource; 033import org.opencms.jsp.CmsJspResourceWrapper; 034import org.opencms.loader.CmsImageScaler; 035import org.opencms.main.CmsException; 036import org.opencms.main.CmsLog; 037import org.opencms.main.OpenCms; 038import org.opencms.util.CmsCollectionsGenericWrapper; 039import org.opencms.util.CmsRequestUtil; 040import org.opencms.util.CmsUriSplitter; 041 042import java.util.Map; 043import java.util.TreeMap; 044 045import org.apache.commons.collections.Transformer; 046import org.apache.commons.logging.Log; 047 048/** 049 * Bean containing image information for the use in JSP (for example formatters). 050 */ 051public class CmsJspImageBean { 052 053 /** 054 * Provides a Map to access hi-DPI versions of the current image.<p> 055 */ 056 public class CmsScaleHiDpiTransformer implements Transformer { 057 058 /** 059 * @see org.apache.commons.collections.Transformer#transform(java.lang.Object) 060 */ 061 @Override 062 public Object transform(Object input) { 063 064 return createHiDpiVariation(String.valueOf(input)); 065 } 066 } 067 068 /** 069 * Provides a Map to access ratio scaled versions of the current image.<p> 070 */ 071 public class CmsScaleRatioTransformer implements Transformer { 072 073 /** 074 * @see org.apache.commons.collections.Transformer#transform(java.lang.Object) 075 */ 076 @Override 077 public Object transform(Object input) { 078 079 return createRatioVariation(String.valueOf(input)); 080 } 081 } 082 083 /** 084 * Provides a Map to access width scaled versions of the current image.<p> 085 */ 086 public class CmsScaleWidthTransformer implements Transformer { 087 088 /** 089 * @see org.apache.commons.collections.Transformer#transform(java.lang.Object) 090 */ 091 @Override 092 public Object transform(Object input) { 093 094 return createWidthVariation(String.valueOf(input)); 095 } 096 } 097 098 /** The minimum dimension (width and height) a generated image must have. */ 099 public static int MIN_DIMENSION = 4; 100 101 /** The log object for this class. */ 102 static final Log LOG = CmsLog.getLog(CmsJspImageBean.class); 103 104 /** Size variations for source sets. */ 105 static final double[] m_sizeVariants = {1.000, 0.7500, 0.5000, 0.3750, 0.2500, 0.1250}; 106 107 /** The wrapped VFS resource for this image. */ 108 CmsJspResourceWrapper m_resource = null; 109 110 /** Lazy initialized map of ratio scaled versions of this image. */ 111 Map<String, CmsJspImageBean> m_scaleRatio = null; 112 113 /** Lazy initialized map of width scaled versions of this image. */ 114 Map<String, CmsJspImageBean> m_scaleWidth = null; 115 116 /** Map used for creating a image source set. */ 117 TreeMap<Integer, CmsJspImageBean> m_srcSet = null; 118 119 /** The CmsImageScaler that describes the basic adjustments (usually cropping) that have been set on the original image. */ 120 private CmsImageScaler m_baseScaler; 121 122 /** The current OpenCms user context. */ 123 private CmsObject m_cms = null; 124 125 /** The CmsImageScaler that is used to create a scaled version of this image. */ 126 private CmsImageScaler m_currentScaler; 127 128 /** 129 * Map used to store hi-DPI variants of the image. 130 * <ul> 131 * <li>key: the variant multiplier, e.g. "2x" (the common retina multiplier)</li> 132 * <li>value: a CmsJspImageBean representing the hi-DPI variant</li> 133 * </ul> 134 */ 135 private Map<String, CmsJspImageBean> m_hiDpiImages = null; 136 137 /** The CmsImageScaler that describes the original pixel proportions of this image. */ 138 private CmsImageScaler m_originalScaler; 139 140 /** The image quality (for JPEG calculation). */ 141 private int m_quality = 0; 142 143 /** The image VFS path. */ 144 private String m_vfsUri; 145 146 /** The ratio of the image, width to height, for example '4-3' or '16-9'. */ 147 private String m_ratio; 148 149 /** The height percentage of the image relative to the image width. */ 150 private String m_ratioHeightPercentage; 151 152 /** 153 * Initializes a new image bean based on a VFS resource and optional scaler parameters.<p> 154 * 155 * @param cms the current OpenCms user context 156 * @param imageRes the VFS resource to read the image from 157 * @param scaleParams optional scaler parameters to apply to the VFS resource 158 */ 159 public CmsJspImageBean(CmsObject cms, CmsResource imageRes, String scaleParams) { 160 161 init(cms, imageRes, scaleParams); 162 } 163 164 /** 165 * Initializes a new image bean based on a string pointing to a VFS resource that may contain appended scaling parameters.<p> 166 * 167 * @param cms the current OpenCms user context 168 * @param imageUri the URI to read the image from in the OpenCms VFS, may also contain appended scaling parameters 169 * 170 * @throws CmsException in case of problems reading the image from the VFS 171 */ 172 public CmsJspImageBean(CmsObject cms, String imageUri) 173 throws CmsException { 174 175 setCmsObject(cms); 176 // split the given image URI to see if there are scaling parameters attached 177 CmsUriSplitter splitSrc = new CmsUriSplitter(imageUri); 178 String scaleParam = null; 179 if (splitSrc.getQuery() != null) { 180 // check if the original URI already has parameters, this is true if original has been cropped 181 String[] scaleStr = CmsRequestUtil.createParameterMap(splitSrc.getQuery()).get(CmsImageScaler.PARAM_SCALE); 182 if (scaleStr != null) { 183 scaleParam = scaleStr[0]; 184 } 185 } 186 187 init(cms, cms.readResource(splitSrc.getPrefix()), scaleParam); 188 } 189 190 /** 191 * Initializes a new image bean based on a VFS input string and applies additional re-scaling.<p> 192 * 193 * The input string is is used to read the images from the VFS. 194 * It can contain scaling parameters. 195 * The additional re-scaling is then applied to the image that has been read.<p> 196 * 197 * @param cms the current uses OpenCms context 198 * @param imageUri the URI to read the image from in the OpenCms VFS, may also contain scaling parameters 199 * @param initScaler the additional re-scaling to apply to the image 200 * 201 * @throws CmsException in case of problems reading the image from the VFS 202 */ 203 public CmsJspImageBean(CmsObject cms, String imageUri, CmsImageScaler initScaler) 204 throws CmsException { 205 206 this(cms, imageUri); 207 208 CmsImageScaler targetScaler = createVariation( 209 getWidth(), 210 getHeight(), 211 getBaseScaler(), 212 initScaler.getWidth(), 213 initScaler.getHeight(), 214 getQuality()); 215 216 if ((targetScaler != null) && targetScaler.isValid()) { 217 setScaler(targetScaler); 218 } 219 } 220 221 /** 222 * Initializes a new empty image bean.<p> 223 * 224 * All values must be set with setters later.<p> 225 */ 226 protected CmsJspImageBean() { 227 228 // all values must be set with setters later 229 } 230 231 /** 232 * Create a variation scaler fir this image.<p> 233 * 234 * @param originalWidth the original image pixel width 235 * @param originalHeight the original image pixel height 236 * @param baseScaler the base scaler that may contain crop parameters 237 * @param targetWidth the target image pixel width 238 * @param targetHeight the target image pixel height 239 * @param quality the compression quality factor to use for image generation 240 * 241 * @return the created variation scaler for this image 242 */ 243 protected static CmsImageScaler createVariation( 244 int originalWidth, 245 int originalHeight, 246 CmsImageScaler baseScaler, 247 int targetWidth, 248 int targetHeight, 249 int quality) { 250 251 CmsImageScaler result = null; 252 253 if ((targetWidth <= 0) || (targetHeight <= 0)) { 254 // not all dimensions have been given, calculate the missing 255 256 double baseRatio; 257 if (baseScaler.isCropping()) { 258 // use the image crop with/height for aspect ratio calculation 259 baseRatio = (double)baseScaler.getCropWidth() / (double)baseScaler.getCropHeight(); 260 } else { 261 // use the image original pixel width/height for aspect ratio calculation 262 baseRatio = (double)originalWidth / (double)originalHeight; 263 } 264 265 // one dimension is missing, calculate it from the image 266 if (targetWidth <= 0) { 267 // width is not set, calculate it with the given height and the aspect ratio 268 targetWidth = (int)Math.round(targetHeight * baseRatio); 269 } else if (targetHeight <= 0) { 270 // height is not set, calculate it with the given width and the aspect ratio 271 targetHeight = (int)Math.round(targetWidth / baseRatio); 272 } 273 } 274 275 if ((targetWidth >= MIN_DIMENSION) 276 && (targetHeight >= MIN_DIMENSION) 277 && (originalWidth >= targetWidth) 278 && (originalHeight >= targetHeight)) { 279 280 // image original dimensions are large enough, generate result scaler 281 result = new CmsImageScaler(); 282 283 result.setWidth(targetWidth); 284 result.setHeight(targetHeight); 285 286 if ((baseScaler.getFocalPoint() != null) 287 && checkCropRegionContainsFocalPoint(baseScaler, baseScaler.getFocalPoint())) { 288 result.setType(8); 289 if (baseScaler.isCropping()) { 290 result.setCropArea( 291 baseScaler.getCropX(), 292 baseScaler.getCropY(), 293 baseScaler.getCropWidth(), 294 baseScaler.getCropHeight()); 295 } else { 296 result.setCropArea(0, 0, originalWidth, originalHeight); 297 } 298 } else { 299 300 result.setType(2); 301 if (baseScaler.isCropping()) { 302 303 double targetRatio = (double)baseScaler.getCropWidth() / (double)targetWidth; 304 int targetCropWidth = baseScaler.getCropWidth(); 305 int targetCropHeight = (int)Math.round(targetHeight * targetRatio); 306 307 if (targetCropHeight > baseScaler.getCropHeight()) { 308 targetRatio = (double)baseScaler.getCropHeight() / (double)targetHeight; 309 targetCropWidth = (int)Math.round(targetWidth * targetRatio); 310 targetCropHeight = baseScaler.getCropHeight(); 311 } 312 313 int targetX = baseScaler.getCropX(); 314 int targetY = baseScaler.getCropY(); 315 316 if (targetCropWidth != baseScaler.getCropWidth()) { 317 targetX = targetX + (int)Math.round((baseScaler.getCropWidth() - targetCropWidth) / 2.0); 318 } 319 if (targetCropHeight != baseScaler.getCropHeight()) { 320 targetY = targetY + (int)Math.round((baseScaler.getCropHeight() - targetCropHeight) / 2.0); 321 } 322 323 result.setCropArea(targetX, targetY, targetCropWidth, targetCropHeight); 324 } 325 } 326 } 327 328 if ((result != null) && (quality > 0)) { 329 // apply compression quality setting 330 result.setQuality(quality); 331 } 332 return result; 333 } 334 335 /** 336 * Helper method to check whether the focal point in the scaler is contained in the scaler's crop region.<p> 337 * 338 * If the scaler has no crop region, true is returned. 339 * 340 * @param scaler the scaler 341 * @param focalPoint the focal point to check 342 * @return true if the scaler's crop region contains the focal point 343 */ 344 private static boolean checkCropRegionContainsFocalPoint(CmsImageScaler scaler, CmsPoint focalPoint) { 345 346 if (!scaler.isCropping()) { 347 return true; 348 } 349 double x = focalPoint.getX(); 350 double y = focalPoint.getY(); 351 return (scaler.getCropX() <= x) 352 && (x < (scaler.getCropX() + scaler.getCropWidth())) 353 && (scaler.getCropY() <= y) 354 && (y < (scaler.getCropY() + scaler.getCropHeight())); 355 } 356 357 /** 358 * adds a CmsJspImageBean as hi-DPI variant to this image 359 * @param factor the variant multiplier, e.g. "2x" (the common retina multiplier) 360 * @param image the image to be used for this variant 361 */ 362 public void addHiDpiImage(String factor, CmsJspImageBean image) { 363 364 if (m_hiDpiImages == null) { 365 m_hiDpiImages = CmsCollectionsGenericWrapper.createLazyMap(new CmsScaleHiDpiTransformer()); 366 } 367 m_hiDpiImages.put(factor, image); 368 } 369 370 /** 371 * Adds a number of size variations to the source set.<p> 372 * 373 * In case the screen size is not really known, it may be a good idea to add 374 * some variations for large images to make sure there are some common options in case the basic 375 * image is very large.<p> 376 * 377 * @param minWidth the minimum image width to add size variations for 378 * @param maxWidth the maximum width size variation to create 379 */ 380 public void addSrcSetWidthVariants(int minWidth, int maxWidth) { 381 382 int imageWidth = getWidth(); 383 if (imageWidth > minWidth) { 384 // only add variants in case the image is larger then the given minimum 385 int srcSetMaxWidth = getSrcSetMaxWidth(); 386 for (double factor : m_sizeVariants) { 387 long width = Math.round(imageWidth * factor); 388 if (width > srcSetMaxWidth) { 389 if (width <= maxWidth) { 390 setSrcSets(createWidthVariation(String.valueOf(width))); 391 } 392 } else { 393 break; 394 } 395 } 396 } 397 } 398 399 /** 400 * Creates a hi-DPI scaled version of the current image.<p> 401 * 402 * @param hiDpiStr the hi-DPI variation to generate, for example "2.5x".<p> 403 * 404 * @return a hi-DPI scaled version of the current image 405 */ 406 public CmsJspImageBean createHiDpiVariation(String hiDpiStr) { 407 408 CmsJspImageBean result = null; 409 if (hiDpiStr.matches("^[0-9]+(.[0-9]+)?x$")) { 410 411 double multiplier = Double.valueOf(hiDpiStr.substring(0, hiDpiStr.length() - 1)).doubleValue(); 412 413 int targetWidth = (int)Math.round(getScaler().getWidth() * multiplier); 414 int targetHeight = (int)Math.round(getScaler().getHeight() * multiplier); 415 416 CmsImageScaler targetScaler = createVariation( 417 getWidth(), 418 getHeight(), 419 getBaseScaler(), 420 targetWidth, 421 targetHeight, 422 getQuality()); 423 424 if (targetScaler != null) { 425 result = createVariation(targetScaler); 426 } 427 428 } else { 429 if (LOG.isWarnEnabled()) { 430 LOG.warn(String.format("Illegal multiplier format: '%s' not usable for image scaling", hiDpiStr)); 431 } 432 } 433 return result; 434 } 435 436 /** 437 * Creates a ratio scaled version of the current image.<p> 438 * 439 * @param ratioStr the rato variation to generate, for example "4-3" or "1-1".<p> 440 * 441 * @return a ratio scaled version of the current image 442 */ 443 public CmsJspImageBean createRatioVariation(String ratioStr) { 444 445 CmsJspImageBean result = null; 446 447 try { 448 int i = ratioStr.indexOf('-'); 449 if (i > 0) { 450 ratioStr = ratioStr.replace(',', '.'); 451 452 double ratioW = Double.valueOf(ratioStr.substring(0, i)).doubleValue(); 453 double ratioH = Double.valueOf(ratioStr.substring(i + 1)).doubleValue(); 454 455 int targetWidth, targetHeight; 456 457 double ratioFactorW = getScaler().getWidth() / ratioW; 458 targetWidth = getScaler().getWidth(); 459 targetHeight = (int)Math.round(ratioH * ratioFactorW); 460 461 if (targetHeight > getScaler().getHeight()) { 462 double ratioFactorH = getScaler().getHeight() / ratioH; 463 targetWidth = (int)Math.round(ratioW * ratioFactorH); 464 targetHeight = getScaler().getHeight(); 465 } 466 467 CmsImageScaler targetScaler = createVariation( 468 getWidth(), 469 getHeight(), 470 getBaseScaler(), 471 targetWidth, 472 targetHeight, 473 getQuality()); 474 475 if (targetScaler != null) { 476 result = createVariation(targetScaler); 477 result.m_ratio = ratioStr; 478 result.m_ratioHeightPercentage = calcRatioHeightPercentage(ratioW, ratioH); 479 } 480 } 481 } catch (NumberFormatException e) { 482 if (LOG.isWarnEnabled()) { 483 LOG.warn(String.format("Illegal ratio format: '%s' not usable for image scaling", ratioStr)); 484 } 485 } 486 487 return result; 488 } 489 490 /** 491 * Creates a width scaled version of the current image.<p> 492 * 493 * @param widthStr the with variation to generate, for example "1078" or "800".<p> 494 * 495 * @return a width scaled version of the current image 496 */ 497 public CmsJspImageBean createWidthVariation(String widthStr) { 498 499 CmsJspImageBean result = null; 500 501 try { 502 503 double baseRatio; 504 if ((getOriginalScaler().getFocalPoint() != null) 505 && checkCropRegionContainsFocalPoint(getScaler(), getOriginalScaler().getFocalPoint())) { 506 // We use scaling mode 8 if there is a focal point, and in this case, 507 // the correct aspect ratio is width x height, not cropWidth x cropHeight 508 // even if cropping is set 509 baseRatio = (double)getScaler().getWidth() / (double)getScaler().getHeight(); 510 } else if (getScaler().isCropping()) { 511 // use the image crop with/height for aspect ratio calculation 512 baseRatio = (double)getScaler().getCropWidth() / (double)getScaler().getCropHeight(); 513 } else { 514 // use the image original pixel width/height for aspect ratio calculation 515 baseRatio = (double)getScaler().getWidth() / (double)getScaler().getHeight(); 516 } 517 518 // height is not set, calculate it with the given width and the aspect ratio 519 int targetWidth = Integer.valueOf(widthStr).intValue(); 520 int targetHeight = (int)Math.round(targetWidth / baseRatio); 521 522 CmsImageScaler targetScaler = createVariation( 523 getWidth(), 524 getHeight(), 525 getBaseScaler(), 526 targetWidth, 527 targetHeight, 528 getQuality()); 529 530 if (targetScaler != null) { 531 result = createVariation(targetScaler); 532 } 533 534 } catch (NumberFormatException e) { 535 if (LOG.isWarnEnabled()) { 536 LOG.warn(String.format("Illegal width format: '%s' not usable for image scaling", widthStr)); 537 } 538 } 539 540 return result; 541 } 542 543 /** 544 * Returns the original pixel height of the image.<p> 545 * 546 * @return the original pixel height of the image 547 */ 548 public int getHeight() { 549 550 return m_originalScaler.getHeight(); 551 } 552 553 /** 554 * Returns a lazy initialized Map that provides access to ratio scaled instances of this image bean.<p> 555 * 556 * @return a lazy initialized Map that provides access to ratio scaled instances of this image bean 557 * 558 * @deprecated use {@link #getScaleHiDpi()} instead 559 */ 560 @Deprecated 561 public Map<String, CmsJspImageBean> getHiDpiImages() { 562 563 return getScaleHiDpi(); 564 } 565 566 /** 567 * Returns the basic source parameters for this image.<p> 568 * 569 * In case the image was cropped or otherwise manipulated, 570 * the values are created for the manipulated version.<p> 571 * 572 * The return form is "src='(srcUrl)' height='(h)' width='(w)'".<p> 573 * 574 * @return the basic source parameters for this image 575 */ 576 public String getImgSrc() { 577 578 StringBuffer result = new StringBuffer(128); 579 580 // append the image source 581 result.append("src=\""); 582 result.append(getSrcUrl()); 583 result.append("\""); 584 // append image width and height 585 result.append(" width=\""); 586 result.append(m_currentScaler.getWidth()); 587 result.append("\""); 588 result.append(" height=\""); 589 result.append(m_currentScaler.getHeight()); 590 result.append("\""); 591 592 return result.toString(); 593 } 594 595 /** 596 * Returns the compression quality factor used for image generation.<p> 597 * 598 * @return the compression quality factor used for image generation 599 */ 600 public int getQuality() { 601 602 return m_quality; 603 } 604 605 /** 606 * Returns the image ratio.<p> 607 * 608 * The ratio is in the form 'width-height', for example '4-3' or '16-9'. 609 * In case no ratio was set, the pixel dimensions of the image are returned.<p> 610 * 611 * @return the image ratio 612 */ 613 public String getRatio() { 614 615 if (m_ratio == null) { 616 m_ratio = "" + getScaler().getWidth() + "-" + getScaler().getHeight(); 617 } 618 return m_ratio; 619 } 620 621 /** 622 * Returns the image height percentage relative to the image width as a String.<p> 623 * 624 * In case a ratio has been used to scale the image, the height percentage is 625 * calculated based on the ratio, not on the actual image pixel size. 626 * This is done to avoid rounding differences.<p> 627 * 628 * @return the image height percentage relative to the image width 629 */ 630 public String getRatioHeightPercentage() { 631 632 if (m_ratioHeightPercentage == null) { 633 634 m_ratioHeightPercentage = calcRatioHeightPercentage(getScaler().getWidth(), getScaler().getHeight()); 635 } 636 return m_ratioHeightPercentage; 637 } 638 639 /** 640 * Returns the JSP access wrapped VFS resource for this image.<p> 641 * 642 * @return the JSP access wrapped VFS resource for this image 643 */ 644 public CmsJspResourceWrapper getResource() { 645 646 return m_resource; 647 } 648 649 /** 650 * Returns a lazy initialized Map that provides access to hi-DPI scaled instances of this image bean.<p> 651 * 652 * <ul> 653 * <li>key: the variant multiplier, e.g. "2x" (the common retina multiplier)</li> 654 * <li>value: a CmsJspImageBean representing the hi-DPI variant</li> 655 * </ul> 656 * 657 * @return a lazy initialized Map that provides access to hi-DPI scaled instances of this image bean 658 */ 659 public Map<String, CmsJspImageBean> getScaleHiDpi() { 660 661 if (m_hiDpiImages == null) { 662 m_hiDpiImages = CmsCollectionsGenericWrapper.createLazyMap(new CmsScaleHiDpiTransformer()); 663 } 664 return m_hiDpiImages; 665 } 666 667 /** 668 * Returns the image scaler that is used for the scaled version of this image bean.<p> 669 * 670 * @return the image scaler that is used for the scaled version of this image bean 671 */ 672 public CmsImageScaler getScaler() { 673 674 return m_currentScaler; 675 } 676 677 /** 678 * Returns a lazy initialized Map that provides access to ratio scaled instances of this image bean.<p> 679 * 680 * @return a lazy initialized Map that provides access to ratio scaled instances of this image bean 681 */ 682 public Map<String, CmsJspImageBean> getScaleRatio() { 683 684 if (m_scaleRatio == null) { 685 m_scaleRatio = CmsCollectionsGenericWrapper.createLazyMap(new CmsScaleRatioTransformer()); 686 } 687 return m_scaleRatio; 688 } 689 690 /** 691 * Returns a lazy initialized Map that provides access to width scaled instances of this image bean.<p> 692 * 693 * @return a lazy initialized Map that provides access to width scaled instances of this image bean 694 */ 695 public Map<String, CmsJspImageBean> getScaleWidth() { 696 697 if (m_scaleWidth == null) { 698 m_scaleWidth = CmsCollectionsGenericWrapper.createLazyMap(new CmsScaleWidthTransformer()); 699 } 700 return m_scaleWidth; 701 } 702 703 /** 704 * Generates a srcset attribute parameter list from all images added to this image bean.<p> 705 * 706 * @return a srcset attribute parameter list from all images added to this image bean 707 */ 708 public String getSrcSet() { 709 710 StringBuffer result = new StringBuffer(128); 711 if (m_srcSet != null) { 712 int items = m_srcSet.size(); 713 for (Map.Entry<Integer, CmsJspImageBean> entry : m_srcSet.entrySet()) { 714 CmsJspImageBean imageBean = entry.getValue(); 715 // append the image source 716 result.append(imageBean.getSrcUrl()); 717 result.append(" "); 718 // append width 719 result.append(imageBean.getScaler().getWidth()); 720 result.append("w"); 721 if (--items > 0) { 722 result.append(", "); 723 } 724 } 725 } 726 return result.toString(); 727 } 728 729 /** 730 * Generates a srcset attribute parameter for this image bean.<p> 731 * 732 * @return a srcset attribute parameter for this image bean 733 */ 734 public String getSrcSetEntry() { 735 736 StringBuffer result = new StringBuffer(128); 737 if (m_currentScaler.isValid()) { 738 // append the image source 739 result.append(getSrcUrl()); 740 result.append(" "); 741 // append width 742 result.append(m_currentScaler.getWidth()); 743 result.append("w"); 744 } 745 return result.toString(); 746 } 747 748 /** 749 * Returns the source set map.<p> 750 * 751 * In case no source set entries have been added before, the map is not initialized and <code>null</code> is returned. 752 * 753 * @return the source set map 754 */ 755 public Map<Integer, CmsJspImageBean> getSrcSetMap() { 756 757 return m_srcSet; 758 } 759 760 /** 761 * Returns the largest image from the generated source set.<p> 762 * 763 * In case the source set has not been initialized, 764 * it returns the instance itself. 765 * 766 * @return the largest image from the generated source set 767 */ 768 public CmsJspImageBean getSrcSetMaxImage() { 769 770 CmsJspImageBean result = this; 771 if (m_srcSet != null) { 772 result = m_srcSet.lastEntry().getValue(); 773 } 774 return result; 775 } 776 777 /** 778 * Returns the largest width value form the source set.<p> 779 * 780 * In case no source set entries have been added before, the map is not initialized and <code>0</code> is returned. 781 * 782 * @return the largest width value form the source set 783 */ 784 public int getSrcSetMaxWidth() { 785 786 int result = 0; 787 if ((m_srcSet != null) && (m_srcSet.size() > 0)) { 788 789 result = m_srcSet.lastKey().intValue(); 790 } 791 return result; 792 } 793 794 /** 795 * Getter for {@link #setSrcSets(CmsJspImageBean)} which returns this image bean.<p> 796 * 797 * Exists to make sure {@link #setSrcSets(CmsJspImageBean)} is available as property on a JSP.<p> 798 * 799 * @return this image bean 800 * 801 * @see CmsJspImageBean#getSrcSet() 802 * @see CmsJspImageBean#getSrcSetMap() 803 */ 804 public CmsJspImageBean getSrcSets() { 805 806 return this; 807 } 808 809 /** 810 * Returns the image URL that may be used in img or picture tags.<p> 811 * 812 * @return the image URL 813 */ 814 public String getSrcUrl() { 815 816 String imageSrc = getCmsObject().getSitePath(getResource()); 817 if ((getScaler() != null) && getScaler().isValid()) { 818 // now append the scaler parameters if required 819 imageSrc += getScaler().toRequestParam(); 820 } 821 return OpenCms.getLinkManager().substituteLink(getCmsObject(), imageSrc); 822 } 823 824 /** 825 * Returns the URI of the image in the OpenCms VFS.<p> 826 * 827 * @return the URI of the image in the OpenCms VFS 828 */ 829 public String getVfsUri() { 830 831 return m_vfsUri; 832 } 833 834 /** 835 * Returns the original (unscaled) width of the image.<p> 836 * 837 * @return the original (unscaled) width of the image 838 */ 839 public int getWidth() { 840 841 return m_originalScaler.getWidth(); 842 } 843 844 /** 845 * Returns <code>true</code> if this image bean has been correctly initialized with an image VFS resource.<p> 846 * 847 * @return <code>true</code> if this image bean has been correctly initialized with an image VFS resource 848 */ 849 public boolean isImage() { 850 851 return getOriginalScaler().isValid(); 852 } 853 854 /** 855 * Returns <code>true</code> if the image has been scaled or otherwise processed.<p> 856 * 857 * @return <code>true</code> if the image has been scaled or otherwise processed 858 */ 859 public boolean isScaled() { 860 861 return !m_currentScaler.isOriginalScaler(); 862 } 863 864 /** 865 * Sets the compression quality factor to use for image generation.<p> 866 * 867 * @param quality the compression quality factor to use for image generation 868 */ 869 public void setQuality(int quality) { 870 871 m_quality = quality; 872 getScaler().setQuality(m_quality); 873 } 874 875 /** 876 * Adjusts the quality settings for all image beans in the srcSet depending on the pixel count.<p> 877 * 878 * The idea is to make sure large pixel images use a higher JPEG compression in order to reduce the size.<p> 879 * 880 * The following quality settings are used depending on the image size: 881 * <ul> 882 * <li>larger then 1200 * 800: quality 75 883 * <li>larger then 1024 * 768: quality 80 884 * <li>otherwise: quality 85 885 * </ul> 886 * 887 */ 888 public void setSrcSetQuality() { 889 890 if (m_srcSet != null) { 891 892 for (Map.Entry<Integer, CmsJspImageBean> entry : m_srcSet.entrySet()) { 893 CmsJspImageBean imageBean = entry.getValue(); 894 int quality; 895 long pixel = imageBean.getScaler().getWidth() * imageBean.getScaler().getWidth(); 896 if (pixel > 960000) { 897 // image size 1200 * 800 898 quality = 75; 899 } else if (pixel > 786432) { 900 // image size 1024 * 768 901 quality = 80; 902 } else { 903 quality = 85; 904 } 905 imageBean.setQuality(quality); 906 } 907 } 908 } 909 910 /** 911 * Adds another image bean instance to the source set map of this bean.<p> 912 * 913 * @param imageBean the image bean to add 914 */ 915 public void setSrcSets(CmsJspImageBean imageBean) { 916 917 if (m_srcSet == null) { 918 m_srcSet = new TreeMap<Integer, CmsJspImageBean>(); 919 } 920 if ((imageBean != null) && imageBean.getScaler().isValid()) { 921 m_srcSet.put(Integer.valueOf(imageBean.getScaler().getWidth()), imageBean); 922 } 923 } 924 925 /** 926 * Sets the URI of the image in the OpenCms VFS.<p> 927 * 928 * @param vfsUri the URI of the image in the OpenCms VFS to set 929 */ 930 public void setVfsUri(String vfsUri) { 931 932 m_vfsUri = vfsUri; 933 } 934 935 /** 936 * Returns the image source URL as String representation.<p> 937 * 938 * @return the image source URL 939 * 940 * @see #getSrcUrl() 941 */ 942 @Override 943 public String toString() { 944 945 return getSrcUrl(); 946 } 947 948 /** 949 * Returns the ratio height percentage of an image based on width and height.<p> 950 * 951 * @param width width to calculate percentage from 952 * @param height height to calculate percentage from 953 * 954 * @return the ratio height percentage of an image based on width and height 955 */ 956 protected String calcRatioHeightPercentage(double width, double height) { 957 958 double p = Math.round((height / width) * 10000000.0) / 100000.0; 959 return String.valueOf(p) + "%"; 960 } 961 962 /** 963 * Returns a variation of the current image scaled with the given scaler.<p> 964 * 965 * It is always the original image which is used as a base, never a scaled version. 966 * So for example if the image has been cropped by the user, the cropping are is ignored.<p> 967 * 968 * @param targetScaler contains the information about how to scale the image 969 * 970 * @return a variation of the current image scaled with the given scaler 971 */ 972 protected CmsJspImageBean createVariation(CmsImageScaler targetScaler) { 973 974 CmsJspImageBean result = new CmsJspImageBean(); 975 976 result.setCmsObject(getCmsObject()); 977 result.setResource(getCmsObject(), getResource()); 978 result.setOriginalScaler(getOriginalScaler()); 979 result.setBaseScaler(getBaseScaler()); 980 result.setVfsUri(getVfsUri()); 981 result.setScaler(targetScaler); 982 result.setQuality(getQuality()); 983 984 return result; 985 } 986 987 /** 988 * Sets the scaler that describes the basic adjustments (usually cropping) that have been set on the original image.<p> 989 * 990 * @return the scaler that describes the basic adjustments (usually cropping) that have been set on the original image 991 */ 992 protected CmsImageScaler getBaseScaler() { 993 994 return m_baseScaler; 995 } 996 997 /** 998 * Returns the current OpenCms user context.<p> 999 * 1000 * @return the current OpenCms user context 1001 */ 1002 protected CmsObject getCmsObject() { 1003 1004 return m_cms; 1005 } 1006 1007 /** 1008 * Returns the image scaler that describes the original proportions of this image.<p> 1009 * 1010 * @return the image scaler that describes the original proportions of this image 1011 */ 1012 protected CmsImageScaler getOriginalScaler() { 1013 1014 return m_originalScaler; 1015 } 1016 1017 /** 1018 * Returns this instance bean, required for the transformers.<p> 1019 * 1020 * @return this instance bean 1021 */ 1022 protected CmsJspImageBean getSelf() { 1023 1024 return this; 1025 } 1026 1027 /** 1028 * Initializes this new image bean based on a VFS resource and optional scaler parameters.<p> 1029 * 1030 * @param cms the current OpenCms user context 1031 * @param imageRes the VFS resource to read the image from 1032 * @param scaleParams optional scaler parameters to apply to the VFS resource 1033 */ 1034 protected void init(CmsObject cms, CmsResource imageRes, String scaleParams) { 1035 1036 setCmsObject(cms); 1037 1038 // set VFS URI without scaling parameters 1039 setResource(cms, imageRes); 1040 setVfsUri(cms.getRequestContext().getSitePath(imageRes)); 1041 1042 // the originalScaler reads the image dimensions from the VFS properties 1043 CmsImageScaler originalScaler = new CmsImageScaler(cms, getResource()); 1044 // set original scaler 1045 setOriginalScaler(originalScaler); 1046 1047 // set base scaler 1048 CmsImageScaler baseScaler = originalScaler; 1049 if (scaleParams != null) { 1050 // scale parameters have been set 1051 baseScaler = new CmsImageScaler(scaleParams); 1052 baseScaler.setFocalPoint(originalScaler.getFocalPoint()); 1053 } 1054 1055 setBaseScaler(baseScaler); 1056 1057 // set the current scaler to the base scaler 1058 setScaler(baseScaler); 1059 } 1060 1061 /** 1062 * Returns the scaler that describes the basic adjustments (usually cropping) that have been set on the original image.<p> 1063 * 1064 * @param baseScaler the scaler that describes the basic adjustments (usually cropping) that have been set on the original image 1065 */ 1066 protected void setBaseScaler(CmsImageScaler baseScaler) { 1067 1068 m_baseScaler = baseScaler; 1069 } 1070 1071 /** 1072 * Sets the current OpenCms user context.<p> 1073 * 1074 * @param cms the current OpenCms user context to set 1075 */ 1076 protected void setCmsObject(CmsObject cms) { 1077 1078 m_cms = cms; 1079 } 1080 1081 /** 1082 * Sets the scaler that describes the original proportions of this image.<p> 1083 * 1084 * @param originalScaler the scaler that describes the original proportions of this image 1085 */ 1086 protected void setOriginalScaler(CmsImageScaler originalScaler) { 1087 1088 m_originalScaler = originalScaler; 1089 } 1090 1091 /** 1092 * Sets the CmsResource for this image.<p> 1093 * 1094 * @param cms the current OpenCms user context, required for wrapping the resource 1095 * @param resource the VFS resource for this image 1096 */ 1097 protected void setResource(CmsObject cms, CmsResource resource) { 1098 1099 m_resource = CmsJspResourceWrapper.wrap(cms, resource); 1100 } 1101 1102 /** 1103 * Sets the image scaler that was used to create this image.<p> 1104 * 1105 * @param scaler the image scaler that was used to create this image. 1106 */ 1107 protected void setScaler(CmsImageScaler scaler) { 1108 1109 m_currentScaler = scaler; 1110 } 1111}