summaryrefslogtreecommitdiff
path: root/libopie2
authoralwin <alwin>2004-11-01 19:52:25 (UTC)
committer alwin <alwin>2004-11-01 19:52:25 (UTC)
commit2dc92f4bfe9e81edc2b0b24ecacf3bc44b344984 (patch) (unidiff)
treeb1d5661ea64931b6eeb2030ceb22532cef699a75 /libopie2
parent5a41dcd5901badbd2e258b0a916fb012b6351eeb (diff)
downloadopie-2dc92f4bfe9e81edc2b0b24ecacf3bc44b344984.zip
opie-2dc92f4bfe9e81edc2b0b24ecacf3bc44b344984.tar.gz
opie-2dc92f4bfe9e81edc2b0b24ecacf3bc44b344984.tar.bz2
exif class added (taken from opie-eye_slave and more c++ like reworked)
it is used in oimageview while loading jpegs checking BEFORE loading if the image should be scaled loaded. ToDo: documentation of the interface, remove it from opie-eye_slave (it should use of course the library version)
Diffstat (limited to 'libopie2') (more/less context) (ignore whitespace changes)
-rw-r--r--libopie2/opiemm/oimagescrollview.cpp14
-rw-r--r--libopie2/opiemm/opieexif.cpp1195
-rw-r--r--libopie2/opiemm/opieexif.h139
-rw-r--r--libopie2/opiemm/opiemm.pro4
4 files changed, 1346 insertions, 6 deletions
diff --git a/libopie2/opiemm/oimagescrollview.cpp b/libopie2/opiemm/oimagescrollview.cpp
index 56be10b..37a1ad5 100644
--- a/libopie2/opiemm/oimagescrollview.cpp
+++ b/libopie2/opiemm/oimagescrollview.cpp
@@ -4,6 +4,7 @@
4#include <opie2/odebug.h> 4#include <opie2/odebug.h>
5#include <opie2/oapplication.h> 5#include <opie2/oapplication.h>
6#include <opie2/owait.h> 6#include <opie2/owait.h>
7#include <opie2/opieexif.h>
7 8
8#include <qimage.h> 9#include <qimage.h>
9#include <qlayout.h> 10#include <qlayout.h>
@@ -87,6 +88,8 @@ void OImageScrollView::loadJpeg(bool interncall)
87 bool real_load = false; 88 bool real_load = false;
88 if (AutoScale()) { 89 if (AutoScale()) {
89 if (!interncall) { 90 if (!interncall) {
91 ExifData xf;
92 bool scanned = xf.scan(m_lastName);
90 int wid, hei; 93 int wid, hei;
91 wid = QApplication::desktop()->width(); 94 wid = QApplication::desktop()->width();
92 hei = QApplication::desktop()->height(); 95 hei = QApplication::desktop()->height();
@@ -95,10 +98,13 @@ void OImageScrollView::loadJpeg(bool interncall)
95 } else { 98 } else {
96 hei = wid; 99 hei = wid;
97 } 100 }
98 param = QString( "Fast Shrink( 3 ) Scale( %1, %2, ScaleMin)" ).arg( wid ).arg( hei ); 101 if ( (scanned && (wid<xf.getWidth()||hei<xf.getHeight()))||!scanned ) {
99 odebug << "Load jpeg scaled \"" << param << "\"" << oendl; 102 param = QString( "Fast Shrink( 3 ) Scale( %1, %2, ScaleMin)" ).arg( wid ).arg( hei );
100 iio.setParameters(param.latin1()); 103 odebug << "Load jpeg scaled \"" << param << "\"" << oendl;
101 setImageScaledLoaded(true); 104 iio.setParameters(param.latin1());
105 setImageScaledLoaded(true);
106 }
107
102 real_load = true; 108 real_load = true;
103 } 109 }
104 } else { 110 } else {
diff --git a/libopie2/opiemm/opieexif.cpp b/libopie2/opiemm/opieexif.cpp
new file mode 100644
index 0000000..0860ea8
--- a/dev/null
+++ b/libopie2/opiemm/opieexif.cpp
@@ -0,0 +1,1195 @@
1#include "opieexif.h"
2
3/* OPIE */
4#include <opie2/odebug.h>
5#include <qpe/timestring.h>
6/* QT */
7#include <qobject.h>
8#include <qimage.h>
9
10/**
11 exif.h
12*/
13
14#include <stdio.h>
15#include <stdlib.h>
16#include <math.h>
17#include <time.h>
18
19#include <qstring.h>
20#include <qfile.h>
21#include <qimage.h>
22
23//static int HaveAll;
24
25//--------------------------------------------------------------------------
26// Table of Jpeg encoding process names
27
28#define M_SOF0 0xC0 // Start Of Frame N
29#define M_SOF1 0xC1 // N indicates which compression process
30#define M_SOF2 0xC2 // Only SOF0-SOF2 are now in common use
31#define M_SOF3 0xC3
32#define M_SOF5 0xC5 // NB: codes C4 and CC are NOT SOF markers
33#define M_SOF6 0xC6
34#define M_SOF7 0xC7
35#define M_SOF9 0xC9
36#define M_SOF10 0xCA
37#define M_SOF11 0xCB
38#define M_SOF13 0xCD
39#define M_SOF14 0xCE
40#define M_SOF15 0xCF
41#define M_SOI 0xD8 // Start Of Image (beginning of datastream)
42#define M_EOI 0xD9 // End Of Image (end of datastream)
43#define M_SOS 0xDA // Start Of Scan (begins compressed data)
44#define M_JFIF 0xE0 // Jfif marker
45#define M_EXIF 0xE1 // Exif marker
46#define M_COM 0xFE // COMment
47
48
49//--------------------------------------------------------------------------
50// Describes format descriptor
51static int BytesPerFormat[] = {0,1,1,2,4,8,1,1,2,4,8,4,8};
52#define NUM_FORMATS 12
53
54#define FMT_BYTE 1
55#define FMT_STRING 2
56#define FMT_USHORT 3
57#define FMT_ULONG 4
58#define FMT_URATIONAL 5
59#define FMT_SBYTE 6
60#define FMT_UNDEFINED 7
61#define FMT_SSHORT 8
62#define FMT_SLONG 9
63#define FMT_SRATIONAL 10
64#define FMT_SINGLE 11
65#define FMT_DOUBLE 12
66
67//--------------------------------------------------------------------------
68// Describes tag values
69
70#define TAG_EXIF_OFFSET 0x8769
71#define TAG_INTEROP_OFFSET 0xa005
72
73#define TAG_MAKE 0x010F
74#define TAG_MODEL 0x0110
75#define TAG_ORIENTATION 0x0112
76
77#define TAG_EXPOSURETIME 0x829A
78#define TAG_FNUMBER 0x829D
79
80#define TAG_SHUTTERSPEED 0x9201
81#define TAG_APERTURE 0x9202
82#define TAG_MAXAPERTURE 0x9205
83#define TAG_FOCALLENGTH 0x920A
84
85#define TAG_DATETIME_ORIGINAL 0x9003
86#define TAG_USERCOMMENT 0x9286
87
88#define TAG_SUBJECT_DISTANCE 0x9206
89#define TAG_FLASH 0x9209
90
91#define TAG_FOCALPLANEXRES 0xa20E
92#define TAG_FOCALPLANEUNITS 0xa210
93#define TAG_EXIF_IMAGEWIDTH 0xA002
94#define TAG_EXIF_IMAGELENGTH 0xA003
95
96// the following is added 05-jan-2001 vcs
97#define TAG_EXPOSURE_BIAS 0x9204
98#define TAG_WHITEBALANCE 0x9208
99#define TAG_METERING_MODE 0x9207
100#define TAG_EXPOSURE_PROGRAM 0x8822
101#define TAG_ISO_EQUIVALENT 0x8827
102#define TAG_COMPRESSION_LEVEL 0x9102
103
104#define TAG_THUMBNAIL_OFFSET 0x0201
105#define TAG_THUMBNAIL_LENGTH 0x0202
106
107
108
109namespace Opie {
110
111namespace MM {
112
113class FatalError {
114 const char* ex;
115public:
116 FatalError(const char* s) { ex = s; }
117 void debug_print() const { owarn << "exception: " << ex << "" << oendl; }
118};
119
120ExifData::TagTable_t ProcessTable[] = {
121 { M_SOF0, "Baseline"},
122 { M_SOF1, "Extended sequential"},
123 { M_SOF2, "Progressive"},
124 { M_SOF3, "Lossless"},
125 { M_SOF5, "Differential sequential"},
126 { M_SOF6, "Differential progressive"},
127 { M_SOF7, "Differential lossless"},
128 { M_SOF9, "Extended sequential, arithmetic coding"},
129 { M_SOF10, "Progressive, arithmetic coding"},
130 { M_SOF11, "Lossless, arithmetic coding"},
131 { M_SOF13, "Differential sequential, arithmetic coding"},
132 { M_SOF14, "Differential progressive, arithmetic coding"},
133 { M_SOF15, "Differential lossless, arithmetic coding"},
134 { 0, "Unknown"}
135};
136
137//--------------------------------------------------------------------------
138// Parse the marker stream until SOS or EOI is seen;
139//--------------------------------------------------------------------------
140int ExifData::ReadJpegSections (QFile & infile, ReadMode_t ReadMode)
141{
142 int a;
143
144 a = infile.getch();
145
146 if (a != 0xff || infile.getch() != M_SOI) {
147 SectionsRead = 0;
148 return false;
149 }
150 for(SectionsRead = 0; SectionsRead < MAX_SECTIONS-1; ){
151 int marker = 0;
152 int got;
153 unsigned int ll,lh;
154 unsigned int itemlen;
155 uchar * Data;
156
157 for (a=0;a<7;a++){
158 marker = infile.getch();
159 if (marker != 0xff) break;
160
161 if (a >= 6){
162
163 owarn << "too many padding bytes" << oendl;
164 return false;
165
166 }
167 }
168
169 if (marker == 0xff){
170 // 0xff is legal padding, but if we get that many, something's wrong.
171 return false;
172 }
173
174 Sections[SectionsRead].Type = marker;
175
176 // Read the length of the section.
177 lh = (uchar) infile.getch();
178 ll = (uchar) infile.getch();
179
180 itemlen = (lh << 8) | ll;
181
182 if (itemlen < 2) {
183 return false;;
184 }
185
186 Sections[SectionsRead].Size = itemlen;
187
188 Data = (uchar *)malloc(itemlen+1); // Add 1 to allow sticking a 0 at the end.
189 Sections[SectionsRead].Data = Data;
190
191 // Store first two pre-read bytes.
192 Data[0] = (uchar)lh;
193 Data[1] = (uchar)ll;
194
195 got = infile.readBlock((char*)Data+2, itemlen-2); // Read the whole section.
196 if (( unsigned ) got != itemlen-2){
197 return false;
198 }
199 SectionsRead++;
200
201 switch(marker){
202
203 case M_SOS: // stop before hitting compressed data
204 // If reading entire image is requested, read the rest of the data.
205 if (ReadMode & READ_IMAGE){
206 unsigned long size;
207
208 size = QMAX( 0ul, infile.size()-infile.at() );
209 Data = (uchar *)malloc(size);
210 if (Data == NULL){
211 return false;
212 }
213
214 got = infile.readBlock((char*)Data, size);
215 if (( unsigned ) got != size){
216 return false;
217 }
218
219 Sections[SectionsRead].Data = Data;
220 Sections[SectionsRead].Size = size;
221 Sections[SectionsRead].Type = PSEUDO_IMAGE_MARKER;
222 SectionsRead ++;
223 //HaveAll = 1;
224 }
225 return true;
226
227 case M_EOI: // in case it's a tables-only JPEG stream
228 owarn << "No image in jpeg!" << oendl;
229 return false;
230
231 case M_COM: // Comment section
232 // pieczy 2002-02-12
233 // now the User comment goes to UserComment
234 // so we can store a Comment section also in READ_EXIF mode
235 process_COM(Data, itemlen);
236 break;
237
238 case M_JFIF:
239 // Regular jpegs always have this tag, exif images have the exif
240 // marker instead, althogh ACDsee will write images with both markers.
241 // this program will re-create this marker on absence of exif marker.
242 // hence no need to keep the copy from the file.
243 free(Sections[--SectionsRead].Data);
244 break;
245
246 case M_EXIF:
247 // Seen files from some 'U-lead' software with Vivitar scanner
248 // that uses marker 31 for non exif stuff. Thus make sure
249 // it says 'Exif' in the section before treating it as exif.
250 if ((ReadMode & READ_EXIF) && memcmp(Data+2, "Exif", 4) == 0){
251 process_EXIF((uchar *)Data, itemlen);
252 }else{
253 // Discard this section.
254 free(Sections[--SectionsRead].Data);
255 }
256 break;
257
258 case M_SOF0:
259 case M_SOF1:
260 case M_SOF2:
261 case M_SOF3:
262 case M_SOF5:
263 case M_SOF6:
264 case M_SOF7:
265 case M_SOF9:
266 case M_SOF10:
267 case M_SOF11:
268 case M_SOF13:
269 case M_SOF14:
270 case M_SOF15:
271 process_SOFn(Data, marker);
272 default:
273 break;
274 break;
275 }
276 }
277 return true;
278}
279
280
281//--------------------------------------------------------------------------
282// Discard read data.
283//--------------------------------------------------------------------------
284void ExifData::DiscardData(void)
285{
286 for (int a=0; a < SectionsRead; a++)
287 free(Sections[a].Data);
288 SectionsRead = 0;
289}
290
291//--------------------------------------------------------------------------
292// Convert a 16 bit unsigned value from file's native byte order
293//--------------------------------------------------------------------------
294int ExifData::Get16u(void * Short)
295{
296 if (MotorolaOrder){
297 return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1];
298 }else{
299 return (((uchar *)Short)[1] << 8) | ((uchar *)Short)[0];
300 }
301}
302
303//--------------------------------------------------------------------------
304// Convert a 32 bit signed value from file's native byte order
305//--------------------------------------------------------------------------
306int ExifData::Get32s(void * Long)
307{
308 if (MotorolaOrder){
309 return ((( char *)Long)[0] << 24) | (((uchar *)Long)[1] << 16)
310 | (((uchar *)Long)[2] << 8 ) | (((uchar *)Long)[3] << 0 );
311 }else{
312 return ((( char *)Long)[3] << 24) | (((uchar *)Long)[2] << 16)
313 | (((uchar *)Long)[1] << 8 ) | (((uchar *)Long)[0] << 0 );
314 }
315}
316
317//--------------------------------------------------------------------------
318// Convert a 32 bit unsigned value from file's native byte order
319//--------------------------------------------------------------------------
320unsigned ExifData::Get32u(void * Long)
321{
322 return (unsigned)Get32s(Long) & 0xffffffff;
323}
324
325//--------------------------------------------------------------------------
326// Evaluate number, be it int, rational, or float from directory.
327//--------------------------------------------------------------------------
328double ExifData::ConvertAnyFormat(void * ValuePtr, int Format)
329{
330 double Value;
331 Value = 0;
332
333 switch(Format){
334 case FMT_SBYTE: Value = *(signed char *)ValuePtr; break;
335 case FMT_BYTE: Value = *(uchar *)ValuePtr; break;
336
337 case FMT_USHORT: Value = Get16u(ValuePtr); break;
338
339 case FMT_ULONG: Value = Get32u(ValuePtr); break;
340
341 case FMT_URATIONAL:
342 case FMT_SRATIONAL:
343 {
344 int Num,Den;
345 Num = Get32s(ValuePtr);
346 Den = Get32s(4+(char *)ValuePtr);
347 if (Den == 0){
348 Value = 0;
349 }else{
350 Value = (double)Num/Den;
351 }
352 break;
353 }
354
355 case FMT_SSHORT: Value = (signed short)Get16u(ValuePtr); break;
356 case FMT_SLONG: Value = Get32s(ValuePtr); break;
357
358 // Not sure if this is correct (never seen float used in Exif format)
359 case FMT_SINGLE: Value = (double)*(float *)ValuePtr; break;
360 case FMT_DOUBLE: Value = *(double *)ValuePtr; break;
361 }
362 return Value;
363}
364
365//--------------------------------------------------------------------------
366// Process one of the nested EXIF directories.
367//--------------------------------------------------------------------------
368void ExifData::ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase, unsigned ExifLength)
369{
370 int de;
371 int a;
372 int NumDirEntries;
373 unsigned ThumbnailOffset = 0;
374 unsigned ThumbnailSize = 0;
375
376 NumDirEntries = Get16u(DirStart);
377 #define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry))
378
379 {
380 unsigned char * DirEnd;
381 DirEnd = DIR_ENTRY_ADDR(DirStart, NumDirEntries);
382 if (DirEnd+4 > (OffsetBase+ExifLength)){
383 if (DirEnd+2 == OffsetBase+ExifLength || DirEnd == OffsetBase+ExifLength){
384 // Version 1.3 of jhead would truncate a bit too much.
385 // This also caught later on as well.
386 }else{
387 // Note: Files that had thumbnails trimmed with jhead 1.3 or earlier
388 // might trigger this.
389 return;
390 }
391 }
392 if (DirEnd < LastExifRefd) LastExifRefd = DirEnd;
393 }
394
395 for (de=0;de<NumDirEntries;de++){
396 int Tag, Format, Components;
397 unsigned char * ValuePtr;
398 int ByteCount;
399 char * DirEntry;
400 DirEntry = (char *)DIR_ENTRY_ADDR(DirStart, de);
401
402 Tag = Get16u(DirEntry);
403 Format = Get16u(DirEntry+2);
404 Components = Get32u(DirEntry+4);
405
406 if ((Format-1) >= NUM_FORMATS) {
407 // (-1) catches illegal zero case as unsigned underflows to positive large.
408 return;
409 }
410
411 ByteCount = Components * BytesPerFormat[Format];
412
413 if (ByteCount > 4){
414 unsigned OffsetVal;
415 OffsetVal = Get32u(DirEntry+8);
416 // If its bigger than 4 bytes, the dir entry contains an offset.
417 if (OffsetVal+ByteCount > ExifLength){
418 // Bogus pointer offset and / or bytecount value
419 //printf("Offset %d bytes %d ExifLen %d\n",OffsetVal, ByteCount, ExifLength);
420
421 return;
422 }
423 ValuePtr = OffsetBase+OffsetVal;
424 }else{
425 // 4 bytes or less and value is in the dir entry itself
426 ValuePtr = (unsigned char *)DirEntry+8;
427 }
428
429 if (LastExifRefd < ValuePtr+ByteCount){
430 // Keep track of last byte in the exif header that was actually referenced.
431 // That way, we know where the discardable thumbnail data begins.
432 LastExifRefd = ValuePtr+ByteCount;
433 }
434
435 // Extract useful components of tag
436 switch(Tag){
437
438 case TAG_MAKE:
439 ExifData::CameraMake = QString((char*)ValuePtr);
440 break;
441
442 case TAG_MODEL:
443 ExifData::CameraModel = QString((char*)ValuePtr);
444 break;
445
446 case TAG_ORIENTATION:
447 Orientation = (int)ConvertAnyFormat(ValuePtr, Format);
448 break;
449
450 case TAG_DATETIME_ORIGINAL:
451 DateTime = QString((char*)ValuePtr);
452 break;
453
454 case TAG_USERCOMMENT:
455 // Olympus has this padded with trailing spaces. Remove these first.
456 for (a=ByteCount;;){
457 a--;
458 if ((ValuePtr)[a] == ' '){
459 (ValuePtr)[a] = '\0';
460 }else{
461 break;
462 }
463 if (a == 0) break;
464 }
465
466 // Copy the comment
467 if (memcmp(ValuePtr, "ASCII",5) == 0){
468 for (a=5;a<10;a++){
469 int c;
470 c = (ValuePtr)[a];
471 if (c != '\0' && c != ' '){
472 //strncpy(ImageInfo.Comments, (const char*)(a+ValuePtr), 199);
473 UserComment.sprintf("%s", (const char*)(a+ValuePtr));
474 break;
475 }
476 }
477 }else{
478 //strncpy(ImageInfo.Comments, (const char*)ValuePtr, 199);
479 UserComment.sprintf("%s", (const char*)ValuePtr);
480 }
481 break;
482
483 case TAG_FNUMBER:
484 // Simplest way of expressing aperture, so I trust it the most.
485 // (overwrite previously computd value if there is one)
486 ExifData::ApertureFNumber = (float)ConvertAnyFormat(ValuePtr, Format);
487 break;
488
489 case TAG_APERTURE:
490 case TAG_MAXAPERTURE:
491 // More relevant info always comes earlier, so only use this field if we don't
492 // have appropriate aperture information yet.
493 if (ExifData::ApertureFNumber == 0){
494 ExifData::ApertureFNumber
495 = (float)exp(ConvertAnyFormat(ValuePtr, Format)*log(2)*0.5);
496 }
497 break;
498
499 case TAG_FOCALLENGTH:
500 // Nice digital cameras actually save the focal length as a function
501 // of how farthey are zoomed in.
502 ExifData::FocalLength = (float)ConvertAnyFormat(ValuePtr, Format);
503 break;
504
505 case TAG_SUBJECT_DISTANCE:
506 // Inidcates the distacne the autofocus camera is focused to.
507 // Tends to be less accurate as distance increases.
508 ExifData::Distance = (float)ConvertAnyFormat(ValuePtr, Format);
509 break;
510
511 case TAG_EXPOSURETIME:
512 // Simplest way of expressing exposure time, so I trust it most.
513 // (overwrite previously computd value if there is one)
514 ExifData::ExposureTime = (float)ConvertAnyFormat(ValuePtr, Format);
515 break;
516
517 case TAG_SHUTTERSPEED:
518 // More complicated way of expressing exposure time, so only use
519 // this value if we don't already have it from somewhere else.
520 if (ExifData::ExposureTime == 0){
521 ExifData::ExposureTime
522 = (float)(1/exp(ConvertAnyFormat(ValuePtr, Format)*log(2)));
523 }
524 break;
525
526 case TAG_FLASH:
527 if (ConvertAnyFormat(ValuePtr, Format)){
528 ExifData::FlashUsed = 1;
529 }
530 break;
531
532 case TAG_EXIF_IMAGELENGTH:
533 ExifImageLength = (int)ConvertAnyFormat(ValuePtr, Format);
534 break;
535
536 case TAG_EXIF_IMAGEWIDTH:
537 ExifImageWidth = (int)ConvertAnyFormat(ValuePtr, Format);
538 break;
539
540 case TAG_FOCALPLANEXRES:
541 FocalplaneXRes = ConvertAnyFormat(ValuePtr, Format);
542 break;
543
544 case TAG_FOCALPLANEUNITS:
545 switch((int)ConvertAnyFormat(ValuePtr, Format)){
546 case 1: FocalplaneUnits = 25.4; break; // inch
547 case 2:
548 // According to the information I was using, 2 means meters.
549 // But looking at the Cannon powershot's files, inches is the only
550 // sensible value.
551 FocalplaneUnits = 25.4;
552 break;
553
554 case 3: FocalplaneUnits = 10; break; // centimeter
555 case 4: FocalplaneUnits = 1; break; // milimeter
556 case 5: FocalplaneUnits = .001; break; // micrometer
557 }
558 break;
559
560 // Remaining cases contributed by: Volker C. Schoech (schoech@gmx.de)
561
562 case TAG_EXPOSURE_BIAS:
563 ExifData::ExposureBias = (float)ConvertAnyFormat(ValuePtr, Format);
564 break;
565
566 case TAG_WHITEBALANCE:
567 ExifData::Whitebalance = (int)ConvertAnyFormat(ValuePtr, Format);
568 break;
569
570 case TAG_METERING_MODE:
571 ExifData::MeteringMode = (int)ConvertAnyFormat(ValuePtr, Format);
572 break;
573
574 case TAG_EXPOSURE_PROGRAM:
575 ExifData::ExposureProgram = (int)ConvertAnyFormat(ValuePtr, Format);
576 break;
577
578 case TAG_ISO_EQUIVALENT:
579 ExifData::ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format);
580 if ( ExifData::ISOequivalent < 50 ) ExifData::ISOequivalent *= 200;
581 break;
582
583 case TAG_COMPRESSION_LEVEL:
584 ExifData::CompressionLevel = (int)ConvertAnyFormat(ValuePtr, Format);
585 break;
586
587 case TAG_THUMBNAIL_OFFSET:
588 ThumbnailOffset = (unsigned)ConvertAnyFormat(ValuePtr, Format);
589 break;
590
591 case TAG_THUMBNAIL_LENGTH:
592 ThumbnailSize = (unsigned)ConvertAnyFormat(ValuePtr, Format);
593 break;
594
595 }
596
597 if (Tag == TAG_EXIF_OFFSET || Tag == TAG_INTEROP_OFFSET){
598 unsigned char * SubdirStart;
599 SubdirStart = OffsetBase + Get32u(ValuePtr);
600 if (SubdirStart < OffsetBase || SubdirStart > OffsetBase+ExifLength){
601 return;
602 }
603 ProcessExifDir(SubdirStart, OffsetBase, ExifLength);
604 continue;
605 }
606 }
607
608 {
609 // In addition to linking to subdirectories via exif tags,
610 // there's also a potential link to another directory at the end of each
611 // directory. this has got to be the result of a comitee!
612 unsigned char * SubdirStart;
613 unsigned Offset;
614
615 if (DIR_ENTRY_ADDR(DirStart, NumDirEntries) + 4 <= OffsetBase+ExifLength){
616 Offset = Get32u(DIR_ENTRY_ADDR(DirStart, NumDirEntries));
617 // There is at least one jpeg from an HP camera having an Offset of almost MAXUINT.
618 // Adding OffsetBase to it produces an overflow, so compare with ExifLength here.
619 // See http://bugs.kde.org/show_bug.cgi?id=54542
620 if (Offset && Offset < ExifLength){
621 SubdirStart = OffsetBase + Offset;
622 if (SubdirStart > OffsetBase+ExifLength){
623 if (SubdirStart < OffsetBase+ExifLength+20){
624 // Jhead 1.3 or earlier would crop the whole directory!
625 // As Jhead produces this form of format incorrectness,
626 // I'll just let it pass silently
627 owarn << "Thumbnail removed with Jhead 1.3 or earlier" << oendl;
628 }else{
629 return;
630 }
631 }else{
632 if (SubdirStart <= OffsetBase+ExifLength){
633 ProcessExifDir(SubdirStart, OffsetBase, ExifLength);
634 }
635 }
636 }
637 }else{
638 // The exif header ends before the last next directory pointer.
639 }
640 }
641
642 if (ThumbnailSize && ThumbnailOffset){
643 if (ThumbnailSize + ThumbnailOffset <= ExifLength){
644 // The thumbnail pointer appears to be valid. Store it.
645 Thumbnail.loadFromData(OffsetBase + ThumbnailOffset, ThumbnailSize, "JPEG");
646 }
647 }
648}
649
650//--------------------------------------------------------------------------
651// Process a COM marker. We want to leave the bytes unchanged. The
652// progam that displays this text may decide to remove blanks, convert
653// newlines, or otherwise modify the text. In particular we want to be
654// safe for passing utf-8 text.
655//--------------------------------------------------------------------------
656void ExifData::process_COM (const uchar * Data, int length)
657{
658 QChar ch;
659 int a;
660
661 for (a=2;a<length;a++){
662 ch = Data[a];
663 if (ch == '\000') continue; // Remove nulls
664 Comment.append(ch);
665 }
666}
667
668
669//--------------------------------------------------------------------------
670// Process a SOFn marker. This is useful for the image dimensions
671//--------------------------------------------------------------------------
672void ExifData::process_SOFn (const uchar * Data, int marker)
673{
674 int data_precision, num_components;
675
676 data_precision = Data[2];
677 ExifData::Height = Get16m(Data+3);
678 ExifData::Width = Get16m(Data+5);
679 num_components = Data[7];
680
681 if (num_components == 3){
682 ExifData::IsColor = 1;
683 }else{
684 ExifData::IsColor = 0;
685 }
686
687 ExifData::Process = marker;
688
689}
690
691//--------------------------------------------------------------------------
692// Get 16 bits motorola order (always) for jpeg header stuff.
693//--------------------------------------------------------------------------
694int ExifData::Get16m(const void * Short)
695{
696 return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1];
697}
698
699
700//--------------------------------------------------------------------------
701// Process a EXIF marker
702// Describes all the drivel that most digital cameras include...
703//--------------------------------------------------------------------------
704void ExifData::process_EXIF(unsigned char * CharBuf, unsigned int length)
705{
706 ExifData::FlashUsed = 0; // If it s from a digicam, and it used flash, it says so.
707
708 FocalplaneXRes = 0;
709 FocalplaneUnits = 0;
710 ExifImageWidth = 0;
711 ExifImageLength = 0;
712
713 { // Check the EXIF header component
714 static const uchar ExifHeader[] = "Exif\0\0";
715 if (memcmp(CharBuf+2, ExifHeader,6)){
716 return;
717 }
718 }
719
720 if (memcmp(CharBuf+8,"II",2) == 0){
721 // printf("Exif section in Intel order\n");
722 MotorolaOrder = 0;
723 }else{
724 if (memcmp(CharBuf+8,"MM",2) == 0){
725 // printf("Exif section in Motorola order\n");
726 MotorolaOrder = 1;
727 }else{
728 return;
729 }
730 }
731
732 // Check the next two values for correctness.
733 if (Get16u(CharBuf+10) != 0x2a
734 || Get32u(CharBuf+12) != 0x08){
735 return;
736 }
737
738 LastExifRefd = CharBuf;
739
740 // First directory starts 16 bytes in. Offsets start at 8 bytes in.
741 ProcessExifDir(CharBuf+16, CharBuf+8, length-6);
742
743 // This is how far the interesting (non thumbnail) part of the exif went.
744 ExifSettingsLength = LastExifRefd - CharBuf;
745
746 // Compute the CCD width, in milimeters.
747 if (FocalplaneXRes != 0){
748 ExifData::CCDWidth = (float)(ExifImageWidth * FocalplaneUnits / FocalplaneXRes);
749 }
750}
751
752//--------------------------------------------------------------------------
753// Convert exif time to Unix time structure
754//--------------------------------------------------------------------------
755int ExifData::Exif2tm(struct ::tm * timeptr, char * ExifTime)
756{
757 int a;
758
759 timeptr->tm_wday = -1;
760
761 // Check for format: YYYY:MM:DD HH:MM:SS format.
762 a = sscanf(ExifTime, "%d:%d:%d %d:%d:%d",
763 &timeptr->tm_year, &timeptr->tm_mon, &timeptr->tm_mday,
764 &timeptr->tm_hour, &timeptr->tm_min, &timeptr->tm_sec);
765
766 if (a == 6){
767 timeptr->tm_isdst = -1;
768 timeptr->tm_mon -= 1; // Adjust for unix zero-based months
769 timeptr->tm_year -= 1900; // Adjust for year starting at 1900
770 return true; // worked.
771 }
772
773 return false; // Wasn't in Exif date format.
774}
775
776//--------------------------------------------------------------------------
777// Contructor for initialising
778//--------------------------------------------------------------------------
779ExifData::ExifData()
780{
781 ExifData::Whitebalance = -1;
782 ExifData::MeteringMode = -1;
783 ExifData::FlashUsed = -1;
784 Orientation = 0;
785 Height = 0;
786 Width = 0;
787 IsColor = 0;
788 Process = 0;
789 FocalLength = 0;
790 ExposureTime = 0;
791 ApertureFNumber = 0;
792 Distance = 0;
793 CCDWidth = 0;
794 ExposureBias = 0;
795 ExposureProgram = 0;
796 ISOequivalent = 0;
797 CompressionLevel = 0;
798 MotorolaOrder = 0;
799}
800
801ExifData::~ExifData()
802{
803}
804
805//--------------------------------------------------------------------------
806// process a EXIF jpeg file
807//--------------------------------------------------------------------------
808bool ExifData::scan(const QString & path)
809{
810 int ret;
811
812 QFile f(path);
813 f.open(IO_ReadOnly);
814
815 // Scan the JPEG headers.
816 ret = ReadJpegSections(f, READ_EXIF);
817
818 if (ret == false){
819 owarn << "Not JPEG file!" << oendl;
820 DiscardData();
821 f.close();
822 return false;
823 }
824 f.close();
825 DiscardData();
826
827 //now make the strings clean,
828 // for exmaple my Casio is a "QV-4000 "
829 CameraMake = CameraMake.stripWhiteSpace();
830 CameraModel = CameraModel.stripWhiteSpace();
831 UserComment = UserComment.stripWhiteSpace();
832 Comment = Comment.stripWhiteSpace();
833 return true;
834}
835
836//--------------------------------------------------------------------------
837// Does the embedded thumbnail match the jpeg image?
838//--------------------------------------------------------------------------
839#ifndef JPEG_TOL
840#define JPEG_TOL 0.02
841#endif
842bool ExifData::isThumbnailSane() {
843 if (Thumbnail.isNull()) return false;
844
845 // check whether thumbnail dimensions match the image
846 // not foolproof, but catches some altered images (jpegtran -rotate)
847 if (ExifImageLength != 0 && ExifImageLength != Height) return false;
848 if (ExifImageWidth != 0 && ExifImageWidth != Width) return false;
849 if (Thumbnail.width() == 0 || Thumbnail.height() == 0) return false;
850 if (Height == 0 || Width == 0) return false;
851 double d = (double)Height/Width*Thumbnail.width()/Thumbnail.height();
852 return (1-JPEG_TOL < d) && (d < 1+JPEG_TOL);
853}
854
855
856
857static QImage flip_image( const QImage& img );
858static QImage rotate_90( const QImage& img );
859static QImage rotate_180( const QImage& );
860static QImage rotate_270( const QImage& );
861
862//--------------------------------------------------------------------------
863// return a thumbnail that respects the orientation flag
864// only if it seems sane
865//--------------------------------------------------------------------------
866QImage ExifData::getThumbnail() {
867 if (!isThumbnailSane()) return NULL;
868 if (!Orientation || Orientation == 1) return Thumbnail;
869
870 // now fix orientation
871
872 QImage dest = Thumbnail;
873 switch (Orientation) { // notice intentional fallthroughs
874 case 2: dest = flip_image( dest ); break;
875 case 4: dest = flip_image( dest );
876 case 3: dest =rotate_180( dest ); break;
877 case 5: dest = flip_image( dest );
878 case 6: dest = rotate_90( dest ); break;
879 case 7: dest = flip_image( dest );
880 case 8: dest = rotate_270( dest ); break;
881 default: break; // should never happen
882 }
883 return dest;
884}
885
886
887/*
888 *
889 */
890static QImage flip_image( const QImage& img ) {
891 return img.mirror( TRUE, FALSE );
892}
893
894
895static QImage dest;
896static int x, y;
897static unsigned int *srcData, *destData; // we're not threaded anyway
898static unsigned char *srcData8, *destData8; // 8 bit is char
899static unsigned int *srcTable, *destTable; // destination table
900
901
902static QImage rotate_90_8( const QImage &img ) {
903 dest.create(img.height(), img.width(), img.depth());
904 dest.setNumColors(img.numColors());
905 srcTable = (unsigned int *)img.colorTable();
906 destTable = (unsigned int *)dest.colorTable();
907 for ( x=0; x < img.numColors(); ++x )
908 destTable[x] = srcTable[x];
909 for ( y=0; y < img.height(); ++y ){
910 srcData8 = (unsigned char *)img.scanLine(y);
911 for ( x=0; x < img.width(); ++x ){
912 destData8 = (unsigned char *)dest.scanLine(x);
913 destData8[img.height()-y-1] = srcData8[x];
914 }
915 }
916 return dest;
917}
918
919static QImage rotate_90_all( const QImage& img ) {
920 dest.create(img.height(), img.width(), img.depth());
921 for ( y=0; y < img.height(); ++y ) {
922 srcData = (unsigned int *)img.scanLine(y);
923 for ( x=0; x < img.width(); ++x ) {
924 destData = (unsigned int *)dest.scanLine(x);
925 destData[img.height()-y-1] = srcData[x];
926 }
927 }
928
929 return dest;
930}
931
932
933static QImage rotate_90( const QImage & img ) {
934 if ( img.depth() > 8)
935 return rotate_90_all( img );
936 else
937 return rotate_90_8( img );
938}
939
940static QImage rotate_180_all( const QImage& img ) {
941 dest.create(img.width(), img.height(), img.depth());
942 for ( y=0; y < img.height(); ++y ){
943 srcData = (unsigned int *)img.scanLine(y);
944 destData = (unsigned int *)dest.scanLine(img.height()-y-1);
945 for ( x=0; x < img.width(); ++x )
946 destData[img.width()-x-1] = srcData[x];
947 }
948 return dest;
949}
950
951static QImage rotate_180_8( const QImage& img ) {
952 dest.create(img.width(), img.height(), img.depth());
953 dest.setNumColors(img.numColors());
954 srcTable = (unsigned int *)img.colorTable();
955 destTable = (unsigned int *)dest.colorTable();
956 for ( x=0; x < img.numColors(); ++x )
957 destTable[x] = srcTable[x];
958 for ( y=0; y < img.height(); ++y ){
959 srcData8 = (unsigned char *)img.scanLine(y);
960 destData8 = (unsigned char *)dest.scanLine(img.height()-y-1);
961 for ( x=0; x < img.width(); ++x )
962 destData8[img.width()-x-1] = srcData8[x];
963 }
964 return dest;
965}
966
967static QImage rotate_180( const QImage& img ) {
968 if ( img.depth() > 8 )
969 return rotate_180_all( img );
970 else
971 return rotate_180_8( img );
972}
973
974
975static QImage rotate_270_8( const QImage& img ) {
976 dest.create(img.height(), img.width(), img.depth());
977 dest.setNumColors(img.numColors());
978 srcTable = (unsigned int *)img.colorTable();
979 destTable = (unsigned int *)dest.colorTable();
980 for ( x=0; x < img.numColors(); ++x )
981 destTable[x] = srcTable[x];
982 for ( y=0; y < img.height(); ++y ){
983 srcData8 = (unsigned char *)img.scanLine(y);
984 for ( x=0; x < img.width(); ++x ){
985 destData8 = (unsigned char *)dest.scanLine(img.width()-x-1);
986 destData8[y] = srcData8[x];
987 }
988 }
989
990 return dest;
991}
992
993static QImage rotate_270_all( const QImage& img ) {
994 dest.create(img.height(), img.width(), img.depth());
995 for ( y=0; y < img.height(); ++y ){
996 srcData = (unsigned int *)img.scanLine(y);
997 for ( x=0; x < img.width(); ++x ){
998 destData = (unsigned int *)dest.scanLine(img.width()-x-1);
999 destData[y] = srcData[x];
1000 }
1001 }
1002 return dest;
1003}
1004
1005static QImage rotate_270( const QImage& img ) {
1006 if ( img.depth() > 8 )
1007 return rotate_270_all( img );
1008 else
1009 return rotate_270_8( img );
1010}
1011
1012
1013static QString color_mode_to_string( bool b ) {
1014 return b ? QObject::tr( "Colormode: Color\n" ) : QObject::tr( "Colormode: Black and white\n" );
1015}
1016
1017static QString compression_to_string( int level ) {
1018 QString str;
1019 switch( level ) {
1020 case 1:
1021 str = QObject::tr( "Basic" );
1022 break;
1023 case 2:
1024 str = QObject::tr( "Normal" );
1025 break;
1026 case 4:
1027 str = QObject::tr( "Fine" );
1028 break;
1029 default:
1030 str = QObject::tr( "Unknown" );
1031
1032 }
1033 return QObject::tr("Quality: %1\n").arg(str);
1034}
1035
1036
1037static QDateTime parseDateTime( const QString& string )
1038{
1039 QDateTime dt;
1040 if ( string.length() != 19 )
1041 return dt;
1042
1043 QString year = string.left( 4 );
1044 QString month = string.mid( 5, 2 );
1045 QString day = string.mid( 8, 2 );
1046 QString hour = string.mid( 11, 2 );
1047 QString minute = string.mid( 14, 2 );
1048 QString seconds = string.mid( 18, 2 );
1049
1050 bool ok;
1051 bool allOk = true;
1052 int y = year.toInt( &ok );
1053 allOk &= ok;
1054
1055 int mo = month.toInt( &ok );
1056 allOk &= ok;
1057
1058 int d = day.toInt( &ok );
1059 allOk &= ok;
1060
1061 int h = hour.toInt( &ok );
1062 allOk &= ok;
1063
1064 int mi = minute.toInt( &ok );
1065 allOk &= ok;
1066
1067 int s = seconds.toInt( &ok );
1068 allOk &= ok;
1069
1070 if ( allOk ) {
1071 dt.setDate( QDate( y, mo, d ) );
1072 dt.setTime( QTime( h, mi, s ) );
1073 }
1074
1075 return dt;
1076}
1077
1078static QString white_balance_string( int i ) {
1079 QString balance;
1080 switch ( i ) {
1081 case 0:
1082 balance = QObject::tr( "Unknown" );
1083 break;
1084 case 1:
1085 balance = QObject::tr( "Daylight" );
1086 break;
1087 case 2:
1088 balance = QObject::tr( "Fluorescent" );
1089 break;
1090 case 3:
1091 balance = QObject::tr( "Tungsten" );
1092 break;
1093 case 17:
1094 balance = QObject::tr( "Standard light A" );
1095 break;
1096 case 18:
1097 balance = QObject::tr( "Standard light B" );
1098 break;
1099 case 19:
1100 balance = QObject::tr( "Standard light C" );
1101 break;
1102 case 20:
1103 balance = QObject::tr( "D55" );
1104 break;
1105 case 21:
1106 balance = QObject::tr( "D65" );
1107 break;
1108 case 22:
1109 balance = QObject::tr( "D75" );
1110 break;
1111 case 255:
1112 balance = QObject::tr( "Other" );
1113 break;
1114 default:
1115 balance = QObject::tr( "Unknown" );
1116 }
1117 return QObject::tr( "White Balance: %1\n" ).arg( balance );
1118
1119}
1120
1121
1122static QString metering_mode( int i) {
1123 QString meter;
1124 switch( i ) {
1125 case 0:
1126 meter = QObject::tr( "Unknown" );
1127 break;
1128 case 1:
1129 meter = QObject::tr( "Average" );
1130 break;
1131 case 2:
1132 meter = QObject::tr( "Center weighted average" );
1133 break;
1134 case 3:
1135 meter = QObject::tr( "Spot" );
1136 break;
1137 case 4:
1138 meter = QObject::tr( "MultiSpot" );
1139 break;
1140 case 5:
1141 meter = QObject::tr( "Pattern" );
1142 break;
1143 case 6:
1144 meter = QObject::tr( "Partial" );
1145 break;
1146 case 255:
1147 meter = QObject::tr( "Other" );
1148 break;
1149 default:
1150 meter = QObject::tr( "Unknown" );
1151 }
1152
1153 return QObject::tr( "Metering Mode: %1\n" ).arg( meter );
1154}
1155
1156
1157static QString exposure_program( int i ) {
1158 QString exp;
1159 switch( i ) {
1160 case 0:
1161 exp = QObject::tr( "Not defined" );
1162 break;
1163 case 1:
1164 exp = QObject::tr( "Manual" );
1165 break;
1166 case 2:
1167 exp = QObject::tr( "Normal progam" );
1168 break;
1169 case 3:
1170 exp = QObject::tr( "Aperture priority" );
1171 break;
1172 case 4:
1173 exp = QObject::tr( "Shutter priority" );
1174 break;
1175 case 5:
1176 exp = QObject::tr( "Creative progam\n(biased toward fast shutter speed" );
1177 break;
1178 case 6:
1179 exp = QObject::tr( "Action progam\n(biased toward fast shutter speed)" );
1180 break;
1181 case 7:
1182 exp = QObject::tr( "Portrait mode\n(for closeup photos with the background out of focus)" );
1183 break;
1184 case 8:
1185 exp = QObject::tr( "Landscape mode\n(for landscape photos with the background in focus)" );
1186 break;
1187 default:
1188 exp = QObject::tr( "Unknown" );
1189 }
1190
1191 return QObject::tr( "Exposure Program: %1\n" ).arg( exp );
1192}
1193
1194} // namespace MM
1195} // namespace OPIE
diff --git a/libopie2/opiemm/opieexif.h b/libopie2/opiemm/opieexif.h
new file mode 100644
index 0000000..efaed71
--- a/dev/null
+++ b/libopie2/opiemm/opieexif.h
@@ -0,0 +1,139 @@
1#ifndef _OPIE_EXIF_H
2#define _OPIE_EXIF_H
3
4#include <qt.h>
5#include <qstring.h>
6#include <qimage.h>
7#include <qfile.h>
8
9#include <time.h>
10
11namespace Opie { namespace MM {
12
13#ifndef uchar
14typedef unsigned char uchar;
15#endif
16
17//#define MAX_SECTIONS 20
18//#define PSEUDO_IMAGE_MARKER 0x123; // Extra value.
19
20//! Class for reading exif data from images
21/*!
22 * This class is mostly used inside OImageScrollView for testing jpegs headers for a faster
23 * loading and scaling. It is taken from libexif and converted into an C++ structure.
24 *
25 * \see OImageScrollView
26 * \since 1.2
27 */
28class ExifData {
29public:
30 enum ReadMode_t {
31 READ_EXIF = 1,
32 READ_IMAGE = 2,
33 READ_ALL = 3
34 };
35
36 //--------------------------------------------------------------------------
37 // This structure is used to store jpeg file sections in memory.
38 struct Section_t {
39 uchar * Data;
40 int Type;
41 unsigned Size;
42 };
43
44 struct TagTable_t {
45 unsigned short Tag;
46 const char*const Desc;
47 };
48
49private:
50 static const int MAX_SECTIONS=20;
51 static const unsigned int PSEUDO_IMAGE_MARKER=0x123;
52 Section_t Sections[MAX_SECTIONS];
53
54 QString CameraMake;
55 QString CameraModel;
56 QString DateTime;
57 int Orientation;
58 int Height, Width;
59 int ExifImageLength, ExifImageWidth;
60 int IsColor;
61 int Process;
62 int FlashUsed;
63 float FocalLength;
64 float ExposureTime;
65 float ApertureFNumber;
66 float Distance;
67 int Whitebalance;
68 int MeteringMode;
69 float CCDWidth;
70 float ExposureBias;
71 int ExposureProgram;
72 int ISOequivalent;
73 int CompressionLevel;
74 QString UserComment;
75 QString Comment;
76 QImage Thumbnail;
77
78 unsigned char * LastExifRefd;
79 int ExifSettingsLength;
80 double FocalplaneXRes;
81 double FocalplaneUnits;
82 int MotorolaOrder;
83 int SectionsRead;
84
85 int ReadJpegSections (QFile & infile, ReadMode_t ReadMode);
86 void DiscardData(void);
87 int Get16u(void * Short);
88 int Get32s(void * Long);
89 unsigned Get32u(void * Long);
90 double ConvertAnyFormat(void * ValuePtr, int Format);
91 void ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase, unsigned ExifLength);
92 void process_COM (const uchar * Data, int length);
93 void process_SOFn (const uchar * Data, int marker);
94 int Get16m(const void * Short);
95 void process_EXIF(unsigned char * CharBuf, unsigned int length);
96 int Exif2tm(struct ::tm * timeptr, char * ExifTime);
97
98public:
99 //! Contructor for initialising
100 ExifData();
101 //! destructor
102 virtual ~ExifData();
103 //! scan a given file
104 /*!
105 * try to scan the EXIF data of a image file
106 * \param aFile the file to scan
107 * \return true if success, otherwise false
108 */
109 bool scan(const QString &aFile);
110 QString getCameraMake() { return CameraMake; }
111 QString getCameraModel() { return CameraModel; }
112 QString getDateTime() { return DateTime; }
113 int getOrientation() { return Orientation; }
114 int getHeight() { return Height; }
115 int getWidth() { return Width; }
116 int getIsColor() { return IsColor; }
117 int getProcess() { return Process; }
118 int getFlashUsed() { return FlashUsed; }
119 float getFocalLength() { return FocalLength; }
120 float getExposureTime() { return ExposureTime; }
121 float getApertureFNumber() { return ApertureFNumber; }
122 float getDistance() { return Distance; }
123 int getWhitebalance() { return Whitebalance; }
124 int getMeteringMode() { return MeteringMode; }
125 float getCCDWidth() { return CCDWidth; }
126 float getExposureBias() { return ExposureBias; }
127 int getExposureProgram() { return ExposureProgram; }
128 int getISOequivalent() { return ISOequivalent; }
129 int getCompressionLevel() { return CompressionLevel; }
130 QString getUserComment() { return UserComment; }
131 QString getComment() { return Comment; }
132 QImage getThumbnail();
133 bool isThumbnailSane();
134 bool isNullThumbnail() { return !isThumbnailSane(); }
135};
136
137}
138}
139#endif
diff --git a/libopie2/opiemm/opiemm.pro b/libopie2/opiemm/opiemm.pro
index 09b5ed4..32580c8 100644
--- a/libopie2/opiemm/opiemm.pro
+++ b/libopie2/opiemm/opiemm.pro
@@ -1,8 +1,8 @@
1TEMPLATE = lib 1TEMPLATE = lib
2CONFIG += qt warn_on 2CONFIG += qt warn_on
3DESTDIR = $(OPIEDIR)/lib 3DESTDIR = $(OPIEDIR)/lib
4HEADERS = osoundsystem.h oimagezoomer.h oimagescrollview.h 4HEADERS = osoundsystem.h oimagezoomer.h oimagescrollview.h opieexif.h
5SOURCES = osoundsystem.cpp oimagezoomer.cpp oimagescrollview.cpp 5SOURCES = osoundsystem.cpp oimagezoomer.cpp oimagescrollview.cpp opieexif.cpp
6INTERFACES = 6INTERFACES =
7TARGET = opiemm2 7TARGET = opiemm2
8VERSION = 1.9.0 8VERSION = 1.9.0