From b174fbd0ca0a48cec20317e984b4e604270816ff Mon Sep 17 00:00:00 2001 From: borg-0300 Date: Mon, 18 Sep 2006 18:31:27 +0000 Subject: [PATCH] "import ...*" removed; properties added; git-svn-id: https://svn.berlios.de/svnroot/repos/yacy/trunk@2626 6c8d7289-2bf4-0310-a012-ef5d649a1542 --- .../de/anomic/kelondro/kelondroXMLTables.java | 385 +++--- .../de/anomic/ymage/ymagePNGEncoderJDE.java | 1081 +++++++++-------- 2 files changed, 739 insertions(+), 727 deletions(-) diff --git a/source/de/anomic/kelondro/kelondroXMLTables.java b/source/de/anomic/kelondro/kelondroXMLTables.java index afcdc2bd9..f88fd30d6 100644 --- a/source/de/anomic/kelondro/kelondroXMLTables.java +++ b/source/de/anomic/kelondro/kelondroXMLTables.java @@ -1,190 +1,195 @@ -// kelondroXMLTables.java -// ------------------------- -// part of The Kelondro Database -// (C) by Michael Peter Christen; mc@anomic.de -// first published on http://www.anomic.de -// Frankfurt, Germany, 2006 -// created 09.02.2006 -// -// $LastChangedDate: 2005-09-22 22:01:26 +0200 (Thu, 22 Sep 2005) $ -// $LastChangedRevision: 774 $ -// $LastChangedBy: orbiter $ -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// Using this software in any meaning (reading, learning, copying, compiling, -// running) means that you agree that the Author(s) is (are) not responsible -// for cost, loss of data or any harm that may be caused directly or indirectly -// by usage of this softare or this documentation. The usage of this software -// is on your own risk. The installation and usage (starting/running) of this -// software may allow other people or application to access your computer and -// any attached devices and is highly dependent on the configuration of the -// software which must be done by the user of the software; the author(s) is -// (are) also not responsible for proper configuration and usage of the -// software, even if provoked by documentation provided together with -// the software. -// -// Any changes to this file according to the GPL as documented in the file -// gpl.txt aside this file in the shipment you received can be done to the -// lines that follows this copyright notice here, but changes must not be -// done inside the copyright notive above. A re-distribution must contain -// the intact and unchanged copyright notice. -// Contributions and changes to the program code must be marked as such. - -package de.anomic.kelondro; - -import java.beans.*; -import java.io.*; -import java.util.*; - -public class kelondroXMLTables { - - private Hashtable tables; - - // tables is a hashtable that contains hashtables as values in the table - private File propFile; - private long timestamp; - - public kelondroXMLTables() { - this.propFile = null; - this.timestamp = System.currentTimeMillis(); - this.tables = new Hashtable(); - } - - public kelondroXMLTables(File file) throws IOException { - this.propFile = file; - this.timestamp = System.currentTimeMillis(); - if (propFile.exists()) { - XMLDecoder xmldec = new XMLDecoder(new FileInputStream(propFile)); - tables = (Hashtable) xmldec.readObject(); - xmldec.close(); - } else { - tables = new Hashtable(); - } - } - - public void commit(File target) throws IOException { - // this method is used if the Mircrotable was created without assigning - // a file to it as an empty table the table then becomes file-based, - // and write operation will be committed to the file - this.propFile = target; - commit(true); - } - - private void commit(boolean force) throws IOException { - // this function commits the data to a file - // it does not save the data until a specific waiting-time has been lasted - if ((force) || (System.currentTimeMillis() - timestamp > 10000)) { - // check error case: can only occur if logical programmic error - // exists - if (this.propFile == null) - throw new RuntimeException("Microtables.commit: no file specified"); - - // write first to a temporary file - File tmpFile = new File(this.propFile.toString() + ".tmp"); - - // write file - XMLEncoder xmlenc = new XMLEncoder(new FileOutputStream(tmpFile)); - xmlenc.writeObject(tables); - xmlenc.close(); - - // delete old file and rename tmp-file to old file's name - this.propFile.delete(); - tmpFile.renameTo(this.propFile); - - // set the new time stamp - timestamp = System.currentTimeMillis(); - } - } - - public boolean hasTable(String table) { - return (tables.get(table) != null); - } - - public int sizeTable(String table) { - // returns number of entries in table; if table does not exist -1 - Hashtable l = (Hashtable) tables.get(table); - if (l == null) - return -1; - return l.size(); - } - - public void createTable(String table) throws IOException { - // creates a new table - Hashtable l = (Hashtable) tables.get(table); - if (l != null) - return; // we do not overwite - tables.put(table, new Hashtable()); - if (this.propFile != null) - commit(false); - } - - public void set(String table, String key, String value) throws IOException { - if (table != null) { - Hashtable l = (Hashtable) tables.get(table); - if (l == null) - throw new RuntimeException("Microtables.set: table does not exist"); - if (value == null) - value = ""; - l.put(key, value); - } - if (this.propFile != null) - commit(false); - } - - public String get(String table, String key, String deflt) { - if (table != null) { - Hashtable l = (Hashtable) tables.get(table); - if (l == null) - throw new RuntimeException("Microtables.get: table does not exist"); - if (l.containsKey(key)) - return (String) l.get(key); - else - return deflt; - } - return null; - } - - public boolean has(String table, String key) { - if (table != null) { - Hashtable l = (Hashtable) tables.get(table); - if (l == null) - throw new RuntimeException("Microtables.has: table does not exist"); - return (l.containsKey(key)); - } - return false; - } - - public Enumeration keys(String table) { - if (table != null) { - Hashtable l = (Hashtable) tables.get(table); - if (l == null) - throw new RuntimeException("Microtables.keys: table does not exist"); - return l.keys(); - } - return null; - } - - public void close() throws IOException { - finalize(); - } - - // we finalize the operation by saving everything throug the scheduler - // this method is called by the java GC bevore it destroys the object - protected void finalize() throws IOException { - commit(true); - } - -} +// kelondroXMLTables.java +// ------------------------- +// part of The Kelondro Database +// (C) by Michael Peter Christen; mc@anomic.de +// first published on http://www.anomic.de +// Frankfurt, Germany, 2006 +// created 09.02.2006 +// +// $LastChangedDate$ +// $LastChangedRevision$ +// $LastChangedBy$ +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// Using this software in any meaning (reading, learning, copying, compiling, +// running) means that you agree that the Author(s) is (are) not responsible +// for cost, loss of data or any harm that may be caused directly or indirectly +// by usage of this softare or this documentation. The usage of this software +// is on your own risk. The installation and usage (starting/running) of this +// software may allow other people or application to access your computer and +// any attached devices and is highly dependent on the configuration of the +// software which must be done by the user of the software; the author(s) is +// (are) also not responsible for proper configuration and usage of the +// software, even if provoked by documentation provided together with +// the software. +// +// Any changes to this file according to the GPL as documented in the file +// gpl.txt aside this file in the shipment you received can be done to the +// lines that follows this copyright notice here, but changes must not be +// done inside the copyright notive above. A re-distribution must contain +// the intact and unchanged copyright notice. +// Contributions and changes to the program code must be marked as such. + +package de.anomic.kelondro; + +import java.beans.XMLDecoder; +import java.beans.XMLEncoder; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; + +public class kelondroXMLTables { + + private Hashtable tables; + + // tables is a hashtable that contains hashtables as values in the table + private File propFile; + private long timestamp; + + public kelondroXMLTables() { + this.propFile = null; + this.timestamp = System.currentTimeMillis(); + this.tables = new Hashtable(); + } + + public kelondroXMLTables(File file) throws IOException { + this.propFile = file; + this.timestamp = System.currentTimeMillis(); + if (propFile.exists()) { + XMLDecoder xmldec = new XMLDecoder(new FileInputStream(propFile)); + tables = (Hashtable) xmldec.readObject(); + xmldec.close(); + } else { + tables = new Hashtable(); + } + } + + public void commit(File target) throws IOException { + // this method is used if the Mircrotable was created without assigning + // a file to it as an empty table the table then becomes file-based, + // and write operation will be committed to the file + this.propFile = target; + commit(true); + } + + private void commit(boolean force) throws IOException { + // this function commits the data to a file + // it does not save the data until a specific waiting-time has been lasted + if ((force) || (System.currentTimeMillis() - timestamp > 10000)) { + // check error case: can only occur if logical programmic error + // exists + if (this.propFile == null) + throw new RuntimeException("Microtables.commit: no file specified"); + + // write first to a temporary file + File tmpFile = new File(this.propFile.toString() + ".tmp"); + + // write file + XMLEncoder xmlenc = new XMLEncoder(new FileOutputStream(tmpFile)); + xmlenc.writeObject(tables); + xmlenc.close(); + + // delete old file and rename tmp-file to old file's name + this.propFile.delete(); + tmpFile.renameTo(this.propFile); + + // set the new time stamp + timestamp = System.currentTimeMillis(); + } + } + + public boolean hasTable(String table) { + return (tables.get(table) != null); + } + + public int sizeTable(String table) { + // returns number of entries in table; if table does not exist -1 + Hashtable l = (Hashtable) tables.get(table); + if (l == null) + return -1; + return l.size(); + } + + public void createTable(String table) throws IOException { + // creates a new table + Hashtable l = (Hashtable) tables.get(table); + if (l != null) + return; // we do not overwite + tables.put(table, new Hashtable()); + if (this.propFile != null) + commit(false); + } + + public void set(String table, String key, String value) throws IOException { + if (table != null) { + Hashtable l = (Hashtable) tables.get(table); + if (l == null) + throw new RuntimeException("Microtables.set: table does not exist"); + if (value == null) + value = ""; + l.put(key, value); + } + if (this.propFile != null) + commit(false); + } + + public String get(String table, String key, String deflt) { + if (table != null) { + Hashtable l = (Hashtable) tables.get(table); + if (l == null) + throw new RuntimeException("Microtables.get: table does not exist"); + if (l.containsKey(key)) + return (String) l.get(key); + else + return deflt; + } + return null; + } + + public boolean has(String table, String key) { + if (table != null) { + Hashtable l = (Hashtable) tables.get(table); + if (l == null) + throw new RuntimeException("Microtables.has: table does not exist"); + return (l.containsKey(key)); + } + return false; + } + + public Enumeration keys(String table) { + if (table != null) { + Hashtable l = (Hashtable) tables.get(table); + if (l == null) + throw new RuntimeException("Microtables.keys: table does not exist"); + return l.keys(); + } + return null; + } + + public void close() throws IOException { + finalize(); + } + + // we finalize the operation by saving everything throug the scheduler + // this method is called by the java GC bevore it destroys the object + protected void finalize() throws IOException { + commit(true); + } + +} diff --git a/source/de/anomic/ymage/ymagePNGEncoderJDE.java b/source/de/anomic/ymage/ymagePNGEncoderJDE.java index 62d5c0087..eee72a98a 100644 --- a/source/de/anomic/ymage/ymagePNGEncoderJDE.java +++ b/source/de/anomic/ymage/ymagePNGEncoderJDE.java @@ -1,537 +1,544 @@ -/** - * 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.4, 31 March 2000 - */ - -/* - * this file was taken from - * http://chem.sis.nlm.nih.gov/chemidplus/applet/chemaxon/marvin/help/PngEncoder.java.txt - * and modified by Michael Peter Christen, Frankfurt, 2005 - * to work with ImagePainter Objects instead of awt.image Objetcs - * this avoids using any awt routines - */ - -package de.anomic.ymage; - -import java.util.zip.*; -import java.io.*; - -public class ymagePNGEncoderJDE extends Object -{ - /** Constants for filters */ - public static final int FILTER_NONE = 0; - public static final int FILTER_SUB = 1; - public static final int FILTER_UP = 2; - public static final int FILTER_LAST = 2; - - protected byte[] pngBytes; - protected byte[] priorRow; - protected byte[] leftBytes; - protected ymageMatrix matrix; - protected int width, height; - protected int bytePos, maxPos; - protected int hdrPos, dataPos, endPos; - protected CRC32 crc = new CRC32(); - protected long crcValue; - protected boolean encodeAlpha; - protected int filter; - protected int bytesPerPixel; - protected int compressionLevel; - - /** - * Class constructor specifying Image source to encode, whether to encode alpha, filter to use, and compression level. - * - * @param image A Java Image object - * @param encodeAlpha Encode the alpha channel? false=no; true=yes - * @param whichFilter 0=none, 1=sub, 2=up - * @param compLevel 0..9 - * @see java.awt.Image - */ - public ymagePNGEncoderJDE(ymageMatrix matrix, int whichFilter, int compLevel) - { - this.matrix = matrix; - this.encodeAlpha = false; - setFilter( whichFilter ); - if (compLevel >=0 && compLevel <=9) - { - this.compressionLevel = compLevel; - } - } - - - /** - * Creates an array of bytes that is the PNG equivalent of the current image, specifying whether to encode alpha or not. - * - * @param encodeAlpha boolean false=no alpha, true=encode alpha - * @return an array of bytes, or null if there was a problem - */ - public byte[] pngEncode() - { - byte[] pngIdBytes = { -119, 80, 78, 71, 13, 10, 26, 10 }; - - if (matrix == null) - { - return null; - } - width = matrix.getWidth(); - height = matrix.getHeight(); - - /* - * start with an array that is big enough to hold all the pixels - * (plus filter bytes), and an extra 200 bytes for header info - */ - pngBytes = new byte[((width+1) * height * 3) + 200]; - - /* - * keep track of largest byte written to the array - */ - maxPos = 0; - - bytePos = writeBytes( pngIdBytes, 0 ); - hdrPos = bytePos; - writeHeader(); - dataPos = bytePos; - if (writeImageData()) - { - writeEnd(); - pngBytes = resizeByteArray( pngBytes, maxPos ); - } - else - { - pngBytes = null; - } - return pngBytes; - } - - /** - * Set the alpha encoding on or off. - * - * @param encodeAlpha false=no, true=yes - */ - public void setEncodeAlpha( boolean encodeAlpha ) - { - this.encodeAlpha = encodeAlpha; - } - - /** - * Retrieve alpha encoding status. - * - * @return boolean false=no, true=yes - */ - public boolean getEncodeAlpha() - { - return encodeAlpha; - } - - /** - * Set the filter to use - * - * @param whichFilter from constant list - */ - public void setFilter( int whichFilter ) - { - this.filter = FILTER_NONE; - if ( whichFilter <= FILTER_LAST ) - { - this.filter = whichFilter; - } - } - - /** - * Retrieve filtering scheme - * - * @return int (see constant list) - */ - public int getFilter() - { - return filter; - } - - /** - * Set the compression level to use - * - * @param level 0 through 9 - */ - public void setCompressionLevel( int level ) - { - if ( level >= 0 && level <= 9) - { - this.compressionLevel = level; - } - } - - /** - * Retrieve compression level - * - * @return int in range 0-9 - */ - public int getCompressionLevel() - { - return compressionLevel; - } - - /** - * Increase or decrease the length of a byte array. - * - * @param array The original array. - * @param newLength The length you wish the new array to have. - * @return Array of newly desired length. If shorter than the - * original, the trailing elements are truncated. - */ - protected byte[] resizeByteArray( byte[] array, int newLength ) - { - byte[] newArray = new byte[newLength]; - int oldLength = array.length; - - System.arraycopy( array, 0, newArray, 0, - Math.min( oldLength, newLength ) ); - return newArray; - } - - /** - * Write an array of bytes into the pngBytes array. - * Note: This routine has the side effect of updating - * maxPos, the largest element written in the array. - * The array is resized by 1000 bytes or the length - * of the data to be written, whichever is larger. - * - * @param data The data to be written into pngBytes. - * @param offset The starting point to write to. - * @return The next place to be written to in the pngBytes array. - */ - protected 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; - } - - /** - * Write an array of bytes into the pngBytes array, specifying number of bytes to write. - * Note: This routine has the side effect of updating - * maxPos, the largest element written in the array. - * The array is resized by 1000 bytes or the length - * of the data to be written, whichever is larger. - * - * @param data The data to be written into pngBytes. - * @param nBytes The number of bytes to be written. - * @param offset The starting point to write to. - * @return The next place to be written to in the pngBytes array. - */ - protected int writeBytes( byte[] data, int nBytes, int offset ) - { - maxPos = Math.max( maxPos, offset + nBytes ); - if (nBytes + offset > pngBytes.length) - { - pngBytes = resizeByteArray( pngBytes, pngBytes.length + - Math.max( 1000, nBytes ) ); - } - System.arraycopy( data, 0, pngBytes, offset, nBytes ); - return offset + nBytes; - } - - /** - * Write a two-byte integer into the pngBytes array at a given position. - * - * @param n The integer to be written into pngBytes. - * @param offset The starting point to write to. - * @return The next place to be written to in the pngBytes array. - */ - protected int writeInt2( int n, int offset ) - { - byte[] temp = { (byte)((n >> 8) & 0xff), - (byte) (n & 0xff) }; - return writeBytes( temp, offset ); - } - - /** - * Write a four-byte integer into the pngBytes array at a given position. - * - * @param n The integer to be written into pngBytes. - * @param offset The starting point to write to. - * @return The next place to be written to in the pngBytes array. - */ - protected int writeInt4( int n, int offset ) - { - byte[] temp = { (byte)((n >> 24) & 0xff), - (byte) ((n >> 16) & 0xff ), - (byte) ((n >> 8) & 0xff ), - (byte) ( n & 0xff ) }; - return writeBytes( temp, offset ); - } - - /** - * Write a single byte into the pngBytes array at a given position. - * - * @param n The integer to be written into pngBytes. - * @param offset The starting point to write to. - * @return The next place to be written to in the pngBytes array. - */ - protected int writeByte( int b, int offset ) - { - byte[] temp = { (byte) b }; - return writeBytes( temp, offset ); - } - - /** - * Write a string into the pngBytes array at a given position. - * This uses the getBytes method, so the encoding used will - * be its default. - * - * @param n The integer to be written into pngBytes. - * @param offset The starting point to write to. - * @return The next place to be written to in the pngBytes array. - * @see java.lang.String#getBytes() - */ - protected int writeString( String s, int offset ) - { - return writeBytes( s.getBytes(), offset ); - } - - /** - * Write a PNG "IHDR" chunk into the pngBytes array. - */ - protected void writeHeader() - { - int startPos; - - startPos = bytePos = writeInt4( 13, bytePos ); - bytePos = writeString( "IHDR", bytePos ); - width = matrix.getWidth(); - height = matrix.getHeight(); - bytePos = writeInt4( width, bytePos ); - bytePos = writeInt4( height, bytePos ); - bytePos = writeByte( 8, bytePos ); // bit depth - bytePos = writeByte( (encodeAlpha) ? 6 : 2, bytePos ); // direct model - bytePos = writeByte( 0, bytePos ); // compression method - bytePos = writeByte( 0, bytePos ); // filter method - bytePos = writeByte( 0, bytePos ); // no interlace - crc.reset(); - crc.update( pngBytes, startPos, bytePos-startPos ); - crcValue = crc.getValue(); - bytePos = writeInt4( (int) crcValue, bytePos ); - } - - /** - * Perform "sub" filtering on the given row. - * Uses temporary array leftBytes to store the original values - * of the previous pixels. The array is 16 bytes long, which - * will easily hold two-byte samples plus two-byte alpha. - * - * @param pixels The array holding the scan lines being built - * @param startPos Starting position within pixels of bytes to be filtered. - * @param width Width of a scanline in pixels. - */ - protected void filterSub( byte[] pixels, int startPos, int width ) - { - int i; - int offset = bytesPerPixel; - int actualStart = startPos + offset; - int nBytes = width * bytesPerPixel; - int leftInsert = offset; - int leftExtract = 0; - - for (i=actualStart; i < startPos + nBytes; i++) - { - leftBytes[leftInsert] = pixels[i]; - pixels[i] = (byte) ((pixels[i] - leftBytes[leftExtract]) % 256); - leftInsert = (leftInsert+1) % 0x0f; - leftExtract = (leftExtract + 1) % 0x0f; - } - } - - /** - * Perform "up" filtering on the given row. - * Side effect: refills the prior row with current row - * - * @param pixels The array holding the scan lines being built - * @param startPos Starting position within pixels of bytes to be filtered. - * @param width Width of a scanline in pixels. - */ - protected void filterUp( byte[] pixels, int startPos, int width ) - { - int i, nBytes; - byte current_byte; - - nBytes = width * bytesPerPixel; - - for (i=0; i < nBytes; i++) - { - current_byte = pixels[startPos + i]; - pixels[startPos + i] = (byte) ((pixels[startPos + i] - priorRow[i]) % 256); - priorRow[i] = current_byte; - } - } - - /** - * Write the image data into the pngBytes array. - * This will write one or more PNG "IDAT" chunks. In order - * to conserve memory, this method grabs as many rows as will - * fit into 32K bytes, or the whole image; whichever is less. - * - * - * @return true if no errors; false if error grabbing pixels - */ - protected boolean writeImageData() - { - - - 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 - int startPos; // where this line's actual pixels start (used for filtering) - - byte[] compressedLines; // the resultant compressed lines - int nCompressed; // how big is the compressed area? - - //int depth; // color depth ( handle only 8 or 32 ) - - bytesPerPixel = (encodeAlpha) ? 4 : 3; - - Deflater scrunch = new Deflater( compressionLevel ); - ByteArrayOutputStream outBytes = - new ByteArrayOutputStream(1024); - - DeflaterOutputStream compBytes = - new DeflaterOutputStream( outBytes, scrunch ); - try - { - while (rowsLeft > 0) - { - - nRows = Math.min( 32767 / (width*(bytesPerPixel+1)), rowsLeft ); - // nRows = rowsLeft; - - /* - * Create a data chunk. scanLines adds "nRows" for - * the filter bytes. - */ - scanLines = new byte[width * nRows * bytesPerPixel + nRows]; - - if (filter == FILTER_SUB) - { - leftBytes = new byte[16]; - } - if (filter == FILTER_UP) - { - priorRow = new byte[width*bytesPerPixel]; - } - - scanPos = 0; - startPos = 1; - int[] pixel; - matrix.getColorMode(true); - for (int i=0; i> 24) & 0xff ); - } - if ((i % width == width-1) && (filter != FILTER_NONE)) - { - if (filter == FILTER_SUB) - { - filterSub( scanLines, startPos, width ); - } - if (filter == FILTER_UP) - { - filterUp( scanLines, startPos, width ); - } - } - } - - /* - * Write these lines to the output area - */ - compBytes.write( scanLines, 0, scanPos ); - - - startRow += nRows; - rowsLeft -= nRows; - } - compBytes.close(); - - /* - * Write the compressed bytes - */ - compressedLines = outBytes.toByteArray(); - nCompressed = compressedLines.length; - - crc.reset(); - bytePos = writeInt4( nCompressed, bytePos ); - bytePos = writeString("IDAT", bytePos ); - crc.update("IDAT".getBytes()); - bytePos = writeBytes( compressedLines, nCompressed, bytePos ); - crc.update( compressedLines, 0, nCompressed ); - - crcValue = crc.getValue(); - bytePos = writeInt4( (int) crcValue, bytePos ); - scrunch.finish(); - return true; - } - catch (IOException e) - { - System.err.println( e.toString()); - return false; - } finally { - } - } - - /** - * Write a PNG "IEND" chunk into the pngBytes array. - */ - protected void writeEnd() - { - bytePos = writeInt4( 0, bytePos ); - bytePos = writeString( "IEND", bytePos ); - crc.reset(); - crc.update("IEND".getBytes()); - crcValue = crc.getValue(); - bytePos = writeInt4( (int) crcValue, bytePos ); - } -} +/** + * 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.4, 31 March 2000 + * + * $LastChangedDate$ + * $LastChangedRevision$ + * $LastChangedBy$ + */ + +/* + * this file was taken from + * http://chem.sis.nlm.nih.gov/chemidplus/applet/chemaxon/marvin/help/PngEncoder.java.txt + * and modified by Michael Peter Christen, Frankfurt, 2005 + * to work with ImagePainter Objects instead of awt.image Objetcs + * this avoids using any awt routines + */ + +package de.anomic.ymage; + +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 ymagePNGEncoderJDE extends Object +{ + /** Constants for filters */ + public static final int FILTER_NONE = 0; + public static final int FILTER_SUB = 1; + public static final int FILTER_UP = 2; + public static final int FILTER_LAST = 2; + + protected byte[] pngBytes; + protected byte[] priorRow; + protected byte[] leftBytes; + protected ymageMatrix matrix; + protected int width, height; + protected int bytePos, maxPos; + protected int hdrPos, dataPos, endPos; + protected CRC32 crc = new CRC32(); + protected long crcValue; + protected boolean encodeAlpha; + protected int filter; + protected int bytesPerPixel; + protected int compressionLevel; + + /** + * Class constructor specifying Image source to encode, whether to encode alpha, filter to use, and compression level. + * + * @param image A Java Image object + * @param encodeAlpha Encode the alpha channel? false=no; true=yes + * @param whichFilter 0=none, 1=sub, 2=up + * @param compLevel 0..9 + * @see java.awt.Image + */ + public ymagePNGEncoderJDE(ymageMatrix matrix, int whichFilter, int compLevel) + { + this.matrix = matrix; + this.encodeAlpha = false; + setFilter( whichFilter ); + if (compLevel >=0 && compLevel <=9) + { + this.compressionLevel = compLevel; + } + } + + + /** + * Creates an array of bytes that is the PNG equivalent of the current image, specifying whether to encode alpha or not. + * + * @param encodeAlpha boolean false=no alpha, true=encode alpha + * @return an array of bytes, or null if there was a problem + */ + public byte[] pngEncode() + { + byte[] pngIdBytes = { -119, 80, 78, 71, 13, 10, 26, 10 }; + + if (matrix == null) + { + return null; + } + width = matrix.getWidth(); + height = matrix.getHeight(); + + /* + * start with an array that is big enough to hold all the pixels + * (plus filter bytes), and an extra 200 bytes for header info + */ + pngBytes = new byte[((width+1) * height * 3) + 200]; + + /* + * keep track of largest byte written to the array + */ + maxPos = 0; + + bytePos = writeBytes( pngIdBytes, 0 ); + hdrPos = bytePos; + writeHeader(); + dataPos = bytePos; + if (writeImageData()) + { + writeEnd(); + pngBytes = resizeByteArray( pngBytes, maxPos ); + } + else + { + pngBytes = null; + } + return pngBytes; + } + + /** + * Set the alpha encoding on or off. + * + * @param encodeAlpha false=no, true=yes + */ + public void setEncodeAlpha( boolean encodeAlpha ) + { + this.encodeAlpha = encodeAlpha; + } + + /** + * Retrieve alpha encoding status. + * + * @return boolean false=no, true=yes + */ + public boolean getEncodeAlpha() + { + return encodeAlpha; + } + + /** + * Set the filter to use + * + * @param whichFilter from constant list + */ + public void setFilter( int whichFilter ) + { + this.filter = FILTER_NONE; + if ( whichFilter <= FILTER_LAST ) + { + this.filter = whichFilter; + } + } + + /** + * Retrieve filtering scheme + * + * @return int (see constant list) + */ + public int getFilter() + { + return filter; + } + + /** + * Set the compression level to use + * + * @param level 0 through 9 + */ + public void setCompressionLevel( int level ) + { + if ( level >= 0 && level <= 9) + { + this.compressionLevel = level; + } + } + + /** + * Retrieve compression level + * + * @return int in range 0-9 + */ + public int getCompressionLevel() + { + return compressionLevel; + } + + /** + * Increase or decrease the length of a byte array. + * + * @param array The original array. + * @param newLength The length you wish the new array to have. + * @return Array of newly desired length. If shorter than the + * original, the trailing elements are truncated. + */ + protected byte[] resizeByteArray( byte[] array, int newLength ) + { + byte[] newArray = new byte[newLength]; + int oldLength = array.length; + + System.arraycopy( array, 0, newArray, 0, + Math.min( oldLength, newLength ) ); + return newArray; + } + + /** + * Write an array of bytes into the pngBytes array. + * Note: This routine has the side effect of updating + * maxPos, the largest element written in the array. + * The array is resized by 1000 bytes or the length + * of the data to be written, whichever is larger. + * + * @param data The data to be written into pngBytes. + * @param offset The starting point to write to. + * @return The next place to be written to in the pngBytes array. + */ + protected 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; + } + + /** + * Write an array of bytes into the pngBytes array, specifying number of bytes to write. + * Note: This routine has the side effect of updating + * maxPos, the largest element written in the array. + * The array is resized by 1000 bytes or the length + * of the data to be written, whichever is larger. + * + * @param data The data to be written into pngBytes. + * @param nBytes The number of bytes to be written. + * @param offset The starting point to write to. + * @return The next place to be written to in the pngBytes array. + */ + protected int writeBytes( byte[] data, int nBytes, int offset ) + { + maxPos = Math.max( maxPos, offset + nBytes ); + if (nBytes + offset > pngBytes.length) + { + pngBytes = resizeByteArray( pngBytes, pngBytes.length + + Math.max( 1000, nBytes ) ); + } + System.arraycopy( data, 0, pngBytes, offset, nBytes ); + return offset + nBytes; + } + + /** + * Write a two-byte integer into the pngBytes array at a given position. + * + * @param n The integer to be written into pngBytes. + * @param offset The starting point to write to. + * @return The next place to be written to in the pngBytes array. + */ + protected int writeInt2( int n, int offset ) + { + byte[] temp = { (byte)((n >> 8) & 0xff), + (byte) (n & 0xff) }; + return writeBytes( temp, offset ); + } + + /** + * Write a four-byte integer into the pngBytes array at a given position. + * + * @param n The integer to be written into pngBytes. + * @param offset The starting point to write to. + * @return The next place to be written to in the pngBytes array. + */ + protected int writeInt4( int n, int offset ) + { + byte[] temp = { (byte)((n >> 24) & 0xff), + (byte) ((n >> 16) & 0xff ), + (byte) ((n >> 8) & 0xff ), + (byte) ( n & 0xff ) }; + return writeBytes( temp, offset ); + } + + /** + * Write a single byte into the pngBytes array at a given position. + * + * @param n The integer to be written into pngBytes. + * @param offset The starting point to write to. + * @return The next place to be written to in the pngBytes array. + */ + protected int writeByte( int b, int offset ) + { + byte[] temp = { (byte) b }; + return writeBytes( temp, offset ); + } + + /** + * Write a string into the pngBytes array at a given position. + * This uses the getBytes method, so the encoding used will + * be its default. + * + * @param n The integer to be written into pngBytes. + * @param offset The starting point to write to. + * @return The next place to be written to in the pngBytes array. + * @see java.lang.String#getBytes() + */ + protected int writeString( String s, int offset ) + { + return writeBytes( s.getBytes(), offset ); + } + + /** + * Write a PNG "IHDR" chunk into the pngBytes array. + */ + protected void writeHeader() + { + int startPos; + + startPos = bytePos = writeInt4( 13, bytePos ); + bytePos = writeString( "IHDR", bytePos ); + width = matrix.getWidth(); + height = matrix.getHeight(); + bytePos = writeInt4( width, bytePos ); + bytePos = writeInt4( height, bytePos ); + bytePos = writeByte( 8, bytePos ); // bit depth + bytePos = writeByte( (encodeAlpha) ? 6 : 2, bytePos ); // direct model + bytePos = writeByte( 0, bytePos ); // compression method + bytePos = writeByte( 0, bytePos ); // filter method + bytePos = writeByte( 0, bytePos ); // no interlace + crc.reset(); + crc.update( pngBytes, startPos, bytePos-startPos ); + crcValue = crc.getValue(); + bytePos = writeInt4( (int) crcValue, bytePos ); + } + + /** + * Perform "sub" filtering on the given row. + * Uses temporary array leftBytes to store the original values + * of the previous pixels. The array is 16 bytes long, which + * will easily hold two-byte samples plus two-byte alpha. + * + * @param pixels The array holding the scan lines being built + * @param startPos Starting position within pixels of bytes to be filtered. + * @param width Width of a scanline in pixels. + */ + protected void filterSub( byte[] pixels, int startPos, int width ) + { + int i; + int offset = bytesPerPixel; + int actualStart = startPos + offset; + int nBytes = width * bytesPerPixel; + int leftInsert = offset; + int leftExtract = 0; + + for (i=actualStart; i < startPos + nBytes; i++) + { + leftBytes[leftInsert] = pixels[i]; + pixels[i] = (byte) ((pixels[i] - leftBytes[leftExtract]) % 256); + leftInsert = (leftInsert+1) % 0x0f; + leftExtract = (leftExtract + 1) % 0x0f; + } + } + + /** + * Perform "up" filtering on the given row. + * Side effect: refills the prior row with current row + * + * @param pixels The array holding the scan lines being built + * @param startPos Starting position within pixels of bytes to be filtered. + * @param width Width of a scanline in pixels. + */ + protected void filterUp( byte[] pixels, int startPos, int width ) + { + int i, nBytes; + byte current_byte; + + nBytes = width * bytesPerPixel; + + for (i=0; i < nBytes; i++) + { + current_byte = pixels[startPos + i]; + pixels[startPos + i] = (byte) ((pixels[startPos + i] - priorRow[i]) % 256); + priorRow[i] = current_byte; + } + } + + /** + * Write the image data into the pngBytes array. + * This will write one or more PNG "IDAT" chunks. In order + * to conserve memory, this method grabs as many rows as will + * fit into 32K bytes, or the whole image; whichever is less. + * + * + * @return true if no errors; false if error grabbing pixels + */ + protected boolean writeImageData() + { + + + 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 + int startPos; // where this line's actual pixels start (used for filtering) + + byte[] compressedLines; // the resultant compressed lines + int nCompressed; // how big is the compressed area? + + //int depth; // color depth ( handle only 8 or 32 ) + + bytesPerPixel = (encodeAlpha) ? 4 : 3; + + Deflater scrunch = new Deflater( compressionLevel ); + ByteArrayOutputStream outBytes = + new ByteArrayOutputStream(1024); + + DeflaterOutputStream compBytes = + new DeflaterOutputStream( outBytes, scrunch ); + try + { + while (rowsLeft > 0) + { + + nRows = Math.min( 32767 / (width*(bytesPerPixel+1)), rowsLeft ); + // nRows = rowsLeft; + + /* + * Create a data chunk. scanLines adds "nRows" for + * the filter bytes. + */ + scanLines = new byte[width * nRows * bytesPerPixel + nRows]; + + if (filter == FILTER_SUB) + { + leftBytes = new byte[16]; + } + if (filter == FILTER_UP) + { + priorRow = new byte[width*bytesPerPixel]; + } + + scanPos = 0; + startPos = 1; + int[] pixel; + matrix.getColorMode(true); + for (int i=0; i> 24) & 0xff ); + } + if ((i % width == width-1) && (filter != FILTER_NONE)) + { + if (filter == FILTER_SUB) + { + filterSub( scanLines, startPos, width ); + } + if (filter == FILTER_UP) + { + filterUp( scanLines, startPos, width ); + } + } + } + + /* + * Write these lines to the output area + */ + compBytes.write( scanLines, 0, scanPos ); + + + startRow += nRows; + rowsLeft -= nRows; + } + compBytes.close(); + + /* + * Write the compressed bytes + */ + compressedLines = outBytes.toByteArray(); + nCompressed = compressedLines.length; + + crc.reset(); + bytePos = writeInt4( nCompressed, bytePos ); + bytePos = writeString("IDAT", bytePos ); + crc.update("IDAT".getBytes()); + bytePos = writeBytes( compressedLines, nCompressed, bytePos ); + crc.update( compressedLines, 0, nCompressed ); + + crcValue = crc.getValue(); + bytePos = writeInt4( (int) crcValue, bytePos ); + scrunch.finish(); + return true; + } + catch (IOException e) + { + System.err.println( e.toString()); + return false; + } finally { + } + } + + /** + * Write a PNG "IEND" chunk into the pngBytes array. + */ + protected void writeEnd() + { + bytePos = writeInt4( 0, bytePos ); + bytePos = writeString( "IEND", bytePos ); + crc.reset(); + crc.update("IEND".getBytes()); + crcValue = crc.getValue(); + bytePos = writeInt4( (int) crcValue, bytePos ); + } +}