digitaltrails
Member
New Zealand
Joined: Dec 28, 2014
Post Count: 95
Status:
Offline
|
|
Photo Date-Time Metadata and Annotations
|
I originally emailed info@eteks, but having got no response, I thought this forum might be the more correct place for development suggestions.
I've been experimenting with sun angles at different times of the year. I found it useful to write the as-at date-time into the PNG to keep track of which date-times each images illustrates. For example:
% exiftool -a ~/3D/test2.png ExifTool Version Number : 9.34 File Name : test2.png Directory : /home/michael/3D File Size : 30 kB File Modification Date/Time : 2015:02:13 22:51:30+13:00 ... Create Date Time : 2015.02.13 22:51:16+13:00 Date Time Original : 2015.02.13 12:00:00+13:00 Image Size : 400x223
As well as this I added code to optionally annotate the actual PNG graphic - note the Add Date check-box and displayed dates in the following image:
Patch is as follows (only tested with English):
diff -r -u ../SweetHome3D-4.6-src-original/src/com/eteks/sweethome3d/swing/package.properties src/com/eteks/sweethome3d/swing/package.properties --- ../SweetHome3D-4.6-src-original/src/com/eteks/sweethome3d/swing/package.properties 2015-02-02 21:39:30.000000000 +1300 +++ src/com/eteks/sweethome3d/swing/package.properties 2015-02-13 21:44:57.009095228 +1300 @@ -1539,6 +1539,8 @@ PhotoSizeAndQualityPanel.heightLabel.mnemonic=H PhotoSizeAndQualityPanel.applyProportionsCheckBox.text=Apply proportions: PhotoSizeAndQualityPanel.applyProportionsCheckBox.mnemonic=A +PhotoSizeAndQualityPanel.addDateCheckBox.text=Add Date +PhotoSizeAndQualityPanel.addDateCheckBox.mnemonic=D PhotoSizeAndQualityPanel.aspectRatioComboBox.view3DRatio.text=3D view PhotoSizeAndQualityPanel.aspectRatioComboBox.squareRatio.text=Square PhotoSizeAndQualityPanel.qualityLabel.text=Quality: diff -r -u ../SweetHome3D-4.6-src-original/src/com/eteks/sweethome3d/swing/PhotoPanel.java src/com/eteks/sweethome3d/swing/PhotoPanel.java --- ../SweetHome3D-4.6-src-original/src/com/eteks/sweethome3d/swing/PhotoPanel.java 2015-02-02 21:39:30.000000000 +1300 +++ src/com/eteks/sweethome3d/swing/PhotoPanel.java 2015-02-13 22:50:38.674348690 +1300 @@ -20,10 +20,12 @@ package com.eteks.sweethome3d.swing; import java.awt.CardLayout; +import java.awt.Color; import java.awt.Component; import java.awt.ComponentOrientation; import java.awt.Dimension; import java.awt.EventQueue; +import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; @@ -62,7 +64,13 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import javax.imageio.IIOImage; import javax.imageio.ImageIO; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataNode; import javax.swing.ActionMap; import javax.swing.DefaultListCellRenderer; import javax.swing.Icon; @@ -139,6 +147,7 @@ private JButton createButton; private JButton saveButton; private JButton closeButton; + private long photoDateTimeOriginal; private static PhotoPanel currentPhotoPanel; // Support only one photo panel opened at a time @@ -761,6 +770,8 @@ int bestImageHeight; // Check correct ratio if lens is fisheye or spherical Camera camera = home.getCamera(); + // Seem to be in local time - adjust back to UTC + photoDateTimeOriginal = camera.getTime() - TimeZone.getDefault().getOffset(camera.getTime()); if (camera.getLens() == Camera.Lens.FISHEYE) { bestImageHeight = imageWidth; } else if (camera.getLens() == Camera.Lens.SPHERICAL) { @@ -781,6 +792,7 @@ } } else { // Compute 3D view offscreen image + photoDateTimeOriginal = System.currentTimeMillis(); HomeComponent3D homeComponent3D = new HomeComponent3D( home, this.preferences, this.object3dFactory, quality == 1, null); image = homeComponent3D.getOffScreenImage(imageWidth, imageHeight); @@ -802,6 +814,9 @@ getActionMap().get(ActionType.SAVE_PHOTO).setEnabled(photoImage != null); if (photoImage != null) { getRootPane().setDefaultButton(saveButton); + if (controller.isDateShown()) { + annotatePhoto(photoImage, photoCreationStartTime, photoDateTimeOriginal); + } } createButton.setAction(getActionMap().get(ActionType.START_PHOTO_CREATION)); photoComponent.setImage(photoImage); @@ -854,14 +869,16 @@ /** * Saves the created image. + * @param showDate */ private void savePhoto() { String pngFile = this.controller.getContentManager().showSaveDialog(this, this.preferences.getLocalizedString(PhotoPanel.class, "savePhotoDialog.title"), ContentManager.ContentType.PNG, this.home.getName()); try { - if (pngFile != null) { - ImageIO.write(this.photoComponent.getImage(), "PNG", new File(pngFile)); + if (pngFile != null) { + writePhoto(this.photoComponent.getImage(), "PNG", new File(pngFile), this.photoCreationStartTime, this.photoDateTimeOriginal); + //ImageIO.write(this.photoComponent.getImage(), "PNG", new File(pngFile)); } } catch (IOException ex) { String messageFormat = this.preferences.getLocalizedString(PhotoPanel.class, "savePhotoError.message"); @@ -870,6 +887,51 @@ } } + private void writePhoto(BufferedImage image, String string, File file, long createDateTime, long dateTimeOriginal) throws IOException { + SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy.MM.dd HH:mm:ssXXX"); + + String createTime = dateFormatter.format(createDateTime); + IIOMetadataNode textEntry1 = new IIOMetadataNode("tEXtEntry"); + textEntry1.setAttribute("keyword", "CreateDateTime"); + textEntry1.setAttribute("value", createTime); + IIOMetadataNode textNode1 = new IIOMetadataNode("tEXt"); + textNode1.appendChild(textEntry1); + + String photoTime = dateFormatter.format(dateTimeOriginal); + IIOMetadataNode textEntry2 = new IIOMetadataNode("tEXtEntry"); + textEntry2.setAttribute("keyword", "DateTimeOriginal"); + textEntry2.setAttribute("value", photoTime); + IIOMetadataNode textNode2 = new IIOMetadataNode("tEXt"); + textNode2.appendChild(textEntry2); + + IIOMetadataNode root = new IIOMetadataNode("javax_imageio_png_1.0"); + root.appendChild(textNode1); + root.appendChild(textNode2); + + ImageWriter writer = ImageIO.getImageWritersByFormatName("png").next(); + ImageWriteParam writeParam = writer.getDefaultWriteParam(); + ImageTypeSpecifier imageTypeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(image.getType()); + IIOMetadata imageMetadata = writer.getDefaultImageMetadata(imageTypeSpecifier, writeParam); + imageMetadata.mergeTree("javax_imageio_png_1.0", root); + + IIOImage imageWithMetadata = new IIOImage(image, null, imageMetadata); + Object stream = ImageIO.createImageOutputStream(file); + writer.setOutput(stream); + + writer.write(imageMetadata, imageWithMetadata, writeParam); + } + + private void annotatePhoto(BufferedImage image, long createDateTime, long dateTimeOriginal) { + SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy.MM.dd HH:mm:ssXXX"); + String photoTime = dateFormatter.format(dateTimeOriginal); + String createTime = dateFormatter.format(createDateTime); + Graphics graphics = image.getGraphics(); + graphics.setFont(graphics.getFont().deriveFont(10f)); + graphics.setColor(Color.RED); + graphics.drawString(photoTime + " (" + createTime + ")", 20, 20); + graphics.dispose(); + } + /** * Manages closing of this pane. */ Only in src/com/eteks/sweethome3d/swing: PhotoPanel.java.dateTimeOriginal Only in src/com/eteks/sweethome3d/swing: PhotoPanel.java.keep diff -r -u ../SweetHome3D-4.6-src-original/src/com/eteks/sweethome3d/swing/PhotoSizeAndQualityPanel.java src/com/eteks/sweethome3d/swing/PhotoSizeAndQualityPanel.java --- ../SweetHome3D-4.6-src-original/src/com/eteks/sweethome3d/swing/PhotoSizeAndQualityPanel.java 2015-02-02 21:39:30.000000000 +1300 +++ src/com/eteks/sweethome3d/swing/PhotoSizeAndQualityPanel.java 2015-02-13 21:52:42.458590124 +1300 @@ -72,6 +72,7 @@ private JLabel heightLabel; private JSpinner heightSpinner; private JCheckBox applyProportionsCheckBox; + private JCheckBox addDateCheckBox; private JComboBox aspectRatioComboBox; private JLabel qualityLabel; private JSlider qualitySlider; @@ -141,6 +142,14 @@ : AspectRatio.FREE_RATIO); } }); + + this.addDateCheckBox = new JCheckBox(); + this.addDateCheckBox.setSelected(controller.isDateShown()); + this.addDateCheckBox.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent ev) { + controller.setDateShown(addDateCheckBox.isSelected()); + } + }); this.aspectRatioComboBox = new JComboBox(new Object [] { AspectRatio.VIEW_3D_RATIO, AspectRatio.SQUARE_RATIO, @@ -196,6 +205,7 @@ applyProportionsCheckBox.setSelected(notFreeAspectRatio); aspectRatioComboBox.setEnabled(notFreeAspectRatio); aspectRatioComboBox.setSelectedItem(controller.getAspectRatio()); + addDateCheckBox.setSelected(controller.isDateShown()); } }); @@ -309,6 +319,8 @@ PhotoSizeAndQualityPanel.class, "heightLabel.text")); this.applyProportionsCheckBox.setText(SwingTools.getLocalizedLabelText(preferences, PhotoSizeAndQualityPanel.class, "applyProportionsCheckBox.text")); + this.addDateCheckBox.setText(SwingTools.getLocalizedLabelText(preferences, + PhotoSizeAndQualityPanel.class, "addDateCheckBox.text")); this.qualityLabel.setText(SwingTools.getLocalizedLabelText(preferences, PhotoSizeAndQualityPanel.class, "qualityLabel.text")); this.fastQualityLabel.setText(SwingTools.getLocalizedLabelText(preferences, @@ -333,6 +345,8 @@ this.heightLabel.setLabelFor(this.heightSpinner); this.applyProportionsCheckBox.setMnemonic(KeyStroke.getKeyStroke(preferences.getLocalizedString( PhotoSizeAndQualityPanel.class, "applyProportionsCheckBox.mnemonic")).getKeyCode()); + this.addDateCheckBox.setMnemonic(KeyStroke.getKeyStroke(preferences.getLocalizedString( + PhotoSizeAndQualityPanel.class, "addDateCheckBox.mnemonic")).getKeyCode()); this.qualityLabel.setDisplayedMnemonic(KeyStroke.getKeyStroke(preferences.getLocalizedString( PhotoSizeAndQualityPanel.class, "qualityLabel.mnemonic")).getKeyCode()); this.qualityLabel.setLabelFor(this.qualitySlider); @@ -394,6 +408,7 @@ JPanel proportionsPanel = new JPanel(); proportionsPanel.add(this.applyProportionsCheckBox); proportionsPanel.add(this.aspectRatioComboBox); + proportionsPanel.add(this.addDateCheckBox); add(proportionsPanel, new GridBagConstraints( 0, 1, 4, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); @@ -423,6 +438,7 @@ this.widthSpinner.setEnabled(enabled); this.heightSpinner.setEnabled(enabled); this.applyProportionsCheckBox.setEnabled(enabled); + this.addDateCheckBox.setEnabled(enabled); this.aspectRatioComboBox.setEnabled(enabled); this.qualitySlider.setEnabled(enabled); } diff -r -u ../SweetHome3D-4.6-src-original/src/com/eteks/sweethome3d/viewcontroller/AbstractPhotoController.java src/com/eteks/sweethome3d/viewcontroller/AbstractPhotoController.java --- ../SweetHome3D-4.6-src-original/src/com/eteks/sweethome3d/viewcontroller/AbstractPhotoController.java 2015-02-02 21:39:30.000000000 +1300 +++ src/com/eteks/sweethome3d/viewcontroller/AbstractPhotoController.java 2015-02-13 21:52:42.439590123 +1300 @@ -51,6 +51,7 @@ private int quality; private float view3DAspectRatio; private int ceilingLightColor; + private boolean dateShown; public AbstractPhotoController(Home home, UserPreferences preferences, @@ -309,4 +310,14 @@ Object propertyValue) { this.home.setVisualProperty(propertyName, propertyValue); } + + public boolean isDateShown() { + return this.dateShown; + } + + public void setDateShown(boolean dateShown) { + this.dateShown = dateShown; + } + + }
|