New revision of my Exif library (v0.9.8a)

It’s taken a while, but I’ve just completed another revision of my MPL’ed Exif reader/writer code, Exif being the standard format for JPEG metadata (see here). The biggest new feature in terms of effort spent is much better XMP support – the default behaviour is now to update the equivalent XMP property whenever an Exif tag value is changed, though only when the former already exists. If you want, you can get the behaviour of Vista’s (and quite possibly Windows 7’s) Windows Explorer instead, which is to always create an XMP value whenever an Exif one is set, with a single property change – set XMPWritePolicy to xwAlwaysUpdate.

In terms of actual usefulness though, possibly a bigger change is the fact that by default, MakerNote tag data are now always written out to their original location. Unlike implementing proper XMP support, which was a right drag, this turned out to be pretty straightforward. Other than that, I’ve also fixed some bugs and fiddled around with some of the lower level code a bit – in particular, where I had previously assumed the ExifImageWidth and ExifImageHeight tags would always have longword values, I now support word-sized ones too. Moreover, the ‘correct’ positions of the JFIF, Exif and XMP segments are now enforced by TExifData.SaveToJPEG, any comment segments (for example) being moved below.

That said, looking at the CodeCentral stats, it seems quite a few people have downloaded earlier revisions of the code, which makes me think – it could do with a better name! Unfortunately, the most obvious one (dExif) has already been taken. So, any ideas..?

Update 1 (19/10/09): in the hours since I first posted this, I’ve slightly amended the original ZIP to avoid some D2009 issues — the current version of CCR.Exif.pas is thus 0.9.8a.

Update 2 (29/10/09): I’ve also now slightly amended CCR.Exif.XMPUtils.pas. Like CCR.Exif.pas, it now stands at version 0.9.8a.

Advertisements

50 thoughts on “New revision of my Exif library (v0.9.8a)

  1. Hi Chris,
    thanks for the new version and taking Saturation, Contrast and Sharpness and the special word types into account.
    Unfortunately I cannot compile the new libraries under D2009. There seems to be a bug in using an overloaded version of Tag.UpdateData(Word(NewValue)) in procedure TCustomExifData.SetExifImageSize (exactly in line 4231). Compiler error code is E2250 (No overloaded version…).
    Best regards, Stefan

    • Me again, this problem is caused because the parameter is not a constant. This could be easily corrected by making the typecast to a word value outside of the procedure call:

      procedure TCustomExifData.SetExifImageSize(ID: Integer; NewValue: LongWord);
      const
      PropNames: array[ttExifImageWidth..ttExifImageHeight] of string = (‘PixelXDimension’,
      ‘PixelYDimension’);
      var
      Tag: TExifTag;
      wordtype: Word;
      begin
      Tag := nil;
      if (NewValue <= High(Word)) and FSections[esDetails].Find(ID, Tag) and
      (Tag.DataType = tdWord) and (Tag.ElementCount = 1) then
      begin
      wordtype:=Word(NewValue);
      Tag.UpdateData(wordtype);
      end
      else if Tag nil then
      Tag.UpdateData(tdLongWord, 1, NewValue)
      else
      PLongWord(FSections[esDetails].Add(ID, tdLongWord, 1).Data)^ := NewValue;
      XMPPacket.UpdateProperty(xsExif, PropNames[ID], Integer(NewValue));
      end;

    • Stefan – that is extremely weird, since the overloaded version of UpdateData being called just has a single untyped parameter. What happens when you remove the Word cast (which is unnecessary anyhow, now that I look at it)? As in, try making the call to UpdateData simply

      Tag.UpdateData(NewValue)

      If that still doesn’t work, try changing it to the following:

      Tag.UpdateData(tdWord, 1, NewValue)

      Also, while you are about it, are there any other D2009+ issues you’ve noticed?

      • Chris, your fix (without the word typecast) works as well as my does (typecast outside of procedure call).

        • Thanks – I count this as a D2009 compiler bug myself, since a cast shouldn’t make a value constant, else you could never cast the left hand side of an expression (even the VCL contains lines like IInterface(WeakRef) := nil;). That said, I’ve just updated the uploaded version to my second suggested fix, not having seen your replies first.

      • Just a couple of minor D2009 warnings about implicit string typecasts from ANSIString (aka tiffstring) to string and ANSIChar to string: e.g. FAsStringCache := TiffStr/if TryStrToInt(S, SubSecs) …
        Lines numbers are: 1671, 2099, 2171, 3237, 3242 and 3733.

  2. Hi,

    Looks good. I’ve been using Gerry McGuire’s dExif for a while (I just need to read the Exif data). But since dExif seems to be dead (at least the website is) I’m wondering whether there is a comparisation in features between your Exif library and dExif?

    Thanks!

    Regards,
    Guus

    • Hi Guus, I’ve ported my application from dEXIF to CCR.EXIF as well a couple of weeks ago. CCR.EXIF is quite easier to use and is still under dev, where as dEXIF is really dead. I’ve tried to send a couple of bugfixes and enhancements to the author who never replied.
      The only thing that was better implemented in dEXIF is the decoding of makernotes, which it did right out of the box. I’m currently struggeling to port the Panasonic specific code to work with a NIKON makernote, so far to no avail.
      – Stefan

    • Hi Guss – my general advice would be to do what you should do with any alternative libraries, paid or free: download what you can and try both out. It’s been a while since I looked at dExif; from what I can remember, I didn’t like the implementation much, though things may be different in what seems to be the final version (the last time I looked, even the dExif webpage was down).

      That said, once I’d got quite far with my own implementation, I noticed there were others at Torry’s, and with implementations more to my liking compared to dExif. Obviously I’m biased, but I still think mine is the most fully-featured out of the lot though — as well as the baseline things like handling both little and big endian Exif blocks transparently, it parses MS tags properly, exposes date/time tags as TDateTime values, exposes GPS tags, has full writing capabilities, and can optionally write XMP ‘meta-metadata’ like more recent versions of Windows Explorer.

  3. Hi Chris

    This seems to be a great library, far superior to dExif which I’ve been using so far. Unfortunately I cannot compile it since I am still using D7. I tried to adapt it to my compiler, but got stuck in all the language extensions introduced after D7. Do you plan to extend the library to older compiler versions?

    With kind regards
    Werner

    • Hi Werner –

      Sorry, but my code was D2007+ from the off — even D2006 requires the odd $IFDEF due to compiler bugs. It would be quite a big undertaking to backport it to D7 or lower, so if you can’t upgrade to a newer Delphi, I’d suggest checking out alternatives at Torry’s.

    • Hi Valerian — well, that’s a plus point, yes… It does rather demonstrate my lack of imagination though, eh?

  4. This library is for your fellows – software engineers. I think it’s the best choice, when the name of product or the product itself helps to learn more about developer, especially about a good developer who contributes an excellent code (for free!) to the communuty 😉

    • Hi Jordi – would that JPEG have been created (or edited) by an old version of Photoshop? Anyhow, I found the issue fairly quickly — download CCR.Exif.XMPUtils.pas again for the fix.

      That said, there should be no problem in ‘swallowing’ EInvalidXMPPacket exceptions, since ordinarily, the Exif data proper will have already been loaded –

      try
        ExifData.LoadFromJpeg(FileName);
      except
        on EInvalidXMPPacket do
          //ignore
        else
          raise;
      end;
      
  5. Hello, I don’t know anything about this image. Our costumers uses my application to send their images and I cannot ask for it. Actually I don’t need XMP data, only Exif, so your solution is ok.

    Thanks again!

    • Jordi – sorry, the second bit of my reply may have been confusing, since I’ve *fixed* the issue had by that image with a small change to CCR.Exif.XMXUtils.pas (so, no need for a try/except block). Basically, the XMP packet in the image had been created using an early version of Adobe’s XMP writing code, and so, wasn’t quite standard according to the current spec. As the difference lies only in a key XML node having a different name though, fixing the problem was easy.

    • ‘My english is not good enough…’

      I think the problem was more with me, trying to say a bunch of things all at the same time!

      • Hi again. I’ve received a new problematic jpg.
        You can download it from

        The code has problems with XMP again, and I allready have patched it adding ‘xmp:xmpmeta’ in line 1064, but maybe you want to update your library.

        Regards.

        • Hi Jordi – yes, adding ‘xmp:xmpmeta’ to the MatchStr call I inserted last time fixes things —

              if MatchStr(Node.nodeName, ['x:xmpmeta', 'x:xapmeta', 'xmp:xmpmeta']) and
          

          Googling, it appears beta versions of Vista MS code generally before this hotfix wrote ‘xmp:xmpmeta’ instead of ‘x:xmpmeta’, contra the XMP spec. Anyhow, I’ve updated the CodeCentral version of my code with the fix, thanks.

        • On second thoughts, changing the MatchStr line to test for Node.localName instead of Node.nodeName is possibly more sensible, so I’ve done that now –

              if MatchStr(Node.localName, ['xmpmeta', 'xapmeta']) and
          
    • Hi Ken –
      Thanks for the compliment. With respect to IPTC metadata — I don’t know, maybe (it’s another kind of APP1 segment isn’t it?). As it is, CCR Exif has gone well beyond what I originally wrote it for, though the main issue is that, since I have almost zero interest in photography beyond point-and-shoot, and have never used Photoshop in my life, I don’t readily have at hand the source JPEGs to test against. ‘Never say never’ though…

      • Sorry for not responding, been a bit bows under with work. I’ll keep visiting on the off-chance you decide to add IPTC. The reason I ask is because of changes in copyright law in many countries which make it a lot more likely that your image could be used without permission. Basically – from what I understand – someone who wants to use an image they find on the web (or anywhere else) must make “reasonable” efforts to contact the copyright holder but can still use the image if they fail. Being able to add IPTC data would make it much more difficult for someone to use the “couldn’t find the photographer” argument.

      • No worries – there wasn’t exactly anything to respond to, was there? I think you’ve missed the point of my previous reply though – it contains a link

        • Oops. Sorry, on my monitor the link looks almost exactly the same colour as the rest of the text.

          Many thanks, I’ll give the code a try as soon as I can. If you need test images from various cameras then please let me know. Between myself and the rest of my family we seem to own a fair few.

        • Well, to be honest, the link indeed looks pretty faint on my monitor too. Should have chosen an uglier theme…

          That said, and just to be clear, relevant ‘test images’ would not be JPEGs that have come straight out of a camera, but JPEGs created or edited by this or that version of Photoshop — just let me know if you come across a case of my code not being able to read a JPEG with ‘valid’ IPTC data, or alternatively, producing output that is not understood by a given application that ‘should’ be able to understand it. On the other hand, the IPTC format is pretty simple, so there shouldn’t be too much leeway for error on my part.

  6. Hi Chris,
    I’m very new to the exif data stuff. Its been a huge learning curve for me.
    I’m trying to save the gps latitude and longitude co-ordinates to my family history photos using your ccr.exif “component”.
    I’m using the using the following without success.
    Jpeg.ExifData.GPSLatitude.Assign(28,37,8,ltsouth).
    I know that this is not correct but I cannot work out what I should use.
    I would like to store the seconds as 8.16 for example and not just 8.
    I know I can get a program that would do that for me but I would have learnt nothing that way.
    Sorry for the very simplistic question.

    Regards
    John.

    • Hi John —

      I’m using the using the following without success. Jpeg.ExifData.GPSLatitude.Assign(28,37,8,ltsouth).

      Typo on my part – find the implementation of TGPSCoordinate.SetDirectionChar in CCR.Exif.pas, and change

        if NewChar = #0 then Exit;
      

      to be just

        if NewChar = #0 then 
      

      (This is fixed in v0.9.9.) If it still doesn’t work (hopefully it will though), then maybe your viewer program/online service of choice requires certain other GPS tags to be set, e.g. GPSVersion –

        Jpeg.ExifData.GPSVersion.Major := 2;
        Jpeg.ExifData.GPSVersion.Minor := 2;
      

      And if that still doesn’t do the trick, you’ll just have to experiment to see what needs to be set, assuming there aren’t any more bugs in my code – try editing an image that already has GPS info in it from another source, removing GPS tags one by one until it doesn’t work any more.

      I would like to store the seconds as 8.16 for example and not just 8.

      Think in terms of fractions rather than decimals, and you’ll be fine. Given 8.16 = 204/25, you can do this:

        Jpeg.ExifData.GPSLatitude.Assign(
          TExifFraction.Create(28, 1),
          TExifFraction.Create(37, 1),
          TExifFraction.Create(204, 25), ltSouth);
      

      If you’re reading up on the Exif format online, you’ll generally see what I call ‘fraction’ values called ‘rational’ ones.

  7. Hi Chris,

    thanks for your great component package!
    I’m trying to read and write XMP metada from PDF using latest 0.9.a lib.
    Extracting the XMP metadata stream and loading it with XMPBrowser gives me only the core properties node. Here is the XMP packet:

    -
    -

    -
    -
    -
    1

    -
    -
    2

    Acrobat displays all properties fine.
    Is it a bug or am i doing something wrong?
    Thanks and best regards
    Dirk Carstensen

    • Hi Jordi – the offset for the thumbail image data is set to the end of the Exif segment, hence the error (I see the image isn’t fresh out of the camera – is it Photoshop that has caused this, or do Olympus cameras just write imperfect Exif segments?). Anyhow, to fix (or rather, to ignore), TExifData.LoadFromStream needs to be altered to validate said offset, and ignore it if it is invalid – something like, for instance, replacing

        if not FindThumbnailOffset(Offset) then Exit;
      

      with

        if not FindThumbnailOffset(Offset) or 
          (Offset + OffsetBase >= Stream.Size) then Exit;
      
  8. Hi, so I cannot read remaining exif text data? Windows shows me that info.

    On the other hand, my “FindThumbnailOffset(Stream, Offset)” function has only “Offset” parameter. I’m using an old library version?

    Regards.

    • “Hi, so I cannot read remaining exif text data?”

      Obviously not, else I wouldn’t have given you a fix, would I?

      “Windows shows me that info.”

      Your point? It’s a case of malformed Exif data where the parser writer needs to decide: silently ignore the error, or make it obvious there is one. In this case, I’ve decided to amend my working code by adding a sanity check that silently ignores such an offset. In principle however, there’s only so many sanity checks you can add.

      “On the other hand, my “FindThumbnailOffset(Stream, Offset)” function has only “Offset” parameter.”

      Typo on my part, sorry (I’ve corrected my original comment now). Really though, couldn’t you have figured it out yourself? After all, I gave you the rationale for the change (‘the offset for the thumbail image data is set to the end of the Exif segment, hence the error’).

  9. Hi guys,

    Im using the CCR.Exif in a simple example using to create a TJpegImageEx and putting it into an image on a form, however I have a number of images which contain a small 160px thumbnail which is going in the image. The actual image when loaded in Paint Shop Pro or Windows previewer comes out at the full size of 500px. How do I get the full size image out of the EXIF component?

    Thanks in Advance.

    Phil C

    • Actually scrap that, the error is that the images and several others work fine in the other image programs, but when I load them up using the library, the image is corrupted – it seems shifted to the right by a quater, the colours are in strips vertically and interlaced with black and white, a bit like watching a black and white TV. Whats going on there when the other programs can read it fine?

      Phil C

      • Hi Phil —

        My TJpegImageEx is just a simple descendent of the standard TJpegImage, done for convenience — it doesn’t do any image loading itself, having an interest only in metadata. If it’s somehow corrupting the former then that would be a bug, yes, though I’m not sure how it could happen. Does the image load OK with TJpegImage? If ‘no’, then your issue is with the Delphi RTL, if ‘yes’, then I’d like to have a look at it.

        • Hi,
          Thanks for thw quick reply.
          Alas no the images all load in the same corrupted manner in a normal JPEG object.. im actually trying to load them into a DirectX texture and its failing with invalid data – the thing that annoys me is that every other program loads it fine – even in web browsers! They are doing something that Delphi isnt, so there is a way of reading these files.. I suspect it will load in C# aswell, I guess I could use a DLL in a different language to load them up on command, and resave them in a different format, but I should have to really.

          • A couple possibilities at the top of my head:
            – Is anything like your problem in QC? If it is, you might find a user-provided fix (I know I’ve had to patch JPEG.pas in the past).
            – If you just want to load the image, see if the OLE API will do it. Delphi provides a handy wrapper – add AxCtrls to your uses clause and instantiate TOleGraphic. Annoyingly, it doesn’t implement LoadFromFile, but that’s easy to fix:

            uses
              AxCtrls;
            
            type
              TOleGraphicEx = class(TOleGraphic)
              public
                procedure LoadFromFile(const Filename: string); override;
              end;
            
            
            procedure TOleGraphicEx.LoadFromFile(const Filename: string);
            var
              Stream: TFileStream;
            begin
              Stream := TFileStream.Create(Filename, fmOpenRead);
              try
                LoadFromStream(Stream);
              finally
                Stream.Free;
              end;
            end;  
            

            Example usage —

            procedure TForm1.Button1Click(Sender: TObject);
            const
              MyJpegFile = 'C:\Users\CCR\Desktop\Test\Sea World 01.jpg';
            var
              OleGraphic: TOleGraphic;
            begin
              OleGraphic := TOleGraphicEx.Create;
              try
                OleGraphic.LoadFromFile(MyJpegFile);
                Image1.Picture.Assign(OleGraphic);
              finally
                OleGraphic.Free;
              end;
            end;
            
  10. Hello. I’ve received another malformed image. You can download it from:

    Can you say me if it would be readeable modifying some code?

    Thanks!

    • The short answer would be, stop editing pictures in crappy photo editors that feel the need to change an image’s Exif tags without having the wherewithal not to corrupt them! More constructively, the problem with that one is that the offset for the JPEG thumbnail is out by two bytes. This may be worked around in the next release…

  11. The problem is that it’s not me who is editing this pictures, but our costumers. My application process hundred of images per day, and a little part of them gives problems. I send you that images to make library better, but tell me if you don’t want I send you more corrupted(not standard) files.

Comments are closed.