infrastructure for preprocessing of metadata
[geeqie.git] / src / exiv2.cc
1 /*
2  * Geeqie
3  * Copyright (C) 2008 The Geeqie Team
4  *
5  * Author: Vladimir Nadvornik
6  *
7  * This software is released under the GNU General Public License (GNU GPL).
8  * Please read the included file COPYING for more information.
9  * This software comes with no warranty of any kind, use at your own risk!
10  */
11
12 #include "config.h"
13
14 #ifdef HAVE_EXIV2
15
16 #include <exiv2/image.hpp>
17 #include <exiv2/exif.hpp>
18 #include <iostream>
19
20 // EXIV2_TEST_VERSION is defined in Exiv2 0.15 and newer.
21 #ifndef EXIV2_TEST_VERSION
22 # define EXIV2_TEST_VERSION(major,minor,patch) \
23         ( EXIV2_VERSION >= EXIV2_MAKE_VERSION(major,minor,patch) )
24 #endif
25
26
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <sys/mman.h>
32
33 #if !EXIV2_TEST_VERSION(0,17,90)
34 #include <exiv2/tiffparser.hpp>
35 #include <exiv2/tiffcomposite.hpp>
36 #include <exiv2/tiffvisitor.hpp>
37 #include <exiv2/tiffimage.hpp>
38 #include <exiv2/cr2image.hpp>
39 #include <exiv2/crwimage.hpp>
40 #if EXIV2_TEST_VERSION(0,16,0)
41 #include <exiv2/orfimage.hpp>
42 #endif
43 #if EXIV2_TEST_VERSION(0,13,0)
44 #include <exiv2/rafimage.hpp>
45 #endif
46 #include <exiv2/futils.hpp>
47 #else
48 #include <exiv2/preview.hpp>
49 #endif
50
51
52 extern "C" {
53 #include <glib.h>
54
55 #include "main.h"
56 #include "exif.h"
57
58 #include "filefilter.h"
59 #include "ui_fileops.h"
60 }
61
62 struct _ExifData
63 {
64         Exiv2::ExifData::const_iterator exifIter; /* for exif_get_next_item */
65         Exiv2::IptcData::const_iterator iptcIter; /* for exif_get_next_item */
66 #if EXIV2_TEST_VERSION(0,16,0)
67         Exiv2::XmpData::const_iterator xmpIter; /* for exif_get_next_item */
68 #endif
69
70         virtual ~_ExifData()
71         {
72         }
73         
74         virtual void writeMetadata()
75         {
76                 g_critical("Unsupported method of writing metadata");
77         }
78
79         virtual ExifData *original()
80         {
81                 return NULL;
82         }
83
84         virtual Exiv2::Image *image() = 0;
85         
86         virtual Exiv2::ExifData &exifData() = 0;
87
88         virtual Exiv2::IptcData &iptcData() = 0;
89
90 #if EXIV2_TEST_VERSION(0,16,0)
91         virtual Exiv2::XmpData &xmpData() = 0;
92 #endif
93
94         virtual void add_jpeg_color_profile(unsigned char *cp_data, guint cp_length) = 0;
95
96         virtual guchar *get_jpeg_color_profile(guint *data_len) = 0;
97 };
98
99 // This allows read-only access to the original metadata
100 struct _ExifDataOriginal : public _ExifData
101 {
102 protected:
103         Exiv2::Image::AutoPtr image_;
104
105         /* the icc profile in jpeg is not technically exif - store it here */
106         unsigned char *cp_data_;
107         guint cp_length_;
108
109 public:
110
111         _ExifDataOriginal(gchar *path)
112         {
113                 cp_data_ = NULL;
114                 cp_length_ = 0;
115                 gchar *pathl = path_from_utf8(path);
116                 image_ = Exiv2::ImageFactory::open(pathl);
117                 g_free(pathl);
118 //              g_assert (image.get() != 0);
119                 image_->readMetadata();
120
121 #if EXIV2_TEST_VERSION(0,16,0)
122                 if (image_->mimeType() == "application/rdf+xml")
123                         {
124                         //Exiv2 sidecar converts xmp to exif and iptc, we don't want it.
125                         image_->clearExifData();
126                         image_->clearIptcData();
127                         }
128 #endif
129
130 #if EXIV2_TEST_VERSION(0,14,0)
131                 if (image_->mimeType() == "image/jpeg")
132                         {
133                         /* try to get jpeg color profile */
134                         Exiv2::BasicIo &io = image_->io();
135                         gint open = io.isopen();
136                         if (!open) io.open();
137                         unsigned char *mapped = (unsigned char*)io.mmap();
138                         if (mapped) exif_jpeg_parse_color(this, mapped, io.size());
139                         io.munmap();
140                         if (!open) io.close();
141                         }
142 #endif
143         }
144         
145         virtual ~_ExifDataOriginal()
146         {
147                 if (cp_data_) g_free(cp_data_);
148         }
149         
150         virtual Exiv2::Image *image()
151         {
152                 return image_.get();
153         }
154         
155         virtual Exiv2::ExifData &exifData ()
156         {
157                 return image_->exifData();
158         }
159
160         virtual Exiv2::IptcData &iptcData ()
161         {
162                 return image_->iptcData();
163         }
164
165 #if EXIV2_TEST_VERSION(0,16,0)
166         virtual Exiv2::XmpData &xmpData ()
167         {
168                 return image_->xmpData();
169         }
170 #endif
171
172         virtual void add_jpeg_color_profile(unsigned char *cp_data, guint cp_length)
173         {
174                 if (cp_data_) g_free(cp_data_);
175                 cp_data_ = cp_data;
176                 cp_length_ = cp_length;
177         }
178
179         virtual guchar *get_jpeg_color_profile(guint *data_len)
180         {
181                 if (cp_data_)
182                 {
183                         if (data_len) *data_len = cp_length_;
184                         return (unsigned char *) g_memdup(cp_data_, cp_length_);
185                 }
186                 return NULL;
187         }
188 };
189
190 // This allows read-write access to the metadata
191 struct _ExifDataProcessed : public _ExifData
192 {
193 protected:
194         _ExifDataOriginal *imageData_;
195         _ExifDataOriginal *sidecarData_;
196
197         Exiv2::ExifData exifData_;
198         Exiv2::IptcData iptcData_;
199 #if EXIV2_TEST_VERSION(0,16,0)
200         Exiv2::XmpData xmpData_;
201 #endif
202
203 public:
204         _ExifDataProcessed(gchar *path, gchar *sidecar_path)
205         {
206                 imageData_ = new _ExifDataOriginal(path);
207                 sidecarData_ = NULL;
208 #if EXIV2_TEST_VERSION(0,16,0)
209                 xmpData_ = imageData_->xmpData();
210                 DEBUG_2("xmp count %li", xmpData_.count());
211                 if (sidecar_path && xmpData_.empty())
212                         {
213                         sidecarData_ = new _ExifDataOriginal(sidecar_path);
214                         xmpData_ = sidecarData_->xmpData();
215                         }
216 #endif
217                 exifData_ = imageData_->exifData();
218                 iptcData_ = imageData_->iptcData();
219         }
220
221         virtual ~_ExifDataProcessed()
222         {
223                 if (imageData_) delete imageData_;
224                 if (sidecarData_) delete sidecarData_;
225         }
226
227         virtual ExifData *original()
228         {
229                 return imageData_;
230         }
231
232         virtual void writeMetadata()
233         {
234                 
235                 if (sidecarData_) 
236                         {
237                         sidecarData_->image()->setXmpData(xmpData_);
238                         //Exiv2 sidecar converts xmp to exif and iptc, we don't want it.
239                         sidecarData_->image()->clearExifData();
240                         sidecarData_->image()->clearIptcData();
241                         sidecarData_->image()->writeMetadata();
242                         }
243                 else
244                         {
245                         imageData_->image()->setExifData(exifData_);
246                         imageData_->image()->setIptcData(iptcData_);
247                         imageData_->image()->setXmpData(xmpData_);
248                         imageData_->image()->writeMetadata();
249                         }
250         }
251         
252         virtual Exiv2::Image *image()
253         {
254                 return imageData_->image();
255         }
256         
257         virtual Exiv2::ExifData &exifData ()
258         {
259                 return exifData_;
260         }
261
262         virtual Exiv2::IptcData &iptcData ()
263         {
264                 return iptcData_;
265         }
266
267 #if EXIV2_TEST_VERSION(0,16,0)
268         virtual Exiv2::XmpData &xmpData ()
269         {
270                 return xmpData_;
271         }
272 #endif
273
274         virtual void add_jpeg_color_profile(unsigned char *cp_data, guint cp_length)
275         {
276                 imageData_->add_jpeg_color_profile(cp_data, cp_length);
277         }
278
279         virtual guchar *get_jpeg_color_profile(guint *data_len)
280         {
281                 return imageData_->get_jpeg_color_profile(data_len);
282         }
283 };
284
285
286
287
288 extern "C" {
289
290 ExifData *exif_read(gchar *path, gchar *sidecar_path)
291 {
292         DEBUG_1("exif read %s, sidecar: %s", path, sidecar_path ? sidecar_path : "-");
293         try {
294                 return new _ExifDataProcessed(path, sidecar_path);
295         }
296         catch (Exiv2::AnyError& e) {
297                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
298                 return NULL;
299         }
300         
301 }
302
303 int exif_write(ExifData *exif)
304 {
305         try {
306                 exif->writeMetadata();
307                 return 1;
308         }
309         catch (Exiv2::AnyError& e) {
310                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
311                 return 0;
312         }
313         
314 }
315
316
317 void exif_free(ExifData *exif)
318 {
319         g_assert(dynamic_cast<_ExifDataProcessed *>(exif)); // this should not be called on ExifDataOriginal
320         delete exif;
321 }
322
323 ExifData *exif_get_original(ExifData *exif)
324 {
325         return exif->original();
326 }
327
328
329 ExifItem *exif_get_item(ExifData *exif, const gchar *key)
330 {
331         try {
332                 Exiv2::Metadatum *item = NULL;
333                 try {
334                         Exiv2::ExifKey ekey(key);
335                         Exiv2::ExifData::iterator pos = exif->exifData().findKey(ekey);
336                         if (pos == exif->exifData().end()) return NULL;
337                         item = &*pos;
338                 }
339                 catch (Exiv2::AnyError& e) {
340                         try {
341                                 Exiv2::IptcKey ekey(key);
342                                 Exiv2::IptcData::iterator pos = exif->iptcData().findKey(ekey);
343                                 if (pos == exif->iptcData().end()) return NULL;
344                                 item = &*pos;
345                         }
346                         catch (Exiv2::AnyError& e) {
347 #if EXIV2_TEST_VERSION(0,16,0)
348                                 Exiv2::XmpKey ekey(key);
349                                 Exiv2::XmpData::iterator pos = exif->xmpData().findKey(ekey);
350                                 if (pos == exif->xmpData().end()) return NULL;
351                                 item = &*pos;
352 #endif
353                         }
354                 }
355                 return (ExifItem *)item;
356         }
357         catch (Exiv2::AnyError& e) {
358                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
359                 return NULL;
360         }
361 }
362
363 ExifItem *exif_add_item(ExifData *exif, const gchar *key)
364 {
365         try {
366                 Exiv2::Metadatum *item = NULL;
367                 try {
368                         Exiv2::ExifKey ekey(key);
369                         exif->exifData().add(ekey, NULL);
370                         Exiv2::ExifData::iterator pos = exif->exifData().end(); // a hack, there should be a better way to get the currently added item
371                         pos--;
372                         item = &*pos;
373                 }
374                 catch (Exiv2::AnyError& e) {
375                         try {
376                                 Exiv2::IptcKey ekey(key);
377                                 exif->iptcData().add(ekey, NULL);
378                                 Exiv2::IptcData::iterator pos = exif->iptcData().end();
379                                 pos--;
380                                 item = &*pos;
381                         }
382                         catch (Exiv2::AnyError& e) {
383 #if EXIV2_TEST_VERSION(0,16,0)
384                                 Exiv2::XmpKey ekey(key);
385                                 exif->xmpData().add(ekey, NULL);
386                                 Exiv2::XmpData::iterator pos = exif->xmpData().end();
387                                 pos--;
388                                 item = &*pos;
389 #endif
390                         }
391                 }
392                 return (ExifItem *)item;
393         }
394         catch (Exiv2::AnyError& e) {
395                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
396                 return NULL;
397         }
398 }
399
400
401 ExifItem *exif_get_first_item(ExifData *exif)
402 {
403         try {
404                 exif->exifIter = exif->exifData().begin();
405                 exif->iptcIter = exif->iptcData().begin();
406 #if EXIV2_TEST_VERSION(0,16,0)
407                 exif->xmpIter = exif->xmpData().begin();
408 #endif
409                 if (exif->exifIter != exif->exifData().end())
410                         {
411                         const Exiv2::Metadatum *item = &*exif->exifIter;
412                         exif->exifIter++;
413                         return (ExifItem *)item;
414                         }
415                 if (exif->iptcIter != exif->iptcData().end())
416                         {
417                         const Exiv2::Metadatum *item = &*exif->iptcIter;
418                         exif->iptcIter++;
419                         return (ExifItem *)item;
420                         }
421 #if EXIV2_TEST_VERSION(0,16,0)
422                 if (exif->xmpIter != exif->xmpData().end())
423                         {
424                         const Exiv2::Metadatum *item = &*exif->xmpIter;
425                         exif->xmpIter++;
426                         return (ExifItem *)item;
427                         }
428 #endif
429                 return NULL;
430                         
431         }
432         catch (Exiv2::AnyError& e) {
433                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
434                 return NULL;
435         }
436 }
437
438 ExifItem *exif_get_next_item(ExifData *exif)
439 {
440         try {
441                 if (exif->exifIter != exif->exifData().end())
442                         {
443                         const Exiv2::Metadatum *item = &*exif->exifIter;
444                         exif->exifIter++;
445                         return (ExifItem *)item;
446                 }
447                 if (exif->iptcIter != exif->iptcData().end())
448                         {
449                         const Exiv2::Metadatum *item = &*exif->iptcIter;
450                         exif->iptcIter++;
451                         return (ExifItem *)item;
452                 }
453 #if EXIV2_TEST_VERSION(0,16,0)
454                 if (exif->xmpIter != exif->xmpData().end())
455                         {
456                         const Exiv2::Metadatum *item = &*exif->xmpIter;
457                         exif->xmpIter++;
458                         return (ExifItem *)item;
459                 }
460 #endif
461                 return NULL;
462         }
463         catch (Exiv2::AnyError& e) {
464                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
465                 return NULL;
466         }
467 }
468
469 char *exif_item_get_tag_name(ExifItem *item)
470 {
471         try {
472                 if (!item) return NULL;
473                 return g_strdup(((Exiv2::Metadatum *)item)->key().c_str());
474         }
475         catch (Exiv2::AnyError& e) {
476                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
477                 return NULL;
478         }
479 }
480
481 guint exif_item_get_tag_id(ExifItem *item)
482 {
483         try {
484                 if (!item) return 0;
485                 return ((Exiv2::Metadatum *)item)->tag();
486         }
487         catch (Exiv2::AnyError& e) {
488                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
489                 return 0;
490         }
491 }
492
493 guint exif_item_get_elements(ExifItem *item)
494 {
495         try {
496                 if (!item) return 0;
497                 return ((Exiv2::Metadatum *)item)->count();
498         }
499         catch (Exiv2::AnyError& e) {
500                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
501                 return 0;
502         }
503 }
504
505 char *exif_item_get_data(ExifItem *item, guint *data_len)
506 {
507         try {
508                 if (!item) return 0;
509                 Exiv2::Metadatum *md = (Exiv2::Metadatum *)item;
510                 if (data_len) *data_len = md->size();
511                 char *data = (char *)g_malloc(md->size());
512                 long res = md->copy((Exiv2::byte *)data, Exiv2::littleEndian /* should not matter */);
513                 g_assert(res == md->size());
514                 return data;
515         }
516         catch (Exiv2::AnyError& e) {
517                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
518                 return NULL;
519         }
520 }
521
522 char *exif_item_get_description(ExifItem *item)
523 {
524         try {
525                 if (!item) return NULL;
526                 return g_locale_to_utf8(((Exiv2::Metadatum *)item)->tagLabel().c_str(), -1, NULL, NULL, NULL);
527         }
528         catch (std::exception& e) {
529 //              std::cout << "Caught Exiv2 exception '" << e << "'\n";
530                 return NULL;
531         }
532 }
533
534 /*
535 invalidTypeId, unsignedByte, asciiString, unsignedShort,
536   unsignedLong, unsignedRational, signedByte, undefined,
537   signedShort, signedLong, signedRational, string,
538   date, time, comment, directory,
539   xmpText, xmpAlt, xmpBag, xmpSeq,
540   langAlt, lastTypeId
541 */
542
543 static guint format_id_trans_tbl [] = {
544         EXIF_FORMAT_UNKNOWN,
545         EXIF_FORMAT_BYTE_UNSIGNED,
546         EXIF_FORMAT_STRING,
547         EXIF_FORMAT_SHORT_UNSIGNED,
548         EXIF_FORMAT_LONG_UNSIGNED,
549         EXIF_FORMAT_RATIONAL_UNSIGNED,
550         EXIF_FORMAT_BYTE,
551         EXIF_FORMAT_UNDEFINED,
552         EXIF_FORMAT_SHORT,
553         EXIF_FORMAT_LONG,
554         EXIF_FORMAT_RATIONAL,
555         EXIF_FORMAT_STRING,
556         EXIF_FORMAT_STRING,
557         EXIF_FORMAT_STRING,
558         EXIF_FORMAT_UNDEFINED,
559         EXIF_FORMAT_STRING,
560         EXIF_FORMAT_STRING,
561         EXIF_FORMAT_STRING,
562         EXIF_FORMAT_STRING
563         };
564         
565         
566
567 guint exif_item_get_format_id(ExifItem *item)
568 {
569         try {
570                 if (!item) return EXIF_FORMAT_UNKNOWN;
571                 guint id = ((Exiv2::Metadatum *)item)->typeId();
572                 if (id >= (sizeof(format_id_trans_tbl) / sizeof(format_id_trans_tbl[0])) ) return EXIF_FORMAT_UNKNOWN;
573                 return format_id_trans_tbl[id];
574         }
575         catch (Exiv2::AnyError& e) {
576                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
577                 return EXIF_FORMAT_UNKNOWN;
578         }
579 }
580
581 const char *exif_item_get_format_name(ExifItem *item, gint brief)
582 {
583         try {
584                 if (!item) return NULL;
585                 return ((Exiv2::Metadatum *)item)->typeName();
586         }
587         catch (Exiv2::AnyError& e) {
588                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
589                 return NULL;
590         }
591 }
592
593
594 gchar *exif_item_get_data_as_text(ExifItem *item)
595 {
596         try {
597                 if (!item) return NULL;
598                 Exiv2::Metadatum *metadatum = (Exiv2::Metadatum *)item;
599 #if EXIV2_TEST_VERSION(0,17,0)
600                 return g_locale_to_utf8(metadatum->print().c_str(), -1, NULL, NULL, NULL);
601 #else
602                 std::stringstream str;
603                 Exiv2::Exifdatum *exifdatum;
604                 Exiv2::Iptcdatum *iptcdatum;
605 #if EXIV2_TEST_VERSION(0,16,0)
606                 Exiv2::Xmpdatum *xmpdatum;
607 #endif
608                 if ((exifdatum = dynamic_cast<Exiv2::Exifdatum *>(metadatum)))
609                         str << *exifdatum;
610                 else if ((iptcdatum = dynamic_cast<Exiv2::Iptcdatum *>(metadatum)))
611                         str << *iptcdatum;
612 #if EXIV2_TEST_VERSION(0,16,0)
613                 else if ((xmpdatum = dynamic_cast<Exiv2::Xmpdatum *>(metadatum)))
614                         str << *xmpdatum;
615 #endif
616
617                 return g_locale_to_utf8(str.str().c_str(), -1, NULL, NULL, NULL);
618 #endif
619         }
620         catch (Exiv2::AnyError& e) {
621                 return NULL;
622         }
623 }
624
625 gchar *exif_item_get_string(ExifItem *item, int idx)
626 {
627         try {
628                 if (!item) return NULL;
629                 Exiv2::Metadatum *em = (Exiv2::Metadatum *)item;
630 #if EXIV2_TEST_VERSION(0,16,0)
631                 std::string str = em->toString(idx);
632 #else
633                 std::string str = em->toString(); // FIXME
634 #endif
635                 if (idx == 0 && str == "") str = em->toString();
636                 if (str.length() > 5 && str.substr(0, 5) == "lang=")
637                         {
638                         std::string::size_type pos = str.find_first_of(' ');
639                         if (pos != std::string::npos) str = str.substr(pos+1);
640                         }
641
642 //              return g_locale_to_utf8(str.c_str(), -1, NULL, NULL, NULL); // FIXME
643                 return g_strdup(str.c_str());
644         }
645         catch (Exiv2::AnyError& e) {
646                 return NULL;
647         }
648 }
649
650
651 gint exif_item_get_integer(ExifItem *item, gint *value)
652 {
653         try {
654                 if (!item) return 0;
655                 *value = ((Exiv2::Metadatum *)item)->toLong();
656                 return 1;
657         }
658         catch (Exiv2::AnyError& e) {
659                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
660                 return 0;
661         }
662 }
663
664 ExifRational *exif_item_get_rational(ExifItem *item, gint *sign, guint n)
665 {
666         try {
667                 if (!item) return NULL;
668                 if (n >= exif_item_get_elements(item)) return NULL;
669                 Exiv2::Rational v = ((Exiv2::Metadatum *)item)->toRational(n);
670                 static ExifRational ret;
671                 ret.num = v.first;
672                 ret.den = v.second;
673                 if (sign) *sign = (((Exiv2::Metadatum *)item)->typeId() == Exiv2::signedRational);
674                 return &ret;
675         }
676         catch (Exiv2::AnyError& e) {
677                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
678                 return NULL;
679         }
680 }
681
682 gchar *exif_get_tag_description_by_key(const gchar *key)
683 {
684         try {
685                 Exiv2::ExifKey ekey(key);
686                 return g_locale_to_utf8(Exiv2::ExifTags::tagLabel(ekey.tag(), ekey.ifdId ()), -1, NULL, NULL, NULL);
687         }
688         catch (Exiv2::AnyError& e) {
689                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
690                 return NULL;
691         }
692 }
693
694 int exif_item_set_string(ExifItem *item, const char *str)
695 {
696         try {
697                 if (!item) return 0;
698                 ((Exiv2::Metadatum *)item)->setValue(std::string(str));
699                 return 1;
700                 }
701         catch (Exiv2::AnyError& e) {
702                 return 0;
703         }
704 }
705
706 int exif_item_delete(ExifData *exif, ExifItem *item)
707 {
708         try {
709                 if (!item) return 0;
710                 for (Exiv2::ExifData::iterator i = exif->exifData().begin(); i != exif->exifData().end(); ++i) {
711                         if (((Exiv2::Metadatum *)item) == &*i) {
712                                 i = exif->exifData().erase(i);
713                                 return 1;
714                         }
715                 }
716                 for (Exiv2::IptcData::iterator i = exif->iptcData().begin(); i != exif->iptcData().end(); ++i) {
717                         if (((Exiv2::Metadatum *)item) == &*i) {
718                                 i = exif->iptcData().erase(i);
719                                 return 1;
720                         }
721                 }
722 #if EXIV2_TEST_VERSION(0,16,0)
723                 for (Exiv2::XmpData::iterator i = exif->xmpData().begin(); i != exif->xmpData().end(); ++i) {
724                         if (((Exiv2::Metadatum *)item) == &*i) {
725                                 i = exif->xmpData().erase(i);
726                                 return 1;
727                         }
728                 }
729 #endif
730                 return 0;
731         }
732         catch (Exiv2::AnyError& e) {
733                 return 0;
734         }
735 }
736
737 void exif_add_jpeg_color_profile(ExifData *exif, unsigned char *cp_data, guint cp_length)
738 {
739         exif->add_jpeg_color_profile(cp_data, cp_length);
740 }
741
742 guchar *exif_get_color_profile(ExifData *exif, guint *data_len)
743 {
744         guchar *ret = exif->get_jpeg_color_profile(data_len);
745         if (ret) return ret;
746
747         ExifItem *prof_item = exif_get_item(exif, "Exif.Image.InterColorProfile");
748         if (prof_item && exif_item_get_format_id(prof_item) == EXIF_FORMAT_UNDEFINED)
749                 ret = (guchar *)exif_item_get_data(prof_item, data_len);
750         return ret;
751 }
752
753 #if EXIV2_TEST_VERSION(0,17,90)
754
755 guchar *exif_get_preview(ExifData *exif, guint *data_len, gint requested_width, gint requested_height)
756 {
757         if (!exif) return NULL;
758
759         const char* path = exif->image()->io().path().c_str();
760         /* given image pathname, first do simple (and fast) file extension test */
761         gboolean is_raw = filter_file_class(path, FORMAT_CLASS_RAWIMAGE);
762         
763         if (!is_raw && requested_width == 0) return NULL;
764
765         try {
766
767                 Exiv2::PreviewManager pm(*exif->image());
768
769                 Exiv2::PreviewPropertiesList list = pm.getPreviewProperties();
770
771                 if (!list.empty())
772                         {
773                         Exiv2::PreviewPropertiesList::iterator pos;
774                         Exiv2::PreviewPropertiesList::iterator last = --list.end();
775                         
776                         if (requested_width == 0)
777                                 {
778                                 pos = last; // the largest
779                                 }
780                         else
781                                 {
782                                 pos = list.begin();
783                                 while (pos != last)
784                                         {
785                                         if (pos->width_ >= (uint32_t)requested_width &&
786                                             pos->height_ >= (uint32_t)requested_height) break;
787                                         ++pos;
788                                         }
789                                 
790                                 // we are not interested in smaller thumbnails in normal image formats - we can use full image instead
791                                 if (!is_raw) 
792                                         {
793                                         if (pos->width_ < (uint32_t)requested_width || pos->height_ < (uint32_t)requested_height) return NULL; 
794                                         }
795                                 }
796
797                         Exiv2::PreviewImage image = pm.getPreviewImage(*pos);
798
799                         Exiv2::DataBuf buf = image.copy();
800                         std::pair<Exiv2::byte*, long> p = buf.release();
801
802                         *data_len = p.second;
803                         return p.first;
804                         }
805                 return NULL;
806         }
807         catch (Exiv2::AnyError& e) {
808                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
809                 return NULL;
810         }
811 }
812
813 void exif_free_preview(guchar *buf)
814 {
815         delete[] (Exiv2::byte*)buf;
816 }
817 #endif
818
819 }
820 #if !EXIV2_TEST_VERSION(0,17,90)
821
822 /* This is a dirty hack to support raw file preview, bassed on
823 tiffparse.cpp from Exiv2 examples */
824
825 class RawFile {
826         public:
827
828         RawFile(Exiv2::BasicIo &io);
829         ~RawFile();
830
831         const Exiv2::Value *find(uint16_t tag, uint16_t group);
832
833         unsigned long preview_offset();
834
835         private:
836         int type;
837         Exiv2::TiffComponent::AutoPtr rootDir;
838         Exiv2::BasicIo &io_;
839         const Exiv2::byte *map_data;
840         size_t map_len;
841         unsigned long offset;
842 };
843
844 typedef struct _UnmapData UnmapData;
845 struct _UnmapData
846 {
847         guchar *ptr;
848         guchar *map_data;
849         size_t map_len;
850 };
851
852 static GList *exif_unmap_list = 0;
853
854 extern "C" guchar *exif_get_preview(ExifData *exif, guint *data_len, gint requested_width, gint requested_height)
855 {
856         unsigned long offset;
857
858         if (!exif) return NULL;
859         const char* path = exif->image()->io().path().c_str();
860
861         /* given image pathname, first do simple (and fast) file extension test */
862         if (!filter_file_class(path, FORMAT_CLASS_RAWIMAGE)) return NULL;
863
864         try {
865                 struct stat st;
866                 guchar *map_data;
867                 size_t map_len;
868                 UnmapData *ud;
869                 int fd;
870                 
871                 RawFile rf(exif->image()->io());
872                 offset = rf.preview_offset();
873                 DEBUG_1("%s: offset %lu", path, offset);
874                 
875                 fd = open(path, O_RDONLY);
876                 if (fd == -1)
877                         {
878                         return NULL;
879                         }
880
881                 if (fstat(fd, &st) == -1)
882                         {
883                         close(fd);
884                         return NULL;
885                         }
886                 map_len = st.st_size;
887                 map_data = (guchar *) mmap(0, map_len, PROT_READ, MAP_PRIVATE, fd, 0);
888                 close(fd);
889                 if (map_data == MAP_FAILED)
890                         {
891                         return NULL;
892                         }
893                 *data_len = map_len - offset;
894                 ud = g_new(UnmapData, 1);
895                 ud->ptr = map_data + offset;
896                 ud->map_data = map_data;
897                 ud->map_len = map_len;
898                 
899                 exif_unmap_list = g_list_prepend(exif_unmap_list, ud);
900                 return ud->ptr;
901                 
902         }
903         catch (Exiv2::AnyError& e) {
904                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
905         }
906         return NULL;
907
908 }
909
910 void exif_free_preview(guchar *buf)
911 {
912         GList *work = exif_unmap_list;
913         
914         while (work)
915                 {
916                 UnmapData *ud = (UnmapData *)work->data;
917                 if (ud->ptr == buf)
918                         {
919                         munmap(ud->map_data, ud->map_len);
920                         exif_unmap_list = g_list_remove_link(exif_unmap_list, work);
921                         g_free(ud);
922                         return;
923                         }
924                 work = work->next;
925                 }
926         g_assert_not_reached();
927 }
928
929 using namespace Exiv2;
930
931 RawFile::RawFile(BasicIo &io) : io_(io), map_data(NULL), map_len(0), offset(0)
932 {
933 /*
934         struct stat st;
935         if (fstat(fd, &st) == -1)
936                 {
937                 throw Error(14);
938                 }
939         map_len = st.st_size;
940         map_data = (Exiv2::byte *) mmap(0, map_len, PROT_READ, MAP_PRIVATE, fd, 0);
941         if (map_data == MAP_FAILED)
942                 {
943                 throw Error(14);
944                 }
945 */
946         if (io.open() != 0) {
947             throw Error(9, io.path(), strError());
948         }
949         
950         map_data = io.mmap();
951         map_len = io.size();
952
953
954         type = Exiv2::ImageFactory::getType(map_data, map_len);
955
956 #if EXIV2_TEST_VERSION(0,16,0)
957         TiffHeaderBase *tiffHeader = NULL;
958 #else
959         TiffHeade2 *tiffHeader = NULL;
960 #endif
961         Cr2Header *cr2Header = NULL;
962
963         switch (type) {
964                 case Exiv2::ImageType::tiff:
965                         tiffHeader = new TiffHeade2();
966                         break;
967                 case Exiv2::ImageType::cr2:
968                         cr2Header = new Cr2Header();
969                         break;
970 #if EXIV2_TEST_VERSION(0,16,0)
971                 case Exiv2::ImageType::orf:
972                         tiffHeader = new OrfHeader();
973                         break;
974 #endif
975 #if EXIV2_TEST_VERSION(0,13,0)
976                 case Exiv2::ImageType::raf:
977                         if (map_len < 84 + 4) throw Error(14);
978                         offset = getULong(map_data + 84, bigEndian);
979                         return;
980 #endif
981                 case Exiv2::ImageType::crw:
982                         {
983                         // Parse the image, starting with a CIFF header component
984                         Exiv2::CiffHeader::AutoPtr parseTree(new Exiv2::CiffHeader);
985                         parseTree->read(map_data, map_len);
986                         CiffComponent *entry = parseTree->findComponent(0x2007, 0);
987                         if (entry) offset =  entry->pData() - map_data;
988                         return;
989                         }
990
991                 default:
992                         throw Error(3, "RAW");
993         }
994
995         // process tiff-like formats
996
997         TiffCompFactoryFct createFct = TiffCreator::create;
998
999         rootDir = createFct(Tag::root, Group::none);
1000         if (0 == rootDir.get()) {
1001                 throw Error(1, "No root element defined in TIFF structure");
1002         }
1003         
1004         if (tiffHeader)
1005                 {
1006                 if (!tiffHeader->read(map_data, map_len)) throw Error(3, "TIFF");
1007 #if EXIV2_TEST_VERSION(0,16,0)
1008                 rootDir->setStart(map_data + tiffHeader->offset());
1009 #else
1010                 rootDir->setStart(map_data + tiffHeader->ifdOffset());
1011 #endif
1012                 }
1013                 
1014         if (cr2Header)
1015                 {
1016                 rootDir->setStart(map_data + cr2Header->offset());
1017                 }
1018         
1019         TiffRwState::AutoPtr state(new TiffRwState(tiffHeader ? tiffHeader->byteOrder() : littleEndian, 0, createFct));
1020
1021         TiffReader reader(map_data,
1022                           map_len,
1023                           rootDir.get(),
1024                           state);
1025
1026         rootDir->accept(reader);
1027         
1028         if (tiffHeader)
1029                 delete tiffHeader;
1030         if (cr2Header)
1031                 delete cr2Header;
1032 }
1033
1034 RawFile::~RawFile(void)
1035 {
1036         io_.munmap();
1037         io_.close();
1038 }
1039
1040 const Value * RawFile::find(uint16_t tag, uint16_t group)
1041 {
1042         TiffFinder finder(tag, group);
1043         rootDir->accept(finder);
1044         TiffEntryBase* te = dynamic_cast<TiffEntryBase*>(finder.result());
1045         if (te)
1046                 {
1047                 DEBUG_1("(tag: %04x %04x) ", tag, group);
1048                 return te->pValue();
1049                 }
1050         else
1051                 return NULL;
1052 }
1053
1054 unsigned long RawFile::preview_offset(void)
1055 {
1056         const Value *val;
1057         if (offset) return offset;
1058         
1059         if (type == Exiv2::ImageType::cr2)
1060                 {
1061                 val = find(0x111, Group::ifd0);
1062                 if (val) return val->toLong();
1063
1064                 return 0;
1065                 }
1066         
1067         val = find(0x201, Group::sub0_0);
1068         if (val) return val->toLong();
1069
1070         val = find(0x201, Group::ifd0);
1071         if (val) return val->toLong();
1072
1073         val = find(0x201, Group::ignr); // for PEF files, originally it was probably ifd2
1074         if (val) return val->toLong();
1075
1076         val = find(0x111, Group::sub0_1); // dng
1077         if (val) return val->toLong();
1078
1079         return 0;
1080 }
1081
1082
1083 #endif
1084
1085
1086 #endif
1087 /* HAVE_EXIV2 */
1088 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */