/* Copyright (c) 2012-2015 Jesper Öqvist <jesper@llbit.se> * * This file is part of Chunky. * * Chunky 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 * (at your option) any later version. * * Chunky 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 Chunky. If not, see <http://www.gnu.org/licenses/>. */ package se.llbit.png; import se.llbit.util.TaskTracker; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.zip.Deflater; /** * @author Jesper Öqvist <jesper@llbit.se> */ public class PngFileWriter implements AutoCloseable { /** * PNG magic value */ public static final long PNG_SIGNATURE = 0x89504E470D0A1A0AL; public static final int MAX_CHUNK_BYTES = 0x100000; // Max input/output buffer size = 1 MiB. private final DataOutputStream out; /** * @throws IOException */ public PngFileWriter(File file) throws IOException { out = new DataOutputStream(new FileOutputStream(file)); out.writeLong(PNG_SIGNATURE); } /** * @throws IOException */ public void writeChunk(PngChunk chunk) throws IOException { chunk.writeChunk(out); } /** * @throws IOException */ @Override public void close() throws IOException { out.close(); } /** * Write the image to a PNG file. */ public void write(int[] data, int width, int height, TaskTracker.Task task) throws IOException { writeChunk(new IHDR(width, height)); IDATWriter idat = new IDATWriter(); int i = 0; for (int y = 0; y < height; ++y) { task.update(height, y); idat.write(IDAT.FILTER_TYPE_NONE); // Scanline header. for (int x = 0; x < width; ++x) { int rgb = data[i++]; idat.write((rgb >> 16) & 0xFF); idat.write((rgb >> 8) & 0xFF); idat.write(rgb & 0xFF); } task.update(height, y + 1); } idat.close(); } /** * Write the image to a PNG file. */ public void write(int[] data, byte[] alpha, int width, int height, TaskTracker.Task task) throws IOException { writeChunk(new IHDR(width, height, IHDR.COLOR_TYPE_RGBA)); IDATWriter idat = new IDATWriter(); int i = 0; for (int y = 0; y < height; ++y) { task.update(height, y); idat.write(IDAT.FILTER_TYPE_NONE); // Scanline header. for (int x = 0; x < width; ++x) { int rgb = data[i]; idat.write((rgb >> 16) & 0xFF); idat.write((rgb >> 8) & 0xFF); idat.write(rgb & 0xFF); idat.write(alpha[i]); i += 1; } task.update(height, y + 1); } idat.close(); } class IDATWriter { Deflater deflater = new Deflater(); int inputSize = 0; byte[] inputBuf = new byte[MAX_CHUNK_BYTES]; int outputSize = 0; byte[] outputBuf = new byte[MAX_CHUNK_BYTES]; void write(int b) throws IOException { if (inputSize == MAX_CHUNK_BYTES) { deflater.setInput(inputBuf, 0, inputSize); inputSize = 0; deflate(); } inputBuf[inputSize++] = (byte) b; } void write16(int bb) throws IOException { write(bb >> 8); write(bb & 0xFF); } private void deflate() throws IOException { int deflated; do { if (outputSize == MAX_CHUNK_BYTES) { writeChunk(); } deflated = deflater.deflate(outputBuf, outputSize, MAX_CHUNK_BYTES - outputSize); outputSize += deflated; } while (deflated != 0); } private void writeChunk() throws IOException { out.writeInt(outputSize); CrcOutputStream crcOut = new CrcOutputStream(); DataOutputStream crc = new DataOutputStream(crcOut); crc.writeInt(IDAT.CHUNK_TYPE); out.writeInt(IDAT.CHUNK_TYPE); crc.write(outputBuf, 0, outputSize); out.write(outputBuf, 0, outputSize); out.writeInt(crcOut.getCRC()); crc.close(); outputSize = 0; } void close() throws IOException { if (inputSize > 0) { deflater.setInput(inputBuf, 0, inputSize); deflater.finish(); inputSize = 0; deflate(); } if (outputSize > 0) { writeChunk(); } deflater.end(); } } }