X-Git-Url: https://gitweb.fperrin.net/?p=GpsPrune.git;a=blobdiff_plain;f=src%2Ftim%2Fprune%2Fsave%2FImageExporter.java;fp=src%2Ftim%2Fprune%2Fsave%2FImageExporter.java;h=ea188921d8b75b4612de63cd1c9335771216d2ed;hp=0000000000000000000000000000000000000000;hb=ce6f2161b8596f7018d6a76bff79bc9e571f35fd;hpb=2d8cb72e84d5cc1089ce77baf1e34ea3ea2f8465 diff --git a/src/tim/prune/save/ImageExporter.java b/src/tim/prune/save/ImageExporter.java new file mode 100644 index 0000000..ea18892 --- /dev/null +++ b/src/tim/prune/save/ImageExporter.java @@ -0,0 +1,450 @@ +package tim.prune.save; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; + +import tim.prune.App; +import tim.prune.GenericFunction; +import tim.prune.I18nManager; +import tim.prune.config.ColourScheme; +import tim.prune.config.Config; +import tim.prune.data.DataPoint; +import tim.prune.data.DoubleRange; +import tim.prune.data.Track; +import tim.prune.gui.BaseImageDefinitionPanel; +import tim.prune.gui.GuiGridLayout; +import tim.prune.gui.WholeNumberField; +import tim.prune.gui.colour.PointColourer; +import tim.prune.gui.map.MapSource; +import tim.prune.gui.map.MapSourceLibrary; +import tim.prune.gui.map.MapUtils; +import tim.prune.gui.map.WpIconDefinition; +import tim.prune.gui.map.WpIconLibrary; +import tim.prune.load.GenericFileFilter; +import tim.prune.threedee.ImageDefinition; + +/** + * Class to handle the exporting of map images, optionally with track data drawn on top. + * This allows images larger than the screen to be generated. + */ +public class ImageExporter extends GenericFunction implements BaseImageConsumer +{ + private JDialog _dialog = null; + private JCheckBox _drawDataCheckbox = null; + private JCheckBox _drawTrackPointsCheckbox = null; + private WholeNumberField _textScaleField = null; + private BaseImageDefinitionPanel _baseImagePanel = null; + private JFileChooser _fileChooser = null; + private JButton _okButton = null; + + /** + * Constructor + * @param inApp App object + */ + public ImageExporter(App inApp) + { + super(inApp); + } + + /** Get the name key */ + public String getNameKey() { + return "function.exportimage"; + } + + /** + * Begin the function by showing the input dialog + */ + public void begin() + { + // Make dialog window + if (_dialog == null) + { + _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true); + _dialog.setLocationRelativeTo(_parentFrame); + _dialog.getContentPane().add(makeDialogComponents()); + _dialog.pack(); + _textScaleField.setValue(100); + } + + // Check if there is a cache to use + if (!BaseImageConfigDialog.isImagePossible()) + { + _app.showErrorMessage(getNameKey(), "dialog.exportimage.noimagepossible"); + return; + } + + _baseImagePanel.updateBaseImageDetails(); + baseImageChanged(); + // Show dialog + _dialog.setVisible(true); + } + + /** + * Make the dialog components to select the export options + * @return Component holding gui elements + */ + private Component makeDialogComponents() + { + JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout(4, 4)); + // Checkbox for drawing track or not + _drawDataCheckbox = new JCheckBox(I18nManager.getText("dialog.exportimage.drawtrack")); + _drawDataCheckbox.setSelected(true); // draw by default + // Also whether to draw track points or not + _drawTrackPointsCheckbox = new JCheckBox(I18nManager.getText("dialog.exportimage.drawtrackpoints")); + _drawTrackPointsCheckbox.setSelected(true); + // Add listener to en/disable trackpoints checkbox + _drawDataCheckbox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + _drawTrackPointsCheckbox.setEnabled(_drawDataCheckbox.isSelected()); + } + }); + + // TODO: Maybe have other controls such as line width, symbol scale factor + JPanel controlsPanel = new JPanel(); + GuiGridLayout grid = new GuiGridLayout(controlsPanel); + grid.add(new JLabel(I18nManager.getText("dialog.exportimage.textscalepercent") + ": ")); + _textScaleField = new WholeNumberField(3); + _textScaleField.setText("888"); + grid.add(_textScaleField); + + // OK, Cancel buttons + JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); + _okButton = new JButton(I18nManager.getText("button.ok")); + _okButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + doExport(); + _baseImagePanel.getGrouter().clearMapImage(); + _dialog.dispose(); + } + }); + buttonPanel.add(_okButton); + JButton cancelButton = new JButton(I18nManager.getText("button.cancel")); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + _baseImagePanel.getGrouter().clearMapImage(); + _dialog.dispose(); + } + }); + buttonPanel.add(cancelButton); + panel.add(buttonPanel, BorderLayout.SOUTH); + + // Listener to close dialog if escape pressed + KeyAdapter closer = new KeyAdapter() { + public void keyReleased(KeyEvent e) + { + if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + _dialog.dispose(); + _baseImagePanel.getGrouter().clearMapImage(); + } + } + }; + _drawDataCheckbox.addKeyListener(closer); + + // Panel for the base image + _baseImagePanel = new BaseImageDefinitionPanel(this, _dialog, _app.getTrackInfo().getTrack()); + + // Panel for the checkboxes at the top + JPanel checkPanel = new JPanel(); + checkPanel.setLayout(new BoxLayout(checkPanel, BoxLayout.Y_AXIS)); + checkPanel.add(_drawDataCheckbox); + checkPanel.add(_drawTrackPointsCheckbox); + + // add these panels to the holder panel + JPanel holderPanel = new JPanel(); + holderPanel.setLayout(new BorderLayout(5, 5)); + holderPanel.add(checkPanel, BorderLayout.NORTH); + holderPanel.add(controlsPanel, BorderLayout.CENTER); + holderPanel.add(_baseImagePanel, BorderLayout.SOUTH); + holderPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); + + panel.add(holderPanel, BorderLayout.NORTH); + return panel; + } + + + /** + * Select the file and export data to it + */ + private void doExport() + { + _okButton.setEnabled(false); + // OK pressed, so choose output file + if (_fileChooser == null) + { + _fileChooser = new JFileChooser(); + _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG); + _fileChooser.setFileFilter(new GenericFileFilter("filetype.png", new String[] {"png"})); + _fileChooser.setAcceptAllFileFilterUsed(false); + // start from directory in config which should be set + final String configDir = Config.getConfigString(Config.KEY_TRACK_DIR); + if (configDir != null) {_fileChooser.setCurrentDirectory(new File(configDir));} + } + + // Allow choose again if an existing file is selected + boolean chooseAgain = false; + do + { + chooseAgain = false; + if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION) + { + // OK pressed and file chosen + File pngFile = _fileChooser.getSelectedFile(); + if (!pngFile.getName().toLowerCase().endsWith(".png")) + { + pngFile = new File(pngFile.getAbsolutePath() + ".png"); + } + // Check if file exists and if necessary prompt for overwrite + Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")}; + if (!pngFile.exists() || JOptionPane.showOptionDialog(_parentFrame, + I18nManager.getText("dialog.save.overwrite.text"), + I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1]) + == JOptionPane.YES_OPTION) + { + // Export the file + if (!exportFile(pngFile)) + { + // export failed so need to choose again + chooseAgain = true; + } + } + else + { + // overwrite cancelled so need to choose again + chooseAgain = true; + } + } + } while (chooseAgain); + } + + /** + * Export the track data to the specified file + * @param inPngFile File object to save to + * @return true if successful + */ + private boolean exportFile(File inPngFile) + { + // Get the image file from the grouter + ImageDefinition imageDef = _baseImagePanel.getImageDefinition(); + MapSource source = MapSourceLibrary.getSource(imageDef.getSourceIndex()); + MapGrouter grouter = _baseImagePanel.getGrouter(); + GroutedImage baseImage = grouter.getMapImage(_app.getTrackInfo().getTrack(), source, + imageDef.getZoom()); + if (baseImage == null || !baseImage.isValid()) + { + _app.showErrorMessage(getNameKey(), "dialog.exportpov.cannotmakebaseimage"); + return true; + } + try + { + if (_drawDataCheckbox.isSelected()) + { + // Draw the track on top of this image + drawData(baseImage); + } + // Write composite image to file + if (!ImageIO.write(baseImage.getImage(), "png", inPngFile)) { + _app.showErrorMessage(getNameKey(), "dialog.exportpov.cannotmakebaseimage"); + return false; // choose again - the image creation worked but the save failed + } + } + catch (IOException ioe) { + System.err.println("Can't write image: " + ioe.getClass().getName()); + } + return true; + } + + /** + * Draw the track and waypoint data from the current Track onto the given image + * @param inImage GroutedImage from map tiles + */ + private void drawData(GroutedImage inImage) + { + // Work out x, y limits for drawing + DoubleRange xRange = inImage.getXRange(); + DoubleRange yRange = inImage.getYRange(); + final int zoomFactor = 1 << _baseImagePanel.getImageDefinition().getZoom(); + Graphics g = inImage.getImage().getGraphics(); + // TODO: Set line width, style etc + final PointColourer pointColourer = _app.getPointColourer(); + final Color defaultPointColour = Config.getColourScheme().getColour(ColourScheme.IDX_POINT); + g.setColor(defaultPointColour); + + // Loop to draw all track points + final Track track = _app.getTrackInfo().getTrack(); + final int numPoints = track.getNumPoints(); + int prevX = 0, prevY = 0; + for (int i=0; i