Skip to content

Commit cec82a6

Browse files
authored
Add a button to copy the current frame to the clipboard. (#583)
* Add a button to copy the current frame to the clipboard. * Always use png for the clipboard.
1 parent 1a46f7b commit cec82a6

File tree

6 files changed

+74
-30
lines changed

6 files changed

+74
-30
lines changed

chunky/src/java/se/llbit/chunky/map/MapBuffer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
package se.llbit.chunky.map;
1818

19+
import java.io.FileOutputStream;
1920
import javafx.scene.canvas.GraphicsContext;
2021
import javafx.scene.image.PixelFormat;
2122
import javafx.scene.image.WritableImage;
@@ -266,7 +267,7 @@ public synchronized void renderPng(File targetFile) throws IOException {
266267
int height = view.height;
267268
int[] pixels = new int[width * height];
268269
image.getPixelReader().getPixels(0, 0, width, height, PIXEL_FORMAT, pixels, 0, width);
269-
try (PngFileWriter pngWriter = new PngFileWriter(targetFile)) {
270+
try (PngFileWriter pngWriter = new PngFileWriter(new FileOutputStream(targetFile))) {
270271
pngWriter.write(pixels, width, height, TaskTracker.Task.NONE);
271272
}
272273
}

chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
package se.llbit.chunky.renderer.scene;
1818

19+
import java.io.FileOutputStream;
1920
import org.apache.commons.math3.util.FastMath;
2021
import se.llbit.chunky.PersistentSettings;
2122
import se.llbit.chunky.block.Air;
@@ -1465,18 +1466,30 @@ public void saveSnapshot(File directory, TaskTracker progress, int threadCount)
14651466
if (!finalized) {
14661467
postProcessFrame(progress);
14671468
}
1468-
writeImage(targetFile, progress);
1469+
writeImage(targetFile, outputMode, progress);
14691470
}
14701471

14711472
/**
1472-
* Save the current frame as a PNG image.
1473+
* Save the current frame as a PNG or TIFF image.
14731474
*/
14741475
public synchronized void saveFrame(File targetFile, TaskTracker progress, int threadCount) {
14751476
computeAlpha(progress, threadCount);
14761477
if (!finalized) {
14771478
postProcessFrame(progress);
14781479
}
1479-
writeImage(targetFile, progress);
1480+
writeImage(targetFile, outputMode, progress);
1481+
}
1482+
1483+
/**
1484+
* Save the current frame as a PNG or TIFF image into the given output stream.
1485+
*/
1486+
public synchronized void writeFrame(OutputStream out, OutputMode mode, TaskTracker progress, int threadCount)
1487+
throws IOException {
1488+
computeAlpha(progress, threadCount);
1489+
if (!finalized) {
1490+
postProcessFrame(progress);
1491+
}
1492+
writeImage(out, mode, progress);
14801493
}
14811494

14821495
/**
@@ -1546,30 +1559,39 @@ public void postProcessFrame(TaskTracker progress) {
15461559
/**
15471560
* Write buffer data to image.
15481561
*
1549-
* @param targetFile file to write to.
1562+
* @param out output stream to write to.
15501563
*/
1551-
private void writeImage(File targetFile, TaskTracker progress) {
1552-
if (outputMode == OutputMode.PNG) {
1553-
writePng(targetFile, progress);
1554-
} else if (outputMode == OutputMode.TIFF_32) {
1555-
writeTiff(targetFile, progress);
1564+
private void writeImage(OutputStream out, OutputMode mode, TaskTracker progress) throws IOException {
1565+
if (mode == OutputMode.PNG) {
1566+
writePng(out, progress);
1567+
} else if (mode == OutputMode.TIFF_32) {
1568+
writeTiff(out, progress);
1569+
}
1570+
}
1571+
1572+
private void writeImage(File targetFile, OutputMode mode, TaskTracker progress) {
1573+
try (FileOutputStream out = new FileOutputStream(targetFile)) {
1574+
writeImage(out, mode, progress);
1575+
} catch (IOException e) {
1576+
Log.warn("Failed to write file: " + targetFile.getAbsolutePath(), e);
15561577
}
15571578
}
15581579

15591580
/**
15601581
* Write PNG image.
15611582
*
1562-
* @param targetFile file to write to.
1583+
* @param out output stream to write to.
15631584
*/
1564-
private void writePng(File targetFile, TaskTracker progress) {
1585+
private void writePng(OutputStream out, TaskTracker progress) throws IOException {
15651586
try (TaskTracker.Task task = progress.task("Writing PNG");
1566-
PngFileWriter writer = new PngFileWriter(targetFile)) {
1587+
PngFileWriter writer = new PngFileWriter(out)) {
15671588
if (transparentSky) {
15681589
writer.write(backBuffer.data, alphaChannel, width, height, task);
15691590
} else {
15701591
writer.write(backBuffer.data, width, height, task);
15711592
}
1572-
if (camera.getProjectionMode() == ProjectionMode.PANORAMIC && camera.getFov() >= 179
1593+
if (camera.getProjectionMode() == ProjectionMode.PANORAMIC
1594+
&& camera.getFov() >= 179
15731595
&& camera.getFov() <= 181) {
15741596
String xmp = "";
15751597
xmp += "<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>\n";
@@ -1596,22 +1618,18 @@ private void writePng(File targetFile, TaskTracker progress) {
15961618
ITXT iTXt = new ITXT("XML:com.adobe.xmp", xmp);
15971619
writer.writeChunk(iTXt);
15981620
}
1599-
} catch (IOException e) {
1600-
Log.warn("Failed to write PNG file: " + targetFile.getAbsolutePath(), e);
16011621
}
16021622
}
16031623

16041624
/**
16051625
* Write TIFF image.
16061626
*
1607-
* @param targetFile file to write to.
1627+
* @param out output stream to write to.
16081628
*/
1609-
private void writeTiff(File targetFile, TaskTracker progress) {
1629+
private void writeTiff(OutputStream out, TaskTracker progress) throws IOException {
16101630
try (TaskTracker.Task task = progress.task("Writing TIFF");
1611-
TiffFileWriter writer = new TiffFileWriter(targetFile)) {
1631+
TiffFileWriter writer = new TiffFileWriter(out)) {
16121632
writer.write32(this, task);
1613-
} catch (IOException e) {
1614-
Log.warn("Failed to write TIFF file: " + targetFile.getAbsolutePath(), e);
16151633
}
16161634
}
16171635

chunky/src/java/se/llbit/chunky/ui/ChunkyFxController.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717
package se.llbit.chunky.ui;
1818

19+
import java.io.PipedInputStream;
20+
import java.io.PipedOutputStream;
1921
import javafx.application.Application;
2022
import javafx.application.Platform;
2123
import javafx.beans.property.BooleanProperty;
@@ -41,7 +43,10 @@
4143
import javafx.scene.control.TextFormatter;
4244
import javafx.scene.control.ToggleButton;
4345
import javafx.scene.control.Tooltip;
46+
import javafx.scene.image.Image;
4447
import javafx.scene.image.ImageView;
48+
import javafx.scene.input.Clipboard;
49+
import javafx.scene.input.ClipboardContent;
4550
import javafx.scene.input.KeyCode;
4651
import javafx.scene.input.KeyCodeCombination;
4752
import javafx.scene.input.KeyCombination;
@@ -150,6 +155,7 @@ public class ChunkyFxController
150155
@FXML private Button loadScene;
151156

152157
@FXML private Button saveFrameBtn;
158+
@FXML private Button copyFrameBtn;
153159
@FXML private ProgressBar progressBar;
154160
@FXML private Label progressLbl;
155161
@FXML private Label etaLbl;
@@ -640,6 +646,7 @@ public synchronized void exportZip(File targetFile, ProgressTracker progress) {
640646
previewTab.setGraphic(new ImageView(Icon.sky.fxImage()));
641647

642648
saveFrameBtn.setOnAction(this::saveCurrentFrame);
649+
copyFrameBtn.setOnAction(this::copyCurrentFrame);
643650
start.setGraphic(new ImageView(Icon.play.fxImage()));
644651
start.setTooltip(new Tooltip("Start rendering."));
645652
start.setOnAction(e -> scene.startRender());
@@ -717,6 +724,25 @@ private void saveCurrentFrame(Event event) {
717724
}
718725
}
719726

727+
private void copyCurrentFrame(Event event) {
728+
try {
729+
PipedInputStream in = new PipedInputStream();
730+
PipedOutputStream out = new PipedOutputStream(in);
731+
new Thread(() -> {
732+
try {
733+
scene.writeFrame(out, OutputMode.PNG, new TaskTracker(ProgressListener.NONE), renderController.getContext().numRenderThreads());
734+
} catch (IOException e) {
735+
Log.warn("Failed to copy image to clipboard", e);
736+
}
737+
}).start();
738+
ClipboardContent content = new ClipboardContent();
739+
content.putImage(new Image(in));
740+
Clipboard.getSystemClipboard().setContent(content);
741+
} catch(IOException e) {
742+
Log.warn("Failed to copy image to clipboard", e);
743+
}
744+
}
745+
720746
@Override public boolean allowSceneRefresh() {
721747
if (scene.getResetReason() == ResetReason.SCENE_LOADED
722748
|| renderer.getRenderStatus().getRenderTime() < SCENE_EDIT_GRACE_PERIOD) {

chunky/src/java/se/llbit/png/PngFileWriter.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@
1616
*/
1717
package se.llbit.png;
1818

19+
import java.io.OutputStream;
1920
import se.llbit.util.TaskTracker;
2021

2122
import java.io.DataOutputStream;
22-
import java.io.File;
23-
import java.io.FileOutputStream;
2423
import java.io.IOException;
2524
import java.util.zip.Deflater;
2625

@@ -41,9 +40,9 @@ public class PngFileWriter implements AutoCloseable {
4140
/**
4241
* @throws IOException
4342
*/
44-
public PngFileWriter(File file) throws IOException {
45-
out = new DataOutputStream(new FileOutputStream(file));
46-
out.writeLong(PNG_SIGNATURE);
43+
public PngFileWriter(OutputStream out) throws IOException {
44+
this.out = new DataOutputStream(out);
45+
this.out.writeLong(PNG_SIGNATURE);
4746
}
4847

4948
/**

chunky/src/java/se/llbit/tiff/TiffFileWriter.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@
2020
import se.llbit.util.TaskTracker;
2121

2222
import java.io.DataOutputStream;
23-
import java.io.File;
24-
import java.io.FileOutputStream;
23+
import java.io.OutputStream;
2524
import java.io.IOException;
2625

2726
/**
@@ -40,8 +39,8 @@ public class TiffFileWriter implements AutoCloseable {
4039

4140
private final DataOutputStream out;
4241

43-
public TiffFileWriter(File file) throws IOException {
44-
out = new DataOutputStream(new FileOutputStream(file));
42+
public TiffFileWriter(OutputStream out) throws IOException {
43+
this.out = new DataOutputStream(out);
4544
out.write(0x4D);
4645
out.write(0x4D);
4746
out.write(0x00);

chunky/src/res/se/llbit/chunky/ui/Chunky.fxml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
</tooltip>
5050
</Button>
5151
<Button fx:id="saveFrameBtn" mnemonicParsing="false" text="Save current frame" />
52+
<Button fx:id="copyFrameBtn" mnemonicParsing="false" text="Copy current frame" />
5253
</HBox>
5354
<SplitPane fx:id="splitPane"
5455
VBox.vgrow="ALWAYS"

0 commit comments

Comments
 (0)