From: <fg...@us...> - 2011-06-23 10:11:36
|
Revision: 3547 http://openutils.svn.sourceforge.net/openutils/?rev=3547&view=rev Author: fgiust Date: 2011-06-23 10:11:29 +0000 (Thu, 23 Jun 2011) Log Message: ----------- MEDIA-237 improved execution speed by using AffineTransform and avoiding filesystem usage Modified Paths: -------------- trunk/openutils-mgnlmedia/src/main/java/net/sourceforge/openutils/mgnlmedia/media/utils/ImageUtils.java Modified: trunk/openutils-mgnlmedia/src/main/java/net/sourceforge/openutils/mgnlmedia/media/utils/ImageUtils.java =================================================================== --- trunk/openutils-mgnlmedia/src/main/java/net/sourceforge/openutils/mgnlmedia/media/utils/ImageUtils.java 2011-06-22 16:12:27 UTC (rev 3546) +++ trunk/openutils-mgnlmedia/src/main/java/net/sourceforge/openutils/mgnlmedia/media/utils/ImageUtils.java 2011-06-23 10:11:29 UTC (rev 3547) @@ -31,19 +31,18 @@ import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Graphics2D; -import java.awt.Image; import java.awt.Point; import java.awt.RenderingHints; import java.awt.Transparency; +import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; -import java.io.BufferedInputStream; import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.GregorianCalendar; @@ -75,7 +74,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.sun.image.codec.jpeg.JPEGEncodeParam; +import com.sun.image.codec.jpeg.JPEGImageEncoder; + /** * Main utility class that works with images and media nodes * @author molaschi @@ -249,21 +251,100 @@ } } + graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + + AffineTransform at = new AffineTransform(); + double delta = ((double) x) / original.getWidth(); if (x > original.getWidth()) { - graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); - graphics2D.drawImage(original, (canvasX - x) / 2, (canvasY - y) / 2, x, y, background, null); + at.scale(delta, delta); + at.translate((canvasX - x) / (2 * delta), (canvasY - y) / (2 * delta)); } - else + else if (x < original.getWidth()) { - Image scaledInstance = original.getScaledInstance(x, y, Image.SCALE_SMOOTH); - graphics2D.drawImage(scaledInstance, (canvasX - x) / 2, (canvasY - y) / 2, x, y, background, null); + original = getScaledInstance(original, x, y, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true); + at.translate((canvasX - x) / 2.0, (canvasY - y) / 2.0); } + graphics2D.drawImage(original, at, null); + graphics2D.dispose(); + return resizedImage; } /** + * Convenience method that returns a scaled instance of the provided {@code BufferedImage}. + * @param img the original image to be scaled + * @param targetWidth the desired width of the scaled instance, in pixels + * @param targetHeight the desired height of the scaled instance, in pixels + * @param hint one of the rendering hints that corresponds to {@code RenderingHints.KEY_INTERPOLATION} (e.g. + * {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR}, {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR}, + * {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC}) + * @param higherQuality if true, this method will use a multi-step scaling technique that provides higher quality + * than the usual one-step technique (only useful in downscaling cases, where {@code targetWidth} or + * {@code targetHeight} is smaller than the original dimensions, and generally only when the {@code BILINEAR} hint + * is specified) + * @return a scaled version of the original {@code BufferedImage} + */ + public static BufferedImage getScaledInstance(BufferedImage img, int targetWidth, int targetHeight, Object hint, + boolean higherQuality) + { + int type = (img.getTransparency() == Transparency.OPAQUE) + ? BufferedImage.TYPE_INT_RGB + : BufferedImage.TYPE_INT_ARGB; + BufferedImage ret = img; + int w, h; + if (higherQuality) + { + // Use multi-step technique: start with original size, then + // scale down in multiple passes with drawImage() + // until the target size is reached + w = img.getWidth(); + h = img.getHeight(); + } + else + { + // Use one-step technique: scale directly from original + // size to target size with a single drawImage() call + w = targetWidth; + h = targetHeight; + } + + do + { + if (higherQuality && w > targetWidth) + { + w /= 2; + + if (w < targetWidth) + { + w = targetWidth; + } + } + + if (higherQuality && h > targetHeight) + { + h /= 2; + if (h < targetHeight) + { + h = targetHeight; + } + } + + BufferedImage tmp = new BufferedImage(w, h, type); + Graphics2D g2 = tmp.createGraphics(); + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint); + g2.drawImage(ret, 0, 0, w, h, null); + g2.dispose(); + + ret = tmp; + } + while (w != targetWidth || h != targetHeight); + + return ret; + } + + /** * Crop an image from left, top for width, height * @param original original image * @param left start crop point left @@ -277,18 +358,14 @@ BufferedImage resizedImage = new BufferedImage(width, height, getType(original.getColorModel())); Graphics2D graphics2D = resizedImage.createGraphics(); graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - if (width > original.getWidth()) - { - graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); - } - else - { - graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - } + graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_DEFAULT); - graphics2D.drawImage(original, 0, 0, width, height, left, top, width + left, height + top, null); + AffineTransform at = new AffineTransform(); + at.translate(-left, -top); + graphics2D.drawImage(original, at, null); + graphics2D.dispose(); + return resizedImage; } @@ -398,70 +475,90 @@ float quality, boolean forceProgressive) throws RepositoryException, IOException { - CountBytesBufferedInputStream stream = null; + Content resolutions = getResolutionsNode(saveTo); + if (resolutions == null) + { + resolutions = saveTo.createContent("resolutions", MediaConfigurationManager.RESOLUTIONS); + resolutions.getMetaData().setModificationDate(); + } - try + String resolution = name; + if (resolution == null) { - stream = new CountBytesBufferedInputStream(getStream(image, extension, quality, forceProgressive)); + resolution = "res-" + image.getWidth() + "x" + image.getHeight(); + } - if (stream == null) - { - throw new IllegalArgumentException("Stream is null"); - } + String originalRes = resolution; + resolution = getResolutionPath(resolution); - Content resolutions = getResolutionsNode(saveTo); - if (resolutions == null) - { - resolutions = saveTo.createContent("resolutions", MediaConfigurationManager.RESOLUTIONS); - resolutions.getMetaData().setModificationDate(); - } + synchronized (ExclusiveWrite.getInstance()) + { - String resolution = name; - if (resolution == null) + if (resolutions.hasNodeData(resolution)) { - resolution = "res-" + image.getWidth() + "x" + image.getHeight(); + NodeData nd = resolutions.getNodeData(resolution); + nd.delete(); } - String originalRes = resolution; - resolution = getResolutionPath(resolution); + // don't remove deprecated method call, needed for magnolia 4.0 compatibility + final NodeData nd = resolutions.createNodeData(resolution, PropertyType.BINARY); + final PipedInputStream stream = new PipedInputStream(); + PipedOutputStream outputstream = new PipedOutputStream(stream); - synchronized (ExclusiveWrite.getInstance()) + log.info("setting value to {}", nd.getHandle()); + + Thread t = new Thread(new Runnable() { - if (resolutions.hasNodeData(resolution)) + /** + * {@inheritDoc} + */ + @Override + public void run() { - NodeData nd = resolutions.getNodeData(resolution); - nd.delete(); + try + { + nd.setValue(stream); + } + catch (RepositoryException e) + { + // TODO Auto-generated catch block + log.error(e.getMessage(), e); + } } - // don't remove deprecated method call, needed for magnolia 4.0 compatibility - NodeData nd = resolutions.createNodeData(resolution, PropertyType.BINARY); + }); + t.start(); - log.info("setting value to {}", nd.getHandle()); - nd.setValue(stream); - String mimetype = "image/" + extension; - if ("jpg".equals(extension)) - { - mimetype = "image/jpeg"; - } - nd.setAttribute(ImageUtils.RESOLUTION_PROPERTY, originalRes); - nd.setAttribute(FileProperties.PROPERTY_EXTENSION, extension); - nd.setAttribute(FileProperties.PROPERTY_FILENAME, saveTo.getName()); - nd.setAttribute(FileProperties.PROPERTY_CONTENTTYPE, mimetype); - nd.setAttribute( - FileProperties.PROPERTY_LASTMODIFIED, - GregorianCalendar.getInstance(TimeZone.getDefault())); - nd.setAttribute(FileProperties.PROPERTY_WIDTH, "" + image.getWidth()); - nd.setAttribute(FileProperties.PROPERTY_HEIGHT, "" + image.getHeight()); + long count = getStream(image, extension, quality, forceProgressive, outputstream); + IOUtils.closeQuietly(outputstream); + try + { + t.join(); + } + catch (InterruptedException e) + { + log.warn(e.getMessage(), e); + } - nd.setAttribute(FileProperties.PROPERTY_SIZE, "" + stream.getCount()); + IOUtils.closeQuietly(stream); + String mimetype = "image/" + extension; + if ("jpg".equals(extension)) + { + mimetype = "image/jpeg"; } + nd.setAttribute(ImageUtils.RESOLUTION_PROPERTY, originalRes); + nd.setAttribute(FileProperties.PROPERTY_EXTENSION, extension); + nd.setAttribute(FileProperties.PROPERTY_FILENAME, saveTo.getName()); + nd.setAttribute(FileProperties.PROPERTY_CONTENTTYPE, mimetype); + nd.setAttribute(FileProperties.PROPERTY_LASTMODIFIED, GregorianCalendar.getInstance(TimeZone.getDefault())); + nd.setAttribute(FileProperties.PROPERTY_WIDTH, "" + image.getWidth()); + nd.setAttribute(FileProperties.PROPERTY_HEIGHT, "" + image.getHeight()); + + nd.setAttribute(FileProperties.PROPERTY_SIZE, "" + count); } - finally - { - IOUtils.closeQuietly(stream); - } + } /** @@ -489,110 +586,116 @@ * @return inputstream * @throws IOException */ - public static InputStream getStream(BufferedImage image, String extension, float quality, boolean forceProgressive) - throws IOException + public static long getStream(BufferedImage image, String extension, float quality, boolean forceProgressive, + PipedOutputStream outputstream) throws IOException { - // don't write ico file, use png - if (StringUtils.equalsIgnoreCase(extension, "ico")) - { - extension = "png"; - } + CountBytesBufferedOutputStream out = new CountBytesBufferedOutputStream(outputstream); - final File tempFile = File.createTempFile("image-", "." + extension); - FileOutputStream fos = new FileOutputStream(tempFile); - - BufferedOutputStream out = new BufferedOutputStream(fos); - try { - - Iterator<ImageWriter> writers; - ImageOutputStream imageOutputStream; - ImageWriteParam params; - ImageWriter imageWriter; - - writers = ImageIO.getImageWritersBySuffix(extension); - - if (writers != null && writers.hasNext()) + if (extension.equals("jpg")) { - // Fetch the first writer in the list - imageWriter = writers.next(); + JPEGImageEncoder encoder = com.sun.image.codec.jpeg.JPEGCodec.createJPEGEncoder(out); + if (quality != 1.0f) + { + JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(image); + param.setQuality(quality, true); + encoder.setJPEGEncodeParam(param); + } + encoder.encode(image); + } + else + { - // Specify the parameters according to those the output file will be written + Iterator<ImageWriter> writers; + ImageOutputStream imageOutputStream; + ImageWriteParam params; + ImageWriter imageWriter; - // Get Default parameters - params = imageWriter.getDefaultWriteParam(); + // don't use "ico" as output format + writers = ImageIO.getImageWritersBySuffix(extension.equals("ico") ? "png" : extension); - try + if (writers != null && writers.hasNext()) { + // Fetch the first writer in the list + imageWriter = writers.next(); - String[] compressionTypes = params.getCompressionTypes(); + // Specify the parameters according to those the output file will be written - if (compressionTypes != null && compressionTypes.length > 0) + // Get Default parameters + params = imageWriter.getDefaultWriteParam(); + + try { - // Define compression mode - params.setCompressionMode(javax.imageio.ImageWriteParam.MODE_EXPLICIT); - params.setCompressionType(compressionTypes[0]); + String[] compressionTypes = params.getCompressionTypes(); - // Define compression quality - params.setCompressionQuality(quality); - } - else - { - params.setCompressionMode(javax.imageio.ImageWriteParam.MODE_COPY_FROM_METADATA); - } + if (compressionTypes != null && compressionTypes.length > 0) + { + // Define compression mode + params.setCompressionMode(javax.imageio.ImageWriteParam.MODE_EXPLICIT); - // Define progressive mode - if (forceProgressive) - { - params.setProgressiveMode(javax.imageio.ImageWriteParam.MODE_DEFAULT); + params.setCompressionType(compressionTypes[0]); + + // Define compression quality + params.setCompressionQuality(quality); + } + else + { + params.setCompressionMode(javax.imageio.ImageWriteParam.MODE_COPY_FROM_METADATA); + } + + // Define progressive mode + if (forceProgressive) + { + params.setProgressiveMode(javax.imageio.ImageWriteParam.MODE_DEFAULT); + } + else + { + params.setProgressiveMode(javax.imageio.ImageWriteParam.MODE_DISABLED); + } } - else + catch (UnsupportedOperationException e) { - params.setProgressiveMode(javax.imageio.ImageWriteParam.MODE_DISABLED); + // go on } - } - catch (UnsupportedOperationException e) - { - // go on - } - // Define destination type - used the ColorModel and SampleModel of the Input Image - params.setDestinationType(new ImageTypeSpecifier(image.getColorModel(), image.getSampleModel())); + // Define destination type - used the ColorModel and SampleModel of the Input Image + params.setDestinationType(new ImageTypeSpecifier(image.getColorModel(), image.getSampleModel())); - imageOutputStream = ImageIO.createImageOutputStream(out); - imageWriter.setOutput(imageOutputStream); + imageOutputStream = ImageIO.createImageOutputStream(out); + imageWriter.setOutput(imageOutputStream); - IIOImage iioimage = new IIOImage(image, null, null); + IIOImage iioimage = new IIOImage(image, null, null); - try - { - // Write the changed Image - imageWriter.write(null, iioimage, params); - } - catch (NullPointerException e) - { - // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6287936 - // GIF writer is buggy for totally transparent gifs + try + { + // Write the changed Image + imageWriter.write(null, iioimage, params); + } + catch (NullPointerException e) + { + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6287936 + // GIF writer is buggy for totally transparent gifs - // workaround: draw a point! - image.createGraphics().drawRect(0, 0, 1, 1); - imageWriter.write(null, iioimage, params); + // workaround: draw a point! + image.createGraphics().drawRect(0, 0, 1, 1); + imageWriter.write(null, iioimage, params); + } + finally + { + // Close the streams + imageOutputStream.close(); + imageWriter.dispose(); + } } - finally + else { - // Close the streams - imageOutputStream.close(); - imageWriter.dispose(); + ImageIO.write(image, extension, out); } } - else - { - ImageIO.write(image, extension, out); - } } catch (IOException ex) { @@ -600,22 +703,7 @@ throw ex; } - out.flush(); - IOUtils.closeQuietly(out); - return new FileInputStream(tempFile) - { - - /** - * {@inheritDoc} - */ - @Override - public void close() throws IOException - { - tempFile.delete(); - super.close(); - } - - }; + return out.count; } /** @@ -758,6 +846,8 @@ Map<String, String> params = parseParameters(resolution); + long t = System.currentTimeMillis(); + BufferedImage img; try { @@ -820,6 +910,8 @@ throw new RuntimeException(e); } + log.debug("Resized {} in: {}ms", finalNodeDataName, System.currentTimeMillis() - t); + } }); @@ -1087,7 +1179,7 @@ } } - static class CountBytesBufferedInputStream extends BufferedInputStream + static class CountBytesBufferedOutputStream extends BufferedOutputStream { private int count = 0; @@ -1096,43 +1188,39 @@ * @param in * @param size */ - public CountBytesBufferedInputStream(InputStream in, int size) + public CountBytesBufferedOutputStream(OutputStream out, int size) { - super(in, size); + super(out, size); count = 0; } /** * @param in */ - public CountBytesBufferedInputStream(InputStream in) + public CountBytesBufferedOutputStream(OutputStream in) { super(in); count = 0; } /** - * {@inheritDoc} + * Returns the count. + * @return the count */ - @Override - public synchronized int read(byte[] b, int off, int len) throws IOException + public int getCount() { - int read = super.read(b, off, len); - if (read > 0) - { - count += read; - } - return read; + return count; } /** - * Returns the count. - * @return the count + * {@inheritDoc} */ - public int getCount() + @Override + public synchronized void write(byte[] b, int off, int len) throws IOException { - return count; + count += len; + super.write(b, off, len); } } -} +} \ No newline at end of file This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |