All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opencms.loader.CmsImageScaler Maven / Gradle / Ivy

Go to download

OpenCms is an enterprise-ready, easy to use website content management system based on Java and XML technology. Offering a complete set of features, OpenCms helps content managers worldwide to create and maintain beautiful websites fast and efficiently.

There is a newer version: 17.0
Show newest version
/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.loader;

import com.alkacon.simapi.RenderSettings;
import com.alkacon.simapi.Simapi;
import com.alkacon.simapi.CmykJpegReader.ByteArrayImageInputStream;
import com.alkacon.simapi.filter.GrayscaleFilter;
import com.alkacon.simapi.filter.ShadowFilter;

import org.opencms.ade.galleries.CmsPreviewService;
import org.opencms.ade.galleries.shared.CmsPoint;
import org.opencms.file.CmsFile;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsProperty;
import org.opencms.file.CmsPropertyDefinition;
import org.opencms.file.CmsResource;
import org.opencms.main.CmsLog;
import org.opencms.main.OpenCms;
import org.opencms.util.CmsStringUtil;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;

/**
 * Creates scaled images, acting as it's own parameter container.

* * @since 6.2.0 */ public class CmsImageScaler { /** The name of the transparent color (for the background image). */ public static final String COLOR_TRANSPARENT = "transparent"; /** The name of the grayscale image filter. */ public static final String FILTER_GRAYSCALE = "grayscale"; /** The name of the shadow image filter. */ public static final String FILTER_SHADOW = "shadow"; /** The supported image filter names. */ public static final List FILTERS = Arrays.asList(new String[] {FILTER_GRAYSCALE, FILTER_SHADOW}); /** The (optional) parameter used for sending the scale information of an image in the http request. */ public static final String PARAM_SCALE = "__scale"; /** The default maximum image size (width * height) to apply image blurring when down scaling (setting this to high may case "out of memory" errors). */ public static final int SCALE_DEFAULT_MAX_BLUR_SIZE = 2500 * 2500; /** The default maximum image size (width or height) to allow when up or down scaling an image using request parameters. */ public static final int SCALE_DEFAULT_MAX_SIZE = 2500; /** The scaler parameter to indicate the requested image background color (if required). */ public static final String SCALE_PARAM_COLOR = "c"; /** The scaler parameter to indicate crop height. */ public static final String SCALE_PARAM_CROP_HEIGHT = "ch"; /** The scaler parameter to indicate crop width. */ public static final String SCALE_PARAM_CROP_WIDTH = "cw"; /** The scaler parameter to indicate crop X coordinate. */ public static final String SCALE_PARAM_CROP_X = "cx"; /** The scaler parameter to indicate crop Y coordinate. */ public static final String SCALE_PARAM_CROP_Y = "cy"; /** The scaler parameter to indicate the requested image filter. */ public static final String SCALE_PARAM_FILTER = "f"; /** The scaler parameter to indicate the requested image height. */ public static final String SCALE_PARAM_HEIGHT = "h"; /** The scaler parameter to indicate the requested image position (if required). */ public static final String SCALE_PARAM_POS = "p"; /** The scaler parameter to indicate to requested image save quality in percent (if applicable, for example used with JPEG images). */ public static final String SCALE_PARAM_QUALITY = "q"; /** The scaler parameter to indicate to requested {@link java.awt.RenderingHints} settings. */ public static final String SCALE_PARAM_RENDERMODE = "r"; /** The scaler parameter to indicate the requested scale type. */ public static final String SCALE_PARAM_TYPE = "t"; /** The scaler parameter to indicate the requested image width. */ public static final String SCALE_PARAM_WIDTH = "w"; /** The log object for this class. */ protected static final Log LOG = CmsLog.getLog(CmsImageScaler.class); /** The target background color (optional). */ private Color m_color; /** The height for image cropping. */ private int m_cropHeight; /** The width for image cropping. */ private int m_cropWidth; /** The x coordinate for image cropping. */ private int m_cropX; /** The y coordinate for image cropping. */ private int m_cropY; /** The list of image filter names (Strings) to apply. */ private List m_filters; /** The focal point. */ private CmsPoint m_focalPoint; /** The target height (required). */ private int m_height; /** Indicates if this image scaler was only used to read the image properties. */ private boolean m_isOriginalScaler; /** The maximum image size (width * height) to apply image blurring when down scaling (setting this to high may case "out of memory" errors). */ private int m_maxBlurSize; /** The maximum target height (for scale type '5'). */ private int m_maxHeight; /** The maximum target width (for scale type '5'). */ private int m_maxWidth; /** The target position (optional). */ private int m_position; /** The target image save quality (if applicable, for example used with JPEG images) (optional). */ private int m_quality; /** The image processing renderings hints constant mode indicator (optional). */ private int m_renderMode; /** The final (parsed and corrected) scale parameters. */ private String m_scaleParameters; /** The target scale type (optional). */ private int m_type; /** The target width (required). */ private int m_width; /** * Creates a new, empty image scaler object.

*/ public CmsImageScaler() { init(); } /** * Creates a new image scaler initialized with the height and width of * the given image contained in the byte array.

* * Please note:The image itself is not stored in the scaler, only the width and * height dimensions of the image. To actually scale an image, you need to use * {@link #scaleImage(CmsFile)}. This constructor is commonly used only * to extract the image dimensions, for example when creating a String value for * the {@link CmsPropertyDefinition#PROPERTY_IMAGE_SIZE} property.

* * In case the byte array can not be decoded to an image, or in case of other errors, * {@link #isValid()} will return false.

* * @param content the image to calculate the dimensions for * @param rootPath the root path of the resource (for error logging) */ public CmsImageScaler(byte[] content, String rootPath) { init(); try { Dimension dim = getImageDimensions(rootPath, content); if (dim != null) { m_width = dim.width; m_height = dim.height; } else { // read the scaled image BufferedImage image = Simapi.read(content); m_height = image.getHeight(); m_width = image.getWidth(); } } catch (Exception e) { // nothing we can do about this, keep the original properties if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.ERR_UNABLE_TO_EXTRACT_IMAGE_SIZE_1, rootPath), e); } // set height / width to default of -1 init(); } } /** * Creates a new image scaler based on the given base scaler and the given width and height.

* * @param base the base scaler to initialize the values with * @param width the width to set for this scaler * @param height the height to set for this scaler */ public CmsImageScaler(CmsImageScaler base, int width, int height) { initValuesFrom(base); setWidth(width); setHeight(height); } /** * Creates a new image scaler by reading the property {@link CmsPropertyDefinition#PROPERTY_IMAGE_SIZE} * from the given resource.

* * In case of any errors reading or parsing the property, * {@link #isValid()} will return false.

* * @param cms the OpenCms user context to use when reading the property * @param res the resource to read the property from */ public CmsImageScaler(CmsObject cms, CmsResource res) { init(); m_isOriginalScaler = true; String sizeValue = null; if ((cms != null) && (res != null)) { try { CmsProperty sizeProp = cms.readPropertyObject(res, CmsPropertyDefinition.PROPERTY_IMAGE_SIZE, false); if (!sizeProp.isNullProperty()) { // parse property value using standard procedures sizeValue = sizeProp.getValue(); } } catch (Exception e) { LOG.debug(e.getMessage(), e); } try { m_focalPoint = CmsPreviewService.readFocalPoint(cms, res); } catch (Exception e) { LOG.debug(e.getLocalizedMessage(), e); } } if (CmsStringUtil.isNotEmpty(sizeValue)) { parseParameters(sizeValue); } } /** * Creates a new image scaler based on the given HTTP request.

* * The maximum scale size is checked in order to prevent DOS attacks. * Without this, it would be possible to request arbitrary huge images with a simple GET request, * which would result in Out-Of-Memory errors if the image is just requested large enough.

* * The maximum blur size is checked since this operation is know to also cause memory issues * with large images. If the original image is larger then this, no blur is applied before * scaling down, which will result in a less optimal but still usable scale result.

* * @param request the HTTP request to read the parameters from * @param maxScaleSize the maximum scale size (width or height) for the image * @param maxBlurSize the maximum size of the image (width * height) to apply blur */ public CmsImageScaler(HttpServletRequest request, int maxScaleSize, int maxBlurSize) { init(); m_maxBlurSize = maxBlurSize; String parameters = request.getParameter(CmsImageScaler.PARAM_SCALE); if (CmsStringUtil.isNotEmpty(parameters)) { parseParameters(parameters); if (isValid()) { // valid parameters, check if scale size is not too big if ((getWidth() > maxScaleSize) || (getHeight() > maxScaleSize)) { // scale size is too big, reset scaler init(); } } } } /** * Creates an image scaler with manually set width and height. * * @param width the width * @param height the height */ public CmsImageScaler(int width, int height) { init(); m_width = width; m_height = height; } /** * Creates a new image scaler based on the given parameter String.

* * @param parameters the scale parameters to use */ public CmsImageScaler(String parameters) { init(); if (CmsStringUtil.isNotEmpty(parameters)) { parseParameters(parameters); } } /** * Calculate the width and height of a source image if scaled inside the given box.

* * @param sourceWidth the width of the source image * @param sourceHeight the height of the source image * @param boxWidth the width of the target box * @param boxHeight the height of the target box * * @return the width [0] and height [1] of the source image if scaled inside the given box */ public static int[] calculateDimension(int sourceWidth, int sourceHeight, int boxWidth, int boxHeight) { int[] result = new int[2]; if ((sourceWidth <= boxWidth) && (sourceHeight <= boxHeight)) { result[0] = sourceWidth; result[1] = sourceHeight; } else { float scaleWidth = (float)boxWidth / (float)sourceWidth; float scaleHeight = (float)boxHeight / (float)sourceHeight; float scale = Math.min(scaleHeight, scaleWidth); result[0] = Math.round(sourceWidth * scale); result[1] = Math.round(sourceHeight * scale); } return result; } /** * Gets image dimensions for given file * @param imgFile image file * @return dimensions of image * @throws IOException if the file is not a known image */ public static Dimension getImageDimensions(String path, byte[] content) throws IOException { String name = CmsResource.getName(path); int pos = name.lastIndexOf("."); if (pos == -1) { LOG.warn("Couldn't determine image dimensions for " + path); return null; } String suffix = name.substring(pos + 1); Iterator iter = ImageIO.getImageReadersBySuffix(suffix); while (iter.hasNext()) { ImageReader reader = iter.next(); try { ByteArrayImageInputStream stream = new ByteArrayImageInputStream(content); reader.setInput(stream); int minIndex = reader.getMinIndex(); int width = reader.getWidth(minIndex); int height = reader.getHeight(minIndex); return new Dimension(width, height); } catch (IOException e) { LOG.warn("Problem determining image size for " + path + ": " + e.getLocalizedMessage(), e); } finally { reader.dispose(); } } LOG.warn("Couldn't determine image dimensions for " + path); return null; } /** * Adds a filter name to the list of filters that should be applied to the image.

* * @param filter the filter name to add */ public void addFilter(String filter) { if (CmsStringUtil.isNotEmpty(filter)) { filter = filter.trim().toLowerCase(); if (FILTERS.contains(filter)) { m_filters.add(filter); } } } /** * @see java.lang.Object#clone() */ @Override public Object clone() { CmsImageScaler clone = new CmsImageScaler(); clone.initValuesFrom(this); return clone; } /** * Returns the color.

* * @return the color */ public Color getColor() { return m_color; } /** * Returns the color as a String.

* * @return the color as a String */ public String getColorString() { StringBuffer result = new StringBuffer(); if (m_color == Simapi.COLOR_TRANSPARENT) { result.append(COLOR_TRANSPARENT); } else { if (m_color.getRed() < 16) { result.append('0'); } result.append(Integer.toString(m_color.getRed(), 16)); if (m_color.getGreen() < 16) { result.append('0'); } result.append(Integer.toString(m_color.getGreen(), 16)); if (m_color.getBlue() < 16) { result.append('0'); } result.append(Integer.toString(m_color.getBlue(), 16)); } return result.toString(); } /** * Returns the crop area height.

* * Use {@link #setCropArea(int, int, int, int)} to set this value.

* * @return the crop area height */ public int getCropHeight() { return m_cropHeight; } /** * Returns a new image scaler that is a cropped rescaler from this cropped scaler * size to the given target scaler size.

* * @param target the image scaler that holds the target image dimensions * * @return a new image scaler that is a cropped rescaler from this cropped scaler * size to the given target scaler size * * @see #getReScaler(CmsImageScaler) * @see #setCropArea(int, int, int, int) */ public CmsImageScaler getCropScaler(CmsImageScaler target) { // first re-scale the image (if required) CmsImageScaler result = getReScaler(target); // now use the crop area from the original result.setCropArea(m_cropX, m_cropY, m_cropWidth, m_cropHeight); return result; } /** * Returns the crop area width.

* * Use {@link #setCropArea(int, int, int, int)} to set this value.

* * @return the crop area width */ public int getCropWidth() { return m_cropWidth; } /** * Returns the crop area X start coordinate.

* * Use {@link #setCropArea(int, int, int, int)} to set this value.

* * @return the crop area X start coordinate */ public int getCropX() { return m_cropX; } /** * Returns the crop area Y start coordinate.

* * Use {@link #setCropArea(int, int, int, int)} to set this value.

* * @return the crop area Y start coordinate */ public int getCropY() { return m_cropY; } /** * Returns a new image scaler that is a down scale from the size of this scaler * to the given scaler size.

* * If no down scale from this to the given scaler is required according to * {@link #isDownScaleRequired(CmsImageScaler)}, then null is returned.

* * @param downScaler the image scaler that holds the down scaled target image dimensions * * @return a new image scaler that is a down scale from the size of this scaler * to the given target scaler size, or null */ public CmsImageScaler getDownScaler(CmsImageScaler downScaler) { if (!isDownScaleRequired(downScaler)) { // no down scaling is required return null; } int downHeight = downScaler.getHeight(); int downWidth = downScaler.getWidth(); int height = getHeight(); int width = getWidth(); if (((height > width) && (downHeight < downWidth)) || ((width > height) && (downWidth < downHeight))) { // adjust orientation downHeight = downWidth; downWidth = downScaler.getHeight(); } if (width > downWidth) { // width is too large, re-calculate height float scale = (float)downWidth / (float)width; downHeight = Math.round(height * scale); } else if (height > downHeight) { // height is too large, re-calculate width float scale = (float)downHeight / (float)height; downWidth = Math.round(width * scale); } else { // something is wrong, don't down scale return null; } // now create and initialize the result scaler return new CmsImageScaler(downScaler, downWidth, downHeight); } /** * Returns the list of image filter names (Strings) to be applied to the image.

* * @return the list of image filter names (Strings) to be applied to the image */ public List getFilters() { return m_filters; } /** * Returns the list of image filter names (Strings) to be applied to the image as a String.

* * @return the list of image filter names (Strings) to be applied to the image as a String */ public String getFiltersString() { StringBuffer result = new StringBuffer(); Iterator i = m_filters.iterator(); while (i.hasNext()) { String filter = i.next(); result.append(filter); if (i.hasNext()) { result.append(':'); } } return result.toString(); } /** * Gets the focal point, or null if it is not set.

* * @return the focal point */ public CmsPoint getFocalPoint() { return m_focalPoint; } /** * Returns the height.

* * @return the height */ public int getHeight() { return m_height; } /** * Returns the image type from the given file name based on the file suffix (extension) * and the available image writers.

* * For example, for the file name "opencms.gif" the type is GIF, for * "opencms.jpg" is is "JPEG" etc.

* * In case the input filename has no suffix, or there is no known image writer for the format defined * by the suffix, null is returned.

* * Any non-null result can be used if an image type input value is required.

* * @param filename the file name to get the type for * * @return the image type from the given file name based on the suffix and the available image writers, * or null if no image writer is available for the format */ public String getImageType(String filename) { return Simapi.getImageType(filename); } /** * Returns the maximum image size (width * height) to apply image blurring when down scaling images.

* * Image blurring is required to achieve the best results for down scale operations when the target image size * is 2 times or more smaller then the original image size. * This parameter controls the maximum size (width * height) of an * image that is blurred before it is down scaled. If the image is larger, no blurring is done. * Image blurring is an expensive operation in both CPU usage and memory consumption. * Setting the blur size to large may case "out of memory" errors.

* * @return the maximum image size (width * height) to apply image blurring when down scaling images */ public int getMaxBlurSize() { return m_maxBlurSize; } /** * Returns the maximum target height (for scale type '5').

* * @return the maximum target height (for scale type '5') */ public int getMaxHeight() { return m_maxHeight; } /** * Returns the maximum target width (for scale type '5').

* * @return the maximum target width (for scale type '5'). */ public int getMaxWidth() { return m_maxWidth; } /** * Returns the image pixel count, that is the image with multiplied by the image height.

* * If this scaler is not valid (see {@link #isValid()}) the result is undefined.

* * @return the image pixel count, that is the image with multiplied by the image height */ public int getPixelCount() { return m_width * m_height; } /** * Returns the position.

* * @return the position */ public int getPosition() { return m_position; } /** * Returns the image saving quality in percent (0 - 100).

* * This is used only if applicable, for example when saving JPEG images.

* * @return the image saving quality in percent */ public int getQuality() { return m_quality; } /** * Returns the image rendering mode constant.

* * Possible values are:

*
{@link Simapi#RENDER_QUALITY} (default)
*
Use best possible image processing - this may be slow sometimes.
* *
{@link Simapi#RENDER_SPEED}
*
Fastest image processing but worse results - use this for thumbnails or where speed is more important then quality.
* *
{@link Simapi#RENDER_MEDIUM}
*
Use default rendering hints from JVM - not recommended since it's almost as slow as the {@link Simapi#RENDER_QUALITY} mode.
* * @return the image rendering mode constant */ public int getRenderMode() { return m_renderMode; } /** * Creates a request parameter configured with the values from this image scaler, also * appends a '?' char as a prefix so that this may be directly appended to an image URL.

* * This can be appended to an image request in order to apply image scaling parameters.

* * @return a request parameter configured with the values from this image scaler * @see #toRequestParam() */ public String getRequestParam() { return toRequestParam(); } /** * Returns a new image scaler that is a rescaler from this scaler * size to the given target scaler size.

* * The height of the target image is calculated in proportion * to the original image width. If the width of the the original image is not known, * the target image width is calculated in proportion to the original image height.

* * @param target the image scaler that holds the target image dimensions * * @return a new image scaler that is a rescaler from the this scaler * size to the given target scaler size */ public CmsImageScaler getReScaler(CmsImageScaler target) { int height = target.getHeight(); int width = target.getWidth(); int type = target.getType(); if (type == 5) { // best fit option without upscale in the provided dimensions if (target.isValid()) { // ensure we have sensible values for maxWidth / minWidth even if one has not been set float maxWidth = target.getMaxWidth() > 0 ? target.getMaxWidth() : height; float maxHeight = target.getMaxHeight() > 0 ? target.getMaxHeight() : width; // calculate the factor of the image and the 3 possible target dimensions float scaleOfImage = (float)getWidth() / (float)getHeight(); float[] scales = new float[3]; scales[0] = (float)width / (float)height; scales[1] = width / maxHeight; scales[2] = maxWidth / height; int useScale = calculateClosest(scaleOfImage, scales); int[] dimensions; switch (useScale) { case 1: dimensions = calculateDimension(getWidth(), getHeight(), width, (int)maxHeight); break; case 2: dimensions = calculateDimension(getWidth(), getHeight(), (int)maxWidth, height); break; case 0: default: dimensions = calculateDimension(getWidth(), getHeight(), width, height); break; } width = dimensions[0]; height = dimensions[1]; } else { // target not valid, switch type to 1 (no upscale) type = 1; } } if (type != 5) { if ((width > 0) && (getWidth() > 0)) { // width is known, calculate height float scale = (float)width / (float)getWidth(); height = Math.round(getHeight() * scale); } else if ((height > 0) && (getHeight() > 0)) { // height is known, calculate width float scale = (float)height / (float)getHeight(); width = Math.round(getWidth() * scale); } else if (isValid() && !target.isValid()) { // scaler is not valid but original is, so use original size of image width = getWidth(); height = getHeight(); } } if ((type == 1) && (!target.isValid())) { // "no upscale" has been requested, only one target dimension was given if ((target.getWidth() > 0) && (getWidth() < width)) { // target width was given, target image should have this width height = getHeight(); } else if ((target.getHeight() > 0) && (getHeight() < height)) { // target height was given, target image should have this height width = getWidth(); } } // now create and initialize the result scaler CmsImageScaler result = new CmsImageScaler(target, width, height); // type may have been switched result.setType(type); return result; } /** * Returns the type.

* * Possible values are:

* *
0 (default): Scale to exact target size with background padding
    *
  • enlarge image to fit in target size (if required) *
  • reduce image to fit in target size (if required) *
  • keep image aspect ratio / proportions intact *
  • fill up with bgcolor to reach exact target size *
  • fit full image inside target size (only applies if reduced)
* *
1: Thumbnail generation mode (like 0 but no image enlargement)
    *
  • dont't enlarge image *
  • reduce image to fit in target size (if required) *
  • keep image aspect ratio / proportions intact *
  • fill up with bgcolor to reach exact target size *
  • fit full image inside target size (only applies if reduced)
* *
2: Scale to exact target size, crop what does not fit
    *
  • enlarge image to fit in target size (if required) *
  • reduce image to fit in target size (if required) *
  • keep image aspect ratio / proportions intact *
  • fit full image inside target size (crop what does not fit)
* *
3: Scale and keep image proportions, target size variable
    *
  • enlarge image to fit in target size (if required) *
  • reduce image to fit in target size (if required) *
  • keep image aspect ratio / proportions intact *
  • scaled image will not be padded or cropped, so target size is likely not the exact requested size
* *
4: Don't keep image proportions, use exact target size
    *
  • enlarge image to fit in target size (if required) *
  • reduce image to fit in target size (if required) *
  • don't keep image aspect ratio / proportions intact *
  • the image will be scaled exactly to the given target size and likely will be loose proportions
*
* *
5: Scale and keep image proportions without enlargement, target size variable with optional max width and height
    *
  • dont't enlarge image *
  • reduce image to fit in target size (if required) *
  • keep image aspect ratio / proportions intact *
  • best fit into target width / height _OR_ width / maxHeight _OR_ maxWidth / height *
  • scaled image will not be padded or cropped, so target size is likely not the exact requested size
* *
6: Crop around point: Use exact pixels
    *
  • This type only applies for image crop operations (full crop parameters must be provided). *
  • In this case the crop coordinates x, y are treated as a point in the middle of width, height. *
  • With this type, the pixels from the source image are used 1:1 for the target image.
* *
7: Crop around point: Use pixels for target size, get maximum out of image
    *
  • This type only applies for image crop operations (full crop parameters must be provided). *
  • In this case the crop coordinates x, y are treated as a point in the middle of width, height. *
  • With this type, as much as possible from the source image is fitted in the target image size.
* *
8: Focal point mode.
*

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 * image, then select the largest region of the aspect ratio defined by w/h in the cropped image containing the focal point, and finally * scale that region to size w x h.

* * @return the type */ public int getType() { return m_type; } /** * Returns the width.

* * @return the width */ public int getWidth() { return m_width; } /** * Returns a new image scaler that is a width based down scale from the size of this scaler * to the given scaler size.

* * If no down scale from this to the given scaler is required because the width of this * scaler is not larger than the target width, then the image dimensions of this scaler * are unchanged in the result scaler. No up scaling is done!

* * @param downScaler the image scaler that holds the down scaled target image dimensions * * @return a new image scaler that is a down scale from the size of this scaler * to the given target scaler size */ public CmsImageScaler getWidthScaler(CmsImageScaler downScaler) { int width = downScaler.getWidth(); int height; if (getWidth() > width) { // width is too large, re-calculate height float scale = (float)width / (float)getWidth(); height = Math.round(getHeight() * scale); } else { // width is ok width = getWidth(); height = getHeight(); } // now create and initialize the result scaler return new CmsImageScaler(downScaler, width, height); } /** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return toString().hashCode(); } /** * Returns true if all required parameters for image cropping are available.

* * Required parameters are "cx","cy" (x, y start coordinate), * and "ch","cw" (crop height and width).

* * @return true if all required cropping parameters are available */ public boolean isCropping() { return (m_cropX >= 0) && (m_cropY >= 0) && (m_cropHeight > 0) && (m_cropWidth > 0); } /** * Returns true if this image scaler must be down scaled when compared to the * given "down scale" image scaler.

* * If either this scaler or the given downScaler is invalid according to * {@link #isValid()}, then false is returned.

* * The use case: this scaler represents an image (that is contains width and height of * an image). The downScaler represents the maximum wanted image. The scalers * are compared and if the image represented by this scaler is too large, * true is returned. Image orientation is ignored, so for example an image with 600x800 pixel * will NOT be down scaled if the target size is 800x600 but kept unchanged.

* * @param downScaler the down scaler to compare this image scaler with * * @return true if this image scaler must be down scaled when compared to the * given "down scale" image scaler */ public boolean isDownScaleRequired(CmsImageScaler downScaler) { if ((downScaler == null) || !isValid() || !downScaler.isValid()) { // one of the scalers is invalid return false; } if (getPixelCount() < (downScaler.getPixelCount() / 2)) { // the image has much less pixels then the target, so don't downscale return false; } int downWidth = downScaler.getWidth(); int downHeight = downScaler.getHeight(); if (downHeight > downWidth) { // normalize image orientation - the width should always be the large side downWidth = downHeight; downHeight = downScaler.getWidth(); } int height = getHeight(); int width = getWidth(); if (height > width) { // normalize image orientation - the width should always be the large side width = height; height = getWidth(); } return (width > downWidth) || (height > downHeight); } /** * Returns true if the image scaler was * only used to read image properties from the VFS. * * @return true if the image scaler was * only used to read image properties from the VFS */ public boolean isOriginalScaler() { return m_isOriginalScaler; } /** * Returns true if all required parameters are available.

* * Required parameters are "h" (height), and "w" (width).

* * @return true if all required parameters are available */ public boolean isValid() { return (m_width > 0) && (m_height > 0); } /** * Parses the given parameters and sets the internal scaler variables accordingly.

* * The parameter String must have a format like "h:100,w:200,t:1", * that is a comma separated list of attributes followed by a colon ":", followed by a value. * As possible attributes, use the constants from this class that start with SCALE_PARAM * for example {@link #SCALE_PARAM_HEIGHT} or {@link #SCALE_PARAM_WIDTH}.

* * @param parameters the parameters to parse */ public void parseParameters(String parameters) { m_width = -1; m_height = -1; m_position = 0; m_type = 0; m_color = Simapi.COLOR_TRANSPARENT; m_cropX = -1; m_cropY = -1; m_cropWidth = -1; m_cropHeight = -1; List tokens = CmsStringUtil.splitAsList(parameters, ','); Iterator it = tokens.iterator(); String k; String v; while (it.hasNext()) { String t = it.next(); // extract key and value k = null; v = null; int idx = t.indexOf(':'); if (idx >= 0) { k = t.substring(0, idx).trim(); if (t.length() > idx) { v = t.substring(idx + 1).trim(); } } if (CmsStringUtil.isNotEmpty(k) && CmsStringUtil.isNotEmpty(v)) { // key and value are available if (SCALE_PARAM_HEIGHT.equals(k)) { // image height m_height = CmsStringUtil.getIntValue(v, Integer.MIN_VALUE, k); } else if (SCALE_PARAM_WIDTH.equals(k)) { // image width m_width = CmsStringUtil.getIntValue(v, Integer.MIN_VALUE, k); } else if (SCALE_PARAM_CROP_X.equals(k)) { // crop x coordinate m_cropX = CmsStringUtil.getIntValue(v, Integer.MIN_VALUE, k); } else if (SCALE_PARAM_CROP_Y.equals(k)) { // crop y coordinate m_cropY = CmsStringUtil.getIntValue(v, Integer.MIN_VALUE, k); } else if (SCALE_PARAM_CROP_WIDTH.equals(k)) { // crop width m_cropWidth = CmsStringUtil.getIntValue(v, Integer.MIN_VALUE, k); } else if (SCALE_PARAM_CROP_HEIGHT.equals(k)) { // crop height m_cropHeight = CmsStringUtil.getIntValue(v, Integer.MIN_VALUE, k); } else if (SCALE_PARAM_TYPE.equals(k)) { // scaling type setType(CmsStringUtil.getIntValue(v, -1, CmsImageScaler.SCALE_PARAM_TYPE)); } else if (SCALE_PARAM_COLOR.equals(k)) { // image background color setColor(v); } else if (SCALE_PARAM_POS.equals(k)) { // image position (depends on scale type) setPosition(CmsStringUtil.getIntValue(v, -1, CmsImageScaler.SCALE_PARAM_POS)); } else if (SCALE_PARAM_QUALITY.equals(k)) { // image position (depends on scale type) setQuality(CmsStringUtil.getIntValue(v, 0, k)); } else if (SCALE_PARAM_RENDERMODE.equals(k)) { // image position (depends on scale type) setRenderMode(CmsStringUtil.getIntValue(v, 0, k)); } else if (SCALE_PARAM_FILTER.equals(k)) { // image filters to apply setFilters(v); } else { if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.ERR_INVALID_IMAGE_SCALE_PARAMS_2, k, v)); } } } else { if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.ERR_INVALID_IMAGE_SCALE_PARAMS_2, k, v)); } } } // initialize image crop area initCropArea(); } /** * Returns a scaled version of the given image byte content according this image scalers parameters.

* * @param content the image byte content to scale * @param image if this is set, this image will be used as base for the scaling rather than a new image read from the content byte array * @param rootPath the root path of the image file in the VFS * * @return a scaled version of the given image byte content according to the provided scaler parameters */ public byte[] scaleImage(byte[] content, BufferedImage image, String rootPath) { byte[] result = content; // flag for processed image boolean imageProcessed = false; // initialize image crop area initCropArea(); RenderSettings renderSettings; if ((m_renderMode == 0) && (m_quality == 0)) { // use default render mode and quality renderSettings = new RenderSettings(Simapi.RENDER_QUALITY); } else { // use special render mode and/or quality renderSettings = new RenderSettings(m_renderMode); if (m_quality != 0) { renderSettings.setCompressionQuality(m_quality / 100f); } } // set max blur size renderSettings.setMaximumBlurSize(m_maxBlurSize); // new create the scaler Simapi scaler = new Simapi(renderSettings); // calculate a valid image type supported by the imaging library (e.g. "JPEG", "GIF") String imageType = Simapi.getImageType(rootPath); if (imageType == null) { // no type given, maybe the name got mixed up String mimeType = OpenCms.getResourceManager().getMimeType(rootPath, null, null); // check if this is another known MIME type, if so DONT use it (images should not be named *.pdf) if (mimeType == null) { // no MIME type found, use JPEG format to write images to the cache imageType = Simapi.TYPE_JPEG; } } if (imageType == null) { // unknown type, unable to scale the image if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.ERR_UNABLE_TO_SCALE_IMAGE_2, rootPath, toString())); } return result; } try { if (image == null) { image = Simapi.read(content); } if (isCropping()) { // check if the crop width / height are not larger then the source image if ((getType() == 0) && ((m_cropHeight > image.getHeight()) || (m_cropWidth > image.getWidth()))) { // crop height / width is outside of image - return image unchanged return result; } } Color color = getColor(); if (!m_filters.isEmpty()) { Iterator i = m_filters.iterator(); while (i.hasNext()) { String filter = i.next(); if (FILTER_GRAYSCALE.equals(filter)) { // add a gray scale filter GrayscaleFilter grayscaleFilter = new GrayscaleFilter(); renderSettings.addImageFilter(grayscaleFilter); } else if (FILTER_SHADOW.equals(filter)) { // add a drop shadow filter ShadowFilter shadowFilter = new ShadowFilter(); shadowFilter.setXOffset(5); shadowFilter.setYOffset(5); shadowFilter.setOpacity(192); shadowFilter.setBackgroundColor(color.getRGB()); color = Simapi.COLOR_TRANSPARENT; renderSettings.setTransparentReplaceColor(Simapi.COLOR_TRANSPARENT); renderSettings.addImageFilter(shadowFilter); } } } if (isCropping()) { if ((getType() == 8) && (m_focalPoint != null)) { image = scaler.cropToSize( image, m_cropX, m_cropY, m_cropWidth, m_cropHeight, m_cropWidth, m_cropHeight, color); // Find the biggest scaling factor which, when applied to a rectangle of dimensions m_width x m_height, // would allow the resulting rectangle to still fit inside a rectangle of dimensions m_cropWidth x m_cropHeight // (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 // vice versa). double scaling = Math.min((1.0 * m_cropWidth) / m_width, (1.0 * m_cropHeight) / m_height); int relW = (int)(scaling * m_width); int relH = (int)(scaling * m_height); // the focal point's coordinates are in the uncropped image's coordinate system, so we have to subtract cx/cy int relX = (int)(m_focalPoint.getX() - m_cropX); int relY = (int)(m_focalPoint.getY() - m_cropY); image = scaler.cropPointToSize(image, relX, relY, false, relW, relH); if ((m_width != relW) || (m_height != relH)) { image = scaler.scale(image, m_width, m_height); } } else if ((getType() == 6) || (getType() == 7)) { // image crop operation around point image = scaler.cropPointToSize(image, m_cropX, m_cropY, getType() == 6, m_cropWidth, m_cropHeight); } else { // image crop operation image = scaler.cropToSize( image, m_cropX, m_cropY, m_cropWidth, m_cropHeight, getWidth(), getHeight(), color); } imageProcessed = true; } else { // only rescale the image, if the width and height are different to the target size int imageWidth = image.getWidth(); int imageHeight = image.getHeight(); // image rescale operation switch (getType()) { // select the "right" method of scaling according to the "t" parameter case 1: // thumbnail generation mode (like 0 but no image enlargement) image = scaler.resize(image, getWidth(), getHeight(), color, getPosition(), false); imageProcessed = true; break; case 2: // scale to exact target size, crop what does not fit if (((imageWidth != getWidth()) || (imageHeight != getHeight()))) { image = scaler.resize(image, getWidth(), getHeight(), getPosition()); imageProcessed = true; } break; case 3: // scale and keep image proportions, target size variable if (((imageWidth != getWidth()) || (imageHeight != getHeight()))) { image = scaler.resize(image, getWidth(), getHeight(), true); imageProcessed = true; } break; case 4: // don't keep image proportions, use exact target size if (((imageWidth != getWidth()) || (imageHeight != getHeight()))) { image = scaler.resize(image, getWidth(), getHeight(), false); imageProcessed = true; } break; case 5: // scale and keep image proportions, target size variable, include maxWidth / maxHeight option // image proportions have already been calculated so should not be a problem, use // 'false' to make sure image size exactly matches height and width attributes of generated tag if (((imageWidth != getWidth()) || (imageHeight != getHeight()))) { image = scaler.resize(image, getWidth(), getHeight(), false); imageProcessed = true; } break; default: // scale to exact target size with background padding image = scaler.resize(image, getWidth(), getHeight(), color, getPosition(), true); imageProcessed = true; } } if (!m_filters.isEmpty()) { Rectangle targetSize = scaler.applyFilterDimensions(getWidth(), getHeight()); image = scaler.resize( image, (int)targetSize.getWidth(), (int)targetSize.getHeight(), Simapi.COLOR_TRANSPARENT, Simapi.POS_CENTER); image = scaler.applyFilters(image); imageProcessed = true; } // get the byte result for the scaled image if some changes have been made. // otherwiese use the original image if (imageProcessed) { result = scaler.getBytes(image, imageType); } } catch (Exception e) { if (LOG.isDebugEnabled()) { LOG.debug( Messages.get().getBundle().key(Messages.ERR_UNABLE_TO_SCALE_IMAGE_2, rootPath, toString()), e); } } return result; } /** * Returns a scaled version of the given image byte content according this image scalers parameters.

* * @param content the image byte content to scale * @param rootPath the root path of the image file in the VFS * * @return a scaled version of the given image byte content according to the provided scaler parameters */ public byte[] scaleImage(byte[] content, String rootPath) { return scaleImage(content, (BufferedImage)null, rootPath); } /** * Returns a scaled version of the given image file according this image scalers parameters.

* * @param file the image file to scale * * @return a scaled version of the given image file according to the provided scaler parameters */ public byte[] scaleImage(CmsFile file) { return scaleImage(file.getContents(), file.getRootPath()); } /** * Sets the color.

* * @param color the color to set */ public void setColor(Color color) { m_color = color; } /** * Sets the color as a String.

* * @param value the color to set */ public void setColor(String value) { if (COLOR_TRANSPARENT.indexOf(value) == 0) { setColor(Simapi.COLOR_TRANSPARENT); } else { setColor(CmsStringUtil.getColorValue(value, Simapi.COLOR_TRANSPARENT, SCALE_PARAM_COLOR)); } } /** * Sets the image crop area.

* * @param x the x coordinate for the crop * @param y the y coordinate for the crop * @param width the crop width * @param height the crop height */ public void setCropArea(int x, int y, int width, int height) { m_cropX = x; m_cropY = y; m_cropWidth = width; m_cropHeight = height; } /** * Sets the list of filters as a String.

* * @param value the list of filters to set */ public void setFilters(String value) { m_filters = new ArrayList(); List filters = CmsStringUtil.splitAsList(value, ':'); Iterator i = filters.iterator(); while (i.hasNext()) { String filter = i.next(); filter = filter.trim().toLowerCase(); Iterator j = FILTERS.iterator(); while (j.hasNext()) { String candidate = j.next(); if (candidate.startsWith(filter)) { // found a matching filter addFilter(candidate); break; } } } } /** * Sets the focal point.

* * @param point the new value for the focal point */ public void setFocalPoint(CmsPoint point) { m_focalPoint = point; } /** * Sets the height.

* * @param height the height to set */ public void setHeight(int height) { m_height = height; } /** * Sets the maximum image size (width * height) to apply image blurring when downscaling images.

* * @param maxBlurSize the maximum image blur size to set * * @see #getMaxBlurSize() for a more detailed description about this parameter */ public void setMaxBlurSize(int maxBlurSize) { m_maxBlurSize = maxBlurSize; } /** * Sets the maximum target height (for scale type '5').

* * @param maxHeight the maximum target height to set */ public void setMaxHeight(int maxHeight) { m_maxHeight = maxHeight; } /** * Sets the maximum target width (for scale type '5').

* * @param maxWidth the maximum target width to set */ public void setMaxWidth(int maxWidth) { m_maxWidth = maxWidth; } /** * Sets the scale position.

* * @param position the position to set */ public void setPosition(int position) { switch (position) { case Simapi.POS_DOWN_LEFT: case Simapi.POS_DOWN_RIGHT: case Simapi.POS_STRAIGHT_DOWN: case Simapi.POS_STRAIGHT_LEFT: case Simapi.POS_STRAIGHT_RIGHT: case Simapi.POS_STRAIGHT_UP: case Simapi.POS_UP_LEFT: case Simapi.POS_UP_RIGHT: // position is fine m_position = position; break; default: m_position = Simapi.POS_CENTER; } } /** * Sets the image saving quality in percent.

* * @param quality the image saving quality (in percent) to set */ public void setQuality(int quality) { if (quality < 0) { m_quality = 0; } else if (quality > 100) { m_quality = 100; } else { m_quality = quality; } } /** * Sets the image rendering mode constant.

* * @param renderMode the image rendering mode to set * * @see #getRenderMode() for a list of allowed values for the rendering mode */ public void setRenderMode(int renderMode) { if ((renderMode < Simapi.RENDER_QUALITY) || (renderMode > Simapi.RENDER_SPEED)) { renderMode = Simapi.RENDER_QUALITY; } m_renderMode = renderMode; } /** * Sets the scale type.

* * @param type the scale type to set * * @see #getType() for a detailed description of the possible values for the type */ public void setType(int type) { if ((type < 0) || (type > 8)) { // invalid type, use 0 m_type = 0; } else { m_type = type; } } /** * Sets the width.

* * @param width the width to set */ public void setWidth(int width) { m_width = width; } /** * Creates a request parameter configured with the values from this image scaler, also * appends a '?' char as a prefix so that this may be directly appended to an image URL.

* * This can be appended to an image request in order to apply image scaling parameters.

* * @return a request parameter configured with the values from this image scaler */ public String toRequestParam() { StringBuffer result = new StringBuffer(128); result.append('?'); result.append(PARAM_SCALE); result.append('='); result.append(toString()); return result.toString(); } /** * @see java.lang.Object#toString() */ @Override public String toString() { if (m_scaleParameters != null) { return m_scaleParameters; } StringBuffer result = new StringBuffer(64); if (isCropping()) { result.append(CmsImageScaler.SCALE_PARAM_CROP_X); result.append(':'); result.append(m_cropX); result.append(','); result.append(CmsImageScaler.SCALE_PARAM_CROP_Y); result.append(':'); result.append(m_cropY); result.append(','); result.append(CmsImageScaler.SCALE_PARAM_CROP_WIDTH); result.append(':'); result.append(m_cropWidth); result.append(','); result.append(CmsImageScaler.SCALE_PARAM_CROP_HEIGHT); result.append(':'); result.append(m_cropHeight); } if (!isCropping() || ((m_width != m_cropWidth) || (m_height != m_cropHeight))) { if (isCropping()) { result.append(','); } result.append(CmsImageScaler.SCALE_PARAM_WIDTH); result.append(':'); result.append(m_width); result.append(','); result.append(CmsImageScaler.SCALE_PARAM_HEIGHT); result.append(':'); result.append(m_height); } if (m_type > 0) { result.append(','); result.append(CmsImageScaler.SCALE_PARAM_TYPE); result.append(':'); result.append(m_type); } if (m_position > 0) { result.append(','); result.append(CmsImageScaler.SCALE_PARAM_POS); result.append(':'); result.append(m_position); } if (m_color != Color.WHITE) { result.append(','); result.append(CmsImageScaler.SCALE_PARAM_COLOR); result.append(':'); result.append(getColorString()); } if (m_quality > 0) { result.append(','); result.append(CmsImageScaler.SCALE_PARAM_QUALITY); result.append(':'); result.append(m_quality); } if (m_renderMode > 0) { result.append(','); result.append(CmsImageScaler.SCALE_PARAM_RENDERMODE); result.append(':'); result.append(m_renderMode); } if (!m_filters.isEmpty()) { result.append(','); result.append(CmsImageScaler.SCALE_PARAM_FILTER); result.append(':'); result.append(getFiltersString()); } m_scaleParameters = result.toString(); return m_scaleParameters; } /** * Calculate the closest match of the given base float with the list of others.

* * @param base the base float to compare the other with * @param others the list of floats to compate to the base * * @return the array index of the closest match */ private int calculateClosest(float base, float[] others) { int result = -1; float bestMatch = Float.MAX_VALUE; for (int count = 0; count < others.length; count++) { float difference = Math.abs(base - others[count]); if (difference < bestMatch) { // new best match found bestMatch = difference; result = count; } if (bestMatch == 0f) { // it does not get better then this break; } } return result; } private Dimension getDimensionsWithSimapi(byte[] content) throws Exception { BufferedImage image = Simapi.read(content); return new Dimension(image.getWidth(), image.getHeight()); } /** * Initializes the members with the default values.

*/ private void init() { m_height = -1; m_width = -1; m_maxHeight = -1; m_maxWidth = -1; m_type = 0; m_position = 0; m_renderMode = 0; m_quality = 0; m_cropX = -1; m_cropY = -1; m_cropHeight = -1; m_cropWidth = -1; m_color = Color.WHITE; m_filters = new ArrayList(); m_maxBlurSize = CmsImageLoader.getMaxBlurSize(); m_isOriginalScaler = false; } /** * Initializes the crop area setting.

* * Only if all 4 required parameters have been set, the crop area is set accordingly. * Moreover, it is not required to specify the target image width and height when using crop, * because these parameters can be calculated from the crop area.

* * Scale type 6 and 7 are used for a 'crop around point' operation, see {@link #getType()} for a description.

*/ private void initCropArea() { if (isCropping()) { // crop area is set up correctly // adjust target image height or width if required if (m_width < 0) { m_width = m_cropWidth; } if (m_height < 0) { m_height = m_cropHeight; } if ((getType() != 6) && (getType() != 7) && (getType() != 8)) { // cropping type can only be 6 or 7 (point cropping) // all other values with cropping coordinates are invalid setType(0); } } } /** * Copies all values from the given scaler into this scaler.

* * @param source the source scaler */ private void initValuesFrom(CmsImageScaler source) { m_color = source.m_color; m_cropHeight = source.m_cropHeight; m_cropWidth = source.m_cropWidth; m_cropX = source.m_cropX; m_cropY = source.m_cropY; m_filters = new ArrayList(source.m_filters); m_focalPoint = source.m_focalPoint; m_height = source.m_height; m_isOriginalScaler = source.m_isOriginalScaler; m_maxBlurSize = source.m_maxBlurSize; m_position = source.m_position; m_quality = source.m_quality; m_renderMode = source.m_renderMode; m_type = source.m_type; m_width = source.m_width; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy