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.loader; 029 030import com.alkacon.simapi.RenderSettings; 031import com.alkacon.simapi.Simapi; 032import com.alkacon.simapi.CmykJpegReader.ByteArrayImageInputStream; 033import com.alkacon.simapi.filter.GrayscaleFilter; 034import com.alkacon.simapi.filter.ShadowFilter; 035 036import org.opencms.ade.galleries.CmsPreviewService; 037import org.opencms.ade.galleries.shared.CmsPoint; 038import org.opencms.file.CmsFile; 039import org.opencms.file.CmsObject; 040import org.opencms.file.CmsProperty; 041import org.opencms.file.CmsPropertyDefinition; 042import org.opencms.file.CmsResource; 043import org.opencms.main.CmsLog; 044import org.opencms.main.OpenCms; 045import org.opencms.util.CmsStringUtil; 046 047import java.awt.Color; 048import java.awt.Dimension; 049import java.awt.Rectangle; 050import java.awt.image.BufferedImage; 051import java.io.IOException; 052import java.util.ArrayList; 053import java.util.Arrays; 054import java.util.Iterator; 055import java.util.List; 056 057import javax.imageio.ImageIO; 058import javax.imageio.ImageReader; 059import javax.servlet.http.HttpServletRequest; 060 061import org.apache.commons.logging.Log; 062 063/** 064 * Creates scaled images, acting as it's own parameter container.<p> 065 * 066 * @since 6.2.0 067 */ 068public class CmsImageScaler { 069 070 /** The name of the transparent color (for the background image). */ 071 public static final String COLOR_TRANSPARENT = "transparent"; 072 073 /** The name of the grayscale image filter. */ 074 public static final String FILTER_GRAYSCALE = "grayscale"; 075 076 /** The name of the shadow image filter. */ 077 public static final String FILTER_SHADOW = "shadow"; 078 079 /** The supported image filter names. */ 080 public static final List<String> FILTERS = Arrays.asList(new String[] {FILTER_GRAYSCALE, FILTER_SHADOW}); 081 082 /** The (optional) parameter used for sending the scale information of an image in the http request. */ 083 public static final String PARAM_SCALE = "__scale"; 084 085 /** The default maximum image size (width * height) to apply image blurring when down scaling (setting this to high may case "out of memory" errors). */ 086 public static final int SCALE_DEFAULT_MAX_BLUR_SIZE = 2500 * 2500; 087 088 /** The default maximum image size (width or height) to allow when up or down scaling an image using request parameters. */ 089 public static final int SCALE_DEFAULT_MAX_SIZE = 2500; 090 091 /** The scaler parameter to indicate the requested image background color (if required). */ 092 public static final String SCALE_PARAM_COLOR = "c"; 093 094 /** The scaler parameter to indicate crop height. */ 095 public static final String SCALE_PARAM_CROP_HEIGHT = "ch"; 096 097 /** The scaler parameter to indicate crop width. */ 098 public static final String SCALE_PARAM_CROP_WIDTH = "cw"; 099 100 /** The scaler parameter to indicate crop X coordinate. */ 101 public static final String SCALE_PARAM_CROP_X = "cx"; 102 103 /** The scaler parameter to indicate crop Y coordinate. */ 104 public static final String SCALE_PARAM_CROP_Y = "cy"; 105 106 /** The scaler parameter to indicate the requested image filter. */ 107 public static final String SCALE_PARAM_FILTER = "f"; 108 109 /** The scaler parameter to indicate the requested image height. */ 110 public static final String SCALE_PARAM_HEIGHT = "h"; 111 112 /** The scaler parameter to indicate the requested image position (if required). */ 113 public static final String SCALE_PARAM_POS = "p"; 114 115 /** The scaler parameter to indicate to requested image save quality in percent (if applicable, for example used with JPEG images). */ 116 public static final String SCALE_PARAM_QUALITY = "q"; 117 118 /** The scaler parameter to indicate to requested <code>{@link java.awt.RenderingHints}</code> settings. */ 119 public static final String SCALE_PARAM_RENDERMODE = "r"; 120 121 /** The scaler parameter to indicate the requested scale type. */ 122 public static final String SCALE_PARAM_TYPE = "t"; 123 124 /** The scaler parameter to indicate the requested image width. */ 125 public static final String SCALE_PARAM_WIDTH = "w"; 126 127 /** The log object for this class. */ 128 protected static final Log LOG = CmsLog.getLog(CmsImageScaler.class); 129 130 /** The target background color (optional). */ 131 private Color m_color; 132 133 /** The height for image cropping. */ 134 private int m_cropHeight; 135 136 /** The width for image cropping. */ 137 private int m_cropWidth; 138 139 /** The x coordinate for image cropping. */ 140 private int m_cropX; 141 142 /** The y coordinate for image cropping. */ 143 private int m_cropY; 144 145 /** The list of image filter names (Strings) to apply. */ 146 private List<String> m_filters; 147 148 /** The focal point. */ 149 private CmsPoint m_focalPoint; 150 151 /** The target height (required). */ 152 private int m_height; 153 154 /** Indicates if this image scaler was only used to read the image properties. */ 155 private boolean m_isOriginalScaler; 156 157 /** The maximum image size (width * height) to apply image blurring when down scaling (setting this to high may case "out of memory" errors). */ 158 private int m_maxBlurSize; 159 160 /** The maximum target height (for scale type '5'). */ 161 private int m_maxHeight; 162 163 /** The maximum target width (for scale type '5'). */ 164 private int m_maxWidth; 165 166 /** The target position (optional). */ 167 private int m_position; 168 169 /** The target image save quality (if applicable, for example used with JPEG images) (optional). */ 170 private int m_quality; 171 172 /** The image processing renderings hints constant mode indicator (optional). */ 173 private int m_renderMode; 174 175 /** The final (parsed and corrected) scale parameters. */ 176 private String m_scaleParameters; 177 178 /** The target scale type (optional). */ 179 private int m_type; 180 181 /** The target width (required). */ 182 private int m_width; 183 184 /** 185 * Creates a new, empty image scaler object.<p> 186 */ 187 public CmsImageScaler() { 188 189 init(); 190 } 191 192 /** 193 * Creates a new image scaler initialized with the height and width of 194 * the given image contained in the byte array.<p> 195 * 196 * <b>Please note:</b>The image itself is not stored in the scaler, only the width and 197 * height dimensions of the image. To actually scale an image, you need to use 198 * <code>{@link #scaleImage(CmsFile)}</code>. This constructor is commonly used only 199 * to extract the image dimensions, for example when creating a String value for 200 * the <code>{@link CmsPropertyDefinition#PROPERTY_IMAGE_SIZE}</code> property.<p> 201 * 202 * In case the byte array can not be decoded to an image, or in case of other errors, 203 * <code>{@link #isValid()}</code> will return <code>false</code>.<p> 204 * 205 * @param content the image to calculate the dimensions for 206 * @param rootPath the root path of the resource (for error logging) 207 */ 208 public CmsImageScaler(byte[] content, String rootPath) { 209 210 init(); 211 try { 212 Dimension dim = getImageDimensions(rootPath, content); 213 if (dim != null) { 214 m_width = dim.width; 215 m_height = dim.height; 216 } else { 217 // read the scaled image 218 BufferedImage image = Simapi.read(content); 219 m_height = image.getHeight(); 220 m_width = image.getWidth(); 221 } 222 } catch (Exception e) { 223 // nothing we can do about this, keep the original properties 224 if (LOG.isDebugEnabled()) { 225 LOG.debug(Messages.get().getBundle().key(Messages.ERR_UNABLE_TO_EXTRACT_IMAGE_SIZE_1, rootPath), e); 226 } 227 // set height / width to default of -1 228 init(); 229 } 230 } 231 232 /** 233 * Creates a new image scaler based on the given base scaler and the given width and height.<p> 234 * 235 * @param base the base scaler to initialize the values with 236 * @param width the width to set for this scaler 237 * @param height the height to set for this scaler 238 */ 239 public CmsImageScaler(CmsImageScaler base, int width, int height) { 240 241 initValuesFrom(base); 242 setWidth(width); 243 setHeight(height); 244 } 245 246 /** 247 * Creates a new image scaler by reading the property <code>{@link CmsPropertyDefinition#PROPERTY_IMAGE_SIZE}</code> 248 * from the given resource.<p> 249 * 250 * In case of any errors reading or parsing the property, 251 * <code>{@link #isValid()}</code> will return <code>false</code>.<p> 252 * 253 * @param cms the OpenCms user context to use when reading the property 254 * @param res the resource to read the property from 255 */ 256 public CmsImageScaler(CmsObject cms, CmsResource res) { 257 258 init(); 259 m_isOriginalScaler = true; 260 String sizeValue = null; 261 if ((cms != null) && (res != null)) { 262 try { 263 CmsProperty sizeProp = cms.readPropertyObject(res, CmsPropertyDefinition.PROPERTY_IMAGE_SIZE, false); 264 if (!sizeProp.isNullProperty()) { 265 // parse property value using standard procedures 266 sizeValue = sizeProp.getValue(); 267 } 268 } catch (Exception e) { 269 LOG.debug(e.getMessage(), e); 270 } 271 try { 272 m_focalPoint = CmsPreviewService.readFocalPoint(cms, res); 273 } catch (Exception e) { 274 LOG.debug(e.getLocalizedMessage(), e); 275 } 276 } 277 if (CmsStringUtil.isNotEmpty(sizeValue)) { 278 parseParameters(sizeValue); 279 } 280 } 281 282 /** 283 * Creates a new image scaler based on the given HTTP request.<p> 284 * 285 * The maximum scale size is checked in order to prevent DOS attacks. 286 * Without this, it would be possible to request arbitrary huge images with a simple GET request, 287 * which would result in Out-Of-Memory errors if the image is just requested large enough.<p> 288 * 289 * The maximum blur size is checked since this operation is know to also cause memory issues 290 * with large images. If the original image is larger then this, no blur is applied before 291 * scaling down, which will result in a less optimal but still usable scale result.<p> 292 * 293 * @param request the HTTP request to read the parameters from 294 * @param maxScaleSize the maximum scale size (width or height) for the image 295 * @param maxBlurSize the maximum size of the image (width * height) to apply blur 296 */ 297 public CmsImageScaler(HttpServletRequest request, int maxScaleSize, int maxBlurSize) { 298 299 init(); 300 m_maxBlurSize = maxBlurSize; 301 String parameters = request.getParameter(CmsImageScaler.PARAM_SCALE); 302 if (CmsStringUtil.isNotEmpty(parameters)) { 303 parseParameters(parameters); 304 if (isValid()) { 305 // valid parameters, check if scale size is not too big 306 if ((getWidth() > maxScaleSize) || (getHeight() > maxScaleSize)) { 307 // scale size is too big, reset scaler 308 init(); 309 } 310 } 311 } 312 } 313 314 /** 315 * Creates an image scaler with manually set width and height. 316 * 317 * @param width the width 318 * @param height the height 319 */ 320 public CmsImageScaler(int width, int height) { 321 322 init(); 323 m_width = width; 324 m_height = height; 325 } 326 327 /** 328 * Creates a new image scaler based on the given parameter String.<p> 329 * 330 * @param parameters the scale parameters to use 331 */ 332 public CmsImageScaler(String parameters) { 333 334 init(); 335 if (CmsStringUtil.isNotEmpty(parameters)) { 336 parseParameters(parameters); 337 } 338 } 339 340 /** 341 * Calculate the width and height of a source image if scaled inside the given box.<p> 342 * 343 * @param sourceWidth the width of the source image 344 * @param sourceHeight the height of the source image 345 * @param boxWidth the width of the target box 346 * @param boxHeight the height of the target box 347 * 348 * @return the width [0] and height [1] of the source image if scaled inside the given box 349 */ 350 public static int[] calculateDimension(int sourceWidth, int sourceHeight, int boxWidth, int boxHeight) { 351 352 int[] result = new int[2]; 353 if ((sourceWidth <= boxWidth) && (sourceHeight <= boxHeight)) { 354 result[0] = sourceWidth; 355 result[1] = sourceHeight; 356 } else { 357 float scaleWidth = (float)boxWidth / (float)sourceWidth; 358 float scaleHeight = (float)boxHeight / (float)sourceHeight; 359 float scale = Math.min(scaleHeight, scaleWidth); 360 result[0] = Math.round(sourceWidth * scale); 361 result[1] = Math.round(sourceHeight * scale); 362 } 363 364 return result; 365 } 366 367 /** 368 * Gets image dimensions for given file 369 * @param imgFile image file 370 * @return dimensions of image 371 * @throws IOException if the file is not a known image 372 */ 373 public static Dimension getImageDimensions(String path, byte[] content) throws IOException { 374 375 String name = CmsResource.getName(path); 376 int pos = name.lastIndexOf("."); 377 if (pos == -1) { 378 LOG.warn("Couldn't determine image dimensions for " + path); 379 return null; 380 } 381 String suffix = name.substring(pos + 1); 382 Iterator<ImageReader> iter = ImageIO.getImageReadersBySuffix(suffix); 383 while (iter.hasNext()) { 384 ImageReader reader = iter.next(); 385 try { 386 ByteArrayImageInputStream stream = new ByteArrayImageInputStream(content); 387 reader.setInput(stream); 388 int minIndex = reader.getMinIndex(); 389 int width = reader.getWidth(minIndex); 390 int height = reader.getHeight(minIndex); 391 return new Dimension(width, height); 392 } catch (IOException e) { 393 LOG.warn("Problem determining image size for " + path + ": " + e.getLocalizedMessage(), e); 394 } finally { 395 reader.dispose(); 396 } 397 } 398 LOG.warn("Couldn't determine image dimensions for " + path); 399 return null; 400 } 401 402 /** 403 * Adds a filter name to the list of filters that should be applied to the image.<p> 404 * 405 * @param filter the filter name to add 406 */ 407 public void addFilter(String filter) { 408 409 if (CmsStringUtil.isNotEmpty(filter)) { 410 filter = filter.trim().toLowerCase(); 411 if (FILTERS.contains(filter)) { 412 m_filters.add(filter); 413 } 414 } 415 } 416 417 /** 418 * @see java.lang.Object#clone() 419 */ 420 @Override 421 public Object clone() { 422 423 CmsImageScaler clone = new CmsImageScaler(); 424 clone.initValuesFrom(this); 425 return clone; 426 } 427 428 /** 429 * Returns the color.<p> 430 * 431 * @return the color 432 */ 433 public Color getColor() { 434 435 return m_color; 436 } 437 438 /** 439 * Returns the color as a String.<p> 440 * 441 * @return the color as a String 442 */ 443 public String getColorString() { 444 445 StringBuffer result = new StringBuffer(); 446 if (m_color == Simapi.COLOR_TRANSPARENT) { 447 result.append(COLOR_TRANSPARENT); 448 } else { 449 if (m_color.getRed() < 16) { 450 result.append('0'); 451 } 452 result.append(Integer.toString(m_color.getRed(), 16)); 453 if (m_color.getGreen() < 16) { 454 result.append('0'); 455 } 456 result.append(Integer.toString(m_color.getGreen(), 16)); 457 if (m_color.getBlue() < 16) { 458 result.append('0'); 459 } 460 result.append(Integer.toString(m_color.getBlue(), 16)); 461 } 462 return result.toString(); 463 } 464 465 /** 466 * Returns the crop area height.<p> 467 * 468 * Use {@link #setCropArea(int, int, int, int)} to set this value.<p> 469 * 470 * @return the crop area height 471 */ 472 public int getCropHeight() { 473 474 return m_cropHeight; 475 } 476 477 /** 478 * Returns a new image scaler that is a cropped rescaler from <code>this</code> cropped scaler 479 * size to the given target scaler size.<p> 480 * 481 * @param target the image scaler that holds the target image dimensions 482 * 483 * @return a new image scaler that is a cropped rescaler from <code>this</code> cropped scaler 484 * size to the given target scaler size 485 * 486 * @see #getReScaler(CmsImageScaler) 487 * @see #setCropArea(int, int, int, int) 488 */ 489 public CmsImageScaler getCropScaler(CmsImageScaler target) { 490 491 // first re-scale the image (if required) 492 CmsImageScaler result = getReScaler(target); 493 // now use the crop area from the original 494 result.setCropArea(m_cropX, m_cropY, m_cropWidth, m_cropHeight); 495 return result; 496 } 497 498 /** 499 * Returns the crop area width.<p> 500 * 501 * Use {@link #setCropArea(int, int, int, int)} to set this value.<p> 502 * 503 * @return the crop area width 504 */ 505 public int getCropWidth() { 506 507 return m_cropWidth; 508 } 509 510 /** 511 * Returns the crop area X start coordinate.<p> 512 * 513 * Use {@link #setCropArea(int, int, int, int)} to set this value.<p> 514 * 515 * @return the crop area X start coordinate 516 */ 517 public int getCropX() { 518 519 return m_cropX; 520 } 521 522 /** 523 * Returns the crop area Y start coordinate.<p> 524 * 525 * Use {@link #setCropArea(int, int, int, int)} to set this value.<p> 526 * 527 * @return the crop area Y start coordinate 528 */ 529 public int getCropY() { 530 531 return m_cropY; 532 } 533 534 /** 535 * Returns a new image scaler that is a down scale from the size of <code>this</code> scaler 536 * to the given scaler size.<p> 537 * 538 * If no down scale from this to the given scaler is required according to 539 * {@link #isDownScaleRequired(CmsImageScaler)}, then <code>null</code> is returned.<p> 540 * 541 * @param downScaler the image scaler that holds the down scaled target image dimensions 542 * 543 * @return a new image scaler that is a down scale from the size of <code>this</code> scaler 544 * to the given target scaler size, or <code>null</code> 545 */ 546 public CmsImageScaler getDownScaler(CmsImageScaler downScaler) { 547 548 if (!isDownScaleRequired(downScaler)) { 549 // no down scaling is required 550 return null; 551 } 552 553 int downHeight = downScaler.getHeight(); 554 int downWidth = downScaler.getWidth(); 555 556 int height = getHeight(); 557 int width = getWidth(); 558 559 if (((height > width) && (downHeight < downWidth)) || ((width > height) && (downWidth < downHeight))) { 560 // adjust orientation 561 downHeight = downWidth; 562 downWidth = downScaler.getHeight(); 563 } 564 565 if (width > downWidth) { 566 // width is too large, re-calculate height 567 float scale = (float)downWidth / (float)width; 568 downHeight = Math.round(height * scale); 569 } else if (height > downHeight) { 570 // height is too large, re-calculate width 571 float scale = (float)downHeight / (float)height; 572 downWidth = Math.round(width * scale); 573 } else { 574 // something is wrong, don't down scale 575 return null; 576 } 577 578 // now create and initialize the result scaler 579 return new CmsImageScaler(downScaler, downWidth, downHeight); 580 } 581 582 /** 583 * Returns the list of image filter names (Strings) to be applied to the image.<p> 584 * 585 * @return the list of image filter names (Strings) to be applied to the image 586 */ 587 public List<String> getFilters() { 588 589 return m_filters; 590 } 591 592 /** 593 * Returns the list of image filter names (Strings) to be applied to the image as a String.<p> 594 * 595 * @return the list of image filter names (Strings) to be applied to the image as a String 596 */ 597 public String getFiltersString() { 598 599 StringBuffer result = new StringBuffer(); 600 Iterator<String> i = m_filters.iterator(); 601 while (i.hasNext()) { 602 String filter = i.next(); 603 result.append(filter); 604 if (i.hasNext()) { 605 result.append(':'); 606 } 607 } 608 return result.toString(); 609 } 610 611 /** 612 * Gets the focal point, or null if it is not set.<p> 613 * 614 * @return the focal point 615 */ 616 public CmsPoint getFocalPoint() { 617 618 return m_focalPoint; 619 } 620 621 /** 622 * Returns the height.<p> 623 * 624 * @return the height 625 */ 626 public int getHeight() { 627 628 return m_height; 629 } 630 631 /** 632 * Returns the image type from the given file name based on the file suffix (extension) 633 * and the available image writers.<p> 634 * 635 * For example, for the file name "opencms.gif" the type is GIF, for 636 * "opencms.jpg" is is "JPEG" etc.<p> 637 * 638 * In case the input filename has no suffix, or there is no known image writer for the format defined 639 * by the suffix, <code>null</code> is returned.<p> 640 * 641 * Any non-null result can be used if an image type input value is required.<p> 642 * 643 * @param filename the file name to get the type for 644 * 645 * @return the image type from the given file name based on the suffix and the available image writers, 646 * or null if no image writer is available for the format 647 */ 648 public String getImageType(String filename) { 649 650 return Simapi.getImageType(filename); 651 } 652 653 /** 654 * Returns the maximum image size (width * height) to apply image blurring when down scaling images.<p> 655 * 656 * Image blurring is required to achieve the best results for down scale operations when the target image size 657 * is 2 times or more smaller then the original image size. 658 * This parameter controls the maximum size (width * height) of an 659 * image that is blurred before it is down scaled. If the image is larger, no blurring is done. 660 * Image blurring is an expensive operation in both CPU usage and memory consumption. 661 * Setting the blur size to large may case "out of memory" errors.<p> 662 * 663 * @return the maximum image size (width * height) to apply image blurring when down scaling images 664 */ 665 public int getMaxBlurSize() { 666 667 return m_maxBlurSize; 668 } 669 670 /** 671 * Returns the maximum target height (for scale type '5').<p> 672 * 673 * @return the maximum target height (for scale type '5') 674 */ 675 public int getMaxHeight() { 676 677 return m_maxHeight; 678 } 679 680 /** 681 * Returns the maximum target width (for scale type '5').<p> 682 * 683 * @return the maximum target width (for scale type '5'). 684 */ 685 public int getMaxWidth() { 686 687 return m_maxWidth; 688 } 689 690 /** 691 * Returns the image pixel count, that is the image with multiplied by the image height.<p> 692 * 693 * If this scaler is not valid (see {@link #isValid()}) the result is undefined.<p> 694 * 695 * @return the image pixel count, that is the image with multiplied by the image height 696 */ 697 public int getPixelCount() { 698 699 return m_width * m_height; 700 } 701 702 /** 703 * Returns the position.<p> 704 * 705 * @return the position 706 */ 707 public int getPosition() { 708 709 return m_position; 710 } 711 712 /** 713 * Returns the image saving quality in percent (0 - 100).<p> 714 * 715 * This is used only if applicable, for example when saving JPEG images.<p> 716 * 717 * @return the image saving quality in percent 718 */ 719 public int getQuality() { 720 721 return m_quality; 722 } 723 724 /** 725 * Returns the image rendering mode constant.<p> 726 * 727 * Possible values are:<dl> 728 * <dt>{@link Simapi#RENDER_QUALITY} (default)</dt> 729 * <dd>Use best possible image processing - this may be slow sometimes.</dd> 730 * 731 * <dt>{@link Simapi#RENDER_SPEED}</dt> 732 * <dd>Fastest image processing but worse results - use this for thumbnails or where speed is more important then quality.</dd> 733 * 734 * <dt>{@link Simapi#RENDER_MEDIUM}</dt> 735 * <dd>Use default rendering hints from JVM - not recommended since it's almost as slow as the {@link Simapi#RENDER_QUALITY} mode.</dd></dl> 736 * 737 * @return the image rendering mode constant 738 */ 739 public int getRenderMode() { 740 741 return m_renderMode; 742 } 743 744 /** 745 * Creates a request parameter configured with the values from this image scaler, also 746 * appends a <code>'?'</code> char as a prefix so that this may be directly appended to an image URL.<p> 747 * 748 * This can be appended to an image request in order to apply image scaling parameters.<p> 749 * 750 * @return a request parameter configured with the values from this image scaler 751 * @see #toRequestParam() 752 */ 753 public String getRequestParam() { 754 755 return toRequestParam(); 756 } 757 758 /** 759 * Returns a new image scaler that is a rescaler from <code>this</code> scaler 760 * size to the given target scaler size.<p> 761 * 762 * The height of the target image is calculated in proportion 763 * to the original image width. If the width of the the original image is not known, 764 * the target image width is calculated in proportion to the original image height.<p> 765 * 766 * @param target the image scaler that holds the target image dimensions 767 * 768 * @return a new image scaler that is a rescaler from the <code>this</code> scaler 769 * size to the given target scaler size 770 */ 771 public CmsImageScaler getReScaler(CmsImageScaler target) { 772 773 int height = target.getHeight(); 774 int width = target.getWidth(); 775 int type = target.getType(); 776 777 if (type == 5) { 778 // best fit option without upscale in the provided dimensions 779 if (target.isValid()) { 780 // ensure we have sensible values for maxWidth / minWidth even if one has not been set 781 float maxWidth = target.getMaxWidth() > 0 ? target.getMaxWidth() : height; 782 float maxHeight = target.getMaxHeight() > 0 ? target.getMaxHeight() : width; 783 // calculate the factor of the image and the 3 possible target dimensions 784 float scaleOfImage = (float)getWidth() / (float)getHeight(); 785 float[] scales = new float[3]; 786 scales[0] = (float)width / (float)height; 787 scales[1] = width / maxHeight; 788 scales[2] = maxWidth / height; 789 int useScale = calculateClosest(scaleOfImage, scales); 790 int[] dimensions; 791 switch (useScale) { 792 case 1: 793 dimensions = calculateDimension(getWidth(), getHeight(), width, (int)maxHeight); 794 break; 795 case 2: 796 dimensions = calculateDimension(getWidth(), getHeight(), (int)maxWidth, height); 797 break; 798 case 0: 799 default: 800 dimensions = calculateDimension(getWidth(), getHeight(), width, height); 801 break; 802 } 803 width = dimensions[0]; 804 height = dimensions[1]; 805 } else { 806 // target not valid, switch type to 1 (no upscale) 807 type = 1; 808 } 809 } 810 811 if (type != 5) { 812 if ((width > 0) && (getWidth() > 0)) { 813 // width is known, calculate height 814 float scale = (float)width / (float)getWidth(); 815 height = Math.round(getHeight() * scale); 816 } else if ((height > 0) && (getHeight() > 0)) { 817 // height is known, calculate width 818 float scale = (float)height / (float)getHeight(); 819 width = Math.round(getWidth() * scale); 820 } else if (isValid() && !target.isValid()) { 821 // scaler is not valid but original is, so use original size of image 822 width = getWidth(); 823 height = getHeight(); 824 } 825 } 826 827 if ((type == 9) && ((width > getWidth()) || (height > getHeight()))) { 828 // type 9 with "no upscale" has been requested but target size is larger than original size 829 width = getWidth(); 830 height = getHeight(); 831 } 832 833 if ((type == 1) && (!target.isValid())) { 834 // type 1 with "no upscale" has been requested, only one target dimension was given 835 if ((target.getWidth() > 0) && (getWidth() < width)) { 836 // target width was given, target image should have this width 837 height = getHeight(); 838 } else if ((target.getHeight() > 0) && (getHeight() < height)) { 839 // target height was given, target image should have this height 840 width = getWidth(); 841 } 842 } 843 844 // now create and initialize the result scaler 845 CmsImageScaler result = new CmsImageScaler(target, width, height); 846 // type may have been switched 847 result.setType(type); 848 return result; 849 } 850 851 /** 852 * Returns the type.<p> 853 * 854 * Possible values are:<dl> 855 * 856 * <dt>0 (default): Scale to exact target size with background padding</dt><dd><ul> 857 * <li>enlarge image to fit in target size (if required) 858 * <li>reduce image to fit in target size (if required) 859 * <li>keep image aspect ratio / proportions intact 860 * <li>fill up with bgcolor to reach exact target size 861 * <li>fit full image inside target size (only applies if reduced)</ul></dd> 862 * 863 * <dt>1: Thumbnail generation mode (like 0 but no image enlargement)</dt><dd><ul> 864 * <li>dont't enlarge image 865 * <li>reduce image to fit in target size (if required) 866 * <li>keep image aspect ratio / proportions intact 867 * <li>fill up with bgcolor to reach exact target size 868 * <li>fit full image inside target size (only applies if reduced)</ul></dd> 869 * 870 * <dt>2: Scale to exact target size, crop what does not fit</dt><dd><ul> 871 * <li>enlarge image to fit in target size (if required) 872 * <li>reduce image to fit in target size (if required) 873 * <li>keep image aspect ratio / proportions intact 874 * <li>fit full image inside target size (crop what does not fit)</ul></dd> 875 * 876 * <dt>3: Scale and keep image proportions, target size variable</dt><dd><ul> 877 * <li>enlarge image to fit in target size (if required) 878 * <li>reduce image to fit in target size (if required) 879 * <li>keep image aspect ratio / proportions intact 880 * <li>scaled image will not be padded or cropped, so target size is likely not the exact requested size</ul></dd> 881 * 882 * <dt>4: Don't keep image proportions, use exact target size</dt><dd><ul> 883 * <li>enlarge image to fit in target size (if required) 884 * <li>reduce image to fit in target size (if required) 885 * <li>don't keep image aspect ratio / proportions intact 886 * <li>the image will be scaled exactly to the given target size and likely will be loose proportions</ul></dd> 887 * </dl> 888 * 889 * <dt>5: Scale and keep image proportions without enlargement, target size variable with optional max width and height</dt><dd><ul> 890 * <li>dont't enlarge image 891 * <li>reduce image to fit in target size (if required) 892 * <li>keep image aspect ratio / proportions intact 893 * <li>best fit into target width / height _OR_ width / maxHeight _OR_ maxWidth / height 894 * <li>scaled image will not be padded or cropped, so target size is likely not the exact requested size</ul></dd> 895 * 896 * <dt>6: Crop around point: Use exact pixels</dt><dd><ul> 897 * <li>This type only applies for image crop operations (full crop parameters must be provided). 898 * <li>In this case the crop coordinates <code>x, y</code> are treated as a point in the middle of <code>width, height</code>. 899 * <li>With this type, the pixels from the source image are used 1:1 for the target image.</ul></dd> 900 * 901 * <dt>7: Crop around point: Use pixels for target size, get maximum out of image</dt><dd><ul> 902 * <li>This type only applies for image crop operations (full crop parameters must be provided). 903 * <li>In this case the crop coordinates <code>x, y</code> are treated as a point in the middle of <code>width, height</code>. 904 * <li>With this type, as much as possible from the source image is fitted in the target image size.</ul></dd> 905 * 906 * <dt>8: Focal point mode.</dt> 907 * <p>If a focal point is set on this scaler, this mode will first crop a region defined by cx,cy,cw,ch from the original 908 * image, then select the largest region of the aspect ratio defined by w/h in the cropped image containing the focal point, and finally 909 * scale that region to size w x h.</p> 910 * 911 * <dt>9: Scale and keep image proportions, target size variable, no image enlargement</dt><dd><ul> 912 * <li>dont't enlarge image 913 * <li>reduce image to fit in target size (if required) 914 * <li>keep image aspect ratio / proportions intact 915 * <li>scaled image will not be padded or cropped, so target size is likely not the exact requested size</ul></dd> 916 * 917 * @return the type 918 */ 919 public int getType() { 920 921 return m_type; 922 } 923 924 /** 925 * Returns the width.<p> 926 * 927 * @return the width 928 */ 929 public int getWidth() { 930 931 return m_width; 932 } 933 934 /** 935 * Returns a new image scaler that is a width based down scale from the size of <code>this</code> scaler 936 * to the given scaler size.<p> 937 * 938 * If no down scale from this to the given scaler is required because the width of <code>this</code> 939 * scaler is not larger than the target width, then the image dimensions of <code>this</code> scaler 940 * are unchanged in the result scaler. No up scaling is done!<p> 941 * 942 * @param downScaler the image scaler that holds the down scaled target image dimensions 943 * 944 * @return a new image scaler that is a down scale from the size of <code>this</code> scaler 945 * to the given target scaler size 946 */ 947 public CmsImageScaler getWidthScaler(CmsImageScaler downScaler) { 948 949 int width = downScaler.getWidth(); 950 int height; 951 952 if (getWidth() > width) { 953 // width is too large, re-calculate height 954 float scale = (float)width / (float)getWidth(); 955 height = Math.round(getHeight() * scale); 956 } else { 957 // width is ok 958 width = getWidth(); 959 height = getHeight(); 960 } 961 962 // now create and initialize the result scaler 963 return new CmsImageScaler(downScaler, width, height); 964 } 965 966 /** 967 * @see java.lang.Object#hashCode() 968 */ 969 @Override 970 public int hashCode() { 971 972 return toString().hashCode(); 973 } 974 975 /** 976 * Returns <code>true</code> if all required parameters for image cropping are available.<p> 977 * 978 * Required parameters are <code>"cx","cy"</code> (x, y start coordinate), 979 * and <code>"ch","cw"</code> (crop height and width).<p> 980 * 981 * @return <code>true</code> if all required cropping parameters are available 982 */ 983 public boolean isCropping() { 984 985 return (m_cropX >= 0) && (m_cropY >= 0) && (m_cropHeight > 0) && (m_cropWidth > 0); 986 } 987 988 /** 989 * Returns <code>true</code> if this image scaler must be down scaled when compared to the 990 * given "down scale" image scaler.<p> 991 * 992 * If either <code>this</code> scaler or the given <code>downScaler</code> is invalid according to 993 * {@link #isValid()}, then <code>false</code> is returned.<p> 994 * 995 * The use case: <code>this</code> scaler represents an image (that is contains width and height of 996 * an image). The <code>downScaler</code> represents the maximum wanted image. The scalers 997 * are compared and if the image represented by <code>this</code> scaler is too large, 998 * <code>true</code> is returned. Image orientation is ignored, so for example an image with 600x800 pixel 999 * will NOT be down scaled if the target size is 800x600 but kept unchanged.<p> 1000 * 1001 * @param downScaler the down scaler to compare this image scaler with 1002 * 1003 * @return <code>true</code> if this image scaler must be down scaled when compared to the 1004 * given "down scale" image scaler 1005 */ 1006 public boolean isDownScaleRequired(CmsImageScaler downScaler) { 1007 1008 if ((downScaler == null) || !isValid() || !downScaler.isValid()) { 1009 // one of the scalers is invalid 1010 return false; 1011 } 1012 1013 if (getPixelCount() < (downScaler.getPixelCount() / 2)) { 1014 // the image has much less pixels then the target, so don't downscale 1015 return false; 1016 } 1017 1018 int downWidth = downScaler.getWidth(); 1019 int downHeight = downScaler.getHeight(); 1020 if (downHeight > downWidth) { 1021 // normalize image orientation - the width should always be the large side 1022 downWidth = downHeight; 1023 downHeight = downScaler.getWidth(); 1024 } 1025 int height = getHeight(); 1026 int width = getWidth(); 1027 if (height > width) { 1028 // normalize image orientation - the width should always be the large side 1029 width = height; 1030 height = getWidth(); 1031 } 1032 1033 return (width > downWidth) || (height > downHeight); 1034 } 1035 1036 /** 1037 * Returns <code>true</code> if the image scaler was 1038 * only used to read image properties from the VFS. 1039 * 1040 * @return <code>true</code> if the image scaler was 1041 * only used to read image properties from the VFS 1042 */ 1043 public boolean isOriginalScaler() { 1044 1045 return m_isOriginalScaler; 1046 } 1047 1048 /** 1049 * Returns <code>true</code> if all required parameters are available.<p> 1050 * 1051 * Required parameters are <code>"h"</code> (height), and <code>"w"</code> (width).<p> 1052 * 1053 * @return <code>true</code> if all required parameters are available 1054 */ 1055 public boolean isValid() { 1056 1057 return (m_width > 0) && (m_height > 0); 1058 } 1059 1060 /** 1061 * Parses the given parameters and sets the internal scaler variables accordingly.<p> 1062 * 1063 * The parameter String must have a format like <code>"h:100,w:200,t:1"</code>, 1064 * that is a comma separated list of attributes followed by a colon ":", followed by a value. 1065 * As possible attributes, use the constants from this class that start with <code>SCALE_PARAM</Code> 1066 * for example {@link #SCALE_PARAM_HEIGHT} or {@link #SCALE_PARAM_WIDTH}.<p> 1067 * 1068 * @param parameters the parameters to parse 1069 */ 1070 public void parseParameters(String parameters) { 1071 1072 m_width = -1; 1073 m_height = -1; 1074 m_position = 0; 1075 m_type = 0; 1076 m_color = Simapi.COLOR_TRANSPARENT; 1077 m_cropX = -1; 1078 m_cropY = -1; 1079 m_cropWidth = -1; 1080 m_cropHeight = -1; 1081 1082 List<String> tokens = CmsStringUtil.splitAsList(parameters, ','); 1083 Iterator<String> it = tokens.iterator(); 1084 String k; 1085 String v; 1086 while (it.hasNext()) { 1087 String t = it.next(); 1088 // extract key and value 1089 k = null; 1090 v = null; 1091 int idx = t.indexOf(':'); 1092 if (idx >= 0) { 1093 k = t.substring(0, idx).trim(); 1094 if (t.length() > idx) { 1095 v = t.substring(idx + 1).trim(); 1096 } 1097 } 1098 if (CmsStringUtil.isNotEmpty(k) && CmsStringUtil.isNotEmpty(v)) { 1099 // key and value are available 1100 if (SCALE_PARAM_HEIGHT.equals(k)) { 1101 // image height 1102 m_height = CmsStringUtil.getIntValue(v, Integer.MIN_VALUE, k); 1103 } else if (SCALE_PARAM_WIDTH.equals(k)) { 1104 // image width 1105 m_width = CmsStringUtil.getIntValue(v, Integer.MIN_VALUE, k); 1106 } else if (SCALE_PARAM_CROP_X.equals(k)) { 1107 // crop x coordinate 1108 m_cropX = CmsStringUtil.getIntValue(v, Integer.MIN_VALUE, k); 1109 } else if (SCALE_PARAM_CROP_Y.equals(k)) { 1110 // crop y coordinate 1111 m_cropY = CmsStringUtil.getIntValue(v, Integer.MIN_VALUE, k); 1112 } else if (SCALE_PARAM_CROP_WIDTH.equals(k)) { 1113 // crop width 1114 m_cropWidth = CmsStringUtil.getIntValue(v, Integer.MIN_VALUE, k); 1115 } else if (SCALE_PARAM_CROP_HEIGHT.equals(k)) { 1116 // crop height 1117 m_cropHeight = CmsStringUtil.getIntValue(v, Integer.MIN_VALUE, k); 1118 } else if (SCALE_PARAM_TYPE.equals(k)) { 1119 // scaling type 1120 setType(CmsStringUtil.getIntValue(v, -1, CmsImageScaler.SCALE_PARAM_TYPE)); 1121 } else if (SCALE_PARAM_COLOR.equals(k)) { 1122 // image background color 1123 setColor(v); 1124 } else if (SCALE_PARAM_POS.equals(k)) { 1125 // image position (depends on scale type) 1126 setPosition(CmsStringUtil.getIntValue(v, -1, CmsImageScaler.SCALE_PARAM_POS)); 1127 } else if (SCALE_PARAM_QUALITY.equals(k)) { 1128 // image position (depends on scale type) 1129 setQuality(CmsStringUtil.getIntValue(v, 0, k)); 1130 } else if (SCALE_PARAM_RENDERMODE.equals(k)) { 1131 // image position (depends on scale type) 1132 setRenderMode(CmsStringUtil.getIntValue(v, 0, k)); 1133 } else if (SCALE_PARAM_FILTER.equals(k)) { 1134 // image filters to apply 1135 setFilters(v); 1136 } else { 1137 if (LOG.isDebugEnabled()) { 1138 LOG.debug(Messages.get().getBundle().key(Messages.ERR_INVALID_IMAGE_SCALE_PARAMS_2, k, v)); 1139 } 1140 } 1141 } else { 1142 if (LOG.isDebugEnabled()) { 1143 LOG.debug(Messages.get().getBundle().key(Messages.ERR_INVALID_IMAGE_SCALE_PARAMS_2, k, v)); 1144 } 1145 } 1146 } 1147 // initialize image crop area 1148 initCropArea(); 1149 } 1150 1151 /** 1152 * Returns a scaled version of the given image byte content according this image scalers parameters.<p> 1153 * 1154 * @param content the image byte content to scale 1155 * @param image if this is set, this image will be used as base for the scaling rather than a new image read from the content byte array 1156 * @param rootPath the root path of the image file in the VFS 1157 * 1158 * @return a scaled version of the given image byte content according to the provided scaler parameters 1159 */ 1160 public byte[] scaleImage(byte[] content, BufferedImage image, String rootPath) { 1161 1162 byte[] result = content; 1163 // flag for processed image 1164 boolean imageProcessed = false; 1165 // initialize image crop area 1166 initCropArea(); 1167 1168 RenderSettings renderSettings; 1169 if ((m_renderMode == 0) && (m_quality == 0)) { 1170 // use default render mode and quality 1171 renderSettings = new RenderSettings(Simapi.RENDER_QUALITY); 1172 } else { 1173 // use special render mode and/or quality 1174 renderSettings = new RenderSettings(m_renderMode); 1175 if (m_quality != 0) { 1176 renderSettings.setCompressionQuality(m_quality / 100f); 1177 } 1178 } 1179 // set max blur size 1180 renderSettings.setMaximumBlurSize(m_maxBlurSize); 1181 // new create the scaler 1182 Simapi scaler = new Simapi(renderSettings); 1183 // calculate a valid image type supported by the imaging library (e.g. "JPEG", "GIF") 1184 String imageType = Simapi.getImageType(rootPath); 1185 if (imageType == null) { 1186 // no type given, maybe the name got mixed up 1187 String mimeType = OpenCms.getResourceManager().getMimeType(rootPath, null, null); 1188 // check if this is another known MIME type, if so DONT use it (images should not be named *.pdf) 1189 if (mimeType == null) { 1190 // no MIME type found, use JPEG format to write images to the cache 1191 imageType = Simapi.TYPE_JPEG; 1192 } 1193 } 1194 if (imageType == null) { 1195 // unknown type, unable to scale the image 1196 if (LOG.isDebugEnabled()) { 1197 LOG.debug(Messages.get().getBundle().key(Messages.ERR_UNABLE_TO_SCALE_IMAGE_2, rootPath, toString())); 1198 } 1199 return result; 1200 } 1201 try { 1202 if (image == null) { 1203 image = Simapi.read(content); 1204 } 1205 1206 if (isCropping()) { 1207 // check if the crop width / height are not larger then the source image 1208 if ((getType() == 0) && ((m_cropHeight > image.getHeight()) || (m_cropWidth > image.getWidth()))) { 1209 // crop height / width is outside of image - return image unchanged 1210 return result; 1211 } 1212 } 1213 1214 Color color = getColor(); 1215 1216 if (!m_filters.isEmpty()) { 1217 Iterator<String> i = m_filters.iterator(); 1218 while (i.hasNext()) { 1219 String filter = i.next(); 1220 if (FILTER_GRAYSCALE.equals(filter)) { 1221 // add a gray scale filter 1222 GrayscaleFilter grayscaleFilter = new GrayscaleFilter(); 1223 renderSettings.addImageFilter(grayscaleFilter); 1224 } else if (FILTER_SHADOW.equals(filter)) { 1225 // add a drop shadow filter 1226 ShadowFilter shadowFilter = new ShadowFilter(); 1227 shadowFilter.setXOffset(5); 1228 shadowFilter.setYOffset(5); 1229 shadowFilter.setOpacity(192); 1230 shadowFilter.setBackgroundColor(color.getRGB()); 1231 color = Simapi.COLOR_TRANSPARENT; 1232 renderSettings.setTransparentReplaceColor(Simapi.COLOR_TRANSPARENT); 1233 renderSettings.addImageFilter(shadowFilter); 1234 } 1235 } 1236 } 1237 1238 if (isCropping()) { 1239 if ((getType() == 8) && (m_focalPoint != null)) { 1240 image = scaler.cropToSize( 1241 image, 1242 m_cropX, 1243 m_cropY, 1244 m_cropWidth, 1245 m_cropHeight, 1246 m_cropWidth, 1247 m_cropHeight, 1248 color); 1249 // Find the biggest scaling factor which, when applied to a rectangle of dimensions m_width x m_height, 1250 // would allow the resulting rectangle to still fit inside a rectangle of dimensions m_cropWidth x m_cropHeight 1251 // (we have to take the minimum because a rectangle that fits on the x axis might still be out of bounds on the y axis, and 1252 // vice versa). 1253 double scaling = Math.min((1.0 * m_cropWidth) / m_width, (1.0 * m_cropHeight) / m_height); 1254 int relW = (int)(scaling * m_width); 1255 int relH = (int)(scaling * m_height); 1256 // the focal point's coordinates are in the uncropped image's coordinate system, so we have to subtract cx/cy 1257 int relX = (int)(m_focalPoint.getX() - m_cropX); 1258 int relY = (int)(m_focalPoint.getY() - m_cropY); 1259 image = scaler.cropPointToSize(image, relX, relY, false, relW, relH); 1260 if ((m_width != relW) || (m_height != relH)) { 1261 image = scaler.scale(image, m_width, m_height); 1262 } 1263 } else if ((getType() == 6) || (getType() == 7)) { 1264 // image crop operation around point 1265 image = scaler.cropPointToSize(image, m_cropX, m_cropY, getType() == 6, m_cropWidth, m_cropHeight); 1266 } else { 1267 // image crop operation 1268 image = scaler.cropToSize( 1269 image, 1270 m_cropX, 1271 m_cropY, 1272 m_cropWidth, 1273 m_cropHeight, 1274 getWidth(), 1275 getHeight(), 1276 color); 1277 } 1278 1279 imageProcessed = true; 1280 } else { 1281 // only rescale the image, if the width and height are different to the target size 1282 int imageWidth = image.getWidth(); 1283 int imageHeight = image.getHeight(); 1284 1285 // image rescale operation 1286 switch (getType()) { 1287 // select the "right" method of scaling according to the "t" parameter 1288 case 1: 1289 // thumbnail generation mode (like 0 but no image enlargement) 1290 image = scaler.resize(image, getWidth(), getHeight(), color, getPosition(), false); 1291 imageProcessed = true; 1292 break; 1293 case 2: 1294 // scale to exact target size, crop what does not fit 1295 if (((imageWidth != getWidth()) || (imageHeight != getHeight()))) { 1296 image = scaler.resize(image, getWidth(), getHeight(), getPosition()); 1297 imageProcessed = true; 1298 } 1299 break; 1300 case 3: 1301 // scale and keep image proportions, target size variable 1302 if (((imageWidth != getWidth()) || (imageHeight != getHeight()))) { 1303 image = scaler.resize(image, getWidth(), getHeight(), true); 1304 imageProcessed = true; 1305 } 1306 break; 1307 case 4: 1308 // don't keep image proportions, use exact target size 1309 if (((imageWidth != getWidth()) || (imageHeight != getHeight()))) { 1310 image = scaler.resize(image, getWidth(), getHeight(), false); 1311 imageProcessed = true; 1312 } 1313 break; 1314 case 5: 1315 // scale and keep image proportions, target size variable, include maxWidth / maxHeight option 1316 // image proportions have already been calculated so should not be a problem, use 1317 // 'false' to make sure image size exactly matches height and width attributes of generated tag 1318 if (((imageWidth != getWidth()) || (imageHeight != getHeight()))) { 1319 image = scaler.resize(image, getWidth(), getHeight(), false); 1320 imageProcessed = true; 1321 } 1322 break; 1323 case 9: 1324 // scale and keep image proportions, target size variable, no image enlargement 1325 if ((imageWidth > getWidth()) && (imageHeight > getHeight())) { 1326 image = scaler.resize(image, getWidth(), getHeight(), true); 1327 imageProcessed = true; 1328 } 1329 break; 1330 default: 1331 // scale to exact target size with background padding 1332 image = scaler.resize(image, getWidth(), getHeight(), color, getPosition(), true); 1333 imageProcessed = true; 1334 } 1335 1336 } 1337 1338 if (!m_filters.isEmpty()) { 1339 Rectangle targetSize = scaler.applyFilterDimensions(getWidth(), getHeight()); 1340 image = scaler.resize( 1341 image, 1342 (int)targetSize.getWidth(), 1343 (int)targetSize.getHeight(), 1344 Simapi.COLOR_TRANSPARENT, 1345 Simapi.POS_CENTER); 1346 image = scaler.applyFilters(image); 1347 imageProcessed = true; 1348 } 1349 1350 // get the byte result for the scaled image if some changes have been made. 1351 // otherwiese use the original image 1352 if (imageProcessed) { 1353 result = scaler.getBytes(image, imageType); 1354 } 1355 } catch (Exception e) { 1356 if (LOG.isDebugEnabled()) { 1357 LOG.debug( 1358 Messages.get().getBundle().key(Messages.ERR_UNABLE_TO_SCALE_IMAGE_2, rootPath, toString()), 1359 e); 1360 } 1361 } 1362 return result; 1363 } 1364 1365 /** 1366 * Returns a scaled version of the given image byte content according this image scalers parameters.<p> 1367 * 1368 * @param content the image byte content to scale 1369 * @param rootPath the root path of the image file in the VFS 1370 * 1371 * @return a scaled version of the given image byte content according to the provided scaler parameters 1372 */ 1373 public byte[] scaleImage(byte[] content, String rootPath) { 1374 1375 return scaleImage(content, (BufferedImage)null, rootPath); 1376 } 1377 1378 /** 1379 * Returns a scaled version of the given image file according this image scalers parameters.<p> 1380 * 1381 * @param file the image file to scale 1382 * 1383 * @return a scaled version of the given image file according to the provided scaler parameters 1384 */ 1385 public byte[] scaleImage(CmsFile file) { 1386 1387 return scaleImage(file.getContents(), file.getRootPath()); 1388 } 1389 1390 /** 1391 * Sets the color.<p> 1392 * 1393 * @param color the color to set 1394 */ 1395 public void setColor(Color color) { 1396 1397 m_color = color; 1398 } 1399 1400 /** 1401 * Sets the color as a String.<p> 1402 * 1403 * @param value the color to set 1404 */ 1405 public void setColor(String value) { 1406 1407 if (COLOR_TRANSPARENT.indexOf(value) == 0) { 1408 setColor(Simapi.COLOR_TRANSPARENT); 1409 } else { 1410 setColor(CmsStringUtil.getColorValue(value, Simapi.COLOR_TRANSPARENT, SCALE_PARAM_COLOR)); 1411 } 1412 } 1413 1414 /** 1415 * Sets the image crop area.<p> 1416 * 1417 * @param x the x coordinate for the crop 1418 * @param y the y coordinate for the crop 1419 * @param width the crop width 1420 * @param height the crop height 1421 */ 1422 public void setCropArea(int x, int y, int width, int height) { 1423 1424 m_cropX = x; 1425 m_cropY = y; 1426 m_cropWidth = width; 1427 m_cropHeight = height; 1428 } 1429 1430 /** 1431 * Sets the list of filters as a String.<p> 1432 * 1433 * @param value the list of filters to set 1434 */ 1435 public void setFilters(String value) { 1436 1437 m_filters = new ArrayList<String>(); 1438 List<String> filters = CmsStringUtil.splitAsList(value, ':'); 1439 Iterator<String> i = filters.iterator(); 1440 while (i.hasNext()) { 1441 String filter = i.next(); 1442 filter = filter.trim().toLowerCase(); 1443 Iterator<String> j = FILTERS.iterator(); 1444 while (j.hasNext()) { 1445 String candidate = j.next(); 1446 if (candidate.startsWith(filter)) { 1447 // found a matching filter 1448 addFilter(candidate); 1449 break; 1450 } 1451 } 1452 } 1453 } 1454 1455 /** 1456 * Sets the focal point.<p> 1457 * 1458 * @param point the new value for the focal point 1459 */ 1460 public void setFocalPoint(CmsPoint point) { 1461 1462 m_focalPoint = point; 1463 } 1464 1465 /** 1466 * Sets the height.<p> 1467 * 1468 * @param height the height to set 1469 */ 1470 public void setHeight(int height) { 1471 1472 m_height = height; 1473 } 1474 1475 /** 1476 * Sets the maximum image size (width * height) to apply image blurring when downscaling images.<p> 1477 * 1478 * @param maxBlurSize the maximum image blur size to set 1479 * 1480 * @see #getMaxBlurSize() for a more detailed description about this parameter 1481 */ 1482 public void setMaxBlurSize(int maxBlurSize) { 1483 1484 m_maxBlurSize = maxBlurSize; 1485 } 1486 1487 /** 1488 * Sets the maximum target height (for scale type '5').<p> 1489 * 1490 * @param maxHeight the maximum target height to set 1491 */ 1492 public void setMaxHeight(int maxHeight) { 1493 1494 m_maxHeight = maxHeight; 1495 } 1496 1497 /** 1498 * Sets the maximum target width (for scale type '5').<p> 1499 * 1500 * @param maxWidth the maximum target width to set 1501 */ 1502 public void setMaxWidth(int maxWidth) { 1503 1504 m_maxWidth = maxWidth; 1505 } 1506 1507 /** 1508 * Sets the scale position.<p> 1509 * 1510 * @param position the position to set 1511 */ 1512 public void setPosition(int position) { 1513 1514 switch (position) { 1515 case Simapi.POS_DOWN_LEFT: 1516 case Simapi.POS_DOWN_RIGHT: 1517 case Simapi.POS_STRAIGHT_DOWN: 1518 case Simapi.POS_STRAIGHT_LEFT: 1519 case Simapi.POS_STRAIGHT_RIGHT: 1520 case Simapi.POS_STRAIGHT_UP: 1521 case Simapi.POS_UP_LEFT: 1522 case Simapi.POS_UP_RIGHT: 1523 // position is fine 1524 m_position = position; 1525 break; 1526 default: 1527 m_position = Simapi.POS_CENTER; 1528 } 1529 } 1530 1531 /** 1532 * Sets the image saving quality in percent.<p> 1533 * 1534 * @param quality the image saving quality (in percent) to set 1535 */ 1536 public void setQuality(int quality) { 1537 1538 if (quality < 0) { 1539 m_quality = 0; 1540 } else if (quality > 100) { 1541 m_quality = 100; 1542 } else { 1543 m_quality = quality; 1544 } 1545 } 1546 1547 /** 1548 * Sets the image rendering mode constant.<p> 1549 * 1550 * @param renderMode the image rendering mode to set 1551 * 1552 * @see #getRenderMode() for a list of allowed values for the rendering mode 1553 */ 1554 public void setRenderMode(int renderMode) { 1555 1556 if ((renderMode < Simapi.RENDER_QUALITY) || (renderMode > Simapi.RENDER_SPEED)) { 1557 renderMode = Simapi.RENDER_QUALITY; 1558 } 1559 m_renderMode = renderMode; 1560 } 1561 1562 /** 1563 * Sets the scale type.<p> 1564 * 1565 * @param type the scale type to set 1566 * 1567 * @see #getType() for a detailed description of the possible values for the type 1568 */ 1569 public void setType(int type) { 1570 1571 if ((type < 0) || (type > 9)) { 1572 // invalid type, use 0 1573 m_type = 0; 1574 } else { 1575 m_type = type; 1576 } 1577 } 1578 1579 /** 1580 * Sets the width.<p> 1581 * 1582 * @param width the width to set 1583 */ 1584 public void setWidth(int width) { 1585 1586 m_width = width; 1587 } 1588 1589 /** 1590 * Creates a request parameter configured with the values from this image scaler, also 1591 * appends a <code>'?'</code> char as a prefix so that this may be directly appended to an image URL.<p> 1592 * 1593 * This can be appended to an image request in order to apply image scaling parameters.<p> 1594 * 1595 * @return a request parameter configured with the values from this image scaler 1596 */ 1597 public String toRequestParam() { 1598 1599 StringBuffer result = new StringBuffer(128); 1600 result.append('?'); 1601 result.append(PARAM_SCALE); 1602 result.append('='); 1603 result.append(toString()); 1604 1605 return result.toString(); 1606 } 1607 1608 /** 1609 * @see java.lang.Object#toString() 1610 */ 1611 @Override 1612 public String toString() { 1613 1614 if (m_scaleParameters != null) { 1615 return m_scaleParameters; 1616 } 1617 1618 StringBuffer result = new StringBuffer(64); 1619 if (isCropping()) { 1620 result.append(CmsImageScaler.SCALE_PARAM_CROP_X); 1621 result.append(':'); 1622 result.append(m_cropX); 1623 result.append(','); 1624 result.append(CmsImageScaler.SCALE_PARAM_CROP_Y); 1625 result.append(':'); 1626 result.append(m_cropY); 1627 result.append(','); 1628 result.append(CmsImageScaler.SCALE_PARAM_CROP_WIDTH); 1629 result.append(':'); 1630 result.append(m_cropWidth); 1631 result.append(','); 1632 result.append(CmsImageScaler.SCALE_PARAM_CROP_HEIGHT); 1633 result.append(':'); 1634 result.append(m_cropHeight); 1635 } 1636 if (!isCropping() || ((m_width != m_cropWidth) || (m_height != m_cropHeight))) { 1637 if (isCropping()) { 1638 result.append(','); 1639 } 1640 result.append(CmsImageScaler.SCALE_PARAM_WIDTH); 1641 result.append(':'); 1642 result.append(m_width); 1643 result.append(','); 1644 result.append(CmsImageScaler.SCALE_PARAM_HEIGHT); 1645 result.append(':'); 1646 result.append(m_height); 1647 } 1648 if (m_type > 0) { 1649 result.append(','); 1650 result.append(CmsImageScaler.SCALE_PARAM_TYPE); 1651 result.append(':'); 1652 result.append(m_type); 1653 } 1654 if (m_position > 0) { 1655 result.append(','); 1656 result.append(CmsImageScaler.SCALE_PARAM_POS); 1657 result.append(':'); 1658 result.append(m_position); 1659 } 1660 if (m_color != Color.WHITE) { 1661 result.append(','); 1662 result.append(CmsImageScaler.SCALE_PARAM_COLOR); 1663 result.append(':'); 1664 result.append(getColorString()); 1665 } 1666 if (m_quality > 0) { 1667 result.append(','); 1668 result.append(CmsImageScaler.SCALE_PARAM_QUALITY); 1669 result.append(':'); 1670 result.append(m_quality); 1671 } 1672 if (m_renderMode > 0) { 1673 result.append(','); 1674 result.append(CmsImageScaler.SCALE_PARAM_RENDERMODE); 1675 result.append(':'); 1676 result.append(m_renderMode); 1677 } 1678 if (!m_filters.isEmpty()) { 1679 result.append(','); 1680 result.append(CmsImageScaler.SCALE_PARAM_FILTER); 1681 result.append(':'); 1682 result.append(getFiltersString()); 1683 } 1684 m_scaleParameters = result.toString(); 1685 return m_scaleParameters; 1686 } 1687 1688 /** 1689 * Calculate the closest match of the given base float with the list of others.<p> 1690 * 1691 * @param base the base float to compare the other with 1692 * @param others the list of floats to compate to the base 1693 * 1694 * @return the array index of the closest match 1695 */ 1696 private int calculateClosest(float base, float[] others) { 1697 1698 int result = -1; 1699 float bestMatch = Float.MAX_VALUE; 1700 for (int count = 0; count < others.length; count++) { 1701 float difference = Math.abs(base - others[count]); 1702 if (difference < bestMatch) { 1703 // new best match found 1704 bestMatch = difference; 1705 result = count; 1706 } 1707 if (bestMatch == 0f) { 1708 // it does not get better then this 1709 break; 1710 } 1711 } 1712 return result; 1713 } 1714 1715 private Dimension getDimensionsWithSimapi(byte[] content) throws Exception { 1716 1717 BufferedImage image = Simapi.read(content); 1718 return new Dimension(image.getWidth(), image.getHeight()); 1719 } 1720 1721 /** 1722 * Initializes the members with the default values.<p> 1723 */ 1724 private void init() { 1725 1726 m_height = -1; 1727 m_width = -1; 1728 m_maxHeight = -1; 1729 m_maxWidth = -1; 1730 m_type = 0; 1731 m_position = 0; 1732 m_renderMode = 0; 1733 m_quality = 0; 1734 m_cropX = -1; 1735 m_cropY = -1; 1736 m_cropHeight = -1; 1737 m_cropWidth = -1; 1738 m_color = Color.WHITE; 1739 m_filters = new ArrayList<String>(); 1740 m_maxBlurSize = CmsImageLoader.getMaxBlurSize(); 1741 m_isOriginalScaler = false; 1742 } 1743 1744 /** 1745 * Initializes the crop area setting.<p> 1746 * 1747 * Only if all 4 required parameters have been set, the crop area is set accordingly. 1748 * Moreover, it is not required to specify the target image width and height when using crop, 1749 * because these parameters can be calculated from the crop area.<p> 1750 * 1751 * Scale type 6 and 7 are used for a 'crop around point' operation, see {@link #getType()} for a description.<p> 1752 */ 1753 private void initCropArea() { 1754 1755 if (isCropping()) { 1756 // crop area is set up correctly 1757 // adjust target image height or width if required 1758 if (m_width < 0) { 1759 m_width = m_cropWidth; 1760 } 1761 if (m_height < 0) { 1762 m_height = m_cropHeight; 1763 } 1764 if ((getType() != 6) && (getType() != 7) && (getType() != 8)) { 1765 // cropping type can only be 6 or 7 (point cropping) 1766 // all other values with cropping coordinates are invalid 1767 setType(0); 1768 } 1769 } 1770 } 1771 1772 /** 1773 * Copies all values from the given scaler into this scaler.<p> 1774 * 1775 * @param source the source scaler 1776 */ 1777 private void initValuesFrom(CmsImageScaler source) { 1778 1779 m_color = source.m_color; 1780 m_cropHeight = source.m_cropHeight; 1781 m_cropWidth = source.m_cropWidth; 1782 m_cropX = source.m_cropX; 1783 m_cropY = source.m_cropY; 1784 m_filters = new ArrayList<String>(source.m_filters); 1785 m_focalPoint = source.m_focalPoint; 1786 m_height = source.m_height; 1787 m_isOriginalScaler = source.m_isOriginalScaler; 1788 m_maxBlurSize = source.m_maxBlurSize; 1789 m_position = source.m_position; 1790 m_quality = source.m_quality; 1791 m_renderMode = source.m_renderMode; 1792 m_type = source.m_type; 1793 m_width = source.m_width; 1794 1795 } 1796}