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

com.alkacon.simapi.Simapi Maven / Gradle / Ivy

Go to download

alkacon-simapi is the JAR-Library for OpenCms. OpenCms is a Content Management System that is based on Open Source Software. Complex Intranet and Internet websites can be quickly and cost-effectively created, maintained and managed.

There is a newer version: 1.0.4
Show newest version
/*
 * File   : $Source: /alkacon/cvs/AlkaconSimapi/src/com/alkacon/simapi/Simapi.java,v $
 * Date   : $Date: 2008/07/15 12:34:37 $
 * Version: $Revision: 1.16 $
 *
 * Copyright (c) 2007 Alkacon Software GmbH (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 GmbH, please see the
 * company website: http://www.alkacon.com
 * 
 * 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 com.alkacon.simapi;

import com.alkacon.simapi.filter.WholeImageFilter;
import com.alkacon.simapi.filter.buffered.BoxBlurFilter;
import com.alkacon.simapi.filter.buffered.GaussianFilter;
import com.alkacon.simapi.util.GifImageWriterSpi;
import com.alkacon.simapi.util.Quantize;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.awt.image.PixelGrabber;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.Iterator;

import javax.imageio.IIOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageOutputStream;

/**
 * SIMple IMage API (SIMAPI) that provides convenient access to commonly used imaging operations.

* * @author Alexander Kandzior */ public class Simapi { /** Constant to identify a transparent background fill color. */ public static final Color COLOR_TRANSPARENT = new Color(0, 0, 0, 255); /** Position indicator: Center (default). */ public static final int POS_CENTER = 0; /** Position indicator: Down left. */ public static final int POS_DOWN_LEFT = 1; /** Position indicator: Down right. */ public static final int POS_DOWN_RIGHT = 2; /** Position indicator: Straight down. */ public static final int POS_STRAIGHT_DOWN = 3; /** Position indicator: Straight left. */ public static final int POS_STRAIGHT_LEFT = 4; /** Position indicator: Straight right. */ public static final int POS_STRAIGHT_RIGHT = 5; /** Position indicator: Straight up. */ public static final int POS_STRAIGHT_UP = 6; /** Position indicator: Up left. */ public static final int POS_UP_LEFT = 7; /** Position indicator: Up right. */ public static final int POS_UP_RIGHT = 8; /** Indicates to use the MEDIUM render settings. */ public static final int RENDER_MEDIUM = 1; /** Indicates to use the QUALITY render settings (default). */ public static final int RENDER_QUALITY = 0; /** Indicates to use the SPEED render settings. */ public static final int RENDER_SPEED = 2; /** Constant to identify the BMP image type. */ public static final String TYPE_BMP = "BMP"; /** Constant to identify the GIF image type. */ public static final String TYPE_GIF = "GIF"; /** Constant to identify the JPEG image type. */ public static final String TYPE_JPEG = "JPEG"; /** Constant to identify the PNG image type. */ public static final String TYPE_PNG = "PNG"; /** Constant to identify the PNM image type. */ public static final String TYPE_PNM = "PNM"; /** Constant to identify the TIFF image type. */ public static final String TYPE_TIFF = "TIFF"; /** Rendering settings for the image generation / scaling / saving. */ private RenderSettings m_renderSettings; /** * Creates a new simapi instance using the default render settings ({@link #RENDER_QUALITY}).

* */ public Simapi() { this(new RenderSettings(RENDER_QUALITY)); } /** * Creates a new simapi instance with the specified render settings.

* * @param renderSettings the render settings to use */ public Simapi(RenderSettings renderSettings) { m_renderSettings = renderSettings; } /** * Register the GIF encoder.

*/ static { // register the Alkacon GIF encoder with the Java ImageIO registry ImageWriterSpi alkaconGifSpi = new GifImageWriterSpi(); IIORegistry.getDefaultInstance().registerServiceProvider(alkaconGifSpi); Iterator i = null; try { i = IIORegistry.getDefaultInstance().getServiceProviders(ImageWriterSpi.class, true); } catch (Throwable t) { t.printStackTrace(System.err); } if (i != null) { while (i.hasNext()) { ImageWriterSpi spi = (ImageWriterSpi)i.next(); if (spi.getClass() != GifImageWriterSpi.class) { String[] formats = spi.getFormatNames(); for (int j = 0; j < formats.length; j++) { String format = formats[j]; if ("gif".equals(format.toLowerCase())) { // the SPI can write GIFs, change order so that Alkacons SPI comes first IIORegistry.getDefaultInstance().setOrdering(ImageWriterSpi.class, alkaconGifSpi, spi); break; } } } } } } /** * 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 "alkacon.gif" the type is GIF, for * "alkacon.jpeg" 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 static String getImageType(String filename) { if (filename == null) { return null; } int pos = filename.lastIndexOf('.'); String type; if (pos < 0) { type = filename; } else { if (pos < filename.length()) { pos++; } type = filename.substring(pos); } type = type.trim().toUpperCase(); if (type.equals(Simapi.TYPE_JPEG) || type.equals("JPG")) { type = Simapi.TYPE_JPEG; } else if (type.equals(Simapi.TYPE_GIF)) { type = Simapi.TYPE_GIF; } else if (type.equals(Simapi.TYPE_PNG)) { type = Simapi.TYPE_PNG; } else if (type.equals(Simapi.TYPE_TIFF) || type.equals("TIF")) { type = Simapi.TYPE_TIFF; } else if (type.equals(Simapi.TYPE_BMP)) { type = Simapi.TYPE_BMP; } else if (type.equals(Simapi.TYPE_PNM) || type.equals("PBM") || type.equals("PGM") || type.equals("PPM")) { type = Simapi.TYPE_PNM; } // check if a writer for the image name can be found Iterator iter = ImageIO.getImageWritersByFormatName(type); if (iter.hasNext()) { // type can be resolved return type; } // type is unknown return null; } /** * Loads an image from a byte array * * @param source the byte array to read the image from * * @return the loaded image * * @throws IOException in case the image could not be loaded */ public static BufferedImage read(byte[] source) throws IOException { return read(new ByteArrayInputStream(source)); } /** * Loads an image from a local file.

* * @param source the file to read the input image from * * @return the loaded image * * @throws IOException in case the image could not be loaded */ public static BufferedImage read(File source) throws IOException { return ensureImageIsSystemType(ImageIO.read(source), true); } /** * Loads an image from an InputStream.

* * @param source the input stream to read the input image from * * @return the loaded image * * @throws IOException in case the image could not be loaded */ public static BufferedImage read(InputStream source) throws IOException { return ensureImageIsSystemType(ImageIO.read(source), true); } /** * Loads an image from a local file whose path is supplied as a String * * @param source the path to the local file to read the input image from * * @return the loaded image * * @throws IOException in case the image could not be loaded */ public static BufferedImage read(String source) throws IOException { return read(new File(source)); } /** * Loads an image from a URL.

* * @param source the URL to read the input image from * * @return the loaded image * * @throws IOException in case the image could not be loaded */ public static BufferedImage read(URL source) throws IOException { return ensureImageIsSystemType(ImageIO.read(source), true); } /** * Returns an image that is ensured the be of either {@link BufferedImage#TYPE_INT_RGB} or * {@link BufferedImage#TYPE_INT_ARGB}.

* * Ensuring the image is of one of the possible return types is done before applying an image filter transformation * since if the image is of a different (not native) type, the transformation can take very long * and consume a lot of resources.

* * @param image the original image * @param allowTransparent if true, transparent (alpha layer) pixels is allowed * @return an image that is ensured the be of a system type */ protected static BufferedImage ensureImageIsSystemType(BufferedImage image, boolean allowTransparent) { switch (image.getType()) { case BufferedImage.TYPE_INT_ARGB: case BufferedImage.TYPE_INT_RGB: // image already uses a system compatible color model, no need for transformation return image; default: // image must be transformed to system color } BufferedImage result; if (allowTransparent && (image.getColorModel().getTransparency() != Transparency.OPAQUE)) { // use RGB color model with alpha result = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); } else { // use RGB color model without alpha (no transparency) result = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB); } // copy the pixels from the source image ti the result image Graphics2D g = result.createGraphics(); g.drawImage(image, 0, 0, null); g.dispose(); // flush original - doesn't actually do anything but looks right to me anyway image.flush(); image = null; return result; } /** * Applies the given filter to the image.

* * @param image the image to apply the filter to * @param filter the filter to apply * * @return the image with the filter applied */ public BufferedImage applyFilter(BufferedImage image, ImageFilter filter) { // apply filter using default AWT toolkit Image img = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(image.getSource(), filter)); // use a pixel grabber to retrieve the image's color model; // grabbing a single pixel is usually sufficient PixelGrabber pg = new PixelGrabber(img, 0, 0, 1, 1, false); try { pg.grabPixels(); } catch (InterruptedException e) { // ignore } // recast the AWT image into a BufferedImage (using alpha RGB) BufferedImage result = new BufferedImage( img.getWidth(null), img.getHeight(null), pg.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); // draw the generated image to the result canvas and return Graphics2D g = result.createGraphics(); g.drawImage(img, 0, 0, null); g.dispose(); return result; } /** * Calculates the image size for an image after all filters returned by {@link RenderSettings#getImageFilters()} * have been applied.

* * @param width the image width * @param height the image height * * @return the image size after all filters have been applied */ public Rectangle applyFilterDimensions(int width, int height) { Rectangle base = new Rectangle(width, height); Iterator i = m_renderSettings.getImageFilters().iterator(); while (i.hasNext()) { ImageFilter filter = (ImageFilter)i.next(); if (filter instanceof WholeImageFilter) { WholeImageFilter wholeFilter = (WholeImageFilter)filter; base = wholeFilter.getTransformedSpace(base); } } return base; } /** * Applies all filters returned by {@link RenderSettings#getImageFilters()} to the given image.

* * @param image the image to apply the filters to * * @return the image with the filters applied */ public BufferedImage applyFilters(BufferedImage image) { // make sure the image is of a compatible system type image = ensureImageIsSystemType(image, true); threadSetNice(); Iterator i = m_renderSettings.getImageFilters().iterator(); while (i.hasNext()) { ImageFilter filter = (ImageFilter)i.next(); image = applyFilter(image, filter); } threadSetNormal(); return image; } /** * Crops an image according to the width and height specified.

* * Use the constants {@link Simapi#POS_CENTER} etc. to indicate the crop position.

* * @param image the image to crop * @param width the width of the target image * @param height the height of the target image * @param cropPosition the position to crop the image at * * @return the transformed image */ public BufferedImage crop(BufferedImage image, int width, int height, int cropPosition) { int imageWidth = image.getWidth(); int imageHeight = image.getHeight(); if ((imageWidth == width) && (imageHeight == height)) { // no resize required return image; } int x; int y; switch (cropPosition) { case Simapi.POS_DOWN_LEFT: x = 0; y = imageHeight - height; break; case Simapi.POS_DOWN_RIGHT: x = imageWidth - width; y = imageHeight - height; break; case Simapi.POS_STRAIGHT_DOWN: x = (imageWidth - width) / 2; y = imageHeight - height; break; case Simapi.POS_STRAIGHT_LEFT: x = 0; y = (imageHeight - height) / 2; break; case Simapi.POS_STRAIGHT_RIGHT: x = imageWidth - width; y = (imageHeight - height) / 2; break; case Simapi.POS_STRAIGHT_UP: x = (imageWidth - width) / 2; y = 0; break; case Simapi.POS_UP_LEFT: x = 0; y = 0; break; case Simapi.POS_UP_RIGHT: x = imageWidth - width; y = 0; break; default: // crop center x = (imageWidth - width) / 2; y = (imageHeight - height) / 2; } // return the result return image.getSubimage(x, y, width, height); } /** * Crops a part of the given image from the specified x,y point to the given width,height. * * Should the target image rectangle be outside of the source image, the source image is enlarged and * the transparent color is used for the additional background pixels. * If the image does not support transparent pixels, the transparent replacement color (whit by default) * will be used.

* * @param image the image to crop * @param x the x position where the crop starts * @param y the y position where the crop starts * @param width the width of the cropped target image * @param height the height of the cropped target image * * @return a cropped part of the given image from the specified * x,y point to the given width,height * * @see #crop(BufferedImage, int, int, int, int, Color) */ public BufferedImage crop(BufferedImage image, int x, int y, int width, int height) { return crop(image, x, y, width, height, COLOR_TRANSPARENT); } /** * Crops a part of the given image from the specified x,y point to the given width,height. * * Should the target image rectangle be outside of the source image, the source image is enlarged and * the given background replace color is used for the additional pixels.

* * @param image the image to crop * @param x the x position where the crop starts * @param y the y position where the crop starts * @param width the width of the cropped target image * @param height the height of the cropped target image * @param backgroundColor the color to use if the background must be enlarged * * @return a cropped part of the given image from the specified * x,y point to the given width,height * * @see #crop(BufferedImage, int, int, int, int) */ public BufferedImage crop(BufferedImage image, int x, int y, int width, int height, Color backgroundColor) { if ((x < 0) || (y < 0) || (x + width >= image.getWidth()) || (y + height >= image.getHeight())) { // crop area lies partly outside of image - blow up result image to fit int xpos = x; int ypos = y; int imageWidth = image.getWidth(); int imageHeight = image.getHeight(); if (x < 0) { xpos = Math.abs(x); x = 0; imageWidth += xpos; } else { xpos = 0; } if (y < 0) { ypos = Math.abs(y); y = 0; imageHeight += ypos; } else { ypos = 0; } if (imageWidth < width) { imageWidth += width; } if (imageHeight < height) { imageHeight += height; } // draw input image to enlarged canvas BufferedImage result = createImage(image.getColorModel(), imageWidth, imageHeight); Graphics2D g = result.createGraphics(); // check the background color ColorModel cm = result.getColorModel(); if (!cm.hasAlpha() && (backgroundColor == COLOR_TRANSPARENT)) { // alpha not supported by target color model backgroundColor = m_renderSettings.getTransparentReplaceColor(); } if (backgroundColor != COLOR_TRANSPARENT) { // don't fill if background is transparent g.setPaintMode(); g.setColor(backgroundColor); g.fillRect(0, 0, result.getWidth(), result.getHeight()); } g.drawImage(image, xpos, ypos, null); g.dispose(); // exchange image with result image = result; } // return the result image return image.getSubimage(x, y, width, height); } /** * Crops a part of the given image from the specified x,y point to the given width,height, * and then resizes this cropped image to the dimensions specified in targetWidth,targetHeight. * * Should the target image rectangle be outside of the source image, the source image is enlarged and * the transparent color is used for the additional background pixels. * If the image does not support transparent pixels, the transparent replacement color (whit by default) * will be used.

* * The aspect ratio of the target image is not kept.

* * @param image the image to crop * @param x the x position where the crop starts * @param y the y position where the crop starts * @param width the width of the cropped area from the source image * @param height the height of the cropped area from the source image * @param targetWidth the width of the target image * @param targetHeight the width of the target image * * @return a cropped part of the given image from the specified x,y point * to the given width,height, resized to the dimensions specified in targetWidth,targetHeight * * @see #cropToSize(BufferedImage, int, int, int, int, int, int, Color) */ public BufferedImage cropToSize( BufferedImage image, int x, int y, int width, int height, int targetWidth, int targetHeight) { return cropToSize(image, x, y, width, height, targetWidth, targetHeight, COLOR_TRANSPARENT); } /** * Crops a part of the given image from the specified x,y point to the given width,height, * and then resizes this cropped image to the dimensions specified in targetWidth,targetHeight. * * Should the target image rectangle be outside of the source image, the source image is enlarged and * the given background replace color is used for the additional pixels.

* * The aspect ratio of the target image is not kept.

* * @param image the image to crop * @param x the x position where the crop starts * @param y the y position where the crop starts * @param width the width of the cropped area from the source image * @param height the height of the cropped area from the source image * @param targetWidth the width of the target image * @param targetHeight the width of the target image * @param backgroundColor the color to use if the background must be enlarged * * @return a cropped part of the given image from the specified x,y point * to the given width,height, resized to the dimensions specified in targetWidth,targetHeight * * @see #cropToSize(BufferedImage, int, int, int, int, int, int) */ public BufferedImage cropToSize( BufferedImage image, int x, int y, int width, int height, int targetWidth, int targetHeight, Color backgroundColor) { image = crop(image, x, y, width, height, backgroundColor); image = resize(image, targetWidth, targetHeight); return image; } /** * Returns the byte contents of the given image.

* * @param image the image to get the byte contents for * @param type the type of the image to get the byte contents for * * @return the byte contents of the given image * * @throws IOException in case the image could not be converted to bytes */ public byte[] getBytes(BufferedImage image, String type) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(4096); write(image, out, type); return out.toByteArray(); } /** * Reduces the colors in the given image to the given maximum color number.

* * @param image the image to reduce the colors from * @param maxColors the maximum number of allowed colors in the output image (usually 256) * @param alphaToBitmask indicates if alpha information should be converted * * @return the transformed image */ public BufferedImage reduceColors(BufferedImage image, int maxColors, boolean alphaToBitmask) { return Quantize.process(image, maxColors, alphaToBitmask); } /** * Resizes an image according to the width and height specified.

* * @param image the image to resize * @param width the width of the target image * @param height the height of the target image * * @return the transformed image */ public BufferedImage resize(BufferedImage image, int width, int height) { return resize(image, width, height, false); } /** * Resizes an image according to the width and height specified, * keeping the aspect ratio if required.

* * If set to true, the bestfit option will keep the image within the dimensions specified * without losing the aspect ratio.

* * @param image the image to resize * @param width the width of the target image * @param height the height of the target image * @param bestfit if true, the aspect ratio of the image will be kept * * @return the transformed image */ public BufferedImage resize(BufferedImage image, int width, int height, boolean bestfit) { return resize(image, width, height, bestfit, true); } /** * Resizes an image according to the width and height specified, * keeping the aspect ratio if required.

* * If set to true, the bestfit option will keep the image within the dimensions specified * without losing the aspect ratio.

* * If set to false, the blowup option will not enlarge an image that is already smaller * then the sepcified target dimensions.

* * @param image the image to resize * @param width the width of the target image * @param height the height of the target image * @param bestfit if true, the aspect ratio of the image will be kept * @param blowup if false, smaller images will not be enlarged to fit in the target dimensions * * @return the transformed image */ public BufferedImage resize(BufferedImage image, int width, int height, boolean bestfit, boolean blowup) { int imageWidth = image.getWidth(); int imageHeight = image.getHeight(); if (((imageWidth == width) && (imageHeight == height)) || (!blowup && (imageWidth < width) && (imageHeight < height))) { // no resize required return image; } float widthScale = (width / (float)imageWidth); float heightScale = (height / (float)imageHeight); int targetWidth = width; int targetHeight = height; if (bestfit) { // keep image aspect ratio, find best scale for the result image if (widthScale < heightScale) { heightScale = widthScale; targetHeight = (int)(imageHeight * heightScale); } else if (widthScale > heightScale) { widthScale = heightScale; targetWidth = (int)(imageWidth * widthScale); } } return scale(image, widthScale, heightScale, targetWidth, targetHeight); } /** * Resizes the given image to best fit into the given dimensions (only if it does not already * fit in the dimensions), placing the scaled image * at the indicated position on a background with the given color. * * @param image the image to resize * @param width the width of the target image * @param height the height of the target image * @param backgroundColor * @param position the position to place the scaled image at * * @return the transformed image */ public BufferedImage resize(BufferedImage image, int width, int height, Color backgroundColor, int position) { return resize(image, width, height, backgroundColor, position, true); } /** * Resizes the given image to best fit into the given dimensions (only if it does not already * fit in the dimensions), placing the scaled image * at the indicated position on a background with the given color. * * @param image the image to resize * @param width the width of the target image * @param height the height of the target image * @param backgroundColor * @param position the position to place the scaled image at * @param blowup if false, smaller images will not be enlarged to fit in the target dimensions * * @return the transformed image */ public BufferedImage resize( BufferedImage image, int width, int height, Color backgroundColor, int position, boolean blowup) { // resize the image to fit into the required dimension BufferedImage scaled = resize(image, width, height, true, blowup); // check if the image fits after rescale int scaledWidth = scaled.getWidth(); int scaledHeight = scaled.getHeight(); // check the background color ColorModel cm = scaled.getColorModel(); if (!cm.hasAlpha() && (backgroundColor == COLOR_TRANSPARENT)) { // alpha not supported by target color model backgroundColor = m_renderSettings.getTransparentReplaceColor(); } if ((scaledWidth == width) && (scaledHeight == height)) { if (image.getColorModel().hasAlpha() && (backgroundColor != COLOR_TRANSPARENT)) { // background color replacement may be required position = Simapi.POS_UP_LEFT; } else { // no resize required and also no background color change return scaled; } } threadSetNice(); // create the background image BufferedImage result = createImage(scaled.getColorModel(), width, height); Graphics2D g = result.createGraphics(); if (backgroundColor != COLOR_TRANSPARENT) { // don't fill if background is transparent g.setPaintMode(); g.setColor(backgroundColor); g.fillRect(0, 0, width, height); } int x; int y; switch (position) { case Simapi.POS_DOWN_LEFT: x = 0; y = height - scaledHeight; break; case Simapi.POS_DOWN_RIGHT: x = width - scaledWidth; y = height - scaledHeight; break; case Simapi.POS_STRAIGHT_DOWN: x = (width - scaledWidth) / 2; y = height - scaledHeight; break; case Simapi.POS_STRAIGHT_LEFT: x = 0; y = (height - scaledHeight) / 2; break; case Simapi.POS_STRAIGHT_RIGHT: x = width - scaledWidth; y = (height - scaledHeight) / 2; break; case Simapi.POS_STRAIGHT_UP: x = (width - scaledWidth) / 2; y = 0; break; case Simapi.POS_UP_LEFT: x = 0; y = 0; break; case Simapi.POS_UP_RIGHT: x = width - scaledWidth; y = 0; break; default: // crop center x = (width - scaledWidth) / 2; y = (height - scaledHeight) / 2; } // draw the scaled image to the conext at the target position g.drawImage(scaled, x, y, null); g.dispose(); scaled.flush(); scaled = null; threadSetNormal(); return result; } /** * Resizes an image according to the width and height specified, * cropping the image along the sides in case the required height and with can not be reached without * changing the apsect ratio of the image.

* * Use the constants {@link Simapi#POS_CENTER} etc. to indicate the crop position.

* * @param image the image to resize * @param width the width of the target image * @param height the height of the target image * @param position the position to place the cropped image at * * @return the transformed image */ public BufferedImage resize(BufferedImage image, int width, int height, int position) { int imageWidth = image.getWidth(); int imageHeight = image.getHeight(); if ((imageWidth == width) && (imageHeight == height)) { // no resize required return image; } float widthScale = (width / (float)imageWidth); float heightScale = (height / (float)imageHeight); // keep image aspect ratio, find best scale for the result image if (widthScale >= heightScale) { heightScale = widthScale; } else { widthScale = heightScale; } BufferedImage scaledImage; if ((widthScale != 1.0) && (heightScale != 1.0)) { // scale the image to the required size scaledImage = scale(image, widthScale, heightScale); // reset to new scale imageWidth = scaledImage.getWidth(); imageHeight = scaledImage.getHeight(); } else { // no scale required scaledImage = image; } // return the cropped result return crop(scaledImage, width, height, position); } /** * Scales an image according to the given scale factor.

* * If the scale is 2.0, the result image will be twice as large as the original, * if the scale is 0.5, the result image will be half as large as the original.

* * @param image the image to scale * @param scale the scale factor * * @return the transformed image */ public BufferedImage scale(BufferedImage image, float scale) { return scale(image, scale, scale); } /** * Scale the image with different ratios along the width and height.

* * @param image the image to scale * @param widthScale the scale factor for the width * @param heightScale the scale factor for the height * * @return the transformed image */ public BufferedImage scale(BufferedImage image, float widthScale, float heightScale) { int targetWidth = Math.round(image.getWidth() * widthScale); int targetHeight = Math.round(image.getHeight() * heightScale); return scale(image, widthScale, heightScale, targetWidth, targetHeight); } /** * Scale the image with different ratios along the width and height to the given target dimensions.

* * Giving both scale factor and target dimensions is required to avoid rounding errors that * lead to the "missing line" issue.

* * @param image the image to scale * @param widthScale the scale factor for the width * @param heightScale the scale factor for the height * @param targetWidth the width of the target image * @param targetHeight the height of the target image * * @return the transformed image */ public BufferedImage scale( BufferedImage image, float widthScale, float heightScale, int targetWidth, int targetHeight) { int width = image.getWidth(); int height = image.getHeight(); // ensure the image uses a RGB system color model (otherwise operation may take very long and results may be bad quality) image = ensureImageIsSystemType(image, m_renderSettings.getTransparentReplaceColor() != COLOR_TRANSPARENT); RenderingHints renderHints = m_renderSettings.getRenderingHints(); if (renderHints == RenderSettings.HINTS_QUALITY) { // default render setting, adjust for thumbnail generation to avoid "slow scaling" issue if (((widthScale < 0.25f) && (heightScale < 0.25f)) || ((widthScale < 0.5f) && (width * widthScale < 101)) || ((heightScale < 0.5f) && (height * heightScale < 101))) { // thumbnail generation, use speed settings renderHints = RenderSettings.HINTS_SPEED; } } threadSetNice(); double factor = ((image.getWidth() / (widthScale * width)) + (image.getHeight() / (heightScale * height))) / 2.0; if (m_renderSettings.isUseBlur() && ((width * height) < m_renderSettings.getMaximumBlurSize()) && ((widthScale < 0.575f) || (heightScale < 0.575f))) { // must apply blur or the result will look jagged if scale is smaller then 0.5 // (actually close to 0.5 it also looks jagged, so we use 0.575 instead) // however, if the image is to big, "out of memory" issues may occur int average = (width + height) / 2; if ((factor < 5.0) && (average < 900)) { // image is quite small and suitable factor - use gaussian blur GaussianFilter gauss = new GaussianFilter(); double radius = Math.sqrt(2.0 * factor); gauss.setRadius((float)radius); image = gauss.filter(image, null); } else { // image is rather large, use much faster box blur double root = Math.sqrt(factor); int radius; if (factor < 2.5) { // this is a rather small scale factor, use Math.floor() or image might get blurry radius = (int)Math.floor(root); } else { // scale factor is rather large, use Math.round() for better result radius = (int)Math.round(root); } BoxBlurFilter blur = new BoxBlurFilter(); blur.setRadius(radius); image = blur.filter(image, null); } } // create the image scaling transformation AffineTransform at = AffineTransform.getScaleInstance(widthScale, heightScale); AffineTransformOp ato = new AffineTransformOp(at, renderHints); // must create the result image manually, otherwise the size of the result image may be 1 pixel off BufferedImage result = createImage(image.getColorModel(), targetWidth, targetHeight); result = ato.filter(image, result); threadSetNormal(); return result; } /** * Writes an image to a local file.

* * @param image the image to write * @param destination the destination file * @param type the type of the image to write * * @throws IOException in case the image could not be written */ public void write(BufferedImage image, File destination, String type) throws IOException { write(image, (Object)destination, type); } /** * Writes an image to an output stream.

* * @param image the image to write * @param destination the output stream to write the image to * @param type the type of the image to write * * @throws IOException in case the image could not be written */ public void write(BufferedImage image, OutputStream destination, String type) throws IOException { write(image, (Object)destination, type); } /** * Writes an image to a local file.

* * @param image the image to write * @param destination the destination file name * @param type the type of the image to write * * @throws IOException in case the image could not be written */ public void write(BufferedImage image, String destination, String type) throws IOException { write(image, new File(destination), type); } /** * Creates a buffered image that has the given dimensions and uses the given color model.

* * @param colorModel the color model to use * @param width the width of the image to create * @param height the height of the image to create * * @return a new image with the given dimensions and uses the given color model */ protected BufferedImage createImage(ColorModel colorModel, int width, int height) { BufferedImage result; if ((colorModel.getTransparency() == Transparency.OPAQUE) && (m_renderSettings.getTransparentReplaceColor() != COLOR_TRANSPARENT)) { result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); } else { result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); } return result; } /** * Writes an image to the given output object, using the the given quality.

* * The quality is used only if the image type supports different qualities. * For example, this it is used when writing JPEG images. * A quality of 0.1 is very poor, 0.75 is ok, 1.0 is maximum.

* * @param im the image to write * @param output the destination to write the image to * @param formatName the type of the image to write * * @throws IOException in case the image could not be written */ protected void write(BufferedImage im, Object output, String formatName) throws IOException { if (output == null) { throw new IllegalArgumentException("output == null!"); } if (im == null) { throw new IllegalArgumentException("image == null!"); } if (formatName == null) { throw new IllegalArgumentException("formatName == null!"); } // create the output stream ImageOutputStream stream = null; try { stream = ImageIO.createImageOutputStream(output); } catch (IOException e) { throw new IIOException("Can't create output stream!", e); } // make sure we have our exact constants to work with formatName = getImageType(formatName); if (formatName == null) { throw new IllegalArgumentException("no writers found for format '" + formatName + "'"); } // make sure there are no transparent pixels left if not supported by the written image format if (im.getColorModel().hasAlpha() && ((TYPE_JPEG == formatName) || (TYPE_TIFF == formatName) || (TYPE_BMP == formatName))) { // several formats do not support alpha BufferedImage result = new BufferedImage(im.getWidth(), im.getHeight(), BufferedImage.TYPE_INT_RGB); Graphics2D g = result.createGraphics(); g.setPaintMode(); g.setColor(m_renderSettings.getTransparentReplaceColor()); g.fillRect(0, 0, result.getWidth(), result.getHeight()); g.drawImage(im, 0, 0, null); g.dispose(); im = result; } // obtain the writer for the image // this must work since it is already done in the #getImageType(String) call above ImageWriter writer = (ImageWriter)ImageIO.getImageWritersByFormatName(formatName).next(); // get default image writer parameter ImageWriteParam param = writer.getDefaultWriteParam(); if (param.canWriteCompressed()) { // set compression parameters if supported by writer param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); if ((param.getCompressionTypes() != null) && (param.getCompressionType() == null)) { // a compression parameter is required but not provided, use the first one available param.setCompressionType(param.getCompressionTypes()[0]); } param.setCompressionQuality(m_renderSettings.getCompressionQuality()); } // now write the image writer.setOutput(stream); writer.write(null, new IIOImage(im, null, null), param); stream.flush(); writer.dispose(); stream.close(); } private void threadSetNice() { Thread t = Thread.currentThread(); if (t.getPriority() > m_renderSettings.getThreadNicePriority()) { m_renderSettings.setThreadOldPriority(t.getPriority()); try { t.setPriority(m_renderSettings.getThreadNicePriority()); } catch (Exception e) { // can't set thread priority, continue with current priority } } } private void threadSetNormal() { Thread t = Thread.currentThread(); if (t.getPriority() != m_renderSettings.getThreadOldPriority()) { try { t.setPriority(m_renderSettings.getThreadOldPriority()); } catch (Exception e) { // can't set thread priority, continue with current priority } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy