/* Copyright (c) 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.tiff; import se.llbit.chunky.renderer.scene.Scene; import se.llbit.util.TaskTracker; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; /** * TIFF image output. This supports 32-bit floating point channel output. * * <p>Non-32bit output has been removed sine it was unused. * * @author Jesper Öqvist <jesper@llbit.se> */ public class TiffFileWriter implements AutoCloseable { private static final int ASCII = 2; private static final int SHORT = 3; private static final int LONG = 4; private static final int RATIONAL = 5; private final DataOutputStream out; public TiffFileWriter(File file) throws IOException { out = new DataOutputStream(new FileOutputStream(file)); out.write(0x4D); out.write(0x4D); out.write(0x00); out.write(0x2A); } /** * @throws IOException */ @Override public void close() throws IOException { out.close(); } private void writeHeader(int width, int height, int bytesPerSample) throws IOException { out.writeInt(ifdOffset(width, height, bytesPerSample)); } private void writeFooter(int width, int height, int bytesPerSample) throws IOException { int ifdOffset = ifdOffset(width, height, bytesPerSample); int numEntries = 15; // Number of IFD entries. out.writeShort(numEntries); // 1: Width. out.writeShort(0x0100); out.writeShort(SHORT); out.writeInt(1); out.writeShort(width); out.writeShort(0); // 2: Height. out.writeShort(0x0101); out.writeShort(SHORT); out.writeInt(1); out.writeShort(height); out.writeShort(0); // 3: Bits per sample. out.writeShort(0x0102); out.writeShort(SHORT); out.writeInt(3); int offsetBps = ifdOffset + 2 + 12 * numEntries + 2; out.writeInt(offsetBps); // 4: Compression type. out.writeShort(0x0103); out.writeShort(SHORT); out.writeInt(1); out.writeShort(1); out.writeShort(0); // 5: PhotometricInterpretation out.writeShort(0x0106); out.writeShort(SHORT); out.writeInt(1); out.writeShort(2); out.writeShort(0); // 7: StripOffsets out.writeShort(0x0111); out.writeShort(LONG); out.writeInt(1); out.writeInt(8); // 6: Orientation out.writeShort(0x0112); out.writeShort(SHORT); out.writeInt(1); out.writeShort(1); // First row is at the top of the image. out.writeShort(0); // 8: SamplesPerPixel out.writeShort(0x0115); out.writeShort(SHORT); out.writeInt(1); out.writeShort(3); out.writeShort(0); // 9: RowsPerStrip out.writeShort(0x0116); out.writeShort(LONG); out.writeInt(1); out.writeInt(height); // 10: StripByteCounts out.writeShort(0x0117); out.writeShort(LONG); out.writeInt(1); int offsetSbc = ifdOffset + 2 + 12 * numEntries + 2 + 2 * 3; out.writeInt(offsetSbc); // 11: XResolution out.writeShort(0x011A); out.writeShort(RATIONAL); out.writeInt(1); int offsetXres = ifdOffset + 2 + 12 * numEntries + 2 + 2 * 3 + 4; out.writeInt(offsetXres); // 12: YResolution out.writeShort(0x011B); out.writeShort(RATIONAL); out.writeInt(1); int offsetYres = ifdOffset + 2 + 12 * numEntries + 2 + 2 * 3 + 4 + 8; out.writeInt(offsetYres); // 13: ResolutionUnit out.writeShort(0x0128); out.writeShort(SHORT); out.writeInt(1); out.writeShort(1); out.writeShort(0); // 14: SampleFormat out.writeShort(0x0153); out.writeShort(SHORT); out.writeInt(3); int offsetSampleFormat = ifdOffset + 2 + 12 * numEntries + 2 + 2 * 3 + 4 + 8 + 8; out.writeInt(offsetSampleFormat); // 15: Software out.writeShort(0x0131); out.writeShort(ASCII); out.writeInt("Chunky".length() + 1); int offsetSoftware = ifdOffset + 2 + 12 * numEntries + 2 + 2 * 3 + 4 + 8 + 8 + 2 * 3; out.writeInt(offsetSoftware); // End of IFD. out.writeShort(0); // Bits per sample, values. out.writeShort(8 * bytesPerSample); out.writeShort(8 * bytesPerSample); out.writeShort(8 * bytesPerSample); // Strip byte count. out.writeInt(width * height * 3 * bytesPerSample); // X resolution. out.writeInt(0); out.writeInt(1); // Y resolution. out.writeInt(0); out.writeInt(1); // Sample formats. if (bytesPerSample == 1) { out.writeShort(1); out.writeShort(1); out.writeShort(1); } else { out.writeShort(3); out.writeShort(3); out.writeShort(3); } for (byte b : "Chunky".getBytes()) { out.write(b); } out.write(0); } /** * Write an image as a 32-bit per channel TIFF file. */ public void write32(Scene scene, TaskTracker.Task task) throws IOException { int width = scene.canvasWidth(); int height = scene.canvasHeight(); writeHeader(width, height, 4); for (int y = 0; y < height; ++y) { task.update(height, y); for (int x = 0; x < width; ++x) { double[] pixel = new double[3]; scene.postProcessPixel(x, y, pixel); out.writeFloat((float) pixel[0]); out.writeFloat((float) pixel[1]); out.writeFloat((float) pixel[2]); } task.update(height, y + 1); } writeFooter(width, height, 4); } private int ifdOffset(int width, int height, int bytesPerSample) { return 8 + width * height * 3 * bytesPerSample; // Offset to first IFD from file start. } }