summaryrefslogtreecommitdiffstats
path: root/kfile-plugins/jpeg
diff options
context:
space:
mode:
Diffstat (limited to 'kfile-plugins/jpeg')
-rw-r--r--kfile-plugins/jpeg/Makefile.am24
-rw-r--r--kfile-plugins/jpeg/README35
-rw-r--r--kfile-plugins/jpeg/exif.cpp961
-rw-r--r--kfile-plugins/jpeg/exif.h127
-rw-r--r--kfile-plugins/jpeg/kfile_jpeg.cpp531
-rw-r--r--kfile-plugins/jpeg/kfile_jpeg.desktop64
-rw-r--r--kfile-plugins/jpeg/kfile_jpeg.h43
-rw-r--r--kfile-plugins/jpeg/kfile_setcomment.cpp536
8 files changed, 2321 insertions, 0 deletions
diff --git a/kfile-plugins/jpeg/Makefile.am b/kfile-plugins/jpeg/Makefile.am
new file mode 100644
index 00000000..fac3b392
--- /dev/null
+++ b/kfile-plugins/jpeg/Makefile.am
@@ -0,0 +1,24 @@
+## Makefile.am for jpeg file meta info plugin
+
+KDE_CXXFLAGS = $(USE_EXCEPTIONS)
+
+# set the include path for X, qt and KDE
+INCLUDES = $(all_includes)
+
+# these are the headers for your project
+noinst_HEADERS = kfile_jpeg.h exif.h
+
+kde_module_LTLIBRARIES = kfile_jpeg.la
+
+kfile_jpeg_la_SOURCES = kfile_jpeg.cpp exif.cpp kfile_setcomment.cpp
+kfile_jpeg_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN)
+kfile_jpeg_la_LIBADD = $(LIB_KIO)
+
+# let automoc handle all of the meta source files (moc)
+METASOURCES = AUTO
+
+messages:
+ $(XGETTEXT) *.cpp -o $(podir)/kfile_jpeg.pot
+
+services_DATA = kfile_jpeg.desktop
+servicesdir = $(kde_servicesdir)
diff --git a/kfile-plugins/jpeg/README b/kfile-plugins/jpeg/README
new file mode 100644
index 00000000..6b714426
--- /dev/null
+++ b/kfile-plugins/jpeg/README
@@ -0,0 +1,35 @@
+-------------------------------------------------------------------------------
+Thanks to:
+Matthias Wandel <matt@rim.net>
+jhead (http://www.sentex.net/~mwandel/jhead/)
+
+Groult Richard <rgroult@jalix.org>
+showimg (http://ric.jalix.org/)
+
+Bryce Nesbitt for setcomment
+based on wrjpgcom.c, Copyright (C) 1994-1997, Thomas G. Lane.
+
+Frank Pieczynski <pieczy@knuut.de>
+
+EXIF spec:
+http://www.pima.net/standards/it10/PIMA15740/exif.htm
+http://www.ba.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html
+
+-------------------------------------------------------------------------------
+Bugs:
+ 0) Just showing the meta info can cause the file to be rewritten!!!
+ 1) utf-8 paths must be tested and supported.
+ 2) Display of EXIF comments need to be tested.
+ Both Unicode & ASCII need testing.
+
+New features needed:
+ 1) Should switch to a multi-line editor for comments. Support "\n".
+ 2) Should autodetect if the jpeg COM is in utf-8 format.
+ 3) Allow editing the EXIF "Orientation" flag (without rewriting the exif header).
+ 4) Extract and return the EXIF thumbnail, oriented properly, as an alternative
+ to the much slower process of generating a new thumbnail.
+
+Future features:
+ 1) Allow the user to erase or move comment in either the EXIF or jpeg COM.
+ 2) Play or return audio files associated with the exif.
+
diff --git a/kfile-plugins/jpeg/exif.cpp b/kfile-plugins/jpeg/exif.cpp
new file mode 100644
index 00000000..f25e8a78
--- /dev/null
+++ b/kfile-plugins/jpeg/exif.cpp
@@ -0,0 +1,961 @@
+//--------------------------------------------------------------------------
+// Program to pull the information out of various types of EFIF digital
+// camera files and show it in a reasonably consistent way
+//
+// This module parses the very complicated exif structures.
+//
+// Matthias Wandel, Dec 1999 - August 2000
+//--------------------------------------------------------------------------
+
+
+#include "exif.h"
+#include <qwmatrix.h>
+#include <kglobal.h>
+
+
+static unsigned char * LastExifRefd;
+static int ExifSettingsLength;
+static double FocalplaneXRes;
+static double FocalplaneUnits;
+static int MotorolaOrder = 0;
+static int SectionsRead;
+//static int HaveAll;
+
+//--------------------------------------------------------------------------
+// Table of Jpeg encoding process names
+
+#define M_SOF0 0xC0 // Start Of Frame N
+#define M_SOF1 0xC1 // N indicates which compression process
+#define M_SOF2 0xC2 // Only SOF0-SOF2 are now in common use
+#define M_SOF3 0xC3
+#define M_SOF5 0xC5 // NB: codes C4 and CC are NOT SOF markers
+#define M_SOF6 0xC6
+#define M_SOF7 0xC7
+#define M_SOF9 0xC9
+#define M_SOF10 0xCA
+#define M_SOF11 0xCB
+#define M_SOF13 0xCD
+#define M_SOF14 0xCE
+#define M_SOF15 0xCF
+#define M_SOI 0xD8 // Start Of Image (beginning of datastream)
+#define M_EOI 0xD9 // End Of Image (end of datastream)
+#define M_SOS 0xDA // Start Of Scan (begins compressed data)
+#define M_JFIF 0xE0 // Jfif marker
+#define M_EXIF 0xE1 // Exif marker
+#define M_COM 0xFE // COMment
+
+
+TagTable_t ProcessTable[] = {
+ { M_SOF0, "Baseline"},
+ { M_SOF1, "Extended sequential"},
+ { M_SOF2, "Progressive"},
+ { M_SOF3, "Lossless"},
+ { M_SOF5, "Differential sequential"},
+ { M_SOF6, "Differential progressive"},
+ { M_SOF7, "Differential lossless"},
+ { M_SOF9, "Extended sequential, arithmetic coding"},
+ { M_SOF10, "Progressive, arithmetic coding"},
+ { M_SOF11, "Lossless, arithmetic coding"},
+ { M_SOF13, "Differential sequential, arithmetic coding"},
+ { M_SOF14, "Differential progressive, arithmetic coding"},
+ { M_SOF15, "Differential lossless, arithmetic coding"},
+ { 0, "Unknown"}
+};
+
+
+
+//--------------------------------------------------------------------------
+// Describes format descriptor
+static int BytesPerFormat[] = {0,1,1,2,4,8,1,1,2,4,8,4,8};
+#define NUM_FORMATS 12
+
+#define FMT_BYTE 1
+#define FMT_STRING 2
+#define FMT_USHORT 3
+#define FMT_ULONG 4
+#define FMT_URATIONAL 5
+#define FMT_SBYTE 6
+#define FMT_UNDEFINED 7
+#define FMT_SSHORT 8
+#define FMT_SLONG 9
+#define FMT_SRATIONAL 10
+#define FMT_SINGLE 11
+#define FMT_DOUBLE 12
+
+//--------------------------------------------------------------------------
+// Describes tag values
+
+#define TAG_EXIF_OFFSET 0x8769
+#define TAG_INTEROP_OFFSET 0xa005
+
+#define TAG_MAKE 0x010F
+#define TAG_MODEL 0x0110
+#define TAG_ORIENTATION 0x0112
+
+#define TAG_EXPOSURETIME 0x829A
+#define TAG_FNUMBER 0x829D
+
+#define TAG_SHUTTERSPEED 0x9201
+#define TAG_APERTURE 0x9202
+#define TAG_MAXAPERTURE 0x9205
+#define TAG_FOCALLENGTH 0x920A
+
+#define TAG_DATETIME_ORIGINAL 0x9003
+#define TAG_USERCOMMENT 0x9286
+
+#define TAG_SUBJECT_DISTANCE 0x9206
+#define TAG_FLASH 0x9209
+
+#define TAG_FOCALPLANEXRES 0xa20E
+#define TAG_FOCALPLANEUNITS 0xa210
+#define TAG_EXIF_IMAGEWIDTH 0xA002
+#define TAG_EXIF_IMAGELENGTH 0xA003
+
+// the following is added 05-jan-2001 vcs
+#define TAG_EXPOSURE_BIAS 0x9204
+#define TAG_WHITEBALANCE 0x9208
+#define TAG_METERING_MODE 0x9207
+#define TAG_EXPOSURE_PROGRAM 0x8822
+#define TAG_ISO_EQUIVALENT 0x8827
+#define TAG_COMPRESSION_LEVEL 0x9102
+
+#define TAG_THUMBNAIL_OFFSET 0x0201
+#define TAG_THUMBNAIL_LENGTH 0x0202
+
+
+/*static TagTable_t TagTable[] = {
+ { 0x100, "ImageWidth"},
+ { 0x101, "ImageLength"},
+ { 0x102, "BitsPerSample"},
+ { 0x103, "Compression"},
+ { 0x106, "PhotometricInterpretation"},
+ { 0x10A, "FillOrder"},
+ { 0x10D, "DocumentName"},
+ { 0x10E, "ImageDescription"},
+ { 0x10F, "Make"},
+ { 0x110, "Model"},
+ { 0x111, "StripOffsets"},
+ { 0x112, "Orientation"},
+ { 0x115, "SamplesPerPixel"},
+ { 0x116, "RowsPerStrip"},
+ { 0x117, "StripByteCounts"},
+ { 0x11A, "XResolution"},
+ { 0x11B, "YResolution"},
+ { 0x11C, "PlanarConfiguration"},
+ { 0x128, "ResolutionUnit"},
+ { 0x12D, "TransferFunction"},
+ { 0x131, "Software"},
+ { 0x132, "DateTime"},
+ { 0x13B, "Artist"},
+ { 0x13E, "WhitePoint"},
+ { 0x13F, "PrimaryChromaticities"},
+ { 0x156, "TransferRange"},
+ { 0x200, "JPEGProc"},
+ { 0x201, "ThumbnailOffset"},
+ { 0x202, "ThumbnailLength"},
+ { 0x211, "YCbCrCoefficients"},
+ { 0x212, "YCbCrSubSampling"},
+ { 0x213, "YCbCrPositioning"},
+ { 0x214, "ReferenceBlackWhite"},
+ { 0x828D, "CFARepeatPatternDim"},
+ { 0x828E, "CFAPattern"},
+ { 0x828F, "BatteryLevel"},
+ { 0x8298, "Copyright"},
+ { 0x829A, "ExposureTime"},
+ { 0x829D, "FNumber"},
+ { 0x83BB, "IPTC/NAA"},
+ { 0x8769, "ExifOffset"},
+ { 0x8773, "InterColorProfile"},
+ { 0x8822, "ExposureProgram"},
+ { 0x8824, "SpectralSensitivity"},
+ { 0x8825, "GPSInfo"},
+ { 0x8827, "ISOSpeedRatings"},
+ { 0x8828, "OECF"},
+ { 0x9000, "ExifVersion"},
+ { 0x9003, "DateTimeOriginal"},
+ { 0x9004, "DateTimeDigitized"},
+ { 0x9101, "ComponentsConfiguration"},
+ { 0x9102, "CompressedBitsPerPixel"},
+ { 0x9201, "ShutterSpeedValue"},
+ { 0x9202, "ApertureValue"},
+ { 0x9203, "BrightnessValue"},
+ { 0x9204, "ExposureBiasValue"},
+ { 0x9205, "MaxApertureValue"},
+ { 0x9206, "SubjectDistance"},
+ { 0x9207, "MeteringMode"},
+ { 0x9208, "LightSource"},
+ { 0x9209, "Flash"},
+ { 0x920A, "FocalLength"},
+ { 0x927C, "MakerNote"},
+ { 0x9286, "UserComment"},
+ { 0x9290, "SubSecTime"},
+ { 0x9291, "SubSecTimeOriginal"},
+ { 0x9292, "SubSecTimeDigitized"},
+ { 0xA000, "FlashPixVersion"},
+ { 0xA001, "ColorSpace"},
+ { 0xA002, "ExifImageWidth"},
+ { 0xA003, "ExifImageLength"},
+ { 0xA005, "InteroperabilityOffset"},
+ { 0xA20B, "FlashEnergy"}, // 0x920B in TIFF/EP
+ { 0xA20C, "SpatialFrequencyResponse"}, // 0x920C - -
+ { 0xA20E, "FocalPlaneXResolution"}, // 0x920E - -
+ { 0xA20F, "FocalPlaneYResolution"}, // 0x920F - -
+ { 0xA210, "FocalPlaneResolutionUnit"}, // 0x9210 - -
+ { 0xA214, "SubjectLocation"}, // 0x9214 - -
+ { 0xA215, "ExposureIndex"}, // 0x9215 - -
+ { 0xA217, "SensingMethod"}, // 0x9217 - -
+ { 0xA300, "FileSource"},
+ { 0xA301, "SceneType"},
+ { 0, NULL}
+} ;
+*/
+
+
+//--------------------------------------------------------------------------
+// Parse the marker stream until SOS or EOI is seen;
+//--------------------------------------------------------------------------
+int ExifData::ReadJpegSections (QFile & infile, ReadMode_t ReadMode)
+{
+ int a;
+
+ a = infile.getch();
+
+ if (a != 0xff || infile.getch() != M_SOI) {
+ SectionsRead = 0;
+ return false;
+ }
+ for(SectionsRead = 0; SectionsRead < MAX_SECTIONS-1; ){
+ int marker = 0;
+ int got;
+ unsigned int ll,lh;
+ unsigned int itemlen;
+ uchar * Data;
+
+ for (a=0;a<7;a++){
+ marker = infile.getch();
+ if (marker != 0xff) break;
+
+ if (a >= 6){
+
+ kdDebug(7034) << "too many padding bytes\n";
+ return false;
+
+ }
+ }
+
+ if (marker == 0xff){
+ // 0xff is legal padding, but if we get that many, something's wrong.
+ throw FatalError("too many padding bytes!");
+ }
+
+ Sections[SectionsRead].Type = marker;
+
+ // Read the length of the section.
+ lh = (uchar) infile.getch();
+ ll = (uchar) infile.getch();
+
+ itemlen = (lh << 8) | ll;
+
+ if (itemlen < 2) {
+ throw FatalError("invalid marker");
+ }
+
+ Sections[SectionsRead].Size = itemlen;
+
+ Data = (uchar *)malloc(itemlen+1); // Add 1 to allow sticking a 0 at the end.
+ Sections[SectionsRead].Data = Data;
+
+ // Store first two pre-read bytes.
+ Data[0] = (uchar)lh;
+ Data[1] = (uchar)ll;
+
+ got = infile.readBlock((char*)Data+2, itemlen-2); // Read the whole section.
+ if (( unsigned ) got != itemlen-2){
+ throw FatalError("reading from file");
+ }
+ SectionsRead++;
+
+ switch(marker){
+
+ case M_SOS: // stop before hitting compressed data
+ // If reading entire image is requested, read the rest of the data.
+ if (ReadMode & READ_IMAGE){
+ unsigned long size;
+
+ size = kMax( 0ul, infile.size()-infile.at() );
+ Data = (uchar *)malloc(size);
+ if (Data == NULL){
+ throw FatalError("could not allocate data for entire image");
+ }
+
+ got = infile.readBlock((char*)Data, size);
+ if (( unsigned ) got != size){
+ throw FatalError("could not read the rest of the image");
+ }
+
+ Sections[SectionsRead].Data = Data;
+ Sections[SectionsRead].Size = size;
+ Sections[SectionsRead].Type = PSEUDO_IMAGE_MARKER;
+ SectionsRead ++;
+ //HaveAll = 1;
+ }
+ return true;
+
+ case M_EOI: // in case it's a tables-only JPEG stream
+ kdDebug(7034) << "No image in jpeg!\n";
+ return false;
+
+ case M_COM: // Comment section
+ // pieczy 2002-02-12
+ // now the User comment goes to UserComment
+ // so we can store a Comment section also in READ_EXIF mode
+ process_COM(Data, itemlen);
+ break;
+
+ case M_JFIF:
+ // Regular jpegs always have this tag, exif images have the exif
+ // marker instead, althogh ACDsee will write images with both markers.
+ // this program will re-create this marker on absence of exif marker.
+ // hence no need to keep the copy from the file.
+ free(Sections[--SectionsRead].Data);
+ break;
+
+ case M_EXIF:
+ // Seen files from some 'U-lead' software with Vivitar scanner
+ // that uses marker 31 for non exif stuff. Thus make sure
+ // it says 'Exif' in the section before treating it as exif.
+ if ((ReadMode & READ_EXIF) && memcmp(Data+2, "Exif", 4) == 0){
+ process_EXIF((uchar *)Data, itemlen); // FIXME: This call
+ // requires Data to be array of at least 8 bytes. Code
+ // above only checks for itemlen < 2.
+ }else{
+ // Discard this section.
+ free(Sections[--SectionsRead].Data);
+ }
+ break;
+
+ case M_SOF0:
+ case M_SOF1:
+ case M_SOF2:
+ case M_SOF3:
+ case M_SOF5:
+ case M_SOF6:
+ case M_SOF7:
+ case M_SOF9:
+ case M_SOF10:
+ case M_SOF11:
+ case M_SOF13:
+ case M_SOF14:
+ case M_SOF15:
+ process_SOFn(Data, marker); //FIXME: This call requires Data to
+ // be array of at least 8 bytes. Code above only checks for
+ // itemlen < 2.
+ break;
+ default:
+ break;
+ }
+ }
+ return true;
+}
+
+
+//--------------------------------------------------------------------------
+// Discard read data.
+//--------------------------------------------------------------------------
+void ExifData::DiscardData(void)
+{
+ for (int a=0; a < SectionsRead; a++)
+ free(Sections[a].Data);
+ SectionsRead = 0;
+}
+
+//--------------------------------------------------------------------------
+// Convert a 16 bit unsigned value from file's native byte order
+//--------------------------------------------------------------------------
+int ExifData::Get16u(void * Short)
+{
+ if (MotorolaOrder){
+ return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1];
+ }else{
+ return (((uchar *)Short)[1] << 8) | ((uchar *)Short)[0];
+ }
+}
+
+//--------------------------------------------------------------------------
+// Convert a 32 bit signed value from file's native byte order
+//--------------------------------------------------------------------------
+int ExifData::Get32s(void * Long)
+{
+ if (MotorolaOrder){
+ return ((( char *)Long)[0] << 24) | (((uchar *)Long)[1] << 16)
+ | (((uchar *)Long)[2] << 8 ) | (((uchar *)Long)[3] << 0 );
+ }else{
+ return ((( char *)Long)[3] << 24) | (((uchar *)Long)[2] << 16)
+ | (((uchar *)Long)[1] << 8 ) | (((uchar *)Long)[0] << 0 );
+ }
+}
+
+//--------------------------------------------------------------------------
+// Convert a 32 bit unsigned value from file's native byte order
+//--------------------------------------------------------------------------
+unsigned ExifData::Get32u(void * Long)
+{
+ return (unsigned)Get32s(Long) & 0xffffffff;
+}
+
+//--------------------------------------------------------------------------
+// Evaluate number, be it int, rational, or float from directory.
+//--------------------------------------------------------------------------
+double ExifData::ConvertAnyFormat(void * ValuePtr, int Format)
+{
+ double Value;
+ Value = 0;
+
+ switch(Format){
+ case FMT_SBYTE: Value = *(signed char *)ValuePtr; break;
+ case FMT_BYTE: Value = *(uchar *)ValuePtr; break;
+
+ case FMT_USHORT: Value = Get16u(ValuePtr); break;
+
+ case FMT_ULONG: Value = Get32u(ValuePtr); break;
+
+ case FMT_URATIONAL:
+ case FMT_SRATIONAL:
+ {
+ int Num,Den;
+ Num = Get32s(ValuePtr);
+ Den = Get32s(4+(char *)ValuePtr);
+ if (Den == 0){
+ Value = 0;
+ }else{
+ Value = (double)Num/Den;
+ }
+ break;
+ }
+
+ case FMT_SSHORT: Value = (signed short)Get16u(ValuePtr); break;
+ case FMT_SLONG: Value = Get32s(ValuePtr); break;
+
+ // Not sure if this is correct (never seen float used in Exif format)
+ case FMT_SINGLE: Value = (double)*(float *)ValuePtr; break;
+ case FMT_DOUBLE: Value = *(double *)ValuePtr; break;
+ }
+ return Value;
+}
+
+//--------------------------------------------------------------------------
+// Process one of the nested EXIF directories.
+//--------------------------------------------------------------------------
+void ExifData::ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase, unsigned ExifLength, unsigned NestingLevel)
+{
+ int de;
+ int a;
+ int NumDirEntries;
+ unsigned ThumbnailOffset = 0;
+ unsigned ThumbnailSize = 0;
+
+ if ( NestingLevel > 4)
+ throw FatalError("Maximum directory nesting exceeded (corrupt exif header)");
+
+ NumDirEntries = Get16u(DirStart);
+ #define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry))
+
+ {
+ unsigned char * DirEnd;
+ DirEnd = DIR_ENTRY_ADDR(DirStart, NumDirEntries);
+ if (DirEnd+4 > (OffsetBase+ExifLength)){
+ if (DirEnd+2 == OffsetBase+ExifLength || DirEnd == OffsetBase+ExifLength){
+ // Version 1.3 of jhead would truncate a bit too much.
+ // This also caught later on as well.
+ }else{
+ // Note: Files that had thumbnails trimmed with jhead 1.3 or earlier
+ // might trigger this.
+ throw FatalError("Illegally sized directory");
+ }
+ }
+ if (DirEnd < LastExifRefd) LastExifRefd = DirEnd;
+ }
+
+ for (de=0;de<NumDirEntries;de++){
+ int Tag, Format, Components;
+ unsigned char * ValuePtr;
+ unsigned ByteCount;
+ char * DirEntry;
+ DirEntry = (char *)DIR_ENTRY_ADDR(DirStart, de);
+
+ Tag = Get16u(DirEntry);
+ Format = Get16u(DirEntry+2);
+ Components = Get32u(DirEntry+4);
+
+ if ((Format-1) >= NUM_FORMATS) {
+ // (-1) catches illegal zero case as unsigned underflows to positive large.
+ throw FatalError("Illegal format code in EXIF dir");
+ }
+
+ if ((unsigned)Components > 0x10000) {
+ throw FatalError("Illegal number of components for tag");
+ continue;
+ }
+
+ ByteCount = Components * BytesPerFormat[Format];
+
+ if (ByteCount > 4){
+ unsigned OffsetVal;
+ OffsetVal = Get32u(DirEntry+8);
+ // If its bigger than 4 bytes, the dir entry contains an offset.
+ if (OffsetVal+ByteCount > ExifLength){
+ // Bogus pointer offset and / or bytecount value
+ //printf("Offset %d bytes %d ExifLen %d\n",OffsetVal, ByteCount, ExifLength);
+
+ throw FatalError("Illegal pointer offset value in EXIF");
+ }
+ ValuePtr = OffsetBase+OffsetVal;
+ }else{
+ // 4 bytes or less and value is in the dir entry itself
+ ValuePtr = (unsigned char *)DirEntry+8;
+ }
+
+ if (LastExifRefd < ValuePtr+ByteCount){
+ // Keep track of last byte in the exif header that was actually referenced.
+ // That way, we know where the discardable thumbnail data begins.
+ LastExifRefd = ValuePtr+ByteCount;
+ }
+
+ // Extract useful components of tag
+ switch(Tag){
+
+ case TAG_MAKE:
+ ExifData::CameraMake = QString::fromLatin1((const char*)ValuePtr, 31);
+ break;
+
+ case TAG_MODEL:
+ ExifData::CameraModel = QString::fromLatin1((const char*)ValuePtr, 39);
+ break;
+
+ case TAG_ORIENTATION:
+ Orientation = (int)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_DATETIME_ORIGINAL:
+ DateTime = QString::fromLatin1((const char*)ValuePtr, 19);
+ break;
+
+ case TAG_USERCOMMENT:
+ // Olympus has this padded with trailing spaces. Remove these first.
+ for (a=ByteCount;;){
+ a--;
+ if ((ValuePtr)[a] == ' '){
+ (ValuePtr)[a] = '\0';
+ }else{
+ break;
+ }
+ if (a == 0) break;
+ }
+
+ // Copy the comment
+ if (memcmp(ValuePtr, "ASCII",5) == 0){
+ for (a=5;a<10;a++){
+ int c;
+ c = (ValuePtr)[a];
+ if (c != '\0' && c != ' '){
+ UserComment = QString::fromLatin1((const char*)(a+ValuePtr), 199);
+ break;
+ }
+ }
+ }else{
+ UserComment = QString::fromLatin1((const char*)ValuePtr, 199);
+ }
+ break;
+
+ case TAG_FNUMBER:
+ // Simplest way of expressing aperture, so I trust it the most.
+ // (overwrite previously computd value if there is one)
+ ExifData::ApertureFNumber = (float)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_APERTURE:
+ case TAG_MAXAPERTURE:
+ // More relevant info always comes earlier, so only use this field if we don't
+ // have appropriate aperture information yet.
+ if (ExifData::ApertureFNumber == 0){
+ ExifData::ApertureFNumber
+ = (float)exp(ConvertAnyFormat(ValuePtr, Format)*log(2.0)*0.5);
+ }
+ break;
+
+ case TAG_FOCALLENGTH:
+ // Nice digital cameras actually save the focal length as a function
+ // of how far they are zoomed in.
+ ExifData::FocalLength = (float)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_SUBJECT_DISTANCE:
+ // Inidcates the distacne the autofocus camera is focused to.
+ // Tends to be less accurate as distance increases.
+ ExifData::Distance = (float)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_EXPOSURETIME:
+ // Simplest way of expressing exposure time, so I trust it most.
+ // (overwrite previously computd value if there is one)
+ ExifData::ExposureTime = (float)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_SHUTTERSPEED:
+ // More complicated way of expressing exposure time, so only use
+ // this value if we don't already have it from somewhere else.
+ if (ExifData::ExposureTime == 0){
+ ExifData::ExposureTime
+ = (float)(1/exp(ConvertAnyFormat(ValuePtr, Format)*log(2.0)));
+ }
+ break;
+
+ case TAG_FLASH:
+ ExifData::FlashUsed = (int)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_EXIF_IMAGELENGTH:
+ ExifImageLength = (int)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_EXIF_IMAGEWIDTH:
+ ExifImageWidth = (int)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_FOCALPLANEXRES:
+ FocalplaneXRes = ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_FOCALPLANEUNITS:
+ switch((int)ConvertAnyFormat(ValuePtr, Format)){
+ case 1: FocalplaneUnits = 25.4; break; // inch
+ case 2:
+ // According to the information I was using, 2 means meters.
+ // But looking at the Cannon powershot's files, inches is the only
+ // sensible value.
+ FocalplaneUnits = 25.4;
+ break;
+
+ case 3: FocalplaneUnits = 10; break; // centimeter
+ case 4: FocalplaneUnits = 1; break; // milimeter
+ case 5: FocalplaneUnits = .001; break; // micrometer
+ }
+ break;
+
+ // Remaining cases contributed by: Volker C. Schoech (schoech@gmx.de)
+
+ case TAG_EXPOSURE_BIAS:
+ ExifData::ExposureBias = (float)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_WHITEBALANCE:
+ ExifData::Whitebalance = (int)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_METERING_MODE:
+ ExifData::MeteringMode = (int)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_EXPOSURE_PROGRAM:
+ ExifData::ExposureProgram = (int)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_ISO_EQUIVALENT:
+ ExifData::ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format);
+ if ( ExifData::ISOequivalent < 50 ) ExifData::ISOequivalent *= 200;
+ break;
+
+ case TAG_COMPRESSION_LEVEL:
+ ExifData::CompressionLevel = (int)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_THUMBNAIL_OFFSET:
+ ThumbnailOffset = (unsigned)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_THUMBNAIL_LENGTH:
+ ThumbnailSize = (unsigned)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ }
+
+ if (Tag == TAG_EXIF_OFFSET || Tag == TAG_INTEROP_OFFSET){
+ unsigned char * SubdirStart;
+ SubdirStart = OffsetBase + Get32u(ValuePtr);
+ if (SubdirStart <= OffsetBase || SubdirStart >= OffsetBase+ExifLength){
+ throw FatalError("Illegal subdirectory link");
+ }
+ ProcessExifDir(SubdirStart, OffsetBase, ExifLength, NestingLevel+1);
+ continue;
+ }
+ }
+
+ {
+ // In addition to linking to subdirectories via exif tags,
+ // there's also a potential link to another directory at the end of each
+ // directory. this has got to be the result of a comitee!
+ unsigned char * SubdirStart;
+ unsigned Offset;
+
+ if (DIR_ENTRY_ADDR(DirStart, NumDirEntries) + 4 <= OffsetBase+ExifLength){
+ Offset = Get32u(DIR_ENTRY_ADDR(DirStart, NumDirEntries));
+ // There is at least one jpeg from an HP camera having an Offset of almost MAXUINT.
+ // Adding OffsetBase to it produces an overflow, so compare with ExifLength here.
+ // See http://bugs.kde.org/show_bug.cgi?id=54542
+ if (Offset && Offset < ExifLength){
+ SubdirStart = OffsetBase + Offset;
+ if (SubdirStart > OffsetBase+ExifLength){
+ if (SubdirStart < OffsetBase+ExifLength+20){
+ // Jhead 1.3 or earlier would crop the whole directory!
+ // As Jhead produces this form of format incorrectness,
+ // I'll just let it pass silently
+ kdDebug(7034) << "Thumbnail removed with Jhead 1.3 or earlier\n";
+ }else{
+ throw FatalError("Illegal subdirectory link 2");
+ }
+ }else{
+ if (SubdirStart <= OffsetBase+ExifLength){
+ ProcessExifDir(SubdirStart, OffsetBase, ExifLength, NestingLevel+1);
+ }
+ }
+ }
+ }else{
+ // The exif header ends before the last next directory pointer.
+ }
+ }
+
+ if (ThumbnailSize && ThumbnailOffset){
+ if (ThumbnailSize + ThumbnailOffset < ExifLength){
+ // The thumbnail pointer appears to be valid. Store it.
+ Thumbnail.loadFromData(OffsetBase + ThumbnailOffset, ThumbnailSize, "JPEG");
+ }
+ }
+}
+
+//--------------------------------------------------------------------------
+// Process a COM marker. We want to leave the bytes unchanged. The
+// progam that displays this text may decide to remove blanks, convert
+// newlines, or otherwise modify the text. In particular we want to be
+// safe for passing utf-8 text.
+//--------------------------------------------------------------------------
+void ExifData::process_COM (const uchar * Data, int length)
+{
+ Comment = QString::fromUtf8((char *)Data+2, (length-2));
+}
+
+
+//--------------------------------------------------------------------------
+// Process a SOFn marker. This is useful for the image dimensions
+//--------------------------------------------------------------------------
+void ExifData::process_SOFn (const uchar * Data, int marker)
+{
+ int data_precision, num_components;
+
+ data_precision = Data[2];
+ ExifData::Height = Get16m(Data+3);
+ ExifData::Width = Get16m(Data+5);
+ num_components = Data[7];
+
+ if (num_components == 3){
+ ExifData::IsColor = 1;
+ }else{
+ ExifData::IsColor = 0;
+ }
+
+ ExifData::Process = marker;
+
+}
+
+//--------------------------------------------------------------------------
+// Get 16 bits motorola order (always) for jpeg header stuff.
+//--------------------------------------------------------------------------
+int ExifData::Get16m(const void * Short)
+{
+ return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1];
+}
+
+
+//--------------------------------------------------------------------------
+// Process a EXIF marker
+// Describes all the drivel that most digital cameras include...
+//--------------------------------------------------------------------------
+void ExifData::process_EXIF(unsigned char * CharBuf, unsigned int length)
+{
+ ExifData::FlashUsed = 0; // If it s from a digicam, and it used flash, it says so.
+
+ FocalplaneXRes = 0;
+ FocalplaneUnits = 0;
+ ExifImageWidth = 0;
+ ExifImageLength = 0;
+
+ { // Check the EXIF header component
+ static const uchar ExifHeader[] = "Exif\0\0";
+ if (memcmp(CharBuf+2, ExifHeader,6)){
+ throw FatalError("Incorrect Exif header");
+ }
+ }
+
+ if (memcmp(CharBuf+8,"II",2) == 0){
+ // printf("Exif section in Intel order\n");
+ MotorolaOrder = 0;
+ }else{
+ if (memcmp(CharBuf+8,"MM",2) == 0){
+ // printf("Exif section in Motorola order\n");
+ MotorolaOrder = 1;
+ }else{
+ throw FatalError("Invalid Exif alignment marker.");
+ }
+ }
+
+ // Check the next two values for correctness.
+ if (Get16u(CharBuf+10) != 0x2a){
+ throw FatalError("Invalid Exif start (1)");
+ }
+
+ long IFDoffset = Get32u(CharBuf+12);
+
+ LastExifRefd = CharBuf;
+
+ // First directory starts 16 bytes in. Offsets start at 8 bytes in.
+ ProcessExifDir(&CharBuf[8+IFDoffset], CharBuf+8, length-6, 0);
+
+ // This is how far the interesting (non thumbnail) part of the exif went.
+ ExifSettingsLength = LastExifRefd - CharBuf;
+
+ // Compute the CCD width, in milimeters.
+ if (FocalplaneXRes != 0){
+ kdDebug(7034) << "ExifImageWidth " << ExifImageWidth << " FocalplaneUnits " << FocalplaneUnits << " FocalplaneXRes " << FocalplaneXRes << endl;
+ ExifData::CCDWidth = (float)(ExifImageWidth * FocalplaneUnits / FocalplaneXRes);
+ }
+}
+
+//--------------------------------------------------------------------------
+// Convert exif time to Unix time structure
+//--------------------------------------------------------------------------
+int ExifData::Exif2tm(struct tm * timeptr, char * ExifTime)
+{
+ int a;
+
+ timeptr->tm_wday = -1;
+
+ // Check for format: YYYY:MM:DD HH:MM:SS format.
+ a = sscanf(ExifTime, "%d:%d:%d %d:%d:%d",
+ &timeptr->tm_year, &timeptr->tm_mon, &timeptr->tm_mday,
+ &timeptr->tm_hour, &timeptr->tm_min, &timeptr->tm_sec);
+
+ if (a == 6){
+ timeptr->tm_isdst = -1;
+ timeptr->tm_mon -= 1; // Adjust for unix zero-based months
+ timeptr->tm_year -= 1900; // Adjust for year starting at 1900
+ return true; // worked.
+ }
+
+ return false; // Wasn't in Exif date format.
+}
+
+//--------------------------------------------------------------------------
+// Contructor for initialising
+//--------------------------------------------------------------------------
+ExifData::ExifData()
+{
+ ExifData::Whitebalance = -1;
+ ExifData::MeteringMode = -1;
+ ExifData::FlashUsed = 0;
+ Orientation = 0;
+ Height = 0;
+ Width = 0;
+ IsColor = 0;
+ Process = 0;
+ FocalLength = 0;
+ ExposureTime = 0;
+ ApertureFNumber = 0;
+ Distance = 0;
+ CCDWidth = 0;
+ ExposureBias = 0;
+ ExposureProgram = 0;
+ ISOequivalent = 0;
+ CompressionLevel = 0;
+}
+
+//--------------------------------------------------------------------------
+// process a EXIF jpeg file
+//--------------------------------------------------------------------------
+bool ExifData::scan(const QString & path)
+{
+ int ret;
+
+ QFile f(path);
+ if ( !f.open(IO_ReadOnly) )
+ return false;
+
+ try {
+ // Scan the JPEG headers.
+ ret = ReadJpegSections(f, READ_EXIF);
+ }
+ catch (FatalError& e) {
+ e.debug_print();
+ f.close();
+ return false;
+ }
+
+ if (ret == false){
+ kdDebug(7034) << "Not JPEG file!\n";
+ DiscardData();
+ f.close();
+ return false;
+ }
+ f.close();
+ DiscardData();
+
+ //now make the strings clean,
+ // for exmaple my Casio is a "QV-4000 "
+ CameraMake = CameraMake.stripWhiteSpace();
+ CameraModel = CameraModel.stripWhiteSpace();
+ UserComment = UserComment.stripWhiteSpace();
+ Comment = Comment.stripWhiteSpace();
+ return true;
+}
+
+//--------------------------------------------------------------------------
+// Does the embedded thumbnail match the jpeg image?
+//--------------------------------------------------------------------------
+#ifndef JPEG_TOL
+#define JPEG_TOL 0.02
+#endif
+bool ExifData::isThumbnailSane() {
+ if (Thumbnail.isNull()) return false;
+
+ // check whether thumbnail dimensions match the image
+ // not foolproof, but catches some altered images (jpegtran -rotate)
+ if (ExifImageLength != 0 && ExifImageLength != Height) return false;
+ if (ExifImageWidth != 0 && ExifImageWidth != Width) return false;
+ if (Thumbnail.width() == 0 || Thumbnail.height() == 0) return false;
+ if (Height == 0 || Width == 0) return false;
+ double d = (double)Height/Width*Thumbnail.width()/Thumbnail.height();
+ return (1-JPEG_TOL < d) && (d < 1+JPEG_TOL);
+}
+
+
+//--------------------------------------------------------------------------
+// return a thumbnail that respects the orientation flag
+// only if it seems sane
+//--------------------------------------------------------------------------
+QImage ExifData::getThumbnail() {
+ if (!isThumbnailSane()) return NULL;
+ if (!Orientation || Orientation == 1) return Thumbnail;
+
+ // now fix orientation
+ QWMatrix M;
+ QWMatrix flip= QWMatrix(-1,0,0,1,0,0);
+ switch (Orientation) { // notice intentional fallthroughs
+ case 2: M = flip; break;
+ case 4: M = flip;
+ case 3: M.rotate(180); break;
+ case 5: M = flip;
+ case 6: M.rotate(90); break;
+ case 7: M = flip;
+ case 8: M.rotate(270); break;
+ default: break; // should never happen
+ }
+ return Thumbnail.xForm(M);
+}
diff --git a/kfile-plugins/jpeg/exif.h b/kfile-plugins/jpeg/exif.h
new file mode 100644
index 00000000..2b4e5606
--- /dev/null
+++ b/kfile-plugins/jpeg/exif.h
@@ -0,0 +1,127 @@
+#ifndef __EXIF_H__
+#define __EXIF_H__
+
+/**
+ exif.h
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <time.h>
+
+#include "qstring.h"
+#include "qfile.h"
+#include "qimage.h"
+#include <kdebug.h>
+
+typedef enum {
+ READ_EXIF = 1,
+ READ_IMAGE = 2,
+ READ_ALL = 3
+}ReadMode_t;
+
+//--------------------------------------------------------------------------
+// This structure is used to store jpeg file sections in memory.
+typedef struct {
+ uchar * Data;
+ int Type;
+ unsigned Size;
+}Section_t;
+
+typedef unsigned char uchar;
+
+typedef struct {
+ unsigned short Tag;
+ const char*const Desc;
+}TagTable_t;
+
+#define MAX_SECTIONS 20
+#define PSEUDO_IMAGE_MARKER 0x123; // Extra value.
+
+class ExifData {
+ Section_t Sections[MAX_SECTIONS];
+
+ QString CameraMake;
+ QString CameraModel;
+ QString DateTime;
+ int Orientation;
+ int Height, Width;
+ int ExifImageLength, ExifImageWidth;
+ int IsColor;
+ int Process;
+ int FlashUsed;
+ float FocalLength;
+ float ExposureTime;
+ float ApertureFNumber;
+ float Distance;
+ int Whitebalance;
+ int MeteringMode;
+ float CCDWidth;
+ float ExposureBias;
+ int ExposureProgram;
+ int ISOequivalent;
+ int CompressionLevel;
+ QString UserComment;
+ QString Comment;
+ QImage Thumbnail;
+
+ int ReadJpegSections (QFile & infile, ReadMode_t ReadMode);
+ void DiscardData(void);
+ int Get16u(void * Short);
+ int Get32s(void * Long);
+ unsigned Get32u(void * Long);
+ double ConvertAnyFormat(void * ValuePtr, int Format);
+ void ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase, unsigned ExifLength,
+ unsigned NestingLevel);
+ void process_COM (const uchar * Data, int length);
+ void process_SOFn (const uchar * Data, int marker);
+ int Get16m(const void * Short);
+ void process_EXIF(unsigned char * CharBuf, unsigned int length);
+ int Exif2tm(struct tm * timeptr, char * ExifTime);
+
+public:
+ ExifData();
+ bool scan(const QString &);
+ QString getCameraMake() { return CameraMake; }
+ QString getCameraModel() { return CameraModel; }
+ QString getDateTime() { return DateTime; }
+ int getOrientation() { return Orientation; }
+ int getHeight() { return Height; }
+ int getWidth() { return Width; }
+ int getIsColor() { return IsColor; }
+ int getProcess() { return Process; }
+ int getFlashUsed() { return FlashUsed; }
+ float getFocalLength() { return FocalLength; }
+ float getExposureTime() { return ExposureTime; }
+ float getApertureFNumber() { return ApertureFNumber; }
+ float getDistance() { return Distance; }
+ int getWhitebalance() { return Whitebalance; }
+ int getMeteringMode() { return MeteringMode; }
+ float getCCDWidth() { return CCDWidth; }
+ float getExposureBias() { return ExposureBias; }
+ int getExposureProgram() { return ExposureProgram; }
+ int getISOequivalent() { return ISOequivalent; }
+ int getCompressionLevel() { return CompressionLevel; }
+ QString getUserComment() { return UserComment; }
+ QString getComment() { return Comment; }
+ QImage getThumbnail();
+ bool isThumbnailSane();
+ bool isNullThumbnail() { return !isThumbnailSane(); }
+};
+
+class FatalError {
+ const char* ex;
+public:
+ FatalError(const char* s) { ex = s; }
+ void debug_print() const { kdDebug(7034) << "exception: " << ex << endl; }
+};
+
+extern TagTable_t ProcessTable[];
+
+//--------------------------------------------------------------------------
+// Define comment writing code, impelemented in setcomment.c
+extern int safe_copy_and_modify( const char * original_filename, const char * comment );
+
+#endif
+
diff --git a/kfile-plugins/jpeg/kfile_jpeg.cpp b/kfile-plugins/jpeg/kfile_jpeg.cpp
new file mode 100644
index 00000000..d302cf65
--- /dev/null
+++ b/kfile-plugins/jpeg/kfile_jpeg.cpp
@@ -0,0 +1,531 @@
+/* This file is part of the KDE project
+ * Copyright (C) 2002 Frank Pieczynski <pieczy@knuut.de>,
+ * 2002 Carsten Pfeiffer <pfeiffer@kde.org>
+ * based on the jhead tool of Matthias Wandel (see below)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+#include <stdlib.h>
+#include "kfile_jpeg.h"
+
+#include <kurl.h>
+#include <kprocess.h>
+#include <klocale.h>
+#include <kgenericfactory.h>
+#include <kdebug.h>
+
+#include <qcstring.h>
+#include <qfile.h>
+#include <qdatetime.h>
+#include <qdict.h>
+#include <qvalidator.h>
+#include <qimage.h>
+
+#include "exif.h"
+
+#define EXIFGROUP "Jpeg EXIF Data"
+
+typedef KGenericFactory<KJpegPlugin> JpegFactory;
+
+K_EXPORT_COMPONENT_FACTORY(kfile_jpeg, JpegFactory("kfile_jpeg"))
+
+KJpegPlugin::KJpegPlugin(QObject *parent, const char *name,
+ const QStringList &args )
+ : KFilePlugin(parent, name, args)
+{
+ kdDebug(7034) << "jpeg plugin\n";
+
+ //
+ // define all possible meta info items
+ //
+ KFileMimeTypeInfo *info = addMimeTypeInfo("image/jpeg");
+ KFileMimeTypeInfo::GroupInfo *exifGroup = addGroupInfo( info, EXIFGROUP,
+ i18n("JPEG Exif") );
+ KFileMimeTypeInfo::ItemInfo* item;
+
+ item = addItemInfo( exifGroup, "Comment", i18n("Comment"), QVariant::String);
+ setAttributes( item,
+ KFileMimeTypeInfo::Modifiable |
+ KFileMimeTypeInfo::Addable |
+ KFileMimeTypeInfo::MultiLine );
+
+ item = addItemInfo( exifGroup, "Manufacturer", i18n("Camera Manufacturer"),
+ QVariant::String );
+
+ item = addItemInfo( exifGroup, "Model", i18n("Camera Model"),
+ QVariant::String );
+
+ item = addItemInfo( exifGroup, "Date/time", i18n("Date/Time"),
+ QVariant::DateTime );
+
+ item = addItemInfo( exifGroup, "CreationDate", i18n("Creation Date"),
+ QVariant::Date );
+
+ item = addItemInfo( exifGroup, "CreationTime", i18n("Creation Time"),
+ QVariant::Time );
+
+ item = addItemInfo( exifGroup, "Dimensions", i18n("Dimensions"),
+ QVariant::Size );
+ setHint( item, KFileMimeTypeInfo::Size );
+ setUnit( item, KFileMimeTypeInfo::Pixels );
+
+ item = addItemInfo( exifGroup, "Orientation", i18n("Orientation"),
+ QVariant::Int );
+
+ item = addItemInfo( exifGroup, "ColorMode", i18n("Color Mode"),
+ QVariant::String );
+
+ item = addItemInfo( exifGroup, "Flash used", i18n("Flash Used"),
+ QVariant::String );
+ item = addItemInfo( exifGroup, "Focal length", i18n("Focal Length"),
+ QVariant::String );
+ setUnit( item, KFileMimeTypeInfo::Millimeters );
+
+ item = addItemInfo( exifGroup, "35mm equivalent", i18n("35mm Equivalent"),
+ QVariant::Int );
+ setUnit( item, KFileMimeTypeInfo::Millimeters );
+
+ item = addItemInfo( exifGroup, "CCD width", i18n("CCD Width"),
+ QVariant::String );
+ setUnit( item, KFileMimeTypeInfo::Millimeters );
+
+ item = addItemInfo( exifGroup, "Exposure time", i18n("Exposure Time"),
+ QVariant::String );
+ setHint( item, KFileMimeTypeInfo::Seconds );
+
+ item = addItemInfo( exifGroup, "Aperture", i18n("Aperture"),
+ QVariant::String );
+
+ item = addItemInfo( exifGroup, "Focus dist.", i18n("Focus Dist."),
+ QVariant::String );
+
+ item = addItemInfo( exifGroup, "Exposure bias", i18n("Exposure Bias"),
+ QVariant::String );
+
+ item = addItemInfo( exifGroup, "Whitebalance", i18n("Whitebalance"),
+ QVariant::String );
+
+ item = addItemInfo( exifGroup, "Metering mode", i18n("Metering Mode"),
+ QVariant::String );
+
+ item = addItemInfo( exifGroup, "Exposure", i18n("Exposure"),
+ QVariant::String );
+
+ item = addItemInfo( exifGroup, "ISO equiv.", i18n("ISO Equiv."),
+ QVariant::String );
+
+ item = addItemInfo( exifGroup, "JPEG quality", i18n("JPEG Quality"),
+ QVariant::String );
+
+ item = addItemInfo( exifGroup, "User comment", i18n("User Comment"),
+ QVariant::String );
+ setHint(item, KFileMimeTypeInfo::Description);
+
+ item = addItemInfo( exifGroup, "JPEG process", i18n("JPEG Process"),
+ QVariant::String );
+
+ item = addItemInfo( exifGroup, "Thumbnail", i18n("Thumbnail"),
+ QVariant::Image );
+ setHint( item, KFileMimeTypeInfo::Thumbnail );
+
+// ###
+// exifGroup.setSupportsVariableKeys(true);
+}
+
+QValidator* KJpegPlugin::createValidator(const KFileMetaInfoItem& /*item*/,
+ QObject */*parent*/,
+ const char */*name*/ ) const
+{
+ // no need to return a validator that validates everything as OK :)
+// if (item.isEditable())
+// return new QRegExpValidator(QRegExp(".*"), parent, name);
+// else
+ return 0L;
+}
+
+bool KJpegPlugin::writeInfo( const KFileMetaInfo& info ) const
+{
+ QString comment = info[EXIFGROUP].value("Comment").toString();
+ QString path = info.path();
+
+ kdDebug(7034) << "exif writeInfo: " << info.path() << " \"" << comment << "\"\n";
+
+ /*
+ Do a strictly safe insertion of the comment:
+
+ Scan original to verify it's a proper jpeg
+ Open a unique temporary file in this directory
+ Write temporary, replacing all COM blocks with this one.
+ Scan temporary, to verify it's a proper jpeg
+ Rename original to another unique name
+ Rename temporary to original
+ Unlink original
+ */
+ /*
+ The jpeg standard does not regulate the contents of the COM block.
+ I'm assuming the best thing to do here is write as unicode utf-8,
+ which is fully backwards compatible with readers expecting ascii.
+ Readers expecting a national character set are out of luck...
+ */
+ if( safe_copy_and_modify( QFile::encodeName( path ), comment.utf8() ) ) {
+ return false;
+ }
+ return true;
+}
+
+bool KJpegPlugin::readInfo( KFileMetaInfo& info, uint what )
+{
+ const QString path( info.path() );
+ if ( path.isEmpty() ) // remote file
+ return false;
+
+ QString tag;
+ ExifData ImageInfo;
+
+ // parse the jpeg file now
+ try {
+ if ( !ImageInfo.scan(info.path()) ) {
+ kdDebug(7034) << "Not a JPEG file!\n";
+ return false;
+ }
+ }
+ catch (FatalError& e) { // malformed exif data?
+ kdDebug(7034) << "Exception caught while parsing Exif data of: " << info.path() << endl;
+ e.debug_print();
+ return false;
+ }
+
+ KFileMetaInfoGroup exifGroup = appendGroup( info, EXIFGROUP );
+
+ tag = ImageInfo.getComment();
+ if ( tag.length() ) {
+ kdDebug(7034) << "exif inserting Comment: " << tag << "\n";
+ appendItem( exifGroup, "Comment", tag );
+ } else {
+ appendItem( exifGroup, "Comment", tag ); // So user can add new comment
+ }
+
+ tag = ImageInfo.getCameraMake();
+ if (tag.length())
+ appendItem( exifGroup, "Manufacturer", tag );
+
+ tag = ImageInfo.getCameraModel();
+ if (tag.length())
+ appendItem( exifGroup, "Model", tag );
+
+ tag = ImageInfo.getDateTime();
+ if (tag.length()){
+ QDateTime dt = parseDateTime( tag.stripWhiteSpace() );
+ if ( dt.isValid() ) {
+ appendItem( exifGroup, "Date/time", dt );
+ appendItem( exifGroup, "CreationDate", dt.date() );
+ appendItem( exifGroup, "CreationTime", dt.time() );
+ }
+ }
+
+ appendItem( exifGroup,"Dimensions", QSize( ImageInfo.getWidth(),
+ ImageInfo.getHeight() ) );
+
+ if ( ImageInfo.getOrientation() )
+ appendItem( exifGroup, "Orientation", ImageInfo.getOrientation() );
+
+ appendItem( exifGroup, "ColorMode", ImageInfo.getIsColor() ?
+ i18n("Color") : i18n("Black and white") );
+
+ int flashUsed = ImageInfo.getFlashUsed(); // -1, <set>
+ if ( flashUsed >= 0 ) {
+ QString flash = i18n("Flash", "(unknown)");
+ switch ( flashUsed ) {
+ case 0: flash = i18n("Flash", "No");
+ break;
+ case 1:
+ case 5:
+ case 7:
+ flash = i18n("Flash", "Fired");
+ break;
+ case 9:
+ case 13:
+ case 15:
+ flash = i18n( "Flash", "Fill Fired" );
+ break;
+ case 16:
+ flash = i18n( "Flash", "Off" );
+ break;
+ case 24:
+ flash = i18n( "Flash", "Auto Off" );
+ break;
+ case 25:
+ case 29:
+ case 31:
+ flash = i18n( "Flash", "Auto Fired" );
+ break;
+ case 32:
+ flash = i18n( "Flash", "Not Available" );
+ break;
+ default:
+ break;
+ }
+ appendItem( exifGroup, "Flash used",
+ flash );
+ }
+
+ if (ImageInfo.getFocalLength()){
+ appendItem( exifGroup, "Focal length",
+ QString().sprintf("%4.1f", ImageInfo.getFocalLength()) );
+
+ if (ImageInfo.getCCDWidth()){
+ appendItem( exifGroup, "35mm equivalent",
+ (int)(ImageInfo.getFocalLength()/ImageInfo.getCCDWidth()*35 + 0.5) );
+ }
+ }
+
+ if (ImageInfo.getCCDWidth()){
+ appendItem( exifGroup, "CCD width",
+ QString().sprintf("%4.2f", ImageInfo.getCCDWidth()) );
+ }
+
+ if (ImageInfo.getExposureTime()){
+ tag=QString().sprintf("%6.3f", ImageInfo.getExposureTime());
+ float exposureTime = ImageInfo.getExposureTime();
+ if (exposureTime > 0 && exposureTime <= 0.5){
+ tag+=QString().sprintf(" (1/%d)", (int)(0.5 + 1/exposureTime) );
+ }
+ appendItem( exifGroup, "Exposure time", tag );
+ }
+
+ if (ImageInfo.getApertureFNumber()){
+ appendItem( exifGroup, "Aperture",
+ QString().sprintf("f/%3.1f",
+ (double)ImageInfo.getApertureFNumber()));
+ }
+
+ if (ImageInfo.getDistance()){
+ if (ImageInfo.getDistance() < 0){
+ tag=i18n("Infinite");
+ }else{
+ tag=QString().sprintf("%5.2fm",(double)ImageInfo.getDistance());
+ }
+ appendItem( exifGroup, "Focus dist.", tag );
+ }
+
+ if (ImageInfo.getExposureBias()){
+ appendItem( exifGroup, "Exposure bias",
+ QString().sprintf("%4.2f",
+ (double)ImageInfo.getExposureBias()) );
+ }
+
+ if (ImageInfo.getWhitebalance() != -1){
+ switch(ImageInfo.getWhitebalance()) {
+ case 0:
+ tag=i18n("Unknown");
+ break;
+ case 1:
+ tag=i18n("Daylight");
+ break;
+ case 2:
+ tag=i18n("Fluorescent");
+ break;
+ case 3:
+ //tag=i18n("incandescent");
+ tag=i18n("Tungsten");
+ break;
+ case 17:
+ tag=i18n("Standard light A");
+ break;
+ case 18:
+ tag=i18n("Standard light B");
+ break;
+ case 19:
+ tag=i18n("Standard light C");
+ break;
+ case 20:
+ tag=i18n("D55");
+ break;
+ case 21:
+ tag=i18n("D65");
+ break;
+ case 22:
+ tag=i18n("D75");
+ break;
+ case 255:
+ tag=i18n("Other");
+ break;
+ default:
+ //23 to 254 = reserved
+ tag=i18n("Unknown");
+ }
+ appendItem( exifGroup, "Whitebalance", tag );
+ }
+
+ if (ImageInfo.getMeteringMode() != -1){
+ switch(ImageInfo.getMeteringMode()) {
+ case 0:
+ tag=i18n("Unknown");
+ break;
+ case 1:
+ tag=i18n("Average");
+ break;
+ case 2:
+ tag=i18n("Center weighted average");
+ break;
+ case 3:
+ tag=i18n("Spot");
+ break;
+ case 4:
+ tag=i18n("MultiSpot");
+ break;
+ case 5:
+ tag=i18n("Pattern");
+ break;
+ case 6:
+ tag=i18n("Partial");
+ break;
+ case 255:
+ tag=i18n("Other");
+ break;
+ default:
+ // 7 to 254 = reserved
+ tag=i18n("Unknown");
+ }
+ appendItem( exifGroup, "Metering mode", tag );
+ }
+
+ if (ImageInfo.getExposureProgram()){
+ switch(ImageInfo.getExposureProgram()) {
+ case 0:
+ tag=i18n("Not defined");
+ break;
+ case 1:
+ tag=i18n("Manual");
+ break;
+ case 2:
+ tag=i18n("Normal program");
+ break;
+ case 3:
+ tag=i18n("Aperture priority");
+ break;
+ case 4:
+ tag=i18n("Shutter priority");
+ break;
+ case 5:
+ tag=i18n("Creative program\n(biased toward fast shutter speed)");
+ break;
+ case 6:
+ tag=i18n("Action program\n(biased toward fast shutter speed)");
+ break;
+ case 7:
+ tag=i18n("Portrait mode\n(for closeup photos with the background out of focus)");
+ break;
+ case 8:
+ tag=i18n("Landscape mode\n(for landscape photos with the background in focus)");
+ break;
+ default:
+ // 9 to 255 = reserved
+ tag=i18n("Unknown");
+ }
+ appendItem( exifGroup, "Exposure", tag );
+ }
+
+ if (ImageInfo.getISOequivalent()){
+ appendItem( exifGroup, "ISO equiv.",
+ QString().sprintf("%2d",
+ (int)ImageInfo.getISOequivalent()) );
+ }
+
+ if (ImageInfo.getCompressionLevel()){
+ switch(ImageInfo.getCompressionLevel()) {
+ case 1:
+ tag=i18n("Basic");
+ break;
+ case 2:
+ tag=i18n("Normal");
+ break;
+ case 4:
+ tag=i18n("Fine");
+ break;
+ default:
+ tag=i18n("Unknown");
+ }
+ appendItem( exifGroup, "JPEG quality", tag );
+ }
+
+ tag = ImageInfo.getUserComment();
+ if (tag.length()){
+ appendItem( exifGroup, "EXIF comment", tag );
+ }
+
+ int a;
+ for (a=0;;a++){
+ if (ProcessTable[a].Tag == ImageInfo.getProcess() || ProcessTable[a].Tag == 0){
+ appendItem( exifGroup, "JPEG process",
+ QString::fromUtf8( ProcessTable[a].Desc) );
+ break;
+ }
+ }
+
+ if ( what & KFileMetaInfo::Thumbnail && !ImageInfo.isNullThumbnail() ){
+ appendItem( exifGroup, "Thumbnail", ImageInfo.getThumbnail() );
+ }
+
+ return true;
+}
+
+// format of the string is:
+// YYYY:MM:DD HH:MM:SS
+QDateTime KJpegPlugin::parseDateTime( const QString& string )
+{
+ QDateTime dt;
+ if ( string.length() != 19 )
+ return dt;
+
+ QString year = string.left( 4 );
+ QString month = string.mid( 5, 2 );
+ QString day = string.mid( 8, 2 );
+ QString hour = string.mid( 11, 2 );
+ QString minute = string.mid( 14, 2 );
+ QString seconds = string.mid( 17, 2 );
+
+ bool ok;
+ bool allOk = true;
+ int y = year.toInt( &ok );
+ allOk &= ok;
+
+ int mo = month.toInt( &ok );
+ allOk &= ok;
+
+ int d = day.toInt( &ok );
+ allOk &= ok;
+
+ int h = hour.toInt( &ok );
+ allOk &= ok;
+
+ int mi = minute.toInt( &ok );
+ allOk &= ok;
+
+ int s = seconds.toInt( &ok );
+ allOk &= ok;
+
+ if ( allOk ) {
+ dt.setDate( QDate( y, mo, d ) );
+ dt.setTime( QTime( h, mi, s ) );
+ }
+
+ return dt;
+}
+
+#include "kfile_jpeg.moc"
diff --git a/kfile-plugins/jpeg/kfile_jpeg.desktop b/kfile-plugins/jpeg/kfile_jpeg.desktop
new file mode 100644
index 00000000..3ca5a011
--- /dev/null
+++ b/kfile-plugins/jpeg/kfile_jpeg.desktop
@@ -0,0 +1,64 @@
+[Desktop Entry]
+Type=Service
+Name=JPEG EXIF Info
+Name[ar]=معلومات JPEG EXIF
+Name[br]=Titouroù EXIF JPEG
+Name[ca]=Informació de JPEG EXIF
+Name[cs]=JPEG EXIF info
+Name[cy]=Gwybodaeth JPEG EXIF
+Name[da]=JPEG EXIF-info
+Name[de]=JPEG EXIF-Info
+Name[el]=Πληροφορίες JPEG EXIF
+Name[eo]=JPEG-EXIF-informo
+Name[es]=Info JPEG EXIF
+Name[et]=JPEG EXIF info
+Name[fa]=اطلاعات JPEG EXIF
+Name[fi]=JPEG EXIF -tiedot
+Name[fr]=Informations JPEG EXIF
+Name[gl]=Inf. JPEG EXIF
+Name[he]=מידע JPEG EXIF
+Name[hi]=JPEG EXIF जानकारी
+Name[hr]=JPEG EXIF Informacije
+Name[hu]=JPEG EXIF-jellemzők
+Name[is]=JPEG EXIF upplýsingar
+Name[it]=Informazioni JPEG EXIF
+Name[ja]=JPEG EXIF 情報
+Name[kk]=JPEG EXIF мәліметі
+Name[km]=ព័ត៌មាន JPEG EXIF
+Name[lt]=JPEG EXIF informacija
+Name[ms]=Maklumat JPEG EXIF
+Name[nds]=JPEG-EXIF-Info
+Name[ne]=JPEG EXIF सूचना
+Name[nl]=JPEG EXIF-info
+Name[nn]=JPEG EXIF-info
+Name[nso]=TshedimoJPEG EXIF Info
+Name[pa]=JPEG EXIF ਜਾਣਕਾਰੀ
+Name[pl]=Informacja o pliku JPEG EXIF
+Name[pt]=Informação do JPEG EXIF
+Name[pt_BR]=Informação sobre JPEG EXIF
+Name[ro]=Informaţii EXIF JPEG
+Name[ru]=Информация о JPEG EXIF
+Name[se]=JPEG EXIF-dieđut
+Name[sl]=Podatki o JPEG EXIF
+Name[sr]=JPEG EXIF информације
+Name[sr@Latn]=JPEG EXIF informacije
+Name[sv]=JPEG EXIF-information
+Name[ta]=JPEG EXIF தகவல்
+Name[tg]=Иттилоот оиди JPEG EXIF
+Name[th]=ข้อมูลแฟ้ม JPEG EXIF
+Name[tr]=JPEG EXIF Bilgisi
+Name[uk]=Інформація про JPEG EXIF
+Name[uz]=JPEG EXIF haqida maʼlumot
+Name[uz@cyrillic]=JPEG EXIF ҳақида маълумот
+Name[ven]=Mafhungo a JPEG EXIF
+Name[wa]=Informåcion sol imådje JPEG EXIF
+Name[xh]=Ulwazi lwe JPEG EXIF
+Name[zh_CN]=JPEG EXIF 信息
+Name[zh_HK]=JPEG EXIF 資訊
+Name[zh_TW]=JPEG EXIF 資訊
+Name[zu]=Ulwazi lwe-JPEG EXIF
+ServiceTypes=KFilePlugin
+X-KDE-Library=kfile_jpeg
+MimeType=image/jpeg
+PreferredItems=User comment,CreationDate,CreationTime,Dimensions,Exposure time,JPEG quality,Comment
+SupportsThumbnail=true
diff --git a/kfile-plugins/jpeg/kfile_jpeg.h b/kfile-plugins/jpeg/kfile_jpeg.h
new file mode 100644
index 00000000..5c585fb0
--- /dev/null
+++ b/kfile-plugins/jpeg/kfile_jpeg.h
@@ -0,0 +1,43 @@
+/* This file is part of the KDE project
+ * Copyright (C) 2002 Frank Pieczynski
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef __KFILE_JPEG_H__
+#define __KFILE_JPEG_H__
+
+#include <qdatetime.h>
+#include <kfilemetainfo.h>
+
+class KJpegPlugin: public KFilePlugin
+{
+ Q_OBJECT
+
+public:
+ KJpegPlugin( QObject *parent, const char *name,
+ const QStringList& args );
+
+ virtual bool readInfo ( KFileMetaInfo& info, uint what );
+ virtual bool writeInfo( const KFileMetaInfo& info ) const;
+ virtual QValidator* createValidator( const KFileMetaInfoItem& item,
+ QObject* parent, const char* name) const;
+
+private:
+ QDateTime parseDateTime( const QString& string );
+};
+
+#endif
diff --git a/kfile-plugins/jpeg/kfile_setcomment.cpp b/kfile-plugins/jpeg/kfile_setcomment.cpp
new file mode 100644
index 00000000..07dca273
--- /dev/null
+++ b/kfile-plugins/jpeg/kfile_setcomment.cpp
@@ -0,0 +1,536 @@
+/*
+ * setcomment.cpp
+ *
+ * Copyright 2002 Bryce Nesbitt
+ *
+ * Based on wrjpgcom.c, Copyright (C) 1994-1997, Thomas G. Lane.
+ * Part of the Independent JPEG Group's software release 6b of 27-Mar-1998
+ *
+ * This file contains a very simple stand-alone application that inserts
+ * user-supplied text as a COM (comment) marker in a JPEG/JFIF file.
+ * This may be useful as an example of the minimum logic needed to parse
+ * JPEG markers.
+ *
+ * There can be an arbitrary number of COM blocks in each jpeg file, with
+ * up to 64K of data each. We, however, write just one COM and blow away
+ * the rest.
+ *
+ *****************
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+#undef STANDALONE_COMPILE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include "config.h"
+
+extern int safe_copy_and_modify( const char * original_filename, const char * comment );
+
+#ifdef DONT_USE_B_MODE /* define mode parameters for fopen() */
+#define READ_BINARY "r"
+#define WRITE_BINARY "w"
+#else
+#ifdef VMS /* VMS is very nonstandard */
+#define READ_BINARY "rb", "ctx=stm"
+#define WRITE_BINARY "wb", "ctx=stm"
+#else /* standard ANSI-compliant case */
+#define READ_BINARY "rb"
+#define WRITE_BINARY "wb"
+#endif
+#endif
+
+#define WARNING_GARBAGE 1 /* Original file had some unspecified content */
+#define ERROR_NOT_A_JPEG 5 /* Original file not a proper jpeg (must be 1st) */
+#define ERROR_TEMP_FILE 6 /* Problem writing temporay file */
+#define ERROR_SCREWUP 7 /* Original file is now damaged. Ooops. */
+#define ERROR_PREMATURE_EOF 8 /* Unexpected end of file */
+#define ERROR_BAD_MARKER 9 /* Marker with illegal length */
+#define ERROR_MARKER_ORDER 10 /* File seems to be mixed up */
+
+static int global_error; /* global error flag. Once set, we're dead. */
+
+/****************************************************************************/
+/*
+ * These macros are used to read the input file and write the output file.
+ * To reuse this code in another application, you might need to change these.
+ */
+static FILE * infile; /* input JPEG file */
+
+/* Return next input byte, or EOF if no more */
+#define NEXTBYTE() getc(infile)
+
+static FILE * outfile; /* output JPEG file */
+
+/* Emit an output byte */
+#define PUTBYTE(x) putc((x), outfile)
+
+
+/****************************************************************************/
+/* Read one byte, testing for EOF */
+static int
+read_1_byte (void)
+{
+ int c;
+
+ c = NEXTBYTE();
+ if (c == EOF) {
+ global_error = ERROR_PREMATURE_EOF;
+ }
+ return c;
+}
+
+/* Read 2 bytes, convert to unsigned int */
+/* All 2-byte quantities in JPEG markers are MSB first */
+static unsigned int
+read_2_bytes (void)
+{
+ int c1, c2;
+
+ c1 = NEXTBYTE();
+ if (c1 == EOF)
+ global_error = ERROR_PREMATURE_EOF;
+ c2 = NEXTBYTE();
+ if (c2 == EOF)
+ global_error = ERROR_PREMATURE_EOF;
+ return (((unsigned int) c1) << 8) + ((unsigned int) c2);
+}
+
+
+/****************************************************************************/
+/* Routines to write data to output file */
+static void
+write_1_byte (int c)
+{
+ PUTBYTE(c);
+}
+
+static void
+write_2_bytes (unsigned int val)
+{
+ PUTBYTE((val >> 8) & 0xFF);
+ PUTBYTE(val & 0xFF);
+}
+
+static void
+write_marker (int marker)
+{
+ PUTBYTE(0xFF);
+ PUTBYTE(marker);
+}
+
+static void
+copy_rest_of_file (void)
+{
+ int c;
+
+ while ((c = NEXTBYTE()) != EOF)
+ PUTBYTE(c);
+}
+
+
+/****************************************************************************/
+/*
+ * JPEG markers consist of one or more 0xFF bytes, followed by a marker
+ * code byte (which is not an FF). Here are the marker codes of interest
+ * in this program. (See jdmarker.c for a more complete list.)
+ */
+
+#define M_SOF0 0xC0 /* Start Of Frame N */
+#define M_SOF1 0xC1 /* N indicates which compression process */
+#define M_SOF2 0xC2 /* Only SOF0-SOF2 are now in common use */
+#define M_SOF3 0xC3
+#define M_SOF5 0xC5 /* NB: codes C4 and CC are NOT SOF markers */
+#define M_SOF6 0xC6
+#define M_SOF7 0xC7
+#define M_SOF9 0xC9
+#define M_SOF10 0xCA
+#define M_SOF11 0xCB
+#define M_SOF13 0xCD
+#define M_SOF14 0xCE
+#define M_SOF15 0xCF
+#define M_SOI 0xD8 /* Start Of Image (beginning of datastream) */
+#define M_EOI 0xD9 /* End Of Image (end of datastream) */
+#define M_SOS 0xDA /* Start Of Scan (begins compressed data) */
+#define M_COM 0xFE /* COMment */
+
+
+/*
+ * Find the next JPEG marker and return its marker code.
+ * We expect at least one FF byte, possibly more if the compressor used FFs
+ * to pad the file. (Padding FFs will NOT be replicated in the output file.)
+ * There could also be non-FF garbage between markers. The treatment of such
+ * garbage is unspecified; we choose to skip over it but emit a warning msg.
+ * NB: this routine must not be used after seeing SOS marker, since it will
+ * not deal correctly with FF/00 sequences in the compressed image data...
+ */
+static int
+next_marker (void)
+{
+ int c;
+ int discarded_bytes = 0;
+
+ /* Find 0xFF byte; count and skip any non-FFs. */
+ c = read_1_byte();
+ while (c != 0xFF) {
+ discarded_bytes++;
+ c = read_1_byte();
+ }
+ /* Get marker code byte, swallowing any duplicate FF bytes. Extra FFs
+ * are legal as pad bytes, so don't count them in discarded_bytes.
+ */
+ do {
+ c = read_1_byte();
+ } while (c == 0xFF);
+
+ if (discarded_bytes != 0) {
+ global_error = WARNING_GARBAGE;
+ }
+
+ return c;
+}
+
+
+/*
+ * Most types of marker are followed by a variable-length parameter segment.
+ * This routine skips over the parameters for any marker we don't otherwise
+ * want to process.
+ * Note that we MUST skip the parameter segment explicitly in order not to
+ * be fooled by 0xFF bytes that might appear within the parameter segment;
+ * such bytes do NOT introduce new markers.
+ */
+static void
+copy_variable (void)
+/* Copy an unknown or uninteresting variable-length marker */
+{
+ unsigned int length;
+
+ /* Get the marker parameter length count */
+ length = read_2_bytes();
+ write_2_bytes(length);
+ /* Length includes itself, so must be at least 2 */
+ if (length < 2) {
+ global_error = ERROR_BAD_MARKER;
+ length = 2;
+ }
+ length -= 2;
+ /* Skip over the remaining bytes */
+ while (length > 0) {
+ write_1_byte(read_1_byte());
+ length--;
+ }
+}
+
+static void
+skip_variable (void)
+/* Skip over an unknown or uninteresting variable-length marker */
+{
+ unsigned int length;
+
+ /* Get the marker parameter length count */
+ length = read_2_bytes();
+ /* Length includes itself, so must be at least 2 */
+ if (length < 2) {
+ global_error = ERROR_BAD_MARKER;
+ length = 2;
+ }
+ length -= 2;
+ /* Skip over the remaining bytes */
+ while (length > 0) {
+ (void) read_1_byte();
+ length--;
+ }
+}
+
+
+static int
+scan_JPEG_header (int keep_COM)
+/*
+ * Parse & copy the marker stream until SOFn or EOI is seen;
+ * copy data to output, but discard COM markers unless keep_COM is true.
+ */
+{
+ int c1, c2;
+ int marker;
+
+ /*
+ * Read the initial marker, which should be SOI.
+ * For a JFIF file, the first two bytes of the file should be literally
+ * 0xFF M_SOI. To be more general, we could use next_marker, but if the
+ * input file weren't actually JPEG at all, next_marker might read the whole
+ * file and then return a misleading error message...
+ */
+ c1 = NEXTBYTE();
+ c2 = NEXTBYTE();
+ if (c1 != 0xFF || c2 != M_SOI) {
+ global_error = ERROR_NOT_A_JPEG;
+ return EOF;
+ }
+
+ write_marker(M_SOI);
+
+ /* Scan miscellaneous markers until we reach SOFn. */
+ for (;;) {
+ marker = next_marker();
+ switch (marker) {
+ /* Note that marker codes 0xC4, 0xC8, 0xCC are not, and must not be,
+ * treated as SOFn. C4 in particular is actually DHT.
+ */
+ case M_SOF0: /* Baseline */
+ case M_SOF1: /* Extended sequential, Huffman */
+ case M_SOF2: /* Progressive, Huffman */
+ case M_SOF3: /* Lossless, Huffman */
+ case M_SOF5: /* Differential sequential, Huffman */
+ case M_SOF6: /* Differential progressive, Huffman */
+ case M_SOF7: /* Differential lossless, Huffman */
+ case M_SOF9: /* Extended sequential, arithmetic */
+ case M_SOF10: /* Progressive, arithmetic */
+ case M_SOF11: /* Lossless, arithmetic */
+ case M_SOF13: /* Differential sequential, arithmetic */
+ case M_SOF14: /* Differential progressive, arithmetic */
+ case M_SOF15: /* Differential lossless, arithmetic */
+ return marker;
+
+ case M_SOS: /* should not see compressed data before SOF */
+ global_error = ERROR_MARKER_ORDER;
+ break;
+
+ case M_EOI: /* in case it's a tables-only JPEG stream */
+ return marker;
+
+ case M_COM: /* Existing COM: conditionally discard */
+ if (keep_COM) {
+ write_marker(marker);
+ copy_variable();
+ } else {
+ skip_variable();
+ }
+ break;
+
+ default: /* Anything else just gets copied */
+ write_marker(marker);
+ copy_variable(); /* we assume it has a parameter count... */
+ break;
+ }
+ } /* end loop */
+}
+
+
+/****************************************************************************/
+/*
+ Verify we know how to set the comment on this type of file.
+
+ TODO: The actual check! This should verify
+ the image size promised in the headers matches the file,
+ and that all markers are properly formatted.
+*/
+static int validate_image_file( const char * filename )
+{
+int status = 1;
+int c1, c2;
+
+ if ( (infile = fopen(filename, READ_BINARY)) ) {
+ c1 = NEXTBYTE();
+ c2 = NEXTBYTE();
+ if (c1 != 0xFF || c2 != M_SOI)
+ status = ERROR_NOT_A_JPEG;
+ else
+ status = 0;
+ fclose( infile );
+ }
+ return( status );
+}
+
+
+/****************************************************************************/
+/*
+ Modify the file in place, but be paranoid and safe about it.
+ It's worth a few extra CPU cycles to make sure we never
+ destory an original image:
+ 1) Validate the input file.
+ 2) Open a temporary file.
+ 3) Copy the data, writing a new comment block.
+ 4) Validate the temporary file.
+ 5) Move the temporary file over the original.
+
+ To be even more paranoid & safe we could:
+ 5) Rename the original to a different temporary name.
+ 6) Rename the temporary to the original.
+ 7) Delete the original.
+*/
+extern int safe_copy_and_modify( const char * original_filename, const char * comment )
+{
+char * temp_filename;
+int temp_filename_length;
+int comment_length = 0;
+int marker;
+int i;
+struct stat statbuf;
+
+ global_error = 0;
+
+ /*
+ * Make sure we're dealing with a proper input file. Safety first!
+ */
+ if( validate_image_file( original_filename ) ) {
+ fprintf(stderr, "error validating original file %s\n", original_filename);
+ return(ERROR_NOT_A_JPEG);
+ }
+
+ /* Get a unique temporary file in the same directory. Hopefully
+ * if things go wrong, this file will still be left for recovery purposes.
+ *
+ * NB: I hate these stupid string functions in C... the buffer length is too
+ * hard to manage...
+ */
+ outfile = NULL;
+ temp_filename_length = strlen( original_filename) + 4;
+ temp_filename = (char *)calloc( temp_filename_length, 1 );
+ for( i=0; i<10; i++ ) {
+ snprintf( temp_filename, temp_filename_length, "%s%d", original_filename, i );
+ if( stat( temp_filename, &statbuf ) ) {
+ outfile = fopen(temp_filename, WRITE_BINARY);
+ break;
+ }
+ }
+ if( !outfile ) {
+ fprintf(stderr, "failed opening temporary file %s\n", temp_filename);
+ free(temp_filename);
+ return(ERROR_TEMP_FILE);
+ }
+
+
+ /*
+ * Let's rock and roll!
+ */
+ if ((infile = fopen(original_filename, READ_BINARY)) == NULL) {
+ fprintf(stderr, "can't open input file %s\n", original_filename);
+ free(temp_filename);
+ return(ERROR_NOT_A_JPEG);
+ }
+ /* Copy JPEG headers until SOFn marker;
+ * we will insert the new comment marker just before SOFn.
+ * This (a) causes the new comment to appear after, rather than before,
+ * existing comments; and (b) ensures that comments come after any JFIF
+ * or JFXX markers, as required by the JFIF specification.
+ */
+ marker = scan_JPEG_header(0);
+ /* Insert the new COM marker, but only if nonempty text has been supplied */
+ if (comment) {
+ comment_length = strlen( comment );
+ }
+ if (comment_length > 0) {
+ write_marker(M_COM);
+ write_2_bytes(comment_length + 2);
+ while (comment_length > 0) {
+ write_1_byte(*comment++);
+ comment_length--;
+ }
+ }
+ /* Duplicate the remainder of the source file.
+ * Note that any COM markers occurring after SOF will not be touched.
+ *
+ * :TODO: Discard COM markers occurring after SOF
+ */
+ write_marker(marker);
+ copy_rest_of_file();
+ fclose( infile );
+ fsync( fileno( outfile) ); /* Make sure its really on disk first. !!!VERY IMPORTANT!!! */
+ if ( fclose( outfile ) ) {
+ fprintf(stderr, "error in temporary file %s\n", temp_filename);
+ free(temp_filename);
+ return(ERROR_TEMP_FILE);
+ }
+
+
+ /*
+ * Make sure we did it right. We've already fsync()'ed the file. Safety first!
+ */
+ if( validate_image_file( temp_filename ) ) {
+ fprintf(stderr, "error in temporary file %s\n", temp_filename);
+ free(temp_filename);
+ return(ERROR_TEMP_FILE);
+ }
+
+ if( global_error >= ERROR_NOT_A_JPEG ) {
+ fprintf(stderr, "error %d processing %s\n", global_error, original_filename);
+ free(temp_filename);
+ return(ERROR_NOT_A_JPEG);
+ }
+
+ if( rename( temp_filename, original_filename ) ) {
+ fprintf(stderr, "error renaming %s to %s\n", temp_filename, original_filename);
+ free(temp_filename);
+ return(ERROR_TEMP_FILE);
+ }
+ free(temp_filename);
+
+ return(0);
+}
+
+
+#ifdef STANDALONE_COMPILE
+int
+main (int argc, char **argv)
+{
+ char * progname; /* program name for error messages */
+ char * filename;
+ char * comment;
+ FILE * fp;
+ int error;
+
+ /* Process command line arguments... */
+ progname = argv[0];
+ if (progname == NULL || progname[0] == 0)
+ progname = "writejpgcomment"; /* in case C library doesn't provide it */
+ if( argc != 3) {
+ fprintf(stderr, "Usage: %s <filename> \"<comment>\"\nOverwrites the comment in a image file with the given comment.\n", progname);
+ return(5);
+ }
+ filename = argv[1];
+ comment = argv[2];
+
+
+ /* Check if file is readable... */
+ if ((fp = fopen(filename, READ_BINARY)) == NULL) {
+ fprintf(stderr, "Error: Can't open file %s\n", filename);
+ fclose(fp);
+ return(5);
+ }
+ fclose(fp);
+
+ /* Check if we really have a commentable image file here... */
+ if( validate_image_file( filename ) ) {
+ fprintf(stderr, "Error: file %s is not of a supported type\n", filename);
+ return(5);
+ }
+
+ /* Lets do it... modify the comment in place */
+ if ((error = safe_copy_and_modify( filename, comment ) )) {
+ fprintf(stderr, "Error: %d setting jpg comment\n", error);
+ return(10);
+ }
+
+
+ /* TODO: Read comment back out of jpg and display it */
+ return( 0 );
+}
+#endif