diff --git a/chunky/src/java/se/llbit/chunky/renderer/export/Tiff32ExportFormat.java b/chunky/src/java/se/llbit/chunky/renderer/export/Tiff32ExportFormat.java index 9b6c29754f..39f794b89a 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/export/Tiff32ExportFormat.java +++ b/chunky/src/java/se/llbit/chunky/renderer/export/Tiff32ExportFormat.java @@ -19,7 +19,13 @@ import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import se.llbit.chunky.renderer.scene.AlphaBuffer; import se.llbit.chunky.renderer.scene.Scene; @@ -59,15 +65,37 @@ public boolean wantsPostprocessing() { @Override public void write(OutputStream out, Scene scene, TaskTracker taskTracker) throws IOException { - assert(out instanceof FileOutputStream); - try ( - TaskTracker.Task task = taskTracker.task("Writing TIFF"); - TiffFileWriter writer = new TiffFileWriter( - ((FileOutputStream) out).getChannel(), - CompressionType.DEFLATE - ) - ) { - writer.export(scene, task); + try (TaskTracker.Task task = taskTracker.task("Writing TIFF")) { + if (out instanceof FileOutputStream) { + write(((FileOutputStream) out).getChannel(), scene, task); + } else { + // fallback for the case, that the output stream was not created on a file + Path tempFile = Files.createTempFile(scene.name + "-", getExtension()); + try (FileChannel fileChannel = FileChannel.open(tempFile, StandardOpenOption.DELETE_ON_CLOSE, + StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, + StandardOpenOption.READ, StandardOpenOption.WRITE + )) { + write(fileChannel, scene, task); + // rewind channel + fileChannel.position(0); + try (InputStream inputStream = Channels.newInputStream(fileChannel)) { + // copy temp file to output + inputStream.transferTo(out); + } + } + } } } + + /** + * Note: does not (!) close the file channel after writing + */ + private void write(FileChannel fileChannel, Scene scene, TaskTracker.Task task) throws IOException { + TiffFileWriter writer = new TiffFileWriter( + fileChannel, + CompressionType.DEFLATE + ); + writer.export(scene, task); + writer.doFinalization(); + } } diff --git a/chunky/src/java/se/llbit/imageformats/tiff/FinalizableBFCOutputStream.java b/chunky/src/java/se/llbit/imageformats/tiff/FinalizableBFCOutputStream.java index 462892dba4..8a3a4c2211 100644 --- a/chunky/src/java/se/llbit/imageformats/tiff/FinalizableBFCOutputStream.java +++ b/chunky/src/java/se/llbit/imageformats/tiff/FinalizableBFCOutputStream.java @@ -29,11 +29,22 @@ private > T writeUnfinalized(T ud, int byteCount) t return ud; } - @Override - public void close() throws IOException { + /** + * writes remaining unfinalized data + */ + public void doFinalization() throws IOException { for(UnfinalizedData data : finalizationQueue) { data.write(this); } + finalizationQueue.clear(); + } + + /** + * does finalization, then closes the output stream + */ + @Override + public void close() throws IOException { + doFinalization(); super.close(); } diff --git a/chunky/src/java/se/llbit/imageformats/tiff/TiffFileWriter.java b/chunky/src/java/se/llbit/imageformats/tiff/TiffFileWriter.java index fbd1c45305..fa08dc361a 100644 --- a/chunky/src/java/se/llbit/imageformats/tiff/TiffFileWriter.java +++ b/chunky/src/java/se/llbit/imageformats/tiff/TiffFileWriter.java @@ -43,6 +43,7 @@ public class TiffFileWriter implements AutoCloseable { private final FinalizableBFCOutputStream out; private final CompressionType compressionType; + private FinalizableBFCOutputStream.UnfinalizedData.Int nextIFDOffset; public TiffFileWriter( FileChannel fileChannel, @@ -54,12 +55,17 @@ public TiffFileWriter( // - MM -> magic bytes // - \0* -> magic number 42 for big-endian byte order out.writeInt(0x4D4D002A); + nextIFDOffset = out.writeUnfinalizedInt(); } public TiffFileWriter(FileOutputStream outputStream) throws IOException { this(outputStream.getChannel(), CompressionType.NONE); } + public void doFinalization() throws IOException { + out.doFinalization(); + } + @Override public void close() throws IOException { out.close(); @@ -68,11 +74,12 @@ public void close() throws IOException { /** * Export sample buffer as Baseline TIFF RGB image / TIFF Class R image * with 32 bits per color component. + * + *

Note: This method does not close the output stream, and can be called multiple times for multiple layers. + * Use {@link #doFinalization()} to complete the export. */ public void export(Scene scene, TaskTracker.Task task) throws IOException { - FinalizableBFCOutputStream.UnfinalizedData.Int ifdOffset = out.writeUnfinalizedInt(); - - writePrimaryIDF(ifdOffset, scene, task); + nextIFDOffset = writePrimaryIDF(nextIFDOffset, scene, task); } private static final DateTimeFormatter DATETIME_FORMAT = DateTimeFormatter.ofPattern("yyyy:MM:dd HH:mm:ss");