// Path: app/src/main/java/com/annimon/hotarufx/visual/Composition.java
// public class Composition {
// 
//     private final int
//             virtualWidth, virtualHeight,
//             sceneWidth, sceneHeight;
//     private final double factor;
// 
//     private final TimeLine timeline;
// 
//     private final VirtualScene scene;
// 
//     private final Paint background;
// 
//     public Composition() {
//         this(1280, 720);
//     }
// 
//     public Composition(double frameRate) {
//         this(1280, 720, frameRate);
//     }
// 
//     public Composition(int sceneWidth, int sceneHeight) {
//         this(sceneWidth, sceneHeight, 30);
//     }
// 
//     public Composition(int sceneWidth, int sceneHeight, double frameRate) {
//         this(sceneWidth, sceneHeight, frameRate, Color.WHITE);
//     }
// 
//     public Composition(int sceneWidth, int sceneHeight, double frameRate, Paint background) {
//         this.sceneWidth = sceneWidth;
//         this.sceneHeight = sceneHeight;
//         this.background = background;
//         virtualHeight = 1080;
//         factor = virtualHeight / (double) sceneHeight;
//         virtualWidth = (int) (sceneWidth * factor);
//         timeline = new TimeLine(frameRate);
//         scene = newScene();
//     }
// 
//     public int getVirtualWidth() {
//         return virtualWidth;
//     }
// 
//     public int getVirtualHeight() {
//         return virtualHeight;
//     }
// 
//     public int getSceneWidth() {
//         return sceneWidth;
//     }
// 
//     public int getSceneHeight() {
//         return sceneHeight;
//     }
// 
//     public double getFactor() {
//         return factor;
//     }
// 
//     public TimeLine getTimeline() {
//         return timeline;
//     }
// 
//     public VirtualScene getScene() {
//         return scene;
//     }
// 
//     public Paint getBackground() {
//         return background;
//     }
// 
//     private VirtualScene newScene() {
//         final var group = new NodesGroup(sceneWidth, sceneHeight);
//         group.setScaleX(1d / factor);
//         group.setScaleY(1d / factor);
//         group.setTranslateX(sceneWidth / 2f);
//         group.setTranslateY(sceneHeight / 2f);
//         return new VirtualScene(group, virtualWidth, virtualHeight);
//     }
// 
//     public Scene producePreviewScene() {
//         final var fxScene = new Scene(scene.getGroup(), sceneWidth, sceneHeight, background);
//         fxScene.setOnKeyPressed(e -> {
//             switch (e.getCode()) {
//                 case SPACE:
//                     timeline.togglePause();
//                     break;
//                 case BACK_SPACE:
//                     timeline.getFxTimeline().stop();
//                     timeline.getFxTimeline().playFromStart();
//                     break;
//                 case LEFT:
//                 case RIGHT:
//                     int sign = e.getCode() == KeyCode.LEFT ? -1 : 1;
//                     if (e.isShiftDown()) {
//                         timeline.seek(sign);
//                     } else if (e.isControlDown()) {
//                         timeline.seek(10 * sign);
//                     } else if (e.isAltDown()) {
//                         timeline.seek(30 * sign);
//                     } else {
//                         timeline.seekFrame(sign);
//                     }
//                     break;
//             }
//         });
//         return fxScene;
//     }
// 
//     public Scene produceRendererScene() {
//         return new Scene(scene.getGroup(), sceneWidth, sceneHeight, background);
//     }
// }
// 
// Path: app/src/main/java/com/annimon/hotarufx/visual/TimeLine.java
// public class TimeLine {
// 
//     private final double frameRate;
// 
//     private final Timeline fxTimeline;
// 
//     public TimeLine(double frameRate) {
//         this.frameRate = frameRate;
//         fxTimeline = new Timeline(frameRate);
//     }
// 
//     public double getFrameRate() {
//         return frameRate;
//     }
// 
//     public Timeline getFxTimeline() {
//         return fxTimeline;
//     }
// 
//     public void addKeyFrame(KeyFrame keyFrame, KeyValue fxKeyValue) {
//         fxTimeline.getKeyFrames().add(new javafx.animation.KeyFrame(
//                 duration(keyFrame), fxKeyValue));
//     }
// 
//     private Duration duration(KeyFrame keyFrame) {
//         return Duration.millis(1000d * keyFrame.getFrame() / frameRate);
//     }
// 
//     public void togglePause() {
//         switch (fxTimeline.getStatus()) {
//             case PAUSED:
//                 fxTimeline.play();
//                 break;
//             case RUNNING:
//                 fxTimeline.pause();
//                 break;
//             case STOPPED:
//                 fxTimeline.playFromStart();
//                 break;
//         }
//     }
// 
//     public void seekFrame(final int value) {
//         fxTimeline.pause();
//         final var offset = Duration.millis(1000d * Math.abs(value) / frameRate);
//         final var now = fxTimeline.getCurrentTime();
//         final var newDuration = value > 0 ? now.add(offset) : now.subtract(offset);
//         fxTimeline.jumpTo(newDuration);
//     }
// 
//     public void seek(final int sec) {
//         final var now = fxTimeline.getCurrentTime();
//         fxTimeline.jumpTo(now.add(Duration.seconds(sec)));
//     }
// }


import com.annimon.hotarufx.visual.Composition;
import com.annimon.hotarufx.visual.TimeLine;
import java.io.File;
import java.util.concurrent.CountDownLatch;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.image.WritableImage;
import javafx.util.Duration;
import javax.imageio.ImageIO;
package com.annimon.hotarufx.ui.controller;


public class RenderTask extends Task<Boolean> {

    private final File directory;
    private final Composition composition;
    private final Scene scene;
// Path: app/src/main/java/com/annimon/hotarufx/visual/Composition.java
// public class Composition {
// 
//     private final int
//             virtualWidth, virtualHeight,
//             sceneWidth, sceneHeight;
//     private final double factor;
// 
//     private final TimeLine timeline;
// 
//     private final VirtualScene scene;
// 
//     private final Paint background;
// 
//     public Composition() {
//         this(1280, 720);
//     }
// 
//     public Composition(double frameRate) {
//         this(1280, 720, frameRate);
//     }
// 
//     public Composition(int sceneWidth, int sceneHeight) {
//         this(sceneWidth, sceneHeight, 30);
//     }
// 
//     public Composition(int sceneWidth, int sceneHeight, double frameRate) {
//         this(sceneWidth, sceneHeight, frameRate, Color.WHITE);
//     }
// 
//     public Composition(int sceneWidth, int sceneHeight, double frameRate, Paint background) {
//         this.sceneWidth = sceneWidth;
//         this.sceneHeight = sceneHeight;
//         this.background = background;
//         virtualHeight = 1080;
//         factor = virtualHeight / (double) sceneHeight;
//         virtualWidth = (int) (sceneWidth * factor);
//         timeline = new TimeLine(frameRate);
//         scene = newScene();
//     }
// 
//     public int getVirtualWidth() {
//         return virtualWidth;
//     }
// 
//     public int getVirtualHeight() {
//         return virtualHeight;
//     }
// 
//     public int getSceneWidth() {
//         return sceneWidth;
//     }
// 
//     public int getSceneHeight() {
//         return sceneHeight;
//     }
// 
//     public double getFactor() {
//         return factor;
//     }
// 
//     public TimeLine getTimeline() {
//         return timeline;
//     }
// 
//     public VirtualScene getScene() {
//         return scene;
//     }
// 
//     public Paint getBackground() {
//         return background;
//     }
// 
//     private VirtualScene newScene() {
//         final var group = new NodesGroup(sceneWidth, sceneHeight);
//         group.setScaleX(1d / factor);
//         group.setScaleY(1d / factor);
//         group.setTranslateX(sceneWidth / 2f);
//         group.setTranslateY(sceneHeight / 2f);
//         return new VirtualScene(group, virtualWidth, virtualHeight);
//     }
// 
//     public Scene producePreviewScene() {
//         final var fxScene = new Scene(scene.getGroup(), sceneWidth, sceneHeight, background);
//         fxScene.setOnKeyPressed(e -> {
//             switch (e.getCode()) {
//                 case SPACE:
//                     timeline.togglePause();
//                     break;
//                 case BACK_SPACE:
//                     timeline.getFxTimeline().stop();
//                     timeline.getFxTimeline().playFromStart();
//                     break;
//                 case LEFT:
//                 case RIGHT:
//                     int sign = e.getCode() == KeyCode.LEFT ? -1 : 1;
//                     if (e.isShiftDown()) {
//                         timeline.seek(sign);
//                     } else if (e.isControlDown()) {
//                         timeline.seek(10 * sign);
//                     } else if (e.isAltDown()) {
//                         timeline.seek(30 * sign);
//                     } else {
//                         timeline.seekFrame(sign);
//                     }
//                     break;
//             }
//         });
//         return fxScene;
//     }
// 
//     public Scene produceRendererScene() {
//         return new Scene(scene.getGroup(), sceneWidth, sceneHeight, background);
//     }
// }
// 
// Path: app/src/main/java/com/annimon/hotarufx/visual/TimeLine.java
// public class TimeLine {
// 
//     private final double frameRate;
// 
//     private final Timeline fxTimeline;
// 
//     public TimeLine(double frameRate) {
//         this.frameRate = frameRate;
//         fxTimeline = new Timeline(frameRate);
//     }
// 
//     public double getFrameRate() {
//         return frameRate;
//     }
// 
//     public Timeline getFxTimeline() {
//         return fxTimeline;
//     }
// 
//     public void addKeyFrame(KeyFrame keyFrame, KeyValue fxKeyValue) {
//         fxTimeline.getKeyFrames().add(new javafx.animation.KeyFrame(
//                 duration(keyFrame), fxKeyValue));
//     }
// 
//     private Duration duration(KeyFrame keyFrame) {
//         return Duration.millis(1000d * keyFrame.getFrame() / frameRate);
//     }
// 
//     public void togglePause() {
//         switch (fxTimeline.getStatus()) {
//             case PAUSED:
//                 fxTimeline.play();
//                 break;
//             case RUNNING:
//                 fxTimeline.pause();
//                 break;
//             case STOPPED:
//                 fxTimeline.playFromStart();
//                 break;
//         }
//     }
// 
//     public void seekFrame(final int value) {
//         fxTimeline.pause();
//         final var offset = Duration.millis(1000d * Math.abs(value) / frameRate);
//         final var now = fxTimeline.getCurrentTime();
//         final var newDuration = value > 0 ? now.add(offset) : now.subtract(offset);
//         fxTimeline.jumpTo(newDuration);
//     }
// 
//     public void seek(final int sec) {
//         final var now = fxTimeline.getCurrentTime();
//         fxTimeline.jumpTo(now.add(Duration.seconds(sec)));
//     }
// }


// Path: app/src/main/java/com/annimon/hotarufx/ui/controller/RenderTask.java
import com.annimon.hotarufx.visual.Composition;
import com.annimon.hotarufx.visual.TimeLine;
import java.io.File;
import java.util.concurrent.CountDownLatch;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.image.WritableImage;
import javafx.util.Duration;
import javax.imageio.ImageIO;

package com.annimon.hotarufx.ui.controller;


public class RenderTask extends Task<Boolean> {

    private final File directory;
    private final Composition composition;
    private final Scene scene;
    private final TimeLine timeLine;