diff --git a/docs/content.md b/docs/content.md index 139c7f5b..faea3ff2 100755 --- a/docs/content.md +++ b/docs/content.md @@ -358,7 +358,8 @@ There are **7 Encoding Presets** you can choose from: While these video files are of perfect quality, most **non-FFmpeg-based video players and video editing software** (e.g. QuickTime Player, Sony Vegas and Adobe Premiere) can't play these videos. Therefore, you should instead use the **MP4 - High Quality** preset in most cases. - **PNG Sequence** Exports the sequence as individual frames in the **PNG Format**. - **Warning:** This can create a huge amount of files, so make sure to save them in a separate folder. + The images are of perfect quality. + Depth map images are 32-bit float encoded in the 8-bit components in BGRA order and will need some kind of post-processing to be useful in most editors. - **OpenEXR Sequence** (Minecraft 1.14 and above) Exports the sequence as individual frames in the **OpenEXR Format**. The images are of perfect quality and may contain diff --git a/src/main/java/com/replaymod/render/PNGWriter.java b/src/main/java/com/replaymod/render/PNGWriter.java new file mode 100644 index 00000000..846e63be --- /dev/null +++ b/src/main/java/com/replaymod/render/PNGWriter.java @@ -0,0 +1,71 @@ +package com.replaymod.render; + +import com.replaymod.core.versions.MCVer; +import com.replaymod.render.blend.Util.IOConsumer; +import com.replaymod.render.frame.BitmapFrame; +import com.replaymod.render.rendering.Channel; +import com.replaymod.render.rendering.FrameConsumer; +import com.replaymod.render.utils.ByteBufferPool; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; +import de.johni0702.minecraft.gui.versions.Image; +import net.minecraft.util.crash.CrashReport; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +public class PNGWriter implements FrameConsumer { + + private final Path outputFolder; + + public PNGWriter(Path outputFolder) throws IOException { + this.outputFolder = outputFolder; + + Files.createDirectories(outputFolder); + } + + @Override + public void consume(Map channels) { + BitmapFrame bgraFrame = channels.get(Channel.BRGA); + BitmapFrame depthFrame = channels.get(Channel.DEPTH); + try { + if (bgraFrame != null) { + withImage(bgraFrame, image -> + image.writePNG(outputFolder.resolve(bgraFrame.getFrameId() + ".png").toFile())); + } + if (depthFrame != null) { + withImage(depthFrame, image -> + image.writePNG(outputFolder.resolve(depthFrame.getFrameId() + ".depth.png").toFile())); + } + } catch (Throwable t) { + MCVer.getMinecraft().setCrashReport(CrashReport.create(t, "Exporting EXR frame")); + } finally { + channels.values().forEach(it -> ByteBufferPool.release(it.getByteBuffer())); + } + } + + private void withImage(BitmapFrame frame, IOConsumer consumer) throws IOException { + ByteBuffer buffer = frame.getByteBuffer(); + ReadableDimension size = frame.getSize(); + int width = size.getWidth(); + int height = size.getHeight(); + try (Image image = new Image(width, height)) { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + byte b = buffer.get(); + byte g = buffer.get(); + byte r = buffer.get(); + byte a = buffer.get(); + image.setRGBA(x, y, r, g, b, a); + } + } + consumer.accept(image); + } + } + + @Override + public void close() { + } +} diff --git a/src/main/java/com/replaymod/render/RenderSettings.java b/src/main/java/com/replaymod/render/RenderSettings.java index 4117389d..bfc8783f 100644 --- a/src/main/java/com/replaymod/render/RenderSettings.java +++ b/src/main/java/com/replaymod/render/RenderSettings.java @@ -72,7 +72,7 @@ public class RenderSettings { EXR(null, "exr"), - PNG("\"%FILENAME%-%06d.png\"", "png"); + PNG(null, "png"); private final String preset; private final String fileExtension; diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java index af9f5cf0..b6eae92f 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java @@ -406,8 +406,9 @@ public class GuiRenderSettings extends AbstractGuiPopup { // Enable/Disable various options for blend export boolean isEXR = encodingPresetDropdown.getSelectedValue() == RenderSettings.EncodingPreset.EXR; + boolean isPNG = encodingPresetDropdown.getSelectedValue() == RenderSettings.EncodingPreset.PNG; boolean isBlend = renderMethod == RenderSettings.RenderMethod.BLEND; - boolean isFFmpeg = !isBlend && !isEXR; + boolean isFFmpeg = !isBlend && !isEXR && !isPNG; if (isBlend) { videoWidth.setDisabled(); videoHeight.setDisabled(); @@ -417,11 +418,11 @@ public class GuiRenderSettings extends AbstractGuiPopup { exportArguments.setEnabled(isFFmpeg); antiAliasingDropdown.setEnabled(isFFmpeg); - if (isEXR) { + if (isEXR || isPNG) { depthMap.setEnabled().setTooltip(null); } else { depthMap.setDisabled().setTooltip(new GuiTooltip().setColor(Colors.RED) - .setI18nText("replaymod.gui.rendersettings.depthmap.onlyexr")); + .setI18nText("replaymod.gui.rendersettings.depthmap.only_exr_or_png")); } // Enable/Disable export args reset button diff --git a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java index 4db4a2fc..762c74e9 100644 --- a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java +++ b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java @@ -8,6 +8,7 @@ import com.replaymod.pathing.player.AbstractTimelinePlayer; import com.replaymod.pathing.player.ReplayTimer; import com.replaymod.pathing.properties.TimestampProperty; import com.replaymod.render.CameraPathExporter; +import com.replaymod.render.PNGWriter; import com.replaymod.render.RenderSettings; import com.replaymod.render.ReplayModRender; import com.replaymod.render.FFmpegWriter; @@ -125,15 +126,17 @@ public class VideoRenderer implements RenderInfo { } else { FrameConsumer frameConsumer; if (settings.getEncodingPreset() == RenderSettings.EncodingPreset.EXR) { - ffmpegWriter = null; //#if MC>=11400 frameConsumer = new EXRWriter(settings.getOutputFile().toPath()); //#else //$$ throw new UnsupportedOperationException("EXR requires LWJGL3"); //#endif + } else if (settings.getEncodingPreset() == RenderSettings.EncodingPreset.PNG) { + frameConsumer = new PNGWriter(settings.getOutputFile().toPath()); } else { - frameConsumer = ffmpegWriter = new FFmpegWriter(this); + frameConsumer = new FFmpegWriter(this); } + ffmpegWriter = frameConsumer instanceof FFmpegWriter ? (FFmpegWriter) frameConsumer : null; FrameConsumer previewingFrameConsumer = new FrameConsumer() { @Override public void consume(Map channels) { diff --git a/src/main/resources/assets/replaymod/lang b/src/main/resources/assets/replaymod/lang index 63b7a8be..54ab62bd 160000 --- a/src/main/resources/assets/replaymod/lang +++ b/src/main/resources/assets/replaymod/lang @@ -1 +1 @@ -Subproject commit 63b7a8bea1ab9a9f005c9ae0e1c54b4432cbc9e6 +Subproject commit 54ab62bdbb646c6b4f4b6d61a26183258e5a017d