1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
|
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.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<numPoints; i++)
{
DataPoint point = track.getPoint(i);
if (!point.isWaypoint())
{
// Determine what colour to use to draw the track point
if (pointColourer != null)
{
Color c = pointColourer.getColour(i);
g.setColor(c == null ? defaultPointColour : c);
}
double x = track.getX(i) - xRange.getMinimum();
double y = track.getY(i) - yRange.getMinimum();
// use zoom level to calculate pixel coords on image
int px = (int) (x * zoomFactor * 256), py = (int) (y * zoomFactor * 256);
// System.out.println("Point: x=" + x + ", px=" + px + ", y=" + y + ", py=" + py);
if (!point.getSegmentStart()) {
// draw from previous point to this one
g.drawLine(prevX, prevY, px, py);
}
// Only draw points if requested
if (_drawTrackPointsCheckbox.isSelected())
{
g.drawRect(px-2, py-2, 3, 3);
}
// save coordinates
prevX = px; prevY = py;
}
}
// Now the waypoints
final Color textColour = Config.getColourScheme().getColour(ColourScheme.IDX_TEXT);
g.setColor(textColour);
// Loop again to draw waypoints
for (int i=0; i<numPoints; i++)
{
DataPoint point = track.getPoint(i);
if (point.isWaypoint())
{
// draw blob for each waypoint
double x = track.getX(i) - xRange.getMinimum();
double y = track.getY(i) - yRange.getMinimum();
// use zoom level to calculate pixel coords on image
int px = (int) (x * zoomFactor * 256), py = (int) (y * zoomFactor * 256);
g.fillRect(px-3, py-3, 6, 6);
}
}
// Set text size according to input
int fontScalePercent = _textScaleField.getValue();
if (fontScalePercent > 10 && fontScalePercent <= 999)
{
Font gFont = g.getFont();
g.setFont(gFont.deriveFont((float) (gFont.getSize() * 0.01 * fontScalePercent)));
}
FontMetrics fm = g.getFontMetrics();
final int nameHeight = fm.getHeight();
final int imageSize = inImage.getImageSize();
// Loop over points again, draw photo points
final Color photoColour = Config.getColourScheme().getColour(ColourScheme.IDX_SECONDARY);
g.setColor(photoColour);
for (int i=0; i<numPoints; i++)
{
DataPoint point = track.getPoint(i);
if (point.hasMedia())
{
// draw blob for each photo
double x = track.getX(i) - xRange.getMinimum();
double y = track.getY(i) - yRange.getMinimum();
// use zoom level to calculate pixel coords on image
int px = (int) (x * zoomFactor * 256), py = (int) (y * zoomFactor * 256);
g.fillRect(px-3, py-3, 6, 6);
}
}
// Loop over points again, now draw names for waypoints
g.setColor(textColour);
for (int i=0; i<numPoints; i++)
{
DataPoint point = track.getPoint(i);
if (point.isWaypoint())
{
double x = track.getX(i) - xRange.getMinimum();
double y = track.getY(i) - yRange.getMinimum();
int px = (int) (x * zoomFactor * 256), py = (int) (y * zoomFactor * 256);
// Figure out where to draw waypoint name so it doesn't obscure track
String waypointName = point.getWaypointName();
int nameWidth = fm.stringWidth(waypointName);
boolean drawnName = false;
// Make arrays for coordinates right left up down
int[] nameXs = {px + 2, px - nameWidth - 2, px - nameWidth/2, px - nameWidth/2};
int[] nameYs = {py + (nameHeight/2), py + (nameHeight/2), py - 2, py + nameHeight + 2};
for (int extraSpace = 4; extraSpace < 13 && !drawnName; extraSpace+=2)
{
// Shift arrays for coordinates right left up down
nameXs[0] += 2; nameXs[1] -= 2;
nameYs[2] -= 2; nameYs[3] += 2;
// Check each direction in turn right left up down
for (int a=0; a<4; a++)
{
if (nameXs[a] > 0 && (nameXs[a] + nameWidth) < imageSize
&& nameYs[a] < imageSize && (nameYs[a] - nameHeight) > 0
&& !MapUtils.overlapsPoints(inImage.getImage(), nameXs[a], nameYs[a],
nameWidth, nameHeight, textColour))
{
// Found a rectangle to fit - draw name here and quit
g.drawString(waypointName, nameXs[a], nameYs[a]);
drawnName = true;
break;
}
}
}
}
}
// Maybe draw note at the bottom, export from GpsPrune? Filename?
// Note: Differences from main map: No mapPosition (modifying position and visible points),
// no selection, no opacities, maybe different scale/text factors
}
/**
* Base image has changed, need to enable/disable ok button
*/
public void baseImageChanged()
{
final boolean useImage = _baseImagePanel.getImageDefinition().getUseImage();
final int zoomLevel = _baseImagePanel.getImageDefinition().getZoom();
final boolean okEnabled = useImage && _baseImagePanel.getFoundData()
&& MapGrouter.isZoomLevelOk(_app.getTrackInfo().getTrack(), zoomLevel);
_okButton.setEnabled(okEnabled);
}
}
|