From c6a1b21399bd47eaa4ea23d0ec0aae5156c968ed Mon Sep 17 00:00:00 2001 From: orbiter Date: Tue, 23 Oct 2012 23:27:41 +0200 Subject: [PATCH] added a 9-year old png encoder from David Eisenberg which I rewrote quite a bit to remove all code that handles transparency. With this highly specialized png writer it is possible to write png images much faster that with the JRE built-in png writer. In a second step it can be possible to add concurrency to increase computation speed further. --- source/net/yacy/visualization/PngEncoder.java | 169 ++++++++++++++++++ .../net/yacy/visualization/RasterPlotter.java | 22 ++- 2 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 source/net/yacy/visualization/PngEncoder.java diff --git a/source/net/yacy/visualization/PngEncoder.java b/source/net/yacy/visualization/PngEncoder.java new file mode 100644 index 000000000..b20da87f1 --- /dev/null +++ b/source/net/yacy/visualization/PngEncoder.java @@ -0,0 +1,169 @@ +/** + * PngEncoder takes a Java Image object and creates a byte string which can be saved as a PNG file. + * The Image is presumed to use the DirectColorModel. + * + *

Thanks to Jay Denny at KeyPoint Software + * http://www.keypoint.com/ + * who let me develop this code on company time.

+ * + *

You may contact me with (probably very-much-needed) improvements, + * comments, and bug fixes at:

+ * + *

david@catcode.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.

+ * + *

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 + * A copy of the GNU LGPL may be found at + * http://www.gnu.org/copyleft/lesser.html

+ * + * @author J. David Eisenberg + * @version 1.5, 19 Oct 2003 + * + * CHANGES: + * -------- + * 19-Nov-2002 : CODING STYLE CHANGES ONLY (by David Gilbert for Object Refinery Limited); + * 19-Sep-2003 : Fix for platforms using EBCDIC (contributed by Paulo Soares); + * 19-Oct-2003 : Change private fields to private fields so that + * PngEncoderB can inherit them (JDE) + * Fixed bug with calculation of nRows + * 23.10.2012 + * For the integration into YaCy this class was adopted to YaCy graphics by Michael Christen: + * - removed alpha encoding + * - removed not used code + * - inlined static values + * - inlined all methods that had been called only once + * - moved class objects which appear after all refactoring only within a single method into this method + * - removed a giant number of useless (obvious things) comments and empty lines to increase readability (!) + */ + +package net.yacy.visualization; + +import java.awt.Image; +import java.awt.image.ImageObserver; +import java.awt.image.PixelGrabber; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.CRC32; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; + +public class PngEncoder extends Object { + + private static final byte IHDR[] = {73, 72, 68, 82}; + private static final byte IDAT[] = {73, 68, 65, 84}; + private static final byte IEND[] = {73, 69, 78, 68}; + private byte[] pngBytes; + private Image image; + private int maxPos; + + public PngEncoder(Image image) { + this.image = image; + } + + public byte[] pngEncode(int compressionLevel) { + if (image == null) return null; + int width = image.getWidth(null); + int height = image.getHeight(null); + pngBytes = new byte[((width + 1) * height * 3) + 200]; + maxPos = 0; + int bytePos = writeBytes(new byte[]{-119, 80, 78, 71, 13, 10, 26, 10}, 0); + int startPos; + startPos = bytePos = writeInt4(13, bytePos); + bytePos = writeBytes(IHDR, bytePos); + width = image.getWidth(null); + height = image.getHeight(null); + bytePos = writeInt4(width, bytePos); + bytePos = writeInt4(height, bytePos); + bytePos = writeBytes(new byte[]{8, 2, 0, 0, 0}, bytePos); + CRC32 crc = new CRC32(); + crc.reset(); + crc.update(pngBytes, startPos, bytePos - startPos); + bytePos = writeInt4((int) crc.getValue(), bytePos); + try { + int rowsLeft = height; // number of rows remaining to write + int startRow = 0; // starting row to process this time through + int nRows; // how many rows to grab at a time + byte[] scanLines; // the scan lines to be compressed + int scanPos; // where we are in the scan lines + byte[] compressedLines; // the resultant compressed lines + int nCompressed; // how big is the compressed area? + PixelGrabber pg; + Deflater scrunch = new Deflater(compressionLevel); + ByteArrayOutputStream outBytes = new ByteArrayOutputStream(1024); + DeflaterOutputStream compBytes = new DeflaterOutputStream(outBytes, scrunch); + while (rowsLeft > 0) { + nRows = Math.min(32767 / (width * 4), rowsLeft); + nRows = Math.max(nRows, 1); + int[] pixels = new int[width * nRows]; + pg = new PixelGrabber(image, 0, startRow, width, nRows, pixels, 0, width); + try {pg.grabPixels();} catch (InterruptedException e) {throw new IOException("interrupted waiting for pixels!");} + if ((pg.getStatus() & ImageObserver.ABORT) != 0) throw new IOException("image fetch aborted or errored"); + scanLines = new byte[width * nRows * 3 + nRows]; + scanPos = 0; + for (int i = 0; i < width * nRows; i++) { + if (i % width == 0) scanLines[scanPos++] = (byte) 0; + scanLines[scanPos++] = (byte) ((pixels[i] >> 16) & 0xff); + scanLines[scanPos++] = (byte) ((pixels[i] >> 8) & 0xff); + scanLines[scanPos++] = (byte) ((pixels[i]) & 0xff); + } + compBytes.write(scanLines, 0, scanPos); + startRow += nRows; + rowsLeft -= nRows; + } + compBytes.close(); + compressedLines = outBytes.toByteArray(); + nCompressed = compressedLines.length; + crc.reset(); + bytePos = writeInt4(nCompressed, bytePos); + bytePos = writeBytes(IDAT, bytePos); + crc.update(IDAT); + maxPos = Math.max(maxPos, bytePos + nCompressed); + if (nCompressed + bytePos > pngBytes.length) { + pngBytes = resizeByteArray(pngBytes, pngBytes.length + Math.max(1000, nCompressed)); + } + System.arraycopy(compressedLines, 0, pngBytes, bytePos, nCompressed); + bytePos += nCompressed; + crc.update(compressedLines, 0, nCompressed); + bytePos = writeInt4((int) crc.getValue(), bytePos); + scrunch.finish(); + bytePos = writeInt4(0, bytePos); + bytePos = writeBytes(IEND, bytePos); + crc.reset(); + crc.update(IEND); + bytePos = writeInt4((int) crc.getValue(), bytePos); + pngBytes = resizeByteArray(pngBytes, maxPos); + return pngBytes; + } catch (IOException e) { + return null; + } + } + + private byte[] resizeByteArray(byte[] array, int newLength) { + byte[] newArray = new byte[newLength]; + System.arraycopy(array, 0, newArray, 0, Math.min(array.length, newLength)); + return newArray; + } + + private int writeBytes(byte[] data, int offset) { + maxPos = Math.max(maxPos, offset + data.length); + if (data.length + offset > pngBytes.length) pngBytes = resizeByteArray(pngBytes, pngBytes.length + Math.max(1000, data.length)); + System.arraycopy(data, 0, pngBytes, offset, data.length); + return offset + data.length; + } + + private int writeInt4(int n, int offset) { + return writeBytes(new byte[]{(byte) ((n >> 24) & 0xff), (byte) ((n >> 16) & 0xff), (byte) ((n >> 8) & 0xff), (byte) (n & 0xff)}, offset); + } + +} diff --git a/source/net/yacy/visualization/RasterPlotter.java b/source/net/yacy/visualization/RasterPlotter.java index 728f0f594..ff22732f7 100644 --- a/source/net/yacy/visualization/RasterPlotter.java +++ b/source/net/yacy/visualization/RasterPlotter.java @@ -704,9 +704,9 @@ public class RasterPlotter { public static ByteBuffer exportImage(final BufferedImage image, final String targetExt) { // generate an byte array from the given image - //serverByteBuffer baos = new serverByteBuffer(); + if ("png".equals(targetExt)) return exportPng(image); final ByteBuffer baos = new ByteBuffer(); - ImageIO.setUseCache(false); + ImageIO.setUseCache(false); // because we write into ram here try { ImageIO.write(image, targetExt, baos); return baos; @@ -716,6 +716,23 @@ public class RasterPlotter { return null; } } + + public static ByteBuffer exportPng(final BufferedImage image) { + PngEncoder png = new PngEncoder(image); + try { + final ByteBuffer baos = new ByteBuffer(); + byte[] pngbytes = png.pngEncode(1); + if (pngbytes == null) return null; + baos.write(pngbytes); + baos.flush(); + baos.close(); + return baos; + } catch (IOException e) { + // should not happen + Log.logException(e); + return null; + } + } public static void main(final String[] args) { // go into headless awt mode @@ -730,6 +747,7 @@ public class RasterPlotter { fos.close(); } catch (final IOException e) {} + Log.shutdown(); // open file automatically, works only on Mac OS X /* Process p = null;