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;
|
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");
|
||||||
writer.writeToSequence(createBufferedImage());
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
Clock clock = model.getClocks().get(0);
|
private void close() {
|
||||||
ObservableValue o = clock.getClockOutput();
|
if (!closed) {
|
||||||
o.setBool(!o.getBool());
|
try {
|
||||||
model.doStep();
|
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 {
|
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);
|
||||||
|
@ -1151,44 +1151,28 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
JFileChooser fc = new MyFileChooser();
|
||||||
|
if (filename != null)
|
||||||
|
fc.setSelectedFile(SaveAsHelper.checkSuffix(filename, "gif"));
|
||||||
|
|
||||||
if (model == null)
|
if (lastExportDirectory != null)
|
||||||
new ErrorMessage(Lang.get("msg_modelNeedsToBeStarted")).show(Main.this);
|
fc.setCurrentDirectory(lastExportDirectory);
|
||||||
else {
|
|
||||||
|
|
||||||
String numStr = JOptionPane.showInputDialog(Lang.get("msg_numberOfFrames"));
|
fc.addChoosableFileFilter(new FileNameExtensionFilter(name, "gif"));
|
||||||
if (numStr != null) {
|
new SaveAsHelper(Main.this, fc, "gif").checkOverwrite(
|
||||||
|
file -> {
|
||||||
int f = 0;
|
lastExportDirectory = file.getParentFile();
|
||||||
try {
|
try {
|
||||||
f = Integer.parseInt(numStr);
|
GifExporter ge = new GifExporter(Main.this, circuitComponent.getCircuit(), 500).export(file);
|
||||||
} catch (NumberFormatException e1) {
|
setDebug(false);
|
||||||
new ErrorMessage().addCause(e1).show(Main.this);
|
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_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">0°</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_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">0°</string>
|
<string name="rot_0">0°</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user