mirror of
https://github.com/hneemann/Digital.git
synced 2025-09-26 22:41:59 -04:00
improved gif exporter
This commit is contained in:
parent
eb811886a2
commit
e1a93cfa42
@ -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++) {
|
||||
writer.writeToSequence(createBufferedImage());
|
||||
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;
|
||||
}
|
||||
|
||||
Clock clock = model.getClocks().get(0);
|
||||
ObservableValue o = clock.getClockOutput();
|
||||
o.setBool(!o.getBool());
|
||||
model.doStep();
|
||||
}
|
||||
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());
|
||||
} 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);
|
||||
|
@ -1151,44 +1151,28 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JFileChooser fc = new MyFileChooser();
|
||||
if (filename != null)
|
||||
fc.setSelectedFile(SaveAsHelper.checkSuffix(filename, "gif"));
|
||||
|
||||
if (model == null)
|
||||
new ErrorMessage(Lang.get("msg_modelNeedsToBeStarted")).show(Main.this);
|
||||
else {
|
||||
if (lastExportDirectory != null)
|
||||
fc.setCurrentDirectory(lastExportDirectory);
|
||||
|
||||
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);
|
||||
fc.addChoosableFileFilter(new FileNameExtensionFilter(name, "gif"));
|
||||
new SaveAsHelper(Main.this, fc, "gif").checkOverwrite(
|
||||
file -> {
|
||||
lastExportDirectory = file.getParentFile();
|
||||
try {
|
||||
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);
|
||||
}
|
||||
}
|
||||
final int frames = f;
|
||||
if (frames > 0) {
|
||||
|
||||
JFileChooser fc = new MyFileChooser();
|
||||
if (filename != null)
|
||||
fc.setSelectedFile(SaveAsHelper.checkSuffix(filename, "gif"));
|
||||
|
||||
if (lastExportDirectory != null)
|
||||
fc.setCurrentDirectory(lastExportDirectory);
|
||||
|
||||
fc.addChoosableFileFilter(new FileNameExtensionFilter(name, "gif"));
|
||||
new SaveAsHelper(Main.this, fc, "gif").checkOverwrite(
|
||||
file -> {
|
||||
lastExportDirectory = file.getParentFile();
|
||||
try {
|
||||
new GifExporter(model, circuitComponent.getCircuit(), frames, 500).export(file);
|
||||
} catch (NodeException e1) {
|
||||
new ErrorMessage().addCause(e1).show(Main.this);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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">0°</string>
|
||||
|
@ -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">0°</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user