summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--dive.h9
-rw-r--r--dives/test31.xml23
-rw-r--r--file.c2
-rw-r--r--file.h8
-rw-r--r--qt-ui/divelistview.cpp89
-rw-r--r--qt-ui/divelistview.h3
-rw-r--r--qt-ui/exif.cpp559
-rw-r--r--qt-ui/exif.h143
-rw-r--r--qt-ui/profilegraphics.cpp7
-rw-r--r--qt-ui/shiftimagetimes.ui137
-rw-r--r--qt-ui/simplewidgets.cpp20
-rw-r--r--qt-ui/simplewidgets.h13
-rw-r--r--subsurface.pro3
-rw-r--r--wreck.jpgbin0 -> 116727 bytes
14 files changed, 1015 insertions, 1 deletions
diff --git a/dive.h b/dive.h
index 894a8183a..bcaeda9d2 100644
--- a/dive.h
+++ b/dive.h
@@ -614,6 +614,11 @@ static inline struct dive *getDiveById(int id)
}
return dive;
}
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
extern struct dive *find_dive_including(timestamp_t when);
extern bool dive_within_time_range(struct dive *dive, timestamp_t when, timestamp_t offset);
struct dive *find_dive_n_near(timestamp_t when, int n, timestamp_t offset);
@@ -692,6 +697,10 @@ extern void set_dc_nickname(struct dive *dive);
extern void set_autogroup(bool value);
extern int total_weight(struct dive *);
+#ifdef __cplusplus
+}
+#endif
+
#define DIVE_ERROR_PARSE 1
#define DIVE_ERROR_PLAN 2
diff --git a/dives/test31.xml b/dives/test31.xml
new file mode 100644
index 000000000..a93155432
--- /dev/null
+++ b/dives/test31.xml
@@ -0,0 +1,23 @@
+<divelog program='subsurface' version='2'>
+<settings>
+<divecomputerid model='Suunto Vyper' deviceid='7fffffff' serial='00522075' firmware='0.0.33'/>
+<divecomputerid model='Suunto Vyper' deviceid='e9237b0a' nickname='Suunto Vyper (e9237b0a)'/>
+<autogroup state='1' />
+</settings>
+<dives>
+<trip date='2012-01-08' time='15:30:03'>
+<dive number='1' date='2012-01-08' time='15:30:03' duration='46:06 min'>
+ <cylinder size='24.0 l' workpressure='232.0 bar' description='D12 232 bar' />
+ <divecomputer model='manually added dive' date='2014-01-09' time='14:22:03'>
+ <depth max='15.0 m' mean='13.671 m' />
+ <sample time='0:00 min' depth='0.0 m' />
+ <sample time='1:00 min' depth='15.0 m' />
+ <sample time='40:00 min' depth='15.0 m' />
+ <sample time='42:00 min' depth='5.0 m' />
+ <sample time='45:00 min' depth='5.0 m' />
+ <sample time='46:06 min' depth='0.0 m' />
+ </divecomputer>
+</dive>
+</trip>
+</dives>
+</divelog>
diff --git a/file.c b/file.c
index 7cc5528c3..8157e9a60 100644
--- a/file.c
+++ b/file.c
@@ -156,7 +156,7 @@ static int try_to_open_db(const char *filename, struct memblock *mem, char **err
return parse_dm4_buffer(filename, mem->buffer, mem->size, &dive_table, error);
}
-static timestamp_t parse_date(const char *date)
+timestamp_t parse_date(const char *date)
{
int hour, min, sec;
struct tm tm;
diff --git a/file.h b/file.h
index b3a1ebac3..1e1e07721 100644
--- a/file.h
+++ b/file.h
@@ -9,6 +9,14 @@ struct memblock {
#if 0
extern int try_to_open_cochran(const char *filename, struct memblock *mem, GError **error);
#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
extern int readfile(const char *filename, struct memblock *mem);
+extern timestamp_t parse_date(const char *date);
+#ifdef __cplusplus
+}
+#endif
#endif
diff --git a/qt-ui/divelistview.cpp b/qt-ui/divelistview.cpp
index 86a3b95fd..1b19d06fd 100644
--- a/qt-ui/divelistview.cpp
+++ b/qt-ui/divelistview.cpp
@@ -10,6 +10,8 @@
#include "mainwindow.h"
#include "subsurfacewebservices.h"
#include "../display.h"
+#include "exif.h"
+#include "../file.h"
#include <QApplication>
#include <QHeaderView>
#include <QDebug>
@@ -21,6 +23,9 @@
#include <QKeyEvent>
#include <QMenu>
#include <QFileDialog>
+#include <string>
+#include <iostream>
+
DiveListView::DiveListView(QWidget *parent) : QTreeView(parent), mouseClickSelection(false),
sortColumn(0), currentOrder(Qt::DescendingOrder), searchBox(new QLineEdit(this))
@@ -735,6 +740,7 @@ void DiveListView::contextMenuEvent(QContextMenuEvent *event)
popup.addAction(tr("save As"), this, SLOT(saveSelectedDivesAs()));
popup.addAction(tr("export As UDDF"), this, SLOT(exportSelectedDivesAsUDDF()));
popup.addAction(tr("shift times"), this, SLOT(shiftTimes()));
+ popup.addAction(tr("load images"), this, SLOT(loadImages()));
}
if (d)
popup.addAction(tr("upload dive(s) to divelogs.de"), this, SLOT(uploadToDivelogsDE()));
@@ -794,7 +800,90 @@ void DiveListView::shiftTimes()
ShiftTimesDialog::instance()->show();
}
+void DiveListView::loadImages()
+{
+ struct memblock mem;
+ EXIFInfo exif;
+ int code;
+ time_t imagetime;
+ QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open Image Files"), lastUsedImageDir(), tr("Image Files (*.jpg *.jpeg *.pnm *.tif *.tiff)"));
+
+ if (fileNames.isEmpty())
+ return;
+
+ updateLastUsedImageDir(QFileInfo(fileNames[0]).dir().path());
+
+ ShiftImageTimesDialog* shiftDialog = ShiftImageTimesDialog::instance();
+ shiftDialog->exec();
+
+ for (int i = 0; i < fileNames.size(); ++i) {
+ struct tm tm;
+ int year, month, day, hour, min, sec;
+ readfile(fileNames.at(i).toUtf8().data(), &mem);
+ code = exif.parseFrom((const unsigned char *) mem.buffer, (unsigned) mem.size);
+ free(mem.buffer);
+ sscanf(exif.DateTime.c_str(), "%d:%d:%d %d:%d:%d", &year, &month, &day, &hour, &min, &sec);
+ tm.tm_year = year;
+ tm.tm_mon = month - 1;
+ tm.tm_mday = day;
+ tm.tm_hour = hour;
+ tm.tm_min = min;
+ tm.tm_sec = sec;
+ imagetime = utc_mktime(&tm) + shiftDialog->amount;
+ int j = 0;
+ struct dive *dive;
+ for_each_dive(j, dive){
+ if (!dive->selected)
+ continue;
+ if (dive->when - 3600 < imagetime && dive->when + dive->duration.seconds + 3600 > imagetime){
+ if (dive->when > imagetime) {
+ ; // Before dive
+ add_event(&(dive->dc), 0, 123, 0, 0, fileNames.at(i).toUtf8().data());
+ mainWindow()->refreshDisplay();
+ mark_divelist_changed(true);
+ }
+ else if (dive->when + dive->duration.seconds < imagetime){
+ ; // After dive
+ add_event(&(dive->dc), dive->duration.seconds, 123, 0, 0, fileNames.at(i).toUtf8().data());
+ mainWindow()->refreshDisplay();
+ mark_divelist_changed(true);
+ }
+ else {
+ add_event(&(dive->dc), imagetime - dive->when, 123, 0, 0, fileNames.at(i).toUtf8().data());
+ mainWindow()->refreshDisplay();
+ mark_divelist_changed(true);
+ }
+ if (!dive->latitude.udeg && !IS_FP_SAME(exif.GeoLocation.Latitude, 0.0)){
+ dive->latitude.udeg = lrint(1000000.0 * exif.GeoLocation.Latitude);
+ dive->longitude.udeg = lrint(1000000.0 * exif.GeoLocation.Longitude);
+ mark_divelist_changed(true);
+ mainWindow()->refreshDisplay();
+ }
+ }
+ }
+ }
+}
+
void DiveListView::uploadToDivelogsDE()
{
DivelogsDeWebServices::instance()->prepareDivesForUpload();
}
+
+QString DiveListView::lastUsedImageDir()
+{
+ QSettings settings;
+ QString lastImageDir = QDir::homePath();
+
+ settings.beginGroup("FileDialog");
+ if (settings.contains("LastImageDir"))
+ if (QDir::setCurrent(settings.value("LastImageDir").toString()))
+ lastImageDir = settings.value("LastIamgeDir").toString();
+ return lastImageDir;
+}
+
+void DiveListView::updateLastUsedImageDir(const QString& dir)
+{
+ QSettings s;
+ s.beginGroup("FileDialog");
+ s.setValue("LastImageDir", dir);
+}
diff --git a/qt-ui/divelistview.h b/qt-ui/divelistview.h
index f65a6780b..99b8fce23 100644
--- a/qt-ui/divelistview.h
+++ b/qt-ui/divelistview.h
@@ -49,6 +49,7 @@ public slots:
void saveSelectedDivesAs();
void exportSelectedDivesAsUDDF();
void shiftTimes();
+ void loadImages();
void uploadToDivelogsDE();
signals:
@@ -71,6 +72,8 @@ private:
void restoreExpandedRows();
int lastVisibleColumn();
void selectTrip ( dive_trip_t* trip );
+ QString lastUsedImageDir();
+ void updateLastUsedImageDir(const QString& s);
};
#endif // DIVELISTVIEW_H
diff --git a/qt-ui/exif.cpp b/qt-ui/exif.cpp
new file mode 100644
index 000000000..c122567cb
--- /dev/null
+++ b/qt-ui/exif.cpp
@@ -0,0 +1,559 @@
+#include <stdio.h>
+/**************************************************************************
+ exif.cpp -- A simple ISO C++ library to parse basic EXIF
+ information from a JPEG file.
+
+ Copyright (c) 2010-2013 Mayank Lahiri
+ mlahiri@gmail.com
+ All rights reserved (BSD License).
+
+ See exif.h for version history.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ -- Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ -- Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESS
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+#include <algorithm>
+#include "exif.h"
+
+using std::string;
+
+namespace {
+ // IF Entry
+ struct IFEntry {
+ // Raw fields
+ unsigned short tag;
+ unsigned short format;
+ unsigned data;
+ unsigned length;
+
+ // Parsed fields
+ string val_string;
+ unsigned short val_16;
+ unsigned val_32;
+ double val_rational;
+ unsigned char val_byte;
+ };
+
+ // Helper functions
+ unsigned int parse32(const unsigned char *buf, bool intel) {
+ if (intel)
+ return ((unsigned)buf[3]<<24) |
+ ((unsigned)buf[2]<<16) |
+ ((unsigned)buf[1]<<8) |
+ buf[0];
+
+ return ((unsigned)buf[0]<<24) |
+ ((unsigned)buf[1]<<16) |
+ ((unsigned)buf[2]<<8) |
+ buf[3];
+ }
+
+ unsigned short parse16(const unsigned char *buf, bool intel) {
+ if (intel)
+ return ((unsigned) buf[1]<<8) | buf[0];
+ return ((unsigned) buf[0]<<8) | buf[1];
+ }
+
+ string parseEXIFString(const unsigned char *buf,
+ const unsigned num_components,
+ const unsigned data,
+ const unsigned base,
+ const unsigned len) {
+ string value;
+ if (num_components <= 4)
+ value.assign( (const char*)&data, num_components );
+ else {
+ if (base+data+num_components <= len)
+ value.assign( (const char*)(buf+base+data), num_components );
+ }
+ return value;
+ }
+
+ double parseEXIFRational(const unsigned char *buf, bool intel) {
+ double numerator = 0;
+ double denominator = 1;
+
+ numerator = (double) parse32(buf, intel);
+ denominator= (double) parse32(buf+4, intel);
+ if(denominator < 1e-20)
+ return 0;
+ return numerator/denominator;
+ }
+
+ IFEntry parseIFEntry(const unsigned char *buf,
+ const unsigned offs,
+ const bool alignIntel,
+ const unsigned base,
+ const unsigned len) {
+ IFEntry result;
+
+ // Each directory entry is composed of:
+ // 2 bytes: tag number (data field)
+ // 2 bytes: data format
+ // 4 bytes: number of components
+ // 4 bytes: data value or offset to data value
+ result.tag = parse16(buf + offs, alignIntel);
+ result.format = parse16(buf + offs + 2, alignIntel);
+ result.length = parse32(buf + offs + 4, alignIntel);
+ result.data = parse32(buf + offs + 8, alignIntel);
+
+ // Parse value in specified format
+ switch (result.format) {
+ case 1:
+ result.val_byte = (unsigned char) *(buf + offs + 8);
+ break;
+ case 2:
+ result.val_string = parseEXIFString(buf, result.length, result.data, base, len);
+ break;
+ case 3:
+ result.val_16 = parse16((const unsigned char *) buf + offs + 8, alignIntel);
+ break;
+ case 4:
+ result.val_32 = result.data;
+ break;
+ case 5:
+ if (base + result.data + 8 <= len)
+ result.val_rational = parseEXIFRational(buf + base + result.data, alignIntel);
+ break;
+ case 7:
+ case 9:
+ case 10:
+ break;
+ default:
+ result.tag = 0xFF;
+ }
+ return result;
+ }
+}
+
+//
+// Locates the EXIF segment and parses it using parseFromEXIFSegment
+//
+int EXIFInfo::parseFrom(const unsigned char *buf, unsigned len) {
+ // Sanity check: all JPEG files start with 0xFFD8 and end with 0xFFD9
+ // This check also ensures that the user has supplied a correct value for len.
+ if (!buf || len < 4)
+ return PARSE_EXIF_ERROR_NO_EXIF;
+ if (buf[0] != 0xFF || buf[1] != 0xD8)
+ return PARSE_EXIF_ERROR_NO_JPEG;
+ if (buf[len-2] != 0xFF || buf[len-1] != 0xD9)
+ return PARSE_EXIF_ERROR_NO_JPEG;
+ clear();
+
+ // Scan for EXIF header (bytes 0xFF 0xE1) and do a sanity check by
+ // looking for bytes "Exif\0\0". The marker length data is in Motorola
+ // byte order, which results in the 'false' parameter to parse16().
+ // The marker has to contain at least the TIFF header, otherwise the
+ // EXIF data is corrupt. So the minimum length specified here has to be:
+ // 2 bytes: section size
+ // 6 bytes: "Exif\0\0" string
+ // 2 bytes: TIFF header (either "II" or "MM" string)
+ // 2 bytes: TIFF magic (short 0x2a00 in Motorola byte order)
+ // 4 bytes: Offset to first IFD
+ // =========
+ // 16 bytes
+ unsigned offs = 0; // current offset into buffer
+ for (offs = 0; offs < len-1; offs++)
+ if (buf[offs] == 0xFF && buf[offs+1] == 0xE1)
+ break;
+ if (offs + 4 > len)
+ return PARSE_EXIF_ERROR_NO_EXIF;
+ offs += 2;
+ unsigned short section_length = parse16(buf + offs, false);
+ if (offs + section_length > len || section_length < 16)
+ return PARSE_EXIF_ERROR_CORRUPT;
+ offs += 2;
+
+ return parseFromEXIFSegment(buf + offs, len - offs);
+}
+
+int EXIFInfo::parseFrom(const string &data) {
+ return parseFrom((const unsigned char *)data.data(), data.length());
+}
+
+//
+// Main parsing function for an EXIF segment.
+//
+// PARAM: 'buf' start of the EXIF TIFF, which must be the bytes "Exif\0\0".
+// PARAM: 'len' length of buffer
+//
+int EXIFInfo::parseFromEXIFSegment(const unsigned char *buf, unsigned len) {
+ bool alignIntel = true; // byte alignment (defined in EXIF header)
+ unsigned offs = 0; // current offset into buffer
+ if (!buf || len < 6)
+ return PARSE_EXIF_ERROR_NO_EXIF;
+
+ if (!std::equal(buf, buf+6, "Exif\0\0"))
+ return PARSE_EXIF_ERROR_NO_EXIF;
+ offs += 6;
+
+ // Now parsing the TIFF header. The first two bytes are either "II" or
+ // "MM" for Intel or Motorola byte alignment. Sanity check by parsing
+ // the unsigned short that follows, making sure it equals 0x2a. The
+ // last 4 bytes are an offset into the first IFD, which are added to
+ // the global offset counter. For this block, we expect the following
+ // minimum size:
+ // 2 bytes: 'II' or 'MM'
+ // 2 bytes: 0x002a
+ // 4 bytes: offset to first IDF
+ // -----------------------------
+ // 8 bytes
+ if (offs + 8 > len)
+ return PARSE_EXIF_ERROR_CORRUPT;
+ unsigned tiff_header_start = offs;
+ if (buf[offs] == 'I' && buf[offs+1] == 'I')
+ alignIntel = true;
+ else {
+ if(buf[offs] == 'M' && buf[offs+1] == 'M')
+ alignIntel = false;
+ else
+ return PARSE_EXIF_ERROR_UNKNOWN_BYTEALIGN;
+ }
+ this->ByteAlign = alignIntel;
+ offs += 2;
+ if (0x2a != parse16(buf+offs, alignIntel))
+ return PARSE_EXIF_ERROR_CORRUPT;
+ offs += 2;
+ unsigned first_ifd_offset = parse32(buf + offs, alignIntel);
+ offs += first_ifd_offset - 4;
+ if (offs >= len)
+ return PARSE_EXIF_ERROR_CORRUPT;
+
+ // Now parsing the first Image File Directory (IFD0, for the main image).
+ // An IFD consists of a variable number of 12-byte directory entries. The
+ // first two bytes of the IFD section contain the number of directory
+ // entries in the section. The last 4 bytes of the IFD contain an offset
+ // to the next IFD, which means this IFD must contain exactly 6 + 12 * num
+ // bytes of data.
+ if (offs + 2 > len)
+ return PARSE_EXIF_ERROR_CORRUPT;
+ int num_entries = parse16(buf + offs, alignIntel);
+ if (offs + 6 + 12 * num_entries > len)
+ return PARSE_EXIF_ERROR_CORRUPT;
+ offs += 2;
+ unsigned exif_sub_ifd_offset = len;
+ unsigned gps_sub_ifd_offset = len;
+ while (--num_entries >= 0) {
+ IFEntry result = parseIFEntry(buf, offs, alignIntel, tiff_header_start, len);
+ offs += 12;
+ switch(result.tag) {
+ case 0x102:
+ // Bits per sample
+ if (result.format == 3)
+ this->BitsPerSample = result.val_16;
+ break;
+
+ case 0x10E:
+ // Image description
+ if (result.format == 2)
+ this->ImageDescription = result.val_string;
+ break;
+
+ case 0x10F:
+ // Digicam make
+ if (result.format == 2)
+ this->Make = result.val_string;
+ break;
+
+ case 0x110:
+ // Digicam model
+ if (result.format == 2)
+ this->Model = result.val_string;
+ break;
+
+ case 0x112:
+ // Orientation of image
+ if (result.format == 3)
+ this->Orientation = result.val_16;
+ break;
+
+ case 0x131:
+ // Software used for image
+ if (result.format == 2)
+ this->Software = result.val_string;
+ break;
+
+ case 0x132:
+ // EXIF/TIFF date/time of image modification
+ if (result.format == 2)
+ this->DateTime = result.val_string;
+ break;
+
+ case 0x8298:
+ // Copyright information
+ if (result.format == 2)
+ this->Copyright = result.val_string;
+ break;
+
+ case 0x8825:
+ // GPS IFS offset
+ gps_sub_ifd_offset = tiff_header_start + result.data;
+ break;
+
+ case 0x8769:
+ // EXIF SubIFD offset
+ exif_sub_ifd_offset = tiff_header_start + result.data;
+ break;
+ }
+ }
+
+ // Jump to the EXIF SubIFD if it exists and parse all the information
+ // there. Note that it's possible that the EXIF SubIFD doesn't exist.
+ // The EXIF SubIFD contains most of the interesting information that a
+ // typical user might want.
+ if (exif_sub_ifd_offset + 4 <= len) {
+ offs = exif_sub_ifd_offset;
+ int num_entries = parse16(buf + offs, alignIntel);
+ if (offs + 6 + 12 * num_entries > len)
+ return PARSE_EXIF_ERROR_CORRUPT;
+ offs += 2;
+ while (--num_entries >= 0) {
+ IFEntry result = parseIFEntry(buf, offs, alignIntel, tiff_header_start, len);
+ switch(result.tag) {
+ case 0x829a:
+ // Exposure time in seconds
+ if (result.format == 5)
+ this->ExposureTime = result.val_rational;
+ break;
+
+ case 0x829d:
+ // FNumber
+ if (result.format == 5)
+ this->FNumber = result.val_rational;
+ break;
+
+ case 0x8827:
+ // ISO Speed Rating
+ if (result.format == 3)
+ this->ISOSpeedRatings = result.val_16;
+ break;
+
+ case 0x9003:
+ // Original date and time
+ if (result.format == 2)
+ this->DateTimeOriginal = result.val_string;
+ break;
+
+ case 0x9004:
+ // Digitization date and time
+ if (result.format == 2)
+ this->DateTimeDigitized = result.val_string;
+ break;
+
+ case 0x9201:
+ // Shutter speed value
+ if (result.format == 5)
+ this->ShutterSpeedValue = result.val_rational;
+ break;
+
+ case 0x9204:
+ // Exposure bias value
+ if (result.format == 5)
+ this->ExposureBiasValue = result.val_rational;
+ break;
+
+ case 0x9206:
+ // Subject distance
+ if (result.format == 5)
+ this->SubjectDistance = result.val_rational;
+ break;
+
+ case 0x9209:
+ // Flash used
+ if (result.format == 3)
+ this->Flash = result.data ? 1 : 0;
+ break;
+
+ case 0x920a:
+ // Focal length
+ if (result.format == 5)
+ this->FocalLength = result.val_rational;
+ break;
+
+ case 0x9207:
+ // Metering mode
+ if (result.format == 3)
+ this->MeteringMode = result.val_16;
+ break;
+
+ case 0x9291:
+ // Subsecond original time
+ if (result.format == 2)
+ this->SubSecTimeOriginal = result.val_string;
+ break;
+
+ case 0xa002:
+ // EXIF Image width
+ if (result.format == 4)
+ this->ImageWidth = result.val_32;
+ if (result.format == 3)
+ this->ImageWidth = result.val_16;
+ break;
+
+ case 0xa003:
+ // EXIF Image height
+ if (result.format == 4)
+ this->ImageHeight = result.val_32;
+ if (result.format == 3)
+ this->ImageHeight = result.val_16;
+ break;
+
+ case 0xa405:
+ // Focal length in 35mm film
+ if (result.format == 3)
+ this->FocalLengthIn35mm = result.val_16;
+ break;
+ }
+ offs += 12;
+ }
+ }
+
+ // Jump to the GPS SubIFD if it exists and parse all the information
+ // there. Note that it's possible that the GPS SubIFD doesn't exist.
+ if (gps_sub_ifd_offset + 4 <= len) {
+ offs = gps_sub_ifd_offset;
+ int num_entries = parse16(buf + offs, alignIntel);
+ if (offs + 6 + 12 * num_entries > len)
+ return PARSE_EXIF_ERROR_CORRUPT;
+ offs += 2;
+ while (--num_entries >= 0) {
+ unsigned short tag = parse16(buf + offs, alignIntel);
+ unsigned short format = parse16(buf + offs + 2, alignIntel);
+ unsigned length = parse32(buf + offs + 4, alignIntel);
+ unsigned data = parse32(buf + offs + 8, alignIntel);
+ switch(tag) {
+ case 1:
+ // GPS north or south
+ this->GeoLocation.LatComponents.direction = *(buf + offs + 8);
+ if ('S' == this->GeoLocation.LatComponents.direction)
+ this->GeoLocation.Latitude = -this->GeoLocation.Latitude;
+ break;
+
+ case 2:
+ // GPS latitude
+ if (format == 5 && length == 3) {
+ this->GeoLocation.LatComponents.degrees =
+ parseEXIFRational(buf + data + tiff_header_start, alignIntel);
+ this->GeoLocation.LatComponents.minutes =
+ parseEXIFRational(buf + data + tiff_header_start + 8, alignIntel);
+ this->GeoLocation.LatComponents.seconds =
+ parseEXIFRational(buf + data + tiff_header_start + 16, alignIntel);
+ this->GeoLocation.Latitude =
+ this->GeoLocation.LatComponents.degrees +
+ this->GeoLocation.LatComponents.minutes / 60 +
+ this->GeoLocation.LatComponents.seconds / 3600;
+ if ('S' == this->GeoLocation.LatComponents.direction)
+ this->GeoLocation.Latitude = -this->GeoLocation.Latitude;
+ }
+ break;
+
+ case 3:
+ // GPS east or west
+ this->GeoLocation.LonComponents.direction = *(buf + offs + 8);
+ if ('W' == this->GeoLocation.LonComponents.direction)
+ this->GeoLocation.Longitude = -this->GeoLocation.Longitude;
+ break;
+
+ case 4:
+ // GPS longitude
+ if (format == 5 && length == 3) {
+ this->GeoLocation.LonComponents.degrees =
+ parseEXIFRational(buf + data + tiff_header_start, alignIntel);
+ this->GeoLocation.LonComponents.minutes =
+ parseEXIFRational(buf + data + tiff_header_start + 8, alignIntel);
+ this->GeoLocation.LonComponents.seconds =
+ parseEXIFRational(buf + data + tiff_header_start + 16, alignIntel);
+ this->GeoLocation.Longitude =
+ this->GeoLocation.LonComponents.degrees +
+ this->GeoLocation.LonComponents.minutes / 60 +
+ this->GeoLocation.LonComponents.seconds / 3600;
+ if ('W' == this->GeoLocation.LonComponents.direction)
+ this->GeoLocation.Longitude = -this->GeoLocation.Longitude;
+ }
+ break;
+
+ case 5:
+ // GPS altitude reference (below or above sea level)
+ this->GeoLocation.AltitudeRef = *(buf + offs + 8);
+ if (1 == this->GeoLocation.AltitudeRef)
+ this->GeoLocation.Altitude = -this->GeoLocation.Altitude;
+ break;
+
+ case 6:
+ // GPS altitude reference
+ if (format == 5) {
+ this->GeoLocation.Altitude =
+ parseEXIFRational(buf + data + tiff_header_start, alignIntel);
+ if (1 == this->GeoLocation.AltitudeRef)
+ this->GeoLocation.Altitude = -this->GeoLocation.Altitude;
+ }
+ break;
+ }
+ offs += 12;
+ }
+ }
+
+ return PARSE_EXIF_SUCCESS;
+}
+
+void EXIFInfo::clear() {
+ // Strings
+ ImageDescription = "";
+ Make = "";
+ Model = "";
+ Software = "";
+ DateTime = "";
+ DateTimeOriginal = "";
+ DateTimeDigitized = "";
+ SubSecTimeOriginal= "";
+ Copyright = "";
+
+ // Shorts / unsigned / double
+ ByteAlign = 0;
+ Orientation = 0;
+
+ BitsPerSample = 0;
+ ExposureTime = 0;
+ FNumber = 0;
+ ISOSpeedRatings = 0;
+ ShutterSpeedValue = 0;
+ ExposureBiasValue = 0;
+ SubjectDistance = 0;
+ FocalLength = 0;
+ FocalLengthIn35mm = 0;
+ Flash = 0;
+ MeteringMode = 0;
+ ImageWidth = 0;
+ ImageHeight = 0;
+
+ // Geolocation
+ GeoLocation.Latitude = 0;
+ GeoLocation.Longitude = 0;
+ GeoLocation.Altitude = 0;
+ GeoLocation.AltitudeRef = 0;
+ GeoLocation.LatComponents.degrees = 0;
+ GeoLocation.LatComponents.minutes = 0;
+ GeoLocation.LatComponents.seconds = 0;
+ GeoLocation.LatComponents.direction = 0;
+ GeoLocation.LonComponents.degrees = 0;
+ GeoLocation.LonComponents.minutes = 0;
+ GeoLocation.LonComponents.seconds = 0;
+ GeoLocation.LonComponents.direction = 0;
+}
diff --git a/qt-ui/exif.h b/qt-ui/exif.h
new file mode 100644
index 000000000..a05399f92
--- /dev/null
+++ b/qt-ui/exif.h
@@ -0,0 +1,143 @@
+/**************************************************************************
+ exif.h -- A simple ISO C++ library to parse basic EXIF
+ information from a JPEG file.
+
+ Based on the description of the EXIF file format at:
+ -- http://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html
+ -- http://www.media.mit.edu/pia/Research/deepview/exif.html
+ -- http://www.exif.org/Exif2-2.PDF
+
+ Copyright (c) 2010-2013 Mayank Lahiri
+ mlahiri@gmail.com
+ All rights reserved.
+
+ VERSION HISTORY:
+ ================
+
+ 2.1: Released July 2013
+ -- fixed a bug where JPEGs without an EXIF SubIFD would not be parsed
+ -- fixed a bug in parsing GPS coordinate seconds
+ -- fixed makefile bug
+ -- added two pathological test images from Matt Galloway
+ http://www.galloway.me.uk/2012/01/uiimageorientation-exif-orientation-sample-images/
+ -- split main parsing routine for easier integration into Firefox
+
+ 2.0: Released February 2013
+ -- complete rewrite
+ -- no new/delete
+ -- added GPS support
+
+ 1.0: Released 2010
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ -- Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ -- Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESS
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+#ifndef __EXIF_H
+#define __EXIF_H
+
+#include <string>
+
+//
+// Class responsible for storing and parsing EXIF information from a JPEG blob
+//
+class EXIFInfo {
+ public:
+ // Parsing function for an entire JPEG image buffer.
+ //
+ // PARAM 'data': A pointer to a JPEG image.
+ // PARAM 'length': The length of the JPEG image.
+ // RETURN: PARSE_EXIF_SUCCESS (0) on succes with 'result' filled out
+ // error code otherwise, as defined by the PARSE_EXIF_ERROR_* macros
+ int parseFrom(const unsigned char *data, unsigned length);
+ int parseFrom(const std::string &data);
+
+ // Parsing function for an EXIF segment. This is used internally by parseFrom()
+ // but can be called for special cases where only the EXIF section is
+ // available (i.e., a blob starting with the bytes "Exif\0\0").
+ int parseFromEXIFSegment(const unsigned char *buf, unsigned len);
+
+ // Set all data members to default values.
+ void clear();
+
+ // Data fields filled out by parseFrom()
+ char ByteAlign; // 0 = Motorola byte alignment, 1 = Intel
+ std::string ImageDescription; // Image description
+ std::string Make; // Camera manufacturer's name
+ std::string Model; // Camera model
+ unsigned short Orientation; // Image orientation, start of data corresponds to
+ // 0: unspecified in EXIF data
+ // 1: upper left of image
+ // 3: lower right of image
+ // 6: upper right of image
+ // 8: lower left of image
+ // 9: undefined
+ unsigned short BitsPerSample; // Number of bits per component
+ std::string Software; // Software used
+ std::string DateTime; // File change date and time
+ std::string DateTimeOriginal; // Original file date and time (may not exist)
+ std::string DateTimeDigitized; // Digitization date and time (may not exist)
+ std::string SubSecTimeOriginal; // Sub-second time that original picture was taken
+ std::string Copyright; // File copyright information
+ double ExposureTime; // Exposure time in seconds
+ double FNumber; // F/stop
+ unsigned short ISOSpeedRatings; // ISO speed
+ double ShutterSpeedValue; // Shutter speed (reciprocal of exposure time)
+ double ExposureBiasValue; // Exposure bias value in EV
+ double SubjectDistance; // Distance to focus point in meters
+ double FocalLength; // Focal length of lens in millimeters
+ unsigned short FocalLengthIn35mm; // Focal length in 35mm film
+ char Flash; // 0 = no flash, 1 = flash used
+ unsigned short MeteringMode; // Metering mode
+ // 1: average
+ // 2: center weighted average
+ // 3: spot
+ // 4: multi-spot
+ // 5: multi-segment
+ unsigned ImageWidth; // Image width reported in EXIF data
+ unsigned ImageHeight; // Image height reported in EXIF data
+ struct Geolocation_t { // GPS information embedded in file
+ double Latitude; // Image latitude expressed as decimal
+ double Longitude; // Image longitude expressed as decimal
+ double Altitude; // Altitude in meters, relative to sea level
+ char AltitudeRef; // 0 = above sea level, -1 = below sea level
+ struct Coord_t {
+ double degrees;
+ double minutes;
+ double seconds;
+ char direction;
+ } LatComponents, LonComponents; // Latitude, Longitude expressed in deg/min/sec
+ } GeoLocation;
+ EXIFInfo() {
+ clear();
+ }
+};
+
+// Parse was successful
+#define PARSE_EXIF_SUCCESS 0
+// No JPEG markers found in buffer, possibly invalid JPEG file
+#define PARSE_EXIF_ERROR_NO_JPEG 1982
+// No EXIF header found in JPEG file.
+#define PARSE_EXIF_ERROR_NO_EXIF 1983
+// Byte alignment specified in EXIF file was unknown (not Motorola or Intel).
+#define PARSE_EXIF_ERROR_UNKNOWN_BYTEALIGN 1984
+// EXIF header was found, but data was corrupted.
+#define PARSE_EXIF_ERROR_CORRUPT 1985
+
+#endif
diff --git a/qt-ui/profilegraphics.cpp b/qt-ui/profilegraphics.cpp
index d500b42e8..65266d31e 100644
--- a/qt-ui/profilegraphics.cpp
+++ b/qt-ui/profilegraphics.cpp
@@ -1018,6 +1018,13 @@ void ProfileGraphicsView::plot_one_event(struct event *ev)
item->setPos(x, y);
scene()->addItem(item);
+ if (ev->type == 123){
+ QPixmap picture;
+ picture.load(ev->name);
+ scene()->addPixmap(picture.scaledToHeight(100, Qt::SmoothTransformation))->setPos(x, y + 10);
+ }
+
+
/* we display the event on screen - so translate (with the correct context for events) */
QString name = gettextFromC::instance()->tr(ev->name);
if (ev->value) {
diff --git a/qt-ui/shiftimagetimes.ui b/qt-ui/shiftimagetimes.ui
new file mode 100644
index 000000000..653103b53
--- /dev/null
+++ b/qt-ui/shiftimagetimes.ui
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ShiftImageTimesDialog</class>
+ <widget class="QDialog" name="ShiftImageTimesDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>343</width>
+ <height>177</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>Shift selected times</string>
+ </property>
+ <property name="windowIcon">
+ <iconset>
+ <normalon>:/subsurface-icon</normalon>
+ </iconset>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item alignment="Qt::AlignTop">
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Shift times of image(s) by</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QTimeEdit" name="timeEdit">
+ <property name="date">
+ <date>
+ <year>2000</year>
+ <month>1</month>
+ <day>1</day>
+ </date>
+ </property>
+ <property name="minimumDateTime">
+ <datetime>
+ <hour>0</hour>
+ <minute>0</minute>
+ <second>0</second>
+ <year>2000</year>
+ <month>1</month>
+ <day>1</day>
+ </datetime>
+ </property>
+ <property name="maximumDate">
+ <date>
+ <year>2000</year>
+ <month>1</month>
+ <day>1</day>
+ </date>
+ </property>
+ <property name="displayFormat">
+ <string>h:mm</string>
+ </property>
+ <property name="timeSpec">
+ <enum>Qt::LocalTime</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="backwards">
+ <property name="text">
+ <string>earlier</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="forward">
+ <property name="text">
+ <string>later</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="../subsurface.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ShiftImageTimesDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ShiftImageTimesDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/qt-ui/simplewidgets.cpp b/qt-ui/simplewidgets.cpp
index f3555e376..c76921c32 100644
--- a/qt-ui/simplewidgets.cpp
+++ b/qt-ui/simplewidgets.cpp
@@ -156,6 +156,26 @@ ShiftTimesDialog::ShiftTimesDialog(QWidget *parent): QDialog(parent)
ui.setupUi(this);
connect(ui.buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonClicked(QAbstractButton*)));
}
+ShiftImageTimesDialog* ShiftImageTimesDialog::instance()
+{
+ static ShiftImageTimesDialog* self = new ShiftImageTimesDialog(mainWindow());
+ return self;
+}
+
+void ShiftImageTimesDialog::buttonClicked(QAbstractButton* button)
+{
+ if (ui.buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) {
+ amount = ui.timeEdit->time().hour() * 3600 + ui.timeEdit->time().minute() * 60;
+ if (ui.backwards->isChecked())
+ amount *= -1;
+ }
+}
+
+ShiftImageTimesDialog::ShiftImageTimesDialog(QWidget *parent): QDialog(parent)
+{
+ ui.setupUi(this);
+ connect(ui.buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonClicked(QAbstractButton*)));
+}
bool isGnome3Session()
{
diff --git a/qt-ui/simplewidgets.h b/qt-ui/simplewidgets.h
index e2ad195e6..b8097b55a 100644
--- a/qt-ui/simplewidgets.h
+++ b/qt-ui/simplewidgets.h
@@ -9,6 +9,7 @@ class QAbstractButton;
#include "ui_renumber.h"
#include "ui_shifttimes.h"
+#include "ui_shiftimagetimes.h"
class MinMaxAvgWidget : public QWidget{
Q_OBJECT
@@ -54,6 +55,18 @@ private:
Ui::ShiftTimesDialog ui;
};
+class ShiftImageTimesDialog : public QDialog {
+ Q_OBJECT
+public:
+ static ShiftImageTimesDialog *instance();
+ int amount;
+private slots:
+ void buttonClicked(QAbstractButton *button);
+private:
+ explicit ShiftImageTimesDialog(QWidget *parent);
+ Ui::ShiftImageTimesDialog ui;
+};
+
bool isGnome3Session();
#endif
diff --git a/subsurface.pro b/subsurface.pro
index ea5dd3488..148b40047 100644
--- a/subsurface.pro
+++ b/subsurface.pro
@@ -54,6 +54,7 @@ HEADERS = \
qt-ui/starwidget.h \
qt-ui/subsurfacewebservices.h \
qt-ui/tableview.h \
+ qt-ui/exif.h \
sha1.h \
statistics.h \
subsurface-icon.h \
@@ -114,6 +115,7 @@ SOURCES = \
qt-ui/starwidget.cpp \
qt-ui/subsurfacewebservices.cpp \
qt-ui/tableview.cpp \
+ qt-ui/exif.cpp \
save-xml.c \
sha1.c \
statistics.c \
@@ -153,6 +155,7 @@ FORMS = \
qt-ui/printoptions.ui \
qt-ui/renumber.ui \
qt-ui/shifttimes.ui \
+ qt-ui/shiftimagetimes.ui \
qt-ui/webservices.ui \
qt-ui/tableview.ui \
qt-ui/divelogimportdialog.ui \
diff --git a/wreck.jpg b/wreck.jpg
new file mode 100644
index 000000000..1ebfde9f0
--- /dev/null
+++ b/wreck.jpg
Binary files differ