1
/***************************************************************************
2
* fqterm, a terminal emulator for both BBS and *nix. *
3
* Copyright (C) 2008 fqterm development group. *
5
* This program is free software; you can redistribute it and/or modify *
6
* it under the terms of the GNU General Public License as published by *
7
* the Free Software Foundation; either version 2 of the License, or *
8
* (at your option) any later version. *
10
* This program is distributed in the hope that it will be useful, *
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13
* GNU General Public License for more details. *
15
* You should have received a copy of the GNU General Public License *
16
* along with this program; if not, write to the *
17
* Free Software Foundation, Inc., *
18
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. *
19
***************************************************************************/
21
#include "fqterm_exif_extractor.h"
25
const ExifExtractor::uint16 ExifExtractor::IFD0Tag[16] = {0x010e, 0x010f, 0x0110, 0x0112, 0x011a,
26
0x011b, 0x0128, 0x0131, 0x0132, 0x013e,
27
0x013f, 0x0211, 0x0213, 0x0214, 0x8298, 0x8769
30
const char ExifExtractor::IFD0TagName[16][30] = { "ImageDescription", "Make", "Model", "Orientation",
31
"XResolution", "YResolution", "ResolutionUnit", "Software",
32
"DateTime", "WhitePoint", "PrimaryChromaticities", "YCbCrCoefficients",
33
"YCbCrPositioning", "ReferenceBlackWhite", "Copyright", "ExifOffset"
36
const ExifExtractor::uint16 ExifExtractor::SubIFDTag[38] = {0x829a,0x829d,0x8822,0x8827,0x9000,0x9003,0x9004,0x9101,0x9102,0x9201,0x9202,0x9203,0x9204,
37
0x9205,0x9206,0x9207,0x9208,0x9209,0x920a,0x927c,0x9286,0x9290,0x9291,0x9292,0xa000,0xa001,
38
0xa002,0xa003,0xa004,0xa005,0xa20e,0xa20f,0xa210,0xa215,0xa217,0xa300,0xa301,0xa302};
40
const char ExifExtractor::SubIFDTagName[38][30] = {"ExposureTime","FNumber","ExposureProgram","ISOSpeedRatings","ExifVersion","DateTimeOriginal","DateTimeDigitized",
41
"ComponentsConfiguration","CompressedBitsPerPixel","ShutterSpeedValue","ApertureValue","BrightnessValue","ExposureBiasValue",
42
"MaxApertureValue","SubjectDistance","MeteringMode","LightSource","Flash","FocalLength","MakerNote","UserComment","SubsecTime",
43
"SubsecTimeOriginal","SubsecTimeDigitized","FlashPixVersion","ColorSpace","ExifImageWidth","ExifImageHeight","RelatedSoundFile",
44
"ExifInteroperabilityOffset","FocalPlaneXResolution","FocalPlaneYResolution","FocalPlaneResolutionUnit","ExposureIndex",
45
"SensingMethod","FileSource","SceneType","CFAPattern"};
47
const char ExifExtractor::exifHeaderStandard[6] = {'E', 'x', 'i', 'f', '\0', '\0'};
49
ExifExtractor::ExifExtractor()
55
ExifExtractor::~ExifExtractor()
60
std::string ExifExtractor::extractExifInfo(std::string fileName) {
61
return extractExifInfo(fopen(fileName.c_str(), "rb"));
64
std::string ExifExtractor::extractExifInfo(FILE* file) {
67
if (!exifFile_ || !isReadable() || !checkEndian()) {
68
return exifInformation_;
71
return exifInformation_;
74
std::map<std::string, std::string>::iterator ii;
75
for (ii = exifKeyValuePairs_.begin();
76
ii != exifKeyValuePairs_.end();
78
if ((*ii).first == "UserComment") {
81
exifInformation_ += (*ii).first + " : " + (*ii).second + "\n";
85
return exifInformation_;
87
std::string ExifExtractor::info() {
88
return exifInformation_;
91
std::string ExifExtractor::operator[]( const std::string& key ) {
92
std::map<std::string, std::string>::iterator ii;
93
if ((ii = exifKeyValuePairs_.find(key)) != exifKeyValuePairs_.end()) {
99
void ExifExtractor::postRead() {
100
std::map<std::string, std::string>::iterator ii;
101
if ((ii = exifKeyValuePairs_.find("Orientation")) != exifKeyValuePairs_.end()) {
102
if ((*ii).second.length() < 1) {
103
exifKeyValuePairs_.erase(ii);
105
switch((*ii).second[0]) {
106
case '1': (*ii).second = "1st row - 1st col : top - left side"; break;
107
case '2': (*ii).second = "1st row - 1st col : top - right side"; break;
108
case '3': (*ii).second = "1st row - 1st col : bottom - right side"; break;
109
case '4': (*ii).second = "1st row - 1st col : bottom - left side"; break;
110
case '5': (*ii).second = "1st row - 1st col : left side - top"; break;
111
case '6': (*ii).second = "1st row - 1st col : right side - top"; break;
112
case '7': (*ii).second = "1st row - 1st col : right side - bottom"; break;
113
case '8': (*ii).second = "1st row - 1st col : left side - bottom"; break;
114
default: exifKeyValuePairs_.erase(ii); break;
119
if ((ii = exifKeyValuePairs_.find("ResolutionUnit")) != exifKeyValuePairs_.end()) {
120
if ((*ii).second.length() < 1) {
121
exifKeyValuePairs_.erase(ii);
123
switch((*ii).second[0]) {
124
case '1': (*ii).second = "no-unit"; break;
125
case '2': (*ii).second = "inch"; break;
126
case '3': (*ii).second = "centimeter"; break;
127
default: (*ii).second = "inch"; break;
131
if ((ii = exifKeyValuePairs_.find("DateTime")) != exifKeyValuePairs_.end()) {
132
if ((*ii).second.length() < 1) {
133
exifKeyValuePairs_.erase(ii);
135
for (size_t i = 0; i < (*ii).second.length() && i < 11; ++i) {
136
if ((*ii).second[i] == ':'){
137
(*ii).second[i] = '.';
142
if ((ii = exifKeyValuePairs_.find("DateTimeOriginal")) != exifKeyValuePairs_.end()) {
143
if ((*ii).second.length() < 1) {
144
exifKeyValuePairs_.erase(ii);
146
for (size_t i = 0; i < (*ii).second.length() && i < 11; ++i) {
147
if ((*ii).second[i] == ':'){
148
(*ii).second[i] = '.';
153
if ((ii = exifKeyValuePairs_.find("DateTimeDigitized")) != exifKeyValuePairs_.end()) {
154
if ((*ii).second.length() < 1) {
155
exifKeyValuePairs_.erase(ii);
157
for (size_t i = 0; i < (*ii).second.length() && i < 11; ++i) {
158
if ((*ii).second[i] == ':'){
159
(*ii).second[i] = '.';
164
if ((ii = exifKeyValuePairs_.find("YCbCrPositioning")) != exifKeyValuePairs_.end()) {
165
if ((*ii).second.length() < 1) {
166
exifKeyValuePairs_.erase(ii);
168
switch((*ii).second[0]) {
169
case '1': (*ii).second = "The center of pixel array"; break;
170
case '2': (*ii).second = "The datum point"; break;
171
default: exifKeyValuePairs_.erase(ii); break;
175
if ((ii = exifKeyValuePairs_.find("MeteringMode")) != exifKeyValuePairs_.end()) {
176
if ((*ii).second.length() < 1) {
177
exifKeyValuePairs_.erase(ii);
180
sscanf((*ii).second.c_str(), "%hu", &value);
182
case 0: (*ii).second = "Unknown"; break;
183
case 1: (*ii).second = "Average"; break;
184
case 2: (*ii).second = "CenterWeightedAverage"; break;
185
case 3: (*ii).second = "Spot"; break;
186
case 4: (*ii).second = "Multi-spot"; break;
187
case 5: (*ii).second = "Pattern"; break;
188
case '6': (*ii).second = "Partial"; break;
189
case 255: (*ii).second = "Other"; break;
190
default: exifKeyValuePairs_.erase(ii); break;
195
if ((ii = exifKeyValuePairs_.find("LightSource")) != exifKeyValuePairs_.end()) {
196
if ((*ii).second.length() < 1) {
197
exifKeyValuePairs_.erase(ii);
200
sscanf((*ii).second.c_str(), "%hu", &value);
202
case 0: (*ii).second = "unknown"; break;
203
case 1: (*ii).second = "daylight"; break;
204
case 2: (*ii).second = "fluorescent"; break;
205
case 3: (*ii).second = "tungsten"; break;
206
case 10: (*ii).second = "flash"; break;
207
case 17: (*ii).second = "standard light A"; break;
208
case 18: (*ii).second = "standard light B"; break;
209
case 19: (*ii).second = "standard light C"; break;
210
case 20: (*ii).second = "D55"; break;
211
case 21: (*ii).second = "D65"; break;
212
case 22: (*ii).second = "D75"; break;
213
case 255: (*ii).second = "other"; break;
214
default: exifKeyValuePairs_.erase(ii); break;
219
if ((ii = exifKeyValuePairs_.find("Flash")) != exifKeyValuePairs_.end()) {
220
if ((*ii).second.length() < 1) {
221
exifKeyValuePairs_.erase(ii);
224
sscanf((*ii).second.c_str(), "%hu", &value);
227
case 0: (*ii).second = "Flash did not fire"; break;
228
case 1: (*ii).second = "Flash fired"; break;
229
case 5: (*ii).second = "Flash fired but strobe return light not detected"; break;
230
case 7: (*ii).second = "Flash fired and strobe return light detected"; break;
231
default: exifKeyValuePairs_.erase(ii); break;
235
if ((ii = exifKeyValuePairs_.find("ColorSpace")) != exifKeyValuePairs_.end()) {
236
if ((*ii).second.length() < 1) {
237
exifKeyValuePairs_.erase(ii);
239
switch((*ii).second[0]) {
240
case '1': (*ii).second = "sRGB color space"; break;
241
case '6': (*ii).second = "Uncalibrated"; break;
242
default: exifKeyValuePairs_.erase(ii); break;
246
if ((ii = exifKeyValuePairs_.find("FocalPlaneResolutionUnit")) != exifKeyValuePairs_.end()) {
247
if ((*ii).second.length() < 1) {
248
exifKeyValuePairs_.erase(ii);
250
switch((*ii).second[0]) {
251
case '1': (*ii).second = "no-unit"; break;
252
case '2': (*ii).second = "inch"; break;
253
case '3': (*ii).second = "centimeter"; break;
254
default: exifKeyValuePairs_.erase(ii); break;
258
if ((ii = exifKeyValuePairs_.find("SensingMethod")) != exifKeyValuePairs_.end()) {
259
if ((*ii).second.length() < 1) {
260
exifKeyValuePairs_.erase(ii);
262
switch((*ii).second[0]) {
263
case '2': (*ii).second = "1 chip color area sensor"; break;
264
default: (*ii).second = "other"; break;
268
if ((ii = exifKeyValuePairs_.find("ExifVersion")) != exifKeyValuePairs_.end()) {
269
if ((*ii).second.length() != 4) {
270
exifKeyValuePairs_.erase(ii);
272
(*ii).second = 'V' + (*ii).second.substr(0, 2) + '.' + (*ii).second.substr(2);
275
if ((ii = exifKeyValuePairs_.find("ComponentsConfiguration")) != exifKeyValuePairs_.end()) {
276
if ((*ii).second.length() != 4) {
277
exifKeyValuePairs_.erase(ii);
279
static const char CCName[7][10] = {"", "[Y]", "[Cb]", "[Cr]", "[Red]", "[Green]", "[Blue]"};
281
for(int i = 0; i < 4; ++i) {
282
int index = (*ii).second[i];
283
if (index < 0 || index >= 7) {
290
if ((ii = exifKeyValuePairs_.find("ExifVersion")) != exifKeyValuePairs_.end()) {
291
if ((*ii).second.length() != 4) {
292
exifKeyValuePairs_.erase(ii);
294
(*ii).second = 'V' + (*ii).second.substr(0, 2) + '.' + (*ii).second.substr(2);
297
if ((ii = exifKeyValuePairs_.find("MakerNote")) != exifKeyValuePairs_.end()) {
298
exifKeyValuePairs_.erase(ii);
300
if ((ii = exifKeyValuePairs_.find("CFAPattern")) != exifKeyValuePairs_.end()) {
301
exifKeyValuePairs_.erase(ii);
303
if ((ii = exifKeyValuePairs_.find("UserComment")) != exifKeyValuePairs_.end()) {
304
if ((*ii).second.length() < 8) {
305
exifKeyValuePairs_.erase(ii);
307
//Leave it to higher level which can deal with encoding.
308
//"\0x41\0x53\0x43\0x49\0x49\0x00\0x00\0x00"//ASCII
309
//"\0x4a\0x49\0x53\0x00\0x00\0x00\0x00\0x00"//JIS
310
//"\0x55\0x4e\0x49\0x43\0x4f\0x44\0x45\0x00"//Unicode
311
//"\0x00\0x00\0x00\0x00\0x00\0x00\0x00\0x00"//Undefined
314
if ((ii = exifKeyValuePairs_.find("FlashPixVersion")) != exifKeyValuePairs_.end()) {
315
if ((*ii).second.length() != 4) {
316
exifKeyValuePairs_.erase(ii);
318
(*ii).second = 'V' + (*ii).second.substr(0, 2) + '.' + (*ii).second.substr(2);
322
if ((ii = exifKeyValuePairs_.find("FileSource")) != exifKeyValuePairs_.end()) {
323
if ((*ii).second.length() != 1) {
324
exifKeyValuePairs_.erase(ii);
326
if ((*ii).second[0] == '\0x03') {
327
(*ii).second = "Digital still camera";
329
exifKeyValuePairs_.erase(ii);
333
if ((ii = exifKeyValuePairs_.find("SceneType")) != exifKeyValuePairs_.end()) {
334
if ((*ii).second.length() != 1) {
335
exifKeyValuePairs_.erase(ii);
337
if ((*ii).second[0] == '\0x01') {
338
(*ii).second = "Directly photographed";
340
exifKeyValuePairs_.erase(ii);
347
std::string ExifExtractor::readInfo() {
361
std::string ret = "";
363
if (!read(&shortValue, 2,1) || shortValue == 0 || shortValue > MAXGUARD) {
366
dataType = (DATATYPE)shortValue;
368
if (!read(&count, 4,1)) {
374
buffer = new char[count + 1];
377
if (!read(buffer, 1, count)) {
381
if (!read(&offset, 4, 1)) {
384
seek(12 + offset, SEEK_SET);
385
memset(buffer, 0, count + 1);
386
read(buffer, 1, count);
388
ret = std::string(buffer, count);
392
if (!read(&offset, 4, 1)) {
395
seek(12 + offset, SEEK_SET);
396
buffer = new char[count + 1];
397
memset(buffer, 0, count + 1);
398
read(buffer, 1, count);
403
if (!read(&shortValue, 2, 1)) {
406
buffer = new char[256];
407
sprintf(buffer, "%hu\0", shortValue);
412
if (!read(&longValue, 4, 1)) {
415
buffer = new char[256];
416
sprintf(buffer, "%lu\0", longValue);
421
case UNSIGNEDRATIONAL:
422
if (!read(&offset, 4, 1)) {
425
seek(12 + offset, SEEK_SET);
426
if (!read(&urationalc, 4, 1)) {
429
if (!read(&urationalp, 4, 1)) {
432
urationalgcd = gcd(urationalp, urationalc);
433
if (urationalgcd == 0) urationalgcd = 1;
434
buffer = new char[256];
435
sprintf(buffer, "%lu/%lu", urationalc / urationalgcd, urationalp / urationalgcd);
440
if (!read(&offset, 4, 1)) {
444
seek(12 + offset, SEEK_SET);
445
if (!read(&rationalc, 4, 1)) {
450
rationalc = -rationalc;
452
if (!read(&rationalp, 4, 1)) {
457
rationalp = -rationalp;
459
rationalgcd = gcd(rationalp, rationalc);
460
if (rationalgcd == 0) rationalgcd = 1;
461
buffer = new char[256];
462
sprintf(buffer, "%ld/%ld", sign * rationalc / rationalgcd, rationalp / rationalgcd);
470
bool ExifExtractor::analyzeIFD0TagInfo() {
473
if (!read(&tag, 2, 1))
476
for (index = 0; index < 16; ++index) {
477
if (IFD0Tag[index] == tag) {
484
long pos = ftell(exifFile_) + 10;
487
std::string info = readInfo();
489
exifKeyValuePairs_[IFD0TagName[index]] = info;
496
bool ExifExtractor::analyzeSubIFDTagInfo() {
499
if (!read(&tag, 2, 1))
502
for (index = 0; index < 38; ++index) {
503
if (SubIFDTag[index] == tag) {
510
long pos = ftell(exifFile_) + 10;
513
std::string info = readInfo();
515
exifKeyValuePairs_[SubIFDTagName[index]] = info;
522
bool ExifExtractor::readIFD0() {
524
if (!read(&ifdOffset_, 4, 1)) {
527
seek(ifdOffset_ + 12, SEEK_SET);
528
uint16 entryCount = 0;
529
if (!read(&entryCount, 2, 1)) {
532
int app1Entry = entryCount;
533
while (app1Entry >= 0) {
534
analyzeIFD0TagInfo();
540
bool ExifExtractor::readSubIFD() {
541
std::map<std::string, std::string>::iterator ii;
542
if ((ii = exifKeyValuePairs_.find("ExifOffset")) == exifKeyValuePairs_.end()) {
545
uint32 exifOffset = 0;
546
sscanf((*ii).second.c_str(), "%lu", &exifOffset);
547
seek(12 + exifOffset, SEEK_SET);
549
uint16 entryCount = 0;
550
if (!read(&entryCount, 2, 1)) {
553
int app1Entry = entryCount;
554
while (app1Entry >= 0) {
555
analyzeSubIFDTagInfo();
561
bool ExifExtractor::isReadable() {
563
unsigned char soi[2];
564
if (!read(soi, 2, 1))
566
if (soi[0] != 0xFF || soi[1] != 0xD8) {
569
unsigned char app1[2];
570
if (!read(app1, 2, 1))
572
if (app1[0] != 0xFF || app1[1] != 0xE1) {
577
if (!read(exifHeader, 1, 6)) {
580
for (int i = 0; i < 6; ++i) {
581
if (exifHeader[i] != exifHeaderStandard[i])
587
bool ExifExtractor::checkEndian() {
591
if (!read(&endian, 2, 1)) {
594
if (endian == 0x4949) {
598
else if (endian == 0x4d4d) {
605
void ExifExtractor::toggleEndian( void* buf, size_t s ) {
606
char* buffer = (char*)buf;
610
for (size_t i = 0; i < s / 2; ++i) {
613
buffer[i] = buffer[s - i - 1];
614
buffer[s - i - 1] = tmp;
618
void ExifExtractor::reset() {
620
exifKeyValuePairs_.clear();
626
exifInformation_.clear();
629
bool ExifExtractor::read( void* buffer, size_t elementSize, size_t count ) {
630
for (size_t i = 0; i < count; ++i) {
631
if (fread((char*)buffer + i * elementSize, elementSize, 1, exifFile_) != 1)
633
toggleEndian((char*)buffer + i * elementSize, elementSize);
638
void ExifExtractor::seek( long offset, int origin ) {
639
fseek(exifFile_, offset, origin);
642
void ExifExtractor::rewind() {