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 == 1) && (!target.isValid())) { 828 // "no upscale" has been requested, only one target dimension was given 829 if ((target.getWidth() > 0) && (getWidth() < width)) { 830 // target width was given, target image should have this width 831 height = getHeight(); 832 } else if ((target.getHeight() > 0) && (getHeight() < height)) { 833 // target height was given, target image should have this height 834 width = getWidth(); 835 } 836 } 837 838 // now create and initialize the result scaler 839 CmsImageScaler result = new CmsImageScaler(target, width, height); 840 // type may have been switched 841 result.setType(type); 842 return result; 843 } 844 845 /** 846 * Returns the type.<p> 847 * 848 * Possible values are:<dl> 849 * 850 * <dt>0 (default): Scale to exact target size with background padding</dt><dd><ul> 851 * <li>enlarge image to fit in target size (if required) 852 * <li>reduce image to fit in target size (if required) 853 * <li>keep image aspect ratio / proportions intact 854 * <li>fill up with bgcolor to reach exact target size 855 * <li>fit full image inside target size (only applies if reduced)</ul></dd> 856 * 857 * <dt>1: Thumbnail generation mode (like 0 but no image enlargement)</dt><dd><ul> 858 * <li>dont't enlarge image 859 * <li>reduce image to fit in target size (if required) 860 * <li>keep image aspect ratio / proportions intact 861 * <li>fill up with bgcolor to reach exact target size 862 * <li>fit full image inside target size (only applies if reduced)</ul></dd> 863 * 864 * <dt>2: Scale to exact target size, crop what does not fit</dt><dd><ul> 865 * <li>enlarge image to fit in target size (if required) 866 * <li>reduce image to fit in target size (if required) 867 * <li>keep image aspect ratio / proportions intact 868 * <li>fit full image inside target size (crop what does not fit)</ul></dd> 869 * 870 * <dt>3: Scale and keep image proportions, target size variable</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>scaled image will not be padded or cropped, so target size is likely not the exact requested size</ul></dd> 875 * 876 * <dt>4: Don't keep image proportions, use exact target size</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>don't keep image aspect ratio / proportions intact 880 * <li>the image will be scaled exactly to the given target size and likely will be loose proportions</ul></dd> 881 * </dl> 882 * 883 * <dt>5: Scale and keep image proportions without enlargement, target size variable with optional max width and height</dt><dd><ul> 884 * <li>dont't enlarge image 885 * <li>reduce image to fit in target size (if required) 886 * <li>keep image aspect ratio / proportions intact 887 * <li>best fit into target width / height _OR_ width / maxHeight _OR_ maxWidth / height 888 * <li>scaled image will not be padded or cropped, so target size is likely not the exact requested size</ul></dd> 889 * 890 * <dt>6: Crop around point: Use exact pixels</dt><dd><ul> 891 * <li>This type only applies for image crop operations (full crop parameters must be provided). 892 * <li>In this case the crop coordinates <code>x, y</code> are treated as a point in the middle of <code>width, height</code>. 893 * <li>With this type, the pixels from the source image are used 1:1 for the target image.</ul></dd> 894 * 895 * <dt>7: Crop around point: Use pixels for target size, get maximum out of image</dt><dd><ul> 896 * <li>This type only applies for image crop operations (full crop parameters must be provided). 897 * <li>In this case the crop coordinates <code>x, y</code> are treated as a point in the middle of <code>width, height</code>. 898 * <li>With this type, as much as possible from the source image is fitted in the target image size.</ul></dd> 899 * 900 * <dt>8: Focal point mode.</dt> 901 * <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 902 * image, then select the largest region of the aspect ratio defined by w/h in the cropped image containing the focal point, and finally 903 * scale that region to size w x h.</p> 904 * 905 * @return the type 906 */ 907 public int getType() { 908 909 return m_type; 910 } 911 912 /** 913 * Returns the width.<p> 914 * 915 * @return the width 916 */ 917 public int getWidth() { 918 919 return m_width; 920 } 921 922 /** 923 * Returns a new image scaler that is a width based down scale from the size of <code>this</code> scaler 924 * to the given scaler size.<p> 925 * 926 * If no down scale from this to the given scaler is required because the width of <code>this</code> 927 * scaler is not larger than the target width, then the image dimensions of <code>this</code> scaler 928 * are unchanged in the result scaler. No up scaling is done!<p> 929 * 930 * @param downScaler the image scaler that holds the down scaled target image dimensions 931 * 932 * @return a new image scaler that is a down scale from the size of <code>this</code> scaler 933 * to the given target scaler size 934 */ 935 public CmsImageScaler getWidthScaler(CmsImageScaler downScaler) { 936 937 int width = downScaler.getWidth(); 938 int height; 939 940 if (getWidth() > width) { 941 // width is too large, re-calculate height 942 float scale = (float)width / (float)getWidth(); 943 height = Math.round(getHeight() * scale); 944 } else { 945 // width is ok 946 width = getWidth(); 947 height = getHeight(); 948 } 949 950 // now create and initialize the result scaler 951 return new CmsImageScaler(downScaler, width, height); 952 } 953 954 /** 955 * @see java.lang.Object#hashCode() 956 */ 957 @Override 958 public int hashCode() { 959 960 return toString().hashCode(); 961 } 962 963 /** 964 * Returns <code>true</code> if all required parameters for image cropping are available.<p> 965 * 966 * Required parameters are <code>"cx","cy"</code> (x, y start coordinate), 967 * and <code>"ch","cw"</code> (crop height and width).<p> 968 * 969 * @return <code>true</code> if all required cropping parameters are available 970 */ 971 public boolean isCropping() { 972 973 return (m_cropX >= 0) && (m_cropY >= 0) && (m_cropHeight > 0) && (m_cropWidth > 0); 974 } 975 976 /** 977 * Returns <code>true</code> if this image scaler must be down scaled when compared to the 978 * given "down scale" image scaler.<p> 979 * 980 * If either <code>this</code> scaler or the given <code>downScaler</code> is invalid according to 981 * {@link #isValid()}, then <code>false</code> is returned.<p> 982 * 983 * The use case: <code>this</code> scaler represents an image (that is contains width and height of 984 * an image). The <code>downScaler</code> represents the maximum wanted image. The scalers 985 * are compared and if the image represented by <code>this</code> scaler is too large, 986 * <code>true</code> is returned. Image orientation is ignored, so for example an image with 600x800 pixel 987 * will NOT be down scaled if the target size is 800x600 but kept unchanged.<p> 988 * 989 * @param downScaler the down scaler to compare this image scaler with 990 * 991 * @return <code>true</code> if this image scaler must be down scaled when compared to the 992 * given "down scale" image scaler 993 */ 994 public boolean isDownScaleRequired(CmsImageScaler downScaler) { 995 996 if ((downScaler == null) || !isValid() || !downScaler.isValid()) { 997 // one of the scalers is invalid 998 return false; 999 } 1000 1001 if (getPixelCount() < (downScaler.getPixelCount() / 2)) { 1002 // the image has much less pixels then the target, so don't downscale 1003 return false; 1004 } 1005 1006 int downWidth = downScaler.getWidth(); 1007 int downHeight = downScaler.getHeight(); 1008 if (downHeight > downWidth) { 1009 // normalize image orientation - the width should always be the large side 1010 downWidth = downHeight; 1011 downHeight = downScaler.getWidth(); 1012 } 1013 int height = getHeight(); 1014 int width = getWidth(); 1015 if (height > width) { 1016 // normalize image orientation - the width should always be the large side 1017 width = height; 1018 height = getWidth(); 1019 } 1020 1021 return (width > downWidth) || (height > downHeight); 1022 } 1023 1024 /** 1025 * Returns <code>true</code> if the image scaler was 1026 * only used to read image properties from the VFS. 1027 * 1028 * @return <code>true</code> if the image scaler was 1029 * only used to read image properties from the VFS 1030 */ 1031 public boolean isOriginalScaler() { 1032 1033 return m_isOriginalScaler; 1034 } 1035 1036 /** 1037 * Returns <code>true</code> if all required parameters are available.<p> 1038 * 1039 * Required parameters are <code>"h"</code> (height), and <code>"w"</code> (width).<p> 1040 * 1041 * @return <code>true</code> if all required parameters are available 1042 */ 1043 public boolean isValid() { 1044 1045 return (m_width > 0) && (m_height > 0); 1046 } 1047 1048 /** 1049 * Parses the given parameters and sets the internal scaler variables accordingly.<p> 1050 * 1051 * The parameter String must have a format like <code>"h:100,w:200,t:1"</code>, 1052 * that is a comma separated list of attributes followed by a colon ":", followed by a value. 1053 * As possible attributes, use the constants from this class that start with <code>SCALE_PARAM</Code> 1054 * for example {@link #SCALE_PARAM_HEIGHT} or {@link #SCALE_PARAM_WIDTH}.<p> 1055 * 1056 * @param parameters the parameters to parse 1057 */ 1058 public void parseParameters(String parameters) { 1059 1060 m_width = -1; 1061 m_height = -1; 1062 m_position = 0; 1063 m_type = 0; 1064 m_color = Simapi.COLOR_TRANSPARENT; 1065 m_cropX = -1; 1066 m_cropY = -1; 1067 m_cropWidth = -1; 1068 m_cropHeight = -1; 1069 1070 List<String> tokens = CmsStringUtil.splitAsList(parameters, ','); 1071 Iterator<String> it = tokens.iterator(); 1072 String k; 1073 String v; 1074 while (it.hasNext()) { 1075 String t = it.next(); 1076 // extract key and value 1077 k = null; 1078 v = null; 1079 int idx = t.indexOf(':'); 1080 if (idx >= 0) { 1081 k = t.substring(0, idx).trim(); 1082 if (t.length() > idx) { 1083 v = t.substring(idx + 1).trim(); 1084 } 1085 } 1086 if (CmsStringUtil.isNotEmpty(k) && CmsStringUtil.isNotEmpty(v)) { 1087 // key and value are available 1088 if (SCALE_PARAM_HEIGHT.equals(k)) { 1089 // image height 1090 m_height = CmsStringUtil.getIntValue(v, Integer.MIN_VALUE, k); 1091 } else if (SCALE_PARAM_WIDTH.equals(k)) { 1092 // image width 1093 m_width = CmsStringUtil.getIntValue(v, Integer.MIN_VALUE, k); 1094 } else if (SCALE_PARAM_CROP_X.equals(k)) { 1095 // crop x coordinate 1096 m_cropX = CmsStringUtil.getIntValue(v, Integer.MIN_VALUE, k); 1097 } else if (SCALE_PARAM_CROP_Y.equals(k)) { 1098 // crop y coordinate 1099 m_cropY = CmsStringUtil.getIntValue(v, Integer.MIN_VALUE, k); 1100 } else if (SCALE_PARAM_CROP_WIDTH.equals(k)) { 1101 // crop width 1102 m_cropWidth = CmsStringUtil.getIntValue(v, Integer.MIN_VALUE, k); 1103 } else if (SCALE_PARAM_CROP_HEIGHT.equals(k)) { 1104 // crop height 1105 m_cropHeight = CmsStringUtil.getIntValue(v, Integer.MIN_VALUE, k); 1106 } else if (SCALE_PARAM_TYPE.equals(k)) { 1107 // scaling type 1108 setType(CmsStringUtil.getIntValue(v, -1, CmsImageScaler.SCALE_PARAM_TYPE)); 1109 } else if (SCALE_PARAM_COLOR.equals(k)) { 1110 // image background color 1111 setColor(v); 1112 } else if (SCALE_PARAM_POS.equals(k)) { 1113 // image position (depends on scale type) 1114 setPosition(CmsStringUtil.getIntValue(v, -1, CmsImageScaler.SCALE_PARAM_POS)); 1115 } else if (SCALE_PARAM_QUALITY.equals(k)) { 1116 // image position (depends on scale type) 1117 setQuality(CmsStringUtil.getIntValue(v, 0, k)); 1118 } else if (SCALE_PARAM_RENDERMODE.equals(k)) { 1119 // image position (depends on scale type) 1120 setRenderMode(CmsStringUtil.getIntValue(v, 0, k)); 1121 } else if (SCALE_PARAM_FILTER.equals(k)) { 1122 // image filters to apply 1123 setFilters(v); 1124 } else { 1125 if (LOG.isDebugEnabled()) { 1126 LOG.debug(Messages.get().getBundle().key(Messages.ERR_INVALID_IMAGE_SCALE_PARAMS_2, k, v)); 1127 } 1128 } 1129 } else { 1130 if (LOG.isDebugEnabled()) { 1131 LOG.debug(Messages.get().getBundle().key(Messages.ERR_INVALID_IMAGE_SCALE_PARAMS_2, k, v)); 1132 } 1133 } 1134 } 1135 // initialize image crop area 1136 initCropArea(); 1137 } 1138 1139 /** 1140 * Returns a scaled version of the given image byte content according this image scalers parameters.<p> 1141 * 1142 * @param content the image byte content to scale 1143 * @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 1144 * @param rootPath the root path of the image file in the VFS 1145 * 1146 * @return a scaled version of the given image byte content according to the provided scaler parameters 1147 */ 1148 public byte[] scaleImage(byte[] content, BufferedImage image, String rootPath) { 1149 1150 byte[] result = content; 1151 // flag for processed image 1152 boolean imageProcessed = false; 1153 // initialize image crop area 1154 initCropArea(); 1155 1156 RenderSettings renderSettings; 1157 if ((m_renderMode == 0) && (m_quality == 0)) { 1158 // use default render mode and quality 1159 renderSettings = new RenderSettings(Simapi.RENDER_QUALITY); 1160 } else { 1161 // use special render mode and/or quality 1162 renderSettings = new RenderSettings(m_renderMode); 1163 if (m_quality != 0) { 1164 renderSettings.setCompressionQuality(m_quality / 100f); 1165 } 1166 } 1167 // set max blur size 1168 renderSettings.setMaximumBlurSize(m_maxBlurSize); 1169 // new create the scaler 1170 Simapi scaler = new Simapi(renderSettings); 1171 // calculate a valid image type supported by the imaging library (e.g. "JPEG", "GIF") 1172 String imageType = Simapi.getImageType(rootPath); 1173 if (imageType == null) { 1174 // no type given, maybe the name got mixed up 1175 String mimeType = OpenCms.getResourceManager().getMimeType(rootPath, null, null); 1176 // check if this is another known MIME type, if so DONT use it (images should not be named *.pdf) 1177 if (mimeType == null) { 1178 // no MIME type found, use JPEG format to write images to the cache 1179 imageType = Simapi.TYPE_JPEG; 1180 } 1181 } 1182 if (imageType == null) { 1183 // unknown type, unable to scale the image 1184 if (LOG.isDebugEnabled()) { 1185 LOG.debug(Messages.get().getBundle().key(Messages.ERR_UNABLE_TO_SCALE_IMAGE_2, rootPath, toString())); 1186 } 1187 return result; 1188 } 1189 try { 1190 if (image == null) { 1191 image = Simapi.read(content); 1192 } 1193 1194 if (isCropping()) { 1195 // check if the crop width / height are not larger then the source image 1196 if ((getType() == 0) && ((m_cropHeight > image.getHeight()) || (m_cropWidth > image.getWidth()))) { 1197 // crop height / width is outside of image - return image unchanged 1198 return result; 1199 } 1200 } 1201 1202 Color color = getColor(); 1203 1204 if (!m_filters.isEmpty()) { 1205 Iterator<String> i = m_filters.iterator(); 1206 while (i.hasNext()) { 1207 String filter = i.next(); 1208 if (FILTER_GRAYSCALE.equals(filter)) { 1209 // add a gray scale filter 1210 GrayscaleFilter grayscaleFilter = new GrayscaleFilter(); 1211 renderSettings.addImageFilter(grayscaleFilter); 1212 } else if (FILTER_SHADOW.equals(filter)) { 1213 // add a drop shadow filter 1214 ShadowFilter shadowFilter = new ShadowFilter(); 1215 shadowFilter.setXOffset(5); 1216 shadowFilter.setYOffset(5); 1217 shadowFilter.setOpacity(192); 1218 shadowFilter.setBackgroundColor(color.getRGB()); 1219 color = Simapi.COLOR_TRANSPARENT; 1220 renderSettings.setTransparentReplaceColor(Simapi.COLOR_TRANSPARENT); 1221 renderSettings.addImageFilter(shadowFilter); 1222 } 1223 } 1224 } 1225 1226 if (isCropping()) { 1227 if ((getType() == 8) && (m_focalPoint != null)) { 1228 image = scaler.cropToSize( 1229 image, 1230 m_cropX, 1231 m_cropY, 1232 m_cropWidth, 1233 m_cropHeight, 1234 m_cropWidth, 1235 m_cropHeight, 1236 color); 1237 // Find the biggest scaling factor which, when applied to a rectangle of dimensions m_width x m_height, 1238 // would allow the resulting rectangle to still fit inside a rectangle of dimensions m_cropWidth x m_cropHeight 1239 // (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 1240 // vice versa). 1241 double scaling = Math.min((1.0 * m_cropWidth) / m_width, (1.0 * m_cropHeight) / m_height); 1242 int relW = (int)(scaling * m_width); 1243 int relH = (int)(scaling * m_height); 1244 // the focal point's coordinates are in the uncropped image's coordinate system, so we have to subtract cx/cy 1245 int relX = (int)(m_focalPoint.getX() - m_cropX); 1246 int relY = (int)(m_focalPoint.getY() - m_cropY); 1247 image = scaler.cropPointToSize(image, relX, relY, false, relW, relH); 1248 if ((m_width != relW) || (m_height != relH)) { 1249 image = scaler.scale(image, m_width, m_height); 1250 } 1251 } else if ((getType() == 6) || (getType() == 7)) { 1252 // image crop operation around point 1253 image = scaler.cropPointToSize(image, m_cropX, m_cropY, getType() == 6, m_cropWidth, m_cropHeight); 1254 } else { 1255 // image crop operation 1256 image = scaler.cropToSize( 1257 image, 1258 m_cropX, 1259 m_cropY, 1260 m_cropWidth, 1261 m_cropHeight, 1262 getWidth(), 1263 getHeight(), 1264 color); 1265 } 1266 1267 imageProcessed = true; 1268 } else { 1269 // only rescale the image, if the width and height are different to the target size 1270 int imageWidth = image.getWidth(); 1271 int imageHeight = image.getHeight(); 1272 1273 // image rescale operation 1274 switch (getType()) { 1275 // select the "right" method of scaling according to the "t" parameter 1276 case 1: 1277 // thumbnail generation mode (like 0 but no image enlargement) 1278 image = scaler.resize(image, getWidth(), getHeight(), color, getPosition(), false); 1279 imageProcessed = true; 1280 break; 1281 case 2: 1282 // scale to exact target size, crop what does not fit 1283 if (((imageWidth != getWidth()) || (imageHeight != getHeight()))) { 1284 image = scaler.resize(image, getWidth(), getHeight(), getPosition()); 1285 imageProcessed = true; 1286 } 1287 break; 1288 case 3: 1289 // scale and keep image proportions, target size variable 1290 if (((imageWidth != getWidth()) || (imageHeight != getHeight()))) { 1291 image = scaler.resize(image, getWidth(), getHeight(), true); 1292 imageProcessed = true; 1293 } 1294 break; 1295 case 4: 1296 // don't keep image proportions, use exact target size 1297 if (((imageWidth != getWidth()) || (imageHeight != getHeight()))) { 1298 image = scaler.resize(image, getWidth(), getHeight(), false); 1299 imageProcessed = true; 1300 } 1301 break; 1302 case 5: 1303 // scale and keep image proportions, target size variable, include maxWidth / maxHeight option 1304 // image proportions have already been calculated so should not be a problem, use 1305 // 'false' to make sure image size exactly matches height and width attributes of generated tag 1306 if (((imageWidth != getWidth()) || (imageHeight != getHeight()))) { 1307 image = scaler.resize(image, getWidth(), getHeight(), false); 1308 imageProcessed = true; 1309 } 1310 break; 1311 default: 1312 // scale to exact target size with background padding 1313 image = scaler.resize(image, getWidth(), getHeight(), color, getPosition(), true); 1314 imageProcessed = true; 1315 } 1316 1317 } 1318 1319 if (!m_filters.isEmpty()) { 1320 Rectangle targetSize = scaler.applyFilterDimensions(getWidth(), getHeight()); 1321 image = scaler.resize( 1322 image, 1323 (int)targetSize.getWidth(), 1324 (int)targetSize.getHeight(), 1325 Simapi.COLOR_TRANSPARENT, 1326 Simapi.POS_CENTER); 1327 image = scaler.applyFilters(image); 1328 imageProcessed = true; 1329 } 1330 1331 // get the byte result for the scaled image if some changes have been made. 1332 // otherwiese use the original image 1333 if (imageProcessed) { 1334 result = scaler.getBytes(image, imageType); 1335 } 1336 } catch (Exception e) { 1337 if (LOG.isDebugEnabled()) { 1338 LOG.debug( 1339 Messages.get().getBundle().key(Messages.ERR_UNABLE_TO_SCALE_IMAGE_2, rootPath, toString()), 1340 e); 1341 } 1342 } 1343 return result; 1344 } 1345 1346 /** 1347 * Returns a scaled version of the given image byte content according this image scalers parameters.<p> 1348 * 1349 * @param content the image byte content to scale 1350 * @param rootPath the root path of the image file in the VFS 1351 * 1352 * @return a scaled version of the given image byte content according to the provided scaler parameters 1353 */ 1354 public byte[] scaleImage(byte[] content, String rootPath) { 1355 1356 return scaleImage(content, (BufferedImage)null, rootPath); 1357 } 1358 1359 /** 1360 * Returns a scaled version of the given image file according this image scalers parameters.<p> 1361 * 1362 * @param file the image file to scale 1363 * 1364 * @return a scaled version of the given image file according to the provided scaler parameters 1365 */ 1366 public byte[] scaleImage(CmsFile file) { 1367 1368 return scaleImage(file.getContents(), file.getRootPath()); 1369 } 1370 1371 /** 1372 * Sets the color.<p> 1373 * 1374 * @param color the color to set 1375 */ 1376 public void setColor(Color color) { 1377 1378 m_color = color; 1379 } 1380 1381 /** 1382 * Sets the color as a String.<p> 1383 * 1384 * @param value the color to set 1385 */ 1386 public void setColor(String value) { 1387 1388 if (COLOR_TRANSPARENT.indexOf(value) == 0) { 1389 setColor(Simapi.COLOR_TRANSPARENT); 1390 } else { 1391 setColor(CmsStringUtil.getColorValue(value, Simapi.COLOR_TRANSPARENT, SCALE_PARAM_COLOR)); 1392 } 1393 } 1394 1395 /** 1396 * Sets the image crop area.<p> 1397 * 1398 * @param x the x coordinate for the crop 1399 * @param y the y coordinate for the crop 1400 * @param width the crop width 1401 * @param height the crop height 1402 */ 1403 public void setCropArea(int x, int y, int width, int height) { 1404 1405 m_cropX = x; 1406 m_cropY = y; 1407 m_cropWidth = width; 1408 m_cropHeight = height; 1409 } 1410 1411 /** 1412 * Sets the list of filters as a String.<p> 1413 * 1414 * @param value the list of filters to set 1415 */ 1416 public void setFilters(String value) { 1417 1418 m_filters = new ArrayList<String>(); 1419 List<String> filters = CmsStringUtil.splitAsList(value, ':'); 1420 Iterator<String> i = filters.iterator(); 1421 while (i.hasNext()) { 1422 String filter = i.next(); 1423 filter = filter.trim().toLowerCase(); 1424 Iterator<String> j = FILTERS.iterator(); 1425 while (j.hasNext()) { 1426 String candidate = j.next(); 1427 if (candidate.startsWith(filter)) { 1428 // found a matching filter 1429 addFilter(candidate); 1430 break; 1431 } 1432 } 1433 } 1434 } 1435 1436 /** 1437 * Sets the focal point.<p> 1438 * 1439 * @param point the new value for the focal point 1440 */ 1441 public void setFocalPoint(CmsPoint point) { 1442 1443 m_focalPoint = point; 1444 } 1445 1446 /** 1447 * Sets the height.<p> 1448 * 1449 * @param height the height to set 1450 */ 1451 public void setHeight(int height) { 1452 1453 m_height = height; 1454 } 1455 1456 /** 1457 * Sets the maximum image size (width * height) to apply image blurring when downscaling images.<p> 1458 * 1459 * @param maxBlurSize the maximum image blur size to set 1460 * 1461 * @see #getMaxBlurSize() for a more detailed description about this parameter 1462 */ 1463 public void setMaxBlurSize(int maxBlurSize) { 1464 1465 m_maxBlurSize = maxBlurSize; 1466 } 1467 1468 /** 1469 * Sets the maximum target height (for scale type '5').<p> 1470 * 1471 * @param maxHeight the maximum target height to set 1472 */ 1473 public void setMaxHeight(int maxHeight) { 1474 1475 m_maxHeight = maxHeight; 1476 } 1477 1478 /** 1479 * Sets the maximum target width (for scale type '5').<p> 1480 * 1481 * @param maxWidth the maximum target width to set 1482 */ 1483 public void setMaxWidth(int maxWidth) { 1484 1485 m_maxWidth = maxWidth; 1486 } 1487 1488 /** 1489 * Sets the scale position.<p> 1490 * 1491 * @param position the position to set 1492 */ 1493 public void setPosition(int position) { 1494 1495 switch (position) { 1496 case Simapi.POS_DOWN_LEFT: 1497 case Simapi.POS_DOWN_RIGHT: 1498 case Simapi.POS_STRAIGHT_DOWN: 1499 case Simapi.POS_STRAIGHT_LEFT: 1500 case Simapi.POS_STRAIGHT_RIGHT: 1501 case Simapi.POS_STRAIGHT_UP: 1502 case Simapi.POS_UP_LEFT: 1503 case Simapi.POS_UP_RIGHT: 1504 // position is fine 1505 m_position = position; 1506 break; 1507 default: 1508 m_position = Simapi.POS_CENTER; 1509 } 1510 } 1511 1512 /** 1513 * Sets the image saving quality in percent.<p> 1514 * 1515 * @param quality the image saving quality (in percent) to set 1516 */ 1517 public void setQuality(int quality) { 1518 1519 if (quality < 0) { 1520 m_quality = 0; 1521 } else if (quality > 100) { 1522 m_quality = 100; 1523 } else { 1524 m_quality = quality; 1525 } 1526 } 1527 1528 /** 1529 * Sets the image rendering mode constant.<p> 1530 * 1531 * @param renderMode the image rendering mode to set 1532 * 1533 * @see #getRenderMode() for a list of allowed values for the rendering mode 1534 */ 1535 public void setRenderMode(int renderMode) { 1536 1537 if ((renderMode < Simapi.RENDER_QUALITY) || (renderMode > Simapi.RENDER_SPEED)) { 1538 renderMode = Simapi.RENDER_QUALITY; 1539 } 1540 m_renderMode = renderMode; 1541 } 1542 1543 /** 1544 * Sets the scale type.<p> 1545 * 1546 * @param type the scale type to set 1547 * 1548 * @see #getType() for a detailed description of the possible values for the type 1549 */ 1550 public void setType(int type) { 1551 1552 if ((type < 0) || (type > 8)) { 1553 // invalid type, use 0 1554 m_type = 0; 1555 } else { 1556 m_type = type; 1557 } 1558 } 1559 1560 /** 1561 * Sets the width.<p> 1562 * 1563 * @param width the width to set 1564 */ 1565 public void setWidth(int width) { 1566 1567 m_width = width; 1568 } 1569 1570 /** 1571 * Creates a request parameter configured with the values from this image scaler, also 1572 * appends a <code>'?'</code> char as a prefix so that this may be directly appended to an image URL.<p> 1573 * 1574 * This can be appended to an image request in order to apply image scaling parameters.<p> 1575 * 1576 * @return a request parameter configured with the values from this image scaler 1577 */ 1578 public String toRequestParam() { 1579 1580 StringBuffer result = new StringBuffer(128); 1581 result.append('?'); 1582 result.append(PARAM_SCALE); 1583 result.append('='); 1584 result.append(toString()); 1585 1586 return result.toString(); 1587 } 1588 1589 /** 1590 * @see java.lang.Object#toString() 1591 */ 1592 @Override 1593 public String toString() { 1594 1595 if (m_scaleParameters != null) { 1596 return m_scaleParameters; 1597 } 1598 1599 StringBuffer result = new StringBuffer(64); 1600 if (isCropping()) { 1601 result.append(CmsImageScaler.SCALE_PARAM_CROP_X); 1602 result.append(':'); 1603 result.append(m_cropX); 1604 result.append(','); 1605 result.append(CmsImageScaler.SCALE_PARAM_CROP_Y); 1606 result.append(':'); 1607 result.append(m_cropY); 1608 result.append(','); 1609 result.append(CmsImageScaler.SCALE_PARAM_CROP_WIDTH); 1610 result.append(':'); 1611 result.append(m_cropWidth); 1612 result.append(','); 1613 result.append(CmsImageScaler.SCALE_PARAM_CROP_HEIGHT); 1614 result.append(':'); 1615 result.append(m_cropHeight); 1616 } 1617 if (!isCropping() || ((m_width != m_cropWidth) || (m_height != m_cropHeight))) { 1618 if (isCropping()) { 1619 result.append(','); 1620 } 1621 result.append(CmsImageScaler.SCALE_PARAM_WIDTH); 1622 result.append(':'); 1623 result.append(m_width); 1624 result.append(','); 1625 result.append(CmsImageScaler.SCALE_PARAM_HEIGHT); 1626 result.append(':'); 1627 result.append(m_height); 1628 } 1629 if (m_type > 0) { 1630 result.append(','); 1631 result.append(CmsImageScaler.SCALE_PARAM_TYPE); 1632 result.append(':'); 1633 result.append(m_type); 1634 } 1635 if (m_position > 0) { 1636 result.append(','); 1637 result.append(CmsImageScaler.SCALE_PARAM_POS); 1638 result.append(':'); 1639 result.append(m_position); 1640 } 1641 if (m_color != Color.WHITE) { 1642 result.append(','); 1643 result.append(CmsImageScaler.SCALE_PARAM_COLOR); 1644 result.append(':'); 1645 result.append(getColorString()); 1646 } 1647 if (m_quality > 0) { 1648 result.append(','); 1649 result.append(CmsImageScaler.SCALE_PARAM_QUALITY); 1650 result.append(':'); 1651 result.append(m_quality); 1652 } 1653 if (m_renderMode > 0) { 1654 result.append(','); 1655 result.append(CmsImageScaler.SCALE_PARAM_RENDERMODE); 1656 result.append(':'); 1657 result.append(m_renderMode); 1658 } 1659 if (!m_filters.isEmpty()) { 1660 result.append(','); 1661 result.append(CmsImageScaler.SCALE_PARAM_FILTER); 1662 result.append(':'); 1663 result.append(getFiltersString()); 1664 } 1665 m_scaleParameters = result.toString(); 1666 return m_scaleParameters; 1667 } 1668 1669 /** 1670 * Calculate the closest match of the given base float with the list of others.<p> 1671 * 1672 * @param base the base float to compare the other with 1673 * @param others the list of floats to compate to the base 1674 * 1675 * @return the array index of the closest match 1676 */ 1677 private int calculateClosest(float base, float[] others) { 1678 1679 int result = -1; 1680 float bestMatch = Float.MAX_VALUE; 1681 for (int count = 0; count < others.length; count++) { 1682 float difference = Math.abs(base - others[count]); 1683 if (difference < bestMatch) { 1684 // new best match found 1685 bestMatch = difference; 1686 result = count; 1687 } 1688 if (bestMatch == 0f) { 1689 // it does not get better then this 1690 break; 1691 } 1692 } 1693 return result; 1694 } 1695 1696 private Dimension getDimensionsWithSimapi(byte[] content) throws Exception { 1697 1698 BufferedImage image = Simapi.read(content); 1699 return new Dimension(image.getWidth(), image.getHeight()); 1700 } 1701 1702 /** 1703 * Initializes the members with the default values.<p> 1704 */ 1705 private void init() { 1706 1707 m_height = -1; 1708 m_width = -1; 1709 m_maxHeight = -1; 1710 m_maxWidth = -1; 1711 m_type = 0; 1712 m_position = 0; 1713 m_renderMode = 0; 1714 m_quality = 0; 1715 m_cropX = -1; 1716 m_cropY = -1; 1717 m_cropHeight = -1; 1718 m_cropWidth = -1; 1719 m_color = Color.WHITE; 1720 m_filters = new ArrayList<String>(); 1721 m_maxBlurSize = CmsImageLoader.getMaxBlurSize(); 1722 m_isOriginalScaler = false; 1723 } 1724 1725 /** 1726 * Initializes the crop area setting.<p> 1727 * 1728 * Only if all 4 required parameters have been set, the crop area is set accordingly. 1729 * Moreover, it is not required to specify the target image width and height when using crop, 1730 * because these parameters can be calculated from the crop area.<p> 1731 * 1732 * Scale type 6 and 7 are used for a 'crop around point' operation, see {@link #getType()} for a description.<p> 1733 */ 1734 private void initCropArea() { 1735 1736 if (isCropping()) { 1737 // crop area is set up correctly 1738 // adjust target image height or width if required 1739 if (m_width < 0) { 1740 m_width = m_cropWidth; 1741 } 1742 if (m_height < 0) { 1743 m_height = m_cropHeight; 1744 } 1745 if ((getType() != 6) && (getType() != 7) && (getType() != 8)) { 1746 // cropping type can only be 6 or 7 (point cropping) 1747 // all other values with cropping coordinates are invalid 1748 setType(0); 1749 } 1750 } 1751 } 1752 1753 /** 1754 * Copies all values from the given scaler into this scaler.<p> 1755 * 1756 * @param source the source scaler 1757 */ 1758 private void initValuesFrom(CmsImageScaler source) { 1759 1760 m_color = source.m_color; 1761 m_cropHeight = source.m_cropHeight; 1762 m_cropWidth = source.m_cropWidth; 1763 m_cropX = source.m_cropX; 1764 m_cropY = source.m_cropY; 1765 m_filters = new ArrayList<String>(source.m_filters); 1766 m_focalPoint = source.m_focalPoint; 1767 m_height = source.m_height; 1768 m_isOriginalScaler = source.m_isOriginalScaler; 1769 m_maxBlurSize = source.m_maxBlurSize; 1770 m_position = source.m_position; 1771 m_quality = source.m_quality; 1772 m_renderMode = source.m_renderMode; 1773 m_type = source.m_type; 1774 m_width = source.m_width; 1775 1776 } 1777}