0001: /*
0002: * File : $Source: /usr/local/cvs/opencms/src/org/opencms/loader/CmsImageScaler.java,v $
0003: * Date : $Date: 2008-02-27 12:05:32 $
0004: * Version: $Revision: 1.8 $
0005: *
0006: * This library is part of OpenCms -
0007: * the Open Source Content Management System
0008: *
0009: * Copyright (c) 2002 - 2008 Alkacon Software GmbH (http://www.alkacon.com)
0010: *
0011: * This library is free software; you can redistribute it and/or
0012: * modify it under the terms of the GNU Lesser General Public
0013: * License as published by the Free Software Foundation; either
0014: * version 2.1 of the License, or (at your option) any later version.
0015: *
0016: * This library is distributed in the hope that it will be useful,
0017: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0018: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0019: * Lesser General Public License for more details.
0020: *
0021: * For further information about Alkacon Software, please see the
0022: * company website: http://www.alkacon.com
0023: *
0024: * For further information about OpenCms, please see the
0025: * project website: http://www.opencms.org
0026: *
0027: * You should have received a copy of the GNU Lesser General Public
0028: * License along with this library; if not, write to the Free Software
0029: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0030: */
0031:
0032: package org.opencms.loader;
0033:
0034: import com.alkacon.simapi.RenderSettings;
0035: import com.alkacon.simapi.Simapi;
0036: import com.alkacon.simapi.filter.GrayscaleFilter;
0037: import com.alkacon.simapi.filter.ShadowFilter;
0038:
0039: import org.opencms.file.CmsFile;
0040: import org.opencms.file.CmsObject;
0041: import org.opencms.file.CmsProperty;
0042: import org.opencms.file.CmsPropertyDefinition;
0043: import org.opencms.file.CmsResource;
0044: import org.opencms.main.CmsLog;
0045: import org.opencms.main.OpenCms;
0046: import org.opencms.util.CmsStringUtil;
0047:
0048: import java.awt.Color;
0049: import java.awt.Rectangle;
0050: import java.awt.image.BufferedImage;
0051: import java.util.ArrayList;
0052: import java.util.Arrays;
0053: import java.util.Iterator;
0054: import java.util.List;
0055:
0056: import javax.servlet.http.HttpServletRequest;
0057:
0058: import org.apache.commons.logging.Log;
0059:
0060: /**
0061: * Creates scaled images, acting as it's own parameter container.<p>
0062: *
0063: * @author Alexander Kandzior
0064: *
0065: * @version $Revision: 1.8 $
0066: *
0067: * @since 6.2.0
0068: */
0069: public class CmsImageScaler {
0070:
0071: /** The name of the transparent color (for the backgound image). */
0072: public static final String COLOR_TRANSPARENT = "transparent";
0073:
0074: /** The name of the grayscale image filter. */
0075: public static final String FILTER_GRAYSCALE = "grayscale";
0076:
0077: /** The name of the shadow image filter. */
0078: public static final String FILTER_SHADOW = "shadow";
0079:
0080: /** The supported image filter names. */
0081: public static final List FILTERS = Arrays.asList(new String[] {
0082: FILTER_GRAYSCALE, FILTER_SHADOW });
0083:
0084: /** The (optional) parameter used for sending the scale information of an image in the http request. */
0085: public static final String PARAM_SCALE = "__scale";
0086:
0087: /** The default maximum image size (width * height) to apply image blurring when downscaling (setting this to high may case "out of memory" errors). */
0088: public static final int SCALE_DEFAULT_MAX_BLUR_SIZE = 2500 * 2500;
0089:
0090: /** The default maximum image size (width or height) to allow when updowscaling an image using request parameters. */
0091: public static final int SCALE_DEFAULT_MAX_SIZE = 1500;
0092:
0093: /** The scaler parameter to indicate the requested image background color (if required). */
0094: public static final String SCALE_PARAM_COLOR = "c";
0095:
0096: /** The scaler parameter to indicate the requested image filter. */
0097: public static final String SCALE_PARAM_FILTER = "f";
0098:
0099: /** The scaler parameter to indicate the requested image height. */
0100: public static final String SCALE_PARAM_HEIGHT = "h";
0101:
0102: /** The scaler parameter to indicate the requested image position (if required). */
0103: public static final String SCALE_PARAM_POS = "p";
0104:
0105: /** The scaler parameter to indicate to requested image save quality in percent (if applicable, for example used with JPEG images). */
0106: public static final String SCALE_PARAM_QUALITY = "q";
0107:
0108: /** The scaler parameter to indicate to requested <code>{@link java.awt.RenderingHints}</code> settings. */
0109: public static final String SCALE_PARAM_RENDERMODE = "r";
0110:
0111: /** The scaler parameter to indicate the requested scale type. */
0112: public static final String SCALE_PARAM_TYPE = "t";
0113:
0114: /** The scaler parameter to indicate the requested image width. */
0115: public static final String SCALE_PARAM_WIDTH = "w";
0116:
0117: /** The log object for this class. */
0118: protected static final Log LOG = CmsLog
0119: .getLog(CmsImageScaler.class);
0120:
0121: /** The target background color (optional). */
0122: private Color m_color;
0123:
0124: /** The list of image filter names (Strings) to apply. */
0125: private List m_filters;
0126:
0127: /** The target height (required). */
0128: private int m_height;
0129:
0130: /** The maximum image size (width * height) to apply image blurring when downscaling (setting this to high may case "out of memory" errors). */
0131: private int m_maxBlurSize;
0132:
0133: /** The target position (optional). */
0134: private int m_position;
0135:
0136: /** The target image save quality (if applicable, for example used with JPEG images) (optional). */
0137: private int m_quality;
0138:
0139: /** The image processing renderings hints constant mode indicator (optional). */
0140: private int m_renderMode;
0141:
0142: /** The final (parsed and corrected) scale parameters. */
0143: private String m_scaleParameters;
0144:
0145: /** The target scale type (optional). */
0146: private int m_type;
0147:
0148: /** The target width (required). */
0149: private int m_width;
0150:
0151: /**
0152: * Creates a new, empty image scaler object.<p>
0153: */
0154: public CmsImageScaler() {
0155:
0156: init();
0157: }
0158:
0159: /**
0160: * Creates a new image scaler for the given image contained in the byte array.<p>
0161: *
0162: * <b>Please note:</b>The image itself is not stored in the scaler, only the width and
0163: * height dimensions of the image. To actually scale an image, you need to use
0164: * <code>{@link #scaleImage(CmsFile)}</code>. This constructor is commonly used only
0165: * to extract the image dimensions, for example when creating a String value for
0166: * the <code>{@link CmsPropertyDefinition#PROPERTY_IMAGE_SIZE}</code> property.<p>
0167: *
0168: * In case the byte array can not be decoded to an image, or in case of other errors,
0169: * <code>{@link #isValid()}</code> will return <code>false</code>.<p>
0170: *
0171: * @param content the image to calculate the dimensions for
0172: * @param rootPath the root path of the resource (for error logging)
0173: */
0174: public CmsImageScaler(byte[] content, String rootPath) {
0175:
0176: init();
0177: try {
0178: // read the scaled image
0179: BufferedImage image = Simapi.read(content);
0180: m_height = image.getHeight();
0181: m_width = image.getWidth();
0182: } catch (Exception e) {
0183: // nothing we can do about this, keep the original properties
0184: if (LOG.isDebugEnabled()) {
0185: LOG.debug(Messages.get().getBundle().key(
0186: Messages.ERR_UNABLE_TO_EXTRACT_IMAGE_SIZE_1,
0187: rootPath), e);
0188: }
0189: // set height / width to default of -1
0190: init();
0191: }
0192: }
0193:
0194: /**
0195: * Creates a new image scaler that is a recale from the original size to the given scaler.<p>
0196: *
0197: * @param original the scaler that holds the original image dimensions
0198: * @param target the image scaler to be used for rescaling this image scaler
0199: *
0200: * @deprecated use {@link #getReScaler(CmsImageScaler)} on the <code>original</code> scaler instead
0201: */
0202: public CmsImageScaler(CmsImageScaler original, CmsImageScaler target) {
0203:
0204: CmsImageScaler scaler = original.getReScaler(target);
0205: initValuesFrom(scaler);
0206: }
0207:
0208: /**
0209: * Creates a new image scaler by reading the property <code>{@link CmsPropertyDefinition#PROPERTY_IMAGE_SIZE}</code>
0210: * from the given resource.<p>
0211: *
0212: * In case of any errors reading or parsing the property,
0213: * <code>{@link #isValid()}</code> will return <code>false</code>.<p>
0214: *
0215: * @param cms the OpenCms user context to use when reading the property
0216: * @param res the resource to read the property from
0217: */
0218: public CmsImageScaler(CmsObject cms, CmsResource res) {
0219:
0220: init();
0221: String sizeValue = null;
0222: if ((cms != null) && (res != null)) {
0223: try {
0224: CmsProperty sizeProp = cms.readPropertyObject(res,
0225: CmsPropertyDefinition.PROPERTY_IMAGE_SIZE,
0226: false);
0227: if (!sizeProp.isNullProperty()) {
0228: // parse property value using standard procedures
0229: sizeValue = sizeProp.getValue();
0230: }
0231: } catch (Exception e) {
0232: // ignore
0233: }
0234: }
0235: if (CmsStringUtil.isNotEmpty(sizeValue)) {
0236: parseParameters(sizeValue);
0237: }
0238: }
0239:
0240: /**
0241: * Creates a new image scaler based on the given http request.<p>
0242: *
0243: * @param request the http request to read the parameters from
0244: * @param maxScaleSize the maximum scale size (width or height) for the image
0245: * @param maxBlurSize the maximum size of the image (width * height) to apply blur (may cause "out of memory" for large images)
0246: */
0247: public CmsImageScaler(HttpServletRequest request, int maxScaleSize,
0248: int maxBlurSize) {
0249:
0250: init();
0251: m_maxBlurSize = maxBlurSize;
0252: String parameters = request
0253: .getParameter(CmsImageScaler.PARAM_SCALE);
0254: if (CmsStringUtil.isNotEmpty(parameters)) {
0255: parseParameters(parameters);
0256: if (isValid()) {
0257: // valid parameters, check if scale size is not too big
0258: if ((getWidth() > maxScaleSize)
0259: || (getHeight() > maxScaleSize)) {
0260: // scale size is too big, reset scaler
0261: init();
0262: }
0263: }
0264: }
0265: }
0266:
0267: /**
0268: * Creates a new image scaler based on the given parameter String.<p>
0269: *
0270: * @param parameters the scale parameters to use
0271: */
0272: public CmsImageScaler(String parameters) {
0273:
0274: init();
0275: if (CmsStringUtil.isNotEmpty(parameters)) {
0276: parseParameters(parameters);
0277: }
0278: }
0279:
0280: /**
0281: * Creates a new image scaler based on the given base scaler and the given width and height.<p>
0282: *
0283: * @param base the base scaler to initialize the values with
0284: * @param width the width to set for this scaler
0285: * @param height the height to set for this scaler
0286: */
0287: protected CmsImageScaler(CmsImageScaler base, int width, int height) {
0288:
0289: initValuesFrom(base);
0290: setWidth(width);
0291: setHeight(height);
0292: }
0293:
0294: /**
0295: * Adds a filter name to the list of filters that should be applied to the image.<p>
0296: *
0297: * @param filter the filter name to add
0298: */
0299: public void addFilter(String filter) {
0300:
0301: if (CmsStringUtil.isNotEmpty(filter)) {
0302: filter = filter.trim().toLowerCase();
0303: if (FILTERS.contains(filter)) {
0304: m_filters.add(filter);
0305: }
0306: }
0307: }
0308:
0309: /**
0310: * @see java.lang.Object#clone()
0311: */
0312: public Object clone() {
0313:
0314: CmsImageScaler clone = new CmsImageScaler();
0315: clone.initValuesFrom(this );
0316: return clone;
0317: }
0318:
0319: /**
0320: * Returns the color.<p>
0321: *
0322: * @return the color
0323: */
0324: public Color getColor() {
0325:
0326: return m_color;
0327: }
0328:
0329: /**
0330: * Returns the color as a String.<p>
0331: *
0332: * @return the color as a String
0333: */
0334: public String getColorString() {
0335:
0336: StringBuffer result = new StringBuffer();
0337: if (m_color == Simapi.COLOR_TRANSPARENT) {
0338: result.append(COLOR_TRANSPARENT);
0339: } else {
0340: if (m_color.getRed() < 16) {
0341: result.append('0');
0342: }
0343: result.append(Integer.toString(m_color.getRed(), 16));
0344: if (m_color.getGreen() < 16) {
0345: result.append('0');
0346: }
0347: result.append(Integer.toString(m_color.getGreen(), 16));
0348: if (m_color.getBlue() < 16) {
0349: result.append('0');
0350: }
0351: result.append(Integer.toString(m_color.getBlue(), 16));
0352: }
0353: return result.toString();
0354: }
0355:
0356: /**
0357: * Returns a new image scaler that is a downscale from the size of <code>this</code> scaler
0358: * to the given scaler size.<p>
0359: *
0360: * If no downscale from this to the given scaler is required according to
0361: * {@link #isDownScaleRequired(CmsImageScaler)}, then <code>null</code> is returned.<p>
0362: *
0363: * @param downScaler the image scaler that holds the downscaled target image dimensions
0364: *
0365: * @return a new image scaler that is a downscale from the size of <code>this</code> scaler
0366: * to the given target scaler size, or <code>null</code>
0367: */
0368: public CmsImageScaler getDownScaler(CmsImageScaler downScaler) {
0369:
0370: if (!isDownScaleRequired(downScaler)) {
0371: // no downscaling is required
0372: return null;
0373: }
0374:
0375: int downHeight = downScaler.getHeight();
0376: int downWidth = downScaler.getWidth();
0377:
0378: int height = getHeight();
0379: int width = getWidth();
0380:
0381: if (((height > width) && (downHeight < downWidth))
0382: || ((width > height) && (downWidth < downHeight))) {
0383: // adjust orientation
0384: downHeight = downWidth;
0385: downWidth = downScaler.getHeight();
0386: }
0387:
0388: if (width > downWidth) {
0389: // width is too large, re-calculate height
0390: float scale = (float) downWidth / (float) width;
0391: downHeight = Math.round(height * scale);
0392: } else if (height > downHeight) {
0393: // height is too large, re-calculate width
0394: float scale = (float) downHeight / (float) height;
0395: downWidth = Math.round(width * scale);
0396: } else {
0397: // something is wrong, don't downscale
0398: return null;
0399: }
0400:
0401: // now create and initialize the result scaler
0402: return new CmsImageScaler(downScaler, downWidth, downHeight);
0403: }
0404:
0405: /**
0406: * Returns the list of image filter names (Strings) to be applied to the image.<p>
0407: *
0408: * @return the list of image filter names (Strings) to be applied to the image
0409: */
0410: public List getFilters() {
0411:
0412: return m_filters;
0413: }
0414:
0415: /**
0416: * Returns the list of image filter names (Strings) to be applied to the image as a String.<p>
0417: *
0418: * @return the list of image filter names (Strings) to be applied to the image as a String
0419: */
0420: public String getFiltersString() {
0421:
0422: StringBuffer result = new StringBuffer();
0423: Iterator i = m_filters.iterator();
0424: while (i.hasNext()) {
0425: String filter = (String) i.next();
0426: result.append(filter);
0427: if (i.hasNext()) {
0428: result.append(':');
0429: }
0430: }
0431: return result.toString();
0432: }
0433:
0434: /**
0435: * Returns the height.<p>
0436: *
0437: * @return the height
0438: */
0439: public int getHeight() {
0440:
0441: return m_height;
0442: }
0443:
0444: /**
0445: * Returns the image type from the given file name based on the file suffix (extension)
0446: * and the available image writers.<p>
0447: *
0448: * For example, for the file name "opencms.gif" the type is GIF, for
0449: * "opencms.jpg" is is "JPEG" etc.<p>
0450: *
0451: * In case the input filename has no suffix, or there is no known image writer for the format defined
0452: * by the suffix, <code>null</code> is returned.<p>
0453: *
0454: * Any non-null result can be used if an image type input value is required.<p>
0455: *
0456: * @param filename the file name to get the type for
0457: *
0458: * @return the image type from the given file name based on the suffix and the available image writers,
0459: * or null if no image writer is available for the format
0460: */
0461: public String getImageType(String filename) {
0462:
0463: return Simapi.getImageType(filename);
0464: }
0465:
0466: /**
0467: * Returns the maximum image size (width * height) to apply image blurring when downscaling images.<p>
0468: *
0469: * Image blurring is required to achive the best results for downscale operatios when the target image size
0470: * is 2 times or more smaller then the original image size. This parameter controls the maximum size (width * height) of an
0471: * image that is blurred before it is downscaled. If the image is larger, no blurring is done.
0472: * However, image blurring is an expensive operation in both CPU usage and memory consumption.
0473: * Setting the blur size to large may case "out of memory" errors.<p>
0474: *
0475: * @return the maximum image size (width * height) to apply image blurring when downscaling images
0476: */
0477: public int getMaxBlurSize() {
0478:
0479: return m_maxBlurSize;
0480: }
0481:
0482: /**
0483: * Returns the image pixel count, that is the image with multiplied by the image height.<p>
0484: *
0485: * If this scalier is not valid (see {@link #isValid()}) the result is undefined.<p>
0486: *
0487: * @return the image pixel count, that is the image with multiplied by the image height
0488: */
0489: public int getPixelCount() {
0490:
0491: return m_width * m_height;
0492: }
0493:
0494: /**
0495: * Returns the position.<p>
0496: *
0497: * @return the position
0498: */
0499: public int getPosition() {
0500:
0501: return m_position;
0502: }
0503:
0504: /**
0505: * Returns the image saving quality in percent (0 - 100).<p>
0506: *
0507: * This is used oly if applicable, for example when saving JPEG images.<p>
0508: *
0509: * @return the image saving quality in percent
0510: */
0511: public int getQuality() {
0512:
0513: return m_quality;
0514: }
0515:
0516: /**
0517: * Returns the image rendering mode constant.<p>
0518: *
0519: * Possible values are:<dl>
0520: * <dt>{@link Simapi#RENDER_QUALITY} (default)</dt>
0521: * <dd>Use best possible image processing - this may be slow sometimes.</dd>
0522: *
0523: * <dt>{@link Simapi#RENDER_SPEED}</dt>
0524: * <dd>Fastest image processing but worse results - use this for thumbnails or where speed is more important then quality.</dd>
0525: *
0526: * <dt>{@link Simapi#RENDER_MEDIUM}</dt>
0527: * <dd>Use default rendering hints from JVM - not recommended since it's almost as slow as the {@link Simapi#RENDER_QUALITY} mode.</dd></dl>
0528: *
0529: * @return the image rendering mode constant
0530: */
0531: public int getRenderMode() {
0532:
0533: return m_renderMode;
0534: }
0535:
0536: /**
0537: * Returns a new image scaler that is a rescaler from the <code>this</code> scaler
0538: * size to the given target scaler size.<p>
0539: *
0540: * The height of the target image is calculated in proportion
0541: * to the original image width. If the width of the the original image is not known,
0542: * the target image width is calculated in proportion to the original image height.<p>
0543: *
0544: * @param target the image scaler that holds the target image dimensions
0545: *
0546: * @return a new image scaler that is a rescale from the <code>this</code> scaler
0547: * size to the given target scaler size.<p>
0548: */
0549: public CmsImageScaler getReScaler(CmsImageScaler target) {
0550:
0551: int height = target.getHeight();
0552: int width = target.getWidth();
0553:
0554: if ((width > 0) && (getWidth() > 0)) {
0555: // width is known, calculate height
0556: float scale = (float) width / (float) getWidth();
0557: height = Math.round(getHeight() * scale);
0558: } else if ((height > 0) && (getHeight() > 0)) {
0559: // height is known, calculate width
0560: float scale = (float) height / (float) getHeight();
0561: width = Math.round(getWidth() * scale);
0562: } else if (isValid() && !target.isValid()) {
0563: // scaler is not valid but original is, so use original size of image
0564: width = getWidth();
0565: height = getHeight();
0566: }
0567:
0568: if ((target.getType() == 1) && (!target.isValid())) {
0569: // "no upscale" has been requested, only one target dimension was given
0570: if ((target.getWidth() > 0) && (getWidth() < width)) {
0571: // target width was given, target image should have this width
0572: height = getHeight();
0573: } else if ((target.getHeight() > 0)
0574: && (getHeight() < height)) {
0575: // target height was given, target image should have this height
0576: width = getWidth();
0577: }
0578: }
0579:
0580: // now create and initialize the result scaler
0581: return new CmsImageScaler(target, width, height);
0582: }
0583:
0584: /**
0585: * Returns the type.<p>
0586: *
0587: * Possible values are:<dl>
0588: *
0589: * <dt>0 (default): Scale to exact target size with background padding</dt><dd><ul>
0590: * <li>enlarge image to fit in target size (if required)
0591: * <li>reduce image to fit in target size (if required)
0592: * <li>keep image aspect ratio / propotions intact
0593: * <li>fill up with bgcolor to reach exact target size
0594: * <li>fit full image inside target size (only applies if reduced)</ul></dd>
0595: *
0596: * <dt>1: Thumbnail generation mode (like 0 but no image enlargement)</dt><dd><ul>
0597: * <li>dont't enlarge image
0598: * <li>reduce image to fit in target size (if required)
0599: * <li>keep image aspect ratio / propotions intact
0600: * <li>fill up with bgcolor to reach exact target size
0601: * <li>fit full image inside target size (only applies if reduced)</ul></dd>
0602: *
0603: * <dt>2: Scale to exact target size, crop what does not fit</dt><dd><ul>
0604: * <li>enlarge image to fit in target size (if required)
0605: * <li>reduce image to fit in target size (if required)
0606: * <li>keep image aspect ratio / propotions intact
0607: * <li>fit full image inside target size (crop what does not fit)</ul></dd>
0608: *
0609: * <dt>3: Scale and keep image propotions, target size variable</dt><dd><ul>
0610: * <li>enlarge image to fit in target size (if required)
0611: * <li>reduce image to fit in target size (if required)
0612: * <li>keep image aspect ratio / propotions intact
0613: * <li>scaled image will not be padded or cropped, so target size is likley not the exact requested size</ul></dd>
0614: *
0615: * <dt>4: Don't keep image propotions, use exact target size</dt><dd><ul>
0616: * <li>enlarge image to fit in target size (if required)
0617: * <li>reduce image to fit in target size (if required)
0618: * <li>don't keep image aspect ratio / propotions intact
0619: * <li>the image will be scaled exactly to the given target size and likley will be loose proportions</ul></dd>
0620: * </dl>
0621: *
0622: * @return the type
0623: */
0624: public int getType() {
0625:
0626: return m_type;
0627: }
0628:
0629: /**
0630: * Returns the width.<p>
0631: *
0632: * @return the width
0633: */
0634: public int getWidth() {
0635:
0636: return m_width;
0637: }
0638:
0639: /**
0640: * Returns a new image scaler that is a width based downscale from the size of <code>this</code> scaler
0641: * to the given scaler size.<p>
0642: *
0643: * If no downscale from this to the given scaler is required because the width of <code>this</code>
0644: * scaler is not larger than the target width, then the image dimensions of <code>this</code> scaler
0645: * are unchanged in the result scaler. No upscaling is done!<p>
0646: *
0647: * @param downScaler the image scaler that holds the downscaled target image dimensions
0648: *
0649: * @return a new image scaler that is a downscale from the size of <code>this</code> scaler
0650: * to the given target scaler size
0651: */
0652: public CmsImageScaler getWidthScaler(CmsImageScaler downScaler) {
0653:
0654: int width = downScaler.getWidth();
0655: int height;
0656:
0657: if (getWidth() > width) {
0658: // width is too large, re-calculate height
0659: float scale = (float) width / (float) getWidth();
0660: height = Math.round(getHeight() * scale);
0661: } else {
0662: // width is ok
0663: width = getWidth();
0664: height = getHeight();
0665: }
0666:
0667: // now create and initialize the result scaler
0668: return new CmsImageScaler(downScaler, width, height);
0669: }
0670:
0671: /**
0672: * @see java.lang.Object#hashCode()
0673: */
0674: public int hashCode() {
0675:
0676: return toString().hashCode();
0677: }
0678:
0679: /**
0680: * Returns <code>true</code> if this image scaler must be downscaled when compared to the
0681: * given "downscale" image scaler.<p>
0682: *
0683: * If either <code>this</code> scaler or the given <code>downScaler</code> is invalid according to
0684: * {@link #isValid()}, then <code>false</code> is returned.<p>
0685: *
0686: * The use case: <code>this</code> scaler represents an image (that is contains width and height of
0687: * an image). The <code>downScaler</code> represents the maximum wanted image. The scalers
0688: * are compared and if the image represented by <code>this</code> scaler is too large,
0689: * <code>true</code> is returned. Image orientation is ignored, so for example an image with 600x800 pixel
0690: * will NOT be downscaled if the target size is 800x600 but kept unchanged.<p>
0691: *
0692: * @param downScaler the downscaler to compare this image scaler with
0693: *
0694: * @return <code>true</code> if this image scaler must be downscaled when compared to the
0695: * given "downscale" image scaler
0696: */
0697: public boolean isDownScaleRequired(CmsImageScaler downScaler) {
0698:
0699: if ((downScaler == null) || !isValid() || !downScaler.isValid()) {
0700: // one of the scalers is invalid
0701: return false;
0702: }
0703:
0704: if (getPixelCount() < (downScaler.getPixelCount() / 2)) {
0705: // the image has much less pixels then the target, so don't downscale
0706: return false;
0707: }
0708:
0709: int downWidth = downScaler.getWidth();
0710: int downHeight = downScaler.getHeight();
0711: if (downHeight > downWidth) {
0712: // normalize image orientation - the width should always be the large side
0713: downWidth = downHeight;
0714: downHeight = downScaler.getWidth();
0715: }
0716: int height = getHeight();
0717: int width = getWidth();
0718: if (height > width) {
0719: // normalize image orientation - the width should always be the large side
0720: width = height;
0721: height = getWidth();
0722: }
0723:
0724: return (width > downWidth) || (height > downHeight);
0725: }
0726:
0727: /**
0728: * Returns <code>true</code> if all required parameters are available.<p>
0729: *
0730: * Required parameters are "h" (height), and "w" (width).<p>
0731: *
0732: * @return <code>true</code> if all required parameters are available
0733: */
0734: public boolean isValid() {
0735:
0736: return (m_width > 0) && (m_height > 0);
0737: }
0738:
0739: /**
0740: * Parses the given parameters and sets the internal scaler variables accordingly.<p>
0741: *
0742: * The parameter String must have the format <code>"h:100,w:200,t:1"</code>,
0743: * that is a comma separated list of attributes followed by a colon ":", followed by a value.
0744: * As possible attributes, use the constants from this class that start with <code>SCALE_PARAM</Code>
0745: * for example {@link #SCALE_PARAM_HEIGHT} or {@link #SCALE_PARAM_WIDTH}.<p>
0746: *
0747: * @param parameters the parameters to parse
0748: */
0749: public void parseParameters(String parameters) {
0750:
0751: m_width = -1;
0752: m_height = -1;
0753: m_position = 0;
0754: m_type = 0;
0755: m_color = Color.WHITE;
0756:
0757: List tokens = CmsStringUtil.splitAsList(parameters, ',');
0758: Iterator it = tokens.iterator();
0759: String k;
0760: String v;
0761: while (it.hasNext()) {
0762: String t = (String) it.next();
0763: // extract key and value
0764: k = null;
0765: v = null;
0766: int idx = t.indexOf(':');
0767: if (idx >= 0) {
0768: k = t.substring(0, idx).trim();
0769: if (t.length() > idx) {
0770: v = t.substring(idx + 1).trim();
0771: }
0772: }
0773: if (CmsStringUtil.isNotEmpty(k)
0774: && CmsStringUtil.isNotEmpty(v)) {
0775: // key and value are available
0776: if (SCALE_PARAM_HEIGHT.equals(k)) {
0777: // image height
0778: m_height = CmsStringUtil.getIntValue(v,
0779: Integer.MIN_VALUE, k);
0780: } else if (SCALE_PARAM_WIDTH.equals(k)) {
0781: // image width
0782: m_width = CmsStringUtil.getIntValue(v,
0783: Integer.MIN_VALUE, k);
0784: } else if (SCALE_PARAM_TYPE.equals(k)) {
0785: // scaling type
0786: setType(CmsStringUtil.getIntValue(v, -1,
0787: CmsImageScaler.SCALE_PARAM_TYPE));
0788: } else if (SCALE_PARAM_COLOR.equals(k)) {
0789: // image background color
0790: setColor(v);
0791: } else if (SCALE_PARAM_POS.equals(k)) {
0792: // image position (depends on scale type)
0793: setPosition(CmsStringUtil.getIntValue(v, -1,
0794: CmsImageScaler.SCALE_PARAM_POS));
0795: } else if (SCALE_PARAM_QUALITY.equals(k)) {
0796: // image position (depends on scale type)
0797: setQuality(CmsStringUtil.getIntValue(v, 0, k));
0798: } else if (SCALE_PARAM_RENDERMODE.equals(k)) {
0799: // image position (depends on scale type)
0800: setRenderMode(CmsStringUtil.getIntValue(v, 0, k));
0801: } else if (SCALE_PARAM_FILTER.equals(k)) {
0802: // image filters to apply
0803: setFilters(v);
0804: } else {
0805: if (LOG.isDebugEnabled()) {
0806: LOG
0807: .debug(Messages
0808: .get()
0809: .getBundle()
0810: .key(
0811: Messages.ERR_INVALID_IMAGE_SCALE_PARAMS_2,
0812: k, v));
0813: }
0814: }
0815: } else {
0816: if (LOG.isDebugEnabled()) {
0817: LOG.debug(Messages.get().getBundle().key(
0818: Messages.ERR_INVALID_IMAGE_SCALE_PARAMS_2,
0819: k, v));
0820: }
0821: }
0822: }
0823: }
0824:
0825: /**
0826: * Returns a scaled version of the given image byte content according this image scalers parameters.<p>
0827: *
0828: * @param content the image byte content to scale
0829: * @param rootPath the root path of the image file in the VFS
0830: *
0831: * @return a scaled version of the given image byte content according to the provided scaler parameters
0832: */
0833: public byte[] scaleImage(byte[] content, String rootPath) {
0834:
0835: byte[] result = content;
0836:
0837: RenderSettings renderSettings;
0838: if ((m_renderMode == 0) && (m_quality == 0)) {
0839: // use default render mode and quality
0840: renderSettings = new RenderSettings(Simapi.RENDER_QUALITY);
0841: } else {
0842: // use special render mode and/or quality
0843: renderSettings = new RenderSettings(m_renderMode);
0844: if (m_quality != 0) {
0845: renderSettings.setCompressionQuality(m_quality / 100f);
0846: }
0847: }
0848: // set max blur siuze
0849: renderSettings.setMaximumBlurSize(m_maxBlurSize);
0850: // new create the scaler
0851: Simapi scaler = new Simapi(renderSettings);
0852: // calculate a valid image type supported by the imaging libary (e.g. "JPEG", "GIF")
0853: String imageType = Simapi.getImageType(rootPath);
0854: if (imageType == null) {
0855: // no type given, maybe the name got mixed up
0856: String mimeType = OpenCms.getResourceManager().getMimeType(
0857: rootPath, null, null);
0858: // check if this is another known mime type, if so DONT use it (images should not be named *.pdf)
0859: if (mimeType == null) {
0860: // no mime type found, use JPEG format to write images to the cache
0861: imageType = Simapi.TYPE_JPEG;
0862: }
0863: }
0864: if (imageType == null) {
0865: // unknown type, unable to scale the image
0866: if (LOG.isDebugEnabled()) {
0867: LOG.debug(Messages.get().getBundle().key(
0868: Messages.ERR_UNABLE_TO_SCALE_IMAGE_2, rootPath,
0869: toString()));
0870: }
0871: return result;
0872: }
0873: try {
0874: BufferedImage image = Simapi.read(content);
0875:
0876: Color color = getColor();
0877:
0878: if (!m_filters.isEmpty()) {
0879: Iterator i = m_filters.iterator();
0880: while (i.hasNext()) {
0881: String filter = (String) i.next();
0882: if (FILTER_GRAYSCALE.equals(filter)) {
0883: // add a grayscale filter
0884: GrayscaleFilter grayscaleFilter = new GrayscaleFilter();
0885: renderSettings.addImageFilter(grayscaleFilter);
0886: } else if (FILTER_SHADOW.equals(filter)) {
0887: // add a drop shadow filter
0888: ShadowFilter shadowFilter = new ShadowFilter();
0889: shadowFilter.setXOffset(5);
0890: shadowFilter.setYOffset(5);
0891: shadowFilter.setOpacity(192);
0892: shadowFilter.setBackgroundColor(color.getRGB());
0893: color = Simapi.COLOR_TRANSPARENT;
0894: renderSettings
0895: .setTransparentReplaceColor(Simapi.COLOR_TRANSPARENT);
0896: renderSettings.addImageFilter(shadowFilter);
0897: }
0898: }
0899: }
0900:
0901: switch (getType()) {
0902: // select the "right" method of scaling according to the "t" parameter
0903: case 1:
0904: // thumbnail generation mode (like 0 but no image enlargement)
0905: image = scaler.resize(image, getWidth(), getHeight(),
0906: color, getPosition(), false);
0907: break;
0908: case 2:
0909: // scale to exact target size, crop what does not fit
0910: image = scaler.resize(image, getWidth(), getHeight(),
0911: getPosition());
0912: break;
0913: case 3:
0914: // scale and keep image propotions, target size variable
0915: image = scaler.resize(image, getWidth(), getHeight(),
0916: true);
0917: break;
0918: case 4:
0919: // don't keep image propotions, use exact target size
0920: image = scaler.resize(image, getWidth(), getHeight(),
0921: false);
0922: break;
0923: default:
0924: // scale to exact target size with background padding
0925: image = scaler.resize(image, getWidth(), getHeight(),
0926: color, getPosition(), true);
0927: }
0928:
0929: if (!m_filters.isEmpty()) {
0930: Rectangle targetSize = scaler.applyFilterDimensions(
0931: getWidth(), getHeight());
0932: image = scaler.resize(image, (int) targetSize
0933: .getWidth(), (int) targetSize.getHeight(),
0934: Simapi.COLOR_TRANSPARENT, Simapi.POS_CENTER);
0935: image = scaler.applyFilters(image);
0936: }
0937:
0938: // get the byte result for the scaled image
0939: result = scaler.getBytes(image, imageType);
0940: } catch (Exception e) {
0941: if (LOG.isDebugEnabled()) {
0942: LOG.debug(Messages.get().getBundle().key(
0943: Messages.ERR_UNABLE_TO_SCALE_IMAGE_2, rootPath,
0944: toString()), e);
0945: }
0946: }
0947: return result;
0948: }
0949:
0950: /**
0951: * Returns a scaled version of the given image file according this image scalers parameters.<p>
0952: *
0953: * @param file the image file to scale
0954: *
0955: * @return a scaled version of the given image file according to the provided scaler parameters
0956: */
0957: public byte[] scaleImage(CmsFile file) {
0958:
0959: return scaleImage(file.getContents(), file.getRootPath());
0960: }
0961:
0962: /**
0963: * Sets the color.<p>
0964: *
0965: * @param color the color to set
0966: */
0967: public void setColor(Color color) {
0968:
0969: m_color = color;
0970: }
0971:
0972: /**
0973: * Sets the color as a String.<p>
0974: *
0975: * @param value the color to set
0976: */
0977: public void setColor(String value) {
0978:
0979: if (COLOR_TRANSPARENT.indexOf(value) == 0) {
0980: setColor(Simapi.COLOR_TRANSPARENT);
0981: } else {
0982: setColor(CmsStringUtil.getColorValue(value, Color.WHITE,
0983: SCALE_PARAM_COLOR));
0984: }
0985: }
0986:
0987: /**
0988: * Sets the list of filters as a String.<p>
0989: *
0990: * @param value the list of filters to set
0991: */
0992: public void setFilters(String value) {
0993:
0994: m_filters = new ArrayList();
0995: List filters = CmsStringUtil.splitAsList(value, ':');
0996: Iterator i = filters.iterator();
0997: while (i.hasNext()) {
0998: String filter = (String) i.next();
0999: filter = filter.trim().toLowerCase();
1000: Iterator j = FILTERS.iterator();
1001: while (j.hasNext()) {
1002: String candidate = (String) j.next();
1003: if (candidate.startsWith(filter)) {
1004: // found a matching filter
1005: addFilter(candidate);
1006: break;
1007: }
1008: }
1009: }
1010: }
1011:
1012: /**
1013: * Sets the height.<p>
1014: *
1015: * @param height the height to set
1016: */
1017: public void setHeight(int height) {
1018:
1019: m_height = height;
1020: }
1021:
1022: /**
1023: * Sets the maximum image size (width * height) to apply image blurring when downscaling images.<p>
1024: *
1025: * @param maxBlurSize the maximum image blur size to set
1026: *
1027: * @see #getMaxBlurSize() for a more detailed description about this parameter
1028: */
1029: public void setMaxBlurSize(int maxBlurSize) {
1030:
1031: m_maxBlurSize = maxBlurSize;
1032: }
1033:
1034: /**
1035: * Sets the scale position.<p>
1036: *
1037: * @param position the position to set
1038: */
1039: public void setPosition(int position) {
1040:
1041: switch (position) {
1042: case Simapi.POS_DOWN_LEFT:
1043: case Simapi.POS_DOWN_RIGHT:
1044: case Simapi.POS_STRAIGHT_DOWN:
1045: case Simapi.POS_STRAIGHT_LEFT:
1046: case Simapi.POS_STRAIGHT_RIGHT:
1047: case Simapi.POS_STRAIGHT_UP:
1048: case Simapi.POS_UP_LEFT:
1049: case Simapi.POS_UP_RIGHT:
1050: // pos is fine
1051: m_position = position;
1052: break;
1053: default:
1054: m_position = Simapi.POS_CENTER;
1055: }
1056: }
1057:
1058: /**
1059: * Sets the image saving quality in percent.<p>
1060: *
1061: * @param quality the image saving quality (in percent) to set
1062: */
1063: public void setQuality(int quality) {
1064:
1065: if (quality < 0) {
1066: m_quality = 0;
1067: } else if (quality > 100) {
1068: m_quality = 100;
1069: } else {
1070: m_quality = quality;
1071: }
1072: }
1073:
1074: /**
1075: * Sets the image rendering mode constant.<p>
1076: *
1077: * @param renderMode the image rendering mode to set
1078: *
1079: * @see #getRenderMode() for a list of allowed values for the rendering mode
1080: */
1081: public void setRenderMode(int renderMode) {
1082:
1083: if ((renderMode < Simapi.RENDER_QUALITY)
1084: || (renderMode > Simapi.RENDER_SPEED)) {
1085: renderMode = Simapi.RENDER_QUALITY;
1086: }
1087: m_renderMode = renderMode;
1088: }
1089:
1090: /**
1091: * Sets the scale type.<p>
1092: *
1093: * @param type the scale type to set
1094: *
1095: * @see #getType() for a detailed description of the possible values for the type
1096: */
1097: public void setType(int type) {
1098:
1099: if ((type < 0) || (type > 4)) {
1100: // invalid type, use 0
1101: m_type = 0;
1102: } else {
1103: m_type = type;
1104: }
1105: }
1106:
1107: /**
1108: * Sets the width.<p>
1109: *
1110: * @param width the width to set
1111: */
1112: public void setWidth(int width) {
1113:
1114: m_width = width;
1115: }
1116:
1117: /**
1118: * Creates a request parameter configured with the values from this image scaler, also
1119: * appends a <code>'?'</code> char as a prefix so that this may be direclty appended to an image URL.<p>
1120: *
1121: * This can be appended to an image request in order to apply image scaling parameters.<p>
1122: *
1123: * @return a request parameter configured with the values from this image scaler
1124: */
1125: public String toRequestParam() {
1126:
1127: StringBuffer result = new StringBuffer(128);
1128: result.append('?');
1129: result.append(PARAM_SCALE);
1130: result.append('=');
1131: result.append(toString());
1132:
1133: return result.toString();
1134: }
1135:
1136: /**
1137: * @see java.lang.Object#toString()
1138: */
1139: public String toString() {
1140:
1141: if (m_scaleParameters != null) {
1142: return m_scaleParameters;
1143: }
1144:
1145: StringBuffer result = new StringBuffer(64);
1146: result.append(CmsImageScaler.SCALE_PARAM_WIDTH);
1147: result.append(':');
1148: result.append(m_width);
1149: result.append(',');
1150: result.append(CmsImageScaler.SCALE_PARAM_HEIGHT);
1151: result.append(':');
1152: result.append(m_height);
1153: if (m_type > 0) {
1154: result.append(',');
1155: result.append(CmsImageScaler.SCALE_PARAM_TYPE);
1156: result.append(':');
1157: result.append(m_type);
1158: }
1159: if (m_position > 0) {
1160: result.append(',');
1161: result.append(CmsImageScaler.SCALE_PARAM_POS);
1162: result.append(':');
1163: result.append(m_position);
1164: }
1165: if (m_color != Color.WHITE) {
1166: result.append(',');
1167: result.append(CmsImageScaler.SCALE_PARAM_COLOR);
1168: result.append(':');
1169: result.append(getColorString());
1170: }
1171: if (m_quality > 0) {
1172: result.append(',');
1173: result.append(CmsImageScaler.SCALE_PARAM_QUALITY);
1174: result.append(':');
1175: result.append(m_quality);
1176: }
1177: if (m_renderMode > 0) {
1178: result.append(',');
1179: result.append(CmsImageScaler.SCALE_PARAM_RENDERMODE);
1180: result.append(':');
1181: result.append(m_renderMode);
1182: }
1183: if (!m_filters.isEmpty()) {
1184: result.append(',');
1185: result.append(CmsImageScaler.SCALE_PARAM_FILTER);
1186: result.append(':');
1187: result.append(getFiltersString());
1188: }
1189: m_scaleParameters = result.toString();
1190: return m_scaleParameters;
1191: }
1192:
1193: /**
1194: * Initializes the members with the default values.<p>
1195: */
1196: private void init() {
1197:
1198: m_height = -1;
1199: m_width = -1;
1200: m_type = 0;
1201: m_position = 0;
1202: m_renderMode = 0;
1203: m_quality = 0;
1204: m_color = Color.WHITE;
1205: m_filters = new ArrayList();
1206: m_maxBlurSize = CmsImageLoader.getMaxBlurSize();
1207: }
1208:
1209: /**
1210: * Copys all values from the given scaler into this scaler.<p>
1211: *
1212: * @param source the source scaler
1213: */
1214: private void initValuesFrom(CmsImageScaler source) {
1215:
1216: m_width = source.m_width;
1217: m_height = source.m_height;
1218: m_type = source.m_type;
1219: m_position = source.m_position;
1220: m_renderMode = source.m_renderMode;
1221: m_quality = source.m_quality;
1222: m_color = source.m_color;
1223: m_filters = new ArrayList(source.m_filters);
1224: m_maxBlurSize = source.m_maxBlurSize;
1225: }
1226: }
|