XE2 update for my image metadata reading/writing library (CCR Exif)

I was going to get round to it eventually, though having an explicit request prompted me to actually do it: updating my open source image metadata reading/writing library (CCR Exif) to properly support XE2. The updates I’ve just posted to Google Code has it cross compiling for each of Delphi XE2’s three DCC-targeting platforms, Win32, Win64 and OS X; it should also still compile back to D2006, though at least D2007, and preferably D2009 are preferred. On the downside, iOS isn’t supported because the code remains as Delphi-specific as ever (and no, FPC 2.6.0 doesn’t help much). Also, there aren’t any FireMonkey demos as yet, though the console ones all cross compile if you have XE2 and add OS X as a target platform.

In practical terms, Win64 took as long as it took to add ‘Win64’ as a target for each of the demos; OS X was somewhat longer for a mixture of reasons, primarily because the code had some entanglement with the VCL graphics classes, and there’s no ‘clean’ way for the same code to work with both VCL and FMX on that score. By far the biggest waste of time effort was managing project files across an array of Delphi versions though, with battling Subversion a close second, i.e. not actual coding at all. As I wanted to make running the demos as newbie-friendly as possible, I’ve ended up with separate project files for D2006, D2007, D2009-10 and XE+ – just open the appropriate project group located at the top of the trunk and do a Project|Compile All. If compiling under D2007 or later, executables will then be outputted in a sub-directory off the Bin folder next to the individual projects, and DCUs in a ‘DCUs’ folder off of the main trunk.

Anyhow, the Google Code page is here; if using the File|Open From Version Control… command in the XE or XE2 IDE, the URL to enter is http://ccr-exif.googlecode.com/svn/trunk/; choose either Console Demos (XE+).groupproj or VCL Demos (XE+).groupproj as appropriate when prompted.

[Update: for information about v1.5.1, please see here.]


28 thoughts on “XE2 update for my image metadata reading/writing library (CCR Exif)

  1. By far the biggest waste of time effort was managing project files across an array of Delphi versions though, with battling Subversion a close second, i.e. not actual coding at all

    I was rolling on the floor when I read this part … But it’s true, currently battling with SVN merge also … It’s 2-3 but I’ll make it 4-3 by the end of the day

  2. Hi Chris. I have a problem with Version 1.5.1 beta
    Windows 7, Delphi XE. Error Compiling ExifList.dproj (Debug, Win32):
    [DCC Error] ExifListFrame.pas(457): E2003 Undeclared identifier: ‘TTiffDirectoryLoadError
    [DCC Error] ExifListFrame.pas(790): E2003 Undeclared identifier: ‘tdUndefined”

    Help me please.

    • Have you updated (or got) from the repository the *whole* trunk? Specifically, the demos need to be updated too – the VCL ones also have had their directory renamed from ‘GUI Demos’ to ‘VCL Demos’ (this is because I’m going to do FireMonkey demos too when I get round to it). What Subversion client are you using? If the IDE, download the whole package again as I detailed at the end of the post, preferably to a new directory. If you’re using TortoiseSVN, then update the root directory.

      • Sorry, but I can’t find a ‘VCL Demos’ directory in zip file.
        One more question. How can I get full list of EXIF tags with their names? I need to create some kind of a .INI file like this:
        [esGeneral] // Section
        Make = FUJIFILM
        Model = FinePix6900ZOOM
        Orientation = 1
        XResolution = 72.00
        YResolution = 72.00
        ResolutionUnit = 2
        Software = Digital Camera FinePix6900ZOOM Ver1.00
        DateTime = 2002:03:14 15:59:39
        YCbCrPositioning = 2
        Copyright =
        FNumber = F11.0
        ExposureProgram = 3
        ISOSpeedRatings = 400
        …etc ?

      • ‘Sorry, but I can’t find a ‘VCL Demos’ directory in zip file.’

        Use a Subversion client (Tortoise SVN or the XE/XE2 IDE) to get the latest version.

        ‘How can I get full list of EXIF tags with their names?’

        Do you mean a full list of *possible* Exif tages or a full list of tags in a specific image? If the former, Google for ‘Exif specification’; if the latter, enumerate the TExifData instance with a for/in loop, firstly to enumerate the various ‘sections’ (IFDs) and secondly to enumerate the tags within each section. You will need to do the mapping between numeric identifier and string representation yourself.

        • Hi Chris. Thank You for reply.
          I meant “a full list of tags in a specific image”. I’ll try to do the mapping between numeric identifier and string representation, though I think this feature may be useful for many other users.

        • Tags are stored by their numbers, not their names, and I don’t believe in hardcoding strings. Further, if you ever want to *write* a tag (which is something my code supports, excepting maker notes), you should understand its type. That said, see my comment below, which should get you going – let me know if it doesn’t.

  3. For Vladimir – the start of some code to list all tags by name, together with their content as a string:

    function GetTagName(SectionKind: TExifSectionKindEx; TagID: TExifTagID): string;
      //assign a default 'name'
      FmtStr(Result, '$%.4x', [TagID]);
      //lookup tag (see CCR.Exif.TagIDs)
      case SectionKind of
          case TagID of
            ttImageDescription: Result := 'Image description';
            ttMake: Result := 'Make';
            ttModel: Result := 'Mode';
            //... and so on
          case TagID of
            ttExposureTime: Result := 'Exposure time';
            ttFNumber: Result := 'F number';
            //... and so on
          case TagID of
            ttInteropIndex: Result := 'Interop index';
            ttInteropVersion: Result := 'Interop version';
            //... and so on
          case TagID of
            ttGPSVersionID: Result := 'GPS version';
            ttGPSLatitudeRef: Result := 'GPS latitude ref';
            //... and so on
          case TagID of
            ttCompression: Result := 'Compression';
            ttJPEGIFOffset: Result := 'JPEG data offset';
            //... and so on
        esMakerNote: {... need to a lookup like the ExifList demo does};
    procedure EnumExifData(ExifData: TCustomExifData);
      Section: TExifSection;
      Tag: TExifTag;
      for Section in ExifData do
        for Tag in Section do
          WriteLn(GetTagName(Section.Kind, Tag.ID), ': ', Tag.AsString);
    • Hi Chris.
      I’ve created an array of Records, containing TagIDs and corresponding TagNames. It works fine. Thank You.

  4. Hi Chris,
    I have some jpg files from a Canon camera. The were taken with the camera rotated. In the EXIF data, I can see the ImageWidth is wider than the ImageHeight indicating that it is NOT rotated but they are (and display that way in any image viewer). If I look at the MakerNotes, I can see it has a “Auto Rotate” tag but it does not seem to be parsed correctly with a value of 26995! I can also see tags $1001 and $1002 that seem to be the correct values but they are in the “general” section so jpgdump does not get the correct text label.

    What would be the correct way to decode the width and height? In my application code, I need to determine the image size so I can reshape my display window to fit. So, if the width and height are reversed, the image gets stretched a bit 🙂

    Using lasted beta version.

    • Hi Bill –
      It would be easiest if you could put up an example image somewhere so I can run it through the debugger. Alternatively, email me one – {first initial}{second initial}{surname}@gmail.com

      • Er, well a few days turned into a few months, but anyhow, my code reckons your example image’s maker note is badly formed (it’s been corrupted somehow in other words). Can you please confirm the image file came *straight* from the camera?

        • Chris, the file I sent to you was not of my making so I cannot confirm its origin. Seeing that the “date taken” and “date modified” are not the same, it is likely been modified. As an interim fix, I have added a function to extract properties ttRelatedImageLength and ttRelatedImageWidth. If it is greater than 0, use these values.

  5. Hi,

    I think there is a bug setting the author for EXIF. Shouldn’t setting this Value to a non-ASCII string be allowed if EnforceASCII is false? This is my change that allows it:

    procedure TCustomExifData.SetAuthor(const Value: UnicodeString);
    var              //While it always writes both XMP properties, Windows Explorer always
      Tag: TExifTag; //set its own unicode Exif tag and clears the 'standard' ASCII one;
    begin            //we'll be a bit more intelligent though.
      XMPPacket.UpdateSeqProperty(xsDublinCore, 'creator', Value);
      XMPPacket.UpdateProperty(xsTIFF, 'Artist', Value);
      if Length(Value) = 0 then
      if not ContainsOnlyASCII(Value) then begin
        if EnforceASCII then begin
          // twm: if only ASCII (the standard) is allowed but the value contains non-ASCII
          // characters we store the value only in ttWindowsAuthor and delete the possibly
          // existing ttArtist entry.
        else begin
          // if non-ASCII characters are allowed
          // we store the value in ttArtist and ttWindowsAuthor
          FSections[esGeneral].SetStringValue(ttArtist, Value);
        if FSections[esGeneral].Find(ttArtist, Tag) then
          Tag.UpdateData(tdAscii, Length(Value), TiffString(Value)[1]);
          if not FSections[esGeneral].Find(ttWindowsAuthor, Tag) then
          FSections[esGeneral].SetStringValue(ttArtist, Value);
      FSections[esGeneral].SetWindowsStringValue(ttWindowsAuthor, Value);
    • Thanks for the suggestion. I’m not completely convinced though, sorry… The Author property of TCustomExifData is designed to correspond to the ‘Author’ tag reported by Windows Explorer. In Explorer’s case, this reads from ttArtist but always writes to ttWindowsAuthor (alias ‘XPAuthor’), deleting any existing ttArtist tag as it does so. In TCustomExifData’s case, in contrast, it always writes to ttWindowsAuthor and maybe ttArtist too *if* it is legal to do so according to the Exif specification.

      Separately, the EnforceASCII property of TCustomExifData controls what happens when you attempt to set a standard (and therefore ASCII-only) string tag with data that includes non-ASCII characters – set to True, and the exceptions that may result enforce compliance with the Exif specification. Since the Author property does not directly map to ttArtist, EnforceASCII doesn’t (obviously) apply.

      OTOH, the default value of EnforceASCII is True, so having it False indicates deliberate intention. I’ll think about it…

  6. Hi Chris,

    I found an issue with some file containing XMP. If I load its ExifData and save it again, the XMP gets corrupted, and Windows throws an error when trying to change metadata.
    Looks like the XMPHeader is written twice for some reason. Could you take a look?

    Sample file: https://dl.dropbox.com/u/11272169/problem_sample.jpg

    Sample code:

    exif := TExifData.Create();

    • Hi Phil

      Sorry, it’s been a while, but I had other things to do. Can you upload test.jpg too please? This is because the SaveToGraphic method writes to an existing graphic (a TExifData instance holds just the metadata, not the actual image), and when I use the following code using v1.5.1, I cannot repeat the bug you report:

        System.SysUtils, CCR.Exif, CCR.Exif.XMPUtils;
        ImageFile = 'C:\Users\CCR\Documents\RAD Studio\Projects\CCR Exif\Test images\Phil (June 2012)\problem_sample - Copy.jpg';
        ExifData: TExifData;
        ExifData := TExifData.Create;
          ExifData.XMPPacket[xsXMPBasic]['Test'].WriteValue('This is a test!');
      • Hey Chris,

        test.jpg is basically just a smaller version of the original, I uploaded it here:

        I checked again with your code and it works, but not if I just want to copy the metadata without modifying (skipping line 12 of yours).

      • Thanks – I’ve found the bug. Update your copy of CCR.Exif.XMPUtils.pas, and it should be fixed.

  7. Hi! First, this is a fantastic library and I really appreciate all that you have done here!!

    I am working with the latest beta (1.5.1) and I do need help one thing, and it may be a matter of just getting a better understanding of what is going on. I am having a problem reading and writing the UserRating property.

    For example, in Windows Explorer I can set the property to 3 stars, and if I view that file with Adobe Bridge or Adobe Photoshop, I can see that is has a rating of 3 stars. However, if I open it with ExifData.LoadFromGraphic, it has a rating of urUndefined. If I assign a new value as save it using ExifData.SaveToGraphic, I do not see any changes (after refreshing) in either Windows Explorer or the Adobe products. However, if I reload the image through my Delphi app, I see the changes.

    What I’m not sure of is 1) where are these changes actually being store when if use ExifData.SaveToGraphic? and 2) exactly how do I modify the star rating so that I can see the changes in Windows Explorer and other applications? This capability, assigning the star rating, is critical to me and I would appreciate any help/guidance you may offer.

    I can provide sample files and/or anything else you may need.


    • For anyone reading this, the two issues Skip found should be fixed in my recent SVN commit…

      • Thanks, Chris! This works perfectly; the Windows rating and the xmp UserRating are in sync. (I had no idea they were separate fields.)

        Another (hopefully simple) question. I’m trying to produce a standard (for photographers) representation of shutter speed. I’m seeing your properties (ShutterSpeedValue Numerator, Denominator, Quotient), but can’t figure out how to use those values to get to 1/200 or 1/15 or 1/2000 or the like. Any suggestions?


      • 1/200 would be

        ExifData.ShutterSpeedValue := TExifSignedFraction.Create(1, 200);

        As the type of ShutterSpeedValue is a record (namely, TExifSignedFraction), you need to assign a whole new record rather than set a sub-property.

Comments are closed.