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