package org.jboss.seam.ui.graphicImage; import java.awt.Color; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.awt.image.ConvolveOp; import java.awt.image.Kernel; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.net.URL; import java.util.Arrays; import java.util.Iterator; import java.util.List; import javax.imageio.ImageIO; import javax.imageio.ImageReadParam; import javax.imageio.ImageReader; import javax.imageio.stream.ImageInputStream; import org.jboss.seam.Component; import org.jboss.seam.annotations.Install; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.intercept.BypassInterceptors; import org.jboss.seam.contexts.Contexts; import org.jboss.seam.core.ResourceLoader; /** * Image manipulation and interrogation * * @author pmuir * */ @Name("org.jboss.seam.graphicImage.image") @Install(precedence = Install.BUILT_IN) @BypassInterceptors public class Image implements Serializable { public enum Type { IMAGE_PNG("image/png", ".png", "PNG"), IMAGE_JPEG("image/jpeg", ".jpg", "JPEG", "image/jpg"), IMAGE_GIF( "image/gif", ".gif", "GIF"), IMAGE_BMP("image/bmp", ".bmp", "BMP") ; private String mimeType; private String extension; private String imageFormatName; private List<String> alternativeMimeTypes; Type(String mimeType, String extension, String imageFormatName, String... alternativeMimeTypes) { this.mimeType = mimeType; this.extension = extension; this.alternativeMimeTypes = Arrays.asList(alternativeMimeTypes); this.imageFormatName = imageFormatName; } public String getMimeType() { return mimeType; } public String getExtension() { return extension; } public List<String> getAlternativeMimeTypes() { return alternativeMimeTypes; } protected String getImageFormatName() { return imageFormatName; } public static Type getTypeByMimeType(String mimeType) { for (Type type : values()) { if (type.getMimeType().equals(mimeType) || type.alternativeMimeTypes.contains(mimeType)) { return type; } } return null; } public static Type getTypeByFormatName(String formatName) { for (Type type : values()) { if (type.getImageFormatName().equalsIgnoreCase(formatName)) { return type; } } return null; } } public static final int PNG_IMAGE_TYPE = BufferedImage.TYPE_INT_ARGB; public static final int DEFAULT_IMAGE_TYPE = BufferedImage.TYPE_INT_RGB; private static final Type DEFAULT_CONTENT_TYPE = Type.IMAGE_PNG; private transient Object input; private byte[] output; private boolean dirty; private Type contentType = DEFAULT_CONTENT_TYPE; private transient BufferedImage bufferedImage; public Image() { } /** * Set the image. This can be one of String (loaded from the classpath), a * URL, a File, an InputStream or a byte[] * * @param value * @throws IOException */ public Image setInput(Object value) throws IOException { this.input = value; readImage(); return this; } /** * Get the image as a byte[], any conversions having been applied. Returns null if the * image could not be read */ public byte[] getImage() throws IOException { if ((dirty || output == null) && bufferedImage != null) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ImageIO.write(bufferedImage, getContentType().getImageFormatName(), outputStream); output = outputStream.toByteArray(); } return output; } /** * The content type of the output image, by default DEFAULT_CONTENT_TYPE */ public Type getContentType() { return contentType; } public void setContentType(Type contentType) { this.contentType = contentType; } public BufferedImage getBufferedImage() { return bufferedImage; } public void setBufferedImage(BufferedImage bufferedImage) { this.bufferedImage = bufferedImage; dirty = true; } /** * The aspect ratio of the image */ public Double getRatio() throws IOException { if (bufferedImage == null) { return null; } // Do the operation with double precision Double ratio = (double) bufferedImage.getWidth() / (double) bufferedImage.getHeight(); return ratio; } /** * Check whether the image is of a given ratio to within a given precision */ public Boolean isRatio(double ratio, double precision) throws IOException { if (bufferedImage == null) { return null; } double error = ratio * precision; return (ratio - error) < getRatio() && getRatio() <= (ratio + error); } /** * The width of the image */ public Integer getWidth() throws IOException { if (bufferedImage == null) { return null; } return bufferedImage.getWidth(); } /** * The height of the image */ public Integer getHeight() throws IOException { if (bufferedImage == null) { return null; } return bufferedImage.getHeight(); } /** * Alter the ratio of the output image <b>without</b> altering the ratio of * the input by adding transparent strips. If the image is already of the * correct ratio (to within the given precision) nothing happens */ public Image adjustRatio(double desiredRatio, double precision) throws InterruptedException, IOException { if (bufferedImage == null) { return this; } if (!isRatio(desiredRatio, precision)) { if (getRatio() > desiredRatio) { // The image is too wide - add a transparent strip across the // top/bottom to make the image squarer double desiredHeight = getRatio() * getHeight() / desiredRatio; double stripHeight = (desiredHeight - getHeight()) / 2; BufferedImage newImage = new BufferedImage(getWidth(), (int) (getHeight() + stripHeight * 2), getImageType()); Graphics2D graphics2D = createGraphics(newImage); graphics2D.drawImage(bufferedImage, 0, (int) stripHeight, null); bufferedImage = newImage; } else if (getRatio() < desiredRatio) { // The image is too wide - add a transparent strip across the // top/bottom to make the image squarer double desiredWidth = getRatio() * getWidth() / desiredRatio; double stripWidth = (desiredWidth - getWidth()) / 2; BufferedImage newImage = new BufferedImage((int) (getWidth() + stripWidth * 2), getHeight(), getImageType()); Graphics2D graphics2D = createGraphics(newImage); graphics2D.drawImage(bufferedImage, (int) stripWidth, 0, null); bufferedImage = newImage; } dirty = true; } return this; } /** * Blur the output image using a convolution */ public Image blur(int radius) throws IOException { if (bufferedImage == null) { return this; } BufferedImage newImage = new BufferedImage(getWidth(), getHeight(), getImageType()); int blurWidth = ((radius - 1) * 2 + 1); int pixels = blurWidth * blurWidth; float weight = 1.0f/ pixels; float[] elements = new float[pixels]; for (int i = 0; i < pixels; i++) { elements[i] = weight; } Kernel kernel = new Kernel(blurWidth, blurWidth, elements); ConvolveOp simpleBlur = new ConvolveOp(kernel); simpleBlur.filter(bufferedImage, newImage); bufferedImage = newImage; dirty = true; return this; } /** * Scale the image to the given width */ public Image scaleToWidth(int width) throws IOException { if (bufferedImage == null) { return this; } // Always scale, never stretch. We don't care if the requested scaled // ratio is different from the current int height = width * getHeight() / getWidth(); BufferedImage newImage = new BufferedImage(width, height, getImageType()); Graphics2D graphics2D = createGraphics(newImage); graphics2D.drawImage(bufferedImage, 0, 0, width, height, null); bufferedImage = newImage; dirty = true; return this; } public Image scaleToFit(int height, int width) throws IOException { float hratio = height/getHeight(); float wratio = width/getWidth(); if (hratio < wratio) { return scaleToHeight(height); } else { return scaleToWidth(width); } } /** * Scale the image to the given height */ public Image scaleToHeight(int height) throws IOException { if (bufferedImage == null) { return this; } // Always scale, never stretch. We don't care if the requested scaled // ratio is different from the current int width = height * getWidth() / getHeight(); BufferedImage newImage = new BufferedImage(width, height, getImageType()); Graphics2D graphics2D = createGraphics(newImage); graphics2D.drawImage(bufferedImage, 0, 0, width, height, null); bufferedImage = newImage; dirty = true; return this; } /** * Scale the image by the given factor */ public Image scale(double factor) throws IOException { if (bufferedImage == null) { return this; } int width = (int) (getWidth() * factor); int height = (int) (getHeight() * factor); BufferedImage newImage = new BufferedImage(width, height, getImageType()); Graphics2D graphics2D = createGraphics(newImage); graphics2D.drawImage(bufferedImage, 0, 0, width, height, null); bufferedImage = newImage; dirty = true; return this; } /** * Resize the image to the given width and height, changing the ratio * if necessary */ public Image resize(int width, int height) { if (bufferedImage == null) { return this; } BufferedImage newImage = new BufferedImage(width, height, getImageType()); Graphics2D graphics2D = createGraphics(newImage); graphics2D.drawImage(bufferedImage, 0, 0, width, height, null); bufferedImage = newImage; dirty = true; return this; } private void readImage() throws IOException { if (input instanceof URL) { readImage(((URL) input).openStream()); } else if (input instanceof File) { readImage(((File) input).toURL().openStream()); } else if (input instanceof String) { readImage(ResourceLoader.instance().getResourceAsStream((String) input)); } else if (input instanceof InputStream) { readImage((InputStream) input); } else if (input != null && input.getClass().isArray()) { if (input.getClass().getComponentType().isAssignableFrom(Byte.TYPE)) { byte[] b = (byte[]) input; readImage(new ByteArrayInputStream(b)); } } } /** * Create Canvas, set some defaults (bg colour, rendering hints) * * @param image */ private Graphics2D createGraphics(BufferedImage image) { if (image == null) { return null; } Graphics2D graphics2D = image.createGraphics(); graphics2D.setBackground(new Color(255, 255, 255)); if (Type.IMAGE_PNG.equals(getContentType())) { graphics2D.setComposite(java.awt.AlphaComposite.Src); } graphics2D.clearRect(0, 0, image.getWidth(), image.getHeight()); graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); return graphics2D; } public static Image instance() { if (!Contexts.isConversationContextActive()) { throw new IllegalStateException("No active conversation scope"); } return (Image) Component.getInstance(Image.class); } private void readImage(InputStream inputStream) throws IOException { if (inputStream == null) { throw new IllegalArgumentException("Image pointed to must exist (input stream must not be null)"); } ImageInputStream stream = ImageIO.createImageInputStream(inputStream); if (stream == null) { throw new IllegalArgumentException("Error creating image input stream from image"); } Iterator iter = ImageIO.getImageReaders(stream); if (!iter.hasNext()) { return; } ImageReader reader = (ImageReader) iter.next(); ImageReadParam param = reader.getDefaultReadParam(); reader.setInput(stream, true, true); String type = reader.getFormatName(); setContentType(Type.getTypeByFormatName(type)); bufferedImage = reader.read(0, param); stream.close(); reader.dispose(); dirty = true; inputStream.close(); } private int getImageType() { if (Type.IMAGE_PNG.equals(getContentType())) { return PNG_IMAGE_TYPE; } else { return DEFAULT_IMAGE_TYPE; } } }