Bug 144604 - Rotation causes Exif data corruption
Summary: Rotation causes Exif data corruption
Status: RESOLVED FIXED
Alias: None
Product: digikam
Classification: Applications
Component: Plugin-Bqm-Rotate (show other bugs)
Version: unspecified
Platform: Ubuntu Linux
: NOR normal
Target Milestone: ---
Assignee: Digikam Developers
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2007-04-24 14:33 UTC by Isaac Wilcox
Modified: 2018-03-23 12:08 UTC (History)
1 user (show)

See Also:
Latest Commit:
Version Fixed In: 0.1.0


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Isaac Wilcox 2007-04-24 14:33:01 UTC
Version:           0.9.1 (Ubuntu package 2:0.9.1-1ubuntu4) (using KDE KDE 3.5.6)
Installed from:    Ubuntu Packages
OS:                Linux

Hi,

Rotating an image in Digikam (either using [right click] -> Rotate -> Rotate 90 or using Image -> Rotate) causes a subtle corruption of the Exif tag for me.  Some Exif tags relating to the image dimensions get added/rewritten with an invalid type.

I've added the hex ID for each tag in brackets after the exiv2 name, because some programs/Exif descriptions out there seem to use non-standard names for the tags (*sigh*).

The original photo has two tags relevant to this bug:
  Exif.Photo.PixelXDimension (0xA002)
  Exif.Photo.PixelYDimension (0xA003)

According to the Exif spec (2.2, page 26-27), these tags must be of Exif type SHORT or LONG, and before doing the rotate the exiv2 commandline tool tells me they're correct:
  $ exiv2 -p t test.jpg | grep 'Exif.Photo.Pixel[XY]Dimension'
  Exif.Photo.PixelXDimension                   Short       1  2272
  Exif.Photo.PixelYDimension                   Short       1  1704

After doing doing the rotation they've changed to SLONG, which isn't valid:
  $ exiv2 -p t test.jpg | grep 'Exif.Photo.Pixel[XY]Dimension'
  Exif.Photo.PixelXDimension                   SLong       1  1704
  Exif.Photo.PixelYDimension                   SLong       1  2272


Similarly, the rotation adds two tags that the original never had:
  Exif.Image.ImageWidth (0x0100)
  Exif.Image.ImageLength (0x0101)

Again, the Exif spec (2.2, page 17) says these must be SHORT or LONG, not SLONG, but they get written as SLONGs:
  $ exiv2 -p t test.jpg | grep 'Exif.Image.Image.*th'
  Exif.Image.ImageWidth                        SLong       1  1704
  Exif.Image.ImageLength                       SLong       1  2272

This causes at least one tool (exifiron) to declare the Exif tag invalid and (understandably) refuse to touch the file.

I suspect that libexiv2 is being given the wrong type by Digikam/the Kipi Lossless plugin/both, because exiv2 does pick sensible default types, but is happy to let you override them (at least in the commandline program).

Two things that might be worth noting here.  Firstly, I'm on x86-64, which might somehow have a bearing on types Digikam is choosing.  Secondly, exiv2 0.12, the version on my Ubuntu Feisty system, has a bug that prevents tag types from being rewritten unless the tag grows (see http://dev.robotbattle.com/bugs/view.php?id=0000452).  It's fixed in 0.13, but the workaround that'll always fix the tags is to remove them and re-add them, e.g. for each broken one do something like:
  $ # save the dimension somehow into $saveddimension, or your head
  $ exiv2 -M"del Exif.Photo.PixelYDimension" modify IMG_2255.jpg
  $ exiv2 -M"add Exif.Photo.PixelYDimension $saveddimension" modify IMG_2255.jpg
exiv2 will choose a valid default type, but you can override it if you're paranoid.

Cheers,

Zak (Isaac Wilcox)
Comment 1 caulier.gilles 2007-04-24 20:20:22 UTC
Isaac,

Witch kipi-plugins, Exiv2, and libkexiv2 releases you use on your computer ?

Gilles Caulier
Comment 2 caulier.gilles 2007-04-24 20:23:20 UTC
Andreas,

We have 2 issues : the bug is relevant of Exiv2 or libkexiv2.

Personally, i suspect a problem in libkexiv2. What do you think about ?

Gilles
Comment 3 Isaac Wilcox 2007-04-24 20:27:57 UTC
Gilles,

Ubuntu seems to divide them like this:

kipi-plugins:
  0.1.3-1ubuntu1
libexiv2-0.12:
  0.12-0ubuntu2
exiv2:
  0.12-0ubuntu2
libkexiv2:
  0.1.1-0ubuntu1
Comment 4 Andreas Huggel 2007-04-25 05:07:25 UTC
Gilles,
This doesn't sound like an exiv2 issue and I have no other feedback of related problems with exiv2 alone.
The exiv2 philosophy is to do exactly what the user asks for. It doesn't add or delete tags by itself and it doesn't modify tag types unless told to do so. But as the bug reporter (and the exiv2 man page) said, it will do things which are not consistent with the Exif standard if you ask it to.

Regarding default types, LONG is used for ImageWidth/Length as well as PixelX/YDimension tags, in conformance with the standard.

Andreas
Comment 5 caulier.gilles 2007-04-26 08:23:43 UTC
Andreas,

I have take a look into libkexiv2 and Exiv2 source code, and i think than the problem come from Exiv2. Let's me explain why :

- in libkexiv2, nothing special is done. The rotation tool set the tags values as well without force to use a tag format. All is delegate to Exiv2.

- in Exiv2::tags.cpp, the "Exif.Photo.PixelXDimension", "Exif.Photo.PixelYDimension", "Exif.Image.ImageWidth", and "Exif.Image.ImageLength" tags are define like "Unsigned Long" not "Long", and this is the problem. Like Isac said, the Exif spec. (http://www.exif.org/Exif2-2.PDF page 23, 26, 27) must be "Long", not "Unsigned Long".

Fixing it into Exiv2 is a 5 minutes job (:=)))...

Gilles
Comment 6 Gerhard Kulzer 2007-04-26 10:01:04 UTC
Am Thursday 26 April 2007 schrieb Gilles Caulier:
[bugs.kde.org quoted mail]

I wonder if there is a similar problem with the GPS tags. Since a few days I'm 
using the google.maps module in Gallery2.2 which reads the coordinates from 
digikam images. They are wrongly interpreted. If I use jpg from other sources 
than digikam, the coordinates are right. 

I haven't hda the time to dig into the problem, it could well be that the 
gallery modules is at fault. But if you have a look to the tags.cpp anyway, 
Andreas, please check the gps tag section as well.

Thanks 
Gerhard
Comment 7 Andreas Huggel 2007-04-26 13:26:28 UTC
Gilles,

Exiv2 typedef unsignedLong is correct for Exif type LONG. 

There is a misunderstanding here. The Exif standard uses a terminology which is different from C++:

Types as defined in the Exif standard (page 14)
Value  Name    Desc
4      LONG    A 32 bit unsigned integer
9      SLONG   A 32 bit signed integer

Exiv2 uses typedefs unsignedLong and signedLong and names "Long" and "SLong" respectively.

Can you please point me to the libkexiv2 code I should look at?

Have you tried to reproduce the problem in digiKam on your computer? If you can, can you also reproduce the problem with the exiv2 tool alone, just by running the same commands using the modify action?


Gerhard,

I suggest you report this as a separate issue, once you have more information and it still looks like digiKam/exiv2 is not doing the right thing. The question here is whether the GPS tags in those images conform to the Exif standard. For a start, use exiv2 -pt and exiv2 -pv to check.

-ahu.
Comment 8 caulier.gilles 2007-04-26 20:25:15 UTC
Andreas,

This is the method from libkexiv2 used to update image dimensions after rotation :

bool KExiv2::setImageDimensions(const QSize& size, bool setProgramName)
{
    if (!setProgramId(setProgramName))
        return false;

    try
    {    
        d->exifMetadata["Exif.Image.ImageWidth"]      = size.width();
        d->exifMetadata["Exif.Image.ImageLength"]     = size.height();
        d->exifMetadata["Exif.Photo.PixelXDimension"] = size.width();
        d->exifMetadata["Exif.Photo.PixelYDimension"] = size.height();
        return true;
    }
    catch( Exiv2::Error &e )
    {
        printExiv2ExceptionError("Cannot set image dimensions using Exiv2 ", e);
    }        
    
    return false;
}

Full code from libkexiv2 is here :

http://websvn.kde.org/trunk/extragear/libs/libkexiv2/kexiv2.cpp?revision=646581&view=markup

The QSize::width() or QSize::height() method return an integer value.

The code is simple and the problem reported by Isaac is reproductible on my computer. After to rotate an image, the tags type is SLONG.

I have not yet tried to play with Exiv2 command line tool. I will do it. Isac can you perform a test with EXiv2 command line tool too to trying to reproduce the problem ?

Gilles
Comment 9 caulier.gilles 2007-04-26 20:30:51 UTC
Andreas,

You have right. if the tags are reported like SLONG by EXIV2, it fine for me. If Isaac, cannot load the image in the exifiron program, this is want mean than the problem is in another place. Perhaps it's a bug in exifiron.

Isaac, are you tried to play with ExifTool program on the rotated image ? Is ExifTool report any problems ?

Gilles 
Comment 10 Gerhard Kulzer 2007-04-26 22:07:36 UTC
Am Thursday 26 April 2007 schrieb Andreas Huggel:
[bugs.kde.org quoted mail]

Digikam JPG exiv2 -pt
Exif.GPSInfo.GPSVersionID                    Byte        4  2 0 0 0
Exif.GPSInfo.GPSLatitudeRef                  Ascii       2  North
Exif.GPSInfo.GPSLatitude                     SRational   3  63deg 59.35280'
Exif.GPSInfo.GPSLongitudeRef                 Ascii       2  West
Exif.GPSInfo.GPSLongitude                    SRational   3  22deg 35.68542'
Exif.GPSInfo.GPSAltitudeRef                  Byte        1  Above sea level
Exif.GPSInfo.GPSAltitude                     SRational   1  37.6 m
Exif.GPSInfo.GPSMapDatum                     Ascii       7  WGS-84

exiv2 -pv
0x0000 GPSInfo      GPSVersionID                Byte        4  2 2 0 0
0x0001 GPSInfo      GPSLatitudeRef              Ascii       2  N
0x0002 GPSInfo      GPSLatitude                 Rational    3  29/1 29/1 
2314/100
0x0003 GPSInfo      GPSLongitudeRef             Ascii       2  W
0x0004 GPSInfo      GPSLongitude                Rational    3  89/1 42/1 
723/100
0x0005 GPSInfo      GPSAltitudeRef              Byte        1  0
0x0006 GPSInfo      GPSAltitude                 Rational    1  4294967273/1
0x0007 GPSInfo      GPSTimeStamp                Rational    3  14/1 4/1 31/1

Other source JPG exiv2 -pt (this one works with Gallery 2.2)
Exif.GPSInfo.GPSVersionID                    Byte        4  2 2 0 0
Exif.GPSInfo.GPSLatitudeRef                  Ascii       2  North
Exif.GPSInfo.GPSLatitude                     Rational    3  29deg 29' 23.140"
Exif.GPSInfo.GPSLongitudeRef                 Ascii       2  West
Exif.GPSInfo.GPSLongitude                    Rational    3  89deg 42' 7.230"
Exif.GPSInfo.GPSAltitudeRef                  Byte        1  Above sea level
Exif.GPSInfo.GPSAltitude                     Rational    1  4294967296 m
Exif.GPSInfo.GPSTimeStamp                    Rational    3  14:04:31

exiv2 -pv
0x0000 GPSInfo      GPSVersionID                Byte        4  2 0 0 0
0x0001 GPSInfo      GPSLatitudeRef              Ascii       2  N
0x0002 GPSInfo      GPSLatitude                 SRational   3  63/1 
59352804/1000000 0/1
0x0003 GPSInfo      GPSLongitudeRef             Ascii       2  W
0x0004 GPSInfo      GPSLongitude                SRational   3  22/1 
35685424/1000000 0/1
0x0005 GPSInfo      GPSAltitudeRef              Byte        1  0
0x0006 GPSInfo      GPSAltitude                 SRational   1  376285/10000
0x0012 GPSInfo      GPSMapDatum                 Ascii       7  WGS-84

This is my first diagnostics.
Gerhard

> -ahu.
> _______________________________________________
> Kde-imaging mailing list
> Kde-imaging@kde.org
> https://mail.kde.org/mailman/listinfo/kde-imaging

Comment 11 Isaac Wilcox 2007-04-27 04:02:04 UTC
It seems we're losing focus here - but I think I've found the bug anyway.  The problem is definitely KExiv2.  Here's why:

The KExiv code Gilles pasted uses QSize::width() and QSize::height(), which return int (i.e. signed int); libexiv2 *really* doesn't care about types, and just uses operator=() overloading to decide what type to give the tag based on the RHS of the assignment.  So when you pass an int, you invoke this libexiv2 function (this is from libexiv2, src/exif.cpp, line 148 ish):
    Exifdatum& Exifdatum::operator=(const int32_t& value)
    {
        return Exiv2::setValue(*this, value);
    }


I think the best way to fix the problem is (strangely) to cast the number to a string first.  From the libexiv2 source it looks like this will force the library to choose the correct type by itself.  Depending on your QT version, something like this should work:
    d->exifMetadata["Exif.Image.ImageWidth"]      = QString::number(size.width()).toStdString(); // QT 4
    d->exifMetadata["Exif.Image.ImageWidth"]      = std::string(QString::number(size.width())); // QT 3


If that doesn't work, use a cast:
    d->exifMetadata["Exif.Image.ImageWidth"]      = (uint32_t)size.width();

I'd attach a patch, but I'd have to do loads of work to set it up, and it's 3am right now, and I've found the bug, and...and... :)

You may well find that Gerhard's GPS types issue is caused by the same kind of operator=() cleverness...

Happy hacking.
Comment 12 Andreas Huggel 2007-04-27 07:23:23 UTC
The code in #8 :

a) generates the two Exif.Image.ImageWidth/Length tags, which the original bug report said were newly introduced.

b) uses the type of QSize::size() to eventually choose the type of the Exif tag. Convert or cast it to uint32_t to get an Exif LONG type, e.g.,

d->exifMetadata["Exif.Photo.PixelXDimension"] =
    static_cast<uint32_t>(size.width());

That will create a LONG type regardless of the original type (which could also be SHORT)

Converting the value to an std::string and assigning that will use even more magic: If the tag already exists and has a value, it will attempt to read the string assuming it is of the type of the current value. If the tag doesn't exist yet or doesn't have a value yet, it will create a new value with the Exiv2 default type for the tag.

On the other hand, if you prefer less magic, you can create an Exiv2::Value of the type you want and assign that.

-ahu.
Comment 13 Andreas Huggel 2007-04-27 09:10:35 UTC
Gerhard,

At first glance, the only thing that is obviously not right here is the altitude in one of the two samples (I'd be surprised if GPS worked at that altitude :). Try to use the exiv2 tool to set it to something more reasonable and see if this helps.

-ahu.
Comment 14 caulier.gilles 2007-04-27 10:37:03 UTC
Andreas,

Ok, i will review libkexiv2 source code to provide a patch about Image Dimensions and GPS stuff (this last one sound like the same cast problem).

Gilles
Comment 15 Andreas Huggel 2007-04-27 11:19:30 UTC
Oh yes now I see it too: SRATIONAL is wrong, these should be RATIONAL types.
-ahu.
Comment 16 Isaac Wilcox 2007-04-27 13:33:37 UTC
Re comment #12: There seems to be an echo in here.  Andreas - with respect - why did you reword my comment #11 and post it again?

Re comment #14: Best mark this confirmed, then.

Glad we've got this one nailed, anyway.
Comment 17 caulier.gilles 2007-04-27 21:06:14 UTC
To Andreas, #15: Please look the KExiv2::setGPSPosition() method, and look if something is wrong for you... Note : i use M$ Vista during this week (holidays) and i'm out of home using a simple RTC internet connexion. It's not trivial to hack under Linux (:=)))

To Isaac, #16: your problem about image dimensions tags type is fixed on my computer following Andreas tips. Gerhard will patch svn at soon.

Gilles
Comment 18 Andreas Huggel 2007-04-28 03:27:48 UTC
Isaac,

Yes, you found the bug and your analysis was correct. Thanks! 

I just elaborated on the implications of each solution, especially that via an std::string, which you found 'strange', and highlighted that there is also one without 'magic', to help Gilles decide which way he prefers to fix this.

-ahu.
Comment 19 Andreas Huggel 2007-04-28 04:37:07 UTC
Gilles,

Try this patch (against a recent pre-release of libkexiv2). It compiles but is untested (I don' have the complete digikam devel environment here at the moment):

--- kexiv2.cpp-0.1.2    2007-04-28 10:16:07.000000000 +0800
+++ kexiv2.cpp  2007-04-28 10:25:14.000000000 +0800
@@ -1570,11 +1570,9 @@
         d->exifMetadata.add(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitudeRef"), value.get());

         // And the actual altitude.
-        value = Exiv2::Value::create(Exiv2::signedRational);
         convertToRational(altitude, &nom, &denom, 4);
         snprintf(scratchBuf, 100, "%ld/%ld", nom, denom);
-        value->read(scratchBuf);
-        d->exifMetadata.add(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitude"), value.get());
+        d->exifMetadata["Exif.GPSInfo.GPSAltitude"] = scratchBuf;

         // LATTITUDE
         // Latitude reference: "N" or "S".
@@ -1606,12 +1604,10 @@
         // Further note: original code did not translate between
         //   dd.dddddd to dd mm.mm - that's why we now multiply
         //   by 6000 - x60 to get minutes, x1000000 to get to mmmm/1000000.
-        value = Exiv2::Value::create(Exiv2::signedRational);
         deg   = (int)floor(fabs(latitude)); // Slice off after decimal.
         min   = (int)floor((fabs(latitude) - floor(fabs(latitude))) * 60000000);
         snprintf(scratchBuf, 100, "%ld/1 %ld/1000000 0/1", deg, min);
-        value->read(scratchBuf);
-        d->exifMetadata.add(Exiv2::ExifKey("Exif.GPSInfo.GPSLatitude"), value.get());
+        d->exifMetadata["Exif.GPSInfo.GPSLatitude"] = scratchBuf;

         // LONGITUDE
         // Longitude reference: "E" or "W".
@@ -1643,12 +1639,10 @@
         // Further note: original code did not translate between
         //   dd.dddddd to dd mm.mm - that's why we now multiply
         //   by 6000 - x60 to get minutes, x1000000 to get to mmmm/1000000.
-        value = Exiv2::Value::create(Exiv2::signedRational);
         deg   = (int)floor(fabs(longitude)); // Slice off after decimal.
         min   = (int)floor((fabs(longitude) - floor(fabs(longitude))) * 60000000);
         snprintf(scratchBuf, 100, "%ld/1 %ld/1000000 0/1", deg, min);
-        value->read(scratchBuf);
-        d->exifMetadata.add(Exiv2::ExifKey("Exif.GPSInfo.GPSLongitude"), value.get());
+        d->exifMetadata["Exif.GPSInfo.GPSLongitude"] = scratchBuf;

         return true;
     }
Comment 20 caulier.gilles 2007-04-28 08:38:41 UTC
Gerhard, 

Can you test it on your computer and with you Gallery envirronement ? If it's fine for you, you can apply it on svn of course.

Gilles
Comment 21 Gerhard Kulzer 2007-04-28 18:36:53 UTC
Ok I tested with patch and Gallery2.2. It works! It was the RATIONAL problem and not the ss/1. My intuition was right :-)

I'll commit it now.
Cheers
Comment 22 caulier.gilles 2007-04-28 19:07:53 UTC
Isaac,

Gerhard have patched libkexiv2 about the wrong cast with image dimension Exif tags.

Can you checkout current implementation of libkexiv2, recompile and install all extragears/libs and recompile and install digiKam to test again ?

Thanks in advance

Gilles Caulier 
Comment 23 caulier.gilles 2007-09-25 16:07:06 UTC
Angelo, Arnd, 

This be certainly closed...

Gilles



Comment 24 Arnd Baecker 2007-09-25 16:34:33 UTC
Yes, closing this bug as FIXED.
If there is any remaining problem, don't hesitate to re-open.

Best, Arnd