improved gif exporter

This commit is contained in:
hneemann 2017-05-21 10:54:46 +02:00
parent eb811886a2
commit e1a93cfa42
4 changed files with 116 additions and 60 deletions

View File

@ -1,18 +1,29 @@
package de.neemann.digital.draw.gif; package de.neemann.digital.draw.gif;
import de.neemann.digital.core.Model; import de.neemann.digital.core.Model;
import de.neemann.digital.core.ModelEvent;
import de.neemann.digital.core.ModelStateObserver;
import de.neemann.digital.core.NodeException; import de.neemann.digital.core.NodeException;
import de.neemann.digital.core.ObservableValue;
import de.neemann.digital.core.wiring.Clock;
import de.neemann.digital.draw.elements.Circuit; import de.neemann.digital.draw.elements.Circuit;
import de.neemann.digital.draw.graphics.GraphicMinMax; import de.neemann.digital.draw.graphics.GraphicMinMax;
import de.neemann.digital.draw.graphics.GraphicsImage; import de.neemann.digital.draw.graphics.GraphicsImage;
import de.neemann.digital.draw.graphics.linemerger.GraphicLineCollector; import de.neemann.digital.draw.graphics.linemerger.GraphicLineCollector;
import de.neemann.digital.draw.graphics.linemerger.GraphicSkipLines; import de.neemann.digital.draw.graphics.linemerger.GraphicSkipLines;
import de.neemann.digital.gui.ModelModifier;
import de.neemann.digital.lang.Lang;
import de.neemann.gui.ErrorMessage;
import de.neemann.gui.Screen;
import de.neemann.gui.ToolTipAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.stream.FileImageOutputStream; import javax.imageio.stream.FileImageOutputStream;
import javax.imageio.stream.ImageOutputStream; import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -21,52 +32,109 @@ import java.io.IOException;
* Exporter which creates an animated GIF file. * Exporter which creates an animated GIF file.
* Created by hneemann on 17.05.17. * Created by hneemann on 17.05.17.
*/ */
public class GifExporter { public class GifExporter extends JDialog implements ModelStateObserver, ModelModifier {
private final Model model; private static final Logger LOGGER = LoggerFactory.getLogger(GifExporter.class);
private final Circuit circuit; private final Circuit circuit;
private final int frames;
private final int delayMs; private final int delayMs;
private final GraphicMinMax minMax; private final GraphicMinMax minMax;
private final JLabel frameLabel;
private int frames;
private FileImageOutputStream output;
private GifSequenceWriter writer;
private boolean closed = false;
/** /**
* Creates a new instance * Creates a new instance
* *
* @param model the mode to use * @param parent the parent frame
* @param circuit the circuit to export * @param circuit the circuit to export
* @param frames then number of frames to write to the file
* @param delayMs the delay between frames im milliseconds * @param delayMs the delay between frames im milliseconds
*/ */
public GifExporter(Model model, Circuit circuit, int frames, int delayMs) { public GifExporter(JFrame parent, Circuit circuit, int delayMs) {
this.model = model; super(parent, "GIF-Export", false);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
frameLabel = new JLabel(Lang.get("msg_framesWritten_N", frames));
frameLabel.setFont(Screen.getInstance().getFont(1.5f));
frameLabel.setBorder(new EmptyBorder(5, 5, 5, 5));
getContentPane().add(frameLabel);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent windowEvent) {
close();
}
});
getContentPane().add(new ToolTipAction(Lang.get("btn_gifComplete")) {
@Override
public void actionPerformed(ActionEvent actionEvent) {
close();
}
}.setToolTip(Lang.get("btn_gifComplete_tt")).createJButton(), BorderLayout.SOUTH);
this.circuit = circuit; this.circuit = circuit;
this.frames = frames;
this.delayMs = delayMs; this.delayMs = delayMs;
minMax = new GraphicMinMax(); minMax = new GraphicMinMax();
circuit.drawTo(minMax); circuit.drawTo(minMax);
pack();
setLocation(parent.getLocation());
} }
/** /**
* Exports the file * Exports the file
* *
* @param file the file to write * @param file the file to write
* @return this for chained calls
* @throws IOException IOException * @throws IOException IOException
* @throws NodeException NodeException * @throws NodeException NodeException
*/ */
public void export(File file) throws IOException, NodeException { public GifExporter export(File file) throws IOException, NodeException {
try (ImageOutputStream output = new FileImageOutputStream(file)) { output = new FileImageOutputStream(file);
try (GifSequenceWriter writer = new GifSequenceWriter(output, BufferedImage.TYPE_INT_ARGB, delayMs, true)) { writer = new GifSequenceWriter(output, BufferedImage.TYPE_INT_ARGB, delayMs, true);
for (int i = 0; i < frames; i++) { LOGGER.debug("open GIF file");
return this;
}
private void close() {
if (!closed) {
try {
writer.close();
output.close();
LOGGER.debug("closed GIF file");
closed = true;
} catch (IOException e) {
SwingUtilities.invokeLater(new ErrorMessage(Lang.get("msg_errorWritingGif")).addCause(e));
}
}
dispose();
}
@Override
public void preInit(Model model) throws NodeException {
SwingUtilities.invokeLater(() -> setVisible(true));
model.addObserver(this);
}
@Override
public void handleEvent(ModelEvent event) {
if (event.equals(ModelEvent.STEP)) {
writeImage();
}
}
private void writeImage() {
if (!closed) {
try {
writer.writeToSequence(createBufferedImage()); writer.writeToSequence(createBufferedImage());
} catch (IOException e) {
Clock clock = model.getClocks().get(0); SwingUtilities.invokeLater(new ErrorMessage(Lang.get("msg_errorWritingGif")).addCause(e));
ObservableValue o = clock.getClockOutput(); }
o.setBool(!o.getBool()); frames++;
model.doStep(); frameLabel.setText(Lang.get("msg_framesWritten_N", frames));
LOGGER.debug("frame written to GIF file");
} }
} }
}
}
private BufferedImage createBufferedImage() throws IOException { private BufferedImage createBufferedImage() throws IOException {
GraphicsImage gri = GraphicsImage.create(null, minMax.getMin(), minMax.getMax(), "gif", 1); GraphicsImage gri = GraphicsImage.create(null, minMax.getMin(), minMax.getMax(), "gif", 1);

View File

@ -1151,23 +1151,6 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
if (model == null)
new ErrorMessage(Lang.get("msg_modelNeedsToBeStarted")).show(Main.this);
else {
String numStr = JOptionPane.showInputDialog(Lang.get("msg_numberOfFrames"));
if (numStr != null) {
int f = 0;
try {
f = Integer.parseInt(numStr);
} catch (NumberFormatException e1) {
new ErrorMessage().addCause(e1).show(Main.this);
}
final int frames = f;
if (frames > 0) {
JFileChooser fc = new MyFileChooser(); JFileChooser fc = new MyFileChooser();
if (filename != null) if (filename != null)
fc.setSelectedFile(SaveAsHelper.checkSuffix(filename, "gif")); fc.setSelectedFile(SaveAsHelper.checkSuffix(filename, "gif"));
@ -1180,7 +1163,11 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS
file -> { file -> {
lastExportDirectory = file.getParentFile(); lastExportDirectory = file.getParentFile();
try { try {
new GifExporter(model, circuitComponent.getCircuit(), frames, 500).export(file); GifExporter ge = new GifExporter(Main.this, circuitComponent.getCircuit(), 500).export(file);
setDebug(false);
windowPosManager.closeAll();
runModelState.enter(false, ge);
circuitComponent.hasChanged();
} catch (NodeException e1) { } catch (NodeException e1) {
new ErrorMessage().addCause(e1).show(Main.this); new ErrorMessage().addCause(e1).show(Main.this);
} }
@ -1188,9 +1175,6 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS
); );
} }
} }
}
}
}
private class RunModelState extends State { private class RunModelState extends State {

View File

@ -858,8 +858,10 @@ Die Icons stammen aus dem Tango Desktop Project.</string>
<string name="msg_pin_N">Pin {0}</string> <string name="msg_pin_N">Pin {0}</string>
<string name="msg_numberingWizard">Nummerierungshilfe</string> <string name="msg_numberingWizard">Nummerierungshilfe</string>
<string name="msg_pin_numbering_N">Wählen Sie Pin {0}:</string> <string name="msg_pin_numbering_N">Wählen Sie Pin {0}:</string>
<string name="msg_modelNeedsToBeStarted">Die Schaltung muss gestartet sein.</string> <string name="msg_framesWritten_N">Geschriebene Bilder: {0}</string>
<string name="msg_numberOfFrames">Anzahl der Einzelbilder:</string> <string name="msg_errorWritingGif">Fehler beim Schreiben der GIF Datei!</string>
<string name="btn_gifComplete">Fertig</string>
<string name="btn_gifComplete_tt">Die GIF-Datei wird abgeschlossen.</string>
<string name="ok">Ok</string> <string name="ok">Ok</string>
<string name="rot_0"></string> <string name="rot_0"></string>

View File

@ -847,8 +847,10 @@ The icons are taken from the Tango Desktop Project.</string>
<string name="msg_speedTestError">Error during speed test!</string> <string name="msg_speedTestError">Error during speed test!</string>
<string name="msg_pin_N">Pin {0}</string> <string name="msg_pin_N">Pin {0}</string>
<string name="msg_numberingWizard">Numbering Wizard</string> <string name="msg_numberingWizard">Numbering Wizard</string>
<string name="msg_modelNeedsToBeStarted">The circuit needs to be started.</string> <string name="msg_framesWritten_N">Written frames: {0}</string>
<string name="msg_numberOfFrames">Number of frames:</string> <string name="msg_errorWritingGif">Error writing to GIF file!</string>
<string name="btn_gifComplete">Ready</string>
<string name="btn_gifComplete_tt">The GIF file is finalized and closed.</string>
<string name="ok">Ok</string> <string name="ok">Ok</string>
<string name="rot_0"></string> <string name="rot_0"></string>