/******************************************************************************* * Copyright (c) MOBAC developers * * 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, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package mobac.utilities.imageio; /* * 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. * */ import static mobac.utilities.imageio.PngConstants.*; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.DataBufferInt; import java.awt.image.DirectColorModel; import java.io.BufferedOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.zip.CRC32; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import javax.activation.UnsupportedDataTypeException; /** * A PNG writer that is able to write extra large PNG images using incremental * writing. * <p> * The image is processed incremental in "tile lines" - e.g. an PNG image of * 30000 x 20000 pixels (width x height) can be written by 200 "tile lines" of * size 30000 x 100 pixels. Each tile line can be written via the method * {@link #writeTileLine(BufferedImage)}. After writing the last line you have * to call {@link #finish()} which will write the final PNG structure * information into the {@link OutputStream}. * </p> * <p> * Please note that this writer creates 24bit/truecolor PNGs. Transparency and * alpha masks are not supported. * </p> * Bases on the PNGWriter written by Matthias Mann - www.matthiasmann.de * * @author r_x */ public class PngXxlWriter { private static final int BUFFER_SIZE = 128 * 1024; private int width; private int height; private DataOutputStream dos; ImageDataChunkWriter imageDataChunkWriter; /** * Creates an PNG writer instance for an image with the specified width and * height. * * @param width * width of the PNG image to be written * @param height * height of the PNG image to be written * @param os * destination to write the PNG image data to * @throws IOException */ public PngXxlWriter(int width, int height, OutputStream os) throws IOException { this.width = width; this.height = height; this.dos = new DataOutputStream(os); dos.write(SIGNATURE); PngChunk cIHDR = new PngChunk(IHDR); cIHDR.writeInt(this.width); cIHDR.writeInt(this.height); cIHDR.writeByte(8); // 8 bit per component cIHDR.writeByte(COLOR_TRUECOLOR); cIHDR.writeByte(COMPRESSION_DEFLATE); cIHDR.writeByte(FILTER_SET_1); cIHDR.writeByte(INTERLACE_NONE); cIHDR.writeTo(dos); imageDataChunkWriter = new ImageDataChunkWriter(dos); } /** * * @param tileLineImage * @throws IOException */ public void writeTileLine(BufferedImage tileLineImage) throws IOException { int tileLineHeight = tileLineImage.getHeight(); int tileLineWidth = tileLineImage.getWidth(); if (width != tileLineWidth) throw new RuntimeException("Invalid width"); ColorModel cm = tileLineImage.getColorModel(); if (!(cm instanceof DirectColorModel)) throw new UnsupportedDataTypeException( "Image uses wrong color model. Only DirectColorModel is supported!"); // We process the image line by line, from head to bottom Rectangle rect = new Rectangle(0, 0, tileLineWidth, 1); DataOutputStream imageDataStream = imageDataChunkWriter.getStream(); byte[] curLine = new byte[width * 3]; for (int line = 0; line < tileLineHeight; line++) { rect.y = line; DataBuffer db = tileLineImage.getData(rect).getDataBuffer(); if (db.getNumBanks() > 1) throw new UnsupportedDataTypeException("Image data has more than one data bank"); if (db instanceof DataBufferByte) curLine = ((DataBufferByte) db).getData(); else if (db instanceof DataBufferInt) { int[] intLine = ((DataBufferInt) db).getData(); int c = 0; for (int i = 0; i < intLine.length; i++) { int pixel = intLine[i]; curLine[c++] = (byte) (pixel >> 16 & 0xFF); curLine[c++] = (byte) (pixel >> 8 & 0xFF); curLine[c++] = (byte) (pixel & 0xFF); } } else throw new UnsupportedDataTypeException(db.getClass().getName()); imageDataStream.write(FILTER_TYPE_NONE); imageDataStream.write(curLine); } } public void finish() throws IOException { imageDataChunkWriter.finish(); PngChunk cIEND = new PngChunk(IEND); cIEND.writeTo(dos); cIEND.close(); dos.flush(); } static class ImageDataChunkWriter extends OutputStream { DeflaterOutputStream dfos; DataOutputStream stream; DataOutputStream out; CRC32 crc = new CRC32(); public ImageDataChunkWriter(DataOutputStream out) throws IOException { this.out = out; dfos = new DeflaterOutputStream(new BufferedOutputStream(this, BUFFER_SIZE), new Deflater(Deflater.BEST_COMPRESSION)); stream = new DataOutputStream(dfos); } public DataOutputStream getStream() { return stream; } public void finish() throws IOException { stream.flush(); stream.close(); dfos.finish(); dfos = null; } @Override public void write(byte[] b, int off, int len) throws IOException { crc.reset(); out.writeInt(len); out.writeInt(IDAT); out.write(b, off, len); crc.update("IDAT".getBytes()); crc.update(b, off, len); out.writeInt((int) crc.getValue()); } @Override public void write(byte[] b) throws IOException { write(b, 0, b.length); } @Override public void write(int b) throws IOException { throw new IOException("Simgle byte writing not supported"); } } }