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