diff --git a/pom.xml b/pom.xml
index 6698a4a4..e777f2dd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
sc.fiji
bigdataviewer-core
- 10.4.6-SNAPSHOT
+ 10.4.7-SNAPSHOT
BigDataViewer Core
BigDataViewer core classes with minimal dependencies.
diff --git a/src/main/java/bdv/BigDataViewer.java b/src/main/java/bdv/BigDataViewer.java
index 7e05d08e..f291f2b4 100644
--- a/src/main/java/bdv/BigDataViewer.java
+++ b/src/main/java/bdv/BigDataViewer.java
@@ -29,6 +29,7 @@
package bdv;
import bdv.tools.PreferencesDialog;
+import bdv.tools.movie.ProduceMovieDialog;
import bdv.ui.UIUtils;
import bdv.ui.keymap.Keymap;
import bdv.ui.keymap.KeymapManager;
@@ -127,6 +128,8 @@ public class BigDataViewer
protected final RecordMovieDialog movieDialog;
+ protected final ProduceMovieDialog produceMovieDialog;
+
protected final RecordMaxProjectionDialog movieMaxProjectDialog;
protected final VisibilityAndGroupingDialog activeSourcesDialog;
@@ -398,6 +401,7 @@ public BigDataViewer(
cropDialog = ( spimData == null ) ? null : new CropDialog( viewerFrame, viewer, spimData.getSequenceDescription() );
movieDialog = new RecordMovieDialog( viewerFrame, viewer, progressWriter );
+ produceMovieDialog = new ProduceMovieDialog(viewerFrame,viewer,progressWriter);
// this is just to get updates of window size:
viewer.getDisplay().overlays().add( movieDialog );
@@ -502,6 +506,10 @@ public boolean accept( final File f )
miMovie.setText( "Record Movie" );
menu.add( miMovie );
+ final JMenuItem miMovieProducer = new JMenuItem(actionMap.get(BigDataViewerActions.PRODUCE_MOVIE));
+ miMovieProducer.setText("Produce Movie");
+ menu.add(miMovieProducer);
+
final JMenuItem miMaxProjectMovie = new JMenuItem( actionMap.get( BigDataViewerActions.RECORD_MAX_PROJECTION_MOVIE ) );
miMaxProjectMovie.setText( "Record Max-Projection Movie" );
menu.add( miMaxProjectMovie );
@@ -775,7 +783,7 @@ public void collapseCardPanel()
public static void main( final String[] args )
{
- final String fn = "/Users/pietzsch/workspace/data/111010_weber_resave.xml";
+ final String fn = "/Users/zouinkhim/Desktop/unfinishedProjects/grid-3d-stitched-h5/dataset.xml";
try
{
System.setProperty( "apple.laf.useScreenMenuBar", "true" );
diff --git a/src/main/java/bdv/BigDataViewerActions.java b/src/main/java/bdv/BigDataViewerActions.java
index 9760aa21..20bc92bf 100644
--- a/src/main/java/bdv/BigDataViewerActions.java
+++ b/src/main/java/bdv/BigDataViewerActions.java
@@ -61,6 +61,7 @@ public class BigDataViewerActions extends Actions
public static final String LOAD_SETTINGS = "load settings";
public static final String EXPAND_CARDS = "expand and focus cards panel";
public static final String COLLAPSE_CARDS = "collapse cards panel";
+ public static final String PRODUCE_MOVIE = "produce movie";
public static final String RECORD_MOVIE = "record movie";
public static final String RECORD_MAX_PROJECTION_MOVIE = "record max projection movie";
public static final String SET_BOOKMARK = "set bookmark";
@@ -78,6 +79,7 @@ public class BigDataViewerActions extends Actions
public static final String[] EXPAND_CARDS_KEYS = new String[] { "P" };
public static final String[] COLLAPSE_CARDS_KEYS = new String[] { "shift P", "shift ESCAPE" };
public static final String[] RECORD_MOVIE_KEYS = new String[] { "F10" };
+ public static final String[] PRODUCE_MOVIE_KEYS = new String[] { "F7" };
public static final String[] RECORD_MAX_PROJECTION_MOVIE_KEYS = new String[] { "F8" };
public static final String[] SET_BOOKMARK_KEYS = new String[] { "shift B" };
public static final String[] GO_TO_BOOKMARK_KEYS = new String[] { "B" };
@@ -108,6 +110,7 @@ public void getCommandDescriptions( final CommandDescriptions descriptions )
descriptions.add( EXPAND_CARDS, EXPAND_CARDS_KEYS, "Expand and focus the BigDataViewer card panel" );
descriptions.add( COLLAPSE_CARDS, COLLAPSE_CARDS_KEYS, "Collapse the BigDataViewer card panel" );
descriptions.add( RECORD_MOVIE, RECORD_MOVIE_KEYS, "Show the Record Movie dialog." );
+ descriptions.add( PRODUCE_MOVIE, PRODUCE_MOVIE_KEYS, "Show the Produce Movie dialog." );
descriptions.add( RECORD_MAX_PROJECTION_MOVIE, RECORD_MAX_PROJECTION_MOVIE_KEYS, "Show the Record Max Projection Movie dialog." );
descriptions.add( SET_BOOKMARK, SET_BOOKMARK_KEYS, "Set a labeled bookmark at the current location." );
descriptions.add( GO_TO_BOOKMARK, GO_TO_BOOKMARK_KEYS, "Retrieve a labeled bookmark location." );
@@ -132,6 +135,7 @@ public static void install( final Actions actions, final BigDataViewer bdv )
toggleDialogAction( actions, bdv.helpDialog, SHOW_HELP, SHOW_HELP_KEYS );
toggleDialogAction( actions, bdv.cropDialog, CROP, CROP_KEYS );
toggleDialogAction( actions, bdv.movieDialog, RECORD_MOVIE, RECORD_MOVIE_KEYS );
+ toggleDialogAction( actions, bdv.produceMovieDialog, PRODUCE_MOVIE, PRODUCE_MOVIE_KEYS );
toggleDialogAction( actions, bdv.movieMaxProjectDialog, RECORD_MAX_PROJECTION_MOVIE, RECORD_MAX_PROJECTION_MOVIE_KEYS );
toggleDialogAction( actions, bdv.preferencesDialog, PREFERENCES_DIALOG, PREFERENCES_DIALOG_KEYS );
bookmarks( actions, bdv.bookmarkEditor );
diff --git a/src/main/java/bdv/tools/movie/MovieProducer.java b/src/main/java/bdv/tools/movie/MovieProducer.java
new file mode 100644
index 00000000..f0f233c7
--- /dev/null
+++ b/src/main/java/bdv/tools/movie/MovieProducer.java
@@ -0,0 +1,207 @@
+
+/**
+ * License: GPL
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 2
+ * as published by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package bdv.tools.movie;
+
+import bdv.cache.CacheControl;
+import bdv.export.ProgressWriter;
+import bdv.viewer.ViewerPanel;
+import bdv.viewer.ViewerState;
+import bdv.viewer.animate.SimilarityTransformAnimator;
+import bdv.viewer.overlay.MultiBoxOverlayRenderer;
+import bdv.viewer.overlay.ScaleBarOverlayRenderer;
+import bdv.viewer.render.MultiResolutionRenderer;
+import bdv.viewer.render.PainterThread;
+import bdv.viewer.render.RenderTarget;
+import bdv.viewer.render.awt.BufferedImageRenderResult;
+import net.imglib2.realtransform.AffineTransform3D;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author Stephan Saalfeld <saalfelds@janelia.hhmi.org>
+ * Code was copied from org.janelia.saalfeldlab.hotknife.VNCMovie;
+ * Modified and adapted by Marwan Zouinkhi
+ */
+
+public class MovieProducer {
+
+ public static class Target implements RenderTarget {
+
+ public BufferedImageRenderResult renderResult = new BufferedImageRenderResult();
+
+ private final int width;
+ private final int height;
+
+ public Target(final int width, final int height) {
+ this.width = width;
+ this.height = height;
+ }
+
+ @Override
+ public BufferedImageRenderResult getReusableRenderResult() {
+ return renderResult;
+ }
+
+ @Override
+ public BufferedImageRenderResult createRenderResult() {
+ return new BufferedImageRenderResult();
+ }
+
+ @Override
+ public void setRenderResult(final BufferedImageRenderResult renderResult) {
+ }
+
+ @Override
+ public int getWidth() {
+ return width;
+ }
+
+ @Override
+ public int getHeight() {
+ return height;
+ }
+ }
+
+ /**
+ * Cosine shape of linear [0,1]
+ */
+ protected static double cos(final double x) {
+
+ return 0.5 - 0.5 * Math.cos(Math.PI * x);
+ }
+
+ /**
+ * Acceleration function for a t in [0,1]:
+ *
+ * types
+ * 0 symmetric
+ * 1 slow start
+ * 2 slow end
+ * 3 soft symmetric
+ * 4 soft slow start
+ * 5 soft slow end
+ * t = current frame / nb frames
+ */
+ public static double accel(final double t, final int type) {
+
+ switch (type) {
+ case 1: // slow start
+ return cos(t * t);
+ case 2: // slow end
+ return 1.0 - cos(Math.pow(1.0 - t, 2));
+ case 3: // soft symmetric
+ return cos(cos(t));
+ case 4: // soft slow start
+ return cos(cos(t * t));
+ case 5: // soft slow end
+ return 1.0 - cos(cos(Math.pow(1.0 - t, 2)));
+ default: // symmetric
+ return cos(t);
+ }
+ }
+
+ public static void recordMovie(
+ final ViewerPanel viewer,
+ final AffineTransform3D[] transforms,
+ final int[] frames,
+ final int[] accel,
+ int width,
+ int height,
+ final String dir,
+ ProgressWriter progressWriter) throws IOException {
+
+ final ViewerState renderState = viewer.state();
+ final ScaleBarOverlayRenderer scalebar = new ScaleBarOverlayRenderer();
+
+ int screenWidth = viewer.getDisplayComponent().getWidth();
+ int screenHeight = viewer.getDisplayComponent().getHeight();
+ double ratio = Math.min(width * 1.0 / screenWidth, height * 1.0 / screenHeight);
+
+ final AffineTransform3D viewerScale = new AffineTransform3D();
+
+ viewerScale.set(
+ ratio, 0, 0, 0,
+ 0, ratio, 0, 0,
+ 0, 0, 1.0, 0);
+
+ final MultiBoxOverlayRenderer box = new MultiBoxOverlayRenderer(width, height);
+
+ final Target target = new Target(width, height);
+
+ final MultiResolutionRenderer renderer = new MultiResolutionRenderer(
+ target,
+ new PainterThread(null),
+ new double[]{1.0},
+ 0l,
+ 12,
+ null,
+ false,
+ viewer.getOptionValues().getAccumulateProjectorFactory(),
+ new CacheControl.Dummy());
+
+ int i = 0;
+
+ for (int k = 1; k < transforms.length; ++k) {
+ progressWriter.setProgress((k*1.0/transforms.length));
+ final SimilarityTransformAnimator animator = new SimilarityTransformAnimator(
+ transforms[k - 1],
+ transforms[k],
+ 0,
+ 0,
+ 0);
+
+ for (int d = 0; d < frames[k]; ++d) {
+ final AffineTransform3D tkd = animator.get(accel((double) d / (double) frames[k], accel[k]));
+ tkd.preConcatenate(viewerScale);
+ viewer.state().setViewerTransform(tkd);
+ renderState.setViewerTransform(tkd);
+ renderer.requestRepaint();
+ try {
+ renderer.paint(renderState);
+ } catch (final Exception e) {
+ e.printStackTrace();
+ return;
+ }
+
+ final BufferedImage bi = target.renderResult.getBufferedImage();
+
+ final Graphics2D g2 = bi.createGraphics();
+ g2.drawImage(bi, 0, 0, null);
+
+ /* scalebar */
+ g2.setClip(0, 0, width, height);
+ scalebar.setViewerState(renderState);
+ scalebar.paint(g2);
+ box.setViewerState(renderState);
+ box.paint(g2);
+
+ /* save image */
+ ImageIO.write(bi, "png", new File(String.format("%s/img-%04d.png", dir, i++)));
+
+ System.out.println(String.format("%s/img-%04d.png", dir, i));
+ }
+ }
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/bdv/tools/movie/ProduceMovieDialog.java b/src/main/java/bdv/tools/movie/ProduceMovieDialog.java
new file mode 100644
index 00000000..ee960b9d
--- /dev/null
+++ b/src/main/java/bdv/tools/movie/ProduceMovieDialog.java
@@ -0,0 +1,351 @@
+/*
+ * #%L
+ * BigDataViewer core classes with minimal dependencies.
+ * %%
+ * Copyright (C) 2012 - 2022 BigDataViewer developers.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+package bdv.tools.movie;
+
+import bdv.export.ProgressWriter;
+import bdv.tools.movie.MovieProducer;
+import bdv.tools.movie.panels.ImagePanel;
+import bdv.tools.movie.panels.MovieFramePanel;
+import bdv.tools.movie.panels.MovieSaveDialog;
+import bdv.tools.movie.preview.MovieFrameInst;
+import bdv.tools.movie.preview.PreviewRender;
+import bdv.tools.movie.preview.PreviewThread;
+import bdv.tools.movie.serilizers.MovieFramesSerializer;
+import bdv.util.DelayedPackDialog;
+import bdv.viewer.ViewerPanel;
+import net.imglib2.realtransform.AffineTransform3D;
+
+import javax.swing.*;
+import javax.swing.border.TitledBorder;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.filechooser.FileSystemView;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ProduceMovieDialog extends DelayedPackDialog {
+
+ private final List framesPanels;
+ private final ViewerPanel viewer;
+
+ private final ProgressWriter progressWriter;
+ private final JPanel mainPanel;
+ private final JButton removeButton;
+ private final MovieSaveDialog saveDialog;
+ private final JButton exportJsonButton;
+ private final JButton exportPNGsButton;
+ private final static int FrameWidth = 920;
+ private final static int DEFAULT_SLEEP = 100;
+ private final static int DEFAULT_DOWN = 10;
+ private PreviewThread previewThread;
+ private final JTextField downsampleField;
+ private final JTextField sleepField;
+
+ public ProduceMovieDialog(final Frame owner, final ViewerPanel viewer, final ProgressWriter progressWriter) {
+ super(owner, "produce movie", false);
+ setLayout(new FlowLayout());
+ setSize(new Dimension(FrameWidth, 340));
+ JPanel playerPanel = new JPanel();
+
+ downsampleField = new JTextField(String.valueOf(DEFAULT_DOWN));
+ downsampleField.setPreferredSize(new Dimension(50, 20));
+ sleepField = new JTextField(String.valueOf(DEFAULT_SLEEP));
+ sleepField.setPreferredSize(new Dimension(50, 20));
+ playerPanel.add(new JLabel("PREVIEW: Downsampling: 1/"));
+ playerPanel.add(downsampleField);
+ playerPanel.add(new JLabel(" Sleep:"));
+ playerPanel.add(sleepField);
+ playerPanel.add(new JLabel("ms "));
+
+ JButton playButton = new JButton("▶");
+ playButton.addActionListener(e -> startPreview());
+ playerPanel.add(playButton);
+
+ JButton pauseButton = new JButton("Stop");
+ pauseButton.addActionListener(e -> pausePreview());
+
+ playerPanel.add(pauseButton);
+
+ playerPanel.setPreferredSize(new Dimension(FrameWidth, 40));
+
+ JPanel makerPanel = new JPanel(new FlowLayout());
+ makerPanel.setPreferredSize(new Dimension(FrameWidth, 280));
+
+ this.saveDialog = new MovieSaveDialog(owner, viewer, progressWriter, this);
+ this.viewer = viewer;
+ this.progressWriter = progressWriter;
+ framesPanels = new ArrayList<>();
+ final JPanel controlPanel = new JPanel(new FlowLayout());
+ controlPanel.setPreferredSize(new Dimension(50, 200));
+
+ JButton addButton = new JButton("+");
+ addButton.setPreferredSize(new Dimension(50, 30));
+ addButton.addActionListener(e -> addFrame());
+ controlPanel.add(addButton);
+
+ removeButton = new JButton("-");
+ removeButton.setPreferredSize(new Dimension(50, 30));
+ removeButton.addActionListener(e -> removeFrame());
+ controlPanel.add(removeButton);
+
+ makerPanel.add(controlPanel);
+
+ this.mainPanel = new JPanel();
+
+ JScrollPane scrollMain = new JScrollPane(mainPanel);
+ scrollMain.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ scrollMain.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ TitledBorder title = BorderFactory.createTitledBorder("Frames: ");
+ scrollMain.setBorder(title);
+ scrollMain.setPreferredSize(new Dimension(750, 240));
+ makerPanel.add(scrollMain);
+
+ final JPanel exportPanel = new JPanel(new FlowLayout());
+ exportPanel.setPreferredSize(new Dimension(100, 200));
+
+ this.exportJsonButton = new JButton("Export Json");
+ exportJsonButton.addActionListener(e -> exportJson());
+ exportPanel.add(exportJsonButton);
+
+ this.exportPNGsButton = new JButton("Generate PNGs");
+ exportPNGsButton.addActionListener(e -> showSavePNGsDialog());
+ exportPanel.add(exportPNGsButton);
+
+ JButton importButton = new JButton("Import");
+ importButton.addActionListener(e -> importSequence());
+ exportPanel.add(importButton);
+
+ makerPanel.add(exportPanel);
+
+ add(playerPanel);
+ add(makerPanel);
+
+ final ActionMap am = getRootPane().getActionMap();
+ final InputMap im = getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+ final Object hideKey = new Object();
+ final Action hideAction = new AbstractAction() {
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ setVisible(false);
+ }
+
+ private static final long serialVersionUID = 1L;
+ };
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), hideKey);
+ am.put(hideKey, hideAction);
+ validateButtons();
+ }
+
+ private void pausePreview() {
+ if (previewThread != null)
+ previewThread.suspend();
+ }
+
+ private void restartPreview() {
+ int sleep = Integer.valueOf(sleepField.getText());
+ int down = Integer.valueOf(downsampleField.getText());
+ int size = framesPanels.size();
+ final AffineTransform3D[] transforms = new AffineTransform3D[size];
+ final int[] frames = new int[size];
+ final int[] accel = new int[size];
+
+ for (int i = 0; i < size; i++) {
+ MovieFrameInst currentFrame = framesPanels.get(i).updateFields().getMovieFrame();
+ transforms[i] = currentFrame.getTransform();
+ frames[i] = currentFrame.getFrames();
+ accel[i] = currentFrame.getAccel();
+ }
+
+// previewThread = new PreviewRender(viewer,
+// transforms,
+// frames,
+// accel,
+// sleep, down);
+
+ previewThread = new PreviewThread(viewer,
+ transforms,
+ frames,
+ accel,
+ sleep, down);
+ }
+
+ private void startPreview() {
+ if (previewThread != null) {
+ previewThread.suspend();
+ }
+ restartPreview();
+ previewThread.start();
+ }
+
+
+ private void importSequence() {
+ JFileChooser jfc = new JFileChooser(FileSystemView.getFileSystemView().getHomeDirectory());
+ jfc.setDialogTitle("Select a Json");
+ jfc.setAcceptAllFileFilterUsed(false);
+ FileNameExtensionFilter filter = new FileNameExtensionFilter("JSON File", "json");
+ jfc.addChoosableFileFilter(filter);
+
+ int returnValue = jfc.showOpenDialog(null);
+ if (returnValue == JFileChooser.APPROVE_OPTION) {
+ System.out.println(jfc.getSelectedFile().getPath());
+ String path = jfc.getSelectedFile().getPath();
+ new Thread(() -> {
+ try {
+ removeAllFrames();
+ List list = MovieFramesSerializer.getFrom(new File(path));
+ for (MovieFrameInst frame : list) {
+ AffineTransform3D currentTransform = frame.getTransform().copy();
+ viewer.state().setViewerTransform(currentTransform);
+ Thread.sleep(50);
+ ImagePanel imagePanel = ImagePanel.snapshotOf(viewer);
+ addFrame(frame, imagePanel);
+ }
+
+
+ } catch (FileNotFoundException | InterruptedException e) {
+ e.printStackTrace();
+ JOptionPane.showMessageDialog(this, e.getMessage(), "ERROR", JOptionPane.ERROR_MESSAGE);
+
+ }
+ }).run();
+ }
+ }
+
+ private void removeAllFrames() {
+ while (!framesPanels.isEmpty())
+ removeFrame();
+ }
+
+ private void exportJson() {
+ JFileChooser fileChooser = new JFileChooser();
+ int returnVal = fileChooser.showSaveDialog(this);
+ if (returnVal == JFileChooser.APPROVE_OPTION) {
+ String path = fileChooser.getSelectedFile().getAbsolutePath();
+ if (!path.endsWith(".json"))
+ path = path + ".json";
+ List list = new ArrayList<>();
+ for (MovieFramePanel panels : framesPanels)
+ list.add(panels.getMovieFrame());
+ MovieFramesSerializer.save(list, new File(path));
+
+ JOptionPane.showMessageDialog(this, "File saved successfully!", "File saved", JOptionPane.PLAIN_MESSAGE);
+ }
+ }
+
+ private void showSavePNGsDialog() {
+ saveDialog.setVisible(true);
+ }
+
+ private void removeFrame() {
+ mainPanel.remove(framesPanels.remove(framesPanels.size() - 1));
+ validateButtons();
+ revalidate();
+ repaint();
+ }
+
+ private void addFrame(MovieFrameInst movieFrame, ImagePanel imagePanel) {
+ MovieFramePanel movieFramePanel = new MovieFramePanel(movieFrame, imagePanel);
+ framesPanels.add(movieFramePanel);
+ mainPanel.add(movieFramePanel);
+ validateButtons();
+ revalidate();
+ repaint();
+ }
+
+ private void addFrame() {
+ AffineTransform3D currentTransform = viewer.state().getViewerTransform();
+ MovieFramePanel movieFramePanel = new MovieFramePanel(currentTransform, ImagePanel.snapshotOf(viewer), framesPanels.size());
+ framesPanels.add(movieFramePanel);
+ mainPanel.add(movieFramePanel);
+ validateButtons();
+ revalidate();
+ repaint();
+ }
+
+ public void exportPNGs(File dir, int width, int height, ProgressWriter progressWriter) {
+ int size = framesPanels.size();
+ new Thread(() -> {
+ final AffineTransform3D[] transforms = new AffineTransform3D[size];
+ final int[] frames = new int[size];
+ final int[] accel = new int[size];
+
+ for (int i = 0; i < size; i++) {
+ MovieFrameInst currentFrame = framesPanels.get(i).updateFields().getMovieFrame();
+ transforms[i] = currentFrame.getTransform();
+ frames[i] = currentFrame.getFrames();
+ accel[i] = currentFrame.getAccel();
+ }
+
+ try {
+ MovieProducer.recordMovie(
+ viewer,
+ transforms,
+ frames,
+ accel,
+ width,
+ height,
+ dir.getAbsolutePath(),
+ progressWriter);
+ JOptionPane.showMessageDialog(mainPanel, "All Files saved successfully!", "Files saved", JOptionPane.PLAIN_MESSAGE);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }).run();
+ }
+
+ private void validateButtons() {
+ if (framesPanels.size() == 2 && !exportPNGsButton.isEnabled()) {
+ exportJsonButton.setEnabled(true);
+ exportPNGsButton.setEnabled(true);
+ exportJsonButton.revalidate();
+ exportPNGsButton.revalidate();
+ }
+ if (framesPanels.size() < 2 && exportPNGsButton.isEnabled()) {
+ exportJsonButton.setEnabled(false);
+ exportPNGsButton.setEnabled(false);
+ exportJsonButton.revalidate();
+ exportPNGsButton.revalidate();
+ }
+ if (framesPanels.size() > 0) {
+ if (!removeButton.isEnabled())
+ removeButton.setEnabled(true);
+ removeButton.revalidate();
+ removeButton.repaint();
+ } else if (removeButton.isEnabled()) {
+ removeButton.setEnabled(false);
+ removeButton.revalidate();
+ removeButton.repaint();
+ }
+ }
+}
diff --git a/src/main/java/bdv/tools/movie/panels/ImageFrame.java b/src/main/java/bdv/tools/movie/panels/ImageFrame.java
new file mode 100644
index 00000000..c65f64f8
--- /dev/null
+++ b/src/main/java/bdv/tools/movie/panels/ImageFrame.java
@@ -0,0 +1,44 @@
+package bdv.tools.movie.panels;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+public class ImageFrame extends JFrame {
+ private BufferedImage image;
+
+ public ImageFrame() throws HeadlessException {
+ super();
+ setSize(new Dimension(420, 420));
+ }
+
+ @Override
+ public void paint(Graphics g) {
+ if (image != null)
+ g.drawImage(image, 10, 10, this);
+ else
+ super.paint(g);
+ }
+
+ public static BufferedImage resize(BufferedImage img, int newW, int newH) {
+ Image tmp = img.getScaledInstance(newW, newH, Image.SCALE_SMOOTH);
+ BufferedImage dimg = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_ARGB);
+
+ Graphics2D g2d = dimg.createGraphics();
+ g2d.drawImage(tmp, 0, 0, null);
+ g2d.dispose();
+
+ return dimg;
+ }
+
+ public void setImage(BufferedImage image) {
+ this.image = resize(image, 400, 400);
+ if (!isVisible())
+ setVisible(true);
+ SwingUtilities.updateComponentTreeUI(this);
+ }
+
+ public static void main(String[] args) {
+ new ImageFrame();
+ }
+}
diff --git a/src/main/java/bdv/tools/movie/panels/ImagePanel.java b/src/main/java/bdv/tools/movie/panels/ImagePanel.java
new file mode 100644
index 00000000..3580b0ac
--- /dev/null
+++ b/src/main/java/bdv/tools/movie/panels/ImagePanel.java
@@ -0,0 +1,49 @@
+package bdv.tools.movie.panels;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+public class ImagePanel extends JPanel {
+ private final BufferedImage image;
+
+ public ImagePanel(BufferedImage image) {
+ super();
+ setPreferredSize(new Dimension(90, 90));
+ this.image = scale(image, 90, 90);
+ }
+
+ public static ImagePanel snapshotOf(JPanel panel) {
+ return new ImagePanel(takeSnapShot(panel));
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ g.drawImage(image, 0, 0, this);
+ }
+
+ public static BufferedImage scale(BufferedImage src, int w, int h) {
+ BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
+ int x, y;
+ int ww = src.getWidth();
+ int hh = src.getHeight();
+ int[] ys = new int[h];
+ for (y = 0; y < h; y++)
+ ys[y] = y * hh / h;
+ for (x = 0; x < w; x++) {
+ int newX = x * ww / w;
+ for (y = 0; y < h; y++) {
+ int col = src.getRGB(newX, ys[y]);
+ img.setRGB(x, y, col);
+ }
+ }
+ return img;
+ }
+
+ public static BufferedImage takeSnapShot(JPanel panel){
+ BufferedImage bufImage = new BufferedImage(panel.getSize().width, panel.getSize().height,BufferedImage.TYPE_INT_RGB);
+ panel.paint(bufImage.createGraphics());
+ return bufImage;
+ }
+}
diff --git a/src/main/java/bdv/tools/movie/panels/MovieFramePanel.java b/src/main/java/bdv/tools/movie/panels/MovieFramePanel.java
new file mode 100644
index 00000000..ca3441b1
--- /dev/null
+++ b/src/main/java/bdv/tools/movie/panels/MovieFramePanel.java
@@ -0,0 +1,92 @@
+package bdv.tools.movie.panels;
+
+import bdv.tools.movie.preview.MovieFrameInst;
+import net.imglib2.realtransform.AffineTransform3D;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.border.TitledBorder;
+import java.awt.*;
+
+public class MovieFramePanel extends JPanel {
+ final private MovieFrameInst movieFrame;
+ final private ImagePanel image;
+ private JTextField framesField;
+ private JComboBox accelField;
+ private final String[] ACCELS = new String[]{"symmetric", "slow start", "slow end", "soft symmetric", "soft slow start", "soft slow end"};
+
+ public MovieFramePanel(AffineTransform3D transform, ImagePanel image, int position) {
+ super();
+ this.movieFrame = new MovieFrameInst(position, transform);
+ this.image = image;
+ initView();
+ }
+
+ public MovieFramePanel(MovieFrameInst movieFrame, ImagePanel image) {
+ super();
+ this.movieFrame = movieFrame;
+ this.image = image;
+ initView();
+ }
+
+ private void initView() {
+ Border blackline = BorderFactory.createLineBorder(Color.lightGray);
+ TitledBorder title = BorderFactory.createTitledBorder(blackline, String.valueOf(movieFrame.getPosition()));
+ title.setTitleJustification(TitledBorder.CENTER);
+ setBorder(title);
+ setPreferredSize(new Dimension(140, 180));
+ if (image != null)
+ add(image);
+ JPanel fieldsPanel = new JPanel(new GridLayout(2, 1));
+ framesField = new JTextField(String.valueOf(movieFrame.getFrames()));
+ framesField.setFont(new Font("Serif", Font.PLAIN, 9));
+ accelField = new JComboBox<>(ACCELS);
+ accelField.setSelectedIndex(movieFrame.getAccel());
+ accelField.setFont(new Font("Serif", Font.PLAIN, 9));
+ JLabel framesLabel = new JLabel("Frames: ");
+ framesLabel.setFont(new Font("Serif", Font.PLAIN, 8));
+ JPanel framePanel = new JPanel(new GridLayout(1, 2));
+ framePanel.add(framesLabel);
+ framePanel.add(framesField);
+ fieldsPanel.add(framePanel);
+ fieldsPanel.add(accelField);
+ add(fieldsPanel);
+ }
+
+ public ImagePanel getImage() {
+ return image;
+ }
+
+ public MovieFramePanel updateFields() {
+ try {
+ int accel = accelField.getSelectedIndex();
+ movieFrame.setAccel(accel);
+ } catch (Exception e) {
+ e.printStackTrace();
+ System.out.println("Invalid value :" + accelField.getSelectedItem());
+ }
+ try {
+ int frames = Integer.valueOf(framesField.getText());
+ movieFrame.setFrames(frames);
+ } catch (Exception e) {
+ e.printStackTrace();
+ System.out.println("Invalid value :" + framesField.getText());
+ }
+ return this;
+ }
+
+ public int getAccel() {
+ updateFields();
+ return movieFrame.getAccel();
+ }
+
+ public int getFrames() {
+ updateFields();
+ return movieFrame.getFrames();
+ }
+
+ public MovieFrameInst getMovieFrame() {
+ updateFields();
+ return movieFrame;
+ }
+}
diff --git a/src/main/java/bdv/tools/movie/panels/MovieSaveDialog.java b/src/main/java/bdv/tools/movie/panels/MovieSaveDialog.java
new file mode 100644
index 00000000..6e2998ca
--- /dev/null
+++ b/src/main/java/bdv/tools/movie/panels/MovieSaveDialog.java
@@ -0,0 +1,134 @@
+package bdv.tools.movie.panels;
+
+import bdv.export.ProgressWriter;
+import bdv.tools.movie.ProduceMovieDialog;
+import bdv.util.DelayedPackDialog;
+import bdv.viewer.OverlayRenderer;
+import bdv.viewer.ViewerPanel;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.io.File;
+
+public class MovieSaveDialog extends DelayedPackDialog implements OverlayRenderer {
+ private static final long serialVersionUID = 1L;
+
+ private final ViewerPanel viewer;
+
+ private final ProgressWriter progressWriter;
+
+ private final JTextField pathTextField;
+
+ private final JSpinner spinnerWidth;
+
+ private final JSpinner spinnerHeight;
+
+ public MovieSaveDialog(final Frame owner, final ViewerPanel viewer, final ProgressWriter progressWriter, ProduceMovieDialog produceMovieDialog) {
+ super(owner, "record movie", false);
+ this.viewer = viewer;
+ this.progressWriter = progressWriter;
+
+ final JPanel boxes = new JPanel();
+ getContentPane().add(boxes, BorderLayout.NORTH);
+ boxes.setLayout(new BoxLayout(boxes, BoxLayout.PAGE_AXIS));
+
+ final JPanel saveAsPanel = new JPanel();
+ saveAsPanel.setLayout(new BorderLayout(0, 0));
+ boxes.add(saveAsPanel);
+
+ saveAsPanel.add(new JLabel("save to"), BorderLayout.WEST);
+
+ pathTextField = new JTextField("./record/");
+ saveAsPanel.add(pathTextField, BorderLayout.CENTER);
+ pathTextField.setColumns(20);
+
+ final JButton browseButton = new JButton("Browse");
+ saveAsPanel.add(browseButton, BorderLayout.EAST);
+
+ final JPanel typePanel = new JPanel();
+ boxes.add(typePanel);
+
+ typePanel.add(new JLabel("Type:"));
+
+ final JPanel widthPanel = new JPanel();
+ boxes.add(widthPanel);
+ widthPanel.add(new JLabel("width"));
+ spinnerWidth = new JSpinner();
+ spinnerWidth.setModel(new SpinnerNumberModel(800, 10, 5000, 1));
+ widthPanel.add(spinnerWidth);
+
+ final JPanel heightPanel = new JPanel();
+ boxes.add(heightPanel);
+ heightPanel.add(new JLabel("height"));
+ spinnerHeight = new JSpinner();
+ spinnerHeight.setModel(new SpinnerNumberModel(600, 10, 5000, 1));
+ heightPanel.add(spinnerHeight);
+
+ final JPanel buttonsPanel = new JPanel();
+ boxes.add(buttonsPanel);
+ buttonsPanel.setLayout(new BorderLayout(0, 0));
+
+ final JButton recordButton = new JButton("Record");
+ buttonsPanel.add(recordButton, BorderLayout.EAST);
+
+ final JFileChooser fileChooser = new JFileChooser();
+ fileChooser.setMultiSelectionEnabled(false);
+ fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+
+ browseButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ fileChooser.setSelectedFile(new File(pathTextField.getText()));
+ final int returnVal = fileChooser.showSaveDialog(null);
+ if (returnVal == JFileChooser.APPROVE_OPTION) {
+ final File file = fileChooser.getSelectedFile();
+ pathTextField.setText(file.getAbsolutePath());
+ }
+ }
+ });
+
+ recordButton.addActionListener(e -> {
+ final String dirname = pathTextField.getText();
+ final File dir = new File(dirname);
+ if (!dir.exists())
+ dir.mkdirs();
+ if (!dir.exists() || !dir.isDirectory()) {
+ System.err.println("Invalid export directory " + dirname);
+ return;
+ }
+ setVisible(false);
+ final int width = (Integer) spinnerWidth.getValue();
+ final int height = (Integer) spinnerHeight.getValue();
+ produceMovieDialog.exportPNGs(dir,width,height,progressWriter);
+ });
+
+ final ActionMap am = getRootPane().getActionMap();
+ final InputMap im = getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+ final Object hideKey = new Object();
+ final Action hideAction = new AbstractAction() {
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ setVisible(false);
+ }
+
+ private static final long serialVersionUID = 1L;
+ };
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), hideKey);
+ am.put(hideKey, hideAction);
+
+ pack();
+ }
+
+ @Override
+ public void drawOverlays(final Graphics g) {
+ }
+
+ @Override
+ public void setCanvasSize(final int width, final int height) {
+ spinnerWidth.setValue(width);
+ spinnerHeight.setValue(height);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/bdv/tools/movie/preview/MovieFrameInst.java b/src/main/java/bdv/tools/movie/preview/MovieFrameInst.java
new file mode 100644
index 00000000..f7b2f071
--- /dev/null
+++ b/src/main/java/bdv/tools/movie/preview/MovieFrameInst.java
@@ -0,0 +1,73 @@
+package bdv.tools.movie.preview;
+
+import net.imglib2.realtransform.AffineTransform3D;
+
+import java.io.Serializable;
+
+public class MovieFrameInst implements Serializable {
+ private volatile transient AffineTransform3D transform;
+ private double[] transformParams;
+ private int position;
+ private int frames;
+ private int accel;
+ private final static int DEFAULT_FRAMES = 120;
+ private final static int DEFAULT_ACCEL = 0;
+
+
+ public MovieFrameInst(int position, AffineTransform3D transform) {
+ this(position, transform, ((position == 0) ? 0 : DEFAULT_FRAMES), ((position == 0) ? 0 : DEFAULT_ACCEL));
+ }
+
+ public MovieFrameInst(int position, AffineTransform3D transform, int frames, int accel) {
+ this.position = position;
+ this.transform = transform;
+ this.transformParams = transform.getRowPackedCopy();
+ this.frames = frames;
+ this.accel = accel;
+ }
+
+ public AffineTransform3D getTransform() {
+ if (transform == null)
+ {
+ transform = new AffineTransform3D();
+ transform.set(transformParams);
+ }
+ return transform;
+ }
+
+ public void setTransform(AffineTransform3D transform) {
+ this.transform = transform;
+ }
+
+ public int getPosition() {
+ return position;
+ }
+
+ public void setPosition(int position) {
+ this.position = position;
+ }
+
+ public int getFrames() {
+ return frames;
+ }
+
+ public void setFrames(int frames) {
+ this.frames = frames;
+ }
+
+ public int getAccel() {
+ return accel;
+ }
+
+ public void setAccel(int accel) {
+ this.accel = accel;
+ }
+
+ public static int getDefaultFrames() {
+ return DEFAULT_FRAMES;
+ }
+
+ public static int getDefaultAccel() {
+ return DEFAULT_ACCEL;
+ }
+}
diff --git a/src/main/java/bdv/tools/movie/preview/PreviewRender.java b/src/main/java/bdv/tools/movie/preview/PreviewRender.java
new file mode 100644
index 00000000..e0ae21cd
--- /dev/null
+++ b/src/main/java/bdv/tools/movie/preview/PreviewRender.java
@@ -0,0 +1,169 @@
+package bdv.tools.movie.preview;
+
+
+import bdv.cache.CacheControl;
+import bdv.tools.movie.MovieProducer;
+import bdv.tools.movie.panels.ImageFrame;
+import bdv.viewer.ViewerPanel;
+import bdv.viewer.ViewerState;
+import bdv.viewer.animate.SimilarityTransformAnimator;
+import bdv.viewer.overlay.MultiBoxOverlayRenderer;
+import bdv.viewer.overlay.ScaleBarOverlayRenderer;
+import bdv.viewer.render.MultiResolutionRenderer;
+import bdv.viewer.render.PainterThread;
+import net.imglib2.realtransform.AffineTransform3D;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+public class PreviewRender implements Runnable {
+
+ private final ViewerPanel viewer;
+ private final AffineTransform3D[] transforms;
+ private final int[] frames;
+ private final int[] accel;
+ private final int sleep;
+ private final int down;
+ public Thread t;
+
+ boolean suspended = false;
+
+ public PreviewRender(
+ final ViewerPanel viewer,
+ final AffineTransform3D[] transforms,
+ final int[] frames,
+ final int[] accel,
+ final int sleep,
+ final int down) {
+ System.out.println("got "+transforms.length+" transforms");
+ this.viewer = viewer;
+ this.transforms = transforms;
+ this.frames = frames;
+ this.accel = accel;
+ this.sleep = sleep;
+ this.down = down;
+ }
+
+ public void run() {
+ ImageFrame preview = new ImageFrame();
+
+ int width = 600;
+ int height = 600;
+
+ int screenWidth = viewer.getDisplayComponent().getWidth();
+ int screenHeight = viewer.getDisplayComponent().getHeight();
+
+ double ratio = Math.min(width * 1.0 / screenWidth, height * 1.0 / screenHeight);
+ final AffineTransform3D viewerScale = new AffineTransform3D();
+ final AffineTransform3D viewerTranslation = new AffineTransform3D();
+
+ viewerScale.set(
+ ratio, 0, 0, 0,
+ 0, ratio, 0, 0,
+ 0, 0, 1.0, 0);
+// viewerTranslation.set(
+// 1, 0, 0, 0.5 * screenWidth,
+// 0, 1, 0, 0.5 * screenHeight,
+// 0, 0, 1, 0);
+
+
+ final ViewerState renderState = viewer.state();
+ final ScaleBarOverlayRenderer scalebar = new ScaleBarOverlayRenderer();
+ final MultiBoxOverlayRenderer box = new MultiBoxOverlayRenderer(width, height);
+
+ final MovieProducer.Target target = new MovieProducer.Target(width, height);
+
+ final MultiResolutionRenderer renderer = new MultiResolutionRenderer(
+ target,
+ new PainterThread(null),
+ new double[]{1.0},
+ 0l,
+ 12,
+ null,
+ false,
+ viewer.getOptionValues().getAccumulateProjectorFactory(),
+ new CacheControl.Dummy());
+
+
+ for (int k = 1; k < transforms.length; ++k) {
+ final SimilarityTransformAnimator animator = new SimilarityTransformAnimator(
+ transforms[k - 1],
+ transforms[k],
+ 0,
+ 0,
+ 0);
+ int downsampledFrames = frames[k]/down;
+
+ for (int d = 0; d < downsampledFrames; ++d) {
+ final AffineTransform3D tkd = animator.get(MovieProducer.accel((double) d / (double) downsampledFrames, accel[k]));
+ tkd.preConcatenate(viewerTranslation.inverse());
+ tkd.preConcatenate(viewerScale);
+ tkd.preConcatenate(viewerTranslation);
+ viewer.state().setViewerTransform(tkd);
+ renderState.setViewerTransform(tkd);
+ renderer.requestRepaint();
+ try {
+ renderer.paint(renderState);
+ } catch (final Exception e) {
+ e.printStackTrace();
+ return;
+ }
+
+ /* clahe */
+ final BufferedImage bi = target.renderResult.getBufferedImage();
+
+ final Graphics2D g2 = bi.createGraphics();
+
+ g2.drawImage(bi, 0, 0, null);
+
+ /* scalebar */
+ g2.setClip(0, 0, width, height);
+ scalebar.setViewerState(renderState);
+ scalebar.paint(g2);
+ box.setViewerState(renderState);
+ box.paint(g2);
+
+ preview.setImage(bi);
+ try {
+ Thread.sleep(sleep);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+ }
+ }
+
+ public boolean getStatus() {
+ return t == null;
+ }
+
+ public boolean isDone() {
+ return (t.getState() == Thread.State.TERMINATED);
+ }
+
+ public void start() {
+ if (getStatus()) {
+ t = new Thread(this);
+ t.start();
+ }
+ }
+
+ public void suspend() {
+ t.stop();
+ suspended = true;
+ }
+
+ public boolean isSuspended() {
+ return suspended;
+ }
+
+ synchronized void resume() {
+ suspended = false;
+ t.resume();
+ this.notify();
+ }
+
+
+}
+
diff --git a/src/main/java/bdv/tools/movie/preview/PreviewThread.java b/src/main/java/bdv/tools/movie/preview/PreviewThread.java
new file mode 100644
index 00000000..2778175b
--- /dev/null
+++ b/src/main/java/bdv/tools/movie/preview/PreviewThread.java
@@ -0,0 +1,101 @@
+package bdv.tools.movie.preview;
+
+import bdv.tools.movie.MovieProducer;
+import bdv.viewer.Interpolation;
+import bdv.viewer.ViewerPanel;
+import bdv.viewer.animate.SimilarityTransformAnimator;
+import net.imglib2.realtransform.AffineTransform3D;
+
+import javax.swing.*;
+
+public class PreviewThread implements Runnable {
+
+ private final ViewerPanel viewer;
+ private final AffineTransform3D[] transforms;
+ private final int[] frames;
+ private final int[] accel;
+ private final int sleep;
+ private final int down;
+ public Thread t;
+
+ boolean suspended = false;
+
+ public PreviewThread(
+ final ViewerPanel viewer,
+ final AffineTransform3D[] transforms,
+ final int[] frames,
+ final int[] accel,
+ final int sleep,
+ final int down) {
+ this.viewer = viewer;
+ this.transforms = transforms;
+ this.frames = frames;
+ this.accel = accel;
+ this.sleep = sleep;
+ this.down = down;
+ }
+
+ public void run() {
+ try {
+ viewer.setInterpolation(Interpolation.NLINEAR);
+ for (int k = 1; k < transforms.length; ++k) {
+ synchronized (this) {
+ if (suspended) {
+ wait();
+ }
+ }
+ final SimilarityTransformAnimator animator = new SimilarityTransformAnimator(
+ transforms[k - 1],
+ transforms[k],
+ 0,
+ 0,
+ 0);
+ int downFrames = frames[k] / down;
+ for (int d = 0; d < downFrames; ++d) {
+ final AffineTransform3D tkd = animator.get(MovieProducer.accel((double) d / (double) downFrames, accel[k]));
+// System.out.println(tkd.toString());
+ SwingUtilities.invokeLater(() -> {
+ viewer.state().setViewerTransform(tkd);
+ viewer.repaint();
+ });
+ Thread.sleep(sleep);
+ }
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ this.start();
+ }
+
+ public boolean getStatus() {
+ return t == null;
+ }
+
+ public boolean isDone(){
+ return (t.getState()==Thread.State.TERMINATED);
+ }
+
+ public void start() {
+ if (getStatus()) {
+ t = new Thread(this);
+ t.start();
+ }
+ }
+
+ public void suspend() {
+ t.stop();
+ suspended = true;
+ }
+
+ public boolean isSuspended() {
+ return suspended;
+ }
+
+ synchronized void resume() {
+ suspended = false;
+ t.resume();
+ this.notify();
+ }
+
+}
+
diff --git a/src/main/java/bdv/tools/movie/serilizers/AffineTransform3DJsonSerializer.java b/src/main/java/bdv/tools/movie/serilizers/AffineTransform3DJsonSerializer.java
new file mode 100644
index 00000000..c57b7ad3
--- /dev/null
+++ b/src/main/java/bdv/tools/movie/serilizers/AffineTransform3DJsonSerializer.java
@@ -0,0 +1,26 @@
+package bdv.tools.movie.serilizers;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSerializationContext;
+import net.imglib2.realtransform.AffineTransform3D;
+
+import java.lang.reflect.Type;
+
+public class AffineTransform3DJsonSerializer implements JsonSerializerDeserializer {
+
+ public AffineTransform3D deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+ double[] data = context.deserialize(json, double[].class);
+ AffineTransform3D t = new AffineTransform3D();
+ t.set(data);
+ return t;
+ }
+
+ public JsonElement serialize(AffineTransform3D src, Type typeOfSrc, JsonSerializationContext context) {
+ double[] data = new double[12];
+ src.toArray(data);
+ return context.serialize(data);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/bdv/tools/movie/serilizers/JsonSerializerDeserializer.java b/src/main/java/bdv/tools/movie/serilizers/JsonSerializerDeserializer.java
new file mode 100644
index 00000000..aaa78465
--- /dev/null
+++ b/src/main/java/bdv/tools/movie/serilizers/JsonSerializerDeserializer.java
@@ -0,0 +1,7 @@
+package bdv.tools.movie.serilizers;
+
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonSerializer;
+
+public interface JsonSerializerDeserializer extends JsonSerializer, JsonDeserializer {
+}
\ No newline at end of file
diff --git a/src/main/java/bdv/tools/movie/serilizers/MovieFramesSerializer.java b/src/main/java/bdv/tools/movie/serilizers/MovieFramesSerializer.java
new file mode 100644
index 00000000..510d6327
--- /dev/null
+++ b/src/main/java/bdv/tools/movie/serilizers/MovieFramesSerializer.java
@@ -0,0 +1,40 @@
+package bdv.tools.movie.serilizers;
+
+import bdv.tools.movie.preview.MovieFrameInst;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import net.imglib2.realtransform.AffineTransform3D;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MovieFramesSerializer {
+
+ private static Gson getGson() {
+ GsonBuilder gsonBuilder = new GsonBuilder();
+ gsonBuilder.serializeNulls().serializeSpecialFloatingPointValues();
+ gsonBuilder.registerTypeAdapter(AffineTransform3D.class, new AffineTransform3DJsonSerializer());
+ return gsonBuilder.create();
+ }
+
+ public static boolean save(List list, File file){
+ try (Writer writer = new FileWriter(file)) {
+ getGson().toJson(list, writer);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ return true;
+ }
+
+ public static ArrayList getFrom(File file) throws FileNotFoundException {
+ return getGson().fromJson(new FileReader(file.getAbsolutePath()),new TypeToken>(){}.getType());
+ }
+}