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