Newsflash

A good friend has spent his lunch time to create a new page header for my new site. You can already see it - I hope you like it.
 
Creating PNG files PDF Print
Written by Matthias Mann   

I showed in the article Creating Screenshots how a screenshot can be saved as TGA file.

We all know that uncompressed TGA files are a bit large and the JPEG files look not so nice for CG images.

In this article I'll show a simple Java class that writes a PNG files without using AWT or ImageIO.

PNG files use Deflate as compression method. To improve the compression ratio several filter are defined by the PNG standard. This PNG encoder uses only the Paeth predictor. Better ratios may be possible if all possible predictors are tried and the smallest output is choosen - this is left as an experiment for the reader of this artcile.

But enough with the boring talk - here comes the code:

/*
 * PNGWriter.java
 *
 * Copyright (c) 2007 Matthias Mann - www.matthiasmann.de
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */
 
package de.matthiasmann.worldscape.textures;
 
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
 
/**
 * A small PNG writer to save RGB data.
 *
 * @author Matthias Mann
 */
public class PNGWriter implements ScreenshotWriterFactory {
 
    private static final byte[] SIGNATURE = {(byte)137, 80, 78, 71, 13, 10, 26, 10};
    private static final int IHDR = (int)0x49484452;
    private static final int IDAT = (int)0x49444154;
    private static final int IEND = (int)0x49454E44;
    private static final byte COLOR_TRUECOLOR = 2;
    private static final byte COMPRESSION_DEFLATE = 0;
    private static final byte FILTER_NONE = 0;
    private static final byte INTERLACE_NONE = 0;
    private static final byte PAETH = 4;
 
    /**
     * Writes an image in OpenGL GL_RGB format to an OutputStream.
     *
     * @param os The output stream where the PNG should be written to
     * @param t The Texture object. Contains a ByteBuffer with
     *          compact RGB data (no padding between lines)
     */
    public static void write(OutputStream os, Texture2D t) throws IOException {
        if(t.getFormat() != TextureFormat.RGB) {
            throw new UnsupportedOperationException("Not yet implemented");
        }
        // duplicate the ByteBuffer to preserve position in case of concurrent access
        ByteBuffer bb = t.getTextureData().duplicate();
 
        DataOutputStream dos = new DataOutputStream(os);
        dos.write(SIGNATURE);
 
        Chunk cIHDR = new Chunk(IHDR);
        cIHDR.writeInt(t.getWidth());
        cIHDR.writeInt(t.getHeight());
        cIHDR.writeByte(8); // 8 bit per component
        cIHDR.writeByte(COLOR_TRUECOLOR);
        cIHDR.writeByte(COMPRESSION_DEFLATE);
        cIHDR.writeByte(FILTER_NONE);
        cIHDR.writeByte(INTERLACE_NONE);
        cIHDR.writeTo(dos);
 
        Chunk cIDAT = new Chunk(IDAT);
        DeflaterOutputStream dfos = new DeflaterOutputStream(
            cIDAT, new Deflater(Deflater.BEST_COMPRESSION));
 
        int lineLen = t.getWidth() * 3;
        byte[] lineOut = new byte[lineLen];
        byte[] curLine = new byte[lineLen];
        byte[] prevLine = new byte[lineLen];
 
        for(int line=0 ; line<t.getHeight() ; line++) {
            bb.position((t.getHeight() - line - 1)*lineLen);
            bb.get(curLine);
 
            lineOut[0] = (byte)(curLine[0] - prevLine[0]);
            lineOut[1] = (byte)(curLine[1] - prevLine[1]);
            lineOut[2] = (byte)(curLine[2] - prevLine[2]);
 
            for(int x=3 ; x<lineLen ; x++) {
                int a = curLine[x-3] & 255;
                int b = prevLine[x] & 255;
                int c = prevLine[x-3] & 255;
                int p = a + b - c;
                int pa = p - a; if(pa < 0) pa = -pa;
                int pb = p - b; if(pb < 0) pb = -pb;
                int pc = p - c; if(pc < 0) pc = -pc;
                if(pa<=pb && pa<=pc)
                    c = a;
                else if(pb<=pc)
                    c = b;
                lineOut[x] = (byte)(curLine[x] - c);
            }
 
            dfos.write(PAETH);
            dfos.write(lineOut);
 
            // swap the line buffers
            byte[] temp = curLine;
            curLine = prevLine;
            prevLine = temp;
        }
 
        dfos.finish();
        try {
            cIDAT.writeTo(dos);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
 
        Chunk cIEND = new Chunk(IEND);
        cIEND.writeTo(dos);
 
        dos.flush();
    }
 
    /**
     * Writes an image in OpenGL GL_RGB format to a File.
     *
     * @param file The file where the PNG should be written to.
                   Existing files will be overwritten.
     * @param t The Texture object. Contains a ByteBuffer with
     *          compact RGB data (no padding between lines)
     */
    public static void write(File file, Texture2D t) throws IOException {
        FileOutputStream fos = new FileOutputStream(file);
        try {
            write(fos, t);
        } finally {
            fos.close();
        }
    }
 
    /**
     * Creates a job for use with {@link java.util.concurrent.ExecutorService}
     *
     * @param file The file where the PNG should be written to.
                   Existing files will be overwritten.
     * @param t The Texture object. Contains a ByteBuffer with
     *          compact RGB data (no padding between lines)
     */
    public Runnable createJob(final File f, final Texture2D t) {
        return new Runnable() {
            public void run() {
                try {
                    write(f, t);
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        };
    }
 
    static class Chunk extends DataOutputStream {
        final CRC32 crc;
        final ByteArrayOutputStream baos;
 
        Chunk(int chunkType) throws IOException {
            this(chunkType, new ByteArrayOutputStream(), new CRC32());
        }
        private Chunk(int chunkType, ByteArrayOutputStream baos,
                      CRC32 crc) throws IOException {
            super(new CheckedOutputStream(baos, crc));
            this.crc = crc;
            this.baos = baos;
 
            writeInt(chunkType);
        }
 
        public void writeTo(DataOutputStream out) throws IOException {
            flush();
            out.writeInt(baos.size() - 4);
            baos.writeTo(out);
            out.writeInt((int)crc.getValue());
        }
    }
}
Comments
Add NewSearch
Only registered users can write comments!
Anonymous - Cool stuff... Unregistered | 2007-10-21 13:29:50
Nice work! This is so usefull, that I based saving images in my engine on this. Having no dependencies on AWT or ImageIO is a very big bonus. Thanks for sharing it.
Last Updated ( Monday, 04 June 2007 )
 
Next >
2014 Matthias Mann, www.matthiasmann.de
Joomla! is Free Software released under the GNU/GPL License.