{------------------------------------------------------------------------------} { } { Helper functions for working with EXIF fields with ImageEn } { } { Nigel Cross } { Xequte Software } { nigel@xequte.com } { http://www.xequte.com } { } { © Xequte Software 2009-2014 } { } { Modified 16/4/13 } { TListView support added by William Miller, Adirondack Software & Graphics } {------------------------------------------------------------------------------} (* Copyright (c) 1998-2014 by Carlotta Calandra. All rights reserved. Copyright (c) 2011-2014 by Xequte software. This software comes without express or implied warranty. In no case shall the author be liable for any damage or unwanted behavior of any computer hardware and/or software. Author grants you the right to include the component in your application, whether COMMERCIAL, SHAREWARE, or FREEWARE. ImageEn, IEvolution and ImageEn ActiveX may not be included in any commercial, shareware or freeware libraries or components. *) (* File version 1007 *) // STRING GRID SUPPORT // Define to include methods for loading and saving content to/from TStringGrids {$DEFINE USE_STRINGGRIDS} // LIST VIEW SUPPORT // Define to include methods for loading and saving content to/from TListViews {$DEFINE USE_LISTVIEW} unit iexEXIFRoutines; {$I ie.inc} interface {$ifdef IEHASRECORDASCLASSES} {$ifdef IEHASUNICODEWARNS} {$WARN IMPLICIT_STRING_CAST OFF} {$WARN IMPLICIT_STRING_CAST_LOSS OFF} {$endif} uses {$IFDEF USE_STRINGGRIDS} grids, {$ENDIF} {$IFDEF USE_LISTVIEW} ComCtrls, {$ENDIF} ImageEnIO, dialogs, Sysutils, math, Graphics, Classes; // returns true if the specified filename can support writing of EXIF Fields function EXIFCompatibleFile(const sFilename: string): boolean; {$IFDEF USE_STRINGGRIDS} // sets up a string grid for EXIF editing/display, by adding all property names // also adds a header row if FixedRow >= 1 // if sFilename is specified then it sets the read/write ability of the grid based on whether the format supports EXIF writign (RAW does not) procedure InitializeEXIFStringGrid(AStringGrid: TStringGrid; sFilename: string = ''); {$ENDIF} {$IFDEF USE_LISTVIEW} // sets up a listview for EXIF editing/display, by adding all property names // also adds a header row if FixedRow >= 1 // if sFilename is specified then it sets the read/write ability of the grid based on whether the format supports EXIF writign (RAW does not) procedure InitializeEXIFListView(AListview: TListView; sFilename: string = ''); {$ENDIF} {$IFDEF USE_STRINGGRIDS} // loads the EXIF fields for the specified file into the String Grid // if bLoadParams=false then it is assumed that AnImageEnIO has already loaded the file // result is whether the file has any fields specified function LoadEXIFFields(AStringGrid: TStringGrid; sFilename: string; AnImageEnIO: TImageEnIO; bLoadParams: boolean = true): boolean; overload; {$ENDIF} {$IFDEF USE_LISTVIEW} // loads the EXIF fields for the specified file into the ListView // if bLoadParams=false then it is assumed that AnImageEnIO has already loaded the file // result is whether the file has any fields specified function LoadEXIFFields(AListView: TListView; sFilename: string; AImageEnIO: TImageEnIO; bLoadParams: boolean = true): boolean; overload; {$ENDIF} {$IFDEF USE_STRINGGRIDS} // Clears the EXIF fields from the specified ImageEnIO object and in the specified stringgrid procedure ClearEXIFFields(AStringGrid: TStringGrid; AnImageEnIO: TImageEnIO); overload; {$ENDIF} {$IFDEF USE_LISTVIEW} // Clears the EXIF fields from the specified ImageEnIO object and in the specified listview procedure ClearEXIFFields(AListView: TListView; AImageEnIO: TImageEnIO); overload; {$ENDIF} {$IFDEF USE_STRINGGRIDS} // saves the EXIF fields in the specified stringgrid to the file procedure SaveEXIFFields(AStringGrid: TStringGrid; sFilename: string; AnImageEnIO: TImageEnIO; const bMaintainFileDates: boolean); // if true the file date is not modified when saving the file {$ENDIF} // gets a string representation of the exif data specified by the field index function GetEXIFField(AnImageEnIO: TImageEnIO; iFieldIndex: Integer): string; // exif data specified by the field index with the value in the specified string procedure SetEXIFField(AnImageEnIO: TImageEnIO; iFieldIndex: Integer; value: string); // returns some common EXIF fields : EXIF_Model, EXIF_ExposureTime, EXIF_Flash function GetUsefulEXIFFields(sFilename: string; AnImageEnIO: TImageEnIO; var sCameraModel: string; var sExposureTime: string; var sFlashMode: string ): boolean; // return the GPS fields from the exif fields function ExifGPSData(sFilename: string; AnImageEnIO: TImageEnIO; out GPSLatitudeDegrees: Double; out GPSLongitudeDegrees: Double ): Boolean; // reformats an EXIF date string using the localized date settings (shortdate) function ReformatEXIFDateTime(sEXIFDateTime: string): string; // EXIF CONST ROUTINES // Allow user to specify exif consts, such as %EXIF-User-Comment%, in their text // then can use this routine to convert all consts to their actual EXIF value // Get a list of all EXIF consts procedure GetAllExifFields(ssDest: TStrings; bClearList: Boolean); // Reurns true is sText cotnains an exif const such as %EXIF-User-Comment% function ContainsExifField(const sText: string): Boolean; // Replaces any exif consts in sText with the actual values // bSampleOnly : if we are just showing the kind of text we might output, returns example text rather than '' function ReplaceExifFields(const sText: string; AnImageEnIO: TImageEnIO; sFilename: string; bSampleOnly: Boolean = False): string; const _EXIF_UserComment = 0; _EXIF_ImageDescription = 1; _EXIF_Camera = 2; _EXIF_XResolution = 3; _EXIF_YResolution = 4; _EXIF_DateTime = 5; _EXIF_DateTimeOriginal = 6; _EXIF_DateTimeDigitized = 7; _EXIF_Copyright = 8; _EXIF_Orientation = 9; _EXIF_ExposureTime = 10; _EXIF_FNumber = 11; _EXIF_ExposureProgram = 12; _EXIF_ISOSpeedRatings = 13; _EXIF_ShutterSpeedValue = 14; _EXIF_ApertureValue = 15; _EXIF_BrightnessValue = 16; _EXIF_ExposureBiasValue = 17; _EXIF_MaxApertureValue = 18; _EXIF_SubjectDistance = 19; _EXIF_MeteringMode = 20; _EXIF_LightSource = 21; _EXIF_Flash = 22; _EXIF_FocalLength = 23; _EXIF_FlashPixVersion = 24; _EXIF_ColorSpace = 25; _EXIF_ExifImageWidth = 26; _EXIF_ExifImageHeight = 27; _EXIF_RelatedSoundFile = 28; _EXIF_FocalPlaneXResolution = 29; _EXIF_FocalPlaneYResolution = 30; _EXIF_ExposureIndex = 31; _EXIF_SensingMethod = 32; _EXIF_FileSource = 33; _EXIF_SceneType = 34; _EXIF_YCbCrPositioning = 35; _EXIF_ExposureMode = 36; _EXIF_WhiteBalance = 37; _EXIF_DigitalZoomRatio = 38; _EXIF_FocalLengthIn35mmFilm = 39; _EXIF_SceneCaptureType = 40; _EXIF_GainControl = 41; _EXIF_Contrast = 42; _EXIF_Saturation = 43; _EXIF_Sharpness = 44; _EXIF_SubjectDistanceRange = 45; _EXIF_GPSLatitude = 46; _EXIF_GPSLongitude = 47; _EXIF_GPSAltitude = 48; _EXIF_GPSImageDirection = 49; _EXIF_GPSTrack = 50; _EXIF_GPSSpeed = 51; _EXIF_GPSDateAndTime = 52; _EXIF_GPSSatellites = 53; _EXIF_GPSVersionID = 54; _EXIF_Artist = 55; _EXIF_XPTitle = 56; _EXIF_XPComment = 57; _EXIF_XPAuthor = 58; _EXIF_XPKeywords = 59; _EXIF_XPSubject = 60; _EXIF_XPRating = 61; _EXIF_Tag_Count = 62; { SKIPPED : Skipped the following fields : EXIF_Bitmap EXIF_ResolutionUnit EXIF_SubsecTime EXIF_SubsecTimeOriginal EXIF_SubsecTimeDigitized EXIF_WhitePoint EXIF_PrimaryChromaticities EXIF_YCbCrCoefficients EXIF_ReferenceBlackWhite EXIF_CompressedBitsPerPixel EXIF_FocalPlaneResolutionUnit EXIF_ExifVersion EXIF_Software EXIF_MakerNote EXIF_ImageUniqueID EXIF_GPSStatus EXIF_GPSMapDatum EXIF_GPSMeasureMode EXIF_GPSDOP EXIF_GPSDestLatitudeRef EXIF_GPSDestLatitudeDegrees EXIF_GPSDestLatitudeMinutes EXIF_GPSDestLatitudeSeconds EXIF_GPSDestLongitudeRef EXIF_GPSDestLongitudeDegrees EXIF_GPSDestLongitudeMinutes EXIF_GPSDestLongitudeSeconds EXIF_GPSDestBearingRef EXIF_GPSDestBearing EXIF_GPSDestDistanceRef EXIF_GPSDestDistance } {$endif} implementation uses Windows, hyieutils; {$ifdef IEHASRECORDASCLASSES} type EXIF_Item = record private function GetFieldAsTextName: string; public Description: string; VarType: Integer; Editable: boolean; property FieldAsTextName: string read GetFieldAsTextName; end; resourcestring // EXIF Data s_UserComment = 'User Comment'; s_Description = 'Description'; s_Camera = 'Camera'; s_Orientation = 'Camera Orientation'; s_Inch = 'Inch'; s_CM = 'cm'; s_XResolution = 'Horizontal Resolution'; s_Yresolution = 'Vertical Resolution'; s_DateTime = 'Date and Time'; s_DateTimeOriginal = 'Original Date and Time'; s_DateTimeDigitized = 'Digitized Date and Time'; s_Copyright = 'Copyright'; s_ExposureTime = 'Exposure Time'; s_FNumber = 'F-Stop'; s_ExposureProgram = 'Exposure Program'; s_ManualControl = 'Manual'; s_ProgramNormal = 'Normal'; s_AperturePriority = 'Aperture Priority'; s_ShutterPriority = 'Shutter Priority'; s_CreativeProgram = 'Creative Program'; s_ActionProgram = 'Action Program'; s_Portraitmode = 'Portrait Mode'; s_LandscapeMode = 'Landscape Mode'; s_CompressedBitsPerPixel = 'Compression Ratio'; s_ShutterSpeedValue = 'Shutter Speed'; s_ApertureValue = 'Aperture Value'; s_ISOSpeedRatings = 'ISO Speed Rating'; s_BrightnessValue = 'Brightness'; s_ExposureBiasValue = 'Exposure Compensation'; s_MaxApertureValue = 'Max Aperture Value'; s_SubjectDistance = 'Subject Distance'; s_MeteringMode = 'Metering Mode'; s_average = 'Average'; s_CenterWeightedAverage = 'Center-weighted Average'; s_Spot = 'Spot'; s_MultiSpot = 'Multi-Spot'; s_MultiSegment = 'Multi-Segment'; s_partial = 'Partial'; s_WhiteBalance = 'Lighting'; s_daylight = 'Daylight'; s_fluorescent = 'Flourescent'; s_tungsten = 'Tungsten'; s_flash = 'Flash'; s_standardLightA = 'Standard Light A'; s_standardLightB = ' Standard Light B'; s_standardLightC = ' Standard Light C'; s_D55 = 'D55'; s_D65 = 'D65'; s_D75 = 'D75'; s_FlashDidNotFire = 'Not Used'; s_flashFired = 'Fired'; s_flashFiredNoStrobeLight = 'Fired, No Strobe Return'; s_flashFiredStrobeLight = 'Fired, with Strobe Return'; s_FocalLength = 'Focal Length'; s_FlashPixVersion = 'FlashPix Version'; s_ColorSpace = 'Color Space'; s_RGB = 'RGB'; s_Uncalibrated = 'Uncalibrated'; s_ExifImageWidth = 'Image Width'; s_ExifImageHeight = 'Image Height'; s_RelatedSoundFile = 'Sound File'; s_FocalPlaneXResolution = 'Focal Plane Horz. Resolution'; s_FocalPlaneYResolution = 'Focal Plane Vert. Resolution'; s_ExposureIndex = 'Exposure Index'; s_SensingMethod = 'Sensing Method'; s_OneChipColorAreaSensor = 'Single Chip Color Area'; s_UnknownMethod = 'Unknown Method'; s_FileSource = 'File Source'; s_DigitalStillCamera = 'Digital Still Camera'; s_UnknownDevice = 'Unknown Device'; s_SceneType = 'Scene Type'; s_DirectlyPhotographed = 'Directly Photographed'; s_YcbCrPositioning = 'Chroma Sample Point'; s_Centered = 'Centered'; s_DataPoint = 'Data Point'; s_Seconds = 'Seconds'; s_Second = 'Second'; s_EXIFOrientation1 = 'Orientated Correctly'; s_EXIFOrientation2 = 'Horizontally Flipped'; s_EXIFOrientation3 = 'Offset by 180°'; s_EXIFOrientation4 = 'Vertically Flipped'; s_EXIFOrientation5 = 'Flipped Horiz. and Offset 90° CCW'; s_EXIFOrientation6 = 'Offset by 90° CCW'; s_EXIFOrientation7 = 'Flipped Horiz. and Offset 90° CW'; s_EXIFOrientation8 = 'Offset by 90° Clockwise'; s_ExposureMode = 'Exposure Mode'; s_DigitalZoomRatio = 'Digital Zoom Ratio'; s_FocalLengthIn35mmFilm = 'Focal Length in 35mm Film'; s_SceneCaptureType = 'Scene Capture Type'; s_GainControl = 'Gain Control'; s_Contrast = 'Contrast'; s_Saturation = 'Saturation'; s_Sharpness = 'Sharpness'; s_SubjectDistanceRange = 'Subject Distance'; s_GPSLatitude = 'GPS Latitude'; // Degrees s_GPSLongitude = 'GPS Longitude'; // Degrees s_GPSAltitude = 'GPS Altitude'; s_GPSImageDirection = 'GPS Image Direction'; s_GPSTrack = 'GPS Movement Direction'; s_GPSSpeed = 'GPS Movement Speed'; s_GPSDateAndTime = 'GPS Date and Time'; s_GPSSatellites = 'GPS Satellites'; s_GPSVersionID = 'GPS Version'; s_AutoExposure = 'Auto exposure'; s_ManualExposure = 'Manual exposure'; s_AutoBracket = 'Auto bracket'; s_Autowhitebalance = 'Auto white balance'; s_Manualwhitebalance = 'Manual white balance'; s_Standard = 'Standard'; s_Landscape = 'Landscape'; s_Portrait = 'Portrait'; s_NightScene = 'Night scene'; s_None = 'None'; s_LowGainup = 'Low gain up'; s_HighGainup = 'High gain up'; s_LowGaindown = 'Low gain down'; s_HighGaindown = 'High gain down'; s_Normal = 'Normal'; s_Soft = 'Soft'; s_Hard = 'Hard'; s_LowSaturation = 'Low saturation'; s_HighSaturation = 'High saturation'; s_Macro = 'Macro'; s_CloseView = 'Close view'; s_DistantView = 'Distant view'; s_Artist = 'Artist'; s_XPTitle = 'Title (Windows)'; s_XPComment = 'Comment (Windows)'; s_XPAuthor = 'Author (Windows)'; s_XPKeywords = 'Keywords (Windows)'; s_XPSubject = 'Subject (Windows)'; s_XPRating = 'Rating (Windows)'; const _vString = 1; _vInteger = 2; _vDouble = 3; Support_Complex = false; Digital_Raw_Camera_Formats = '*.ARW;*.CRW;*.CR2;*.DNG;*.NEF;*.RAW;*.PEF;*.RAF;*.X3F;*.BAY;*.ORF;*.MRW;*.SRF;*.DCR;'; EXIF_COMPATIBLE_EXTENSIONS = '*.TIF;*.TIFF;*.JPE;*.JPG;*.JPEG;' + Digital_Raw_Camera_Formats; EXIF : array [0.._EXIF_Tag_Count-1] of EXIF_Item=( (Description : s_UserComment ; VarType : _vString; Editable : TRUE ), // _EXIF_UserComment (Description : s_Description ; VarType : _vString; Editable : TRUE ), // _EXIF_ImageDescription (Description : s_Camera ; VarType : _vString; Editable : TRUE ), // _EXIF_Camera (Description : s_XResolution ; VarType : _vDouble; Editable : FALSE ), // _EXIF_XResolution (Description : s_YResolution ; VarType : _vDouble; Editable : FALSE ), // _EXIF_YResolution (Description : s_DateTime ; VarType : _vString; Editable : TRUE ), // _EXIF_DateTime (Description : s_DateTimeOriginal ; VarType : _vString; Editable : TRUE ), // _EXIF_DateTimeOriginal (Description : s_DateTimeDigitized ; VarType : _vString; Editable : TRUE ), // _EXIF_DateTimeDigitized (Description : s_Copyright ; VarType : _vString; Editable : TRUE ), // _EXIF_Copyright (Description : s_Orientation ; VarType : _vInteger; Editable : FALSE ), // _EXIF_Orientation (Description : s_ExposureTime ; VarType : _vDouble; Editable : Support_Complex ), // _EXIF_ExposureTime (Description : s_FNumber ; VarType : _vDouble; Editable : Support_Complex ), // _EXIF_FNumber (Description : s_ExposureProgram ; VarType : _vInteger; Editable : Support_Complex ), // _EXIF_ExposureProgram (Description : s_ISOSpeedRatings ; VarType : _vInteger; Editable : Support_Complex ), // _EXIF_ISOSpeedRatings (Description : s_ShutterSpeedValue ; VarType : _vDouble; Editable : Support_Complex ), // _EXIF_ShutterSpeedValue (Description : s_ApertureValue ; VarType : _vDouble; Editable : Support_Complex ), // _EXIF_ApertureValue (Description : s_BrightnessValue ; VarType : _vDouble; Editable : Support_Complex ), // _EXIF_BrightnessValue (Description : s_ExposureBiasValue ; VarType : _vDouble; Editable : Support_Complex ), // _EXIF_ExposureBiasValue (Description : s_MaxApertureValue ; VarType : _vDouble; Editable : Support_Complex ), // _EXIF_MaxApertureValue (Description : s_SubjectDistance ; VarType : _vDouble; Editable : Support_Complex ), // _EXIF_SubjectDistance (Description : s_MeteringMode ; VarType : _vInteger; Editable : FALSE ), // _EXIF_MeteringMode (Description : s_WhiteBalance ; VarType : _vInteger; Editable : FALSE ), // _EXIF_LightSource (Description : s_Flash ; VarType : _vInteger; Editable : FALSE ), // _EXIF_Flash (Description : s_FocalLength ; VarType : _vDouble; Editable : TRUE ), // _EXIF_FocalLength (Description : s_FlashPixVersion ; VarType : _vString; Editable : TRUE ), // _EXIF_FlashPixVersion (Description : s_ColorSpace ; VarType : _vInteger; Editable : FALSE ), // _EXIF_ColorSpace (Description : s_ExifImageWidth ; VarType : _vInteger; Editable : TRUE ), // _EXIF_ExifImageWidth (Description : s_ExifImageHeight ; VarType : _vInteger; Editable : TRUE ), // _EXIF_ExifImageHeight (Description : s_RelatedSoundFile ; VarType : _vString; Editable : TRUE ), // _EXIF_RelatedSoundFile (Description : s_FocalPlaneXResolution ; VarType : _vDouble; Editable : FALSE ), // _EXIF_FocalPlaneXResolution (Description : s_FocalPlaneYResolution ; VarType : _vDouble; Editable : FALSE ), // _EXIF_FocalPlaneYResolution (Description : s_ExposureIndex ; VarType : _vDouble; Editable : TRUE ), // _EXIF_ExposureIndex (Description : s_SensingMethod ; VarType : _vInteger; Editable : FALSE ), // _EXIF_SensingMethod (Description : s_FileSource ; VarType : _vInteger; Editable : FALSE ), // _EXIF_FileSource (Description : s_SceneType ; VarType : _vInteger; Editable : FALSE ), // _EXIF_SceneType (Description : s_YCbCrPositioning ; VarType : _vInteger; Editable : FALSE ), // _EXIF_YCbCrPositioning (Description : s_ExposureMode ; VarType : _vInteger; Editable : FALSE ), // _EXIF_ExposureMode (Description : s_WhiteBalance ; VarType : _vInteger; Editable : FALSE ), // _EXIF_WhiteBalance (Description : s_DigitalZoomRatio ; VarType : _vDouble; Editable : Support_Complex ), // _EXIF_DigitalZoomRatio (Description : s_FocalLengthIn35mmFilm ; VarType : _vInteger; Editable : Support_Complex ), // _EXIF_FocalLengthIn35mmFilm (Description : s_SceneCaptureType ; VarType : _vInteger; Editable : FALSE ), // _EXIF_SceneCaptureType (Description : s_GainControl ; VarType : _vInteger; Editable : FALSE ), // _EXIF_GainControl (Description : s_Contrast ; VarType : _vInteger; Editable : FALSE ), // _EXIF_Contrast (Description : s_Saturation ; VarType : _vInteger; Editable : FALSE ), // _EXIF_Saturation (Description : s_Sharpness ; VarType : _vInteger; Editable : FALSE ), // _EXIF_Sharpness (Description : s_SubjectDistanceRange ; VarType : _vInteger; Editable : FALSE ), // _EXIF_SubjectDistanceRange (Description : s_GPSLatitude ; VarType : _vDouble; Editable : FALSE ), // _EXIF_GPSLatitudeDegrees (Description : s_GPSLongitude ; VarType : _vDouble; Editable : FALSE ), // _EXIF_GPSLongitudeDegrees (Description : s_GPSAltitude ; VarType : _vDouble; Editable : FALSE ), // _EXIF_GPSAltitude (Description : s_GPSImageDirection ; VarType : _vDouble; Editable : FALSE ), // _EXIF_GPSImageDirection (Description : s_GPSTrack ; VarType : _vDouble; Editable : FALSE ), // _EXIF_GPSTrack (Description : s_GPSSpeed ; VarType : _vDouble; Editable : FALSE ), // _EXIF_GPSSpeed (Description : s_GPSDateAndTime ; VarType : _vDouble; Editable : FALSE ), // _EXIF_GPSDateAndTime (Description : s_GPSSatellites ; VarType : _vString; Editable : TRUE ), // _EXIF_GPSSatellites (Description : s_GPSVersionID ; VarType : _vString; Editable : TRUE ), // _EXIF_GPSVersionID (Description : s_Artist ; VarType : _vString; Editable : TRUE ), // _EXIF_Artist (Description : s_XPTitle ; VarType : _vString; Editable : TRUE ), // _EXIF_XPTitle (Description : s_XPComment ; VarType : _vString; Editable : TRUE ), // _EXIF_XPComment (Description : s_XPAuthor ; VarType : _vString; Editable : TRUE ), // _EXIF_XPAuthor (Description : s_XPKeywords ; VarType : _vString; Editable : TRUE ), // _EXIF_XPKeywords (Description : s_XPSubject ; VarType : _vString; Editable : TRUE ), // _EXIF_XPSubject (Description : s_XPRating ; VarType : _vInteger; Editable : TRUE ) // _EXIF_XPRating ); // FieldAsText is a representation of the exif field that a user can put in a text string to be replaced // dymanically by the actual value read from the file, e.g. %EXIF-User-Comment% Exif_Field_As_Text_Prefix = '%EXIF-'; Exif_Field_As_Text_Suffix = '%'; // return a double value as a fraction function DoubleToFraction(dValue: double): string; begin if (dValue = 0) or (dValue = -1) then result := '' else result := '1/' + floattostr(dValue); end; // return a double value as a fraction function FractionToDouble(dValue: string): double; begin if pos('1/', dValue) <> 1 then raise EConvertError.create('Not a fraction'); delete(dValue, 1, 2); result := StrToFloat(dValue); end; function IntToStrOrNull(iValue: Integer): string; begin if (iValue = 0) or (iValue = -1) then Result := '' else result := inttostr(iValue); end; function FloatToStrOrNull(dValue: double; sAppend: string): string; begin if (dValue = 0) or (dValue = -1) then result := '' else result := floattostr(dValue) + sAppend; end; // convert an apex value to a string function ApexToStr(dBase: double; dValue: double; sPrepend: string): string; begin try if (dValue = 0) or (dValue = -1) then raise EConvertError.create('Invalid value'); dValue := Power(dBase, dValue); if (sPrepend = '1/') and (dValue > 0) and (dvalue <= 1) then // cope with values such as 0.17 result := format('%0.1g', [1 / dValue]) else result := sPrepend + format('%0.1g', [dValue]); except result := ''; end; end; // convert a string to an apex value function FStrToApex(Value: string; sPrepended: string; dBase: double): double; begin // NOT YET SUPPORTED raise EConvertError.create('Apex value'); end; // remove sStrip from the end of the string value function StripSuffix(Value: string; sStrip: string): string; begin result := value; if pos(uppercase(sStrip), uppercase(result)) = (length(result) - length(sStrip)) + 1 then setlength(result, length(result) - length(sStrip)); end; // remove the null terminator from a string function RemoveNull(sValue: string): string; begin result := trim(svalue); if (result <> '') and (result[length(result)] = #0) then SetLength(result, length(result) - 1); result := trim(result); end; // return true if the specified filename can support writing of EXIF Fields function EXIFCompatibleFile(const sFilename: string): boolean; begin Result := IEFilenameInExtensions(sFilename, EXIF_COMPATIBLE_EXTENSIONS); end; // return true if the specified filename is a digital camera raw file function IsRawFile(const sFilename: string): boolean; begin result := IEFilenameInExtensions(sFilename, Digital_Raw_Camera_Formats); end; {$IFDEF USE_STRINGGRIDS} // sets up a string grid for EXIF editing/display, by adding all property names // also adds a header row if FixedRow >= 1 procedure InitializeEXIFStringGrid(AStringGrid: TStringGrid; sFilename: string = ''); var i: integer; begin with AStringGrid do begin // add a row for each EXIF plus the header (if tjhere is a fixed row) RowCount := high(EXIF) + 1 + AStringGrid.FixedRows; // header row Cells[0, 0] := 'Property'; Cells[1, 0] := 'Value'; // fill the fields with the EXIF property names for i := 0 to high(EXIF) do begin Cells[0, i + AStringGrid.FixedRows] := EXIF[i].Description; Cells[1, i + AStringGrid.FixedRows] := ''; end; if (sFilename <> '') and IsRawFile(sFilename) then begin Color := clBtnFace; Options := Options - [goEditing]; end else begin Color := clWindow; Options := Options + [goEditing]; end; end; end; {$ENDIF} {$IFDEF USE_LISTVIEW} // sets up a listview for EXIF editing/display, by adding all property names procedure InitializeEXIFListView(AListView: TListView; sFilename: string = ''); var i: integer; iListColumn: TListColumn; iListItem: TListItem; begin with AListView do begin { setup the columns } iListColumn := Columns.Add; iListColumn.Caption := 'Parameter'; iListColumn.Width := 150; iListColumn := Columns.Add; iListColumn.Caption := 'Value'; iListColumn.Width := 450; // fill the fields with the EXIF property names for i := 0 to high(EXIF) do begin iListItem := AListView.Items.Add; iListItem.Caption := EXIF[i].Description; iListItem.SubItems.Add('') end; if (sFilename <> '') and IsRawFile(sFilename) then begin Color := clBtnFace; end else begin Color := clWindow; end; end; end; {$ENDIF} function ExposureToString(iExposureTime: double): string; begin if iExposureTime > 1 then // then it will be something like 4 i.e. 4 seconds result := inttostr(round(iExposureTime)) + ' ' + s_Seconds else if (iExposureTime > 0) then // then it is an integer second value, e.g. 0.14.. which transslated to 7 seconds result := DoubleToFraction(round(1 / iExposureTime)) + ' ' + s_Second; end; function FlashModeToString(iFlash: Integer): string; begin Case iFlash of 0: result := s_FlashDidNotFire; 1: result := s_flashFired; 5: result := s_flashFiredNoStrobeLight; 7: result := s_flashFiredStrobeLight; else result := ''; end; end; {$IFDEF USE_STRINGGRIDS} // loads the EXIF fields for the specified file into the String Grid // if bLoadParams=false then it is assumed that AnImageEnIO has already loaded the file // result is whether the file has any fields specified function LoadEXIFFields(AStringGrid: TStringGrid; sFilename: string; AnImageEnIO: TImageEnIO; bLoadParams: boolean = true): boolean; var i: integer; begin result := True; InitializeEXIFStringGrid(AStringGrid, sFilename); if bLoadParams then AnImageEnIO.paramsfromfile(sFilename); if (AnImageEnIO.params.EXIF_HasEXIFData = false) and (IsRawFile(sFilename) = false) then begin result := False; exit; end; for i := low(EXIF) to high(EXIF) do AStringGrid.cells[1, i + AStringGrid.FixedRows] := GetEXIFField(AnImageEnIO, i); end; {$ENDIF} {$IFDEF USE_LISTVIEW} // loads the EXIF fields for the specified file into the ListView // if bLoadParams=false then it is assumed that AnImageEnIO has already loaded the file // result is whether the file has any fields specified function LoadEXIFFields(AListView: TListView; sFilename: string; AImageEnIO: TImageEnIO; bLoadParams: boolean = true): boolean; var i: integer; begin result := True; InitializeEXIFListView(AListview, sFilename); if bLoadParams then AImageEnIO.ParamsFromFile(sFilename); if (AImageEnIO.Params.EXIF_HasEXIFData = false) and (IsRawFile(sFilename) = false) then begin result := False; exit; end; for i := low(EXIF) to high(EXIF) do AListView.Items.Item[i].SubItems[0] := GetEXIFField(AImageEnIO, i); end; {$ENDIF} // get a string representation of the exif data specified by the field index function GetEXIFField(AnImageEnIO: TImageEnIO; iFieldIndex: Integer): string; var sFPUnit: string; // used in retrieval of EXIF data (resolution unit) sResUnit: string; // used in retrieval of EXIF data (resolution unit) begin result := ''; with AnImageEnIO.params do try case iFieldIndex of _EXIF_UserComment: result := RemoveNull(EXIF_UserComment); // EXIF_ImageDescription // Describes the image _EXIF_ImageDescription: result := RemoveNull(EXIF_ImageDescription); //_EXIF_Make : result := RemoveNull(EXIF_Make); // EXIF_Model // Shows model number of digicam //_EXIF_Model : result := RemoveNull(EXIF_Model); _EXIF_Camera: begin result := RemoveNull(EXIF_Make) + ' ' + RemoveNull(EXIF_Model); if (Result <> '') and (Result[1] = ' ') then Delete(Result, 1, 1); if (Result <> '') and (Result[Length(Result)] = ' ') then SetLength(Result, Length(Result) - 1); end; // EXIF_XResolution // Display/Print resolution of image. Default value is 1/72inch, but it has no mean because personal computer doesn't use this value to display/print out. // EXIF_ResolutionUnit // Unit of XResolution/YResolution. { 1 means no-unit 2 means inch 3 means centimeter Default value is 2 (inch) } _EXIF_XResolution: begin case EXIF_ResolutionUnit of 2: sResUnit := s_Inch; 3: sResUnit := s_CM; else sResUnit := ''; end; result := DoubleToFraction(EXIF_Xresolution) + ' ' + sResUnit; end; // EXIF_YResolution // Display/Print resolution of image. Default value is 1/72inch, but it has no mean because personal computer doesn't use this value to display/print out. // EXIF_ResolutionUnit // Unit of XResolution/YResolution. { 1 means no-unit 2 means inch 3 means centimeter Default value is 2 (inch) } _EXIF_YResolution: begin case EXIF_ResolutionUnit of 2: sResUnit := s_Inch; 3: sResUnit := s_CM; else sResUnit := ''; end; result := DoubleToFraction(EXIF_Yresolution) + ' ' + sResUnit; end; // EXIF_Software // Shows firmware(internal software of digicam) version number // _EXIF_Software : result := RemoveNull(EXIF_Software); // EXIF_DateTime // Date/Time of image was last modified. Data format is "YYYY : MM : DD HH : MM : SS"+0x00, total 20bytes. If clock has not set or digicam doesn't have clock, the field may be filled with spaces. In usual, it has the same value of DateTimeOriginal _EXIF_DateTime: result := ReformatEXIFDateTime(RemoveNull(EXIF_DateTime)) {+sSubsecTime}; // EXIF_DateTimeOriginal // Date/Time of original image taken. This value should not be modified by user program. Data format is "YYYY : MM : DD HH : MM : SS"+0x00, total 20bytes. If clock has not set or digicam doesn't have clock, the field may be filled with spaces. In the Exif standard, this tag is optional, but it is mandatory for DCF. _EXIF_DateTimeOriginal: result := ReformatEXIFDateTime(RemoveNull(EXIF_DateTimeOriginal)) {+ sSubsecTimeOriginal}; // EXIF_DateTimeDigitized // Date/Time of image digitized. Usually, it contains the same value of DateTimeOriginal(0x9003). Data format is "YYYY : MM : DD HH : MM : SS"+0x00, total 20bytes. If clock has not set or digicam doesn't have clock, the field may be filled with spaces. In the Exif standard, this tag is optional, but it is mandatory for DCF. _EXIF_DateTimeDigitized: result := ReformatEXIFDateTime(RemoveNull(EXIF_DateTimeDigitized)) {+ sSubsecTimeDigitized}; // EXIF_Copyright // Shows copyright information _EXIF_Copyright: result := RemoveNull(EXIF_Copyright); // EXIF_Orientation // The orientation of the camera relative to the scene, when the image was captured _EXIF_Orientation: case EXIF_Orientation of 1: result := s_EXIFOrientation1; 2: result := s_EXIFOrientation2; 3: result := s_EXIFOrientation3; 4: result := s_EXIFOrientation4; 5: result := s_EXIFOrientation5; 6: result := s_EXIFOrientation6; 7: result := s_EXIFOrientation7; 8: result := s_EXIFOrientation8; end; // EXIF_ExposureTime // Exposure time (reciprocal of shutter speed). Unit is second. _EXIF_ExposureTime: result := ExposureToString(EXIF_ExposureTime); // EXIF_FNumber // The actual F-number(F-stop) of lens when the image was taken. _EXIF_FNumber: if (EXIF_Fnumber <> 0) and (EXIF_Fnumber <> -1) then result := 'F' + FloatToStrOrNull(EXIF_Fnumber, ''); // EXIF_ExposureProgram // Exposure program that the camera used when image was taken. { 1 means manual control 2 program normal 3 aperture priority 4 shutter priority 5 program creative (slow program) 6 program action(high-speed program) 7 portrait mode 8 landscape mode. } _EXIF_ExposureProgram: case EXIF_ExposureProgram of 1: result := s_ManualControl; 2: result := s_ProgramNormal; 3: result := s_AperturePriority; 4: result := s_ShutterPriority; 5: result := s_CreativeProgram; 6: result := s_ActionProgram; 7: result := s_Portraitmode; 8: result := s_LandscapeMode; end; // EXIF_ISOSpeedRatings // CCD sensitivity equivalent to Ag-Hr film speedrate. _EXIF_ISOSpeedRatings: if (EXIF_ISOSpeedRatings[0] <> 0) and (EXIF_ISOSpeedRatings[0] <> -1) then result := inttostr(EXIF_ISOSpeedRatings[0]); // EXIF_ShutterSpeedValue // Shutter speed by APEX value. To convert this value to ordinary 'Shutter Speed'; calculate this value's power of 2, then reciprocal. For example, if the ShutterSpeedValue is '4', shutter speed is 1/(24)=1/16 second. _EXIF_ShutterSpeedValue: result := ApexToStr(2, EXIF_ShutterSpeedValue, '1/'); // EXIF_ApertureValue // The actual aperture value of lens when the image was taken. Unit is // APEX. To convert this value to ordinary F-number (F-stop), calculate // this value's power of root 2 (=1.4142). For example, if the // ApertureValue is '5', F-number is 1.41425 = F5.6. _EXIF_ApertureValue: result := ApexToStr(Sqrt(2), EXIF_ApertureValue, 'F'); // EXIF_BrightnessValue // Brightness of taken subject, unit is APEX. To calculate Exposure(Ev) from BrigtnessValue(Bv), you must add SensitivityValue(Sv). { Ev=Bv+Sv Sv=log2(ISOSpeedRating/3.125) ISO100 : Sv=5, ISO200 : Sv=6, ISO400 : Sv=7, ISO125 : Sv=5.32. } _EXIF_BrightnessValue: Result := FloatToStrOrNull(EXIF_BrightnessValue, ''); //???DoubleToFraction(EXIF_BrightnessValue); ddd // EXIF_ExposureBiasValue // Exposure bias(compensation) value of taking picture. Unit is APEX (EV). _EXIF_ExposureBiasValue: result := DoubleToFraction(EXIF_ExposureBiasValue); // EXIF_MaxApertureValue // Maximum aperture value of lens. You can convert to F-number by calculating power of root 2 (same process of ApertureValue). _EXIF_MaxApertureValue: result := ApexToStr(Sqrt(2), EXIF_MaxApertureValue, 'F'); // EXIF_SubjectDistance // Distance to focus point, unit is meter. _EXIF_SubjectDistance: if EXIF_SubjectDistance > 0 then result := FloatToStrOrNull(EXIF_SubjectDistance, ' m'); // EXIF_MeteringMode // Exposure metering method. { 0 means unknown 1 average 2 center weighted average 3 spot 4 multi-spot 5 multi-segment 6 partial 255 other. } _EXIF_MeteringMode: case EXIF_MeteringMode of 1: result := s_average; 2: result := s_CenterWeightedAverage; 3: result := s_Spot; 4: result := s_MultiSpot; 5: result := s_MultiSegment; 6: result := s_partial; end; // EXIF_LightSource // Light source, actually this means white balance setting. { 0 means unknown 1 daylight 2 fluorescent 3 tungsten 10 flash 17 standard light A 18 standard light B 19 standard light C 20 D55 21 D65 22 D75 255 other. } _EXIF_LightSource: case EXIF_LightSource of 1: result := s_daylight; 2: result := s_fluorescent; 3: result := s_tungsten; 10: result := s_flash; 17: result := s_standardLightA; 18: result := s_standardLightB; 19: result := s_standardLightC; 20: result := s_D55; 21: result := s_D65; 22: result := s_D75; end; // EXIF_Flash { 0 means flash did not fire 1 flash fired 5 flash fired but strobe return light not detected 7 flash fired and strobe return light detected } _EXIF_Flash: result := FlashModeToString(EXIF_Flash); // EXIF_FocalLength // Focal length of lens used to take image. Unit is millimeter. _EXIF_FocalLength: if EXIF_FocalLength > 0 then result := FloatToStrOrNull(EXIF_FocalLength, ' mm'); // EXIF_FlashPixVersion // Stores FlashPix version. If the image data is based on FlashPix formar Ver.1.0, value is "0100". _EXIF_FlashPixVersion: result := RemoveNull(EXIF_FlashPixVersion); // EXIF_ColorSpace // Defines Color Space. DCF image must use sRGB color space so value is always '1'. If the picture uses the other color space, value is '65535' : Uncalibrated. _EXIF_ColorSpace: case EXIF_ColorSpace of 1: result := s_RGB; 65535: result := s_Uncalibrated; end; // EXIF,_EXIFImageWidth, EXIF,_EXIFImageHeight // Size of main image. _EXIF_ExifImageWidth: result := IntToStrOrNull(EXIF_ExifImageWidth); // EXIF,_EXIFImageWidth, EXIF,_EXIFImageHeight // Size of main image. _EXIF_ExifImageHeight: result := IntToStrOrNull(EXIF_ExifImageHeight); // EXIF_RelatedSoundFile // If this digicam can record audio data with image, shows name of audio data. _EXIF_RelatedSoundFile: result := RemoveNull(EXIF_RelatedSoundFile); // EXIF_FocalPlaneResolutionUnit // Unit of FocalPlaneXResoluton/FocalPlaneYResolution. { 1 means no-unit 2 inch 3 centimeter. Note : Some of Fujifilm's digicam(e.g.FX2700,FX2900,Finepix4700Z/40i etc) uses value '3' so it must be 'centimeter', but it seems that they use a '8.3mm?'(1/3in.?) to their ResolutionUnit. Fuji's BUG? Finepix4900Z has been changed to use value '2' but it doesn't match to actual value also. } _EXIF_FocalPlaneXResolution: begin case EXIF_FocalPlaneResolutionUnit of 2: sFPUnit := s_inch; 3: sFPUnit := s_CM; else sFPUnit := ''; end; // EXIF_FocalPlaneXResolution, EXIF_FocalPlaneYResolution // Pixel density at CCD's position. If you have MegaPixel digicam and take a picture by lower resolution(e.g.VGA mode), this value is re-sampled by picture resolution. In such case, FocalPlaneResolution is not same as CCD's actual resolution. if EXIF_FocalPlaneXResolution > 0 then result := IEFloatToFormatString(EXIF_FocalPlaneXResolution, 2, true) + ' ' + sFPUnit; end; _EXIF_FocalPlaneYResolution: begin case EXIF_FocalPlaneResolutionUnit of 2: sFPUnit := s_inch; 3: sFPUnit := s_CM; else sFPUnit := ''; end; // EXIF_FocalPlaneXResolution, EXIF_FocalPlaneYResolution // Pixel density at CCD's position. If you have MegaPixel digicam and take a picture by lower resolution(e.g.VGA mode), this value is re-sampled by picture resolution. In such case, FocalPlaneResolution is not same as CCD's actual resolution. if EXIF_FocalPlaneYResolution > 0 then result := IEFloatToFormatString(EXIF_FocalPlaneYResolution, 2, true) + ' ' + sFPUnit; end; // EXIF_ExposureIndex // Same as ISOSpeedRatings(0x8827) but data type is unsigned rational. Only Kodak's digicam uses this tag instead of ISOSpeedRating. _EXIF_ExposureIndex: result := FloatToStrOrNull(EXIF_ExposureIndex, ''); // EXIF_SensingMethod // Shows type of image sensor unit. '2' means 1 chip color area sensor, most of all digicam use this type. _EXIF_SensingMethod: if EXIF_SensingMethod = 2 then result := s_OneChipColorAreaSensor {ELSE result := s_UnknownMethod)}; // EXIF_FileSource // Indicates the image source. Value '0x03' means the image source is digital still camera. _EXIF_FileSource: if EXIF_FileSource = $03 then result := s_DigitalStillCamera {ELSE result := s_UnknownDevice)}; // EXIF_SceneType // Indicates the type of scene. Value '0x01' means that the image was directly photographed. _EXIF_SceneType: if EXIF_SceneType = $01 then result := s_DirectlyPhotographed {ELSE result := s_UnknownMethod)}; // EXIF_YCbCrPositioning // When image format is YCbCr and uses 'Subsampling' (cropping of chroma data, all the digicam do that), defines the chroma sample point of subsampling pixel array. { 1 means the center of pixel array 2 means the datum point. } _EXIF_YCbCrPositioning: case EXIF_YcbCrPositioning of 1: result := s_Centered; 2: result := s_DataPoint; end; { EXIF_ExposureMode This tag indicates the exposure mode set when the image was shot. In auto-bracketing mode, the camera shoots a series of frames of the same scene at different exposure settings. 0 = Auto exposure 1 = Manual exposure 2 = Auto bracket } _EXIF_ExposureMode: case EXIF_ExposureMode of 0: s_Autoexposure; // 'Auto exposure'; 1: s_Manualexposure; // 'Manual exposure'; 2: s_Autobracket; // 'Auto bracket'; end; { EXIF_WhiteBalance This tag indicates the white balance mode set when the image was shot. 0 = Auto white balance 1 = Manual white balance } _EXIF_WhiteBalance: case EXIF_WhiteBalance of 0: s_Autowhitebalance; // 'Auto white balance'; 1: s_Manualwhitebalance; // 'Manual white balance'; end; { EXIF_DigitalZoomRatio This tag indicates the digital zoom ratio when the image was shot. If the numerator of the recorded value is 0, this indicates that digital zoom was not used. } _EXIF_DigitalZoomRatio: if (EXIF_DigitalZoomRatio = 0) or (EXIF_DigitalZoomRatio = -1) then result := s_None else result := floatToStrOrNull(EXIF_DigitalZoomRatio, ''); { EXIF_FocalLengthIn35mmFilm This tag indicates the equivalent focal length assuming a 35mm film camera, in mm. A value of 0 means the focal length is unknown. Note that this tag differs from the FocalLength tag. } _EXIF_FocalLengthIn35mmFilm: if EXIF_FocalLengthIn35mmFilm > 0 then result := IntToStr(EXIF_FocalLengthIn35mmFilm) + ' mm'; { EXIF_SceneCaptureType This tag indicates the type of scene that was shot. It can also be used to record the mode in which the image was shot. Note that this differs from the scene type (SceneType) tag. 0 = Standard 1 = Landscape 2 = Portrait 3 = Night scene } _EXIF_SceneCaptureType: case EXIF_SceneCaptureType of 0: s_Standard; // 'Standard'; 1: s_Landscape; // 'Landscape'; 2: s_Portrait; // 'Portrait'; 3: s_Nightscene; // 'Night scene'; end; { EXIF_GainControl This tag indicates the degree of overall image gain adjustment. 0 = None 1 = Low gain up 2 = High gain up 3 = Low gain down 4 = High gain down } _EXIF_GainControl: case EXIF_GainControl of 0: s_None; // 'None'; 1: s_LowGainup; // 'Low gain up'; 2: s_HighGainup; // 'High gain up'; 3: s_LowGaindown; // 'Low gain down'; 4: s_HighGaindown; // 'High gain down'; end; { EXIF_Contrast This tag indicates the direction of contrast processing applied by the camera when the image was shot. 0 = Normal 1 = Soft 2 = Hard } _EXIF_Contrast: case EXIF_Contrast of 0: s_Normal; // 'Normal'; 1: s_Soft; // 'Soft'; 2: s_Hard; // 'Hard'; end; { EXIF_Saturation This tag indicates the direction of saturation processing applied by the camera when the image was shot. 0 = Normal 1 = Low saturation 2 = High saturation } _EXIF_Saturation: case EXIF_Saturation of 0: s_Normal; // 'Normal'; 1: s_Lowsaturation; // 'Low saturation'; 2: s_Highsaturation; // 'High saturation'; end; { EXIF_Sharpness This tag indicates the direction of sharpness processing applied by the camera when the image was shot. 0 = Normal 1 = Soft 2 = Hard } _EXIF_Sharpness: case EXIF_Sharpness of 0: s_Normal; // 'Normal'; 1: s_Soft; // 'Soft'; 2: s_Hard; // 'Hard'; end; { EXIF_SubjectDistanceRange This tag indicates the distance to the subject. 0 = unknown 1 = Macro 2 = Close view 3 = Distant view } _EXIF_SubjectDistanceRange: case EXIF_SubjectDistanceRange of // 0 : s_unknown ; // 'unknown'; 1: s_Macro; // 'Macro'; 2: s_Closeview; // 'Close view'; 3: s_Distantview; // 'Distant view'; end; { Indicates the latitude degrees } _EXIF_GPSLatitude: Result := EXIF_GPSLatitude_Str; { Indicates the longitude degrees. } _EXIF_GPSLongitude: Result := EXIF_GPSLongitude_Str; _EXIF_GPSAltitude: { Indicates the altitude based on the reference in Ref. The reference unit is meters. } if (EXIF_GPSAltitude <> 0) or (EXIF_GPSAltitudeRef <> '') then begin result := FloatToStrOrNull(EXIF_GPSAltitude, ' m'); if RemoveNull(EXIF_GPSAltitudeRef) = '1' then Result := Result + ' (Below sea-level)'; end; _EXIF_GPSDateAndTime: { Indicates the altitude based on the reference in Ref. The reference unit is meters. } if (EXIF_GPSDateStamp <> '') then begin result := Format('%s %s : %s.%s', [RemoveNull(EXIF_GPSDateStamp), FloatToStr(EXIF_GPSTimeStampHour), FloatToStr(EXIF_GPSTimeStampMinute), FloatToStr(EXIF_GPSTimeStampSecond)]); end; _EXIF_GPSImageDirection: { EXIF_GPSImgDirection : Indicates the direction of the image when it was captured. The range of values is from 0.00 to 359.99. EXIF_GPSImgDirectionRef : Indicates the reference for giving the direction of the image when it is captured. 'T' denotes true direction and 'M' is magnetic direction. } if (EXIF_GPSImgDirection <> 0) or (RemoveNull(EXIF_GPSImgDirectionRef) <> '') then begin result := FloatToStrOrNull(EXIF_GPSImgDirection, ' degrees'); if uppercase(RemoveNull(EXIF_GPSImgDirectionRef)) = 'T' then Result := Result + ' (True)' else if uppercase(RemoveNull(EXIF_GPSImgDirectionRef)) = 'M' then Result := Result + ' (Magnetic)'; end; _EXIF_GPSSpeed: { EXIF_GPSSpeed : speed of GPS receiver movement EXIF_GPSSpeedRef : unit used to express the GPS receiver speed of movement. 'K' 'M' and 'N' represents kilometers per hour, miles per hour, and knots. } if (EXIF_GPSSpeed <> 0) or (RemoveNull(EXIF_GPSSpeedRef) <> '') then begin if uppercase(RemoveNull(EXIF_GPSSpeedRef)) = 'K' then Result := FloatToStr(EXIF_GPSSpeed) + ' km/h' else if uppercase(RemoveNull(EXIF_GPSSpeedRef)) = 'M' then Result := FloatToStr(EXIF_GPSSpeed) + ' mph' else if uppercase(RemoveNull(EXIF_GPSSpeedRef)) = 'N' then Result := FloatToStr(EXIF_GPSSpeed) + ' knots' else Result := FloatToStr(EXIF_GPSSpeed) + ' ' + RemoveNull(EXIF_GPSSpeedRef); end; _EXIF_GPSTrack: { EXIF_GPSTrack : direction of GPS receiver movement. The range of values is from 0.00 to 359.99. EXIF_GPSTrackRef : Indicates the reference for giving the direction of the direction of GPS receiver movement. 'T' denotes true direction and 'M' is magnetic direction. } if (EXIF_GPSTrack <> 0) or (RemoveNull(EXIF_GPSTrackRef) <> '') then begin result := FloatToStrOrNull(EXIF_GPSTrack, ' degrees'); if uppercase(RemoveNull(EXIF_GPSTrackRef)) = 'T' then Result := Result + ' (True)' else if uppercase(RemoveNull(EXIF_GPSTrackRef)) = 'M' then Result := Result + ' (Magnetic)'; end; _EXIF_GPSSatellites: result := RemoveNull(EXIF_GPSSatellites); _EXIF_GPSVersionID: result := RemoveNull(EXIF_GPSVersionID); _EXIF_Artist: result := RemoveNull(EXIF_Artist); _EXIF_XPTitle: result := RemoveNull(EXIF_XPTitle); _EXIF_XPComment: result := RemoveNull(EXIF_XPComment); _EXIF_XPAuthor: result := RemoveNull(EXIF_XPAuthor); _EXIF_XPKeywords: result := RemoveNull(EXIF_XPKeywords); _EXIF_XPSubject: result := RemoveNull(EXIF_XPSubject); _EXIF_XPRating: begin { Windows XP Rating Allowed values from 0 up to 5. -1 means not available. } if EXIF_XPRating < 0 then Result := '' else Result := IntToStr(EXIF_XPRating); end; end; except // ERROR end; end; // modifies the date/time stamp of a file // Result is true, unless there is a file load error function SetFileDate(const sFilename: string; DateTime: TDateTime): boolean; var liHandle: INTEGER; begin result := False; if DateTime <> 0 then try liHandle := FileOpen(sFileName, fmOpenReadWrite or fmShareDenyNone); if liHandle > 0 then begin result := FileSetDate(liHandle, DateTimeToFileDate(DateTime)) <> 0; FileClose(liHandle); end; except // ERROR end; end; {$IFDEF USE_STRINGGRIDS} // saves the EXIF fields in the specified stringgrid to the file procedure SaveEXIFFields(AStringGrid: TStringGrid; sFilename: string; AnImageEnIO: TImageEnIO; const bMaintainFileDates: boolean); // if true the file date is not modified when saving the file var sCurrentField : string; i : integer; dFileDate : TDateTime; bSomeFieldHasData : boolean; begin // need to know if any field has data assinged bSomeFieldHasData := false; dFileDate := 0; // to avoid compiler warniongs // load values from the properties grid for i := low(EXIF) to high(EXIF) do begin sCurrentField := AStringGrid.Cells[1, i + AStringGrid.FixedRows]; if sCurrentField <> '' then bSomeFieldHasData := True; // Has the data for that field changed? if sCurrentField <> GetEXIFField(AnImageEnIO, i) then SetEXIFField(AnImageEnIO, i, sCurrentField); end; AnImageEnIO.params.EXIF_HasEXIFData := bSomeFieldHasData; {$WARNINGS OFF} // FileAge is deprecated if bMaintainFileDates then dFileDate := FileDateToDateTime(FileAge(sFileName)); {$WARNINGS ON} case AnImageEnIO.Params.FileType of ioJPEG: // inject EXIF in jpeg AnImageEnIO.InjectJpegEXIF(sFileName); ioTIFF: // save EXIF in TIFF AnImageEnIO.SaveToFile(sFilename); else messagedlg('The format of the specified file does not support EXIF storage.', mtError, [mbok], 0); end; if bMaintainFileDates and (dFileDate <> 0) then SetFileDate(sFilename, dFileDate); end; {$ENDIF} function GetTextBeforeChar(const Value, SeekStr: string; bNullIfNotFound: boolean = false): string; var iPos: integer; begin result := Value; iPos := pos(uppercase(SeekStr), uppercase(Value)); if iPos > 0 then delete(Result, iPos, (length(Result) - iPos) + 1) else if bNullIfNotFound then result := ''; end; function GetTextAfterChar(const Value, SeekStr: string; bNullIfNotFound: boolean = false): string; var ipos: integer; begin result := Value; iPos := pos(uppercase(SeekStr), uppercase(Value)); if ipos > 0 then Delete(Result, 1, iPos + (length(SeekStr) - 1)) else if bNullIfNotFound then result := ''; end; // exif data specified by the field index with the value in the specified string procedure SetEXIFField(AnImageEnIO: TImageEnIO; iFieldIndex: Integer; value: string); begin value := trim(value); with AnImageEnIO.params do try case iFieldIndex of _EXIF_UserComment: EXIF_UserComment := value; // EXIF_ImageDescription // Describes the image _EXIF_ImageDescription: EXIF_ImageDescription := value; //_EXIF_Make : EXIF_Make :=value; // EXIF_Model // Shows model number of digicam //_EXIF_Model : EXIF_Model :=value; _Exif_Camera: begin EXIF_Make := GetTextBeforeChar(Value, ' ', False); EXIF_Model := GetTextAfterChar(Value, ' ', True); end; // EXIF_XResolution // Display/Print resolution of image. Default value is 1/72inch, but it has no mean because personal computer doesn't use this value to display/print out. // EXIF_ResolutionUnit // Unit of XResolution/YResolution. { 1 means no-unit 2 means inch 3 means centimeter Default value is 2 (inch) } _EXIF_XResolution: begin if pos(uppercase(s_Inch), uppercase(value)) > 0 then EXIF_ResolutionUnit := 2 else if pos(uppercase(s_CM), uppercase(value)) > 0 then EXIF_ResolutionUnit := 3; Value := StripSuffix(value, ' ' + s_Inch); Value := StripSuffix(value, ' ' + s_CM); EXIF_Xresolution := FractionToDouble(value); end; // EXIF_YResolution // Display/Print resolution of image. Default value is 1/72inch, but it has no mean because personal computer doesn't use this value to display/print out. // EXIF_ResolutionUnit // Unit of XResolution/YResolution. { 1 means no-unit 2 means inch 3 means centimeter Default value is 2 (inch) } _EXIF_YResolution: begin if pos(uppercase(s_Inch), uppercase(value)) > 0 then EXIF_ResolutionUnit := 2 else if pos(uppercase(s_CM), uppercase(value)) > 0 then EXIF_ResolutionUnit := 3; Value := StripSuffix(value, s_Inch); Value := StripSuffix(value, s_CM); EXIF_Yresolution := FractionToDouble(value); end; // EXIF_Software // Shows firmware(internal software of digicam) version number //_EXIF_Software : EXIF_Software :=value; // EXIF_DateTime // Date/Time of image was last modified. Data format is "YYYY : MM : DD HH : MM : SS"+0x00, total 20bytes. If clock has not set or digicam doesn't have clock, the field may be filled with spaces. In usual, it has the same value of DateTimeOriginal _EXIF_DateTime: EXIF_DateTime := DateTimeToEXIFDate(StrToDateTime(value)); // EXIF_DateTimeOriginal // Date/Time of original image taken. This value should not be modified by user program. Data format is "YYYY : MM : DD HH : MM : SS"+0x00, total 20bytes. If clock has not set or digicam doesn't have clock, the field may be filled with spaces. In the Exif standard, this tag is optional, but it is mandatory for DCF. _EXIF_DateTimeOriginal: EXIF_DateTimeOriginal := DateTimeToEXIFDate(StrToDateTime(value)); // EXIF_DateTimeDigitized // Date/Time of image digitized. Usually, it contains the same value of DateTimeOriginal(0x9003). Data format is "YYYY : MM : DD HH : MM : SS"+0x00, total 20bytes. If clock has not set or digicam doesn't have clock, the field may be filled with spaces. In the Exif standard, this tag is optional, but it is mandatory for DCF. _EXIF_DateTimeDigitized: EXIF_DateTimeDigitized := DateTimeToEXIFDate(StrToDateTime(value)); // EXIF_Copyright // Shows copyright information _EXIF_Copyright: EXIF_Copyright := value; // EXIF_Orientation // The orientation of the camera relative to the scene, when the image was captured _EXIF_Orientation: begin {ADDWRITE case EXIF_Orientation of 1 : result := s_EXIFOrientation1; 2 : result := s_EXIFOrientation2; 3 : result := s_EXIFOrientation3; 4 : result := s_EXIFOrientation4; 5 : result := s_EXIFOrientation5; 6 : result := s_EXIFOrientation6; 7 : result := s_EXIFOrientation7; 8 : result := s_EXIFOrientation8 ; end; } end; // EXIF_ExposureTime // Exposure time (reciprocal of shutter speed). Unit is second. _EXIF_ExposureTime: begin Value := StripSuffix(value, ' ' + s_Seconds); Value := StripSuffix(value, ' ' + s_Second); if pos('1/', value) > 0 then EXIF_ExposureTime := 1 / FractionToDouble(value) else EXIF_ExposureTime := StrToInt(value); end; // EXIF_FNumber // The actual F-number(F-stop) of lens when the image was taken. _EXIF_FNumber: begin if uppercase(value)[1] = 'F' then delete(value, 1, 1); EXIF_Fnumber := StrToFloat(value); end; // EXIF_ExposureProgram // Exposure program that the camera used when image was taken. { 1 means manual control 2 program normal 3 aperture priority 4 shutter priority 5 program creative (slow program) 6 program action(high-speed program) 7 portrait mode 8 landscape mode. } _EXIF_ExposureProgram: begin {ADDWRITE case EXIF_ExposureProgram of 1 : result := s_ManualControl; 2 : result := s_ProgramNormal; 3 : result := s_AperturePriority; 4 : result := s_ShutterPriority; 5 : result := s_CreativeProgram; 6 : result := s_ActionProgram; 7 : result := s_Portraitmode; 8 : result := s_LandscapeMode; end; } end; // EXIF_ISOSpeedRatings // CCD sensitivity equivalent to Ag-Hr film speedrate. _EXIF_ISOSpeedRatings: EXIF_ISOSpeedRatings[0] := StrToInt(value); // EXIF_ExifVersion // Exif version number. Stored as 4bytes of ASCII character. If the picture is based on Exif V2.1, value is "0210". Since the type is 'undefined', there is no NULL(0x00) for termination. //_EXIF_ExifVersion : EXIF_ExifVersion :=value; // EXIF_ShutterSpeedValue // Shutter speed by APEX value. To convert this value to ordinary 'Shutter Speed'; calculate this value's power of 2, then reciprocal. For example, if the ShutterSpeedValue is '4', shutter speed is 1/(24)=1/16 second. _EXIF_ShutterSpeedValue: EXIF_ShutterSpeedValue := FStrToApex(value, '1/', 2); // EXIF_ApertureValue // The actual aperture value of lens when the image was taken. Unit is // APEX. To convert this value to ordinary F-number (F-stop), calculate // this value's power of root 2 (=1.4142). For example, if the // ApertureValue is '5', F-number is 1.41425 = F5.6. _EXIF_ApertureValue: EXIF_ApertureValue := FStrToApex(value, 'F', Sqrt(2)); // EXIF_BrightnessValue // Brightness of taken subject, unit is APEX. To calculate Exposure(Ev) from BrigtnessValue(Bv), you must add SensitivityValue(Sv). { Ev=Bv+Sv Sv=log2(ISOSpeedRating/3.125) ISO100 : Sv=5, ISO200 : Sv=6, ISO400 : Sv=7, ISO125 : Sv=5.32. } _EXIF_BrightnessValue: if Value <> '' then EXIF_BrightnessValue := StrToFloat(value); // EXIF_ExposureBiasValue // Exposure bias(compensation) value of taking picture. Unit is APEX(EV). _EXIF_ExposureBiasValue: EXIF_ExposureBiasValue := FractionToDouble(value); // EXIF_MaxApertureValue // Maximum aperture value of lens. You can convert to F-number by calculating power of root 2 (same process of ApertureValue). _EXIF_MaxApertureValue: EXIF_MaxApertureValue := FStrToApex(value, 'F', Sqrt(2)); // EXIF_SubjectDistance // Distance to focus point, unit is meter. _EXIF_SubjectDistance: begin value := StripSuffix(value, ' m'); EXIF_SubjectDistance := StrToFloat(value); end; // EXIF_MeteringMode // Exposure metering method. { 0 means unknown 1 average 2 center weighted average 3 spot 4 multi-spot 5 multi-segment 6 partial 255 other. } _EXIF_MeteringMode: begin {ADDWRITE case EXIF_MeteringMode of 1 : result := s_average; 2 : result := s_CenterWeightedAverage; 3 : result := s_Spot; 4 : result := s_MultiSpot; 5 : result := s_MultiSegment; 6 : result := s_partial; end; } end; // EXIF_LightSource // Light source, actually this means white balance setting. { 0 means unknown 1 daylight 2 fluorescent 3 tungsten 10 flash 17 standard light A 18 standard light B 19 standard light C 20 D55 21 D65 22 D75 255 other. } _EXIF_LightSource: begin {ADDWRITE case EXIF_LightSource of 1 : result := s_daylight; 2 : result := s_fluorescent; 3 : result := s_tungsten; 10 : result := s_flash; 17 : result := s_standardLightA; 18 : result := s_standardLightB; 19 : result := s_standardLightC; 20 : result := s_D55; 21 : result := s_D65; 22 : result := s_D75; end; } end; // EXIF_Flash { 0 means flash did not fire 1 flash fired 5 flash fired but strobe return light not detected 7 flash fired and strobe return light detected } _EXIF_Flash: begin {ADDWRITE Case EXIF_Flash of 0 : result := s_FlashDidNotFire; 1 : result := s_flashFired; 5 : result := s_flashFiredNoStrobeLight; 7 : result := s_flashFiredStrobeLight; end; } end; // EXIF_FocalLength // Focal length of lens used to take image. Unit is millimeter. _EXIF_FocalLength: begin value := StripSuffix(value, ' mm'); EXIF_FocalLength := StrToFloat(value); end; // EXIF_FlashPixVersion // Stores FlashPix version. If the image data is based on FlashPix formar Ver.1.0, value is "0100". _EXIF_FlashPixVersion: EXIF_FlashPixVersion := Value; // EXIF_ColorSpace // Defines Color Space. DCF image must use sRGB color space so value is always '1'. If the picture uses the other color space, value is '65535' : Uncalibrated. _EXIF_ColorSpace: begin {ADDWRITE Case EXIF_ColorSpace of 1 : result := s_RGB; 65535 : result := s_Uncalibrated; end; } end; // EXIF,_EXIFImageWidth, EXIF,_EXIFImageHeight // Size of main image. _EXIF_ExifImageWidth: EXIF_ExifImageWidth := StrToInt(value); // EXIF,_EXIFImageWidth, EXIF,_EXIFImageHeight // Size of main image. _EXIF_ExifImageHeight: EXIF_ExifImageHeight := StrToInt(value); // EXIF_RelatedSoundFile // If this digicam can record audio data with image, shows name of audio data. _EXIF_RelatedSoundFile: EXIF_RelatedSoundFile := value; // EXIF_FocalPlaneResolutionUnit // Unit of FocalPlaneXResoluton/FocalPlaneYResolution. { 1 means no-unit 2 inch 3 centimeter. Note : Some of Fujifilm's digicam(e.g.FX2700,FX2900,Finepix4700Z/40i etc) uses value '3' so it must be 'centimeter', but it seems that they use a '8.3mm?'(1/3in.?) to their ResolutionUnit. Fuji's BUG? Finepix4900Z has been changed to use value '2' but it doesn't match to actual value also. } _EXIF_FocalPlaneXResolution: begin if pos(uppercase(s_Inch), uppercase(value)) > 0 then EXIF_FocalPlaneResolutionUnit := 2 else if pos(uppercase(s_CM), uppercase(value)) > 0 then EXIF_FocalPlaneResolutionUnit := 3; Value := StripSuffix(value, ' ' + s_Inch); Value := StripSuffix(value, ' ' + s_CM); EXIF_FocalPlaneXResolution := StrToFloat(value); end; _EXIF_FocalPlaneYResolution: begin if pos(uppercase(s_Inch), uppercase(value)) > 0 then EXIF_FocalPlaneResolutionUnit := 2 else if pos(uppercase(s_CM), uppercase(value)) > 0 then EXIF_FocalPlaneResolutionUnit := 3; Value := StripSuffix(value, ' ' + s_Inch); Value := StripSuffix(value, ' ' + s_CM); EXIF_FocalPlaneYResolution := StrToFloat(value); end; // EXIF_ExposureIndex // Same as ISOSpeedRatings(0x8827) but data type is unsigned rational. Only Kodak's digicam uses this tag instead of ISOSpeedRating. _EXIF_ExposureIndex: EXIF_ExposureIndex := StrToFloat(value); // EXIF_SensingMethod // Shows type of image sensor unit. '2' means 1 chip color area sensor, most of all digicam use this type. _EXIF_SensingMethod: begin {ADDWRITE If EXIF_SensingMethod=2 then result := s_OneChipColorAreaSensor //ELSE // result := s_UnknownMethod) } end; // EXIF_FileSource // Indicates the image source. Value '0x03' means the image source is digital still camera. _EXIF_FileSource: begin {ADDWRITE If EXIF_FileSource=$03 then result := s_DigitalStillCamera //ELSE // result := s_UnknownDevice) } end; // EXIF_SceneType // Indicates the type of scene. Value '0x01' means that the image was directly photographed. _EXIF_SceneType: begin {ADDWRITE If EXIF_SceneType=$01 then result := s_DirectlyPhotographed //ELSE // result := s_UnknownMethod) } end; // EXIF_YCbCrPositioning // When image format is YCbCr and uses 'Subsampling' (cropping of chroma data, all the digicam do that), defines the chroma sample point of subsampling pixel array. { 1 means the center of pixel array 2 means the datum point. } _EXIF_YCbCrPositioning: begin {ADDWRITE case EXIF_YcbCrPositioning of 1 : result := s_Centered; 2 : result := s_DataPoint; end; } end; _EXIF_GPSSatellites: EXIF_GPSSatellites := Value; _EXIF_GPSVersionID: EXIF_GPSVersionID := Value; _EXIF_Artist: EXIF_Artist := Value; _EXIF_XPTitle: EXIF_XPTitle := Value; _EXIF_XPComment: EXIF_XPComment := Value; _EXIF_XPAuthor: EXIF_XPAuthor := Value; _EXIF_XPKeywords: EXIF_XPKeywords := Value; _EXIF_XPSubject: EXIF_XPSubject := Value; _EXIF_XPRating: EXIF_XPRating := StrToIntDef(Value, -1); { TO-DO ADD WRITE SUPPORT : _EXIF_ExposureMode _EXIF_WhiteBalance _EXIF_DigitalZoomRatio _EXIF_FocalLengthIn35mmFilm _EXIF_SceneCaptureType _EXIF_GainControl _EXIF_Contrast _EXIF_Saturation _EXIF_Sharpness _EXIF_SubjectDistanceRange _EXIF_GPSLatitude _EXIF_GPSLongitude _EXIF_GPSAltitude _EXIF_GPSImageDirection _EXIF_GPSTrack _EXIF_GPSSpeed _EXIF_GPSDateAndTime } end; except // ERROR end; end; {$IFDEF USE_STRINGGRIDS} // Clear the EXIF fields from the specified ImageEnIO object and in the specified stringgrid procedure ClearEXIFFields(AStringGrid: TStringGrid; AnImageEnIO: TImageEnIO); var i: integer; begin AnImageEnIO.params.EXIF_HasEXIFData := False; for i := low(EXIF) to high(EXIF) do AStringGrid.Cells[1, i + AStringGrid.FixedRows] := ''; end; {$ENDIF} {$IFDEF USE_LISTVIEW} // Clear the EXIF fields from the specified ImageEnIO object and in the specified listview procedure ClearEXIFFields(AListView: TListview; AImageEnIO: TImageEnIO); var i: integer; begin AImageEnIO.Params.EXIF_HasEXIFData := False; for i := low(EXIF) to high(EXIF) do AListView.Items.Item[i].SubItems[0] := ''; end; {$ENDIF} // returns some common EXIF fields: EXIF_Model, EXIF_ExposureTime, EXIF_Flash function GetUsefulEXIFFields(sFilename : string; AnImageEnIO : TImageENIO; var sCameraModel : string; var sExposureTime : string; var sFlashMode : string ) : boolean; var sModel: string; sMake: string; begin result := false; if EXIFCompatibleFile(sFilename) then try AnImageEnIO.paramsfromfile(sFilename); if AnImageEnIO.params.EXIF_HasEXIFData = false then exit; sMake := RemoveNull(AnImageEnIO.params.EXIF_Make); sModel := RemoveNull(AnImageEnIO.params.EXIF_Model); // sometimes EXIF_Model already includes the make if (pos(' ', sMake) = 0) and (pos(uppercase(sMake), uppercase(sModel)) = 0) then sCameraModel := TrimLeft(TrimRight(sMake + ' ' + sModel)) else sCameraModel := sModel; sExposureTime := ExposureToString(AnImageEnIO.params.EXIF_ExposureTime); sFlashMode := FlashModeToString(AnImageEnIO.params.EXIF_Flash); result := (sCameraModel <> '') or (sExposureTime <> ''); except // ERROR end; end; // Returns true is sText contains an exif const such as %EXIF-User-Comment% function ContainsExifField(const sText: string): Boolean; begin Result := Pos(uppercase(Exif_Field_As_Text_Prefix), uppercase(sText)) > 0; end; // Replace any exif consts in sText with the actual values // bSampleOnly : if we are just showing the kind of text we might output, returns example text rather than '' function ReplaceExifFields(const sText: string; AnImageEnIO: TImageEnIO; sFilename: string; bSampleOnly: Boolean = False): string; var i: Integer; bValid: Boolean; sValue: string; begin Result := sText; if ContainsExifField(sText) = False then Exit; bValid := False; if EXIFCompatibleFile(sFilename) then try AnImageEnIO.paramsfromfile(sFilename); bValid := AnImageEnIO.params.EXIF_HasEXIFData; except // ERROR end; // NOTE : THIS WILL BE SLOW!!! for i := 0 to high(EXIF) do begin if bValid then sValue := GetEXIFField(AnImageEnIO, I) else sValue := ''; if bSampleOnly and (sValue = '') then sValue := 'Sample ' + Exif[I].Description; Result := StringReplace(result, Exif[i].FieldAsTextName, sValue, [rfReplaceAll, rfIgnoreCase]); end; end; // Get a list of all EXIF consts procedure GetAllExifFields(ssDest: TStrings; bClearList: Boolean); var i: integer; begin if bClearList then ssDest.Clear; // fill the fields with the EXIF property names for i := 0 to high(EXIF) do ssDest.Add(Exif[i].FieldAsTextName); end; { EXIF_Item } function EXIF_Item.GetFieldAsTextName: string; begin Result := Exif_Field_As_Text_Prefix + StringReplace(Description, ' ', '-', [rfReplaceAll]) + Exif_Field_As_Text_Suffix; end; // return the GPS fields from the exif fields function ExifGPSData(sFilename: string; AnImageEnIO: TImageEnIO; out GPSLatitudeDegrees: Double; out GPSLongitudeDegrees: Double ): Boolean; begin result := False; GPSLatitudeDegrees := 0; GPSLongitudeDegrees := 0; if EXIFCompatibleFile(sFilename) then try // use get params from file to fill the _ImageEnIO with the EXIF data AnImageEnIO.paramsfromfile(sFilename); if AnImageEnIO.params.EXIF_HasEXIFData then begin GPSLatitudeDegrees := AnImageEnIO.Params.EXIF_GPSLatitude; GPSLongitudeDegrees := AnImageEnIO.Params.EXIF_GPSLongitude; end; Result := (GPSLatitudeDegrees <> 0) or (GPSLongitudeDegrees <> 0); except // ERROR end; end; // reformat an EXIF date string using the localized date settings (shortdate) function ReformatEXIFDateTime(sEXIFDateTime: string): string; var ADateTime: TDateTime; begin result := ''; ADateTime := EXIFDateToDateTime(sEXIFDateTime); if ADateTime > 0 then result := DateTimeToStr(ADateTime); end; {$endif} {!! iexEXIFRoutines iexEXIFRoutines.pas provides helper functions for working with the EXIF data held in compatible files (e.g. JPEG and TIFF files). It includes an array of all common EXIF fields and provides methods for reading and writing fields by ID, as well as displaying and saving data in a TStringGrid or TListView. !!} end.