From 29f4faa3a1865d633b5628fafd2aa2d3baa81bfa Mon Sep 17 00:00:00 2001 From: lehni Date: Fri, 12 Aug 2005 11:28:55 +0000 Subject: [PATCH] Various changes to the imaging code: - added Image.trim(), that trims an image based on a specified pixel, just like in Photoshop. - saveAs exists in two versions now, one that takes a filename, the other an OutputStream + Mime type (first step toward an Java activation framework based approach) --- src/helma/image/ImageGenerator.java | 13 ++ src/helma/image/ImageInfo.java | 27 ++-- src/helma/image/Quantize.java | 4 - src/helma/image/imageio/ImageIOGenerator.java | 116 +++++++++++------- src/helma/image/jimi/JimiGenerator.java | 99 ++++++++++++--- 5 files changed, 188 insertions(+), 71 deletions(-) diff --git a/src/helma/image/ImageGenerator.java b/src/helma/image/ImageGenerator.java index 7eabcc53..97a1b1df 100644 --- a/src/helma/image/ImageGenerator.java +++ b/src/helma/image/ImageGenerator.java @@ -21,6 +21,7 @@ import helma.main.Server; import java.awt.*; import java.awt.image.*; import java.io.IOException; +import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; @@ -208,4 +209,16 @@ public abstract class ImageGenerator { */ public abstract void write(ImageWrapper wrapper, String filename, float quality, boolean alpha) throws IOException; + + /** + * Saves the image. Image format is deduced from the dataSource. + * + * @param wrapper + * @param out + * @param quality + * @param alpha + * @throws IOException + */ + public abstract void write(ImageWrapper wrapper, OutputStream out, String type, + float quality, boolean alpha) throws IOException; } \ No newline at end of file diff --git a/src/helma/image/ImageInfo.java b/src/helma/image/ImageInfo.java index a5f8ca41..1de5f211 100644 --- a/src/helma/image/ImageInfo.java +++ b/src/helma/image/ImageInfo.java @@ -221,6 +221,7 @@ public class ImageInfo { private int width; private int height; private int bitsPerPixel; + private int numColors; private int colorType = COLOR_TYPE_UNKNOWN; private boolean progressive; private int format; @@ -257,6 +258,7 @@ public class ImageInfo { numberOfImages = 1; physicalHeightDpi = -1; physicalWidthDpi = -1; + numColors = -1; comments = null; try { int b1 = read() & 0xff; @@ -353,14 +355,14 @@ public class ImageInfo { int flags = a[8] & 0xff; bitsPerPixel = ((flags >> 4) & 0x07) + 1; progressive = (flags & 0x02) != 0; - if (!determineNumberOfImages) { - return true; - } // skip global color palette if ((flags & 0x80) != 0) { - int tableSize = (1 << ((flags & 7) + 1)) * 3; - skip(tableSize); + numColors = (1 << ((flags & 7) + 1)); + skip(numColors * 3); } + if (!determineNumberOfImages) { + return true; + } numberOfImages = 0; int blockType; do @@ -379,7 +381,11 @@ public class ImageInfo { bitsPerPixel = localBitsPerPixel; } if ((flags & 0x80) != 0) { - skip((1 << localBitsPerPixel) * 3); + int localNumColors = 1 << localBitsPerPixel; + skip(localNumColors * 3); + if (localNumColors > numColors) { + numColors = localNumColors; + } } skip(1); // initial code length int n; @@ -777,7 +783,14 @@ public class ImageInfo { return bitsPerPixel; } - /** + /** + * @return number of colors, for palette based images + */ + public int getNumColors() { + return numColors; + } + + /** * Returns the index'th comment retrieved from the image. * @throws IllegalArgumentException if index is smaller than 0 or larger than or equal * to the number of comments retrieved diff --git a/src/helma/image/Quantize.java b/src/helma/image/Quantize.java index 9ca83762..a61c39b2 100644 --- a/src/helma/image/Quantize.java +++ b/src/helma/image/Quantize.java @@ -785,10 +785,6 @@ public class Quantize { int db = b1 - b2; return da * da + dr * dr + dg * dg + db * db; -// return (SQUARES[r1 - r2 + MAX_RGB] + -// SQUARES[g1 - g2 + MAX_RGB] + -// SQUARES[b1 - b2 + MAX_RGB] + -// SQUARES[a1 - a2 + MAX_RGB]); } public String toString() { diff --git a/src/helma/image/imageio/ImageIOGenerator.java b/src/helma/image/imageio/ImageIOGenerator.java index 4fb365b0..f249cc35 100644 --- a/src/helma/image/imageio/ImageIOGenerator.java +++ b/src/helma/image/imageio/ImageIOGenerator.java @@ -71,6 +71,43 @@ public class ImageIOGenerator extends ImageGenerator { return ImageIO.read(new ByteArrayInputStream(src)); } + protected void write(ImageWrapper wrapper, ImageWriter writer, float quality, boolean alpha) throws IOException { + BufferedImage bi = wrapper.getBufferedImage(); + // Set some parameters + ImageWriteParam param = writer.getDefaultWriteParam(); + if (param.canWriteCompressed() && + quality >= 0.0 && quality <= 1.0) { + param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + param.setCompressionQuality(quality); + } + if (param.canWriteProgressive()) + param.setProgressiveMode(ImageWriteParam.MODE_DISABLED); + // if bi has type ARGB and alpha is false, we have to tell the writer to not use the alpha channel: + // this is especially needed for jpeg files where imageio seems to produce wrong jpeg files right now... + if (bi.getType() == BufferedImage.TYPE_INT_ARGB + && !alpha) { + // create a new BufferedImage that uses a WritableRaster of bi, with all the bands except the alpha band: + WritableRaster raster = bi.getRaster(); + WritableRaster newRaster = raster.createWritableChild( + 0, 0, raster.getWidth(), raster.getHeight(), + 0, 0, new int[] {0, 1, 2 } + ); + // create a ColorModel that represents the one of the ARGB except the alpha channel: + DirectColorModel cm = (DirectColorModel) bi.getColorModel(); + DirectColorModel newCM = new DirectColorModel( + cm.getPixelSize(), cm.getRedMask(), + cm.getGreenMask(), cm.getBlueMask()); + // now create the new buffer that is used ot write the image: + BufferedImage rgbBuffer = new BufferedImage(newCM, + newRaster, false, null); + writer.write(null, new IIOImage(rgbBuffer, null, + null), param); + } else { + writer.write(null, new IIOImage(bi, null, null), + param); + } + } + /** * Saves the image. Image format is deduced from filename. * @@ -81,65 +118,62 @@ public class ImageIOGenerator extends ImageGenerator { * @see helma.image.ImageGenerator#write(helma.image.ImageWrapper, java.lang.String, float, boolean) */ public void write(ImageWrapper wrapper, String filename, float quality, boolean alpha) throws IOException { + // determine suffix: int pos = filename.lastIndexOf('.'); if (pos != -1) { - String extension = filename.substring(pos + 1, - filename.length()).toLowerCase(); + String extension = filename.substring(pos + 1, filename.length()).toLowerCase(); - // Find a writer for that file extensions + // Find a writer for that file suffix ImageWriter writer = null; - Iterator iter = ImageIO.getImageWritersByFormatName(extension); + Iterator iter = ImageIO.getImageWritersBySuffix(extension); if (iter.hasNext()) - writer = (ImageWriter) iter.next(); + writer = (ImageWriter)iter.next(); if (writer != null) { ImageOutputStream ios = null; try { - BufferedImage bi = wrapper.getBufferedImage(); // Prepare output file File file = new File(filename); if (file.exists()) file.delete(); ios = ImageIO.createImageOutputStream(file); writer.setOutput(ios); - // Set some parameters - ImageWriteParam param = writer.getDefaultWriteParam(); - if (param.canWriteCompressed() && - quality >= 0.0 && quality <= 1.0) { - param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); - param.setCompressionQuality(quality); - } - if (param.canWriteProgressive()) - param.setProgressiveMode(ImageWriteParam.MODE_DISABLED); - // if bi has type ARGB and alpha is false, we have to tell the writer to not use the alpha channel: - // this is especially needed for jpeg files where imageio seems to produce wrong jpeg files right now... - if (bi.getType() == BufferedImage.TYPE_INT_ARGB - && !alpha) { - // create a new BufferedImage that uses a WritableRaster of bi, with all the bands except the alpha band: - WritableRaster raster = bi.getRaster(); - WritableRaster newRaster = raster.createWritableChild( - 0, 0, wrapper.getWidth(), wrapper.getHeight(), - 0, 0, new int[] {0, 1, 2 } - ); - // create a ColorModel that represents the one of the ARGB except the alpha channel: - DirectColorModel cm = (DirectColorModel) bi.getColorModel(); - DirectColorModel newCM = new DirectColorModel( - cm.getPixelSize(), cm.getRedMask(), - cm.getGreenMask(), cm.getBlueMask()); - // now create the new buffer that is used ot write the image: - BufferedImage rgbBuffer = new BufferedImage(newCM, - newRaster, false, null); - writer.write(null, new IIOImage(rgbBuffer, null, - null), param); - } else { - writer.write(null, new IIOImage(bi, null, null), - param); - } - } finally { + this.write(wrapper, writer, quality, alpha); + } finally { if (ios != null) ios.close(); writer.dispose(); } } } -} + } + + /** + * Saves the image. Image format is deduced from type. + * + * @param out ... + * @param type ... + * @param quality ... + * @param alpha ... + * @throws IOException + * @see helma.image.ImageGenerator#write(helma.image.ImageWrapper, java.io.OutputStream, java.lang.String, float, boolean) + */ + public void write(ImageWrapper wrapper, OutputStream out, String mimeType, float quality, boolean alpha) throws IOException { + // Find a writer for that type + ImageWriter writer = null; + Iterator iter = ImageIO.getImageWritersByMIMEType(mimeType); + if (iter.hasNext()) + writer = (ImageWriter)iter.next(); + if (writer != null) { + ImageOutputStream ios = null; + try { + ios = ImageIO.createImageOutputStream(out); + writer.setOutput(ios); + this.write(wrapper, writer, quality, alpha); + } finally { + if (ios != null) + ios.close(); + writer.dispose(); + } + } + } } \ No newline at end of file diff --git a/src/helma/image/jimi/JimiGenerator.java b/src/helma/image/jimi/JimiGenerator.java index a8e754ce..bc76157e 100644 --- a/src/helma/image/jimi/JimiGenerator.java +++ b/src/helma/image/jimi/JimiGenerator.java @@ -28,25 +28,24 @@ import com.sun.jimi.core.options.JPGOptions; import com.sun.jimi.core.options.PNGOptions; public class JimiGenerator extends ImageGenerator { + /** - * Saves the image. Image format is deduced from filename. + * Internal function for writing images. * - * @param filename ... + * @param wrapper ... + * @param type either a file extension or a mimetype with stripped image/ or image/x- * @param quality ... * @param alpha ... * @throws IOException * @see helma.image.ImageGenerator#write(helma.image.ImageWrapper, java.lang.String, float, boolean) */ - public void write(ImageWrapper wrapper, String filename, float quality, boolean alpha) throws IOException { + protected boolean write(ImageWrapper wrapper, String type, OutputStream out, float quality, boolean alpha) throws IOException { try { - String lowerCaseName = filename.toLowerCase(); - if (lowerCaseName.endsWith(".gif")) { + if ("gif".equals(type)) { // sun's jimi package doesn't encode gifs, use helma's encoder instead - DataOutputStream out = new DataOutputStream( - new FileOutputStream(filename)); + DataOutputStream dataOut = new DataOutputStream(out); GIFEncoder encoder = new GIFEncoder(); - encoder.encode(wrapper.getBufferedImage(), out); - out.close(); + encoder.encode(wrapper.getBufferedImage(), dataOut); } else { // let's not rely on Jimi's file-extension detecting mechanisms, // as these do not seem to specify file type depending options @@ -54,8 +53,7 @@ public class JimiGenerator extends ImageGenerator { JimiImage source = Jimi.createRasterImage(wrapper.getSource()); JimiEncoder encoder = null; - if (lowerCaseName.endsWith(".jpg") - || lowerCaseName.endsWith(".jpeg")) { + if ("jpg".equals(type) || "jpeg".equals(type)) { // JPEG encoder = new JPGEncoder(); // the quality value does mean something here and can be specified: @@ -64,7 +62,7 @@ public class JimiGenerator extends ImageGenerator { options.setQuality(Math.round(quality * 100)); source.setOptions(options); } - } else if (lowerCaseName.endsWith(".png")) { + } else if ("png".equals(type)) { // PNG encoder = new PNGEncoder(); // the alpha parameter does mean something here: @@ -74,17 +72,80 @@ public class JimiGenerator extends ImageGenerator { options.setCompressionType(PNGOptions.COMPRESSION_MAX); source.setOptions(options); } - if (encoder != null) { - FileOutputStream out = new FileOutputStream(filename); - encoder.encodeImages(new JimiImageEnumeration(source), out); - out.close(); - } else { // if nothing worked, fall back to the Jimi mechanisms and see wether something comes out - Jimi.putImage(wrapper.getImage(), filename); - } + // if no encoder was found, return false. let jimi handle this in the functions bellow + if (encoder == null) return false; + encoder.encodeImages(new JimiImageEnumeration(source), out); } + return true; } catch (JimiException e) { throw new IOException(e.getMessage()); } + } + + /** + * Saves the image. Image format is deduced from filename. + * + * @param wrapper ... + * @param filename ... + * @param quality ... + * @param alpha ... + * @throws IOException + * @see helma.image.ImageGenerator#write(helma.image.ImageWrapper, java.lang.String, float, boolean) + */ + public void write(ImageWrapper wrapper, String filename, float quality, boolean alpha) throws IOException { + // determine the type from the file extension + int pos = filename.lastIndexOf('.'); + if (pos != -1) { + String extension = filename.substring(pos + 1, filename.length()).toLowerCase(); + FileOutputStream out = new FileOutputStream(filename); + boolean written = false; + try { + written = this.write(wrapper, extension, out, quality, alpha); + } finally { + out.close(); + } + // if nothing worked, fall back to the Jimi mechanisms and see wether something comes out + if (!written) { + try { + Jimi.putImage(wrapper.getImage(), filename); + } catch (JimiException e) { + throw new IOException(e.getMessage()); + } + } + } } + /** + * Saves the image. Image format is deduced from filename. + * + * @param wrapper ... + * @param out ... + * @param mimeType ... + * @param quality ... + * @param alpha ... + * @throws IOException + * @see helma.image.ImageGenerator#write(helma.image.ImageWrapper, java.io.OutputStream, java.lang.String, float, boolean) + */ + public void write(ImageWrapper wrapper, OutputStream out, String mimeType, float quality, boolean alpha) throws IOException { + // determine the type from the mime type by taking away image/ and image/x- + if (mimeType.startsWith("image/")) { + String type = mimeType.substring(6); + if (type.startsWith("x-")) + type = type.substring(2); + boolean written = false; + try { + written = this.write(wrapper, type, out, quality, alpha); + } finally { + out.close(); + } + // if nothing worked, fall back to the Jimi mechanisms and see wether something comes out + if (!written) { + try { + Jimi.putImage(mimeType, wrapper.getImage(), out); + } catch (JimiException e) { + throw new IOException(e.getMessage()); + } + } + } + } } \ No newline at end of file