Here is my ZXing ByteMatrix to PNG encoder, derived from the PNGEncoder from KeyPoint Software distributed under the LGPL license.
It generates GrayScale only PNG bar codes. /* * Copyright (C) 2009 Francois Masurel * * 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 3 of the License, or * 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, see <http://www.gnu.org/licenses/ >. */ package com.mably.cms.utils.zxing; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.zip.CRC32; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import com.google.zxing.common.ByteMatrix; /** * 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. * * <p> * Thanks to Jay Denny at KeyPoint Software http://www.keypoint.com/ who let me * develop this code on company time. * </p> * * <p> * You may contact me with (probably very-much-needed) improvements, comments, * and bug fixes at: * </p> * * <p> * <code>da...@catcode.com</code> * </p> * * <p> * 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. * </p> * * <p> * 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. * </p> * * <p> * 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 <code>http://www.gnu.org/copyleft/lesser.html</ code> * </p> * * @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 protected fields so that PngEncoderB can * inherit them (JDE) Fixed bug with calculation of nRows */ public class ByteMatrixPngEncoder extends Object { /** IHDR tag. */ protected static final byte IHDR[] = { 73, 72, 68, 82 }; /** IDAT tag. */ protected static final byte IDAT[] = { 73, 68, 65, 84 }; /** IEND tag. */ protected static final byte IEND[] = { 73, 69, 78, 68 }; /** Constants for filter (NONE) */ protected static final int FILTER_NONE = 0; protected static final byte BLACK = (byte) 0x00; protected static final byte WHITE = (byte) 0xFF; /** The png bytes. */ protected byte[] pngBytes; /** The prior row. */ protected byte[] priorRow; /** The left bytes. */ protected byte[] leftBytes; /** The width. */ protected int width, height; /** The byte position. */ protected int bytePos, maxPos; /** CRC. */ protected CRC32 crc = new CRC32(); /** The CRC value. */ protected long crcValue; /** The bytes-per-pixel. */ protected int bytesPerPixel; /** The compression level. */ protected int compressionLevel; private ByteMatrix matrix; /** * 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 ByteMatrixPngEncoder(ByteMatrix matrix, int compLevel) { this.matrix = matrix; this.width = matrix.getWidth(); this.height = matrix.getHeight(); this.bytesPerPixel = 1; 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 }; /* * 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 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 b * 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 PNG "IHDR" chunk into the pngBytes array. */ protected void writeHeader() { int startPos; startPos = bytePos = writeInt4(13, bytePos); bytePos = writeBytes(IHDR, bytePos); bytePos = writeInt4(width, bytePos); bytePos = writeInt4(height, bytePos); bytePos = writeByte(8, bytePos); // bit depth bytePos = writeByte(0, bytePos); // gray scale 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); } /** * 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() { byte[] compressedLines; // the resultant compressed lines int nCompressed; // how big is the compressed area? byte[] pixels; // storage area for byte-sized pixels Deflater scrunch = new Deflater(compressionLevel); ByteArrayOutputStream outBytes = new ByteArrayOutputStream(1024); DeflaterOutputStream compBytes = new DeflaterOutputStream(outBytes, scrunch); try { int index = 0; pixels = new byte[this.width * this.height + this.height]; for (int y = 0; y < this.height; y++) { pixels[index++] = 0; // No filter for (int x = 0; x < this.width; x++) { byte pixel = this.matrix.get(x, y); if (pixel == 0) { // BLACK pixels[index++] = BLACK; } else { // WHITE pixels[index++] = WHITE; } } } compBytes.write(pixels, 0, pixels.length); compBytes.close(); /* * Write the compressed bytes */ compressedLines = outBytes.toByteArray(); nCompressed = compressedLines.length; crc.reset(); bytePos = writeInt4(nCompressed, bytePos); bytePos = writeBytes(IDAT, bytePos); crc.update(IDAT); 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; } } /** * Write a PNG "IEND" chunk into the pngBytes array. */ protected void writeEnd() { bytePos = writeInt4(0, bytePos); bytePos = writeBytes(IEND, bytePos); crc.reset(); crc.update(IEND); crcValue = crc.getValue(); bytePos = writeInt4((int) crcValue, bytePos); } } And here is a sample BarCode generator servlet : /* * Copyright (C) 2009 Francois Masurel * * 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 3 of the License, or * 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, see <http://www.gnu.org/licenses/ >. */ package com.mably.cms.web; import java.io.IOException; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.inject.Singleton; import com.google.zxing.BarcodeFormat; import com.google.zxing.common.ByteMatrix; import com.google.zxing.qrcode.QRCodeWriter; import com.mably.cms.utils.zxing.ByteMatrixPngEncoder; /** * @author f.masurel * */ @Singleton public class BarCodeServlet extends HttpServlet { /** serialVersionUID. */ private static final long serialVersionUID = 7131942698661805870L; /** Logger. */ private static final Logger LOG = LoggerFactory.getLogger(BarCodeServlet.class); /** * {...@inheritdoc} */ public void init(ServletConfig config) throws ServletException { super.init(config); } /** * {...@inheritdoc} */ public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { try { String contents = "http://www.google.com"; int width = 200; int height = 200; QRCodeWriter bcWriter = new QRCodeWriter(); ByteMatrix matrix = bcWriter.encode( contents, BarcodeFormat.QR_CODE, width, height); ByteMatrixPngEncoder pngEncoder = new ByteMatrixPngEncoder(matrix, 9); byte[] data = pngEncoder.pngEncode(); res.setContentType("image/png"); res.setContentLength(data.length); ServletOutputStream out = res.getOutputStream(); out.write(data); out.flush(); out.close(); } catch (Exception e) { LOG.error(e.toString(), e); res.sendError (HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } } -- You received this message because you are subscribed to the Google Groups "Google App Engine for Java" group. To post to this group, send email to google-appengine-j...@googlegroups.com. To unsubscribe from this group, send email to google-appengine-java+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/google-appengine-java?hl=en.