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