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;
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.ObservableValue;
import de.neemann.digital.core.wiring.Clock;
import de.neemann.digital.draw.elements.Circuit;
import de.neemann.digital.draw.graphics.GraphicMinMax;
import de.neemann.digital.draw.graphics.GraphicsImage;
import de.neemann.digital.draw.graphics.linemerger.GraphicLineCollector;
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.ImageOutputStream;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
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.io.File;
import java.io.IOException;
@ -21,52 +32,109 @@ import java.io.IOException;
* Exporter which creates an animated GIF file.
* Created by hneemann on 17.05.17.
*/
public class GifExporter {
private final Model model;
public class GifExporter extends JDialog implements ModelStateObserver, ModelModifier {
private static final Logger LOGGER = LoggerFactory.getLogger(GifExporter.class);
private final Circuit circuit;
private final int frames;
private final int delayMs;
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
*
* @param model the mode to use
* @param parent the parent frame
* @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
*/
public GifExporter(Model model, Circuit circuit, int frames, int delayMs) {
this.model = model;
public GifExporter(JFrame parent, Circuit circuit, int delayMs) {
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.frames = frames;
this.delayMs = delayMs;
minMax = new GraphicMinMax();
circuit.drawTo(minMax);
pack();
setLocation(parent.getLocation());
}
/**
* Exports the file
*
* @param file the file to write
* @return this for chained calls
* @throws IOException IOException
* @throws NodeException NodeException
*/
public void export(File file) throws IOException, NodeException {
try (ImageOutputStream output = new FileImageOutputStream(file)) {
try (GifSequenceWriter writer = new GifSequenceWriter(output, BufferedImage.TYPE_INT_ARGB, delayMs, true)) {
for (int i = 0; i < frames; i++) {
public GifExporter export(File file) throws IOException, NodeException {
output = new FileImageOutputStream(file);
writer = new GifSequenceWriter(output, BufferedImage.TYPE_INT_ARGB, delayMs, true);
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());
Clock clock = model.getClocks().get(0);
ObservableValue o = clock.getClockOutput();
o.setBool(!o.getBool());
model.doStep();
} catch (IOException e) {
SwingUtilities.invokeLater(new ErrorMessage(Lang.get("msg_errorWritingGif")).addCause(e));
}
frames++;
frameLabel.setText(Lang.get("msg_framesWritten_N", frames));
LOGGER.debug("frame written to GIF file");
}
}
}
}
private BufferedImage createBufferedImage() throws IOException {
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
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();
if (filename != null)
fc.setSelectedFile(SaveAsHelper.checkSuffix(filename, "gif"));
@ -1180,7 +1163,11 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS
file -> {
lastExportDirectory = file.getParentFile();
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) {
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 {

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_numberingWizard">Nummerierungshilfe</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_numberOfFrames">Anzahl der Einzelbilder:</string>
<string name="msg_framesWritten_N">Geschriebene Bilder: {0}</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="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_pin_N">Pin {0}</string>
<string name="msg_numberingWizard">Numbering Wizard</string>
<string name="msg_modelNeedsToBeStarted">The circuit needs to be started.</string>
<string name="msg_numberOfFrames">Number of frames:</string>
<string name="msg_framesWritten_N">Written frames: {0}</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="rot_0"></string>