Small revision of my Exif code (v1.1.2)

I’ve formalised the changes to my Exif reading/writing code I’ve made since the last minor release, so it’s now at v1.1.2. For the individuals who’ve been using the trunk version, there’s no changes beyond updated file headers. Nonetheless, I’ve tagged this release in the repository and uploaded ZIPs for both it and all previous releases to Google Code (see here).

Because of the latter, I’ve also deleted the CodeCentral entries (two less things to remember to update…). Lastly, at some point I’ll get round to updating the trunk with my working version, which has a fair bit of rejigging internally to support a few more image formats beyond JPEG, and consequently, a few interface changes (not many though).

[Edit: I’ve now updated the trunk with the 1.5.0 beta code, the main structural change being CCR.Exif.JPEGUtils.pas effectively replaced with CCR.Exif.BaseUtils.pas and CCR.Exif.TiffUtils.pas (the new image formats supported are PSD and TIFF by the way, the various LoadFromJPEG and SaveToJPEG methods being deprecated and replaced with LoadFromGraphic and SaveToGraphic ones). Along with the usual bug reports, I will be pleased to receive any reports of the new code working successfully in the comments below, especially from individuals who have used older versions.]

[Edit 2: while I’ve now closed the comments, feel free to continue any discussions under this post.]

Advertisements

48 thoughts on “Small revision of my Exif code (v1.1.2)

  1. Thanks for the update. There is a small issue when trying to read IPTC data from a file that is “ShareDenyWrite” locked by another application. It is caused by a TFilestream call that has no share mode specified. TFilestream uses an exclusive lock when you don’t specify one.

    If you change line 1554 in CCR.Exif.IPTC.pas from

    > Stream := TFileStream.Create(FileName, fmOpenRead);

    into

    > Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone);

    the problem is solved.

    • Thanks — the same goes for the file name variant of TExifData.LoadFromJPEG. My private working version (which I’ve just made the public trunk version) has been using fmShareDenyWrite. Do you have a particular reason for preferring fmShareDenyNone…?

      • I always try to be as little restrictive as possible. There are probably a few situations where deny none may succeed where deny write won’t, but deny write is the safer solution as it will prevent the IPTC section from being changed while reading it. Thinking about it again, I prefer your deny write.

      • Off topic: The VCL is not always consequent. TFile.ReadAllText in ioutils.pas does not specify a share mode either and therefore uses an exclusive lock. I have reported it some time ago:

        http://qc.embarcadero.com/wc/qcmain.aspx?d=87287

        Votes are welcome 🙂 as I prefer to make as little changes to the VCL as possible. Unfortunately Embarcadero does not respond as quickly as you, so the bug is still there in Delphi XE. The good news is that the bug report has recently been opened.

      • Ah well… if it were down to me, I’m not sure IOUtils would have been added to the product in the first place! That’s truly going off topic though. 😉

  2. One more small issue. The library raises an exception on some JPEG images with corrupted IPTC sections. You can download one here:

    I changed the code in line 1260 in CCR.Exif.BaseUtils a bit trying to make it a bit more robust:

    procedure TAdobeBlock.LoadFromStream(Stream: TStream);
    var
      Len: Integer;
    begin
      SetString(FSignature, nil, 4);
      Stream.ReadBuffer(Pointer(FSignature)^, 4);
      FTypeID := Stream.ReadWord(BigEndian);
      Len := Stream.ReadByte;
      // JDMOD
      // Don't read anything if len is invalid.
      if Len >= Stream.Size - Stream.Position  then Len := 0;
      // END JDMOD
      SetString(FName, nil, Len);
      if Len <> 0 then Stream.ReadBuffer(Pointer(FName)^, Len);
      if not Odd(Len) then Stream.ReadByte;
      Len := Stream.ReadLongInt(BigEndian);
      if Len >= Stream.Size - Stream.Position then Len := 0;
      // END JDMOD
      Data.SetSize(Len);
      if Len <> 0 then
      begin
        Stream.ReadBuffer(Data.Memory^, Len);
        if Odd(Len) then Stream.ReadByte;
      end;
    end;
    

    I haven’t done anything about it but saving IPTC to this image also fails.

    • Thanks, I’ll have a look at this later – I expect there’ll be other things that can be done to the IPTC code on that general theme, since it’s only been the Exif/TIFF parsing that I’ve actively worked at making robust and less exception prone WRT malformed data. E.g., with the method you’ve highlighted, it would probably be more user friendly to replace the ReadBuffer call at the top with a Read one and simply exit if less data than required is read.

    • OK, the latest trunk revision (14) should have fixed matters for that particular image – both reading and rewriting should work now. Fixing the writing actually entailed fixing an outright bug (TStream.CopyFrom biting me on the behind, not for the first time…), which was useful.

    • Thanks. The reading and writing works great now for images with corrupted sections.

      Another issue with the tiff support. I tested it on a wide range of different tiff images. It works great when saving to standard 8bit images. But when saving IPTC to all 16bit or 32bit tiff images I tested and then opening it in photoshops CS4 results in Photoshop showing a “This document may be damaged (the file may be truncated or incomplete). Continue?” error.

      Original Photoshop image:
      http://janderk.com/Photoshop-16bit.tif

      Image with IPTC saved by CCR which shows this error:
      http://janderk.com/CCR-16bit.tif

      If you click OK all seems well, but there is probably a reason why Photoshop shows this. I have to admit that I know very little about tiff images. And wouldn’t blame you for letting this one go.

      • It works great when saving to standard 8bit images. But when saving IPTC to all 16bit or 32bit tiff images…

        Yes, it works fine for ‘standard 8bit images’ for me too, but not for some others (you can more basically test this with a simple roundtrip: call RewriteTiff in CCR.Exif.TiffUtils with a nil callback). I’d like to look at this properly eventually, but I think it’s going to involve acquiring the knowledge to actually write the image data, which I currently don’t have. At present, the code rewrites ‘strip’ and ’tile’ offsets correctly (or should do, else 8 bit images wouldn’t work), protects the Exif maker note (if it exists) in the first IFD, and makes an attempt to be friendly towards non-standard/user-defined sub-IFDs (to use the jargon). Beyond that though, I don’t know what to try short of learning how to create the image data itself.

      • No problem. One thought I had is that maybe it is a alignment or boundary issue. In some old code of mine I found a remark that Photoshop wants the IPTC data segments to be 4byte aligned. The official spec (p. 13) talks says that the data offset must begin on a word boundary.

        http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf

        And I do see some odd (as in not even) data offsets in your code which I suspect should be word or even 4-byte aligned. Just a hunch. For the time being I just switch off tiff IPTC writing support.

  3. I’ve just started upgrading an old Exif program that I created using dExif, which is no longer supported. Your CCR package was noteworthy for its elegant solution to Makers Notes via the .INI file, so I selected it. Thank you for all your efforts on this! My goal is to read Makers Notes for my Panasonic G-1 camera into my Access photo log, via the DDE interface that my program implements. While upgrading to CCR I’ve been running dExif in parallel and found some issues:

    1. ExifData.Flash.RedEyeReduction reports false regardless of whether my camera had it turned on. dExif correctly reports. I can provide sample images.
    2. ExifData.FocalLengthIn35mmFilm reports nothing for some cameras. dExif calculates the result when needed. I’ve modified its code into CCR compatible code if you would like to have CCR take care of this. (Though I’m sure you will do better!)

    3. Based on http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html I modified Color Space as follows:

      TExifColorSpace = (csTagMissing = 0, csRGB = 1, csAdobeRGB = 2, csWide = $FFFD,
         csICC = $FFFE, csUncalibrated = $FFFF);
    
          case ExifData.ColorSpace of
            csTagMissing: AddValue('Color Space','Tag Missing (BW?)');
            csAdobeRGB: AddValue('Color Space','Adobe RGB');
            csRGB: AddValue('Color space', 'sRGB');
            csWide: AddValue('Color space', 'Wide Gamut RGB (Sony)');
            csICC: AddValue('Color space', 'ICC Profile (Sony)');
            csUncalibrated: AddValue('Color space', 'Uncalibrated (probably Adobe RGB)');   
          end;
    

    My change to ‘Uncalibrated’ is because my camera stores 65535 when I set it to use Adobe RGB and Photoshop Elements 8 (PSE) also uses that value when converting an image.

    4. ExifData.Subject appears like it might be the same as PSE Key Words, but it reports nothing. 2:25 in the raw CCR data contains those keywords.

    5. ExifData.Title appears to be the same as PSE Document Title, but it reports nothing. 2:5 in the raw CCR data contains the title.

    I’m learning a lot by using your package. I apologize in advance if I’ve missed something obvious. I look forward to your new version with .TIF file support when it is ready.

    • Hi John —

      Thanks for the suggestions. WRT to your numbered points –

      1. If you could upload a few sample images, that would be great. In fact, just one would probably do, assuming the images come from the same source.

      2. If it’s not too long, feel free to post the amended code snippet here (just use the sourcecode tag – see http://en.support.wordpress.com/code/posting-source-code/). If you do though, I suggest ‘replying’ to your own original post rather than this one (and yeah, I should probably bite the bullet and turn off threaded comments…).

      3. Thanks — I’ve now amended the trunk version of the units concerned with a version of your suggestions.

      4. The ‘2:25’ notation relates to IPTC not Exif data, so you’ll need to use my TIPTCData class to read it (if you check out the demos, you’ll see one of them is a simple IPTC editor). Looking it up, I see 2:25 is the Keywords repeatable tag, so use either the Keywords dynamic array property on TIPTCData or the GetKeywords method, which fills a TStrings object.

      5. Similar to the last point; 2:5 is the ‘object name’ tag according to the IPTC spec, so use the ObjectName property of a TIPTCData instance to read it (note that in general, the property names on TIPTCData follow the IPTC spec rather than what the GUI of an Adobe application will use – Adobe basically took the IPTC format and repurposed it somewhat).

      As for TIFF file support, you can download the SVN trunk version of my 1.5.x code and try it out, if you haven’t already (TortoiseSVN or even the Delphi XE IDE works great, if you don’t already have a Subversion client). This is a good idea anyway, given it’s there that I’m now making the occasional bugfix between releases.

    • I should have mentioned that I’m using Delphi Explorer which apparently includes the 2006 buggy compiler.

      This is the dExif source code for calculating 35mm equivalent focal length.

      procedure TImageInfo.Calc35Equiv;
      const Diag35mm : double = 43.26661531; // sqrt(sqr(24)+sqr(36))
      var tmp:integer;
        CCDWidth, CCDHeight,
          fpu, fl, fl35, ratio : double;
        NewE, LookUpE : TTagEntry;
      begin
        if LookUpTag('FocalLengthin35mmFilm') >= 0 then
          exit;  // no need to calculate - already have it
      
        CCDWidth  := 0.0;
        CCDHeight := 0.0;
        tmp := GetRawInt('FocalPlaneResolutionUnit');
        if (tmp <= 0) then
           tmp := GetRawInt('ResolutionUnit');
        case tmp of
          2: fpu := 25.4;   // inch
          3: fpu := 10;     // centimeter
        else
          fpu := 0.0
        end;
      
        fl := GetRawFloat('FocalLength');
        if (fpu = 0.0) or (fl = 0.0) then
          exit;
      
        tmp := GetRawInt('FocalPlaneXResolution');
        if (tmp > 0) then
          CCDWidth := Width * fpu / tmp;
        tmp := GetRawInt('FocalPlaneYResolution');
        if (tmp > 0) then
          CCDHeight := Height * fpu / tmp;
      
        if CCDWidth*CCDHeight <= 0 then  // if either is zero
        begin
          if not estimateValues then
            exit;
          ratio := LookupRatio()	//<<< this just provides a fixed ratio for the author's own camera
        end
        else
          ratio :=  Diag35mm / sqrt (sqr (CCDWidth) + sqr (CCDHeight));
      
        fl35 := fl *  ratio;
      
      // now load it into the tag array
          tmp := LookupTagDefn('FocalLengthIn35mmFilm');
          LookUpE := TagTable[tmp];
          NewE := LookupE;
          NewE.Data := Format('%5.2f',[fl35]);
          NewE.Raw := '';
          NewE.FormatS := '%s mm';
          NewE.TType := FMT_SRATIONAL;
          AddTagToArray(NewE);
          TraceStr := TraceStr+#13#10+
                siif(ExifTrace > 0,'tag[$'+inttohex(tmp,4)+']: ','')+
                NewE.Desc+DexifDelim+NewE.Data+
                siif(ExifTrace > 0,' [size: 0]','')+
                siif(ExifTrace > 0,' [start: 0]','');
      end;
      
      
      

      This is my modification using CCR:

      
             AddValueCalc35Equiv(inShowEmptyTags, ExifData, ExifData.FocalLengthIn35mmFilm);
      
      Procedure AddValueCalc35Equiv(inShowEmptyTags : boolean; ExifData: TExifData; Equiv35 : Integer);
      const Diag35mm : double = 43.26661531; // sqrt(sqr(24)+sqr(36))
      var
        tmp:integer;
        CCDWidth, CCDHeight, fpu, fl, fl35, ratio, dTemp : double;
        jrResolution : string;
        ImgWidth, ImgHeight : integer;
      
      begin
        ImgWidth := ExifData.ExifImageWidth;
        ImgHeight := ExifData.ExifImageHeight;
      
        if Equiv35 > 0 then
          begin
            AddValueJR('Focal Length in 35mm Film', IntToStr(Equiv35) + '.00 mm');
            exit;
          end;
          if inShowEmptyTags = true then
          begin
            AddValueJR('Focal Length in 35mm Film', 'Value not stored in file');
            exit;
          end;
      
      //    exit;  // no need to calculate - already have it
            CCDWidth  := 0.0;
            CCDHeight := 0.0;
            if ExifData.FocalPlaneResolution.MissingOrInvalid then
              begin
                AddValueJR('Focal Length in 35mm Film', 'unknown');
                exit;
              end
            else
              begin
                jrResolution :=  ResolutionUnitsToStr(ExifData.FocalPlaneResolution.Units);
                if jrResolution = 'Inch' then
                   tmp := 2 //'Inch';
                else if jrResolution = 'Centimetre' then
                   tmp :=3 //'Centimeter';
                else
                  tmp := 0;
              end;
              if tmp = 0 then
                begin
                  jrResolution := ResolutionUnitsToStr(ExifData.Resolution.Units);
                  if jrResolution = 'Inch' then
                   tmp := 2 //'Inch';
                else if jrResolution = 'Centimetre' then
                   tmp :=3 //'Centimeter';
                else
                  tmp := 0;
            end;
      
      
            case tmp of
              2: fpu := 25.4;   // inch
              3: fpu := 10;     // centimeter
            else
              fpu := 0.0
            end;
        fl := jrDoubleFocalLength(ExifData.FocalLength);
      
        if (fpu = 0.0) or (fl = 0.0) then
          begin
            AddValueJR('Focal Length in 35mm Film', 'unknown');
            exit;
          end;
      
          dTemp := jrDoubleFocalLength(ExifData.FocalPlaneResolution.X) ;
      ////    if dTemp = 0  then   //the remaining values in dExif don't work. See above exit
            tmp := Round(dTemp);
          if (tmp > 0) then
            CCDWidth := ImgWidth * fpu / tmp;
      
          dTemp := jrDoubleFocalLength(ExifData.FocalPlaneResolution.Y) ;
            tmp := Round(dTemp);
          if (tmp > 0) then
            CCDHeight := ImgHeight * fpu / tmp;
      
          if CCDWidth*CCDHeight <= 0 then  // if either is zero
            begin
              //no way to get ratio
              AddValueJR('Focal Length in 35mm Film', 'unknown');
              exit;
            end
            else
              ratio :=  Diag35mm / sqrt (sqr (CCDWidth) + sqr (CCDHeight));
      
          fl35 := fl *  ratio;
          jrResolution := Format('%5.2f',[fl35]);
          AddValueJR('Focal Length in 35mm Film', Format('%5.2f',[fl35]) + ' mm EST');
      end;
      
      Function jrDoubleFocalLength(Fraction : TExifFraction) : double;
      begin
          if Fraction.MissingOrInvalid then
               result := 0
          else                            
            begin
              result := Fraction.Quotient;
            end;
      end;
      
      
    • Thanks for posting the code. One or two sample images would still be helpful though — if you don’t have anywhere to post them, we can take it to email if you confirm you’ve put in a real email address to post here.

      ‘I should have mentioned that I’m using Delphi Explorer which apparently includes the 2006 buggy compiler.’

      No problem. In the IPTC editor demo case, it should still run, just not load all the tags it should do… The best solution would be to define a helper function like this:

      function WordTagToStr(const Tag: TWordTagValue): string;
      begin
        Result := Tag.AsString;
      endl
      

      This done, lines like the following –

          edtRecordVersion.Text := IPTCData.RecordVersion.AsString;
      

      can then be replaced with ones like this –

        edtRecordVersion.Text := WordTagToStr(IPTCData.RecordVersion);
      

      The underlying problem here actually afflicts TExifData too – I just lost patience working around it in the IPTC demo’s case.

      • I hope you received the sample photos I sent on 2/14 via email since I couldn’t find a method of uploading them on this site. I also sent some results of your suggestions.

        Since then I broke down and purchased Delphi XE Starter Edition. After considerable work with your IPTC Editor compiled in Delphi Explorer 2006 and in XE, I find the following:

        IPTCEditor 2006 fails to read/save Record Version, Model version, File Format, File format version, ARM identifier and Arm version. I removed buggy compiler directives and compiled under XE. I resaved again with IPTCEditor XE but both still failed to read these tags. JPEGDump 2006 or XE fails to find them in raw data. Is it failing to write? These tags all share the same code, but I can’t figure out what is going wrong:

        function TfrmIPTC.WordTagToStr(const Tag: TWordTagValue): string;
        begin
          Result := Tag.AsString;
        end;
            {$IFNDEF BUGGYCOMPILER}
        
            edtModelVersion.Text := WordTagToStr(IPTCData.ModelVersion);
            edtFileFormat.Text := WordTagToStr(IPTCData.FileFormat);
            edtFileFormatVersion.Text := WordTagToStr(IPTCData.FileFormatVersion);
            edtARMIdentifier.Text := WordTagToStr(IPTCData.ARMIdentifier);
            edtARMVersion.Text := WordTagToStr(IPTCData.ARMVersion);
            edtRecordVersion.Text := WordTagToStr(IPTCData.RecordVersion);
            {$IFNDEF BUGGYCOMPILER}
        
        
        • I hope you received the sample photos I sent on 2/14
          I didn’t, so I’ve emailed you just now.

          By the by, that code you’ve posted confuses my previous comment – if BUGGYCOMPILER is *not* defined, you don’t need the helper function, just call AsString directly. More generally, if you’re compiling in anything other than D2006 (BDS2006 or Turbo Delphi), you shouldn’t be needing to make any edits whatsoever.

          That said, I take it you’ve moved to using the trunk version, so we can at least be sure we’re talking about exactly the same code? If you haven’t, from XE, go to File|Open From Version Control, and enter http://ccr-exif.googlecode.com/svn/trunk/ for the URL.

  4. One more small remark. In TIPTCSection.SetDateValue you compare a TDateTime with zero. As TDatetime is a float this is unreliable and often fails. It is better to use the math.IsZero() function or just use < 0.000001

    Instead of this:

    if Value=0 then

    Use this:

    if IsZero(Value) then

    or this:

    if Value < 0.00001 then

    • It is better to use the math.IsZero() function or just use < 0.000001

      Not really in this situation. The ‘imprecision’ IsZero covers concerns the result of a calculation of some sort, which doesn’t apply here.

    • You are right. My problem was caused by the DevExpress TcxDateEdit returning -700000 if the datetime value is cleared. As a result TIPTCSection.SetDateValue thinks the date is valid.

      How about changing the line to?
      if Value <= 0 then

      That would help users with Devexpress components. And it would not hurt as negative datetime values are invalid (at least that is what I think).

      Hmmm
      just did some more research and TDateTime can be negative too. So 0 is valid date. Looks like -700000 is used by both Delphi and DevExpress to indicate a null date.

    • 0 is valid date

      For sure, though not a likely one for a tag value for a digital photograph! Nonetheless, I suppose in principle it would be better if my code defined a TDateTimeTagValue on the lines of the existing TXXXTagValue records so that a ‘missing’ value is made explicit. Moreover, attempts to set an invalid date/time value should then raise an exception?

      Looks like -700000 is used by both Delphi and DevExpress to indicate a null date.

      -700000 would be a nice round number just inside the BC rather than AD range. In principle, I would say TDateTime should support dates from antiquity, but the RTL’s support routines don’t.

    • OK, try the revision I’ve just uploaded (no. 29). I wouldn’t normally be that quick, but I was actually working on the code for something else when WordPress notified me of your first comment (it tends to be Sunday evening when I look at any bug reports that have come in…).

    • Thanks. Works fine now.

      Another tiny suggestion: It would be nice if there were an efficient AddRepeatableValue() function (and AddKeyword(), AddSuppCategory(), etc). Currently I read the keywords, add a new one and save them. No real problem, but current library deletes existing keywords and then reinserts them. It would be more efficient if only the new keyword were inserted if it did not exist.

    • It would be nice if there were…

      It would be nice, yes. Care to suggest some code? It is a tiny suggestion after all.

      current library deletes existing keywords and then reinserts them

      Yes, since the current model of TExifData, TIPTCData and TXMPPacket is not to have a notion of the ‘original’ file in the first place (cf. TExifDataPatcher, which ‘opens’ a file rather than ‘loads from’ one). One reason for this is to allow other programs to do what they want in between the tags being loaded and saved.

      It would be more efficient if only the new keyword were inserted if it did not exist.

      The IPTC spec says records should be stored in ID order.

  5. First, I wanted to thank you for the great work you did on the EXIF code.
    Second, I have a small extension to it:

    To be precise I’ve added a lot of information to TNikonType3MakerNote

    Please download it at http://borrisholt.dk/CCR/CCR.7z

    Let me know what you think …

    You can ofcause get the code for free etc etc … (All the usally stuff)

    Jens Borrisholt

  6. Hi I’m trying to port my app to Max OSX from Delphi with Lazarus. Does my Exif code works under Lazarus?

    jus

    • Probably not, though it all depends on whether FPC’s Delphi mode fully supports D2007-level code or not.

  7. Hi!
    I want to use CCR-Exif in my application to change date and time of jpg files, before archiving with Picasa. I noticed “tIptcData.DateCreated”, but I can’t find how to set the time field (recordnumber=2, dataset=60).

    Is it possible?

    • The short answer is that it isn’t surfaced. To get and set the value manually, you can use IPTCData.ApplicationSection.GetStringValue(itTimeCreated) and IPTCData.ApplicationSection.SetStringValue(itTimeCreated, NewValue) respectively (itTimeCreated is a constant declared in CCR.Exif.TagIDs). According to the IPTC standard, the format is HHMMSS+HHMM or HHMMSS-HHMM, where HHMMSS is the local hour, minute and seconds and HHMM specifies the UTC/GMT offset.

      What version of Delphi are you using?

    • I use Delphi 2010.
      Before now, in my application I used ImageEn library, but in some cases date-time is setted wrong. Moreover, ImageEn is no longer free 😛
      CCR-Exif is pretty easy and quick to use, for my scope; and work correctly. So thank you very much!

      • Have a go of the revision of CCR.Exif.IPTC.pas I’ve just uploaded to the Google Code trunk (r.36) – I’ve added DateTimeCreated and DateTimeSent props to TIPTCData. While the new props are only read only in D2010 or earlier (I use TTimeZone for the setters), you can call the SetDateTimeCreated or SetDateTimeSent methods instead to write. Be warned I haven’t particularly tested this though – I’ve just written the code.

      • I found some problems.
        New revision r36 of “CCR.Exif.IPTC.pas” need some other files, I downloaded from svn/trunk “CCR.Exif.BaseUtils”, “CCR.Exif.TiffUtils” and “CCR.Exif.inc”. Building project I have a compile error:

        CCR.Exif.BaseUtils, line 711,
        “raise EInvalidGraphic.CreateRes(@SUnsupportedGraphicFormat);”,

        “[DCC Error] CCR.Exif.BaseUtils.pas(711): E2003 Undeclared identifier: ‘SUnsupportedGraphicFormat'”

        • Forget it. I’m a noob. I downloaded ALL the files from svn/trunk and now project compiles.

          Now I go to do some tests. Please excuse me!

        • Yes, sorry about that – I should get round to formally making the trunk the current version rather than a ‘beta’ (it’s actually had some fixes relative to v1.1.2). I’l probably wait until I can check whether it compiles OK with XE2 targetting OSX though (it should do, but you never know until you actually try it).

  8. It looks like there are some problems with editing EXIF metadata which is written by a specific camera model. Te reproduce the problem: add the following lines to the ReSavingTest program as follows:

    procedure DoExifDataDirectly(const SourceFile, DestFile: string);
    var
    ExifData: TExifData;
    TempStream: TMemoryStream;
    begin
    { While LoadFromJPEG as well as SaveToJPEG has a file name overload, SaveToJPEG
    assumes it is editing an already-existing JPEG file (and indeed, has no image data
    to stream out anyhow). Because of this, we use a memory stream as an intermediary. }
    ExifData := nil;
    TempStream := TMemoryStream.Create;
    try
    TempStream.LoadFromFile(SourceFile);
    ExifData := TExifData.Create;
    ExifData.LoadFromJPEG(TempStream);
    DoIt(ExifData);

    //* 3delite re-save fails with these changes
    ExifData.Comments := ‘My comment’;
    ExifData.Copyright := ‘My copyright’;
    ExifData.Author := ‘Me’;
    ExifData.Keywords := ‘My keyword’;
    ExifData.Title := ‘My title’;
    ExifData.Subject := ‘My subject’;
    ExifData.ImageDescription := ‘My description’;

    TempStream.SaveToFile(DestFile);
    ExifData.SaveToJPEG(DestFile);
    finally
    ExifData.Free;
    TempStream.Free;
    end;
    end;

    Do a re-save and open the result in the EXIF Tag List viewer. It shows some data and then says: EXIF sub-IFD: Loaded cleanly: No: bad IFD offset and GPS sub-IFD: Loaded cleanly: No: bad IFD offset

    Also the re-saved file is smaller by 6KBs.

    Could you please fix this problem?

    An example JPG file created by this camera is here:

    • Could you please fix this problem?

      Just checked your website – I see you are selling a ‘Photo EXIF & Watermark Maker’ application for $25. Perhaps you may wish to contribute a bug fix yourself, given my code is free?

  9. Hi Chris,
    first thanks for this great library!
    Do you plan to support MacOsX target for Delphi XE2 (Firemonkey)?
    Currently it doesn’t compile because of WinApi and VCL dependencies.

    Thanks in advance & best reagards
    Dirk

    • Hi Dirk – short answer is yes, the longer answer is that I’m not sure when exactly. There aren’t actually many dependencies in the core library by design, though I’d like to redo all the demos using FireMonkey to prove the point.

    • OK, I looked at this last evening. While the core parsing code is OS X friendly, I had forgotten just how embedded the VCL graphic classes had become… I’ll probably have a ‘beta’ of the CCR.Exif.* units and console demos by the end of the week though, so keep an eye on the SVN repository.

      • Hi Chris,
        this sounds very promising! I will test the beta and give you feedback.
        I’m looking forward..
        Best regards
        Dirk

  10. Hi Chris

    My First post here…so please bear with me.

    I have been using your excellent EXIF tools and have a question. I am trying to extract degrees, minutes and seconds from a geotegged JPG file. the coordinates are something like 50°44’23.399”N, 1°56’55.8”W. When I try to get the Degrees using EXIF they are correct but i get odd results where Seconds has no value and minutes comes out as xxxx/100. I guess I have missed something – any pointers?

    Cheers
    James

    • Hi James – the best way to proceed would be if you could post an example JPEG with the problem so I can run it through the debugger.

Comments are closed.