Java tutorial
/* * Weblounge: Web Content Management System * Copyright (c) 2003 - 2011 The Weblounge Team * http://entwinemedia.com/weblounge * * This program 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 * of the License, or (at your option) any later version. * * This program 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. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package ch.entwine.weblounge.preview.jai; import ch.entwine.weblounge.common.content.Resource; import ch.entwine.weblounge.common.content.ResourceContent; import ch.entwine.weblounge.common.content.image.ImagePreviewGenerator; import ch.entwine.weblounge.common.content.image.ImageResource; import ch.entwine.weblounge.common.content.image.ImageStyle; import ch.entwine.weblounge.common.impl.content.image.ImageStyleUtils; import ch.entwine.weblounge.common.language.Language; import ch.entwine.weblounge.common.site.Environment; import ch.entwine.weblounge.common.site.ImageScalingMode; import com.sun.media.jai.codec.FileCacheSeekableStream; import com.sun.media.jai.codec.SeekableStream; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.RenderingHints; import java.awt.image.renderable.ParameterBlock; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import javax.imageio.ImageIO; import javax.media.jai.BorderExtender; import javax.media.jai.Interpolation; import javax.media.jai.JAI; import javax.media.jai.RenderedOp; /** * Utility class used for dealing with images and image styles. */ public final class JAIPreviewGenerator implements ImagePreviewGenerator { /** The logging facility */ private static final Logger logger = LoggerFactory.getLogger(JAIPreviewGenerator.class); /** * {@inheritDoc} * * @see ch.entwine.weblounge.common.content.PreviewGenerator#supports(ch.entwine.weblounge.common.content.Resource) */ public boolean supports(Resource<?> resource) { return (resource instanceof ImageResource); } /** * {@inheritDoc} * * @see ch.entwine.weblounge.common.content.PreviewGenerator#supports(java.lang.String) */ public boolean supports(String format) { if (format == null) throw new IllegalArgumentException("Format cannot be null"); return ImageIO.getImageWritersBySuffix(format.toLowerCase()).hasNext(); } /** * {@inheritDoc} * * @see ch.entwine.weblounge.common.content.PreviewGenerator#createPreview(ch.entwine.weblounge.common.content.Resource, * ch.entwine.weblounge.common.site.Environment, * ch.entwine.weblounge.common.language.Language, * ch.entwine.weblounge.common.content.image.ImageStyle, String, * java.io.InputStream, java.io.OutputStream) */ public void createPreview(Resource<?> resource, Environment environment, Language language, ImageStyle style, String format, InputStream is, OutputStream os) throws IOException { if (format == null) { if (resource == null) throw new IllegalArgumentException("Resource cannot be null"); if (resource.getContent(language) == null) { logger.warn("Skipping creation of preview for {} in language '{}': no content", resource, language.getIdentifier()); return; } String mimetype = resource.getContent(language).getMimetype(); logger.trace("Image preview is generated using the resource's mimetype '{}'", mimetype); format = mimetype.substring(mimetype.indexOf("/") + 1); } style(is, os, format, style); } /** * {@inheritDoc} * * @see ch.entwine.weblounge.common.content.image.ImagePreviewGenerator#createPreview(java.io.File, * ch.entwine.weblounge.common.site.Environment, * ch.entwine.weblounge.common.language.Language, * ch.entwine.weblounge.common.content.image.ImageStyle, * java.lang.String, java.io.InputStream, java.io.OutputStream) */ public void createPreview(File imageFile, Environment environment, Language language, ImageStyle style, String format, InputStream is, OutputStream os) throws IOException { if (format == null) { if (imageFile == null) throw new IllegalArgumentException("Image file cannot be null"); format = FilenameUtils.getExtension(imageFile.getName()); logger.trace("Image preview is generated as '{}'", format); } style(is, os, format, style); } /** * {@inheritDoc} * * @see ch.entwine.weblounge.common.content.PreviewGenerator#getContentType(ch.entwine.weblounge.common.content.Resource, * ch.entwine.weblounge.common.language.Language, * ch.entwine.weblounge.common.content.image.ImageStyle) */ public String getContentType(Resource<?> resource, Language language, ImageStyle style) { String mimetype = resource.getContent(language).getMimetype(); return mimetype; } /** * {@inheritDoc} * * @see ch.entwine.weblounge.common.content.PreviewGenerator#getSuffix(ch.entwine.weblounge.common.content.Resource, * ch.entwine.weblounge.common.language.Language, * ch.entwine.weblounge.common.content.image.ImageStyle) */ public String getSuffix(Resource<?> resource, Language language, ImageStyle style) { // Load the resource ResourceContent content = resource.getContent(language); if (content == null) { content = resource.getOriginalContent(); if (content == null) { logger.warn("Trying to get filename suffix for {}, which has no content", resource); return null; } } // Get the file name String filename = content.getFilename(); if (StringUtils.isBlank(filename)) { logger.warn("Trying to get filename suffix for {}, which has no filename", resource); return null; } // Add the file identifier name if (StringUtils.isNotBlank(style.getIdentifier())) { filename += "-" + style.getIdentifier(); } return FilenameUtils.getExtension(filename); } /** * {@inheritDoc} * * @see ch.entwine.weblounge.common.content.PreviewGenerator#getPriority() */ public int getPriority() { return 0; } /** * Resizes the given image to what is defined by the image style and writes * the result to the output stream. * * @param is * the input stream * @param os * the output stream * @param format * the image format * @param style * the style * @throws IllegalArgumentException * if the image is in an unsupported format * @throws IllegalArgumentException * if the input stream is empty * @throws IOException * if reading from or writing to the stream fails * @throws OutOfMemoryError * if the image is too large to be processed in memory */ private void style(InputStream is, OutputStream os, String format, ImageStyle style) throws IllegalArgumentException, IOException, OutOfMemoryError { // Does the input stream contain any data? if (is.available() == 0) throw new IllegalArgumentException("Empty input stream was passed to image styling"); // Do we need to do any work at all? if (style == null || ImageScalingMode.None.equals(style.getScalingMode())) { logger.trace("No scaling needed, performing a noop stream copy"); IOUtils.copy(is, os); return; } SeekableStream seekableInputStream = null; RenderedOp image = null; try { // Load the image from the given input stream seekableInputStream = new FileCacheSeekableStream(is); image = JAI.create("stream", seekableInputStream); if (image == null) throw new IOException("Error reading image from input stream"); // Get the original image size int imageWidth = image.getWidth(); int imageHeight = image.getHeight(); // Resizing float scale = ImageStyleUtils.getScale(imageWidth, imageHeight, style); RenderingHints scaleHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); scaleHints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); scaleHints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); scaleHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); int scaledWidth = Math.round(scale * image.getWidth()); int scaledHeight = Math.round(scale * image.getHeight()); int cropX = 0; int cropY = 0; // If either one of scaledWidth or scaledHeight is < 1.0, then // the scale needs to be adapted to scale to 1.0 exactly and accomplish // the rest by cropping. if (scaledWidth < 1.0f) { scale = 1.0f / imageWidth; scaledWidth = 1; cropY = imageHeight - scaledHeight; scaledHeight = Math.round(imageHeight * scale); } else if (scaledHeight < 1.0f) { scale = 1.0f / imageHeight; scaledHeight = 1; cropX = imageWidth - scaledWidth; scaledWidth = Math.round(imageWidth * scale); } if (scale > 1.0) { ParameterBlock scaleParams = new ParameterBlock(); scaleParams.addSource(image); scaleParams.add(scale).add(scale).add(0.0f).add(0.0f); scaleParams.add(Interpolation.getInstance(Interpolation.INTERP_BICUBIC_2)); image = JAI.create("scale", scaleParams, scaleHints); } else if (scale < 1.0) { ParameterBlock subsampleAverageParams = new ParameterBlock(); subsampleAverageParams.addSource(image); subsampleAverageParams.add(Double.valueOf(scale)); subsampleAverageParams.add(Double.valueOf(scale)); image = JAI.create("subsampleaverage", subsampleAverageParams, scaleHints); } // Cropping cropX = (int) Math.max(cropX, (float) Math.ceil(ImageStyleUtils.getCropX(scaledWidth, scaledHeight, style))); cropY = (int) Math.max(cropY, (float) Math.ceil(ImageStyleUtils.getCropY(scaledWidth, scaledHeight, style))); if ((cropX > 0 && Math.floor(cropX / 2.0f) > 0) || (cropY > 0 && Math.floor(cropY / 2.0f) > 0)) { ParameterBlock cropTopLeftParams = new ParameterBlock(); cropTopLeftParams.addSource(image); cropTopLeftParams.add(cropX > 0 ? ((float) Math.floor(cropX / 2.0f)) : 0.0f); cropTopLeftParams.add(cropY > 0 ? ((float) Math.floor(cropY / 2.0f)) : 0.0f); cropTopLeftParams.add(scaledWidth - Math.max(cropX, 0.0f)); // width cropTopLeftParams.add(scaledHeight - Math.max(cropY, 0.0f)); // height RenderingHints croppingHints = new RenderingHints(JAI.KEY_BORDER_EXTENDER, BorderExtender.createInstance(BorderExtender.BORDER_COPY)); image = JAI.create("crop", cropTopLeftParams, croppingHints); } // Write resized/cropped image encoded as JPEG to the output stream ParameterBlock encodeParams = new ParameterBlock(); encodeParams.addSource(image); encodeParams.add(os); encodeParams.add("jpeg"); JAI.create("encode", encodeParams); } catch (Throwable t) { if (t.getClass().getName().contains("ImageFormat")) { throw new IllegalArgumentException(t.getMessage()); } } finally { IOUtils.closeQuietly(seekableInputStream); if (image != null) image.dispose(); } } }