From e03afa62f10b03191a758ecfde3ffdada7ca74ee Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Sat, 2 Dec 2017 17:12:00 +0000 Subject: [PATCH] fpexif: Initial commit of units and tests git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@6080 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- components/fpexif/fpEXIF.pas | 3597 +++++++++++++++++ components/fpexif/fpeexifdata.pas | 1664 ++++++++ components/fpexif/fpeexifreadwrite.pas | 1146 ++++++ components/fpexif/fpeglobal.pas | 156 + components/fpexif/fpeiptcdata.pas | 674 +++ components/fpexif/fpeiptcreadwrite.pas | 485 +++ components/fpexif/fpemakernote.pas | 838 ++++ components/fpexif/fpemetadata.pas | 701 ++++ components/fpexif/fpestrconsts.pas | 660 +++ components/fpexif/fpetags.pas | 1712 ++++++++ components/fpexif/fpeutils.pas | 1451 +++++++ components/fpexif/fpexif.inc | 34 + components/fpexif/fpexif_fpc.inc | 3 + components/fpexif/fpexif_pkg.lpk | 84 + components/fpexif/fpexif_pkg.pas | 16 + .../fpexif/languages/fpestrconsts.de.po | 1693 ++++++++ components/fpexif/languages/fpestrconsts.po | 1682 ++++++++ .../fpexif/tests/multiread/MultiRead_D7.cfg | 40 + .../fpexif/tests/multiread/MultiRead_D7.dof | 151 + .../fpexif/tests/multiread/MultiRead_D7.dpr | 24 + .../fpexif/tests/multiread/MultiRead_D7.res | Bin 0 -> 1536 bytes .../tests/multiread/MultiRead_Delphi.dpr | 24 + .../tests/multiread/MultiRead_Delphi.dproj | 142 + .../tests/multiread/MultiRead_Delphi.res | Bin 0 -> 59460 bytes .../fpexif/tests/multiread/MultiRead_Laz.ico | Bin 0 -> 137040 bytes .../fpexif/tests/multiread/MultiRead_Laz.lpi | 122 + .../fpexif/tests/multiread/MultiRead_Laz.lpr | 21 + .../fpexif/tests/multiread/MultiRead_Laz.res | Bin 0 -> 139052 bytes .../fpexif/tests/multiread/common/mrtmain.dfm | 601 +++ .../fpexif/tests/multiread/common/mrtmain.lfm | 425 ++ .../fpexif/tests/multiread/common/mrtmain.pas | 526 +++ .../tests/pictures/originals/ExThBE_Nokia.jpg | Bin 0 -> 32589 bytes .../tests/pictures/originals/no_metadata.jpg | Bin 0 -> 5505 bytes .../tests/pictures/originals/with_exif.jpg | Bin 0 -> 5078 bytes .../tests/pictures/originals/with_exif.tif | Bin 0 -> 90406 bytes .../tests/pictures/originals/with_iptc.jpg | Bin 0 -> 4594 bytes .../tests/pictures/originals/with_iptc.tif | Bin 0 -> 90856 bytes .../tests/readwrite/ReadWriteTest_D7.cfg | 39 + .../tests/readwrite/ReadWriteTest_D7.dof | 151 + .../tests/readwrite/ReadWriteTest_D7.dpr | 25 + .../tests/readwrite/ReadWriteTest_D7.res | Bin 0 -> 1648 bytes .../tests/readwrite/ReadWriteTest_Delphi.dpr | 25 + .../readwrite/ReadWriteTest_Delphi.dproj | 141 + .../tests/readwrite/ReadWriteTest_Delphi.res | Bin 0 -> 59488 bytes .../tests/readwrite/ReadWriteTest_Laz.lpi | 139 + .../tests/readwrite/ReadWriteTest_Laz.lpr | 19 + .../tests/readwrite/ReadWriteTest_Laz.res | Bin 0 -> 1779 bytes .../fpexif/tests/readwrite/common/rwMain.dfm | 308 ++ .../fpexif/tests/readwrite/common/rwMain.pas | 630 +++ .../fpexif/tests/readwrite/common/rwmain.lfm | 234 ++ .../tests/readwrite/common/testcases1.txt | 32 + .../tests/readwrite/common/testcases2.txt | 16 + .../tests/unittest/common/fetexifbe.pas | 470 +++ .../tests/unittest/common/fetexifle.pas | 727 ++++ .../fpexif/tests/unittest/common/fetiptc.pas | 815 ++++ .../tests/unittest/common/fettestutils.pas | 33 + .../fpexif/tests/unittest/common/fetutils.pas | 327 ++ .../fpexif/tests/unittest/dunit/readme.txt | 3 + .../fpexif/tests/unittest/fpExifTests.ico | Bin 0 -> 137040 bytes .../fpexif/tests/unittest/fpExifTests.lpi | 163 + .../fpexif/tests/unittest/fpExifTests.lpr | 19 + .../tests/unittest/fpExifTests_Delphi.dpr | 30 + .../tests/unittest/fpExifTests_Delphi.dproj | 141 + .../tests/unittest/fpExifTests_Delphi.res | Bin 0 -> 3272 bytes .../tests/unittest/fpExifTests_Delphi7.cfg | 42 + .../tests/unittest/fpExifTests_Delphi7.dof | 145 + .../tests/unittest/fpExifTests_Delphi7.dpr | 30 + .../tests/unittest/fpExifTests_Delphi7.dproj | 138 + .../tests/unittest/fpExifTests_Delphi7.res | Bin 0 -> 876 bytes components/fpexif/tools/readme.txt | 2 + 70 files changed, 23516 insertions(+) create mode 100644 components/fpexif/fpEXIF.pas create mode 100644 components/fpexif/fpeexifdata.pas create mode 100644 components/fpexif/fpeexifreadwrite.pas create mode 100644 components/fpexif/fpeglobal.pas create mode 100644 components/fpexif/fpeiptcdata.pas create mode 100644 components/fpexif/fpeiptcreadwrite.pas create mode 100644 components/fpexif/fpemakernote.pas create mode 100644 components/fpexif/fpemetadata.pas create mode 100644 components/fpexif/fpestrconsts.pas create mode 100644 components/fpexif/fpetags.pas create mode 100644 components/fpexif/fpeutils.pas create mode 100644 components/fpexif/fpexif.inc create mode 100644 components/fpexif/fpexif_fpc.inc create mode 100644 components/fpexif/fpexif_pkg.lpk create mode 100644 components/fpexif/fpexif_pkg.pas create mode 100644 components/fpexif/languages/fpestrconsts.de.po create mode 100644 components/fpexif/languages/fpestrconsts.po create mode 100644 components/fpexif/tests/multiread/MultiRead_D7.cfg create mode 100644 components/fpexif/tests/multiread/MultiRead_D7.dof create mode 100644 components/fpexif/tests/multiread/MultiRead_D7.dpr create mode 100644 components/fpexif/tests/multiread/MultiRead_D7.res create mode 100644 components/fpexif/tests/multiread/MultiRead_Delphi.dpr create mode 100644 components/fpexif/tests/multiread/MultiRead_Delphi.dproj create mode 100644 components/fpexif/tests/multiread/MultiRead_Delphi.res create mode 100644 components/fpexif/tests/multiread/MultiRead_Laz.ico create mode 100644 components/fpexif/tests/multiread/MultiRead_Laz.lpi create mode 100644 components/fpexif/tests/multiread/MultiRead_Laz.lpr create mode 100644 components/fpexif/tests/multiread/MultiRead_Laz.res create mode 100644 components/fpexif/tests/multiread/common/mrtmain.dfm create mode 100644 components/fpexif/tests/multiread/common/mrtmain.lfm create mode 100644 components/fpexif/tests/multiread/common/mrtmain.pas create mode 100644 components/fpexif/tests/pictures/originals/ExThBE_Nokia.jpg create mode 100644 components/fpexif/tests/pictures/originals/no_metadata.jpg create mode 100644 components/fpexif/tests/pictures/originals/with_exif.jpg create mode 100644 components/fpexif/tests/pictures/originals/with_exif.tif create mode 100644 components/fpexif/tests/pictures/originals/with_iptc.jpg create mode 100644 components/fpexif/tests/pictures/originals/with_iptc.tif create mode 100644 components/fpexif/tests/readwrite/ReadWriteTest_D7.cfg create mode 100644 components/fpexif/tests/readwrite/ReadWriteTest_D7.dof create mode 100644 components/fpexif/tests/readwrite/ReadWriteTest_D7.dpr create mode 100644 components/fpexif/tests/readwrite/ReadWriteTest_D7.res create mode 100644 components/fpexif/tests/readwrite/ReadWriteTest_Delphi.dpr create mode 100644 components/fpexif/tests/readwrite/ReadWriteTest_Delphi.dproj create mode 100644 components/fpexif/tests/readwrite/ReadWriteTest_Delphi.res create mode 100644 components/fpexif/tests/readwrite/ReadWriteTest_Laz.lpi create mode 100644 components/fpexif/tests/readwrite/ReadWriteTest_Laz.lpr create mode 100644 components/fpexif/tests/readwrite/ReadWriteTest_Laz.res create mode 100644 components/fpexif/tests/readwrite/common/rwMain.dfm create mode 100644 components/fpexif/tests/readwrite/common/rwMain.pas create mode 100644 components/fpexif/tests/readwrite/common/rwmain.lfm create mode 100644 components/fpexif/tests/readwrite/common/testcases1.txt create mode 100644 components/fpexif/tests/readwrite/common/testcases2.txt create mode 100644 components/fpexif/tests/unittest/common/fetexifbe.pas create mode 100644 components/fpexif/tests/unittest/common/fetexifle.pas create mode 100644 components/fpexif/tests/unittest/common/fetiptc.pas create mode 100644 components/fpexif/tests/unittest/common/fettestutils.pas create mode 100644 components/fpexif/tests/unittest/common/fetutils.pas create mode 100644 components/fpexif/tests/unittest/dunit/readme.txt create mode 100644 components/fpexif/tests/unittest/fpExifTests.ico create mode 100644 components/fpexif/tests/unittest/fpExifTests.lpi create mode 100644 components/fpexif/tests/unittest/fpExifTests.lpr create mode 100644 components/fpexif/tests/unittest/fpExifTests_Delphi.dpr create mode 100644 components/fpexif/tests/unittest/fpExifTests_Delphi.dproj create mode 100644 components/fpexif/tests/unittest/fpExifTests_Delphi.res create mode 100644 components/fpexif/tests/unittest/fpExifTests_Delphi7.cfg create mode 100644 components/fpexif/tests/unittest/fpExifTests_Delphi7.dof create mode 100644 components/fpexif/tests/unittest/fpExifTests_Delphi7.dpr create mode 100644 components/fpexif/tests/unittest/fpExifTests_Delphi7.dproj create mode 100644 components/fpexif/tests/unittest/fpExifTests_Delphi7.res create mode 100644 components/fpexif/tools/readme.txt diff --git a/components/fpexif/fpEXIF.pas b/components/fpexif/fpEXIF.pas new file mode 100644 index 000000000..7b931b3d9 --- /dev/null +++ b/components/fpexif/fpEXIF.pas @@ -0,0 +1,3597 @@ +unit fpeExif; + +//////////////////////////////////////////////////////////////////////////////// +// unit dEXIF - Copyright 2001-2006, Gerry McGuire +//-------------------------------------------------------------------------- +// Program to pull the information out of various types of EXIF digital +// camera files and show it in a reasonably consistent way +// +// This module parses the very complicated exif structures. +// +// Matthias Wandel, Dec 1999 - August 2000 (most of the comments) +// +// Translated to Delphi: +// Gerry McGuire, March - April 2001 - Currently - read only +// May 2001 - add EXIF to jpeg output files +// September 2001 - read TIF files, IPTC data +// June 2003 - First (non-beta) Release +//-------------------------------------------------------------------------- +// In addition to the basic information provided by Matthias, the +// following web page contains reference informtion regarding the +// exif standard: http://www.pima.net/standards/iso/tc42/wg18/WG18_POW.htm +// (the documents themselves are PDF). +//-------------------------------------------------------------------------- +// 17.05.2002 MS Corrections/additions M. Schwaiger +//-------------------------------------------------------------------------- + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I fpExif.inc} + +interface + +uses + SysUtils, Classes, Math, Variants, + {$IFDEF FPC} + LazUTF8, + {$ELSE} + {$IFNDEF dExifNoJpeg} jpeg, {$ENDIF} + {$ENDIF} + fpeGlobal, fpeUtils, fpeTags, fpeIptc; + +const + { + ExifTag = 1; // default tag Types + GpsTag = 2; + ThumbTag = 4; + } + + // To be used in Exifobj.IterateFoundTags + GenericEXIF = 0; + CustomEXIF = 1; + +// AllEXIF = -1; + GenNone = 0; + GenAll = 255; + GenString = 2; + GenList = 4; +// VLMin = 0; +// VLMax = 1; + +type + { TEndInd } + + TEndInd = class + private + FData: ansistring; + public + MotorolaOrder: boolean; + function Get16u(AOffs: integer): word; + function Get32s(AOffs: integer): Longint; + function Get32u(AOffs: integer): Longword; + function Put32s(data: Integer): AnsiString; + procedure WriteInt16(var buff: AnsiString; int,posn: integer); + procedure WriteInt32(var buff: AnsiString; int,posn: longint); + function GetDataBuff: Ansistring; + procedure SetDataBuff(const Value: AnsiString); + property DataBuff: AnsiString read GetDataBuff write SetDataBuff; + end; + + { TImageInfo } + + TImageInfo = class(tEndInd) + private + FParent: TObject; // must be cast to TImgData, can't be done here due to unit circular reference + FExifVersion: string; + + FITagArray: array of TTagEntry; + FITagCount: integer; + + FIThumbArray: array of TTagEntry; + FIThumbCount: integer; + + FThumbStart: integer; + FThumbLength: integer; + FThumbType: integer; + + FThumbnailBuffer: TBytes; + FThumbnailStartOffset: Integer; + FThumbnailSize: Integer; + + FIterator: integer; + FThumbIterator: integer; + + // Getter / setter + function GetDateTimeOriginal: TDateTime; + procedure SetDateTimeOriginal(const AValue: TDateTime); + + function GetDateTimeDigitized: TDateTime; + procedure SetDateTimeDigitized(const AValue: TDateTime); + + function GetDateTimeModified: TDateTime; + procedure SetDateTimeModified(const AValue: TDateTime); + + function GetArtist: String; + procedure SetArtist(v: String); + + function GetExifComment: String; overload; + procedure SetExifComment(AValue: String); + function GetUserComment(const ATag: TTagEntry): String; overload; + + function GetImageDescription: String; + procedure SetImageDescription(const AValue: String); + + function GetCameraMake: String; + procedure SetCameraMake(const AValue: String); + + function GetCameraModel: String; + procedure SetCameraModel(const AValue: String); + + function GetCopyright: String; + procedure SetCopyright(const AValue: String); + + function GetGPSCoordinate(ATagName: String; + ACoordType: TGpsCoordType): Extended; + procedure SetGPSCoordinate(ATagName: String; const AValue: Extended; + ACoordType: TGpsCoordType); + function GetGPSLatitude: Extended; + procedure SetGPSLatitude(const AValue: Extended); + function GetGPSLongitude: Extended; + procedure SetGPSLongitude(const AValue: Extended); + + function GetHeight: Integer; + procedure Setheight(AValue: Integer); + function GetWidth: Integer; + procedure SetWidth(AValue: Integer); + + function GetVersion(ATag: TTagEntry): String; + + function GetTagByID(ATagID: Word): TTagEntry; + procedure SetTagByID(ATagID: Word; const AValue: TTagEntry); + function GetTagByIndex(AIndex: Integer): TTagEntry; + procedure SetTagByIndex(AIndex: Integer; const AValue: TTagEntry); + function GetTagByName(ATagName: String): TTagEntry; + procedure SetTagByName(ATagName: String; const AValue: TTagEntry); + function GetTagValue(ATagName: String): variant; + procedure SetTagValue(ATagName: String; AValue: variant); + function GetTagValueAsString(ATagName: String): String; + procedure SetTagValueAsString(ATagName: String; AValue: String); + + function GetThumbTagByID(ATagID: Word): TTagEntry; + procedure SetThumbTagByID(ATagID: Word; const AValue: TTagEntry); + function GetThumbTagByIndex(AIndex: Integer): TTagEntry; + procedure SetThumbTagByIndex(AIndex: Integer; const AValue: TTagEntry); + function GetThumbTagByName(ATagName: String): TTagEntry; + procedure SetThumbTagByName(ATagName: String; const AValue: TTagEntry); + function GetThumbTagValue(ATagName: String): Variant; + procedure SetThumbTagValue(ATagName: String; AValue: variant); + function GetThumbTagValueAsString(ATagName: String): string; + procedure SetThumbTagValueAsString(ATagName: String; AValue: String); + + procedure InternalGetBinaryTagValue(const ATag: TTagEntry; var ABuffer: ansistring); + function InternalGetTagValue(const ATag: TTagEntry): Variant; + function InternalGetTagValueAsString(const ATag: TTagEntry): String; + procedure InternalSetTagValue(const ATagName: String; AValue: Variant; + ATagTypes: TTagTypes; ABinaryData: Pointer = nil; ABinaryDataCount: Word = 0); + function BinaryTagToStr(const ATag: TTagEntry): String; + function BinaryTagToVar(const ATag: TTagEntry): Variant; + function NumericTagToVar(ABuffer: Pointer; ATagType: Integer): Variant; + procedure VarToNumericTag(AValue:variant; ATag: PTagEntry); + + // misc + function CreateTagPtr(const ATagDef: TTagEntry; IsThumbTag: Boolean; AParentID: Word = 0): PTagEntry; + function FindTagPtr(const ATagDef: TTagEntry; IsThumbTag: Boolean): PTagEntry; + + (* + function GetTagPtr(ATagTypes: TTagTypes; ATagID: Word; AForceCreate: Boolean=false; + AParentID: word=0; ATagType: word=65535): PTagEntry; + *) + procedure RemoveTag(ATagTypes: TTagTypes; ATagID: Word; AParentID: Word=0); + + procedure ClearDirStack; + procedure PushDirStack(dirStart, offsetbase: Integer); + function TestDirStack(dirStart, offsetbase: Integer): boolean; + + protected + function AddTagToArray(ANewTag: iTag): integer; + function AddTagToThumbArray(ANewTag: iTag): integer; + procedure Calc35Equiv; + function CvtInt(ABuffer: Pointer; ABufferSize: Integer): Longint; + function Decode: Boolean; + function ExifDateToDateTime(ARawStr: ansistring): TDateTime; +// procedure ExtractThumbnail; + function FormatNumber(ABuffer: PByte; ABufferSize: Integer; + AFmt: integer; AFmtStr: string; ADecodeStr: string=''): String; + function GetNumber(ABuffer: PByte; ABufferSize: Integer; + AFmt: integer): double; + function LookupRatio: double; + + public + MaxTag: integer; +// Height, Width, HPosn, WPosn: integer; + FlashUsed: integer; + BuildList: integer; + MakerNote: ansistring; + TiffFmt: boolean; +// Add support for thumbnail + ThumbTrace: ansistring; + MaxThumbTag: integer; +// Added the following elements to make the structure a little more code-friendly + TraceLevel: integer; + TraceStr: ansistring; + msTraceStr: ansistring; + msAvailable: boolean; + msName:ansistring; + MakerOffset : integer; + + public + constructor Create(AParent: TObject; ABigEndian: Boolean; + ABuildCode: integer = GenAll); + procedure Assign(source: TImageInfo); + destructor Destroy; override; + + // Reader interface + procedure AddTagFromReader(ATag: TTagEntry); + procedure AddThumbnailFromReader(ABuffer: TBytes); + + // Date/time routines + procedure AdjDateTime(ADays, AHours, AMins, ASecs: integer); + function GetImgDateTime: TDateTime; + + // Manufacturer-specific + procedure AddMSTag(ATagName: String; ARawStr: ansistring; AType: word); + + // Iterate through found tags + procedure ResetIterator; + procedure ResetThumbIterator; + function IterateFoundTags(TagId:integer; var retVal:TTagEntry):boolean; + function IterateFoundThumbTags(TagId: integer; + var retVal: TTagEntry): boolean; + + // Collective output + procedure EXIFArrayToXML(AList: TStrings); overload; + function ToShortString: String; // Summarizes in a single line + function ToLongString(ALabelWidth: Integer = 15): String; + + // Special actions + procedure AdjExifSize(AHeight, AWidth: Integer); + + // Looking up tags and tag values + function GetRawFloat(ATagName: String): double; + function GetRawInt(ATagName: String): integer; + function GetTagByDesc(SearchStr: String): TTagEntry; + function LookupTagIndex(ATagName: String): integer; virtual; +// function LookupTagVal(ATagName: String): String; virtual; + function LookupTagDefn(ATagName: String): integer; + function LookupTagByDesc(ADesc: String): integer; + function LookupTagInt(ATagName: String): integer; + + // Tag values as variant + property TagValue[ATagName: String]: Variant + read GetTagValue write SetTagValue; default; + + // Tag values as string + property TagValueAsString[ATagName: String]: String + read GetTagValueAsString write SetTagValueAsString; + + // Accessing entire tag record + property TagByID[ATagID: Word]: TTagEntry + read GetTagByID write SetTagByID; + property TagByIndex[AIndex: Integer]: TTagEntry + read GetTagByIndex write SetTagByIndex; + property TagByName[ATagName: String]: TTagEntry + read GetTagByName write SetTagByName; + property TagCount: Integer + read fiTagCount; + + property Artist: String + read GetArtist write SetArtist; + property CameraMake: String + read GetCameraMake write SetCameraMake; + property CameraModel: String + read GetCameraModel write SetCameraModel; + property Copyright: String + read GetCopyright write SetCopyright; + property DateTimeOriginal: TDateTime + read GetDateTimeOriginal write SetDateTimeOriginal; + property DateTimeDigitized: TDateTime + read GetDateTimeDigitized write SetDateTimeDigitized; + property DateTimeModified: TDateTime + read GetDateTimeModified write SetDateTimeModified; + property ExifComment: String + read GetExifComment write SetExifComment; + property ExifVersion: String + read FExifVersion; + property GPSLatitude: Extended + read GetGPSLatitude write SetGPSLatitude; + property GPSLongitude: Extended + read GetGPSLongitude write SetGPSLongitude; + property ImageDescription: String + read GetImageDescription write SetImageDescription; + property Height: Integer + read GetHeight write SetHeight; + property Width: Integer + read GetWidth write SetWidth; + + public + // General processing, called internally + procedure ProcessExifDir(DirStart, OffsetBase, ExifLength: LongInt; + ATagType: TTagType = ttExif; APrefix: string=''; AParentID: word=0); + procedure ProcessHWSpecific(AMakerBuff: ansistring; + TagTbl: array of TTagEntry; ADirStart, AMakerOffset: Longint; + spOffset: integer = 0); + + public + // Thumbnail + procedure CreateThumbnail(AThumbnailSize: Integer = DEFAULT_THUMBNAIL_SIZE); + function HasThumbnail: boolean; +// procedure ProcessThumbnail; + procedure RemoveThumbnail; + procedure LoadThumbnailFromStream(AStream: TStream); + procedure SaveThumbnailToStream(AStream: TStream); + property ThumbnailBuffer: TBytes + read FThumbnailBuffer; + property ThumbTagByID[ATagID: Word]: TTagEntry + read GetThumbTagByID write SetThumbTagByID; + property ThumbTagByIndex[AIndex: Integer]: TTagEntry + read GetThumbTagByIndex write SetThumbTagByIndex; + property ThumbTagCount: Integer + read fiThumbCount; + property ThumbTagValue[ATagName: String]: variant + read GetThumbTagValue write SetThumbTagValue; + property ThumbTagValueAsString[ATagName: String]: String + read GetThumbTagValueAsString; + + property Parent: TObject + read FParent; + end; // TInfoData + +var + CurTagArray: TImageInfo = nil; + fmtInt: tfmtInt = defIntFmt; + fmtReal: tfmtReal = defRealFmt; + fmtFrac: tfmtFrac = defFracFmt; + + ExifNonThumbnailLength : integer; + ShowTags: integer; + ExifTrace: integer = 0; + +function FindExifTagDefByID(ATagID: Word): PTagEntry; +function FindGPSTagDefByID(ATagID: Word): PTagEntry; + +function FindExifTagDefByName(ATagName: String): PTagEntry; +function FindGPSTagDefByName(ATagName: String): PTagEntry; + +function LookupType(idx: integer): String; + + +implementation + +uses + fpeMetadata, fpeMsData; + +const +// Compression Type Constants + JPEG_COMP_TYPE = 6; + TIFF_COMP_TYPE = 1; + + GPSCnt = 32; + ExifTagCnt = 251; // NOTE: was 250 before, but "count" is 251 + TotalTagCnt = GPSCnt + ExifTagCnt; + +{ Many tags added based on Php4 source... + http://lxr.php.net/source/php4/ext/exif/exif.c + + See also: https://sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html } +var + TagTable : array [0..ExifTagCnt-1] of TTagEntry = +// TagTable : array of TTagEntry = +// TagTable : TTagDefArray [0..ExifTagCnt] = +// TagTable: TTagDefArray = + ((TID:0; TType:2; Tag:$0001; Count:1; Name:'InteroperabilityIndex' ), {0} + (TID:0; TType:7; Tag:$0002; Count:1; Name:'InteroperabilityVersion'; + Desc:''; Code:''; Data:''; Raw:''; FormatS:''; Size:4; Callback:VersionCallback), + (TID:0; TType:2; Tag:$000B; Count:1; Name:'ACDComment' ), + (TID:0; TType:4; Tag:$00FE; Count:1; Name:'NewSubfileType' ), + (TID:0; TType:3; Tag:$00FF; Count:1; Name:'SubfileType' ), + (TID:0; TType:4; Tag:$0100; ParentID:$0000; Count:1; Name:'ImageWidth'), + (TID:0; TType:4; Tag:$0101; ParentID:$0000; Count:1; Name:'ImageLength'), + (TID:0; TType:3; Tag:$0102; ParentID:$0000; Count:3; Name:'BitsPerSample'), + (TID:0; TType:3; Tag:$0103; ParentID:$0000; Count:1; Name:'Compression'; + Desc:''; Code:'6:Jpeg,3:Uncompressed,1:TIFF'), + (TID:0; TType:3; Tag:$0106; ParentID:$0000; Count:1; Name:'PhotometricInterpretation'; + Desc:''; Code:'1:Monochrome, 2:RGB, 6:YCbCr'), + (TID:0; TType:3; Tag:$010A; ParentID:$0000; Count:1; Name:'FillOrder'), {10} + (TID:0; TType:2; Tag:$010D; ParentID:$0000; Count:1; Name:'DocumentName'), + (TID:0; TType:2; Tag:$010E; ParentID:$0000; Count:1; Name:'ImageDescription'), + (TID:0; TType:2; Tag:$010F; ParentID:$0000; Count:1; Name:'Make'), + (TID:0; TType:2; Tag:$0110; ParentID:$0000; Count:1; Name:'Model'), + (TID:0; TType:4; Tag:$0111; ParentID:$0000; Count:1; Name:'StripOffsets'), + (TID:0; TType:3; Tag:$0112; ParentID:$0000; Count:1; Name:'Orientation'; + Desc:''; Code:'1:Horizontal (normal),2:Mirror horizontal,3:Rotate 180,'+ + '4:Mirror vertical,5:Mirror horizontal and rotate 270 CW,'+ + '6:Rotate 90 CW,7:Mirror horizontal and rotate 90 CW,'+ + '8:Rotate 270 CW'), + (TID:0; TType:3; Tag:$0115; ParentID:$0000; Count:1; Name:'SamplesPerPixel'), + (TID:0; TType:4; Tag:$0116; ParentID:$0000; Count:1; Name:'RowsPerStrip'), + (TID:0; TType:4; Tag:$0117; ParentID:$0000; Count:1; Name:'StripByteCounts'), + (TID:0; TType:3; Tag:$0118; ParentID:$0000; Count:1; Name:'MinSampleValue'), {20} + (TID:0; TType:3; Tag:$0119; ParentID:$0000; Count:1; Name:'MaxSampleValue'), + (TID:0; TType:5; Tag:$011A; ParentID:$0000; Count:1; Name:'XResolution'), +// Desc:''; Code:''; Data:''; Raw:''; FormatS:'%f'), + (TID:0; TType:5; Tag:$011B; ParentID:$0000; Count:1; Name:'YResolution'), +// Desc:''; Code:''; Data:''; Raw:''; FormatS:'%f'), + (TID:0; TType:3; Tag:$011C; ParentID:$0000; Count:1; Name:'PlanarConfiguration'), + (TID:0; TType:2; Tag:$011D; ParentID:$0000; Count:1; Name:'PageName'), + (TID:0; TType:5; Tag:$011E; ParentID:$0000; Count:1; Name:'XPosition'), + (TID:0; TType:5; Tag:$011F; ParentID:$0000; Count:1; Name:'YPosition'), + (TID:0; TType:0; Tag:$0120; ParentID:$0000; Count:1; Name:'FreeOffsets'), + (TID:0; TType:0; Tag:$0121; ParentID:$0000; Count:1; Name:'FreeByteCounts'), + (TID:0; TType:3; Tag:$0122; ParentID:$0000; Count:1; Name:'GrayReponseUnit'), {30} + (TID:0; TType:0; Tag:$0123; ParentID:$0000; Count:1; Name:'GrayReponseCurve'), + (TID:0; TType:0; Tag:$0124; ParentID:$0000; Count:1; Name:'T4Options'), + (TID:0; TType:0; Tag:$0125; ParentID:$0000; Count:1; Name:'T6Options'), + (TID:0; TType:3; Tag:$0128; ParentID:$0000; Count:1; Name:'ResolutionUnit'; + Desc:''; Code:'1:None specified,2:inches,3:cm'), + (TID:0; TType:3; Tag:$0129; ParentID:$0000; Count:2; Name:'PageNumber'), + (TID:0; TType:3; Tag:$012D; ParentID:$0000; Count:768; Name:'TransferFunction'), + (TID:0; TType:2; Tag:$0131; ParentID:$0000; Count:1; Name:'Software'), + (TID:0; TType:2; Tag:$0132; ParentID:$0000; Count:1; Name:'DateTime'), + (TID:0; TType:2; Tag:$013B; ParentID:$0000; Count:1; Name:'Artist'), + (TID:0; TType:2; Tag:$013C; ParentID:$0000; Count:1; Name:'HostComputer'), {40} + (TID:0; TType:3; Tag:$013D; ParentID:$0000; Count:1; Name:'Predictor'), + (TID:0; TType:5; Tag:$013E; ParentID:$0000; Count:2; Name:'WhitePoint'), + (TID:0; TType:5; Tag:$013F; ParentID:$0000; Count:6; Name:'PrimaryChromaticities'), + (TID:0; TType:0; Tag:$0140; ParentID:$0000; Count:1; Name:'ColorMap'), + (TID:0; TType:3; Tag:$0141; ParentID:$0000; Count:2; Name:'HalfToneHints'), + (TID:0; TType:4; Tag:$0142; ParentID:$0000; Count:1; Name:'TileWidth'), + (TID:0; TType:4; Tag:$0143; ParentID:$0000; Count:1; Name:'TileLength'), + (TID:0; TType:0; Tag:$0144; ParentID:$0000; Count:1; Name:'TileOffsets'), + (TID:0; TType:0; Tag:$0145; ParentID:$0000; Count:1; Name:'TileByteCounts'), + (TID:0; TType:0; Tag:$014A; ParentID:$0000; Count:1; Name:'SubIFDs'), {50} + (TID:0; TType:3; Tag:$014C; ParentID:$0000; Count:1; Name:'InkSet'), + (TID:0; TType:0; Tag:$014D; ParentID:$0000; Count:1; Name:'InkNames'), + (TID:0; TType:0; Tag:$014E; ParentID:$0000; Count:1; Name:'NumberOfInks'), + (TID:0; TType:0; Tag:$0150; ParentID:$0000; Count:1; Name:'DotRange'), + (TID:0; TType:2; Tag:$0151; ParentID:$0000; Count:1; Name:'TargetPrinter'), + (TID:0; TType:0; Tag:$0152; ParentID:$0000; Count:1; Name:'ExtraSample'), + (TID:0; TType:0; Tag:$0153; ParentID:$0000; Count:1; Name:'SampleFormat'), + (TID:0; TType:0; Tag:$0154; ParentID:$0000; Count:1; Name:'SMinSampleValue'), + (TID:0; TType:0; Tag:$0155; ParentID:$0000; Count:1; Name:'SMaxSampleValue'), + (TID:0; TType:0; Tag:$0156; ParentID:$0000; Count:1; Name:'TransferRange'), {60} + (TID:0; TType:0; Tag:$0157; ParentID:$0000; Count:1; Name:'ClipPath'), + (TID:0; TType:0; Tag:$0158; ParentID:$0000; Count:1; Name:'XClipPathUnits'), + (TID:0; TType:0; Tag:$0159; ParentID:$0000; Count:1; Name:'YClipPathUnits'), + (TID:0; TType:0; Tag:$015A; ParentID:$0000; Count:1; Name:'Indexed'), + (TID:0; TType:0; Tag:$015B; ParentID:$0000; Count:1; Name:'JPEGTables'), + (TID:0; TType:0; Tag:$015F; ParentID:$0000; Count:1; Name:'OPIProxy'), + (TID:0; TType:0; Tag:$0200; ParentID:$0000; Count:1; Name:'JPEGProc'), + (TID:0; TType:4; Tag:$0201; ParentID:$0000; Count:1; Name:'JPEGInterchangeFormat'; + Desc:''; Code:''; Data:''; Raw:''; FormatS:''; Size:4), + (TID:0; TType:4; Tag:$0202; ParentID:$0000; Count:1; Name:'JPEGInterchangeFormatLength'), + (TID:0; TType:0; Tag:$0203; ParentID:$0000; Count:1; Name:'JPEGRestartInterval'), {70} + (TID:0; TType:0; Tag:$0205; ParentID:$0000; Count:1; Name:'JPEGLosslessPredictors'), + (TID:0; TType:0; Tag:$0206; ParentID:$0000; Count:1; Name:'JPEGPointTransforms'), + (TID:0; TType:0; Tag:$0207; ParentID:$0000; Count:1; Name:'JPEGQTables'), + (TID:0; TType:0; Tag:$0208; ParentID:$0000; Count:1; Name:'JPEGDCTables'), + (TID:0; TType:0; Tag:$0209; ParentID:$0000; Count:1; Name:'JPEGACTables'), + (TID:0; TType:5; Tag:$0211; ParentID:$0000; Count:3; Name:'YCbCrCoefficients'), + (TID:0; TType:3; Tag:$0212; ParentID:$0000; Count:2; Name:'YCbCrSubSampling'), + (TID:0; TType:3; Tag:$0213; ParentID:$0000; Count:1; Name:'YCbCrPositioning'; + Desc:''; Code:'1:Centered,2:Co-sited'), + (TID:0; TType:5; Tag:$0214; ParentID:$0000; Count:6; Name:'ReferenceBlackWhite'), + (TID:0; TType:1; Tag:$02BC; ParentID:$0000; Count:1; Name:'ExtensibleMetadataPlatform'), {80} + (TID:0; TType:0; Tag:$0301; ParentID:$0000; Count:1; Name:'Gamma'), + (TID:0; TType:0; Tag:$0302; ParentID:$0000; Count:1; Name:'ICCProfileDescriptor'), + (TID:0; TType:0; Tag:$0303; ParentID:$0000; Count:1; Name:'SRGBRenderingIntent'), + (TID:0; TType:0; Tag:$0304; ParentID:$0000; Count:1; Name:'ImageTitle'), + (TID:0; TType:2; Tag:$1000; ParentID:$0000; Count:1; Name:'RelatedImageFileFormat'), + (TID:0; TType:3; Tag:$1001; ParentID:$0000; Count:1; Name:'RelatedImageWidth'), + (TID:0; TType:3; Tag:$1002; ParentID:$0000; Count:1; Name:'RelatedImageHeight'), + (TID:0; TType:0; Tag:$5001; ParentID:$0000; Count:1; Name:'ResolutionXUnit'), + (TID:0; TType:0; Tag:$5002; ParentID:$0000; Count:1; Name:'ResolutionYUnit'), + (TID:0; TType:0; Tag:$5003; ParentID:$0000; Count:1; Name:'ResolutionXLengthUnit'), {90} + (TID:0; TType:0; Tag:$5004; ParentID:$0000; Count:1; Name:'ResolutionYLengthUnit'), + (TID:0; TType:0; Tag:$5005; ParentID:$0000; Count:1; Name:'PrintFlags'), + (TID:0; TType:0; Tag:$5006; ParentID:$0000; Count:1; Name:'PrintFlagsVersion'), + (TID:0; TType:0; Tag:$5007; ParentID:$0000; Count:1; Name:'PrintFlagsCrop'), + (TID:0; TType:0; Tag:$5008; ParentID:$0000; Count:1; Name:'PrintFlagsBleedWidth'), + (TID:0; TType:0; Tag:$5009; ParentID:$0000; Count:1; Name:'PrintFlagsBleedWidthScale'), + (TID:0; TType:0; Tag:$500A; ParentID:$0000; Count:1; Name:'HalftoneLPI'), + (TID:0; TType:0; Tag:$500B; ParentID:$0000; Count:1; Name:'HalftoneLPIUnit'), + (TID:0; TType:0; Tag:$500C; ParentID:$0000; Count:1; Name:'HalftoneDegree'), + (TID:0; TType:0; Tag:$500D; ParentID:$0000; Count:1; Name:'HalftoneShape'), {100} + (TID:0; TType:0; Tag:$500E; ParentID:$0000; Count:1; Name:'HalftoneMisc'), + (TID:0; TType:0; Tag:$500F; ParentID:$0000; Count:1; Name:'HalftoneScreen'), + (TID:0; TType:0; Tag:$5010; ParentID:$0000; Count:1; Name:'JPEGQuality'), + (TID:0; TType:0; Tag:$5011; ParentID:$0000; Count:1; Name:'GridSize'), + (TID:0; TType:0; Tag:$5012; ParentID:$0000; Count:1; Name:'ThumbnailFormat'), + (TID:0; TType:0; Tag:$5013; ParentID:$0000; Count:1; Name:'ThumbnailWidth'), + (TID:0; TType:0; Tag:$5014; ParentID:$0000; Count:1; Name:'ThumbnailHeight'), + (TID:0; TType:0; Tag:$5015; ParentID:$0000; Count:1; Name:'ThumbnailColorDepth'), + (TID:0; TType:0; Tag:$5016; ParentID:$0000; Count:1; Name:'ThumbnailPlanes'), + (TID:0; TType:0; Tag:$5017; ParentID:$0000; Count:1; Name:'ThumbnailRawBytes'), {110} + (TID:0; TType:0; Tag:$5018; ParentID:$0000; Count:1; Name:'ThumbnailSize'), + (TID:0; TType:0; Tag:$5019; ParentID:$0000; Count:1; Name:'ThumbnailCompressedSize'), + (TID:0; TType:0; Tag:$501A; ParentID:$0000; Count:1; Name:'ColorTransferFunction'), + (TID:0; TType:0; Tag:$501B; ParentID:$0000; Count:1; Name:'ThumbnailData'), + (TID:0; TType:0; Tag:$5020; ParentID:$0000; Count:1; Name:'ThumbnailImageWidth'), + (TID:0; TType:0; Tag:$5021; ParentID:$0000; Count:1; Name:'ThumbnailImageHeight'), + (TID:0; TType:0; Tag:$5022; ParentID:$0000; Count:1; Name:'ThumbnailBitsPerSample'), + (TID:0; TType:0; Tag:$5023; ParentID:$0000; Count:1; Name:'ThumbnailCompression'), + (TID:0; TType:0; Tag:$5024; ParentID:$0000; Count:1; Name:'ThumbnailPhotometricInterp'), + (TID:0; TType:0; Tag:$5025; ParentID:$0000; Count:1; Name:'ThumbnailImageDescription'), {120} + (TID:0; TType:2; Tag:$5026; ParentID:$0000; Count:1; Name:'ThumbnailEquipMake'), + (TID:0; TType:2; Tag:$5027; ParentID:$0000; Count:1; Name:'ThumbnailEquipModel'), + (TID:0; TType:0; Tag:$5028; ParentID:$0000; Count:1; Name:'ThumbnailStripOffsets'), + (TID:0; TType:0; Tag:$5029; ParentID:$0000; Count:1; Name:'ThumbnailOrientation'), + (TID:0; TType:0; Tag:$502A; ParentID:$0000; Count:1; Name:'ThumbnailSamplesPerPixel'), + (TID:0; TType:0; Tag:$502B; ParentID:$0000; Count:1; Name:'ThumbnailRowsPerStrip'), + (TID:0; TType:0; Tag:$502C; ParentID:$0000; Count:1; Name:'ThumbnailStripBytesCount'), + (TID:0; TType:0; Tag:$502D; ParentID:$0000; Count:1; Name:'ThumbnailResolutionX'), + (TID:0; TType:0; Tag:$502E; ParentID:$0000; Count:1; Name:'ThumbnailResolutionY'), + (TID:0; TType:0; Tag:$502F; ParentID:$0000; Count:1; Name:'ThumbnailPlanarConfig'), {130} + (TID:0; TType:0; Tag:$5030; ParentID:$0000; Count:1; Name:'ThumbnailResolutionUnit'), + (TID:0; TType:0; Tag:$5031; ParentID:$0000; Count:1; Name:'ThumbnailTransferFunction'), + (TID:0; TType:2; Tag:$5032; ParentID:$0000; Count:1; Name:'ThumbnailSoftwareUsed'), + (TID:0; TType:2; Tag:$5033; ParentID:$0000; Count:1; Name:'ThumbnailDateTime'), + (TID:0; TType:2; Tag:$5034; ParentID:$0000; Count:1; Name:'ThumbnailArtist'), + (TID:0; TType:0; Tag:$5035; ParentID:$0000; Count:1; Name:'ThumbnailWhitePoint'), + (TID:0; TType:0; Tag:$5036; ParentID:$0000; Count:1; Name:'ThumbnailPrimaryChromaticities'), + (TID:0; TType:0; Tag:$5037; ParentID:$0000; Count:1; Name:'ThumbnailYCbCrCoefficients'), + (TID:0; TType:0; Tag:$5038; ParentID:$0000; Count:1; Name:'ThumbnailYCbCrSubsampling'), + (TID:0; TType:0; Tag:$5039; ParentID:$0000; Count:1; Name:'ThumbnailYCbCrPositioning'), {140} + (TID:0; TType:0; Tag:$503A; ParentID:$0000; Count:1; Name:'ThumbnailRefBlackWhite'), + (TID:0; TType:2; Tag:$503B; ParentID:$0000; Count:1; Name:'ThumbnailCopyRight'), + (TID:0; TType:0; Tag:$5090; ParentID:$0000; Count:1; Name:'LuminanceTable'), + (TID:0; TType:0; Tag:$5091; ParentID:$0000; Count:1; Name:'ChrominanceTable'), + (TID:0; TType:0; Tag:$5100; ParentID:$0000; Count:1; Name:'FrameDelay'), + (TID:0; TType:0; Tag:$5101; ParentID:$0000; Count:1; Name:'LoopCount'), + (TID:0; TType:0; Tag:$5110; ParentID:$0000; Count:1; Name:'PixelUnit'), + (TID:0; TType:0; Tag:$5111; ParentID:$0000; Count:1; Name:'PixelPerUnitX'), + (TID:0; TType:0; Tag:$5112; ParentID:$0000; Count:1; Name:'PixelPerUnitY'), + (TID:0; TType:0; Tag:$5113; ParentID:$0000; Count:1; Name:'PaletteHistogram'), {150} + (TID:0; TType:0; Tag:$800D; ParentID:$0000; Count:1; Name:'ImageID'), + (TID:0; TType:0; Tag:$80E3; ParentID:$0000; Count:1; Name:'Matteing'), //* obsoleted by ExtraSamples */ + (TID:0; TType:0; Tag:$80E4; ParentID:$0000; Count:1; Name:'DataType'), //* obsoleted by SampleFormat */ + (TID:0; TType:0; Tag:$80E5; ParentID:$0000; Count:1; Name:'ImageDepth'), + (TID:0; TType:0; Tag:$80E6; ParentID:$0000; Count:1; Name:'TileDepth'), + (TID:0; TType:3; Tag:$828D; ParentID:$0000; Count:2; Name:'CFARepeatPatternDim'), + (TID:0; TType:1; Tag:$828E; ParentID:$0000; Count:1; Name:'CFAPattern'), //count: ??? + (TID:0; TType:0; Tag:$828F; ParentID:$0000; Count:1; Name:'BatteryLevel'), + (TID:0; TType:2; Tag:$8298; ParentID:$0000; Count:1; Name:'Copyright'), + (TID:0; TType:5; Tag:$829A; ParentID:$8769; Count:1; Name:'ExposureTime'; + Desc:'Exposure time'; Code:''; Data:''; Raw:''; FormatS:'%s sec'; Size:8; Callback:nil), //SSpeedCallback), {160} + (TID:0; TType:5; Tag:$829D; ParentID:$8769; Count:1; Name:'FNumber'; + Desc:''; Code:''; Data:''; Raw:''; FormatS:'F%0.1f'), + (TID:0; TType:4; Tag:$83BB; ParentID:$0000; Count:1; Name:'IPTC/NAA'; + Desc:'IPTC/NAA'), + (TID:0; TType:0; Tag:$84E3; ParentID:$0000; Count:1; Name:'IT8RasterPadding'), + (TID:0; TType:0; Tag:$84E5; ParentID:$0000; Count:1; Name:'IT8ColorTable'), + (TID:0; TType:0; Tag:$8649; ParentID:$0000; Count:1; Name:'ImageResourceInformation'), + (TID:0; TType:4; Tag:$8769; ParentID:$0000; Count:1; Name:'ExifOffset'; + Desc:''; Code:''; Data:''; Raw:''; FormatS:''; Size:4), + (TID:0; TType:0; Tag:$8773; ParentID:$0000; Count:1; Name:'InterColorProfile'), + (TID:0; TType:3; Tag:$8822; ParentID:$8769; Count:1; Name:'ExposureProgram'; + Desc:''; Code:'0:Not denfined,1:Manual,2:Program AE,3:Aperture-priority AE,'+ + '4:Shutter speed priority AE,5:Creative (slow speed),'+ + '6:Action (high speed),7:Portrait,8:Landscape;9:Bulb'), + (TID:0; TType:2; Tag:$8824; ParentID:$8769; Count:1; Name:'SpectralSensitivity'), + (TID:0; TType:4; Tag:$8825; ParentID:$0000; Count:1; Name:'GPSInfo'; + Desc:''; Code:''; Data:''; Raw:''; FormatS:''; Size:4), {170} + (TID:0; TType:3; Tag:$8827; ParentID:$8769; Count:1; Name:'ISOSpeedRatings'), {171} + (TID:0; TType:0; Tag:$8828; ParentID:$8769; Count:1; Name:'OECF'), + (TID:0; TType:0; Tag:$8829; ParentID:$8769; Count:1; Name:'Interlace'), + (TID:0; TType:8; Tag:$882A; ParentID:$8769; Count:1; Name:'TimeZoneOffset'), + (TID:0; TType:3; Tag:$882B; ParentID:$8769; Count:1; Name:'SelfTimerMode'), + (TID:0; TType:7; Tag:$9000; ParentID:$8769; Count:1; Name:'ExifVersion'; + Desc:''; Code:''; Data:''; Raw:''; FormatS:''; Size:4; Callback:VersionCallback), + (TID:0; TType:2; Tag:$9003; ParentID:$8769; Count:1; Name:'DateTimeOriginal'), + (TID:0; TType:2; Tag:$9004; ParentID:$8769; Count:1; Name:'DateTimeDigitized'), + (TID:0; TType:7; Tag:$9101; ParentID:$8769; Count:1; Name:'ComponentsConfiguration'; + Desc:''; Code:''; Data:''; Raw:''; FormatS:''; Size:0; Callback:CompCfgCallBack), + (TID:0; TType:5; Tag:$9102; ParentID:$8769; Count:1; Name:'CompressedBitsPerPixel'), {180} + (TID:0; TType:10; Tag:$9201; ParentID:$8769; Count:1; Name:'ShutterSpeedValue'; + Desc:''; Code:''; Data:''; Raw:''; FormatS:''; Size:0; Callback:SSpeedCallBack), + (TID:0; TType:5; Tag:$9202; ParentID:$8769; Count:1; Name:'ApertureValue'; + Desc:'Aperture value'; Code:''; Data:''; Raw:''; FormatS:'F%0.1f'), + (TID:0; TType:10;Tag:$9203; ParentID:$8769; Count:1; Name:'BrightnessValue'), + (TID:0; TType:10;Tag:$9204; ParentID:$8769; Count:1; Name:'ExposureBiasValue'), + (TID:0; TType:5; Tag:$9205; ParentID:$8769; Count:1; Name:'MaxApertureValue'; + Desc:''; Code:''; Data:''; Raw:''; FormatS:'F%0.1f'), + (TID:0; TType:5; Tag:$9206; ParentID:$8769; Count:1; Name:'SubjectDistance'), + (TID:0; TType:3; Tag:$9207; ParentID:$8769; Count:1; Name:'MeteringMode'; + Desc:''; + Code:'0:Unknown,1:Average,2:Center,3:Spot,4:Multi-spot,5:Multi-segment,6:Partial'), + (TID:0; TType:3; Tag:$9208; ParentID:$8769; Count:1; Name:'LightSource'; + Desc:''; + Code:'0:Unknown,1:Daylight,2:Fluorescent,3:Tungsten,10:Flash,17:Std A,18:Std B,19:Std C'), + (TID:0; TType:3; Tag:$9209; ParentID:$8769; Count:1; Name:'Flash'; + Desc:''; Code:''; Data:''; Raw:''; FormatS:''; Size:0; CallBack:FlashCallBack), + (TID:0; TType:5; Tag:$920A; ParentID:$8769; Count:1; Name:'FocalLength'; + Desc:'Focal length'; Code:''; Data:''; Raw:''; FormatS:'%0.1f mm'), {190} + (TID:0; TType:0; Tag:$920B; ParentID:$8769; Count:1; Name:'FlashEnergy'), + (TID:0; TType:0; Tag:$920C; ParentID:$8769; Count:1; Name:'SpatialFrequencyResponse'), + (TID:0; TType:0; Tag:$920D; ParentID:$8769; Count:1; Name:'Noise'), + (TID:0; TType:0; Tag:$920E; ParentID:$8769; Count:1; Name:'FocalPlaneXResolution'; + Desc:''; code:''; Data:''; Raw:''; FormatS:'%f'; Size:0; CallBack:nil), + (TID:0; TType:0; Tag:$920F; ParentID:$8769; Count:1; Name:'FocalPlaneYResolution'; + Desc:''; Code:''; Data:''; Raw:''; FormatS:'%f'; Size:0; CallBack:nil), + (TID:0; TType:0; Tag:$9210; ParentID:$8769; Count:1; Name:'FocalPlaneResolutionUnit'; + Desc:''; Code:'1:None specified,2:inches,3:cm'), + (TID:0; TType:4; Tag:$9211; ParentID:$8769; Count:1; Name:'ImageNumber'), + (TID:0; TType:2; Tag:$9212; ParentID:$8769; Count:1; Name:'SecurityClassification'), + (TID:0; TType:2; Tag:$9213; ParentID:$8769; Count:1; Name:'ImageHistory'), + (TID:0; TType:3; Tag:$9214; ParentID:$8769; Count:2; Name:'SubjectLocation'), {200} + (TID:0; TType:0; Tag:$9215; ParentID:$8769; Count:1; Name:'ExposureIndex'), + (TID:0; TType:0; Tag:$9216; ParentID:$8769; Count:1; Name:'TIFF/EPStandardID'), + (TID:0; TType:0; Tag:$9217; ParentID:$8769; Count:1; Name:'SensingMethod'), + (TID:0; TType:0; Tag:$923F; ParentID:$8769; Count:1; Name:'StoNits'), + (TID:0; TType:7; Tag:$927C; ParentID:$8769; Count:1; Name:'MakerNote'), + (TID:0; TType:7; Tag:$9286; ParentID:$8769; Count:1; Name:'UserComment'), + (TID:0; TType:2; Tag:$9290; ParentID:$8769; Count:1; Name:'SubSecTime'), + (TID:0; TType:2; Tag:$9291; ParentID:$8769; Count:1; Name:'SubSecTimeOriginal'), + (TID:0; TType:2; Tag:$9292; ParentID:$8769; Count:1; Name:'SubSecTimeDigitized'), + (TID:0; TType:0; Tag:$953C; ParentID:$0000; Count:1; Name:'ImageSourceData'), // "Adobe Photoshop Document Data Block": 8BIM... {210} + (TID:0; TType:0; Tag:$9C9B; ParentID:$0000; Count:1; Name:'Title'; + Code:''; Data:''; Raw:''; FormatS:''; Size:0; CallBack:xpTranslate), // Win XP specific, Unicode + (TID:0; TType:0; Tag:$9C9C; ParentID:$0000; Count:1; Name:'Comments'; + Code:''; Data:''; Raw:''; FormatS:''; Size:0; CallBack:xpTranslate), // Win XP specific, Unicode + (TID:0; TType:0; Tag:$9C9D; ParentID:$0000; Count:1; Name:'Author'; + Code:''; Data:''; Raw:''; FormatS:''; Size:0; CallBack:xpTranslate), // Win XP specific, Unicode + (TID:0; TType:0; Tag:$9C9E; ParentID:$0000; Count:1; Name:'Keywords'; + Code:''; Data:''; Raw:''; FormatS:''; Size:0; CallBack:xpTranslate), // Win XP specific, Unicode + (TID:0; TType:0; Tag:$9C9F; ParentID:$0000; Count:1; Name:'Subject'; + Code:''; Data:''; Raw:''; FormatS:''; Size:0; CallBack:xpTranslate), // Win XP specific, Unicode + (TID:0; TType:0; Tag:$A000; ParentID:$8769; Count:1; Name:'FlashPixVersion'), + (TID:0; TType:3; Tag:$A001; ParentID:$8769; Count:1; Name:'ColorSpace'; + Desc:''; Code:'0:sBW,1:sRGB'), + (TID:0; TType:3; Tag:$A002; ParentID:$8769; Count:1; Name:'ExifImageWidth'), + (TID:0; TType:3; Tag:$A003; ParentID:$8769; Count:1; Name:'ExifImageLength'), + (TID:0; TType:2; Tag:$A004; ParentID:$8769; Count:1; Name:'RelatedSoundFile'), {220} + (TID:0; TType:0; Tag:$A005; ParentID:$8769; Count:1; Name:'InteroperabilityOffset'), + (TID:0; TType:5; Tag:$A20B; ParentID:$8769; Count:1; Name:'FlashEnergy'), // TID:0;TType:0;ICode: 2;Tag: $920B in TIFF/EP + (TID:0; TType:0; Tag:$A20C; ParentID:$8769; Count:1; Name:'SpatialFrequencyResponse'), // TID:0;TType:0;ICode: 2;Tag: $920C - - + (TID:0; TType:5; Tag:$A20E; ParentID:$8769; Count:1; Name:'FocalPlaneXResolution'; + Desc:''; code:''; Data:''; Raw:''; FormatS:'%f'; Size:0; CallBack:nil), + (TID:0; TType:5; Tag:$A20F; ParentID:$8769; Count:1; Name:'FocalPlaneYResolution'; + Desc:''; code:''; Data:''; Raw:''; FormatS:'%f'; Size:0; CallBack:nil), + (TID:0; TType:3; Tag:$A210; ParentID:$8769; Count:1; Name:'FocalPlaneResolutionUnit'; + Desc:''; Code:'1:None specified,2:inches,3:cm'), // TID:0;TType:0;ICode: 2;Tag: $9210 - - + (TID:0; TType:0; Tag:$A211; ParentID:$8769; Count:1; Name:'ImageNumber'), + (TID:0; TType:0; Tag:$A212; ParentID:$8769; Count:1; Name:'SecurityClassification'), + (TID:0; TType:0; Tag:$A213; ParentID:$8769; Count:1; Name:'ImageHistory'), + (TID:0; TType:3; Tag:$A214; ParentID:$8769; Count:2; Name:'SubjectLocation'), {230} + (TID:0; TType:5; Tag:$A215; ParentID:$8769; Count:1; Name:'ExposureIndex'), + (TID:0; TType:0; Tag:$A216; ParentID:$8769; Count:1; Name:'TIFF/EPStandardID'; + Desc:'TIFF/EPStandardID'), + (TID:0; TType:3; Tag:$A217; ParentID:$8769; Count:1; Name:'SensingMethod'; Desc:''; + Code:'0:Unknown,1:Not defined,2:One-chip color area,3:Two-chip color area,'+ + '4:Three-chip color area,5:Color sequential area,7:Trilinear,'+ + '8:Color-sequential linear'), + (TID:0; TType:1; Tag:$A300; ParentID:$8769; Count:1; Name:'FileSource'; Desc:''; + Code:'0:Unknown,1:Film scanner,2:Reflection print scanner,3:Digital camera'), + (TID:0; TType:7; Tag:$A301; ParentID:$8769; Count:1; Name:'SceneType'; + Desc:''; Code:'0:Unknown,1:Directly Photographed'), + (TID:0; TType:7; Tag:$A302; ParentID:$8769; Count:1; Name:'CFAPattern'), + (TID:0; TType:3; Tag:$A401; ParentID:$8769; Count:1; Name:'CustomRendered'; + Desc:''; Code:'0:Normal,1:Custom'), + (TID:0; TType:3; Tag:$A402; ParentID:$8769; Count:1; Name:'ExposureMode'; + Desc:''; Code:'0:Auto,1:Manual,2:Auto bracket'), + (TID:0; TType:3; Tag:$A403; ParentID:$8769; Count:1; Name:'WhiteBalance'; + Desc:''; Code:'0:Auto,1:Manual'), + (TID:0; TType:5; Tag:$A404; ParentID:$8769; Count:1; Name:'DigitalZoomRatio'), {240} + (TID:0; TType:3; Tag:$A405; ParentID:$8769; Count:1; Name:'FocalLengthIn35mmFilm'; + Desc:'Focal Length in 35mm Film'; Code:''; Data:''; Raw:''; FormatS:'%.1f mm'), + (TID:0; TType:3; Tag:$A406; ParentID:$8769; Count:1; Name:'SceneCaptureType'; + Desc:''; Code:'0:Standard,1:Landscape,2:Portrait,3:Night scene'), + (TID:0; TType:3; Tag:$A407; ParentID:$8769; Count:1; Name:'GainControl'; Desc:''; + Code:'0:None,1:Low gain up,2:High gain up,3:Low gain down,4:High gain down'), + (TID:0; TType:3; Tag:$A408; ParentID:$8769; Count:1; Name:'Contrast'; + Desc:''; Code:'0:Normal,1:Soft,2:Hard'), + (TID:0; TType:3; Tag:$A409; ParentID:$8769; Count:1; Name:'Saturation'; + Desc:''; Code:'0:Normal,1:Low,2:High'), + (TID:0; TType:3; Tag:$A40A; ParentID:$8769; Count:1; Name:'Sharpness'; + Desc:''; Code:'0:Normal,1:Soft,2:Hard'), + (TID:0; TType:0; Tag:$A40B; ParentID:$8769; Count:1; Name:'DeviceSettingDescription'), + (TID:0; TType:3; Tag:$A40C; ParentID:$8769; Count:1; Name:'SubjectDistanceRange'; {250} + Desc:''; Code:'0:Unknown,1:Macro,2:Close view,3:Distant view'), + (TID:0; TType:2; Tag:$A420; ParentID:$8769; Count:1; Name:'ImageUniqueID'; + Desc:''; Code:'0:Close view,1:Distant view'), + (TID:0; TType:0; Tag:0; ParentID:$0000; Count:1; Name:'Unknown') +); + + GPSTable : array [0..GPSCnt-1] of TTagEntry = ( + (TID:0; TType:1; Tag:$000; ParentID:$8825; Count:4; Name:'GPSVersionID'; + Desc:''; Code:''; Data:''; RAw:''; FormatS:''; Size:0; CallBack:GpsVersionID), + (TID:0; TType:2; Tag:$001; ParentID:$8825; Count:2; Name:'GPSLatitudeRef'; Desc:''), + (TID:0; TType:5; Tag:$002; ParentID:$8825; Count:3; Name:'GPSLatitude'; + Desc:''; Code:''; Data:''; Raw:''; FormatS:''; Size:0; CallBack:GpsPosn), + (TID:0; TType:2; Tag:$003; ParentID:$8825; Count:2; Name:'GPSLongitudeRef';Desc:''), + (TID:0; TType:5; Tag:$004; ParentID:$8825; Count:3; Name:'GPSLongitude'; + Desc:''; Code:''; Data:''; Raw:''; FormatS:''; Size:0; CallBack:GpsPosn), + (TID:0; TType:1; Tag:$005; ParentID:$8825; Count:1; Name:'GPSAltitudeRef'; Desc:''; + Code:'0:Above Sealevel,1:Below Sealevel'), + (TID:0; TType:5; Tag:$006; ParentID:$8825; Count:1; Name:'GPSAltitude'; Desc:''; + Code:''; Data:''; Raw:''; FormatS:''; Size:0; CallBack:GpsAltitude), + (TID:0; TType:5; Tag:$007; ParentID:$8825; Count:3; Name:'GPSTimeStamp'; Desc:''; + Code:''; Data:''; Raw:''; FormatS:''; Size:0; CallBack:CvtTime), + (TID:0; TType:2; Tag:$008; ParentID:$8825; Count:1; Name:'GPSSatellites'; Desc:''), + (TID:0; TType:2; Tag:$009; ParentID:$8825; Count:2; Name:'GPSStatus'; + Desc:''; Code:'A:Active;V:Void'), + (TID:0; TType:2; Tag:$00A; ParentID:$8825; Count:2; Name:'GPSMeasureMode'; + Desc:''; Code:'2:2D,3:3D'), + (TID:0; TType:5; Tag:$00B; ParentID:$8825; Count:1; Name:'GPSDOP'; Desc:''), + (TID:0; TType:2; Tag:$00C; ParentID:$8825; Count:2; Name:'GPSSpeedRef'; + Desc:''; Code:'K:km/h,M:mph,N:knots'), + (TID:0; TType:5; Tag:$00D; ParentID:$8825; Count:1; Name:'GPSSpeed'; Desc:''), + (TID:0; TType:2; Tag:$00E; ParentID:$8825; Count:2; Name:'GPSTrackRef'; + Desc:''; Code:'M:Magnetic North,T:True North'), + (TID:0; TType:5; Tag:$00F; ParentID:$8825; Count:1; Name:'GPSTrack'; Desc:''), + (TID:0; TType:2; Tag:$010; ParentID:$8825; Count:2; Name:'GPSImageDirectionRef'; + Desc:''; Code:'M:Magnetic North,T:True North'), + (TID:0; TType:5; Tag:$011; ParentID:$8825; Count:1; Name:'GPSImageDirection'; Desc:''), + (TID:0; TType:2; Tag:$012; ParentID:$8825; Count:1; Name:'GPSMapDatum'; Desc:''), + (TID:0; TType:2; Tag:$013; ParentID:$8825; Count:2; Name:'GPSDestLatitudeRef'; + Desc:''; Code:'N:North,S:South'), + (TID:0; TType:5; Tag:$014; ParentID:$8825; Count:3; Name:'GPSDestLatitude'; Desc:''; + Code:''; Data:''; Raw:''; FormatS:''; Size:0; CallBack:GpsPosn), + (TID:0; TType:2; Tag:$015; ParentID:$8825; Count:2; Name:'GPSDestLongitudeRef'; + Desc:''; Code: 'E:East,W:West'), + (TID:0; TType:5; Tag:$016; ParentID:$8825; Count:3; Name:'GPSDestLongitude'; Desc:''; + Code:''; Data:''; Raw:''; FormatS:''; Size:0; CallBack:GpsPosn), + (TID:0; TType:2; Tag:$017; ParentID:$8825; Count:2; Name:'GPSDestBearingRef'; + Desc:''; Code:'M:Magnetic North,T:True North'), + (TID:0; TType:5; Tag:$018; ParentID:$8825; Count:1; Name:'GPSDestBearing'; Desc:''), + (TID:0; TType:2; Tag:$019; ParentID:$8825; Count:2; Name:'GPSDestDistanceRef'; + Desc:''; Code:'K:Kilometers,M:Miles,N:Nautic Miles'), + (TID:0; TType:5; Tag:$01A; ParentID:$8825; Count:1; Name:'GPSDestDistance'; Desc:''), + (TID:0; TType:7; Tag:$01B; ParentID:$8825; Count:1; Name:'GPSProcessingMode'; Desc:''), + (TID:0; TType:7; Tag:$01C; ParentID:$8825; Count:1; Name:'GPSAreaInformation'; Desc:''), + (TID:0; TType:2; Tag:$01D; ParentID:$8825; Count:7; Name:'GPSDateStamp'; Desc:''), + (TID:0; TType:3; Tag:$01E; ParentID:$8825; Count:1; Name:'GPSDifferential'; + Desc:''; Code:'0:No Correction,1:Differential Correction'), + (TID:0; TType:5; Tag:$01F; ParentID:$8825; Count:1; Name:'GPSHPositioningError'; Desc:'') + ); + + tagInit : boolean = false; + +function FindExifTagDefByName(ATagName: String): PTagEntry; +var + i: Integer; +begin + for i:=0 to High(TagTable) do begin + Result := @TagTable[i]; + if AnsiSameText(Result^.Name, ATagName) then + exit; + end; + Result := nil; +end; + +function FindExifTagDefByID(ATagID: word): PTagEntry; +var + i: Integer; +begin + for i:=0 to High(TagTable) do begin + Result := @TagTable[i]; + if Result^.Tag = ATagID then + exit; + end; + Result := nil; +end; + +function FindGpsTagDefByName(ATagName: String): PTagEntry; +var + i: Integer; +begin + for i:=0 to High(GpsTable) do begin + Result := @GpsTable[i]; + if AnsiSameText(Result^.Name, ATagName) then + exit; + end; + Result := nil; +end; + +function FindGpsTagDefByID(ATagID: word): PTagEntry; +var + i: Integer; +begin + for i:=0 to High(GpsTable) do begin + Result := @GpsTable[i]; + if Result^.Tag = ATagID then + exit; + end; + Result := nil; +end; + +Procedure FixTagTable(var tags:array of TTagEntry); +var i:integer; +begin + for i := low(tags) to high(tags) do + begin + if Length(tags[i].Desc) <= 0 then + tags[i].Desc := tags[i].Name; + end; +end; + +Procedure FixTagTableParse(var tags:array of TTagEntry); +var i:integer; +begin + for i := low(tags) to high(tags) do + begin + if Length(tags[i].Desc) <= 0 then + tags[i].Desc := InsertSpaces(tags[i].Name); + end; +end; + +procedure LoadTagDescs(fancy:boolean = false); +begin + if tagInit + then exit + else tagInit := true; + if fancy then + begin + FixTagTableParse(TagTable); + FixTagTableParse(GPSTable); + end + else + begin + FixTagTable(TagTable); + FixTagTable(GPSTable); + end; +end; + +function LookupMTagID(idx:integer; ManuTable: array of TTagEntry):integer; +var + i: integer; +begin + result := -1; + for i := 0 to high(ManuTable) do + if ManuTable[i].Tag = idx then + begin + result := i; + break; + end; +end; + +function LookupType(idx: integer): String; +var + i: integer; +begin + result := 'Unknown'; +// for i := 0 to (Sizeof(ProcessTable) div SizeOf(TTagEntry))-1 do + for i := 0 to High(ProcessTable) do + if ProcessTable[i].Tag = idx then begin + Result := ProcessTable[i].Desc; + exit; + end; +end; + +function LookupTagDefByID(idx: integer; ATagType: TTagType = ttExif): integer; +var + i:integer; +begin + Result := -1; + case ATagType of + ttExif, ttThumb: + for i := 0 to ExifTagCnt-1 do + if TagTable[i].Tag = idx then begin + Result := i; + break; + end; + ttGps: + for i := 0 to GPSCnt-1 do + if GPSTable[i].Tag = idx then begin + Result := i; + break; + end; + end; +end; + +function FetchTagDefByID(idx: integer; ATagType: TTagType = ttExif): TTagEntry; +var + i: integer; +begin + Result := TagTable[ExifTagCnt-1]; + case ATagType of + ttExif, ttThumb: + for i := 0 to ExifTagCnt-1 do + if TagTable[i].Tag = idx then begin + result := TagTable[i]; + break; + end; + ttGps: + for i := 0 to GPSCnt-1 do + if GPSTable[i].Tag = idx then begin + result := GPSTable[i]; + break; + end; + end; +end; + +function LookupCode(ATagID: Word; ATagType: TTagType=ttExif): String; overload; +var + i:integer; +begin + Result := ''; + case ATagType of + ttExif, ttThumb: + for i := 0 to ExifTagCnt-1 do + if TagTable[i].Tag = ATagID then begin + Result := TagTable[i].Code; + break; + end; + ttGps: + for i := 0 to GPSCnt-1 do + if GPSTable[i].Tag = ATagID then begin + Result := GPSTable[i].Code; + break; + end; + end; +end; + +function LookupCode(ATagID: Word; TagTbl: array of TTagEntry): String; overload; +var + i: integer; +begin + Result := ''; + for i := 0 to High(TagTbl) do + if TagTbl[i].Tag = ATagID then begin + Result := TagTbl[i].Code; + break; + end; +end; + +{ Tries to find the string AValue within TTagEntry.Code and + returns the numerical value assigned to the Code (before the colon). + + Example: + The codes defined for the Tag "ResolutionUnits" are + '1:None Specified,2:Inch,3:Centimeter'. + If AValue is 'Inch' then the value 2 is returned. } +function GetTagCode(ATag: TTagEntry; AValue: String): Integer; +var + i: Integer; +begin + if ATag.Code <> '' then + Result := FindTextIndexInCode(AValue, ATag.Code) + else + if TryStrToInt(AValue, i) then + Result := i + else + Result := -1; +end; + + +//------------------------------------------------------------------------------ +// TEndInd +// +// Here we implement the Endian Independent layer. Outside of these methods +// we don't care about endian issues. +//------------------------------------------------------------------------------ + +function TEndInd.GetDataBuff: AnsiString; +begin + result := FData; +end; + +procedure TEndInd.SetDataBuff(const Value: AnsiString); +begin + FData := Value; +end; + +procedure TEndInd.WriteInt16(var buff: AnsiString; int,posn: integer); +begin + if MotorolaOrder then + begin + buff[posn+1] := ansichar(int mod 256); + buff[posn] := ansichar(int div 256); + end + else + begin + buff[posn] := ansichar(int mod 256); + buff[posn+1] := ansichar(int div 256); + end +end; + +procedure TEndInd.WriteInt32(var buff: ansistring; int, posn: longint); +begin + if MotorolaOrder then + begin + buff[posn+3] := ansichar(int mod 256); + buff[posn+2] := ansichar((int shr 8) mod 256); + buff[posn+1] := ansichar((int shr 16) mod 256); + buff[posn] := ansichar((int shr 24) mod 256); + end + else + begin + buff[posn] := ansichar(int mod 256); + buff[posn+1] := ansichar((int shr 8) mod 256); + buff[posn+2] := ansichar((int shr 16) mod 256); + buff[posn+3] := ansichar((int shr 24) mod 256); + end +end; + +// Convert a 16 bit unsigned value from file's native byte order +function TEndInd.Get16u(AOffs: integer):word; +// var hibyte,lobyte:byte; +begin +// To help debug, uncomment the following two lines +// hibyte := byte(llData[oset+1]); +// lobyte := byte(llData[oset]); + if MotorolaOrder then + result := (byte(FData[AOffs]) shl 8) or byte(FData[AOffs+1]) + else + result := (byte(FData[AOffs+1]) shl 8) or byte(FData[AOffs]); +end; + +// Convert a 32 bit signed value from file's native byte order +function TEndInd.Get32s(AOffs: integer):Longint; +begin + if MotorolaOrder then + result := (byte(FData[AOffs]) shl 24) + or (byte(FData[AOffs+1]) shl 16) + or (byte(FData[AOffs+2]) shl 8) + or byte(FData[AOffs+3]) + else + result := (byte(FData[AOffs+3]) shl 24) + or (byte(FData[AOffs+2]) shl 16) + or (byte(FData[AOffs+1]) shl 8) + or byte(FData[AOffs]); +end; + +// Convert a 32 bit unsigned value from file's native byte order +function TEndInd.Put32s(data: Longint): AnsiString; +var + data2: integer; + // buffer: string[4] absolute data2; + // bbuff: AnsiChar; +begin + data2 := data; + if MotorolaOrder then + data2 := NtoBE(data) else + data2 := NtoLE(data); + SetLength(Result, 4); + Move(data2, Result[1], 4); + { + begin + bbuff := buffer[1]; + buffer[1] := buffer[4]; + buffer[4] := bbuff; + bbuff := buffer[2]; + buffer[2] := buffer[3]; + buffer[3] := bbuff; + end; + } +// Result := buffer; +end; + +// Convert a 32 bit unsigned value from file's native byte order +function TEndInd.Get32u(AOffs: integer): Longword; +begin + result := Longword(Get32S(AOffs)) and $FFFFFFFF; +end; + + +{------------------------------------------------------------------------------} +{ TImageInfo } +{------------------------------------------------------------------------------} + +constructor TImageInfo.Create(AParent: TObject; ABigEndian: Boolean; + ABuildCode: integer = GenAll); +begin + inherited Create; + FParent := AParent; + MotorolaOrder := ABigEndian; + LoadTagDescs(True); // initialize global structures + FITagCount := 0; + BuildList := ABuildCode; + ClearDirStack; +end; + +// These destructors provided by Keith Murray of byLight Technologies - Thanks! +destructor TImageInfo.Destroy; +begin + SetLength(fITagArray, 0); + inherited; +end; + +// To be called by the reader. +procedure TImageInfo.AddTagFromReader(ATag: TTagEntry); +begin + ATag.Data := InternalGetTagValueAsString(ATag); + if ATag.ParentID = 1 then + SetThumbTagByID(ATag.Tag, ATag) + else + SetTagByID(ATag.Tag, ATag); +end; + +// To be called by the reader. +procedure TImageInfo.AddThumbnailFromReader(ABuffer: TBytes); +begin + SetLength(FThumbnailBuffer, Length(ABuffer)); + if Length(ABuffer) > 0 then + Move(ABuffer[0], FThumbnailBuffer[0], Length(ABuffer)); +end; + +procedure TImageInfo.Assign(Source: TImageInfo); +begin +// FCameraMake := Source.FCameraMake; +// FCameraModel := Source.FCameraModel; +// DateTime := Source.DateTime; + Height := Source.Height; + Width := Source.Width; + FlashUsed := Source.FlashUsed; +// Comments := Source.Comments; + MakerNote := Source.MakerNote; + TraceStr := Source.TraceStr; + msTraceStr := Source.msTraceStr; + msAvailable := Source.msAvailable; + msName := Source.msName; +end; + +function TImageInfo.GetTagByDesc(SearchStr: String): TTagEntry; +var + i: integer; +begin + i := LookupTagByDesc(SearchStr); + if i >= 0 then + Result := fiTagArray[i] + else + Result := EmptyEntry; +end; + + +// This function returns the index of a tag name in the tag buffer. +function TImageInfo.LookupTagIndex(ATagName: String): integer; +var + i: integer; +begin + ATagName := UpperCase(ATagName); + for i := 0 to fiTagCount-1 do + if UpperCase(fiTagArray[i].Name) = ATagName then + begin + Result := i; + Exit; + end; + Result := -1; +end; + +(* +// This function returns the data value for a given tag name. +function TImageInfo.LookupTagVal(ATagName: String): String; +var + i: integer; +begin + ATagName := UpperCase(ATagName); + for i := 0 to fiTagCount-1 do + if UpperCase(fiTagArray[i].Name) = ATagName then + begin + Result := fiTagArray[i].Data; + Exit; + end; + Result := ''; +end; + *) + +// This function returns the integer data value for a given tag name. +function TImageInfo.LookupTagInt(ATagName: String):integer; +var + i: integer; + x: Double; + {$IFDEF FPC} + fs: TFormatSettings; + {$ELSE} + res: Integer; + {$ENDIF} +begin + ATagName := UpperCase(ATagName); + for i := 0 to fiTagCount-1 do + if UpperCase(fiTagArray[i].Name) = ATagName then + begin + if not TryStrToInt(fiTagArray[i].Data, Result) then + begin + if TryStrToFloat(fiTagArray[i].Data, x) then + Result := Round(x) + else + begin + {$IFDEF FPC} + fs := FormatSettings; + if fs.DecimalSeparator = '.' then fs.DecimalSeparator := ',' else + fs.DecimalSeparator := '.'; + if TryStrToFloat(fiTagArray[i].Data, x, fs) then + Result := Round(x) + else + Result := -1; + {$ELSE} + val(fiTagArray[i].Data, x, res); + if res = 0 then + Result := Round(x) + else + Result := -1; + {$ENDIF} + end; + end; + Exit; + end; + Result := -1; +end; + +// This function returns the index of a tag in the tag buffer. +// It searches by the description which is most likely to be used as a label +Function TImageInfo.LookupTagByDesc(ADesc: String):integer; +var + i: integer; +begin + ADesc := UpperCase(ADesc); + for i := 0 to FITagCount-1 do + if UpperCase(fiTagArray[i].Desc) = ADesc then + begin + Result := i; + Exit; + end; + Result := -1; +end; + +// This function returns the index of a tag definition for a given tag name. +function TImageInfo.LookupTagDefn(ATagName: String): integer; +var + i: integer; +begin + for i := 0 to ExifTagCnt-1 do + begin + if LowerCase(ATagName) = LowerCase(TagTable[i].Name) then + begin + Result := i; + Exit; + end; + end; + Result := -1; +end; + +function TImageInfo.ExifDateToDateTime(ARawStr: ansistring): TDateTime; +type + TConvert= packed record + year: Array [1..4] of ansichar; f1:ansichar; + mon: Array [1..2] of ansichar; f2:ansichar; + day: Array [1..2] of ansichar; f3:ansichar; + hr: Array [1..2] of ansichar; f4:ansichar; + min: Array [1..2] of ansichar; f5:ansichar; + sec: Array [1..2] of ansichar; + end; + PConvert= ^TConvert; +var + yr, mn, dy, h, m, s: Integer; + d: TDateTime; + t: TDateTime; +begin + Result := 0; + if Length(ARawStr) >= SizeOf(TConvert) then + with PConvert(@ARawStr[1])^ do + if TryStrToInt(year, yr) and + TryStrToInt(mon, mn) and + TryStrToInt(day, dy) and + TryEncodeDate(yr, mn, dy, d) + and + TryStrToInt(hr, h) and + TryStrToInt(min, m) and + TryStrToInt(sec, s) and + TryEncodeTime(h, m, s, 0, t) + then + Result := d + t; +end; + + +function TImageInfo.GetImgDateTime: TDateTime; +begin + Result := GetDateTimeOriginal; + if Result = 0 then + Result := GetDateTimeDigitized; + if Result = 0 then + Result := GetDateTimeModified; + if Result = 0 then + Result := TImgData(Parent).FileDatetime; +end; + +function TImageInfo.GetDateTimeOriginal: TDateTime; +var + t: TTagEntry; +begin + Result := 0.0; + t := TagByName['DateTimeOriginal']; + if t.Tag <> 0 then + Result := ExifDateToDateTime(t.Raw); +end; + +procedure TImageInfo.SetDateTimeOriginal(const AValue: TDateTime); +var + v: Variant; +begin + v := FormatDateTime(EXIF_DATETIME_FORMAT, AValue); + SetTagValue('DateTimeOriginal', v); +end; +function TImageInfo.GetDateTimeDigitized: TDateTime; +var + t: TTagEntry; +begin + Result := 0.0; + t := TagByName['DateTimeDigitized']; + if t.Tag <> 0 then + Result := ExifDateToDateTime(t.Raw); +end; + +procedure TImageInfo.SetDateTimeDigitized(const AValue: TDateTime); +var + v: Variant; +begin + v := FormatDateTime(EXIF_DATETIME_FORMAT, AValue); + SetTagValue('DateTimeDigitized', v); +end; + +function TImageInfo.GetDateTimeModified: TDateTime; +var + t: TTagEntry; +begin + Result := 0.0; + t := TagByName['DateTime']; + if t.Tag <> 0 then + Result := ExifDateToDateTime(t.Raw); +end; + +procedure TImageInfo.SetDateTimeModified(const AValue: TDateTime); +var + v: Variant; +begin + v := FormatDateTime(EXIF_DATETIME_FORMAT, AValue); + SetTagValue('DateTime', v); +end; + +Procedure TImageInfo.AdjDateTime(ADays, AHours, AMins, ASecs: Integer); +var + delta: double; + dt: TDateTime; +begin + // hrs/day min/day sec/day + delta := ADays + (AHours/24) + (AMins/1440) + (ASecs/86400); + + dt := GetDateTimeOriginal; + if dt > 0 then SetDateTimeOriginal(dt + delta); + + dt := GetDateTimeDigitized; + if dt > 0 then SetDateTimeDigitized(dt + delta); + + dt := GetDateTimeModified; + if dt > 0 then SetDateTimeModified(dt + delta); +end; + +function TImageInfo.AddTagToArray(ANewTag:iTag):integer; +begin + if not ((ANewTag.Name = '') or (ANewTag.Name = 'Unknown')) then // Empty fields are masked out + begin + if fITagCount >= MaxTag-1 then + begin + inc(MaxTag, TagArrayGrowth); + SetLength(fITagArray, MaxTag); + end; + fITagArray[fITagCount] := ANewTag; + inc(fITagCount); + end; + result := fITagCount-1; +end; + +function TImageInfo.AddTagToThumbArray(ANewTag: iTag): integer; +begin + if ANewTag.Tag <> 0 then // Empty fields are masked out + begin + if fIThumbCount >= MaxThumbTag-1 then + begin + inc(MaxThumbTag, TagArrayGrowth); + SetLength(fIThumbArray, MaxThumbTag); + end; + fIThumbArray[fIThumbCount] := ANewTag; + inc(fIThumbCount); + end; + result := fIThumbCount-1; +end; + +function TImageInfo.CvtInt(ABuffer: Pointer; ABufferSize: Integer): Longint; +var + i: integer; + r: Int64; + P: PByte; +begin + r := 0; + if MotorolaOrder then begin + P := PByte(ABuffer); + for i := 1 to ABufferSize do begin + r := r*256 + P^; + inc(P); + end; + end else begin + P := PByte(ABuffer); + inc(P, ABufferSize - 1); + for i := 1 to ABufferSize do begin + r := r*256 + P^; + dec(P); + end; + end; + Result := LongInt(r); +end; + +function TImageInfo.Decode: Boolean; +begin + Result := TImgData(FParent).Decode; +end; + +function TImageInfo.FormatNumber(ABuffer: PByte; ABufferSize: Integer; + AFmt: integer; AFmtStr: String; ADecodeStr: String=''): String; +var + P: PByte; + i, len: integer; + tmp, tmp2: longint; + dv: double; +begin + Result := ''; + len := BYTES_PER_FORMAT[AFmt]; + if len = 0 then + exit; + + P := ABuffer; + for i := 0 to min(ABufferSize div len, 128) - 1 do + begin + if Result <> '' then + Result := Result + dExifDataSep; // Used for data display + case AFmt of + FMT_SBYTE, + FMT_BYTE, + FMT_USHORT, + FMT_ULONG, + FMT_SSHORT, + FMT_SLONG: + begin + tmp := CvtInt(P, len); + if (ADecodeStr = '') or not Decode then + Result := Result + defIntFmt(tmp) + else + Result := Result + DecodeField(ADecodeStr, IntToStr(tmp)); + end; + FMT_URATIONAL, + FMT_SRATIONAL: + begin + tmp := CvtInt(P, 4); + inc(P, 4); + tmp2 := CvtInt(P, 4); + dec(P, 4); + Result := Result + defFracFmt(tmp, tmp2); + if (ADecodeStr <> '') or not Decode then + Result := Result + DecodeField(ADecodeStr, Result); + end; + FMT_SINGLE, + FMT_DOUBLE: + begin + // not used anyway; not sure how to interpret endian issues + Result := Result + '-9999.99'; + end; + FMT_BINARY: + if ABufferSize = 1 then begin + tmp := CvtInt(P, 1); + if (ADecodeSTr = '') or not Decode then + Result := Result + DefIntFmt(tmp) + else + Result := Result + DecodeField(ADecodeStr, IntToStr(tmp)); + end else + Result := Result + '?'; + else + Result := Result + '?'; + end; + inc(P, len); + end; + + if AFmtStr <> '' then + begin + if Pos('%s', AFmtStr) > 0 then + Result := Format(AFmtStr, [Result], dExifFmtSettings) + else begin + dv := GetNumber(ABuffer, ABufferSize, AFmt); // wp: Will this always work? + Result := Format(AFmtStr, [dv], dExifFmtSettings); + end; + end; +end; + +function TImageInfo.GetNumber(ABuffer: PByte; ABufferSize: Integer; + AFmt:integer): Double; +var + tmp: Longint; + tmp2: Longint; +begin + Result := 0; + try + case AFmt of + FMT_SBYTE, + FMT_BYTE, + FMT_USHORT, + FMT_ULONG, + FMT_SSHORT, + FMT_SLONG: + Result := CvtInt(ABuffer, ABufferSize); + FMT_URATIONAL, + FMT_SRATIONAL: + begin + tmp := CvtInt(ABuffer, 4); + inc(ABuffer, 4); + tmp2 := CvtInt(ABuffer, 4); + Result := tmp / tmp2; + end; + FMT_SINGLE: + Result := PSingle(ABuffer)^; + FMT_DOUBLE: + Result := PDouble(ABuffer)^; + end; + except + end; +end; + +var + dirStack: String = ''; + +procedure TImageInfo.ClearDirStack; +begin + dirStack := ''; +end; + +procedure TImageInfo.PushDirStack(dirStart, offsetbase:longint); +var + ts: String; +begin + ts := '[' + IntToStr(offsetbase) + ':' + IntToStr(dirStart) + ']'; + dirStack := dirStack + ts; +end; + +function TImageInfo.TestDirStack(dirStart, offsetbase: Longint): boolean; +var + ts: String; +begin + ts := '[' + IntToStr(offsetbase) + ':' + IntToStr(dirStart) + ']'; + result := Pos(ts,dirStack) > 0; +end; + (* +//{$DEFINE CreateExifBufDebug} // uncomment to see written Exif data +{$ifdef CreateExifBufDebug}var CreateExifBufDebug : String;{$endif} + +function TImageInfo.CreateExifBuf(ParentID:word=0; OffsetBase:integer=0): AnsiString; + {offsetBase required, because the pointers of subIFD are referenced from parent IFD (WTF!!)} + // msta Creates APP1 block with IFD0 only +var + i, f, n: integer; + size, pDat, p: Cardinal; + head: ansistring; + + function Check (const t: TTagEntry; pid: word): Boolean; //inline; + var + i: integer; + begin + if (t.parentID <> pid) or (t.TType >= Length(BYTES_PER_FORMAT)) or + (BYTES_PER_FORMAT[t.TType] = 0) + then + Result := false + else begin + Result := Length(whitelist) = 0; + for i := 0 to Length(whitelist)-1 do if (whitelist[i] = t.Tag) then begin + Result := true; + break; + end; + end; + end; + + function CalcSubIFDSize(pid : integer) : integer; + var + i: integer; + begin + Result := 6; + for i := 0 to Length(fiTagArray)-1 do begin + if (not check(fiTagArray[i], pid)) then continue; + Result := Result + 12; + if (fiTagArray[i].id <> 0) then + Result := Result + calcSubIFDSize(fiTagArray[i].id) + else + if (Length(fiTagArray[i].Raw) > 4) then + Result := Result + Length(fiTagArray[i].Raw); // calc size + end; + end; + +begin + {$ifdef CreateExifBufDebug} + if (parentID = 0) then CreateExifBufDebug := ''; + {$endif} + + if (parentID = 0) then + head := #0#0 // APP1 block size (calculated later) + + 'Exif' + #$00+#$00 // Exif Header + + 'II' + #$2A+#$00 + #$08+#$00+#$00+#$00 // TIFF Header (Intel) + else + head := ''; + n := 0; + size := 0; +// for i := 0 to Length(fiTagArray)-1 do begin + for i := 0 to fiTagCount-1 do begin + if (not Check(fiTagArray[i], parentID)) then + continue; + n := n + 1; // calc number of Tags in current IFD + if (fiTagArray[i].id <> 0) then + size := size + CalcSubIFDSize(fiTagArray[i].id) + else + if (Length(fiTagArray[i].Raw) > 4) then + size := size + Length(fiTagArray[i].Raw); // calc size + end; + pDat := Length(head) + 2 + n*12 + 4; // position of data area + p := pDat; + size := size + pDat; + SetLength(Result, size); + if (parentID = 0) then begin + head[1] := ansichar(size div 256); + head[2] := ansichar(size mod 256); + move(head[1], Result[1], Length(head)); // write header + end; + PWord(@Result[1+Length(head)])^ := n; // write tag count + PCardinal(@Result[1+Length(head)+2+12*n])^ := 0; // write offset to next IFD (0, because just IFD0 is included) + n := 0; + for f := 0 to 1 do for i := 0 to Length(fiTagArray)-1 do begin // write tags + if (not check(fiTagArray[i], parentID)) then continue; + if (f = 0) and (fiTagArray[i].Tag <> TAG_EXIF_OFFSET) then + continue; // Sub-IFD must be first data block... more or less (WTF) + if (f = 1) and (fiTagArray[i].Tag = TAG_EXIF_OFFSET) then + continue; + PWord(@Result[1+Length(head)+2+12*n+0])^ := fiTagArray[i].Tag; + if (fiTagArray[i].Tag = TAG_EXIF_OFFSET) then begin + PWord(@Result[1+Length(head)+2+12*n+2])^ := 4; // Exif-Pointer is not a real data block but really a pointer (WTF) + PCardinal(@Result[1+Length(head)+2+12*n+4])^ := 1; + end + else begin + PWord(@Result[1+Length(head)+2+12*n+2])^ := fiTagArray[i].TType; + PCardinal(@Result[1+Length(head)+2+12*n+4])^ := Length(fiTagArray[i].Raw) div BYTES_PER_FORMAT[fiTagArray[i].TType]; + end; + {$ifdef CreateExifBufDebug}CreateExifBufDebug := CreateExifBufDebug + ' ' + fiTagArray[i].Name;{$endif} + if (Length(fiTagArray[i].Raw) <= 4) and (fiTagArray[i].id = 0) then begin + PCardinal(@Result[1+Length(head)+2+12*n+8])^ := 0; + if (Length(fiTagArray[i].Raw) > 0) then + move(fiTagArray[i].Raw[1], Result[1+Length(head)+2+12*n+8], Length(fiTagArray[i].Raw)); + end + else begin + PCardinal(@Result[1+Length(head)+2+12*n+8])^ := p - 8 + offsetBase; + if (fiTagArray[i].id <> 0) then begin + {$ifdef CreateExifBufDebug}CreateExifBufDebug := CreateExifBufDebug + ' { ';{$endif} + fiTagArray[i].Raw := CreateExifBuf(fiTagArray[i].id, p); // create sub IFD + fiTagArray[i].Size := Length(fiTagArray[i].Raw); + {$ifdef CreateExifBufDebug}CreateExifBufDebug := CreateExifBufDebug + ' } ';{$endif} + end; + move(fiTagArray[i].Raw[1], Result[1+p], Length(fiTagArray[i].Raw)); + p := p + Length(fiTagArray[i].Raw); + end; + n := n+1; + end; + {$ifdef CreateExifBufDebug}if (parentID = 0) then ShowMessage(CreateExifBufDebug);{$endif} +end; *) + +//-------------------------------------------------------------------------- +// Process one of the nested EXIF directories. +//-------------------------------------------------------------------------- +procedure TImageInfo.ProcessExifDir(DirStart, OffsetBase, ExifLength: longint; + ATagType: TTagType = ttExif; APrefix: string=''; AParentID: word = 0); +var + byteCount: integer; + tag, tagFormat, tagComponents: integer; + de, dirEntry, offsetVal, numDirEntries, valuePtr, subDirStart: Longint; + value: Integer; + rawStr, fStr, transStr: ansistring; + msInfo: TMsInfo; + lookupEntry, newEntry: TTagEntry; + tmpTR: ansistring; + tagID: word; +begin + PushDirStack(DirStart, OffsetBase); + numDirEntries := Get16u(DirStart); + if (ExifTrace > 0) then + TraceStr := TraceStr + crlf + + Format('Directory: Start, entries = %d, %d', [DirStart, numDirEntries]); + if (DirStart + 2 + numDirEntries*12) > (DirStart + OffsetBase + ExifLength) then + begin + TImgData(FParent).SetError('Illegally sized directory'); + exit; + end; + + // Uncomment to trace directory structure + { + Parent.ErrStr:= + Format('%d,%d,%d,%d+%s', [DirStart, numDirEntries,OffsetBase,ExifLength, parent.ErrStr]); + } + + if (ATagType = ttExif) and (FThumbStart = 0) and not TiffFmt then + begin + DirEntry := DirStart + 2 + 12*numDirEntries; + FThumbStart := Get32u(DirEntry); + FThumbLength := OffsetBase + ExifLength - FThumbStart; + end; + + for de := 0 to numDirEntries-1 do + begin + tagID := 0; + dirEntry := DirStart + 2 + 12*de; + tag := Get16u(dirEntry); + tagFormat := Get16u(dirEntry + 2); + tagComponents := Get32u(dirEntry + 4); + byteCount := tagComponents * BYTES_PER_FORMAT[tagFormat]; + if byteCount = 0 then + Continue; + if byteCount > 4 then + begin + offsetVal := Get32u(dirEntry+8); + valuePtr := OffsetBase + offsetVal; + end + else + valuePtr := dirEntry + 8; + rawStr := Copy(TImgData(FParent).EXIFsegment^.Data, valuePtr, byteCount); + + fStr := ''; + if BuildList in [GenString, GenAll] then + begin + lookUpEntry := FetchTagDefByID(tag, ATagType); + + with lookUpEntry do + begin + case tagFormat of + FMT_UNDEFINED: + fStr := '"' + StrBefore(rawStr, #0) + '"'; + FMT_STRING: + begin + fStr := Copy(TImgData(FParent).EXIFsegment^.Data, valuePtr, byteCount); + if fStr[byteCount] = #0 then + Delete(fStr, byteCount, 1); + end; + else + fStr := FormatNumber(@rawStr[1], Length(rawStr), tagFormat, FormatS, Code); + end; + if ((tag > 0) or (lookupEntry.Name <> 'Unknown')) and Assigned(Callback) and Decode then + fStr := Callback(fStr) + else + fStr := MakePrintable(fStr); + transStr := Desc; + end; + + case tag of + TAG_USERCOMMENT: + // strip off comment header + fStr := trim(Copy(rawStr, 9, byteCount-8)); + TAG_DATETIME_MODIFY, + TAG_DATETIME_ORIGINAL, + TAG_DATETIME_DIGITIZED: + fStr := FormatDateTime(TImgData(FParent).DateTimeFormat, ExifDateToDateTime(fStr)); + end; + + // Update trace strings + tmpTR := crlf + + siif(ExifTrace > 0, 'tag[$' + IntToHex(tag,4) + ']: ', '') + + transStr + dExifDelim + fStr + + siif(ExifTrace > 0, ' [size: ' + IntToStr(byteCount) + ']', '') + + siif(ExifTrace > 0, ' [start: ' + IntToStr(valuePtr) + ']', ''); + + if ATagType = ttThumb then + Thumbtrace := ThumbTrace + tmpTR + else + TraceStr := TraceStr + tmpTR; + end; + + // Additional processing done here: + case tag of + TAG_SUBIFD_OFFSET, + TAG_EXIF_OFFSET, + TAG_INTEROP_OFFSET: + begin + try + value := Get32u(valuePtr); + subdirStart := OffsetBase + LongInt(value); + // some mal-formed images have recursive references... + // if (subDirStart <> DirStart) then + if not TestDirStack(subDirStart, OffsetBase) then begin + tagID := tag; + ProcessExifDir(subdirStart, OffsetBase, ExifLength, ttExif, '', tagID); + end; + except + end; + end; + TAG_GPS_OFFSET: + begin + try + subdirStart := OffsetBase + LongInt(Get32u(ValuePtr)); + if not TestDirStack(subDirStart, OffsetBase) then begin + tagID := tag; + ProcessExifDir(subdirStart, OffsetBase, ExifLength, ttGps, '', tagID); + end; + except + end; + end; + + TAG_EXIFVERSION: + FExifVersion := rawstr; + + TAG_MAKERNOTE: + begin + MakerNote := rawStr; + MakerOffset := valuePtr; + msInfo := TMsInfo.Create(TiffFmt, self); + msAvailable := msInfo.ReadMSData(self); + FreeAndNil(msInfo); + end; + TAG_FLASH: + FlashUsed := round(getNumber(@rawStr[1], Length(rawSTr), tagFormat)); + (* + TAG_IMAGELENGTH, + TAG_EXIF_IMAGELENGTH: + begin + HPosn := DirEntry + 8; + Height := round(GetNumber(rawStr, tagFormat)); + end; + TAG_IMAGEWIDTH, + TAG_EXIF_IMAGEWIDTH: + begin + WPosn := DirEntry + 8; + Width := round(GetNumber(rawStr, tagFormat)); + end; + *) + TAG_THUMBSTARTOFFSET: + FThumbnailStartOffset := Get32u(ValuePtr); + TAG_THUMBSIZE: + FThumbnailSize := Get32u(ValuePtr); + TAG_COMPRESSION: + if ATagType = ttThumb then + FThumbType := round(GetNumber(@rawStr[1], Length(rawStr), tagFormat)); + end; + + if BuildList in [GenList,GenAll] then + begin + try + NewEntry := LookupEntry; + NewEntry.Data := fStr; + NewEntry.Raw := rawStr; + NewEntry.Size := Length(rawStr); + NewEntry.TType := tagFormat; + NewEntry.Count := tagComponents; + NewEntry.ParentID := AParentID; + NewEntry.TID := GenericEXIF; // 0 + if ATagType = ttThumb then + AddTagToThumbArray(newEntry) + else + AddTagToArray(newEntry); + except + // if we're here: unknown tag. + // item is recorded in trace string + end; + end; + end; + + if (ATagType = ttExif) and + ((TImgData(FParent).ErrStr = '') or (TImgData(FParent).ErrStr = NO_ERROR)) + then + Calc35Equiv(); +end; + +procedure TImageInfo.ProcessHWSpecific(AMakerBuff: ansistring; + TagTbl: array of TTagEntry; ADirStart, AMakerOffset: Longint; + spOffset: Integer = 0); +var + NumDirEntries: integer; + de, ByteCount, tagID: integer; + DirEntry, tag, tagFormat, tagComponents: integer; + OffsetVal, ValuePtr: Longint; + rawStr: ansistring; + tagStr: String; + fStr, fStr2, ds: ansistring; + OffsetBase: longint; + NewEntry: TTagEntry; +begin + ADirStart := ADirStart+1; + OffsetBase := ADirStart - AMakerOffset + 1; + SetDataBuff(AMakerBuff); + try + NumDirEntries := Get16u(ADirStart); + for de := 0 to NumDirEntries-1 do + begin + DirEntry := ADirStart + 2 + 12*de; + tag := Get16u(DirEntry); + tagFormat := Get16u(DirEntry+2); + tagComponents := Get32u(DirEntry+4); + ByteCount := tagComponents * BYTES_PER_FORMAT[tagFormat]; + OffsetVal := 0; + if ByteCount > 4 then + begin + OffsetVal := Get32u(DirEntry + 8); + ValuePtr := OffsetBase + OffsetVal; + end + else + ValuePtr := DirEntry + 8; + + // Adjustment needed by Olympus Cameras + if ValuePtr + ByteCount > Length(AMakerBuff) then + rawStr := Copy(TImgData(FParent).DataBuff, OffsetVal + spOffset, ByteCount) + else + rawStr := copy(AMakerBuff, ValuePtr, ByteCount); + + tagID := LookupMTagID(tag, TagTbl); + if tagID < 0 then + tagStr := 'Unknown' + else + tagStr := TagTbl[tagID].Desc; + + fstr := ''; + if UpperCase(tagStr) = 'SKIP' then + continue; + + if BuildList in [GenList, GenAll] then + begin + case tagFormat of + FMT_STRING: + fStr := '"' + StrBefore(rawStr, #0) + '"'; + FMT_UNDEFINED: + fStr := '"' + rawStr + '"'; + else + try + ds := siif(Decode, LookupCode(tag, TagTbl), ''); + if tagID < 0 then + fStr := FormatNumber(@rawStr[1], Length(rawStr), tagFormat, '', '') + else + fStr := FormatNumber(@rawStr[1], Length(rawStr), tagFormat, TagTbl[tagID].FormatS, ds); + except + fStr := '"' + rawStr + '"'; + end; + end; + + rawDefered := false; + if (tagID > 0) and Assigned(TagTbl[tagID].CallBack) and Decode then + fstr2 := TagTbl[tagID].CallBack(fstr) + else + fstr2 := MakePrintable(fstr); + + if (ExifTrace > 0) then + begin + if not rawDefered then + msTraceStr := msTraceStr + crlf + + 'tag[$' + IntToHex(tag, 4) + ']: ' + + TagStr + dExifDelim + fstr2 + + ' [size: ' + IntToStr(ByteCount) + ']' + + ' [raw: ' + MakeHex(rawStr) + ']' + + ' [start: ' + IntToStr(ValuePtr) + ']' + else + msTraceStr := msTraceStr + crlf + + 'tag[$' + IntToHex(tag, 4) + ']: '+ + TagStr + dExifDelim + + ' [size: ' + IntToStr(ByteCount) + ']' + + ' [raw: ' + MakeHex(RawStr) + ']' + + ' [start: '+ IntToStr(ValuePtr) + ']' + + fstr2; + end else + begin + if not rawDefered then + msTraceStr := msTraceStr + crlf + + tagStr + dExifDelim + fstr2 + else + msTraceStr := msTraceStr + + fstr2 + // has cr/lf as first element + crlf + TagStr + dExifDelim + fstr; + end; + end; + + if (BuildList in [GenList, GenAll]) and (tagID > 0) then + begin + try + NewEntry := TagTbl[tagID]; + + if rawdefered then + NewEntry.Data := fStr + else + NewEntry.Data := fStr2; + + NewEntry.Raw := rawStr; + NewEntry.TType := tagFormat; + NewEntry.Count := tagComponents; + NewEntry.TID := CustomEXIF; // = 1 --> Manufacturer-specific + + AddTagToArray(NewEntry); + except + // if we're here: unknown tag. + // item is recorded in trace string + end; + end; + + end; + + except + on E: Exception do + TImgData(FParent).SetError('Error detected: ' + E.Message); + end; + + SetDataBuff(TImgData(FParent).DataBuff); +end; + +procedure TImageInfo.AddMSTag(ATagName: String; ARawStr: ansistring; AType: word); +var + newEntry: TTagEntry; +begin + if BuildList in [GenList,GenAll] then + begin + try + InitTagEntry(newEntry); + newEntry.Name := ATagName; + newEntry.Desc := InsertSpaces(ATagName); + newEntry.Data := ARawStr; + newEntry.Raw := ARawStr; + newEntry.Size := Length(ARawStr); + NewEntry.TType:= AType; + NewEntry.Count := 1; + newEntry.ParentID := 0; + newEntry.TID := CustomEXIF; // = 1 --> manufacturer-specific + AddTagToArray(newEntry); + except + // if we're here: unknown tag. + // item is recorded in trace string + end; + end; +end; + +{ Creates a thumbnail image from the main image loaded. The size of the thumbnail + (width or height whichever is longer) is specified as AThumbnailSize. + The current thumbnail image is replaced by the new one, or, if the image did + not have a thumbnail image so far it is added to the image. } +procedure TImageInfo.CreateThumbnail(AThumbnailSize: Integer = DEFAULT_THUMBNAIL_SIZE); +var + srcStream, destStream: TMemoryStream; +begin + srcStream := TMemoryStream.Create; + destStream := TMemoryStream.Create; + try + srcStream.LoadFromFile(TImgData(FParent).FileName); + JpegScaleImage(srcStream, destStream, AThumbnailSize); + destStream.Position := 0; + LoadThumbnailFromStream(destStream); + finally + destStream.Free; + srcStream.Free; + end; +end; + +function TImageInfo.HasThumbnail: boolean; +begin + Result := Length(FThumbnailBuffer) > 0; +end; + (* +procedure TImageInfo.ProcessThumbnail; +var + start: Integer; +begin + exit; + + + FiThumbCount := 0; + start := FThumbStart + 9; + ProcessExifDir(start, 9, FThumbLength - 12, ttThumb, 'Thumbnail', 1); + ExtractThumbnail; +end; + +procedure TImageInfo.ExtractThumbnail; +begin + if FThumbnailStartOffset > 0 then begin + SetLength(FThumbnailBuffer, FThumbnailSize); + Move(TImgData(FParent).ExifSegment^.Data[FThumbnailStartOffset + 9], FThumbnailBuffer[0], FThumbnailSize); + end else + FThumbnailBuffer := nil; +end; + *) +procedure TImageInfo.LoadThumbnailFromStream(AStream: TStream); +var + n: Integer; + w, h: Integer; +begin + RemoveThumbnail; + + // Check whether the image is a jpeg, and extract size of the thrumbnail image + if not JPEGImageSize(AStream, w, h) then + exit; + + // Write the image from the stream into the thumbnail buffer + n := AStream.Size; + if n > 65000 then // limit probably still too high, thumbnail must fit into a 64k segment along with all other tags... + raise Exception.Create('Thumbnail too large.'); + + SetLength(FThumbnailBuffer, n); + if AStream.Read(FThumbnailBuffer[0], n) < n then + raise Exception.Create('Could not read thumbnail image.'); + + // Make sure that the IFD1 tags for the thumbnail are correct + SetThumbTagValue('Compression', 6); // 6 = JPEG - this was checked above. + SetThumbTagValue('ImageWidth', w); + SetThumbTagValue('ImageLength', h); + SetThumbTagValue('JPEGInterchangeFormat', 0); // to be replaced by the offset to the thumbnail + SetThumbTagValue('JPEGInterchangeFormatLength', n); +end; + +procedure TImageInfo.RemoveThumbnail; +var + newSize: integer; +begin + SetLength(FThumbnailBuffer, 0); + fiThumbCount := 0; + + if FThumbStart > 1 then begin + newSize := FThumbStart - 6; + with TImgData(FParent) do + begin + SetLength(ExifSegment^.Data, newSize); + ExifSegment^.Size := newSize; + // size calculations should really be moved to save routine + ExifSegment^.data[1] := ansichar(newSize div 256); + ExifSegment^.data[2] := ansichar(newSize mod 256); + end; + + FThumbStart := 0; + end; +end; + +procedure TImageInfo.SaveThumbnailToStream(AStream: TStream); +var + n: Int64; +begin + if HasThumbnail then + begin + n := Length(FThumbnailBuffer); + if AStream.Write(FThumbnailBuffer[0], n) <> n then + raise Exception.Create('Cannot write Thumbnail image to stream.'); + end; +end; + +function TImageInfo.ToLongString(ALabelWidth: Integer = 15): String; +var + tmpStr: String; + FileDateTime: String; + L: TStringList; + W: Integer; + lParent: TImgData; +begin + lParent := TImgData(FParent); + W := ALabelWidth; + L := TStringList.Create; + try + (* + if parent.ExifSegment = nil then + Result := '' + else + *) + if lParent.ErrStr <> NO_ERROR then + begin + L.Add(Format('File Name: %s', [ExtractFileName(lParent.Filename)])); + L.Add(Format('Exif Error: %s', [lParent.ErrStr])); + Result := L.Text; + end else + begin + FileDateTime := FormatDateTime(lParent.DateTimeFormat, lParent.FileDateTime); + + L.Add(Format('%-*s %s', [w, 'File name:', ExtractFileName(lParent.Filename)])); + L.Add(Format('%-*s %dkB', [w, 'File size:', lParent.FileSize div 1024])); + L.Add(Format('%-*s %s', [w, 'File date:', FileDateTime])); + L.Add(Format('%-*s %s', [w, 'Photo date:', FormatDateTime(lParent.DateTimeFormat, GetImgDateTime)])); + L.Add(Format('%-*s %s (%s)', [w, 'Make (model):', CameraMake, CameraModel])); + L.Add(Format('%-*s %d x %d', [w, 'Dimensions:', Width, Height])); + + if BuildList in [GenString,GenAll] then + begin + tmpStr := TagValueAsString['ExposureTime']; + if tmpStr <> '' then + L.Add(Format('%-*s %s', [w, 'Exposure time:', tmpStr])) + else + begin + tmpStr := TagValueAsstring['ShutterSpeedValue']; + if tmpStr <> '' then + L.Add(Format('%-*s %s', [w, 'Exposure time:', tmpStr])); + end; + + tmpStr := TagValueAsString['FocalLength']; + if tmpStr <> '' then + L.Add(Format('%-*s %s', [w, 'Focal length:', tmpStr])); + + tmpStr := TagValueAsString['FocalLengthIn35mm']; + if tmpStr <> '' then + L.Add(Format('%-*s %s', [w, 'Focal length (35mm):', tmpStr])); + + tmpStr := TagValueAsString['FNumber']; + if tmpStr <> '' then + L.Add(Format('%-*s %s', [w, 'F number', tmpStr])); + + tmpStr := TagValueAsString['ISOSpeedRatings']; + if tmpStr <> '' then + L.Add(Format('%-*s %s', [w, 'ISO:', tmpStr])); + end; + + L.Add(Format('%-*s %s', [w, 'Flash fired:', siif(odd(FlashUsed),'Yes','No')])); + Result := L.Text; + end; + finally + L.Free; + end; +end; + +function TImageInfo.ToShortString: String; +var + lParent: TImgData; +begin + lParent := TImgData(FParent); + if lParent.ErrStr <> NO_ERROR then + Result := ExtractFileName(lParent.Filename) + ' Exif Error: ' + lParent.ErrStr + else + Result := ExtractFileName(lParent.Filename) + ' ' + + IntToStr(lParent.FileSize div 1024) + 'kB '+ + FormatDateTime(lParent.DateTimeFormat, GetImgDateTime) + ' ' + + IntToStr(Width) + 'w ' + IntToStr(Height) + 'h '+ + siif(odd(FlashUsed),' Flash', ''); +end; + +procedure TImageInfo.AdjExifSize(AHeight, AWidth: Integer); +begin + TagValue['ImageWidth'] := AWidth; + TagValue['ImageLength'] := AHeight; +end; + +procedure TImageInfo.InternalGetBinaryTagValue(const ATag: TTagEntry; + var ABuffer: ansistring); +begin + ABuffer := ''; + + if ATag.Tag = 0 then + exit; + + if ATag.TType = FMT_BINARY then begin + SetLength(ABuffer, Length(ATag.Raw)); + Move(ATag.Raw[1], ABuffer[1], Length(ATag.Raw)); + end; +end; + +function TImageInfo.InternalGetTagValue(const ATag: TTagEntry): Variant; +var + s: String; + r: TExifRational; + i: Integer; + intValue: Integer; + floatValue: Extended; +begin + Result := Null; + if ATag.Tag = 0 then + exit; + + // Handle strings + case ATag.TType of + FMT_STRING: + begin + {$IFDEF FPC} + {$IFDEF FPC3+} + s := ATag.Raw; + {$ELSE} + s := AnsiToUTF8(ATag.Raw); + {$ENDIF} + {$ELSE} + s := ATag.Raw; + {$ENDIF} + while (s <> '') and (s[Length(s)] = #0) do + Delete(s, Length(s), 1); + Result := s; + exit; + end; + FMT_BINARY: + begin + Result := BinaryTagToVar(ATag); + exit; + end; + end; + + // Handle numeric data. Be aware that they may be arrays + if ATag.Count = 1 then +// Result := NumericTagToInt(@ATag.Raw[1], ATag.TType) + Result := NumericTagToVar(@ATag.Raw[1], ATag.TType) + else begin + case ATag.TType of + FMT_BYTE, FMT_USHORT, FMT_ULONG: + Result := VarArrayCreate([0, ATag.Count-1], varInteger); + FMT_URATIONAL, FMT_SRATIONAL: + Result := VarArrayCreate([0, ATag.Count-1], varDouble); + end; + for i:=0 to ATag.Count-1 do + Result[i] := NumericTagToVar(@ATag.Raw[1 + BYTES_PER_FORMAT[ATag.TType]*i], ATag.TType); + end; + + // Correction for some special cases + case ATag.Tag of + TAG_SHUTTERSPEED: + // Is stored as -log2 of exposure time + Result := power(2.0, -Result); + end; +end; + +function TImageInfo.BinaryTagToStr(const ATag: TTagEntry): String; +begin + Result := ATag.Raw; +end; + +function TImageInfo.BinaryTagToVar(const ATag: TTagEntry): Variant; +var + s: String; +begin + case ATag.Tag of + TAG_EXIFVERSION, + TAG_FLASHPIXVERSION, + TAG_INTEROPVERSION: + begin + SetLength(s, Length(ATag.Raw)); + Move(ATag.Raw[1], s[1], Length(s)); + Result := s; + end; + TAG_USERCOMMENT: + begin + Result := GetExifComment; + end; + else + Result := ''; + end; +end; + +{ ABuffer points into the raw buffer of a tag. The number pointed to will be + converted to a numeric value; its type depends on ATagType. } +function TImageInfo.NumericTagToVar(ABuffer: Pointer; ATagType: Integer): Variant; +var + r: TExifRational; +begin + case ATagType of + FMT_BYTE: + Result := PByte(ABuffer)^; + FMT_USHORT: + if MotorolaOrder then + Result := BEToN(PWord(ABuffer)^) else + Result := LEToN(PWord(ABuffer)^); + FMT_ULONG: + if MotorolaOrder then + Result := BEToN(PDWord(ABuffer)^) else + Result := LEToN(PDWord(ABuffer)^); + FMT_URATIONAL, + FMT_SRATIONAL: + begin + r := PExifRational(ABuffer)^; + if MotorolaOrder then begin + r.Numerator := LongInt(BEToN(DWord(r.Numerator))); // Type cast needed for D7 + r.Denominator := LongInt(BEToN(DWord(r.Denominator))); + end else begin + r.Numerator := LongInt(LEToN(DWord(r.Numerator))); + r.Denominator := LongInt(LEtoN(DWord(r.Denominator))); + end; + if ATagType = FMT_SRATIONAL then begin + r.Numerator := LongInt(r.Numerator); + r.Denominator := LongInt(r.Denominator); + end; + Result := Extended(r.Numerator / r.Denominator); + end; + { + FMT_BINARY: + if ATag.Size = 1 then + Result := PByte(@ATag.Raw[1])^ + else + Result := ''; + } + else + raise Exception.CreateFmt('NumericTagToVar does not handle Tag type %d', [ord(ATagType)]); + end; +end; + +{ Central routine for writing data to a tag. + ATagName ........... Name of the tag + AValue ............. Value to be written to the tag if the tag is not binary + ABinaryData ........ Data to be written to the tag if it is binary + ABinaryDataCount ... Number of bytes to be written to a binary tag. + ATagTypes .......... Determines in which list the tag definition is found + (Exif&Thumb, or GPS), and which list will get the new tag + (Exif&GPS, or thumb } +procedure TImageInfo.InternalSetTagValue(const ATagName: String; AValue: Variant; + ATagTypes: TTagTypes; ABinaryData: Pointer = nil; ABinaryDataCount: Word = 0); +const + IGNORE_PARENT = $FFFF; +var + P: PTagEntry; + tagDef: PTagEntry; + tagID: Word; + parentID: Word; + strValue: String; + i: Integer; +begin + // Find the tag's ID from the lists of tag definitions. + // Note: Normal ("Exif") and thumbnail tags share the same list, gps tags + // are separate. + if (ATagTypes * [ttExif, ttThumb] <> []) then + tagDef := FindExifTagDefByName(ATagName) else + tagDef := nil; + if (tagDef = nil) and (ttGps in ATagTypes) then + tagDef := FindGpsTagDefByName(ATagName); + if tagDef = nil then + raise Exception.CreateFmt('Tag "%s" not found.', [ATagName]); + tagID := tagDef.Tag; + + // Delete this tag if the provided value is varNull or varEmpty + if tagDef.TType = FMT_BINARY then begin + if ABinaryData = nil then begin + RemoveTag(ATagTypes, tagID, tagDef^.ParentID); + exit; + end; + end else begin + if VarIsNull(AValue) or VarIsEmpty(AValue) then begin + RemoveTag(ATagTypes, tagID, tagDef^.ParentID); + exit; + end; + end; + + // Find the pointer to the tag + P := FindTagPtr(tagDef^, (ttThumb in ATagTypes)); +// P := GetTagPtr(ATagTypes, tagID, false, IGNORE_PARENT); + if P = nil then begin + // The tag does not yet exist --> create a new one. + // BUT: The TagTable does not show the ParentIDs... + // Until somebody updates this we put the new tag into the root directory + // (IFD0). Since this may not be allowed there's a risk that the EXIF in the + // modified file cannot be read correctly... + { + if(ttGps in ATagTypes) then + parentID := TAG_GPS_OFFSET + else + parentID := 0; + } + P := CreateTagPtr(tagDef^, (ttThumb in ATagTypes), tagDef^.ParentID); + end; + if P = nil then + raise Exception.CreateFmt('Failure to create tag "%s"', [ATagName]); + + // Handle string data + if P^.TType = FMT_STRING then begin + strValue := VarToStr(AValue); + {$IFDEF FPC} + P^.Raw := UTF8ToAnsi(strValue) + #0; + {$ELSE} + P^.Raw := AnsiString(strValue) + #0; + {$ENDIF} + p^.Size := Length(p^.Raw); + P^.Data := P^.Raw; + exit; + end; + + // Handle binary data + if P^.TType = FMT_BINARY then begin + SetLength(P^.Raw, ABinaryDataCount); + Move(ABinaryData^, P^.Raw[1], ABinaryDataCount); + P^.Size := ABinaryDataCount; + P^.Data := ''; + exit; + end; + + // NOTE: Since hardware-specific data are not yet decoded the element Raw + // is still in the endianness of the source! + + // Handle some special cases + case tagID of + TAG_SHUTTERSPEED: + begin + strValue := VarToStr(AValue); + if pos('/', strValue) > 0 then + AValue := CvtRational(ansistring(strValue)); + // The shutter speed value is stored as -log2 of exposure time + AValue := -log2(AValue); + end; + TAG_EXPOSURETIME: + begin + strValue := VarToStr(AValue); + if pos('/', strValue) > 0 then + AValue := CvtRational(ansistring(strValue)); + end; + end; + + p^.Raw := ''; + p^.Data := ''; + p^.Size := 0; + if VarIsArray(AValue) then + for i:=VarArrayLowBound(AValue, 1) to VarArrayHighBound(AValue, 1) do + VarToNumericTag(AValue[i], p) + else + VarToNumericTag(AValue, p); +end; + +procedure TImageInfo.VarToNumericTag(AValue:variant; ATag: PTagEntry); +var + intValue: Integer; + fracvalue: TExifRational; + len: Integer; + s: String; + w: Word; + dw: DWord; + ok: Boolean; +begin + if VarIsArray(AValue) then + raise Exception.Create('No variant arrays allowed in VarToTag'); + + // fractional data + if (ATag^.TType in [FMT_URATIONAL, FMT_SRATIONAL]) then + begin + fracvalue := DoubleToRational(AValue); + if MotorolaOrder then begin + fracvalue.Numerator := LongInt(NToBE(DWord(fracValue.Numerator))); // Type-cast needed for D7 + fracValue.Denominator := LongInt(NToBE(DWord(fracValue.Denominator))); + end else begin + fracValue.Numerator := LongInt(NtoLE(DWord(fracValue.Numerator))); + fracValue.Denominator := LongInt(NtoLE(DWord(fracValue.Denominator))); + end; + len := Length(ATag^.Raw); + SetLength(ATag^.Raw, len + 8); + Move(fracValue, ATag^.Raw[len + 1], 8); + ATag^.Size := Length(ATag^.Raw); + s := FormatNumber(@ATag^.Raw[1], Length(ATag^.Raw), ATag^.TType, ATag^.FormatS, ATag^.Code); + { + if Assigned(ATag.Callback) and Parent.Decode then + s := ATag.Callback(s); + } + ATag^.Data := s; //siif(len = 0, s, ATag^.Data + dExifDataSep + s); + exit; + end; + + // integer data + if VarIsType(AValue, vtInteger) then begin + case ATag^.TType of + FMT_BYTE : ok := (AValue >= 0) and (AValue <= 255); + FMT_USHORT : ok := (AValue >= 0) and (AValue <= Word($FFFF)); + FMT_ULONG : ok := (AValue >= 0) and (AValue <= DWord($FFFFFFFF)); + FMT_SBYTE : ok := (AValue >= -128) and (AValue <= 127); + FMT_SSHORT : ok := (AValue >= -32768) and (AValue <= 32767); + FMT_SLONG : ok := (AValue >= -2147483647) and (AValue <= 2147483647); + { NOTE: D7 does not run with the correct lower limit -2147483648 } + end; + if not ok then + raise Exception.CreateFmt('Tag "%s": Value "%s" is out of range.', [ATag^.Name, VarToStr(AValue)]); + end; + + if not TryStrToInt(VarToStr(AValue), intValue) then begin + intValue := GetTagCode(ATag^, VarToStr(AValue)); + if (intValue = -1) then + raise Exception.CreateFmt('Lookup value "%s" of tag "%s" not found', [VarToStr(AValue), ATag^.Name]); + end; + + len := Length(ATag^.Raw); + SetLength(ATag^.Raw, len + BYTES_PER_FORMAT[ATag^.TType]); + case ATag^.TType of + FMT_BYTE: + Move(intValue, ATag^.Raw[1+len], 1); + FMT_USHORT: + begin + if MotorolaOrder then w := NtoBE(word(intValue)) else w := NtoLE(word(intvalue)); + Move(w, ATag^.Raw[1+len], 2); + end; + FMT_ULONG: + begin + if MotorolaOrder then + dw := NtoBE(DWord(intValue)) else + dw := NtoLE(DWord(intValue)); + Move(dw, ATag^.Raw[1+len], 4); + end; + else + raise Exception.Create('Unhandled data format in VarToNumericTag'); + end; + ATag^.Size := Length(ATag^.Raw); + s := FormatNumber(@ATag^.Raw[1], Length(ATag^.Raw), ATag^.TType, ATag^.FormatS, ATag^.Code); + ATag^.Data := siif(len = 0, s, ATag^.Data + dExifDataSep + s); +end; + +function TImageInfo.GetTagByID(ATagID: Word): TTagEntry; +var + i: Integer; +begin + for i:= 0 to fiTagCount - 1 do + if (fiTagArray[i].Tag = ATagID) and (fiTagArray[i].TID = GenericEXIF) then + begin + Result := fiTagArray[i]; + exit; + end; + Result := EmptyEntry; +end; + +procedure TImageInfo.SetTagByID(ATagID: Word; const AValue: TTagEntry); +var + i: Integer; + P: PTagEntry; +begin + for i:=0 to fiTagCount-1 do + if (fITagArray[i].Tag = ATagID) and (fiTagArray[i].TID = GenericEXIF) then + begin + fITagArray[i] := AValue; + exit; + end; + + // If not found: add it as a new tag to the array + P := FindExifTagDefByID(ATagID); + if P = nil then begin + P := FindGpsTagDefByID(ATagID); + if P = nil then + raise Exception.CreateFmt('TagID $%.4x unknown.', [ATagID]); + end; + AddTagToArray(AValue); +end; + +function TImageInfo.GetTagByIndex(AIndex: Integer): TTagEntry; +begin + Result := fiTagArray[AIndex]; +end; + +procedure TImageInfo.SetTagByIndex(AIndex: Integer; const AValue: TTagEntry); +begin + FITagArray[AIndex] := AValue; +end; + + +function TImageInfo.GetTagByName(ATagName: String): TTagEntry; +var + i: integer; +begin + i := LookupTagIndex(ATagName); + if i >= 0 then + Result := fITagArray[i] + else + Result := EmptyEntry; +end; + +procedure TImageInfo.SetTagByName(ATagName: String; const AValue: TTagEntry); +var + i: integer; + P: PTagEntry; +begin + i := LookupTagIndex(ATagName); + if i >= 0 then + fITagArray[i] := AValue + else + begin + // If not found: add it as a new tag to the array + P := FindExifTagDefByName(ATagName); + if P = nil then begin + P := FindGpsTagDefByName(ATagName); + if P = nil then + raise Exception.Create('Tag "' + ATagName + '" unknown.'); + end; + AddTagToArray(AValue); + end; +end; + +function TImageInfo.GetTagValue(ATagName: String): Variant; +var + tag: TTagEntry; +begin + Result := Null; + tag := GetTagByName(ATagName); + if (tag.Name = '') or (tag.Name = 'Unknown') then + exit; + Result := InternalGetTagValue(tag); +end; + +procedure TImageInfo.SetTagValue(ATagName: String; AValue: Variant); +begin + InternalSetTagValue(ATagName, AValue, [ttExif, ttGps]); +end; + +function TImageInfo.GetTagValueAsString(ATagName: String): String; +var + tag: TTagEntry; +begin + Result := ''; + tag := GetTagByName(ATagName); + if (tag.Name = '') or (tag.Name = 'Unknown') then + exit; + Result := InternalGetTagValueAsString(tag); +end; + +function TImageInfo.InternalGetTagValueAsString(const ATag: TTagEntry): String; +var + s: String; +begin + if ATag.TType = FMT_STRING then + begin + {$IFDEF FPC} + {$IFDEF FPC3+} + s := ATag.Raw; + {$ELSE} + s := AnsiToUTF8(ATag.Raw); + {$ENDIF} + {$ELSE} + s := ATag.Raw; + {$ENDIF} + while (s <> '') and ((s[Length(s)] = #0) or (s[Length(s)] = ' ')) do + Delete(s, Length(s), 1); + Result := s; + end else + if ATag.TType = FMT_BINARY then + begin + if (ATag.Size=1) then begin + Result := FormatNumber(@ATag.Raw[1], Length(ATag.Raw), ATag.TType, ATag.FormatS, ATag.Code); + if Assigned(ATag.Callback) and Decode then + Result := ATag.Callback(Result); + end else + if ATag.Name = 'ExifVersion' then + Result := GetVersion(ATag) + else if ATag.Name = 'FlashPixVersion' then + Result := GetVersion(ATag) + else if ATag.Name = 'InteroperabilityVersion' then + Result := GetVersion(ATag) + else if ATag.Name = 'UserComment' then + Result := GetExifComment + else begin + Result := BinaryTagToStr(ATag); + if Assigned(ATag.Callback) and Decode then + Result := ATag.Callback(Result); + end; + end else + begin + Result := FormatNumber(@ATag.Raw[1], Length(ATag.Raw), ATag.TType, ATag.FormatS, ATag.Code); + if Assigned(ATag.Callback) and Decode then + Result := ATag.Callback(Result) + end; +end; + +procedure TImageInfo.SetTagValueAsString(ATagName: String; AValue: String); +var + v: Variant; +begin + v := AValue; + SetTagValue(ATagName, v); +end; + +function TImageInfo.GetThumbTagByID(ATagID: Word): TTagEntry; +var + i: Integer; +begin + for i:= 0 to fiThumbCount - 1 do + if (fiThumbArray[i].Tag = ATagID) then + begin + Result := fiThumbArray[i]; + exit; + end; + Result := EmptyEntry; +end; + +procedure TImageInfo.SetThumbTagByID(ATagID: Word; const AValue: TTagEntry); +var + i: Integer; + P: PTagEntry; +begin + for i:=0 to fiThumbCount-1 do + if fIThumbArray[i].Tag = ATagID then begin + fIThumbArray[i] := AValue; + exit; + end; + + // If not found: add it as a new tag to the array + P := FindExifTagDefByID(ATagID); // Thumb tags are stored in Exif table + if P = nil then + raise Exception.CreateFmt('TagID $%.4x unknown.', [ATagID]); + AddTagToThumbArray(AValue); +end; + +function TImageInfo.GetThumbTagByIndex(AIndex: Integer): TTagEntry; +begin + Result := fiThumbArray[AIndex]; +end; + +procedure TImageInfo.SetThumbTagByIndex(AIndex: Integer; const AValue: TTagEntry); +begin + fiThumbArray[AIndex] := AValue; +end; + +function TImageInfo.GetThumbTagByName(ATagName: String): TTagEntry; +var + i: integer; +begin + ATagName := Uppercase(ATagName); + for i:= 0 to fiThumbCount - 1 do + if Uppercase(fiThumbArray[i].Name) = ATagName then begin + Result := fiThumbArray[i]; + exit; + end; + Result := EmptyEntry; +end; + +procedure TImageInfo.SetThumbTagByName(ATagName: String; const AValue: TTagEntry); +var + i: Integer; + P: PTagEntry; +begin + ATagName := Uppercase(ATagName); + for i:=0 to fiThumbCount-1 do + if Uppercase(fIThumbArray[i].Name) = ATagName then begin + fIThumbArray[i] := AValue; + exit; + end; + { + // If not found: add it as a new tag to the array + P := FindExifTagDefByName(ATagName); // Thumb tags are stored in Exif table + if P = nil then + raise Exception.Create('Tag "' + ATagName + '" unknown.'); + AddTagToThumbArray(AValue); + } +end; + +function TImageInfo.GetThumbTagValue(ATagName: String): Variant; +var + tag: TTagEntry; +begin + tag := GetThumbTagByName(ATagName); + Result := InternalGetTagValue(tag); +end; + +procedure TImageInfo.SetThumbTagValue(ATagName: String; AValue: Variant); +begin + InternalSetTagValue(ATagName, AValue, [ttThumb]); +end; + +function TImageInfo.GetThumbTagValueAsString(ATagName: String): String; +var + tag: TTagEntry; +begin + Result := ''; + tag := GetThumbTagByName(ATagName); + if (tag.Name = '') or (tag.Name = 'Unknown') then + exit; + Result := InternalGetTagValueAsString(tag); +end; + +procedure TImageInfo.SetThumbTagValueAsString(ATagName: String; AValue: String); +var + v: Variant; +begin + v := AValue; + SetThumbTagValue(ATagName, v); +end; + +function TImageInfo.GetWidth: Integer; +var + v: Variant; +begin + Result := 0; + v := TagValue['ImageWidth']; + if VarIsNull(v) then begin + v := TagValue['ExifImageWidth']; + if VarIsNull(v) then + exit; + end; + Result := v; +end; + +procedure TImageInfo.SetWidth(AValue: Integer); +begin + TagValue['ImageWidth'] := AValue; +end; + +function TImageInfo.GetHeight: Integer; +var + v: Variant; +begin + Result := 0; + v := TagValue['ImageLength']; + if VarIsNull(v) then begin + v := TagValue['ExifImageLength']; + if VarIsNull(v) then + exit; + end; + Result := v; +end; + +procedure TImageInfo.SetHeight(AValue: Integer); +begin + TagValue['ImageLength'] := AValue; +end; + +procedure TImageInfo.RemoveTag(ATagTypes: TTagTypes; ATagID: Word; AParentID: Word=0); +var + i: Integer; +begin + i := 0; + if ttThumb in ATagTypes then + begin + while i < fiThumbCount do + begin + if (fiThumbArray[i].Tag = ATagID) and (fiThumbArray[i].ParentID = AParentID) then + begin + while (i < fiThumbCount-1) do begin + fiThumbArray[i] := fiThumbArray[i+1]; + inc(i); + end; + dec(fiThumbCount); + break; + end else + inc(i); + end; + end else + begin + while i < fiTagCount do + begin + if (fiTagArray[i].Tag = ATagID) and (fiTagArray[i].ParentID = AParentID) then + begin + while (i < fiTagCount-1) do begin + fiTagArray[i] := fiTagArray[i+1]; + inc(i); + end; + dec(fiTagCount); + break; + end else + inc(i); + end; + end; +end; + (* +procedure TImageInfo.RemoveTag(ATagTypes: TTagTypes; ATagID: Word; AParentID: Word=0); +var + i, j: integer; +begin + j := 0; + if ttThumb in ATagTypes then begin + for i := 0 to fiThumbCount-1 do begin + if (j <> 0) then + fiThumbArray[i-j] := fiThumbArray[i]; + if (fiThumbArray[i].ParentID = AParentID) and (fiThumbArray[i].Tag = ATagID) then + inc(j); + end; + if (j <> 0) and (fiThumbCount > 0) then + dec(fiThumbCount); + end else + begin + for i := 0 to fiTagCount-1 do begin + if (j <> 0) then + fiTagArray[i-j] := fiTagArray[i]; + if (fiTagArray[i].ParentID = AParentID) and (fiTagArray[i].Tag = ATagID) then + inc(j); + end; + if (j <> 0) and (fiTagCount > 0) then + dec(fiTagCount); + end; +end; + *) +function TImageInfo.CreateTagPtr(const ATagDef: TTagEntry; IsThumbTag: Boolean; + AParentID: Word = 0): PTagEntry; +var + pTag: PTagEntry; + tag: TTagEntry; + idx: Integer; +begin + tag := ATagDef; + if tag.Size > 0 then + tag.Raw := StringOfChar(#0, tag.Size); + if IsThumbTag then + begin + tag.ParentID := 1; + idx := AddTagToThumbArray(tag); + Result := @fiThumbArray[idx]; + end else + begin + // Create the parent tag if it does not exist, yet. + if (AParentID <> 0) and (GetTagByID(AParentID).Tag = 0) then begin + pTag := FindExifTagDefByID(AParentID); + if pTag = nil then + raise Exception.CreateFmt('Definition for tag $%.4x not found.', [AParentID]); + pTag^.ParentID := 0; + pTag^.Raw := StringOfChar(#0, pTag^.Size); + AddTagToArray(pTag^); + end; + tag.ParentID := AParentID; + idx := AddTagToArray(tag); + Result := @fiTagArray[idx]; + end; +end; + +function TImageInfo.FindTagPtr(const ATagDef: TTagEntry; IsThumbTag: Boolean): PTagEntry; +var + i: Integer; +begin + if IsThumbTag then + begin + for i:=0 to fiThumbCount-1 do + if (fiThumbArray[i].Tag = ATagDef.Tag) and (fiThumbArray[i].Name = ATagDef.Name) then + begin + Result := @fiThumbArray[i]; + exit; + end; + end else + begin + for i:=0 to fiTagCount-1 do + if (fiTagArray[i].Tag = ATagDef.Tag) and (fiTagArray[i].Name = ATagDef.Name) then + begin + Result := @fiTagArray[i]; + exit; + end; + end; + Result := nil; +end; + (* +function TImageInfo.GetTagPtr(ATagTypes: TTagTypes; ATagID: word; + AForceCreate: Boolean=false; AParentID:word=0; ATagType: word=65535): PTagEntry; +var + i, j: integer; + tag: TTagEntry; +begin + Result := nil; + + if (ttThumb in ATagTypes) then begin + if AParentID = $FFFF then // $FFFF: ignore parent + for i:= 0 to fiThumbCount-1 do + if (fiThumbArray[i].Tag = ATagID) then begin + Result := @fiThumbArray[i]; + exit; + end; + for i := 0 to fiThumbCount-1 do + if (fiThumbArray[i].ParentID = AParentID) and (fiThumbArray[i].Tag = ATagID) then + begin + Result := @fiThumbArray[i]; + exit; + end; + end else + begin + if AParentID = $FFFF then // $FFFF: ignore parent + for i := 0 to fiTagCount - 1 do + if (fiTagArray[i].Tag = ATagID) then begin + Result := @fiTagArray[i]; + exit; + end; + for i := 0 to fiTagCount-1 do + if (fiTagArray[i].ParentID = AParentID) and (fiTagArray[i].Tag = ATagID) then + begin + Result := @fiTagArray[i]; + exit; + end; + end; + + if AForceCreate then begin + tag := FindExifTagDefByID(ATagID)^; + if ATagType <> 65535 then + tag.TType := ATagType; + tag.Id := 0; + if tag.Size > 0 then + tag.Raw := StringOfChar(#0, tag.Size); + if (ttThumb in ATagTypes) then begin + tag.ParentID := 1; + i := AddTagToThumbArray(tag); + Result := @fiThumbArray[i]; + end; + if ([ttExif, ttGps] * ATagTypes <> []) then begin + tag.parentID := AParentID; + i := AddTagToArray(tag); + Result := @fiTagArray[i]; + end; + end; +end; + *) +function TImageInfo.GetArtist: String; +begin + Result := GetTagValueAsString('Artist'); +end; + +procedure TImageInfo.SetArtist(v: String); +begin + SetTagValue('Artist', v); +end; + +function TImageInfo.GetUserComment(const ATag: TTagEntry): String; +var + buf: ansistring; + w: widestring; + a: ansistring; + n: Integer; +begin + Result := ''; + + InternalGetBinaryTagValue(ATag, buf); + if buf = '' then + exit; + + if pos('UNICODE', buf) = 1 then begin + SetLength(w, (Length(buf) - 8) div SizeOf(WideChar)); + Move(buf[9], w[1], Length(w) * Sizeof(WideChar)); + {$IFDEF FPC} + Result := UTF8Encode(w); + {$ELSE} + Result := w; + {$ENDIF} + end else + if pos('ASCII', buf) = 1 then begin + a := Copy(buf, 9, MaxInt); + while (a <> '') and ((a[Length(a)] = #0) or (a[Length(a)] = ' ')) do + Delete(a, Length(a), 1); + Result := a; + end else + if pos(#0#0#0#0#0#0#0#0, buf) = 1 then begin + a := Copy(buf, 9, MaxInt); + while (a <> '') and ((a[Length(a)] = #0) or (a[Length(a)] = ' ')) do + Delete(a, Length(a), 1); + {$IFDEF FPC} + {$IFDEF FPC3+} + Result := WinCPToUTF8(a); + {$ELSE} + Result := SysToUTF8(a); + {$ENDIF} + {$ELSE} + Result := a; + {$ENDIF} + end else + if Pos('JIS', buf) = 1 then + raise Exception.Create('JIS-encoded user comment is not supported.'); +end; + +function TImageInfo.GetExifComment: String; +var + tag: TTagEntry; +begin + tag := GetTagByName('UserComment'); + if tag.Tag <> 0 then + Result := GetUserComment(tag) + else + Result := ''; +end; + +(* +function TImageInfo.GetExifComment: String; +var + p : PTagEntry; + w : WideString; + n: Integer; + sa: AnsiString; +begin + Result := ''; + w := ''; + p := GetTagPtr([ttExif], TAG_EXIF_OFFSET); + if (p = nil) then + exit; + p := GetTagPtr([ttExif], TAG_USERCOMMENT, false, TAG_EXIF_OFFSET); + if (p = nil) or (Length(p^.Raw) <= 10) then + exit; + + if Pos('UNICODE', p^.Raw) = 1 then begin + SetLength(w, (Length(p^.Raw) - 8) div SizeOf(WideChar)); + Move(p^.Raw[9], w[1], Length(w) * SizeOf(WideChar)); + {$IFDEF FPC} + Result := UTF8Encode(w); + {$ELSE} + Result := w; + {$ENDIF} + end else + if Pos('ASCII', p^.Raw) = 1 then begin + SetLength(Result, Length(p^.Raw)-9); + sa := p^.Raw; + Delete(sa, 1, 8); + Result := sa; + end else + if Pos(#0#0#0#0#0#0#0#0, p^.Raw) = 1 then begin + SetLength(sa, Length(p^.Raw) - 9); + Move(p^.raw[9], sa[1], Length(sa)); + {$IFDEF FPC} + {$IFNDEF FPC3+} + Result := SysToUTF8(sa); + {$ELSE} + Result := WinCPToUTF8(sa); + {$ENDIF} + {$ELSE} + Result := sa; + {$ENDIF} + end else + if Pos('JIS', p^.Raw) = 1 then + raise Exception.Create('JIS-encoded user comment is not supported.'); +end; +*) + +procedure TImageInfo.SetExifComment(AValue: String); +var + p: PTagEntry; + i: integer; + w: WideString; + a: AnsiString; + u: Boolean; + buf: array of byte; + len: Integer; +begin + if AValue = '' then + SetLength(buf, 0) + else + begin + u := false; + for i:=1 to Length(AValue) do + if byte(AValue[i]) > 127 then begin + u := true; + break; + end; + + if u then begin + {$IFDEF FPC} + w := UTF8Decode(AValue); + {$ELSE} + w := AValue; + {$ENDIF} + SetLength(buf, 8 + Length(w) * SizeOf(WideChar)); // +8 for header + a := 'UNICODE'#0; + Move(a[1], buf[0], 8); + Move(w[1], buf[8], Length(w) * Sizeof(WideChar)); + end else + begin + SetLength(buf, 8 + Length(AValue)); + a := 'ASCII'#0#0#0; + Move(a[1], buf[0], 8); + a := ansistring(AValue); + Move(a[1], buf[8], Length(a)); + end; + end; + InternalSetTagValue('UserComment', NULL, [ttExif, ttGps], @buf[0], Length(buf)); + +(* + p := GetTagPtr([ttExif], TAG_EXIF_OFFSET, true, 0, FMT_ULONG{, true}); + if (v = '') then begin + RemoveTag([ttExif], TAG_USERCOMMENT, TAG_EXIF_OFFSET); + exit; + end; + + p := GetTagPtr([ttExif], TAG_USERCOMMENT, true, TAG_EXIF_OFFSET, FMT_BINARY); + u := false; + for i:=1 to Length(v) do + if byte(v[i]) > 127 then begin + u := true; + break; + end; + + if u then begin + p^.Raw := 'UNICODE'#0; + // According to docs: no need to add a trailing zero byte + {$IFDEF FPC} + w := UTF8Decode(v); + {$ELSE} + w := v; + {$ENDIF} + SetLength(p^.Raw, Length(w) * SizeOf(WideChar) + 8); + Move(w[1], p^.Raw[9], Length(w) * SizeOf(WideChar)); + end else begin + p^.Raw := 'ASCII'#0#0#0; + // According to docs: no need to add a trailing zero byte + a := AnsiString(v); + SetLength(p^.Raw, Length(a) + 8); + i := Length(p^.Raw); + Move(a[1], p^.Raw[9], Length(a)); + end; + p^.Size := Length(p^.Raw); + p^.Data := v; + *) +end; + +function TImageInfo.GetImageDescription: String; +begin + Result := GetTagValueAsString('ImageDescription'); +end; + +procedure TImageInfo.SetImageDescription(const AValue: String); +begin + SetTagValue('ImageDescription', AValue); +end; + +function TImageInfo.GetCameraMake: String; +begin + Result := GetTagValueAsString('Make'); +end; + +procedure TImageInfo.SetCameraMake(const AValue: String); +begin + SetTagValue('Make', AValue); +end; + +function TImageInfo.GetCameraModel: String; +begin + Result := GetTagValueAsString('Model'); +end; + +procedure TImageInfo.SetCameraModel(const AValue: String); +begin + SetTagValue('Model', AValue); +end; + +function TImageInfo.GetCopyright: String; +begin + Result := GetTagValueAsString('Copyright'); +end; + +procedure TImageInfo.SetCopyright(const AValue: String); +begin + SetTagValue('Copyright', AValue); +end; + +function TImageInfo.GetGPSCoordinate(ATagName: String; + ACoordType: TGPSCoordType): Extended; +var + vDeg, vSgn: Variant; +begin + Result := NaN; + vDeg := GetTagValue(ATagName); + if VarIsNull(vDeg) then + exit; + if not VarIsArray(vDeg) then + exit; + + Result := vDeg[0] + vDeg[1]/60 + vDeg[2]/3600; + vSgn := GetTagValue(ATagName + 'Ref'); + if VarIsNull(vSgn) then + exit; + case ACoordType of + ctLatitude : if VarToStr(vSgn)[1] in ['S', 's'] then Result := -Result; + ctLongitude : if VarToStr(vSgn)[1] in ['W', 'w'] then Result := -Result; + end; +end; + +procedure TImageInfo.SetGPSCoordinate(ATagName: String; const AValue: Extended; + ACoordType: TGPSCoordType); +const + Ref: array[TGPSCoordType] of string[2] = ('NS', 'EW'); +var + v: Variant; + degs, mins, secs: double; + val: Extended; +begin + if IsNaN(AValue) then + v := NULL + else begin + val := abs(AValue); + degs := trunc(val); + mins := trunc(frac(val) * 60); + secs := (frac(val) * 60 - mins) * 60; + v := VarArrayOf([degs, mins, secs]); + end; + InternalSetTagValue(ATagName, v, [ttGps]); + if IsNaN(AValue) then + InternalSetTagValue(ATagName + 'Ref', NULL, [ttGps]) + else + if AValue > 0 then + InternalSetTagValue(ATagName + 'Ref', Ref[ACoordType, 1], [ttGps]) + else + InternalSetTagValue(ATagName + 'Ref', Ref[ACoordType, 2], [ttGps]); + VarClear(v); +end; + +function TImageInfo.GetGPSLatitude: Extended; +begin + Result := GetGPSCoordinate('GPSLatitude', ctLatitude); +end; + +procedure TImageInfo.SetGPSLatitude(const AValue: Extended); +begin + SetGPSCoordinate('GPSLatitude', AValue, ctLatitude); +end; + +function TImageInfo.GetGPSLongitude: Extended; +begin + Result := GetGPSCoordinate('GPSLongitude', ctLongitude); +end; + +procedure TImageInfo.SetGPSLongitude(const AValue: Extended); +begin + SetGPSCoordinate('GPSLongitude', AValue, ctLongitude); +end; + +{ The version of the supported Exif or FlashPix standard. + + All four bytes should be interpreted as ASCII values. The first two bytes + encode the upper part of the standard version, the next two bytes encode the + lower part. For example, the byte sequence 48, 50, 50, 48, is the equivalent + of the ASCII value "0220", and denotes version 2.20. + + http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif/exifversion.html + http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif/flashpixversion.html +} +function TImageInfo.GetVersion(ATag: TTagEntry): String; +var + s: AnsiString; +begin + Result := ''; + InternalGetBinaryTagValue(ATag, s); + Result := s; +end; + +function TImageInfo.IterateFoundTags(TagId: integer; var RetVal: TTagEntry): boolean; +begin + InitTagEntry(Retval); + + while (FIterator < FITagCount) and (FITagArray[FIterator].TID <> TagId) do + inc(FIterator); + if (FIterator < FITagCount) then + begin + RetVal := FITagArray[FIterator]; + inc(FIterator); + Result := true; + end + else + Result := false; +end; + +procedure TImageInfo.ResetIterator; +begin + FIterator := 0; +end; + +function TImageInfo.IterateFoundThumbTags(TagId: integer; + var RetVal: TTagEntry): boolean; +begin + InitTagEntry(RetVal); + + while (FThumbIterator < FIThumbCount) and (FITagArray[FThumbIterator].TID <> TagId) do + inc(FThumbIterator); + if (FThumbIterator < FIThumbCount) then + begin + RetVal := FIThumbArray[FThumbIterator]; + inc(FThumbIterator); + Result := true; + end + else + Result := false; +end; + +procedure TImageInfo.ResetThumbIterator; +begin + FThumbIterator := 0; +end; + +function TImageInfo.GetRawFloat(ATagName: String): Double; +var + tiq: TTagEntry; +begin + tiq := GetTagByName(ATagName); + if tiq.Tag = 0 then // EmptyEntry + Result := 0.0 + else + Result := GetNumber(@tiq.Raw[1], Length(tiq.Raw), tiq.TType); +end; + +function TImageInfo.GetRawInt(ATagName: String): Integer; +var + tiq: TTagEntry; +begin + tiq := GetTagByName(ATagName); + if tiq.Tag = 0 then // EmptyEntry + Result := -1 + else + if (tiq.TType = FMT_BINARY) and (tiq.Size = 1) then + Result := byte(tiq.Raw[1]) + else + result := round(GetNumber(@tiq.Raw[1], Length(tiq.Raw), tiq.TType)); +end; + +// Unfortunatly if we're calling this function there isn't +// enough info in the EXIF to calculate the equivalent 35mm +// focal length and it needs to be looked up on a camera +// by camera basis. - next rev - maybe +function TImageInfo.LookupRatio: double; +var + estRatio: double; + upMake, upModel: String; +begin + upMake := Uppercase(copy(CameraMake, 1, 5)); + upModel := Uppercase(copy(Cameramodel, 1, 5)); + estRatio := 4.5; // ballpark for *my* camera - + Result := estRatio; +end; + +procedure TImageInfo.Calc35Equiv; +const + Diag35mm : double = 43.26661531; // sqrt(sqr(24)+sqr(36)) +var + tmp: integer; + CCDWidth, CCDHeight, fpu, fl, fl35, ratio: double; + NewE, LookUpE: TTagEntry; + w: Word; +begin + if LookUpTagIndex('FocalLengthin35mmFilm') >= 0 then + exit; // no need to calculate - already have it + + CCDWidth := 0.0; + CCDHeight := 0.0; + tmp := GetRawInt('FocalPlaneResolutionUnit'); + if (tmp <= 0) then + tmp := GetRawInt('ResolutionUnit'); + case tmp of + 2: fpu := 25.4; // inch + 3: fpu := 10; // centimeter + else + fpu := 0.0 + end; + + fl := GetRawFloat('FocalLength'); + if (fpu = 0.0) or (fl = 0.0) then + exit; + + tmp := GetRawInt('FocalPlaneXResolution'); + if (tmp <= 0) then + exit; + CCDWidth := Width * fpu / tmp; + + tmp := GetRawInt('FocalPlaneYResolution'); + if (tmp <= 0) then + exit; + + CCDHeight := Height * fpu / tmp; + + if CCDWidth*CCDHeight <= 0 then // if either is zero + begin + if not estimateValues then + exit; + ratio := LookupRatio() + end + else + ratio := Diag35mm / sqrt (sqr (CCDWidth) + sqr (CCDHeight)); + + fl35 := fl * ratio; + w := Round(fl35); + +// now load it into the tag array + tmp := LookupTagDefn('FocalLengthIn35mmFilm'); + if tmp = -1 then + exit; + + LookUpE := TagTable[tmp]; + NewE := LookupE; + NewE.Data := ansistring(Format('%0.2f',[fl35])); + NewE.FormatS := '%s mm'; + SetLength(NewE.Raw, 2); + Move(w, NewE.Raw[1], 2); + NewE.TType := FMT_USHORT; + AddTagToArray(NewE); + + TraceStr := TraceStr + crlf + + siif(ExifTrace > 0, 'tag[$' + IntToHex(tmp,4) + ']: ', '') + + NewE.Desc + dExifDelim + NewE.Data + + siif(ExifTrace > 0,' [size: 0]', '') + + siif(ExifTrace > 0,' [start: 0]', ''); +end; + +procedure TImageInfo.EXIFArrayToXML(AList: TStrings); +var + i: integer; +begin + Assert(AList <> nil, 'TImageInfo.ExifArrayToXML called with AList=nil.'); + AList.Add(' '); + for i := 0 to fiTagCount-1 do + with fITagArray[i] do + begin + AList.Add(' <' + Name + '>'); + if Tag in [105, 120] // headline and image caption // wp: ?? 105 = $0069, 120 = $0078 -- there are no such tags! + then AList.Add(' ') + else AList.Add(' ' + Data); + AList.Add(' '); + end; + AList.Add(' '); +end; + + +end. + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/fpexif/fpeexifdata.pas b/components/fpexif/fpeexifdata.pas new file mode 100644 index 000000000..2187d190c --- /dev/null +++ b/components/fpexif/fpeexifdata.pas @@ -0,0 +1,1664 @@ +unit fpeExifData; + +{$IFDEF FPC} + {$mode objfpc}{$H+} +{$ENDIF} + +{$I fpexif.inc} + +interface + +uses + Classes, SysUtils, + fpeGlobal, fpeTags; + +const + // Constants for tag IDs used explicitly somewhere + TAG_IMAGEWIDTH = $0100; + TAG_IMAGELENGTH = $0101; + TAG_IMAGEHEIGHT = $0101; + TAG_COMPRESSION = $0103; + TAG_MAKE = $010F; + TAG_MODEL = $0110; + TAG_THUMBSTARTOFFSET = $0201; + TAG_THUMBSIZE = $0202; + TAG_EXIFVERSION = $9000; + TAG_FOCALLENGTH35MM = $A405; + + // Parent's ID tag's ID + FULLTAG_IMAGEWIDTH = TAGPARENT_PRIMARY or TAG_IMAGEWIDTH; + FULLTAG_IMAGELENGTH = TAGPARENT_PRIMARY or TAG_IMAGELENGTH; + FULLTAG_COMPRESSION = TAGPARENT_PRIMARY or TAG_COMPRESSION; + FULLTAG_MAKE = TAGPARENT_PRIMARY or TAG_MAKE; + FULLTAG_MODEL = TAGPARENT_PRIMARY or TAG_MODEL; + FULLTAG_THUMBSTARTOFFSET = TAGPARENT_THUMBNAIL or TAG_THUMBSTARTOFFSET; + FULLTAG_THUMBSIZE = TAGPARENT_THUMBNAIL or TAG_THUMBSIZE; + FULLTAG_THUMBCOMPRESSION = TAGPARENT_THUMBNAIL or TAG_COMPRESSION; + FULLTAG_THUMBWIDTH = TAGPARENT_THUMBNAIL or TAG_IMAGEWIDTH; + FULLTAG_THUMBHEIGHT = TAGPARENT_THUMBNAIL or TAG_IMAGEHEIGHT; + FULLTAG_THUMBLENGTH = TAGPARENT_THUMBNAIL or TAG_IMAGELENGTH; + FULLTAG_EXIFVERSION = TAGPARENT_EXIF or TAG_EXIFVERSION; + FULLTAG_FOCALLENGTH35mm = TAGPARENT_EXIF or TAG_FOCALLENGTH35mm; + +type + TExifBeginReadingEvent = procedure of object; + TExifEndReadingEvent = procedure of object; + + { TExifData } + + TExifData = class + private + FTagList: TTagList; + FBigEndian: Boolean; + FThumbnailBuffer: TBytes; + FReadFlag: Integer; + FExportOptions: TExportOptions; + FOnBeginReading: TExifBeginReadingEvent; + FOnEndReading: TExifEndReadingEvent; + function GetImgHeight: Integer; + function GetImgWidth: Integer; + function GetTagByID(ATagID: TTagID): TTag; + function GetTagByIndex(AIndex: Integer): TTag; + function GetTagByName(AFullTagName: String): TTag; + function GetTagCount: Integer; + procedure SetExportOptions(const AValue: TExportOptions); + procedure SetTagByID(ATagID: TTagID; ATag: TTag); + procedure SetTagByIndex(AIndex: Integer; ATag: TTag); + procedure SetTagByName(AFullTagName: String; ATag: TTag); + protected + FTiffHeaderOffset: Int64; + procedure CheckFocalLengthIn35mm; + procedure DoBeginReading; + procedure DoEndReading; + function InternalAddTag(ATagDef: TTagDef): TTag; + public + constructor Create(ABigEndian: Boolean); + destructor Destroy; override; + + function AddMakerNoteTag(AIndex: Integer; ATagID: TTagID; ATagName: String; + ADataValue: Integer; ALkupTbl: String = ''; AFormatStr: String = ''; + ATagType: TTagType = ttUInt16): Integer; overload; + function AddMakerNoteTag(AIndex: Integer; ATagID: TTagID; ATagName: String; + ADataValue: Double; AFormatStr: String = ''; + ATagType: TTagType = ttURational): Integer; overload; + function AddOrReplaceTag(ATag: TTag): Integer; + function AddTag(ATag: TTag): Integer; + function AddTagByID(ATagID: TTagID): TTag; + function AddTagByName(AFullTagName: String): TTag; + procedure Clear; + function ExportOptionsToTagOptions: TTagOptions; + procedure ExportToStrings(AList: TStrings; ASeparator: String = '='; + AGroup: TTagGroup = tgUnknown); + function FindTagByID(ATagID: TTagID): TTag; + function FindTagByName(AFullTagName: String): TTag; + function GetParentTag(ATag: TTag): TTag; + function HasTagsOfGroup(AGroup: TTagGroup): Boolean; + function IndexOfTagID(ATagID: TTagID): Integer; + function IndexOfTagName(AFullTagName: String): Integer; + + // Reading + procedure BeginReading; + procedure EndReading; + function IsReading: Boolean; + property TiffHeaderOffset: Int64 read FTiffHeaderOffset; + + // Thumbnail + procedure LoadThumbnailFromStream(AStream: TStream; ASize: Integer = -1; + AUpdateThumbnailTags: Boolean = true); + function HasThumbnail: Boolean; + procedure RemoveThumbnail; + procedure SaveThumbnailToStream(AStream: TStream); + function ThumbnailSize: Integer; + + // Properties + property BigEndian: Boolean + read FBigEndian; + property ExportOptions: TExportOptions + read FExportOptions write SetExportOptions; + property TagByID[ATagID: TTagID]: TTag + read GetTagByID write SetTagByID; + property TagByIndex[AIndex: Integer]: TTag + read GetTagByIndex write SetTagByIndex; + property TagByName[ATagName: String]: TTag + read GetTagByName write SetTagByName; + property TagCount: Integer + read GetTagCount; + + property ImgHeight: Integer + read GetImgHeight; + property ImgWidth: Integer + read GetImgWidth; + + property OnBeginReading: TExifBeginReadingEvent + read FOnBeginReading write FOnBeginReading; + property OnEndReading: TExifEndReadingEvent + read FOnEndReading write FOnEndReading; + end; + + TVersionTag = class(TBinaryTag) + private + FSeparator: String; + protected + function GetAsString: String; override; + procedure SetAsString(const AValue: String); override; + public + property Separator: String read FSeparator write FSeparator; + end; + + TComponentsConfigTag = class(TBinaryTag) + protected + function GetAsString: String; override; + procedure SetAsString(const AValue: String); override; + end; + + TDateTimeTag = class(TStringTag) + private + function GetDateTime: TDateTime; + function GetFormat: String; + procedure SetDateTime(const AValue: TDateTime); + protected + function ExifDateToDateTime(AStr: string): TDateTime; + function GetAsString: String; override; + procedure SetAsString(const AValue: String); override; + public + procedure AdjustBy(ADays, AHours, AMinutes, ASeconds: Integer); + property AsDateTime: TDateTime read GetDateTime write SetDateTime; + property FormatStr; // e.g.: 'yyyy-mm-dd hh:nn:ss' + end; + + TGPSPositionTag = class(TFloatTag) + protected + function GetAsFloat: Double; override; + function GetAsString: String; override; + procedure SetAsFloat(const AValue: Double); override; + procedure SetAsString(const AValue: String); override; + end; + + TMakerNoteIntegerTag = class(TIntegerTag) + public + constructor Create(ATagID, {%H-}AIndex: Integer; AName: String; AValue: Integer; + ALkupTbl, AFormatStr: String; ATagType: TTagType; AOptions: TTagOptions); reintroduce; + end; + + TMakerNoteFloatTag = class(TFloatTag) + public + constructor Create(ATagID, {%H-}AIndex: Integer; AName: String; AValue: Double; + AFormatStr: String; ATagType: TTagType; AOptions: TTagOptions); reintroduce; + end; + + TExposureTimeTag = class(TFloatTag) + protected + function GetAsString: String; override; + procedure SetAsString(const AValue: String); override; + public + property FormatStr; + end; + + TShutterSpeedTag = class(TExposureTimeTag) + protected +// function GetFloat(AIndex: Integer; out AValue: Double): Boolean; override; + function GetRational(AIndex: Integer; out AValue: TExifRational): Boolean; override; + procedure SetFloat(AIndex: Integer; const AValue: Double); override; + procedure SetRational(AIndex: Integer; const AValue: TExifRational); override; + (* + function GetAsFloat: Double; override; + function GetAsRational: TExifRational; override; + procedure SetAsFloat(const AValue: Double); override; + procedure SetAsRational(const AValue: TExifRational); override; + *) + end; + + TApertureTag = class(TFloatTag) + protected + function GetFloat(AIndex: Integer; out AValue: Double): Boolean; override; + function GetRational(AIndex: Integer; out AValue: TExifRational): Boolean; override; + procedure SetFloat(AIndex: Integer; const AValue: Double); override; + procedure SetRational(AIndex: Integer; const AValue: TExifRational); override; + end; + + TUserCommentTag = class(TBinaryTag) + protected + function GetAsString: String; override; + procedure SetAsString(const AValue: String); override; + end; + + TXPTag = class(TBinaryTag) + protected + function GetAsString: String; override; + end; + + +procedure BuildExifTagDefs; +procedure FreeExifTagDefs; +function FindExifTagDef(ATagID: TTagID): TTagDef; overload; +function FindExifTagDef(AFullTagName: String): TTagDef; overload; +function FindExifTagDefWithoutParent(ATagID: word): TTagDef; + + +implementation + +uses + {$IFDEF FPC} + LazUTF8, + {$ENDIF} + Math, DateUtils, StrUtils, + fpeStrConsts, fpeUtils; + +//============================================================================== +// Tag definitions (TagDef) +//============================================================================== +var + ExifTagDefs: TTagDefList = nil; + +procedure BuildExifTagDefs; +const + I = TAGPARENT_INTEROP; // for shorter lines... + P = TAGPARENT_PRIMARY; + T = TAGPARENT_THUMBNAIL; + E = TAGPARENT_EXIF; + G = TAGPARENT_GPS; +begin + if ExifTagDefs = nil then + ExifTagDefs := TTagDefList.Create; + + with ExifTagDefs do begin + Clear; + AddStringTag (I+$0001, 'InterOpIndex', 1, rsInterOpIndex); + AddBinaryTag (I+$0002, 'InterOpVersion', 1, rsInterOpVersion, '', '', TVersionTag); + AddULongTag (P+$00FE, 'SubfileType', 1, '', rsSubfileTypeLkup, '', nil, true); + AddULongTag (P+$0100, 'ImageWidth', 1, rsImageWidth); + AddULongTag (T+$0100, 'ThumbnailWidth', 1, rsThumbnailWidth); + AddULongTag (P+$0101, 'ImageHeight', 1, rsImageHeight); // official: "Image length" + AddULongTag (T+$0101, 'ThumbnailHeight', 1, rsThumbnailHeight); // official: "Image length" + AddULongTag (P+$0101, 'ImageLength', 1, rsImageHeight); + AddULongTag (T+$0101, 'ThumbnailLength', 1, rsThumbnailHeight); + AddUShortTag (P+$0102, 'BitsPerSample', 1, rsBitsPerSample); + AddUShortTag (P+$0103, 'Compression', 1, rsCompression, rsCompressionLkup); + AddUShortTag (T+$0103, 'ThumbnailCompression', 1, rsCompression, rsCompressionLkup); + AddUShortTag (P+$0106, 'PhotometricInterpretation', 1, rsPhotometricInt, rsPhotometricIntLkup); + AddUShortTag (P+$0107, 'Thresholding', 1, rsThresholding, rsThresholdingLkup); + AddUShortTag (P+$0108, 'CellWidth', 1, rsCellWidth); + AddUShortTag (P+$0109, 'CellHeight', 1, rsCellHeight); + AddUShortTag (P+$010A, 'FillOrder', 1, rsFillOrder, rsFillOrderLkup); + AddStringTag (P+$010D, 'DocumentName', 1, rsDocumentName); + AddStringTag (P+$010E, 'ImageDescription', 1, rsImageDescr); + AddStringTag (P+$010F, 'Make', 1, rsMake); + AddStringTag (P+$0110, 'Model', 1, rsModel); + AddULongTag (P+$0111, 'StripOffsets', 1, rsStripOffsets); + AddUShortTag (P+$0112, 'Orientation', 1, rsOrientation, rsOrientationLkup); + AddUShortTag (T+$0112, 'Orientation', 1, rsOrientation, rsOrientationLkup); + AddUShortTag (P+$0115, 'SamplesPerPixel', 1, rsSamplesPerPixel); + AddULongTag (P+$0116, 'RowsPerStrip', 1, rsRowsPerStrip); + AddULongTag (P+$0117, 'StripByteCounts', 1, rsStripByteCounts); + AddUShortTag (P+$0118, 'MinSampleValue', 1, rsMinSampleValue); + AddUShortTag (P+$0119, 'MaxSampleValue', 1, rsMaxSampleValue); + AddURationalTag(P+$011A, 'XResolution', 1, rsXResolution); + AddURationalTag(T+$011A, 'ThumbnailXResolution', 1, rsXResolution); + AddURationalTag(P+$011B, 'YResolution', 1, rsYResolution); + AddURationalTag(T+$011B, 'ThumbnailYResolution', 1, rsYResolution); + AddUShortTag (P+$011C, 'PlanarConfiguration', 1, rsPlanarConfiguration, rsPlanarConfigurationLkup); + AddStringTag (P+$011D, 'PageName', 1, rsPageName); + AddURationalTag(P+$011E, 'XPosition', 1, rsXPosition); + AddURationalTag(P+$011F, 'YPosition', 1, rsYPosition); + AddUShortTag (P+$0128, 'ResolutionUnit', 1, rsResolutionUnit, rsResolutionUnitLkup); + AddUShortTag (T+$0128, 'ThumbnailResolutionUnit', 1, rsResolutionUnit, rsResolutionUnitLkup); + AddUShortTag (P+$0129, 'PageNumber', 2, rsPageNumber); + AddUShortTag (P+$012D, 'TransferFunction', 768, rsTransferFunction); + AddStringTag (P+$0131, 'Software', 1, rsSoftware); + AddStringTag (P+$0132, 'DateTime', 1, rsDateTime, '', TDateTimeTag); + AddStringTag (P+$013B, 'Artist', 1, rsArtist); + AddStringTag (P+$013C, 'HostComputer', 1, rsHostComputer); + AddUShortTag (P+$013D, 'Predictor', 1, rsPredictor, rsPredictorLkup); + AddURationalTag(P+$013E, 'WhitePoint', 2, rsWhitePoint); + AddURationaltag(P+$013F, 'PrimaryChromaticities', 6, rsPrimaryChromaticities); + AddUShortTag (P+$0141, 'HalftoneHints', 2, rsHalftoneHints); + AddULongTag (P+$0142, 'TileWidth', 1, rsTileWidth); + AddULongTag (P+$0143, 'TileLength', 1, rsTileLength); + AddULongTag (P+$014C, 'InkSet', 1, rsInkSet, rsInkSetLkup); + AddUShortTag (P+$0151, 'TargetPrinter', 1, rsTargetPrinter); + AddULongTag (T+$0201, 'ThumbnailOffset', 1, rsThumbnailOffset, '', '', TOffsetTag); + AddULongTag (T+$0202, 'ThumbnailSize', 1, rsThumbnailSize); + AddURationaltag(P+$0211, 'YCbCrCoefficients', 3, rsYCbCrCoefficients); + AddUShortTag (P+$0212, 'YCbCrSubsamping', 2, rsYCbCrSubsampling); + AddUShortTag (P+$0213, 'YCbCrPositioning', 1, rsYCbCrPositioning, rsYCbCrPosLkup); + AddUShortTag (T+$0213, 'YCbCrPositioning', 1, rsYCbCrPositioning, rsYCbCrPosLkup); + AddURationalTag(P+$0214, 'ReferenceBlackWhite', 6, rsRefBlackWhite); +// AddByteTag(P+$02BC, 'ExtensibleMetadataPlatform', 1, rsExtensibleMetadataPlatform); + AddStringTag (P+$02BC, 'ExtensibleMetadataPlatform',1, rsExtensibleMetadataPlatform); + AddStringTag (I+$1000, 'RelatedImageFileFormat', 1, rsRelatedImageFileFormat); + AddUShortTag (I+$1001, 'RelatedImageWidth', 1, rsRelatedImageWidth); + AddUShortTag (I+$1002, 'RelatedImageHeight', 1, rsRelatedImageHeight); + AddStringTag (P+$8298, 'Copyright', 1, rsCopyright); + AddURationalTag(E+$829A, 'ExposureTime', 1, rsExposureTime, '', '', TExposureTimeTag); //, nil, '%0:.0f/%1:.0f s'); + AddURationalTag(E+$829D, 'FNumber', 1, rsFNumber); //, nil, 'F/%2:.1f'); + AddULongTag (P+$83BB, 'IPTC/NAA', 1, rsIPTCNAA); + AddStringTag (P+$8546, 'SEMInfo', 1, rsSEMInfo); + AddBinaryTag (P+$8649, 'PhotoShopSettings', 1, ''); + AddULongTag (P+$8769, 'ExifOffset', 1, rsExifOffset, '', '', TSubIFDTag, true); + AddBinaryTag (P+$83BB, 'IPTC', 1, rsIPTCNAA); + AddUShortTag (E+$8822, 'ExposureProgram', 1, rsExposureProgram, rsExposureProgramLkup); + AddStringTag (E+$8824, 'SpectralSensitivity', 1, rsSpectralSensitivity); + AddULongTag (P+$8825, 'GPSInfo', 1, rsGPSInfo, '', '', TSubIFDTag); + AddULongTag (E+$8827, 'ISO', 1, rsISO); + AddUShortTag (E+$882A, 'TimeZoneOffset', 2, rsTimeZoneOffset); + AddUShortTag (E+$882B, 'SelfTimerMode', 1, rsSelfTimerMode); + AddUShortTag (E+$8830, 'SensitivityType', 1, rsSensitivityType, rsSensitivityTypeLkup); + AddULongTag (E+$8831, 'StandardOutputSensitivity', 1, rsStdOutputSens); + AddULongTag (E+$8832, 'RecommendedExposureIndex', 1, rsRecExpIndex); + AddULongTag (E+$8833, 'ISOSpeed', 1, rsIsoSpeed); + AddULongTag (E+$8834, 'ISOSpeedLatitudeYYY', 1, rsIsoSpeedLatitudeYYY); + AddULongTag (E+$8835, 'ISOSpeedLatitudeZZZ', 1, rsIsoSpeedLatitudeZZZ); + AddBinaryTag (E+$9000, 'ExifVersion', 4, rsExifVersion, '', '', TVersionTag); + AddStringTag (E+$9003, 'DateTimeOriginal', 1, rsDateTimeOriginal, '', TDateTimeTag); + AddStringTag (E+$9004, 'DateTimeDigitized', 1, rsDateTimeDigitized, '', TDateTimeTag); + AddStringTag (E+$9010, 'OffsetTime', 1, rsOffsetTime); + AddStringTag (E+$9011, 'OffsetTimeOriginal', 1, rsOffsetTimeOriginal); + AddStringTag (E+$9012, 'OffsetTimeDigitized', 1, rsOffsetTimeDigitized); + AddBinaryTag (E+$9101, 'ComponentsConfiguration', 1, rsComponentsConfig, '', '', TComponentsConfigTag, true); + AddURationalTag(E+$9102, 'CompressedBitsPerPixel', 1, rsCompressedBitsPerPixel); + AddSRationalTag(E+$9201, 'ShutterSpeedValue', 1, rsShutterSpeedValue, '', '', TShutterSpeedTag); + AddURationalTag(E+$9202, 'ApertureValue', 1, rsApertureValue, '', 'F/%2:.1f', TApertureTag); + AddSRationalTag(E+$9203, 'BrightnessValue', 1, rsBrightnessValue); + AddSRationalTag(E+$9204, 'ExposureBiasValue', 1, rsExposureBiasValue); + AddURationalTag(E+$9205, 'MaxApertureValue', 1, rsMaxApertureValue, '', 'F/%2:.1f', TApertureTag); + AddURationalTag(E+$9206, 'SubjectDistance', 1, rsSubjectDistance); + AddUShortTag (E+$9207, 'MeteringMode', 1, rsMeteringMode, rsMeteringModeLkup); + AddUShortTag (E+$9208, 'LightSource', 1, rsLightSource, rsLightSourceLkup); + AddUShortTag (E+$9209, 'Flash', 1, rsFlash, rsFlashLkup); + AddURationalTag(E+$920A, 'FocalLength', 1, rsFocalLength, '', '%2:.1f mm'); + AddULongTag (E+$9211, 'ImageNumber', 1, rsImageNumber); + AddStringTag (E+$9212, 'SecurityClassification', 1, rsSecurityClassification); + AddStringTag (E+$9213, 'ImageHistory', 1, rsImageHistory); + AddUShortTag (E+$9214, 'SubjectArea', 4, rsSubjectArea); + AddBinaryTag (E+$927C, 'MakerNote', 1, rsMakerNote, '', '', TMakerNoteTag, true); + AddBinaryTag (E+$9286, 'UserComment', 1, rsUserComment, '', '', TUserCommentTag); + AddStringTag (E+$9286, 'SubSecTime', 1, rsSubSecTime); + AddStringTag (E+$9291, 'SubSecTimeOriginal', 1, rsSubSecTimeOriginal); + AddStringTag (E+$9292, 'SubSecTimeDigitized', 1, rsSubSecTimeDigitized); + AddURationalTag(E+$9400, 'Temperature', 1, rsTemperature); + AddURationalTag(E+$9401, 'Humidity', 1, rsHumidity); + AddURationalTag(E+$9402, 'Pressure', 1, rsPressure); + AddSRationalTag(E+$9403, 'WaterDepth', 1, rsWaterDepth); + AddURationalTag(E+$9404, 'Acceleration', 1, rsAcceleration); + AddURationalTag(E+$9405, 'CameraElevationAngle', 1, rsCameraElevationAngle); + AddBinaryTag (P+$9C9B, 'XPTitle', 1, '', '', '', TXPTag); + AddBinaryTag (P+$9C9C, 'XPComment', 1, '', '', '', TXPTag); + AddBinaryTag (P+$9C9D, 'XPAuthor', 1, '', '', '', TXPTag); + AddBinaryTag (P+$9C9E, 'XPKeywords', 1, '', '', '', TXPTag); + AddBinaryTag (P+$9C9F, 'XPSubject', 1, '', '', '', TXPTag); + AddBinaryTag (E+$A000, 'FlashPixVersion', 1, rsFlashPixVersion, '', '', TVersionTag); + AddUShortTag (E+$A001, 'ColorSpace', 1, rsColorSpace, rsColorSpaceLkup); + AddUShortTag (E+$A002, 'ExifImageWidth', 1, rsExifImageWidth); + AddUShortTag (E+$A003, 'ExifImageHeight', 1, rsExifImageHeight); // is called "ExifImageLength" in Specs + AddStringTag (E+$A004, 'RelatedSoundFile', 1, rsRelatedSoundFile); + AddULongTag (E+$A005, 'InterOperabilityOffset', 1, rsInterOpOffset, '', '', TSubIFDTag, true); + AddURationalTag(E+$A20B, 'FlashEnergy', 1, rsFlashEnergy); + AddBinaryTag (E+$A20C, 'SpatialFrequencyResponse', 1, rsSpatialFrequResponse); + AddURationalTag(E+$A20E, 'FocalPlaneXResolution', 1, rsFocalPlaneXRes, '', '%2:f'); + AddURationalTag(E+$A20F, 'FocalPlaneYResolution', 1, rsFocalPlaneYRes, '', '%2:f'); + AddUShortTag (E+$A210, 'FocalPlaneResolutionUnit', 1, rsFocalPlaneResUnit, rsFocalPlaneResUnitLkup); + AddBinaryTag (E+$A211, 'ImageNumber', 1, rsImageNumber); + AddStringTag (E+$A212, 'SecurityClassification', 1, rsSecurityClassification); + AddBinaryTag (E+$A213, 'ImageHistory', 1, rsImageHistory); + AddUShortTag (E+$A214, 'SubjectLocation', 2, rsSubjectLocation); + AddURationalTag(E+$A215, 'ExposureIndex', 1, rsExposureIndex); + AddUShortTag (E+$A217, 'SensingMethod', 1, rsSensingMethod, rsSensingMethodLkup); + AddBinaryTag (E+$A300, 'FileSource', 1, rsFileSource, rsFileSourceLkup); + AddBinaryTag (E+$A301, 'SceneType', 1, rsSceneType, rsSceneTypeLkup); + AddBinaryTag (E+$A302, 'CFAPattern', 1, rsCFAPattern); + AddUShortTag (E+$A401, 'CustomRendered', 1, rsCustomRendered, rsCustomRenderedLkup); + AddUShortTag (E+$A402, 'ExposureMode', 1, rsExposureMode, rsExposureModeLkup); + AddUShortTag (E+$A403, 'WhiteBalance', 1, rsWhiteBalance, rsAutoManual); + AddURationalTag(E+$A404, 'DigitalZoomRatio', 1, rsDigitalZoomRatio); + AddUShortTag (E+$A405, 'FocalLengthIn35mmFilm', 1, rsFocalLengthIn35mm, '', '%d mm'); + AddUShortTag (E+$A406, 'SceneCaptureType', 1, rsSceneCaptureType, rsSceneCaptureTypeLkup); + AddUShortTag (E+$A407, 'GainControl', 1, rsGainControl, rsGainControlLkup); + AddUShortTag (E+$A408, 'Contrast', 1, rsContrast, rsNormalLowHigh); + AddUShortTag (E+$A409, 'Saturation', 1, rsSaturation, rsNormalLowHigh); + AddUShortTag (E+$A40A, 'Sharpness', 1, rsSharpness, rsNormalSoftHard); + AddBinaryTag (E+$A40B, 'DeviceSettingDescription', 1, rsDeviceSettingDescription); + AddUShortTag (E+$A40C, 'SubjectDistanceRange', 1, rsSubjectDistancerange, rsSubjectDistanceRangeLkup); + AddStringTag (E+$A420, 'ImgeUniqueID', 1, rsImageUniqueID); + AddStringTag (E+$A430, 'OwnerName', 1, rsOwnerName); + AddStringTag (E+$A431, 'SerialNumber', 1, rsSerialNumber); + AddURationalTag(E+$A432, 'LensInfo', 4, rsLensInfo); + AddStringTag (E+$A433, 'LensMake', 1, rsLensMake); + AddStringTag (E+$A434, 'LensModel', 1, rsLensModel); + AddStringTag (E+$A435, 'LensSerialNumber', 1, rsLensSerialNumber); + AddURationalTag(E+$A500, 'Gamma', 1, rsGamma); + AddBinaryTag (P+$C4A5, 'PrintIM', $FFFF, '', '', '', nil, true); + AddBinaryTag (P+$C6D2, 'PanasonicTitle', $FFFF, '', '', '', nil, true); + AddBinaryTag (P+$C6D3, 'PanasonicTitle2', $FFFF, '', '', '', nil, true); + AddBinaryTag (E+$EA1C, 'Padding', $FFFF, '', '', '', nil, true); + AddSLongTag (E+$EA1D, 'OffsetSchema', 1, '', '', '', nil, true); + AddByteTag (G+$0000, 'GPSVersionID', 4, rsGpsVersionID, '', '', TVersionTag); + AddStringTag (G+$0001, 'GPSLatitudeRef', 2, rsGPSLatitudeRef, rsGPSLatitudeRefLkup); + AddURationalTag(G+$0002, 'GPSLatitude', 3, rsGPSLatitude, '', '%0:.0f° %1:.0f'' %2:.3f"', TGPSPositionTag); + AddStringTag (G+$0003, 'GPSLongitudeRef', 2, rsGPSLongitudeRef, rsGPSLongitudeRefLkup); + AddURationalTag(G+$0004, 'GPSLongitude', 3, rsGPSLongitude, '', '%0:.0f° %1:.0f'' %2:.3f"', TGPSPositionTag); + AddByteTag (G+$0005, 'GPSAltitudeRef', 1, rsGPSAltitudeRef, rsGPSAltitudeRefLkup); + AddURationalTag(G+$0006, 'GPSAltitude', 1, rsGPSAltitude); + AddURationalTag(G+$0007, 'GPSTimeStamp', 3, rsGPSTimeStamp); // !!!!!!!!!!!, nil, '', @CvtTime); + AddStringTag (G+$0008, 'GPSSatellites', 1, rsGPSSatellites); + AddStringTag (G+$0009, 'GPSStatus', 2, rsGPSStatus); + AddStringTag (G+$000A, 'GPSMeasureMode', 2, rsGPSMeasureMode, rsGPSMeasureModeLkup); + AddURationalTag(G+$000B, 'GPSDOP', 1, rsGPSDOP); + AddStringTag (G+$000C, 'GPSSpeedRef', 2, rsGPSSpeedRef, rsGPSSpeedRefLkup); + AddURationalTag(G+$000D, 'GPSSpeed', 1, rsGPSSpeed); + AddStringTag (G+$000E, 'GPSTrackRef', 2, rsGPSTrackRef, rsGPSTrackRefLkup); + AddURationalTag(G+$000F, 'GPSTrack', 1, rsGPSTrack); + AddStringTag (G+$0010, 'GPSImageDirectionRef', 2, rsGPSImageDirectionRef, rsGPSTrackRefLkup); // same option texts + AddURationalTag(G+$0011, 'GPSImageDirection', 1, rsGPSImageDirection); + AddStringTag (G+$0012, 'GPSMapDatum', 1, rsGPSMapDatum); + AddStringTag (G+$0013, 'GPSDestLatitudeRef', 2, rsGPSDestLatitudeRef, rsGPSLatitudeRefLkup); + AddURationalTag(G+$0014, 'GPSDestLatitude', 3, rsGPSDestLatitude, '', '%0:.0f° %1:.0f'' %2:.3f"', TGPSPositionTag); + AddStringTag (G+$0015, 'GPSDestLongitudeRef', 2, rsGPSDestLongitudeRef, rsGPSLongitudeRefLkup); + AddURationalTag(G+$0016, 'GPSDestLongitude', 3, rsGPSDestLongitude, '', '%0:.0f° %1:.0f'' %2:.3f"', TGPSPositionTag); + AddStringTag (G+$0017, 'GPSDestBearingRef', 2, rsGPSDestBearingRef, rsGPSTrackRefLkup); + AddURationalTag(G+$0018, 'GPSDestBearing', 1, rsGPSDestBearing); + AddStringTag (G+$0019, 'GPSDestDistanceRef', 2, rsGPSDestDistanceRef, rsGPSDistanceRefLkup); + AddURationalTag(G+$001A, 'GPSDestDistance', 1, rsGPSDestDistance); + AddBinaryTag (G+$001B, 'GPSProcessingMode', 1, rsGPSProcessingMode); + AddBinaryTag (G+$001C, 'GPSAreaInformation', 1, rsGPSAreaInformation); + AddStringTag (G+$001D, 'GPSDateStamp', 11, rsGPSDateStamp); + AddUShortTag (G+$001E, 'GPSDifferential', 1, rsGPSDateDifferential, rsGPSDateDifferentialLkup); + AddURationalTag(G+$001F, 'GPSHPositioningError', 1, rsGPSHPositioningError); + end; +end; + +function FindExifTagDef(ATagID: TTagID): TTagDef; +begin + if ExifTagDefs = nil then + BuildExifTagDefs; + Result := ExifTagDefs.FindByID(ATagID); +end; + +function FindExifTagDef(AFullTagName: String): TTagDef; +begin + if ExifTagDefs = nil then + BuildExifTagDefs; + Result := ExifTagDefs.FindByName(AFullTagName); +end; + +{ seeks for the definition of the tag specified by the given id of the tag part + only, the parent ID is ignored. } +function FindExifTagDefWithoutParent(ATagID: Word): TTagDef; +begin + if ExifTagDefs = nil then + BuildExifTagDefs; + Result := ExifTagDefs.FindByIDWithoutParent(ATagID); +end; + +procedure FreeExifTagDefs; +begin + FreeAndNil(ExifTagDefs); +end; + + +//============================================================================== +// TExifData +//============================================================================== + +constructor TExifData.Create(ABigEndian: Boolean); +begin + BuildExifTagDefs; + FTagList := TTagList.Create; + FBigEndian := ABigEndian; + FExportOptions := [eoShowTagName, eoDecodeValue, eoTruncateBinary]; +end; + +destructor TExifData.Destroy; +begin + FTagList.Free; + inherited; +end; + +function TExifData.AddMakerNoteTag(AIndex: Integer; ATagID: TTagID; ATagName: String; + ADataValue: Integer; ALkupTbl: String = ''; AFormatStr: String = ''; + ATagType: TTagType = ttUInt16): Integer; +var + tag: TTag; +begin + tag := TMakerNoteIntegerTag.Create(ATagID, AIndex, ATagName, ADataValue, + ALkupTbl, AFormatStr, ATagType, ExportOptionsToTagOptions); + Result := FTagList.Add(tag); +end; + +function TExifData.AddMakerNoteTag(AIndex: Integer; ATagID: TTagID; ATagName: String; + ADataValue: Double; AFormatStr: String = ''; + ATagType: TTagType = ttURational): Integer; +var + tag: TTag; +begin + tag := TMakerNoteFloatTag.Create(ATagID, AIndex, ATagName, ADataValue, + AFormatStr, ATagType, ExportOptionsToTagOptions); + Result := FTagList.Add(tag); +end; + +function TExifData.AddOrReplaceTag(ATag: TTag): Integer; +begin + Result := IndexOfTagID(ATag.TagID); + if Result <> -1 then begin + FTagList.Delete(Result); + FTagList.Insert(Result, ATag); + end else + Result := AddTag(ATag); +end; + +function TExifData.AddTag(ATag: TTag): Integer; +var + parentID: TTagID; + parentTag: TTag; + parentTagDef: TTagDef; +begin + parentID := ATag.TagID and $FFFF0000; + if not ((parentID = TAGPARENT_PRIMARY) or (parentID = TAGPARENT_THUMBNAIL)) + then begin + // Make sure that the parent directories of the new tag already exist. + // If not, create them. + repeat + // Look if the parent tag already exists. + parentTag := GetParentTag(ATag); + if parentTag <> nil then + break; + + // No - not found... + // The tagID of the tag which defines the subIFD is encoded in the high-word + // of the tagID + parentID := TTagIDRec(ATag.TagID).Parent; + // Just to make sure: the primary and thumbnail IFDs are always existing... + if (parentID = TAG_PRIMARY) or (parentID = TAG_THUMBNAIL) then + break; + // Find definition of the sub-ifd tag + parentTagDef := FindExifTagDefWithoutParent(parentID); + // ... create tag for it + parentTag := TSubIFDTag.Create(parentTagDef, FBigEndian); + // ... and add it to the list (recursively, i.e. it will check for parents again) + AddOrReplaceTag(parentTag); + until false; + end; + + // Add the new tag + Result := FTagList.Add(ATag); +end; + +function TExifData.AddTagByID(ATagID: TTagID): TTag; +var + idx: Integer; + tagDef: TTagDef; +begin + idx := IndexOfTagID(ATagID); + if idx > -1 then + Result := FTagList[idx] + else begin + tagDef := FindExifTagDef(ATagID); + Result := InternalAddTag(tagDef); + end; +end; + +function TExifData.AddTagByName(AFullTagName: String): TTag; +var + idx: Integer; + tagdef: TTagDef; +begin + idx := IndexOfTagName(AFullTagName); + if idx > -1 then + Result := FTagList[idx] + else begin + tagDef := FindExifTagDef(AFullTagName); + Result := InternalAddTag(tagDef); + end; +end; + +procedure TExifData.BeginReading; +begin + inc(FReadFlag); + if FReadFlag = 1 then + DoBeginReading; +end; + +{ Checks whether the tag "FocalLengthIn35mm" is available. Otherwise it is + created as a volatile, readonly tag. } +procedure TExifData.CheckFocalLengthIn35mm; +var + tag: TTag; + fpu, flen, resol: Double; + ccdwidth, ccdheight, ratio: Double; + tagdef: TTagDef; + optns: TTagOptions; +begin + tag := TagByID[FULLTAG_FOCALLENGTH35mm]; + if tag <> nil then + exit; + + tag := TagByName['Exif.FocalLength']; + if tag = nil then + exit; + flen := tag.AsFloat; + if IsNaN(flen) or (flen <= 0.0) then + exit; + + tag := TagByName['Exif.FocalPlaneResolutionUnit']; + if tag = nil then + tag := TagByName['ResolutionUnit']; + if tag = nil then + exit; + fpu := tag.AsFloat; + if IsNaN(fpu) or (fpu <= 0) then + exit; + + tag := TagByName['Exif.FocalPlaneResolutionX']; + if tag = nil then + exit; + resol := tag.AsFloat; + if IsNaN(resol) or (resol <= 0.0) then + exit; + ccdwidth := GetImgWidth() * fpu/resol; + + tag := TagByName['Exif.FocalPlaneResolutionY']; + if tag = nil then + exit; + resol := tag.AsFloat; + if IsNaN(resol) or (resol <= 0.0) then + exit; + ccdheight := GetImgHeight() * fpu/resol; + + ratio := sqrt(sqr(24) + sqr(36)) / sqrt(sqr(CCDWidth) + sqr(CCDHeight)); + + optns := [toReadOnly, toVolatile]; + if BigEndian then optns := optns + [toBigEndian]; + + tagDef := FindExifTagDef(FULLTAG_FOCALLENGTH35mm); + tag := TFloatTag.Create(tagDef, optns); + tag.AsFloat := flen * ratio; + AddOrReplaceTag(tag); +end; + +procedure TExifData.Clear; +begin + FTagList.Clear; +end; + +procedure TExifData.DoBeginReading; +begin + if Assigned(FOnBeginReading) then FOnBeginReading(); +end; + +procedure TExifData.DoEndReading; +begin + if Assigned(FOnEndReading) then FOnEndReading(); +end; + +procedure TExifData.EndReading; +begin + dec(FReadFlag); + if FReadFlag = 0 then begin + CheckFocalLengthIn35mm; + DoEndReading; + end; +end; + +function TExifData.ExportOptionsToTagOptions: TTagOptions; +begin + Result := []; + if eoDecodeValue in FExportOptions then + Include(Result, toDecodeValue); + if eoTruncateBinary in FExportOptions then + Include(Result, toTruncateBinary); + if eoBinaryAsASCII in FExportOptions then + Include(Result, toBinaryAsASCII); +end; + +procedure TExifData.ExportToStrings(AList: TStrings; ASeparator: String = '='; + AGroup: TTagGroup = tgUnknown); +var + i: Integer; + tag: TTag; + nam: String; + tagval: String; + usedExportOptions: TExportOptions; +begin + Assert(AList <> nil); + if AGroup = tgUnknown then begin + ExportToStrings(AList, ASeparator, tgExifPrimary); + ExportToStrings(AList, ASeparator, tgExifThumbnail); + ExportToStrings(AList, ASeparator, tgExifSub); + ExportToStrings(AList, ASeparator, tgExifGps); + ExportToStrings(AList, ASeparator, tgExifInterop); + ExportToStrings(AList, ASeparator, tgExifMakerNote); + exit; + end; + + if not HasTagsOfGroup(AGroup) then + exit; + + if AList.Count > 0 then + AList.Add(''); + AList.Add('*** ' + NiceGroupNames[AGroup] + ' ***'); + + for i := 0 to TagCount-1 do begin + tag := TagByIndex[i]; + if tag.Group = AGroup then begin + usedExportOptions := FExportOptions * [eoShowDecimalTagID, eoShowHexTagID]; + if usedExportOptions = [eoShowDecimalTagID] then + nam := Format('[%d %d] %s', [ + tag.TagIDRec.Parent, tag.TagIDRec.Tag, tag.Description + ]) + else + if usedExportOptions = [eoShowHexTagID] then + nam := Format('[$%.4x %.4x] %s', [ + tag.TagIDRec.Parent, tag.TagIDRec.Tag, tag.Description + ]) + else + nam := tag.Description; + tagval := tag.AsString; + if tagval <> '' then + AList.Add(nam + ASeparator + tagval); + end; + end; +end; + +{ Seeks the tag list for the tag with the specified (full) TagID. + The function returns nil if the tag is not found. } +function TExifData.FindTagByID(ATagID: TTagID): TTag; +var + i: Integer; +begin + for i:=0 to FTagList.Count-1 do + begin + Result := FTagList[i]; + if (Result.TagID = ATagID) then + exit; + end; + Result := nil; +end; + +{ Seeks the tag list for the tag with the specified name. The name must be + composed of the name of the tag group and the name of the tag, i.e. + 'EXIF.FNumber'. If the group is not specified (i.e. 'FNumber' only) the + first matching tag is returned (in spite of other tags possibly having the + same name in other groups). + The function returns nil if the tag is not found. } +function TExifData.FindTagByName(AFullTagName: String): TTag; +var + idx: Integer; +begin + idx := IndexOfTagName(AFullTagName); + if idx = -1 then + Result := nil + else + Result := FTagList[idx]; +end; + +function TExifData.GetImgHeight: Integer; +var + tag: TTag; +begin + tag := TagByName['ImageHeight']; + if tag = nil then + tag := TagByName['Exif.ExifImageHeight']; + if tag = nil then + result := 0 + else + Result := tag.AsInteger; +end; + +function TExifData.GetImgWidth: Integer; +var + tag: TTag; +begin + tag := TagByName['ImageWidth']; + if tag = nil then + tag := TagByName['Exif.ExifImageWidth']; + if tag = nil then + Result := 0 + else + Result := tag.AsInteger; +end; + +{ Finds the tag which defines the sub-IFD to which the specified tag belongs } +function TExifData.GetParentTag(ATag: TTag): TTag; +var + idx: Integer; +begin + Result := nil; + if ATag <> nil then begin + idx := FTagList.IndexOfParentByID(ATag.TagID); + if idx <> -1 then + Result := FTagList[idx]; + end; +end; + +{ Seeks the tag list for the tag with the specified TagID and the specified + tag group } +function TExifData.GetTagByID(ATagID: TTagID): TTag; +var + idx: Integer; +begin + idx := IndexOfTagID(ATagID); + if idx = -1 then + Result := nil + else + Result := FTagList.Items[idx]; +end; + +function TExifData.GetTagByIndex(AIndex: Integer): TTag; +begin + Result := FTagList[AIndex]; +end; + +{ Seeks the tag list for the tag with the specified name. The name must be + composed of the name of the tag group and the name of the tag, i.e. + 'EXIF.FNumber'. If the group is not specified (i.e. 'FNumber' only) the + first matching tag is returned (in spite of other tags possibly having the + same name in other groups). } +function TExifData.GetTagByName(AFullTagName: String): TTag; +var + idx: Integer; +begin + idx := IndexOfTagName(AFullTagName); + if idx > -1 then + Result := FTagList[idx] + else + Result := nil +end; + +function TExifData.GetTagCount: Integer; +begin + Result := FTagList.Count; +end; + +function TExifData.HasTagsOfGroup(AGroup: TTagGroup): Boolean; +var + i: Integer; + tag: TTag; +begin + Result := true; + for i:=0 to FTagList.Count-1 do begin + tag := FTagList[i]; + if (tag.Group = AGroup) then + exit; + end; + Result := false; +end; + +function TExifData.HasThumbnail: Boolean; +begin + Result := Length(FThumbnailBuffer) > 0; +end; + +function TExifData.IndexOfTagID(ATagID: TTagID): Integer; +begin + Result := FTagList.IndexOfTagByID(ATagID); +end; + +function TExifData.IndexOfTagName(AFullTagName: String): Integer; +var + gname: String; + tname: String; + p: Integer; + g: TTagGroup; + i: Integer; + tag: TTag; +begin + p := pos('.', AFullTagName); + if p <> 0 then + begin + gname := copy(AFullTagName, 1, p-1); + tname := copy(AFullTagName, p+1, MaxInt); + for g := Low(TTagGroup) to High(TTagGroup) do + if SameText(gname, GroupNames[g]) or SameText(gname, NiceGroupNames[g]) then begin + for i:=0 to FTagList.Count-1 do begin + tag := FTagList[i]; + if SameText(tag.Name, tname) and (tag.Group = g) then begin + Result := i; + exit; + end; + end; + end; + end else + begin + for i:=0 to FTagList.Count-1 do begin + tag := FTagList[i]; + if SameText(tag.Name, AFullTagName) then begin + Result := i; + exit; + end; + end; + end; + Result := -1; +end; + +function TExifData.InternalAddTag(ATagDef: TTagDef): TTag; +var + optns: TTagOptions; +begin + if ATagDef <> nil then begin + optns := ExportOptionsToTagOptions; + if FBigEndian then Include(optns, toBigEndian); + Result := ATagDef.TagClass.Create(ATagDef, optns); + AddTag(Result); + end else + Result := nil +end; + +function TExifData.IsReading: Boolean; +begin + Result := FReadFlag > 0; +end; + +procedure TExifData.LoadThumbnailFromStream(AStream: TStream; + ASize: Integer = -1; AUpdateThumbnailTags: Boolean = true); +var + n: Integer; + w, h: Integer; +begin + SetLength(FThumbnailBuffer, 0); + if AUpdateThumbnailTags then + RemoveThumbnail; + + // Check whether the image is a jpeg, and extract size of the thrumbnail image + if not JPEGImageSize(AStream, w, h) then + raise EFpExif.Create('Only jpeg images accepted for thumbnail.'); + + // Write the image from the stream into the thumbnail buffer + if ASize < 0 then + n := AStream.Size else + n := ASize; + if n > 65000 then // limit probably still too high, thumbnail must fit into a 64k segment along with all other tags... + raise EFpExif.Create('Thumbnail too large.'); + + SetLength(FThumbnailBuffer, n); + if AStream.Read(FThumbnailBuffer[0], n) < n then + raise EFpExif.Create('Could not read thumbnail image.'); + + if AUpdateThumbnailTags then + begin + // Make sure that the IFD1 tags for the thumbnail are correct + AddTagByID(FULLTAG_THUMBCOMPRESSION).AsInteger := 6; // 6 = JPEG - this was checked above. + AddTagByID(FULLTAG_THUMBWIDTH).AsInteger := w; + AddTagByID(FULLTAG_THUMBLENGTH).AsInteger := h; + AddTagByID(FULLTAG_THUMBSTARTOFFSET).AsInteger := 0; // to be replaced by the offset to the thumbnail when writing + AddTagByID(FULLTag_THUMBSIZE).AsInteger := n; + end; +end; + +procedure TExifData.RemoveThumbnail; +var + tag: TTag; + i: Integer; +begin + SetLength(FThumbnailBuffer, 0); + + for i:=FTagList.Count-1 downto 0 do begin + tag := FTagList[i]; + if tag.Group = tgExifThumbnail then + FTagList.Delete(i) + end; +end; + +procedure TExifData.SaveThumbnailToStream(AStream: TStream); +var + n: Int64; +begin + if HasThumbnail then + begin + n := Length(FThumbnailBuffer); + if AStream.Write(FThumbnailBuffer[0], n) <> n then + raise EFpExif.Create('Error writing thumbnail image to stream.'); + end; +end; + +procedure TExifData.SetExportOptions(const AValue: TExportOptions); +var + i: Integer; + tag: TTag; + decodeVal, truncBin, binASCII: Boolean; + needUpdate: Boolean; + optns: set of TExportOption; +begin + optns := [eoDecodeValue, eoTruncateBinary, eoBinaryAsASCII]; + needUpdate := (optns * FExportOptions <> optns * AValue); + FExportOptions := AValue; + if not needUpdate then + exit; + + decodeVal := eoDecodeValue in FExportOptions; + truncBin := eoTruncateBinary in FExportOptions; + binASCII := eoBinaryAsASCII in FExportOptions; + for i:=0 to TagCount-1 do + begin + tag := TagByIndex[i]; + tag.DecodeValue := decodeVal; + tag.TruncateBinary := truncBin; + tag.BinaryAsASCII := binASCII; + end; +end; + +procedure TExifData.SetTagByID(ATagID: TTagID; ATag: TTag); +var + idx: Integer; +begin + if (ATag <> nil) and ATag.ReadOnly then + exit; + + idx := IndexOfTagID(ATagID); + SetTagByIndex(idx, ATag); +end; + +procedure TExifData.SetTagByIndex(AIndex: Integer; ATag: TTag); +var + tag: TTag; +begin + if (ATag <> nil) and ATag.ReadOnly then + exit; + + if AIndex > -1 then begin + tag := FTagList[AIndex]; + if tag.ReadOnly then + exit; + FTagList.Delete(AIndex); + if ATag <> nil then + FTagList.Insert(AIndex, ATag); + end else + AddOrReplaceTag(ATag); +end; + +procedure TExifData.SetTagByName(AFullTagName: String; ATag: TTag); +var + idx: Integer; +begin + if (ATag <> nil) and ATag.ReadOnly then + exit; + + idx := IndexOfTagName(AFullTagName); + SetTagByIndex(idx, ATag); +end; + +function TExifData.ThumbnailSize: Integer; +begin + Result := Length(FThumbnailBuffer); +end; + + +//============================================================================== +// TVersionTag +//============================================================================== + +function TVersionTag.GetAsString: String; +var + i: Integer; + ch: Char; +begin + for i:=0 to High(FRawData) do begin + if (FType = ttUInt8) then + ch := char(ord('0') + FRawData[i]) + else + ch := char(FRawData[i]); + if i = 0 then + Result := ch + else + if FSeparator = #0 then + Result := Result + ch + else + Result := Result + FSeparator + ch; + end; +end; + +procedure TVersionTag.SetAsString(const AValue: String); +var + i, n: Integer; + sa: ansistring; + b: Byte; +begin + sa := ansistring(AValue); + SetLength(FRawData, Length(sa)); + i := 1; + n := 0; + while i <= Length(sa) do begin + if sa[i] <> FSeparator then + begin + if (FType = ttUInt8) then + b := ord(sa[i]) - ord('0') + else + b := ord(sa[i]); + FRawData[n] := b; + inc(n); + end; + inc(i); + end; + SetLength(FRawData, n); + FCount := n; +end; + + +//============================================================================== +// TComponentsConfigTag +//============================================================================== +function TComponentsConfigTag.GetAsString: String; +var + i: Integer; +begin + Result := ''; + for i:=0 to 3 do + case FRawData[i] of + 1: Result := Result + 'Y'; + 2: Result := Result + 'Cb'; + 3: Result := Result + 'Cr'; + 4: Result := Result + 'R'; + 5: Result := Result + 'G'; + 6: Result := Result + 'B'; + end; +end; + +procedure TComponentsConfigTag.SetAsString(const AValue: String); +var + i, j: Integer; + s: String; + elem: String; +begin + SetLength(FRawData, 4); + FCount := 4; + s := InsertSpaces(AValue) + ' '; + elem := ''; + j := 0; + for i:=1 to Length(s) do begin + if (s[i] >= 'A') and (s[i] <= 'Z') then + elem := s[i] + else + if (s[i] = ' ') then begin + if elem = 'Y' then + FRawData[j] := 1 + else + if elem = 'Cb' then + FRawData[j] := 2 + else + if elem = 'Cr' then + FRawData[j] := 3 + else + if elem = 'R' then + FRawdata[j] := 4 + else + if elem = 'G' then + FRawData[j] := 5 + else + if elem = 'B' then + FRawData[j] := 6 + else + continue; + inc(j); + if j = 4 then + exit; + end else + elem := elem + s[i]; + end; +end; + + +//============================================================================== +// TDateTimeTag +//============================================================================== + +procedure TDateTimeTag.AdjustBy(ADays, AHours, AMinutes, ASeconds: Integer); +var + dt: TDateTime; +begin + dt := GetDateTime; + dt := dt + ADays + AHours/24 + AMinutes/(24*60) + ASeconds/(24*60*60); + SetDateTime(dt); +end; + +function TDateTimeTag.ExifDateToDateTime(AStr: string): TDateTime; +type + TConvert= packed record + year: Array [1..4] of char; f1:char; + mon: Array [1..2] of char; f2:char; + day: Array [1..2] of char; f3:char; + hr: Array [1..2] of char; f4:char; + min: Array [1..2] of char; f5:char; + sec: Array [1..2] of char; + end; + PConvert= ^TConvert; +var + yr, mn, dy, h, m, s: Integer; + d: TDateTime; + t: TDateTime; +begin + Result := 0; + if Length(AStr) = 10 then + AStr := AStr + ' 00:00:00'; + if Length(AStr) * SizeOf(Char) >= SizeOf(TConvert) then // take care of Delphi's WideChars + with PConvert(@AStr[1])^ do + if TryStrToInt(year, yr) and + TryStrToInt(mon, mn) and + TryStrToInt(day, dy) and + TryEncodeDate(yr, mn, dy, d) + and + TryStrToInt(hr, h) and + TryStrToInt(min, m) and + TryStrToInt(sec, s) and + TryEncodeTime(h, m, s, 0, t) + then + Result := d + t; +end; + +function TDateTimeTag.GetAsString: String; +var + dt: TDateTime; + i: Integer; +begin + dt := GetDateTime; + Result := FormatDateTime(GetFormat, dt); + if dt = 0 then + for i:= 1 to Length(Result) do + if Result[i] in ['1'..'9'] then Result[i] := '0'; +end; + +function TDateTimeTag.GetDateTime: TDateTime; +var + s: String; +begin + s := inherited GetAsString; + Result := ExifDateToDateTime(s); +end; + +function TDateTimeTag.GetFormat: String; +begin + Result := IfThen(FFormatStr = '', + fpExifFmtSettings.ShortDateFormat + ' ' + fpExifFmtSettings.LongTimeFormat, + FFormatStr + ); +end; + +procedure TDateTimeTag.SetAsString(const AValue: String); +var + d: TDateTime; + {$IFNDEF FPC} + fs: TFormatSettings; + p: Integer; + fmt: String; + {$ENDIF} +begin + {$IFDEF FPC} + d := ScanDateTime(GetFormat, AValue); + {$ELSE} + fmt := GetFormat; + fs := fpExifFmtSettings; + p := pos(' ', fmt); + if p <> 0 then begin + fs.ShortDateFormat := Copy(fmt, 1, p-1); + fs.LongTimeFormat := Copy(fmt, p+1, MaxInt); + d := StrToDateTime(AValue, fs); + end else begin + fs.ShortDateFormat := fmt; + d := StrToDate(AValue, fs); + end; + {$ENDIF} + SetDateTime(d); +end; + +procedure TDateTimeTag.SetDateTime(const AValue: TDateTime); +var + s: string; +begin + s := FormatDateTime(EXIF_DATETIME_FORMAT, AValue); + inherited SetAsString(s); +end; + + +//============================================================================== +// TGPSPositionTag +//============================================================================== + +function TGPSPositionTag.GetAsFloat: Double; +var + arr: TExifDoubleArray; +begin + arr := GetAsFloatArray; + Result := arr[0] + arr[1]/60 + arr[2]/3600; +end; + +{ Parmeters in the FormatString are expected to be in this order + #0 degrees as integer + #1 minutes as integer + #2 seconds as float + #3 minutes + seconds as float (mins) + #4 degrees + minutes + seconds as float (degs) + Example: '%0:d° %3:.6'' --> 45° 12.123456' } +function TGPSPositionTag.GetAsString: String; +var + arr: TExifDoubleArray; + degs: Double; + mins: Double; +begin + arr := GetAsFloatArray; + if Length(arr) = 0 then begin + Result := ''; + exit; + end; + + degs := arr[0] + arr[1]/60 + arr[2]/3600; // Fix me: consider the case that all may be floats + mins := arr[1] + arr[2]/60; + Result := Format(FFormatStr, [arr[0], arr[1], arr[2], mins, degs], FpExifFmtSettings); +end; + +procedure TGPSPositionTag.SetAsFloat(const AValue: Double); +var + arr: TExifDoubleArray; +begin + SetLength(arr, 3); + SplitGps(AValue, arr[0], arr[1], arr[2]); + SetAsFloatArray(arr); +end; + +procedure TGPSPositionTag.SetAsString(const AValue: String); +var + deg: Double; +begin + if TryStrToGps(AValue, deg) then + SetAsFloat(deg) + else + raise EFpExif.CreateFmt('"%s" is not a valid GPS position string.', [AValue]); +end; + + +//============================================================================== +// TMakerNoteTag +//============================================================================== +constructor TMakerNoteIntegerTag.Create(ATagID, AIndex: Integer; AName: String; + AValue: Integer; ALkupTbl, AFormatStr: String; ATagType: TTagType; + AOptions: TTagOptions); +begin + if not (ATagType in [ttUInt8, ttUInt16, ttUInt32, ttSInt8, ttSInt16, ttSInt32]) then + raise EFpExif.Create('Tag type not allowed for TMakerNoteIntegerTag'); + + FTagID := ATagID; //AIndex; + FGroup := tgExifMakerNote; + FName := AName; + FDesc := ''; + FType := ATagType; + FLkupTbl := ALkupTbl; + FFormatStr := AFormatStr; + FOptions := [toReadOnly, toVolatile] + AOptions; + FCount := 1; + SetLength(FRawData, TagElementSize[ord(FType)]); + SetInteger(0, AValue, false); // false: MakeNote tags are poorly defined -> don't crash +end; + +constructor TMakerNoteFloatTag.Create(ATagID, AIndex: Integer; AName: String; + AValue: Double; AFormatStr: String; ATagType: TTagType; + AOptions: TTagOptions); +begin + if not (ATagType in [ttURational, ttSRational]) then + raise EFpExif.Create('Tag type not allowed for TMakerNoteFloatTag'); + + FTagID := ATagID; //AIndex; + FGroup := tgExifMakerNote; + FName := AName; + FDesc := ''; + FType := ATagType; + FFormatStr := AFormatStr; + FOptions := [toReadOnly, toVolatile] + AOptions; + + AsFloat := AValue; +end; + + +//============================================================================== +// TExposureTimeTag +//============================================================================== +{ The FormatStr of the ExposureTag consists of 2 sections separated by a colon: + - 1st part for times < 1s, using reciprocal exposure time + - 2nd part for times >= 1s, using (non-reciprocal) exposure time + If only a single section is used then it is applied to all + (non-reciprocal) exposure times. + Example: '1/%.0f;%.0f' } +function TExposureTimeTag.GetAsString: String; +var + floatVal: Double; + fmt1, fmt2: String; + p: Integer; +begin + floatVal := GetAsFloat; + if FFormatStr = '' then begin + if IsNaN(floatVal) then + Result := '' + else if floatVal >= 10 then + Result := Format('%.0fs', [floatVal]) + else if floatVal >= 1 then + Result := Format('%.1fs', [floatVal]) + else + Result := Format('1/%.0fs', [1.0/floatVal]); + end else + begin + p := pos(';', FFormatStr); + if p > 0 then begin + fmt1 := copy(FFormatStr, 1, p-1); + fmt2 := copy(FFormatStr, p+1, MaxInt); + if floatVal < 1.0 then + Result := Format(fmt1, [1.0/floatVal]) + else + Result := Format(fmt2, [floatVal]); + end else + Result := Format(FFormatStr, [floatVal]); + end; +end; + +procedure TExposureTimeTag.SetAsString(const AValue: String); +var + i: Integer; + s, sNum, sDenom: String; + r: TExifRational; + floatVal: Double; + code: Integer; +begin + s := ''; + snum := ''; + sdenom := ''; + for i:=1 to Length(AValue) do + if AValue[i] in ['0'..'9','.'] then + s := s + AValue[i] + else + if AValue[i] = ',' then + s := s + '.' + else + if AValue[i] = '/' then begin + snum := s; + s := ''; + end; + if snum <> '' then begin + sdenom := s; + r.Numerator := StrToInt(snum); + r.Denominator := StrToInt(sdenom); + SetAsRational(r); + end else begin + val(s, floatVal, code); + SetAsFloat(floatVal); + end; +end; + + +//============================================================================== +// TShutterSpeedTag +// +// Sputter speed value (Tv) is stored in APEX units: +// Tv := -log2(t), t = exposure time in seconds +// http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf +//============================================================================== + +function TShutterSpeedTag.GetRational(AIndex: Integer; + out AValue: TExifRational): Boolean; +var + r: TExifRational; + dbl: double; +begin + Result := inherited GetRational(AIndex, r); + if Result then begin + dbl := r.Numerator / r.Denominator; + AValue := FloatToRational(Power(2.0, -dbl), 1E-9); + end; +end; + +procedure TShutterSpeedTag.SetFloat(AIndex: integer; const AValue: Double); +begin + inherited SetFloat(AIndex, -log2(AValue)); +end; + +procedure TShutterSpeedTag.SetRational(AIndex: Integer; const AValue: TExifRational); +begin + SetFloat(AIndex, AValue.Numerator / AValue.Denominator); +end; + + +//============================================================================== +// TApertureTag +// +// Aperture value (AV) is stored in APEX units: +// AV = 2 log2(FNumber) +// see http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf +//============================================================================== + +function TApertureTag.GetFloat(AIndex: Integer; out AValue: Double): Boolean; +var + dbl: Double; +begin + Result := inherited GetFloat(AIndex, dbl); + AValue := Power(2.0, dbl * 0.5); +end; + +function TApertureTag.GetRational(AIndex: Integer; + out AValue: TExifRational): Boolean; +var + r: TExifRational; + dbl: Double; +begin + Result := inherited GetRational(AIndex, r); + dbl := r.Numerator / r.Denominator; + AValue := FloatToRational(Power(2.0, dbl/2), 1E-9); +end; + +procedure TApertureTag.SetFloat(AIndex: integer; const AValue: Double); +begin + inherited SetFloat(AIndex, 2.0 * log2(AValue)); +end; + +procedure TApertureTag.SetRational(AIndex: Integer; const AValue: TExifRational); +begin + SetFloat(AIndex, AValue.Numerator / AValue.Denominator); +end; + + +//============================================================================== +// TUserCommentTag +//============================================================================== + +function TUserCommentTag.GetAsString: String; +var + sw: WideString; + sa: AnsiString; +begin + Result := ''; + + if PosInBytes('UNICODE', FRawData) = 0 then begin + SetLength(sw, (Length(FRawData) - 8) div SizeOf(WideChar)); + Move(FRawData[8], sw[1], Length(sw) * SizeOf(WideChar)); + if BigEndian then sw := BEtoN(sw) else sw := LEtoN(sw); + {$IFDEF FPC} + Result := UTF8Encode(sw); + {$ELSE} + Result := sw; + {$ENDIF} + end else + if PosInBytes('ASCII', FRawData) = 0 then begin + SetLength(sa, Length(FRawData) - 8); + Move(FRawData[8], sa[1], Length(sa)); + Result := sa; + end else + if PosInBytes(#0#0#0#0#0#0#0#0, FRawData) = 0 then begin + SetLength(sa, Length(FRawData) - 8); + Move(FRawData[8], sa[1], Length(sa)); + {$IFDEF FPC} + {$IFDEF FPC3+} + Result := WinCPToUTF8(sa); + {$ELSE} + Result := SysToUTF8(sa); + {$ENDIF} + {$ELSE} + Result := sa; + {$ENDIF} + end else + if PosInBytes('JIS', FRawData) = 0 then + raise EFpExif.Create('JIS-encoded user comment is not supported.'); + + while (Result <> '') and (Result[Length(Result)] = #0) do + Delete(Result, Length(Result), 1); +end; + +// Note: No trailing zero needed here. +procedure TUserCommentTag.SetAsString(const AValue: String); +var + i: integer; + sw: WideString; + sa: AnsiString; + isASCII: Boolean; +begin + if AValue = '' then + SetLength(FRawData, 0) + else + begin + isASCII := true; + for i:=1 to Length(AValue) do + if AValue[i] > #127 then begin + isASCII := false; + break; + end; + + if isASCII then + begin + SetLength(FRawData, 8 + Length(AValue)); + sa := 'ASCII'#0#0#0; + Move(sa[1], FRawData[0], 8); + sa := ansistring(AValue); + Move(sa[1], FRawData[8], Length(sa)); + end else + begin + {$IFDEF FPC} + sw := UTF8Decode(AValue); + {$ELSE} + sw := AValue; + {$ENDIF} + if BigEndian then sw := NtoBE(sw) else sw := NtoLE(sw); + SetLength(FRawData, 8 + Length(sw) * SizeOf(WideChar)); // +8 for header + sa := 'UNICODE'#0; + Move(sa[1], FRawData[0], 8); + Move(sw[1], FRawData[8], Length(sw) * SizeOf(WideChar)); + end; + end; + FCount := Length(FRawData); +end; + + +//============================================================================== +// TXPTag +// +// tag used by Windows, encoded in UCS2 +// See http://www.exiv2.org/tags.html +//============================================================================== + +function TXPTag.GetAsString: String; +var + ws: WideString; +begin + SetLength(ws, Length(FRawData) div SizeOf(WideChar)); + Move(FRawData[0], ws[1], Length(FRawData)); + Result := UTF8Encode(ws); +end; + + +initialization + +finalization + FreeExifTagDefs; + +end. + diff --git a/components/fpexif/fpeexifreadwrite.pas b/components/fpexif/fpeexifreadwrite.pas new file mode 100644 index 000000000..634dbb2ac --- /dev/null +++ b/components/fpexif/fpeexifreadwrite.pas @@ -0,0 +1,1146 @@ +{ Writer for EXIF data + + Writes the TIFF part of the APP0 segment. + In a JPEG image, the header of the APP0 segment must have been written before. +} + +unit fpeExifReadWrite; + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +interface + +uses + Classes, SysUtils, + fpeGlobal, fpeUtils, fpeMetadata, fpeTags, fpeExifData; + +type + TTiffHeader = packed record + BOM: Array[0..1] of AnsiChar; // 'II' for little endian, 'MM' for big endian + Signature: Word; // Signature (42) + IFDOffset: DWord; // Offset where image data begin, from start of TIFF header + end; + + TIFDRecord = packed record + TagID: Word; + DataType: Word; + DataCount: DWord; + DataValue: DWord; + end; + { A note on DataCount, from the EXIF specification: + "Count - The number of values. It should be noted carefully that the count + is not the sum of the bytes. In the case of one value of SHORT (16 bits), + for example, the count is '1' even though it is 2 Bytes." } + + TBasicExifReader = class(TBasicMetadataReader) + protected + FStartPosition: Int64; // Beginning of TIFF header + FBigEndian: Boolean; + function AddTag(AStream: TStream; const AIFDRecord: TIFDRecord; + const AData: TBytes; AParent: TTagID): Integer; virtual; + function FindTagDef(ATagID: TTagID): TTagDef; virtual; + function FixEndian16(AValue: Word): Word; + function FixEndian32(AValue: DWord): DWord; +// procedure ReadIFD(AStream: TStream; AGroup: TTagGroup); virtual; //overload; + procedure ReadIFD(AStream: TStream; AParent: TTagID); virtual; + end; + + TExifReader = class(TBasicExifReader) + private + FThumbPosition: Int64; + FThumbSize: Integer; + FExifVersion: AnsiString; + protected + FMake: String; + FModel: String; + function AddTag(AStream: TStream; const AIFDRecord: TIFDRecord; + const AData: TBytes; AParent: TTagID): Integer; override; + function FindTagDef(ATagID: TTagID): TTagDef; override; + procedure ReadIFD(AStream: TStream; AParent: TTagID); override; + public + constructor Create(AImgInfo: TImgInfo); override; + function ReadExifHeader(AStream: TStream): Boolean; + procedure ReadFromStream(AStream: TStream; AImgFormat: TImgFormat); override; + function ReadTiffHeader(AStream: TStream; out ABigEndian: Boolean): Boolean; + property BigEndian: Boolean read FBigEndian; + end; + + TMakerNoteReader = class(TBasicExifReader) + private + FExifVersion: string; + protected + FMake: String; + FModel: String; + FTagDefs: TTagDefList; + FDataStartPosition: Int64; + procedure Error(const AMsg: String); override; + function FindTagDef(ATagID: TTagID): TTagDef; override; + procedure GetTagDefs(AStream: TStream; AImgFormat: TImgFormat); virtual; + function Prepare(AStream: TStream): Boolean; virtual; + public + constructor Create(AImgInfo: TImgInfo; AStartPos: Int64; + const AMake, AModel, AExifVersion: string; ABigEndian: Boolean); reintroduce; + destructor Destroy; override; + procedure ReadFromStream(AStream: TStream; AImgFormat: TImgFormat); override; + end; + + TMakerNoteReaderClass = class of TMakerNoteReader; + + TExifWriter = class(TBasicMetadataWriter) + private + FBigEndian: Boolean; + FTiffHeaderPosition: Int64; + FExifSegmentStartPos: Int64; + protected + function CalcOffsetFromTiffHeader(APosition: Int64): DWord; + function CanWriteTag(ATag: TTag): Boolean; + function FixEndian16(AValue: Word): Word; + function FixEndian32(AValue: DWord): DWord; + procedure WriteExifHeader(AStream: TStream); + procedure WriteIFD(AStream: TStream; ASubIFDList: TInt64List; AParentID: TTagID); + procedure WriteSubIFDs(AStream: TStream; ASubIFDList: TInt64List); + procedure WriteTag(AStream, AValueStream: TStream; ADataStartOffset: Int64; + ATag: TTag); + procedure WriteTiffHeader(AStream: TStream); + public + constructor Create(AImgInfo: TImgInfo); override; + procedure WriteToStream(AStream: TStream; AImgFormat: TImgFormat); override; + property BigEndian: Boolean read FBigEndian write FBigEndian; + end; + +procedure RegisterMakerNoteReader(AClass: TMakerNoteReaderClass; AMake, AModel: String); +function GetMakerNoteReaderClass(AMake, AModel: String): TMakerNoteReaderClass; + + +implementation + +uses + Math, Contnrs, + fpeStrConsts, fpeMakerNote, fpeIptcReadWrite; + +const + EXIF_SIGNATURE: array[0..5] of AnsiChar = ('E', 'x', 'i', 'f', #0, #0); + LITTLE_ENDIAN_BOM: array[0..1] of AnsiChar = ('I', 'I'); + BIG_ENDIAN_BOM: array[0..1] of AnsiChar = ('M', 'M'); + +type + TReaderItem = class + ReaderClass: TMakerNoteReaderClass; + Make: String; + Model: String; + end; + +var + RegisteredReaders: TObjectList = nil; + +function GetRegisteredReader(AMake, AModel: String): Integer; +var + item: TReaderItem; + ucMake: String; + Makes: TStrings; + j: Integer; +begin + if RegisteredReaders <> nil then + begin + Makes := TStringList.Create; + try + ucMake := Uppercase(AMake); + for Result:=0 to RegisteredReaders.Count-1 do begin + item := TReaderItem(RegisteredReaders[Result]); + Makes.Text := item.Make; + for j := 0 to Makes.Count-1 do begin + if pos(Uppercase(Makes[j]), ucMake) <> 0 then + if (item.Model = '') or (AModel = '') or SameText(item.Model, AModel) then + exit; + end; + end; + finally + Makes.Free; + end; + end; + Result := -1; +end; + +procedure RegisterMakerNoteReader(AClass: TMakerNoteReaderClass; AMake: String; + AModel: String); +var + item: TReaderItem; + idx: Integer; +begin + if RegisteredReaders = nil then + RegisteredReaders := TObjectList.Create; + + idx := GetRegisteredReader(AMake, AModel); + if idx = -1 then begin + item := TReaderItem.Create; + item.ReaderClass := AClass; + item.Make := AMake; + item.Model := AModel; + idx := RegisteredReaders.Add(item); + end else begin + item := TReaderItem(RegisteredReaders[idx]); + item.ReaderClass := AClass; + item.Make := AMake; + item.Model := AModel; + end; +end; + +function GetMakerNoteReaderClass(AMake, AModel: String): TMakerNoteReaderClass; +var + idx: Integer; +begin + idx := GetRegisteredReader(AMake, AModel); + if idx = -1 then + Result := TMakerNoteReader + else + Result := TReaderItem(RegisteredReaders[idx]).ReaderClass; +end; + + +//============================================================================== +// TBasicExifReader +//============================================================================== + +//------------------------------------------------------------------------------ +// Creates a tag from the IFD record and its data, and adds it to the tag list +// of the Exif. +// AParent is the ID of the sub-IFD to which the tag will belong (ID must already +// be left-shifted by 16 bits) +//------------------------------------------------------------------------------ +function TBasicExifReader.AddTag(AStream: TStream; const AIFDRecord: TIFDRecord; + const AData: TBytes; AParent: TTagID): Integer; +var + tag: TTag; + tagDef: TTagDef; + newTagDef: TTagDef; + optns: TTagOptions; + tagIDRec: TTagIDRec; +begin + Unused(AStream); + + Result := -1; + + // Find the definition of the tag as specified by the ifd record + tagDef := FindTagDef(AIFDRecord.TagID or AParent); + if tagDef = nil then + begin + if (AIFDRecord.DataType < 1) or (AIFDRecord.DataType > 10) then begin + Error(Format('Unknown tag $%.4x has invalid datatype (%d)', [AIFDRecord.TagID, AIFDRecord.DataType])); + exit; + end; + + tagIDRec.Tag := AIFDRecord.TagID; + tagIDRec.Parent := TTagIDRec(AParent).Parent; + + newTagDef := TTagDef.Create; + newTagDef.TagIDRec := tagIDRec; + newTagDef.TagType := TTagType(AIFDRecord.DataType); + newTagDef.TagClass := DefaultTagClasses[newTagDef.TagType]; + newTagDef.ReadOnly := true; + tagDef := newTagDef; + end else + newTagDef := nil; + + // Populate the tag + optns := []; + if FBigEndian then Include(optns, toBigEndian); + if (eoTruncateBinary in FImgInfo.ExifData.ExportOptions) then + Include(optns, toTruncateBinary); + if (eoDecodeValue in FImgInfo.ExifData.ExportOptions) then + Include(optns, toDecodeValue); + tag := tagDef.TagClass.Create(tagDef, optns); + tag.TagType := TTagType(AIFDRecord.DataType); + tag.RawData := AData; + tag.Count := AIFDRecord.DataCount; // must be after setting RawData, its calculation of Count may be wrong! + + // Add the tag to the EXIF tag list + Result := FImgInfo.ExifData.AddOrReplaceTag(tag); + + newTagDef.Free; +end; + +//------------------------------------------------------------------------------ +// Looks for the tag with specified TagID and Group. Must be overridden by +// descendant classes. +//------------------------------------------------------------------------------ +function TBasicExifReader.FindTagDef(ATagID: TTagID): TTagDef; +begin + Unused(ATagID); + Result := nil; +end; + +//------------------------------------------------------------------------------ +// Converts a 2-byte integer from big endian byte order to system endianness. +//------------------------------------------------------------------------------ +function TBasicExifReader.FixEndian16(AValue: Word): Word; +begin + if FBigEndian then + Result := BEtoN(AValue) + else + Result := LEtoN(AValue); +end; + +//------------------------------------------------------------------------------ +// Converts a 4-byte integer from big endian byte order to system endianness. +//------------------------------------------------------------------------------ +function TBasicExifReader.FixEndian32(AValue: DWord): DWord; +begin + if FBigEndian then + Result := BEtoN(AValue) + else + Result := LEtoN(AValue); +end; + +//------------------------------------------------------------------------------ +// Reads the image file directory (IFD) starting at the current stream position +// and adds the found tags to the specified group +//------------------------------------------------------------------------------ +//procedure TBasicExifReader.ReadIFD(AStream: TStream; AGroup: TTagGroup); +procedure TBasicExifReader.ReadIFD(AStream: TStream; AParent: TTagID); +var + numRecords: Word; + i: Integer; + ifdRec: TIFDRecord; + byteCount: Integer; + data: TBytes; + n: Int64; + tagPos: Int64; + newPos: Int64; +begin + // Read count of directory entries + numRecords := FixEndian16(ReadWord(AStream)); + if (AParent = TAGPARENT_THUMBNAIL) and (numRecords > 10) then begin + Warning(rsMoreThumbnailTagsThanExpected); + exit; + end; + + tagPos := AStream.Position; + for i:=1 to numRecords do begin + AStream.Position := tagPos; + // Read directory entry... + n := SizeOf(ifdRec); + if AStream.Read(ifdRec{%H-}, n) < n then begin + Error(Format(rsReadIncompleteIFDRecord, [tagPos])); + exit; + end; + + ifdRec.TagID := FixEndian16(ifdRec.TagID); + ifdRec.DataType := FixEndian16(ifdRec.DataType); + if not (ifdRec.DataType in [1..ord(High(TTagType))]) then begin + Error(Format(rsIncorrectTagType, [ifdRec.DataType, i, ifdRec.TagID, FImgInfo.Filename])); + exit; + end; + + ifdRec.DataCount := FixEndian32(ifdRec.DataCount); + // ifRec.DataValue will be converted later. + byteCount := Integer(ifdRec.DataCount) * TagElementSize[ifdRec.DataType]; + SetLength(data, bytecount); + if byteCount <= 4 then + Move(ifdRec.DataValue, data[0], byteCount) + else begin + AStream.Position := FStartPosition + FixEndian32(ifdRec.DataValue); + AStream.Read(data[0], byteCount); + end; + AddTag(AStream, ifdRec, data, AParent); + + if ifdRec.DataType = ord(ttIFD) then begin + newPos := FStartPosition + FixEndian32(ifdRec.DataValue); + if newPos < AStream.Size then begin + AStream.Position := newPos; + ReadIFD(AStream, ifdRec.TagID shl 16); + end; + end; + + tagPos := tagPos + SizeOf(TIFDRecord); + end; + AStream.Position := tagPos; +end; + + +//============================================================================== +// TExifReader +//============================================================================== + +//------------------------------------------------------------------------------ +// Constructor of the EXIF reader +//------------------------------------------------------------------------------ +constructor TExifReader.Create(AImgInfo: TImgInfo); +begin + inherited; + FStartPosition := -1; +end; + +//------------------------------------------------------------------------------ +// Creates a tag from the specified IFD record and its data, and adds it to the +// corresponding tag list of the EXIF object. +//------------------------------------------------------------------------------ +function TExifReader.AddTag(AStream: TStream; const AIFDRecord: TIFDRecord; + const AData: TBytes; AParent: TTagID): Integer; +var + p: Int64; + iptcreader: TIPTCReader; + makernotereader: TMakerNoteReader; + readerClass: TMakerNoteReaderClass; + tag: TTag; +begin + Result := inherited AddTag(AStream, AIFDRecord, AData, AParent); + if Result = -1 then + exit; + + tag := FImgInfo.ExifData.TagByIndex[Result]; + if (tag is TOffsetTag) then + TOffsetTag(tag).TiffHeaderOffset := FStartPosition; + + // Special handling for some tags + case tag.TagID of + FULLTAG_MAKE: + FMake := tag.AsString; + FULLTAG_MODEL: + FModel := tag.AsString; + FULLTAG_THUMBSTARTOFFSET: + FThumbPosition := FStartPosition + FixEndian32(AIFDRecord.DataValue); + FULLTAG_THUMBSIZE: + FThumbSize := FixEndian32(AIFDRecord.DataValue); + FULLTAG_EXIFVERSION: + begin + SetLength(FExifVersion, Length(AData)); + Move(AData[0], FExifVersion[1], Length(FExifVersion)); + end; + FULLTAG_MAKERNOTE: + begin + // The stream is at the end of the makernote data area --> rewind it to start + AStream.Position := AStream.Position - Length(AData); + readerClass := GetMakerNoteReaderClass(FMake, FModel); + makernotereader := readerClass.Create(FImgInfo, FStartPosition, FMake, FModel, FExifVersion, FBigEndian); + try + makernotereader.ReadFromStream(AStream, FImgFormat); + finally + makernotereader.Free; + end; + end; + FULLTAG_IPTC: // Reads the IPTC tags as used in TIFF files. + if Length(tag.RawData) <> 0 then + begin + FImgInfo.CreateIptcData; + iptcReader := TIptcReader.Create(FImgInfo); + try + iptcReader.ReadIPTCData(tag.RawData); + finally + iptcReader.Free; + end; + end; + end; + + // Some tags define a subdirectory --> Read it recursively + if (tag is TSubIFDTag) then begin + p := AStream.Position; + try + AStream.Position := FStartPosition + FixEndian32(AIFDRecord.DataValue); + ReadIFD(AStream, tag.TagID shl 16); + finally + AStream.Position := p; + end; + end; +end; + +function TExifReader.FindTagDef(ATagID: TTagID): TTagDef; +begin + Result := FindExifTagDef(ATagID); +end; + +//------------------------------------------------------------------------------ +// For JPEG files only: +// Reads the header of the APP1 jpeg segment ("EXIF segment") +// Note that the segment marker and the segment size already have been read. +// The function returns FALSE if the header is not valid. +// Call ReadFromStream immediately afterwards +//------------------------------------------------------------------------------ +function TExifReader.ReadExifHeader(AStream: TStream): Boolean; +var + hdr: array[0..5] of ansichar; +begin + AStream.Read({%H-}hdr[0], SizeOf(hdr)); + Result := CompareMem(@hdr[0], @EXIF_SIGNATURE[0], SizeOf(hdr)); +end; + +//------------------------------------------------------------------------------ +// Public method for reading the IFDs of the EXIF structure. +// +// IT IS REQUIRED THAT THE METHOD IS CALLED WHEN THE STREAM IS RIGHT AFTER +// THE TIFF HEADER. +//------------------------------------------------------------------------------ +procedure TExifReader.ReadFromStream(AStream: TStream; AImgFormat: TImgFormat); +begin + FThumbPosition := -1; + FThumbSize := 0; + FImgFormat := AImgFormat; + + FImgInfo.ExifData.BeginReading; + try + // Read IFD0 (primary directory). This routine will recursively also read + // the thumbnail directory (IFD1) and any subdirectories. + ReadIFD(AStream, TAGPARENT_PRIMARY); + finally + FImgInfo.ExifData.EndReading; + end; +end; + +//------------------------------------------------------------------------------ +// Read an image file directory (IFD) from the stream. +// The directory is specified by the parameter AGroup. +//------------------------------------------------------------------------------ +procedure TExifReader.ReadIFD(AStream: TStream; AParent: TTagID); +var + p: Int64; + // thumbBuff: TBytes; +begin + inherited ReadIFD(AStream, AParent); + + // The primary directory has the offset to the thumbnail directory (IFD1) as + // last DWord entry + if AParent = TAGPARENT_PRIMARY then + begin + // Read the offset from the stream + p := FixEndian32(ReadDWord(AStream)); + if p > 0 then begin + // Move stream to beginning of IFD1... + p := FStartPosition + p; + if p < AStream.Size then begin + AStream.Position := p; + // ... and read IFD1 + ReadIFD(AStream, TAGPARENT_THUMBNAIL); + end; + end; + end; + + // In case of the thumbnail directory we read the thumbnail if available. + if (AParent = TAGPARENT_THUMBNAIL) and + (FThumbPosition > -1) and (FThumbSize > 0) and + (FThumbPosition < FThumbSize) then + begin + // Move stream to beginning of thumbnail... + AStream.Position := FThumbPosition; + // ... and read thumbnail from stream to EXIF + FImgInfo.ExifData.LoadThumbnailFromStream(AStream, FThumbsize, false); + end; +end; + + +//------------------------------------------------------------------------------ +// Reads the TIFF header which is before the EXIF structure and returns the +// endianness used in this file. +// NOTE: ReadFromStream must be called immediately afterwards +//------------------------------------------------------------------------------ +function TExifReader.ReadTiffHeader(AStream: TStream; + out ABigEndian: Boolean): Boolean; +var + hdr: TTiffHeader; +begin + Result := false; + + // The stream is at the beginning of the TIFF header. We store this + // position because all offsets within the EXIF segment are relative to + // the beginning of the TIFF header. + FStartPosition := AStream.Position; + + // Determine endianness + AStream.Read(hdr{%H-}, SizeOf(hdr)); + if CompareMem(@hdr.BOM[0], @BIG_ENDIAN_BOM[0], SizeOf(BIG_ENDIAN_BOM)) then + FBigEndian := true + else + if CompareMem(@hdr.BOM[0], @LITTLE_ENDIAN_BOM[0], SizeOf(LITTLE_ENDIAN_BOM)) then + FBigEndian := false + else + exit; + + ABigEndian := FBigEndian; + + // Check signature byte + hdr.Signature := FixEndian16(hdr.Signature); + if hdr.Signature <> 42 then + exit; + + // Determine where the first directory (IFD0) begins... + hdr.IFDOffset := FixEndian32(hdr.IFDOffset); + + // ... and move stream to there. + AStream.Position := FStartPosition + hdr.IFDOffset; + + Result := true; +end; + + +//============================================================================== +// TMakerNoteReader +//============================================================================== +constructor TMakerNoteReader.Create(AImgInfo: TImgInfo; AStartPos: Int64; + const AMake, AModel, AExifVersion: String; ABigEndian: Boolean); +begin + inherited Create(AImgInfo); + FTagDefs := TTagDefList.Create; + FStartPosition := AStartPos; + FDataStartPosition := -1; + FMake := AMake; + FModel := AModel; + FExifVersion := AExifVersion; + FBigEndian := ABigEndian; +end; + +destructor TMakerNoteReader.Destroy; +begin + FTagDefs.Free; + inherited; +end; + +{ Since the MakerNotes are not well-defined we don't want to abort reading of + the entire file by an incorrectly interpreted MakeNote tag. + IMPORTANT: All methods calling Error() must be exited afterwards because + the faulty file structure may lead to crashes. } +procedure TMakerNoteReader.Error(const AMsg: String); +begin + Warning(AMsg); +end; + +function TMakerNoteReader.FindTagDef(ATagID: TTagID): TTagDef; +var + i: Integer; +begin + if FTagDefs <> nil then + begin + for i:=0 to FTagDefs.Count-1 do begin + Result := FTagDefs[i]; + if Result.TagID = ATagID then + exit; + end; + end; + Result := nil; +end; + +procedure TMakerNoteReader.GetTagDefs(AStream: TStream; AImgFormat: TImgFormat); +var + UCMake, {%H-}UCModel: String; + tmp, tmp2: String; + p: Integer; + streamPos: Int64; +begin + UCMake := Uppercase(FMake); + UCModel := Uppercase(FModel); + + if UCMake = 'CANON' then + BuildCanonTagDefs(FTagDefs) + else + if UCMake = 'SEIKO' then + BuildEpsonTagDefs(FTagDefs) + else + if UCMake = 'SANYO' then + BuildSanyoTagDefs(FTagDefs) + else + if pos('MINOLTA', UCMake) = 1 then + BuildMinoltaTagDefs(FTagDefs) + else + if UCMake = 'FUJI' then begin + FBigEndian := false; + BuildFujiTagDefs(FTagDefs) + end else + (* + if pos('OLYMP', UCMake) = 1 then + //BuildOlympusTagDefs(FTagDefs) -- is done by specific Olympus reader + else + if UCMake = 'CASIO' then + { + streamPos := AStream.Position; + if PosInStream('QVC', AStream, streamPos) <> -1 then begin + FTagDefs := @Casio1Table; + FNumTagDefs := Length(Casio1Table); + end else begin + FTagDefs := @Casio12Table; + FNumTagDefs := Length(Casio2Table); + end; + } + BuildCasio1TagDefs(FTagDefs) + else + *) + if UCMake = 'NIKON' then begin + SetLength(tmp, 5); + streamPos := AStream.Position; + AStream.Read(tmp[1], 5); + AStream.Position := streamPos; + p := Max(0, Pos(' ', FModel)); + tmp2 := FModel[p+1]; + if (FExifVersion > '0210') or + ((FExifVersion = '') and (tmp2 = 'D') and (AImgFormat = ifTiff)) + then + BuildNikon2TagDefs(FTagDefs) + else + if (tmp = 'Nikon') then + BuildNikon1TagDefs(FTagDefs) + else + BuildNikon2TagDefs(FTagDefs); + end; +end; + +function TMakerNoteReader.Prepare(AStream: TStream): Boolean; +begin + Unused(AStream); + Result := true; +end; + +procedure TMakerNoteReader.ReadFromStream(AStream: TStream; AImgFormat: TImgFormat); +begin + if FDataStartPosition = -1 then + FDataStartPosition := AStream.Position; + FImgFormat := AImgFormat; + GetTagDefs(AStream, AImgFormat); + if FTagDefs.Count = 0 then + exit; + AStream.Position := FDataStartPosition; + if not Prepare(AStream) then + exit; + ReadIFD(AStream, TAGPARENT_MAKERNOTE); +end; + + +//============================================================================== +// TExifWriter +//============================================================================== + +//------------------------------------------------------------------------------ +// Constructor of the EXIF writer +//------------------------------------------------------------------------------ +constructor TExifWriter.Create; +begin + inherited; + FExifSegmentStartPos := -1; +end; + +//------------------------------------------------------------------------------ +// Calculates the difference of the specified stream position to the position +// where the TIFF header starts. +//------------------------------------------------------------------------------ +function TExifWriter.CalcOffsetFromTiffHeader(APosition: Int64): DWord; +begin + if APosition > FTiffHeaderPosition then + Result := DWord(APosition - FTiffHeaderPosition) + else + Error('Incorrect stream position'); +end; + +//------------------------------------------------------------------------------ +// Returns false if the specified tag must not be written to the stream. +// This happens if the option toVolatile of the tag's Options is set. +//------------------------------------------------------------------------------ +function TExifWriter.CanWriteTag(ATag: TTag): Boolean; +begin + Result := (ATag <> nil) and (not ATag.IsVolatile); +end; + +//------------------------------------------------------------------------------ +// Converts a 2-byte integer to BigEndian format if required +//------------------------------------------------------------------------------ +function TExifWriter.FixEndian16(AValue: Word): Word; +begin + if FBigEndian then + Result := NtoBE(AValue) + else + Result := NtoLE(AValue); +end; + +//------------------------------------------------------------------------------ +// Converts a 4-byte integer to BigEndian format if required +//------------------------------------------------------------------------------ +function TExifWriter.FixEndian32(AValue: DWord): DWord; +begin + if FBigEndian then + Result := NtoBE(AValue) + else + Result := NtoLE(AValue); +end; + +//------------------------------------------------------------------------------ +// Writes the Exif header needed by JPEG files. +// Call WriteToStream immediately afterwards +//------------------------------------------------------------------------------ +procedure TExifWriter.WriteExifHeader(AStream: TStream); +const + SEGMENT_MARKER: array[0..1] of byte = ($FF, $E1); + SIZE: Word = 0; +begin + FExifSegmentStartPos := AStream.Position; + AStream.WriteBuffer(SEGMENT_MARKER[0], 2); + // Next two zero bytes are the size of the entire Exif segiment, they will be + // replaced when the segment is completely written. For this, we store the + // offset to the beginning of the EXIF segment in FExifSegmentStartPos. + AStream.WriteBuffer(SIZE, 2); + AStream.WriteBuffer(EXIF_SIGNATURE[0], 6); +end; + +//------------------------------------------------------------------------------ +// Writes all IFD records belonging to the same directory specified by the +// TagID of the tag which defines it. +// ASubIFDList is provided to collect all stream index positions with tags +// defining a sub-IFD; these sub-IFDs will be written later in WriteSubIFDs +// Data, in general, are written in the following order +// |<--- SubIFD records --->|<--- SubIFD data --->| +// In case of thumbnail directory (IFD1): +// |<--- IFD1 records --->|<--- Thumbnail image --->|<--- IFD1 data --->| +// +// ----------------------------------------------------------------------------- +procedure TExifWriter.WriteIFD(AStream: TStream; ASubIFDList: TInt64List; + AParentID: TTagID); +var + valueStream: TMemoryStream; + i: Integer; + count: Integer; + tag: TTag; + startPos: Int64; + sizeOfTagPart: DWord; + dataStartOffset: Int64; + thumbStartOffset: Int64; + offsetToIFD1: Int64; + w: Word; + dw: DWord; +begin + // Don't write MakerNote sub-tags, they are already contained in the data of + // the MAKERNOTE tag itself. + if AParentID = TAGPARENT_MAKERNOTE then + exit; + + valueStream := TMemoryStream.Create; + try + // Count IFD records in this directory + count := 0; + for i:=0 to FImgInfo.ExifData.TagCount-1 do begin + tag := FImgInfo.ExifData.TagByIndex[i]; + if (tag.TagID and $FFFF0000 = AParentID) and not (tag.IsVolatile) and tag.HasData then + inc(count); + end; + + // The IFD begins at the current stream position... + startPos := AStream.Position; + // ... and, knowing the size of the tag part of the subdirectory, we can + // calculate where the data part of the subdirectory will begin. + // This is needed as the offset from the beginning of the TIFF header. + sizeOfTagPart := SizeOf(Word) + // count of tags in IFD as 16bit integer + count * SizeOf(TIFDRecord) + // each tag occupies an IFDRecord + SizeOf(DWord); // 32-bit offset to next IFD, or terminating zero + dataStartOffset := startPos + sizeOfTagPart - FTiffHeaderPosition; + + // In case of IFD1 (Thumbnail group) the thumbnail will be written + // immediately after all tags of IFD1. This offset position must be noted + // in the tag. We calculate and store this value here for usage later. + if (AParentID = TAGPARENT_THUMBNAIL) and FImgInfo.HasThumbnail then begin + thumbStartOffset := dataStartOffset; + dataStartOffset := dataStartOffset + FImgInfo.ExifData.ThumbnailSize; + end else + thumbStartOffset := 0; + + // Write IFD record count as 16-bit integer + w := FixEndian16(count); + AStream.WriteBuffer(w, SizeOf(w)); + + // Now write all the records in this directory + if count > 0 then begin + for i:=0 to FImgInfo.ExifData.TagCount-1 do begin + tag := FImgInfo.ExifData.TagByIndex[i]; + + // Skip tags which do not belong to the requested group + if (tag.TagID and $FFFF0000 <> AParentID) or tag.IsVolatile or not tag.HasData then + Continue; + + // Offset to the thumbnail image + if tag.TagID = FULLTAG_THUMBSTARTOFFSET then begin + dw := FixEndian32(thumbStartOffset); + tag.AsInteger := dw; + end else + // Some tags will link to subdirectories. The offset to the start of + // a subdirectory must be specified in the DataValue field of the + // written ifd record. Since it is not clear at this moment where the + // subdirectory will begin we store the offset to the ifd record in + // ASubIFDlist for later correction. + if (tag is TSubIFDTag) and (tag.TagID <> FULLTAG_MAKERNOTE) + then + ASubIFDList.Add(AStream.Position); + + // Now write the tag + WriteTag(AStream, valueStream, datastartOffset, tag); + end; + end; + + // The last entry of the directory is the offset to the next IFD, or 0 + // if not other IFD follows at the same level. This affects only IFD0 + // where IFD1 can follow if an embedded thumbnail image exists. + if (AParentID = TAGPARENT_PRIMARY) and FImgInfo.HasThumbnail then begin + offsetToIFD1 := AStream.Position + SizeOf(DWord) + valuestream.Size; + dw := CalcOffsetFromTiffHeader(offsetToIFD1); + end else + dw := 0; + dw := FixEndian32(dw); + AStream.WriteBuffer(dw, SizeOf(dw)); + + // Write the thumbnail + if AParentID = TAGPARENT_THUMBNAIL then + FImgInfo.ExifData.SaveThumbnailToStream(AStream); + + // Copy the valuestream to the end of the tag stream (AStream) + valueStream.Seek(0, soFromBeginning); + AStream.CopyFrom(valueStream, valueStream.Size); + + // Rewind the stream to its end + AStream.Seek(0, soFromEnd); + finally + valueStream.Free; + end; +end; + +//------------------------------------------------------------------------------ +// The integer list ASubIFDList contains all the stream positions (in AStream) +// where tags begin which link to a subdirectory. +// WriteSubIFDs will read back the TagID of the subdirectory, write the tags +// of the subdirectory and write the position where the subdirectory starts +// to the tag's DataValue field in AStream. +//------------------------------------------------------------------------------ +procedure TExifWriter.WriteSubIFDs(AStream: TStream; ASubIFDList: TInt64List); +var + subIFDStartPos: Int64; + tagPos: Int64; + i: Integer; + tagid: TTagID; + rec: TIFDRecord; + offs: DWord; +begin + i := 0; + while i < ASubIFDList.Count do begin + // The current stream position is where the subdirectory tags will be + // begin. It must be written to the subdirectory tag's DataValue field. + subIFDStartPos := AStream.Position; + + // Extract the ID of the tag linking to the first subdirectory in the list + // from the already written stream. Use the offset stored in ASubIFDList + // to find it. + tagPos := ASubIFDList[0]; + AStream.Position := tagPos; + + // Read the tag's IFD record + AStream.ReadBuffer(rec{%H-}, SizeOf(rec)); + + // Get the TagID of the subdirectory (note: this might be written as big-endian) + // Then get the TagGroup corresponding to this tag; this is needed when calling WriteIFD + if FBigEndian then tagid := BEToN(rec.TagID) else tagid := LEtoN(rec.TagID); + + // Write the correct subdirectory start position to the IFD record + offs := CalcOffsetFromTiffHeader(subIFDStartPos); + rec.DataValue := FixEndian32(offs); + + // Write the IFD record back to the stream. Don't forget to return to + // where the tag starts! + AStream.Position := tagPos; + AStream.WriteBuffer(rec, SizeOf(rec)); + + // Now return the stream to the end (i.e. where the subdirectory should be) + // and write the tags of the subdirectory. + AStream.Seek(0, soFromEnd); + WriteIFD(AStream, ASubIFDList, tagID shl 16); + + // Delete the current SubIFDList entry because it has been handled now. + ASubIFDList.Delete(0); + end; +end; + +//------------------------------------------------------------------------------ +// Writes a tag and all its related elements to the stream as an IFDRecord. +// +// AStream: stream to which the tag is written +// AValueStream: Since the data of tags being longer than 4 bytes are written +// after the tag part of the streasm, but AStream has not seen all tags yet +// we temporarily write the data part into a separate "value stream". +// ADataStartOffset: Indiates the offset of the first data bytes in the +// value stream once it has been appended to the output stream (AStream). +// It is measureed from the beginning of the TIFF header. +// ATag: Tag entry to be written +//------------------------------------------------------------------------------ +procedure TExifWriter.WriteTag(AStream, AValueStream: TStream; + ADataStartOffset: Int64; ATag: TTag); +var + rec: TIFDRecord; + len: Integer; +begin + if (ATag = nil) or (not CanWriteTag(ATag)) or (not ATag.HasData) then + exit; + + // Calculate number of data bytes + len := ATag.Count * TagElementSize[ord(ATag.TagType)]; + + // Populate elements of the IFD record + rec.TagID := FixEndian16(TTagIDRec(ATag.TagID).Tag); + rec.DataType := FixEndian16(ord(ATag.TagType)); + rec.DataCount := FixEndian32(ATag.Count); + if len <= 4 then begin + rec.DataValue := 0; + Move(ATag.RawData[0], rec.DataValue, len); + end else + begin + rec.DataValue := FixEndian32(DWord(ADataStartOffset + AValueStream.Position)); + AValueStream.WriteBuffer(ATag.RawData[0], Length(ATag.RawData)); + end; + + // Write out + AStream.Write(rec, SizeOf(Rec)); +end; + (* +procedure TExifWriter.WriteTag(AStream, AValueStream: TStream; + ADataStartOffset: Int64; ATag: TTagEntry); +var + rec: TIFDRecord; + rat: TExifRational; + s: ansistring; + n: DWord; +begin + rec.TagID := FixEndian16(ATag.Tag); + rec.DataType := FixEndian16(ATag.TType); + if ATag.TType = FMT_STRING then + begin + s := ATag.Raw; + if s[Length(s)] <> #0 then s := s + #0; + rec.DataCount := FixEndian32(Length(s)); + if Length(s) <= 4 then begin + n := 0; + Move(s[1], n, Length(s)); + rec.DataValue := n; // tag.Raw is already has the endianness needed //FixEndian32(n); + end else begin + rec.DataValue := FixEndian32(DWord(ADataStartOffset + AValueStream.Position)); + AValueStream.WriteBuffer(s[1], Length(s)); + end; + end else + if ATag.TType = FMT_BINARY then begin + rec.DataCount := FixEndian32(Length(ATag.Raw)); + if Length(ATag.Raw) <= 4 then begin + n := 0; + Move(ATag.Raw[1], n, Length(ATag.Raw)); + rec.DataValue := n; // tag.Raw is already has the endianness needed //FixEndian32(n); +// rec.DataValue := FixEndian32(n); + end else begin + rec.DataValue := FixEndian32(DWord(ADataStartOffset + AValueStream.Position)); + AValueStream.WriteBuffer(ATag.Raw[1], Length(ATag.Raw)); + end; + end else + if BYTES_PER_FORMAT[ATag.TType] > 4 then begin + // If the value requires mote than 4 bytes the data bytes are written to + // the ValueStream, and the DataValue field gets the offset to the begin + // of data, counted from the start of the TIFF header. Since the stream + // with all the IDFRecords is not complete at this moment we store the + // offsets to these fields in the OffsetList for correction later. + // For this reason, we do not take care of endianness here as well. + rec.DataCount := FixEndian32(Length(ATag.Raw) div BYTES_PER_FORMAT[ATag.TType]); + rec.DataValue := FixEndian32(DWord(ADataStartOffset + AValueStream.Position)); + case ATag.TType of + FMT_URATIONAL, FMT_SRATIONAL: + begin + AValueStream.WriteBuffer(ATag.Raw[1], Length(ATag.Raw)); + { + // Note: ATag.Raw already has the correct endianness! + rat := PExifRational(@ATag.Raw[1])^; +// rat.Numerator := FixEndian32(rat.Numerator); +// rat.Denominator := FixEndian32(rat.Denominator); + rat.Numerator := rat.Numerator; + rat.Denominator := rat.Denominator; + AValueStream.WriteBuffer(rat, SizeOf(TExifRational)); + } + end; + FMT_DOUBLE: + begin + AValueStream.WriteBuffer(ATag.Raw[1], Length(ATag.Raw)); + end; + end; + end else + begin + // If the size of the data field is not larger than 4 bytes + // then the data value is written to the rec.DataValue field directly. + // Note: ATag.Raw already has the correct endianness + rec.DataCount := FixEndian32(Length(ATag.Raw) div BYTES_PER_FORMAT[ATag.TType]); + rec.DataValue := 0; + Move(ATag.Raw[1], rec.DataValue, Length(ATag.Raw)); + { + rec.DataValue : + case ATag.TType of + FMT_BYTE, FMT_SBYTE: + rec.DataValue := byte(ATag.Raw[1]); + FMT_USHORT, FMT_SSHORT: + rec.DataValue := PWord(@ATag.Raw[1])^; + //rec.DataValue := FixEndian32(PWord(@ATag.Raw[1])^); + FMT_ULONG, FMT_SLONG: + rec.DataValue := PDWord(@ATag.Raw[1])^; + //rec.DataValue := FixEndian32(PDWord(@ATag.Raw[1])^); + FMT_SINGLE: + Move(ATag.Raw[1], rec.DataValue, SizeOf(Single)); + end; + } + end; + + // Write out + AStream.Write(rec, SizeOf(Rec)); +end; + *) + +procedure TExifWriter.WriteTiffHeader(AStream: TStream); +var + header: TTiffHeader; + offs: DWord; +begin + if FBigEndian then + Move(BIG_ENDIAN_BOM[0], {%H-}header.BOM[0], 2) + else + Move(LITTLE_ENDIAN_BOM[0], header.BOM[0], 2); + header.Signature := FixEndian16(42); // magic number + offs := SizeOf(header); + header.IFDOffset := FixEndian32(offs); // Offset to start of IFD0, from begin of TIFF header + + // Write out + AStream.WriteBuffer(header, SizeOf(header)); +end; + +procedure TExifWriter.WriteToStream(AStream: TStream; AImgFormat: TImgFormat); +var + subIFDList: TInt64List; +begin + FImgFormat := AImgFormat; + case FImgFormat of + ifJpeg: + WriteExifHeader(AStream); + else + Error('Image format not supported.'); + end; + + subIFDList := TInt64List.Create; + try + // Tiff header + FTiffHeaderPosition := AStream.Position; + WriteTiffHeader(AStream); + + // Write IFD0 + WriteIFD(AStream, subIFDList, TAGPARENT_PRIMARY); + + // Write IFD1 + if FImgInfo.HasThumbnail then + WriteIFD(AStream, subIFDList, TAGPARENT_THUMBNAIL); + + // Write special subIFDs collected in subIFDList + WriteSubIFDs(AStream, subIFDList); + + // If WriteToStream is called within a JPEG structure we must update the + // size of the EXIF segment. + UpdateSegmentSize(AStream, FExifSegmentStartPos); + + finally + subIFDList.Free; + end; +end; + +initialization + +finalization + RegisteredReaders.Free; + +end. + diff --git a/components/fpexif/fpeglobal.pas b/components/fpexif/fpeglobal.pas new file mode 100644 index 000000000..a14ead2c9 --- /dev/null +++ b/components/fpexif/fpeglobal.pas @@ -0,0 +1,156 @@ +unit fpeGlobal; + +{$IFDEF FPC} + {$mode objfpc}{$H+} +{$ENDIF} + +{$I fpExif.inc} + +interface + +uses + Classes, SysUtils; + +type + TMetaDataKind = (mdkExif, mdkIPTC, mdkComment); + TMetaDataKinds = set of TMetaDataKind; + +const + mdkAll = [mdkExif, mdkIPTC, mdkComment]; + +type +{$IFNDEF FPC} + DWord = Cardinal; + PDWord = ^DWord; + PtrInt = NativeInt; + + Int32 = LongInt; +{$ENDIF} + + TBytes = array of byte; // Needed for Delphi 7 and old Lazarus (1.0) + + TTagGroup = ( + tgUnknown, + tgJFIF, + tgExifPrimary, tgExifThumbnail, tgExifSub, tgExifInterop, tgExifGps, + tgExifMakerNote, tgExifMakerNoteSub, + tgIPTC + ); + + // The TagID consists of two parts: the low-word is the ID of the tag itself, + // the high-word the ID of its parent (sub-IFD) + TTagID = DWord; + TTagIDRec = record + Tag: Word; + Parent: Word; + end; + + TTagType = ( + ttUInt8 = 1, ttString, ttUInt16, ttUInt32, ttURational, + ttSInt8, ttBinary, ttSInt16, ttSInt32, ttSRational, + ttSingle, ttDouble, + ttIFD // rarely used, in Olympus maker notes + ); + + TTagOption = ( + toBigEndian, // value is stored in big-endian byte order + toDecodeValue, // (enumerated) value is converted to string representation + toReadOnly, // tag value cannot be changed by user + toTruncateBinary, // show only first bytes of a binary tag + toBinaryAsASCII, // should display binary tag values as ASCII text + toVolatile // tag value is not written to file + ); + TTagOptions = set of TTagOption; + + TExportOption = (eoShowTagName, eoShowDecimalTagID, eoShowHexTagID, + eoDecodeValue, eoTruncateBinary, eoBinaryAsASCII); + TExportOptions = set of TExportOption; + + TExifRational = record + Numerator, Denominator: LongInt; + end; + PExifRational = ^TExifRational; + + TExifIntegerArray = array of Integer; + TExifDoubleArray = array of Double; + TExifRationalArray = array of TExifRational; + + TImgFormat = (ifUnknown, ifJpeg, ifTiff); + + TLookupCompareFunc = function(AValue1, AValue2: String): Boolean; + + EFpExif = class(Exception); + EFpExifReader = class(EFpExif); + EFpExifWriter = class(EFpExif); + +// TTagAcceptProc = function(ATag: TTagEntry): Boolean; + +const + TagElementSize: array[1..13] of Integer = (1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8, 4); + // The index is ord(TTagtype). + + GroupNames: array[TTagGroup] of string = ('', + 'JFIF', + 'IFD0', 'IFD1', 'EXIF', 'INTEROP', 'GPS', 'MAKERNOTE', 'MAKERNOTE_SUBIFD', + 'IPTC' + ); + NiceGroupNames: array[TTagGroup] of String = ('', + 'JPEG', + 'Primary', 'Thumbnail', 'EXIF', 'InterOperability', 'GPS', 'Maker Notes', 'Maker Notes Subdir', + 'IPTC' + ); + + ISO_DATE_FORMAT = 'yyyy-mm-dd'; + ISO_TIME_FORMAT = 'hh:nn:ss'; + ISO_DATETIME_FORMAT = ISO_DATE_FORMAT + ' ' + ISO_TIME_FORMAT; + IPTC_DATE_FORMAT = 'yyyymmdd'; + IPTC_TIME_FORMAT = 'hhnnss'; + IPTC_DATETIME_FORMAT = IPTC_DATE_FORMAT + ' ' + IPTC_TIME_FORMAT; + EXIF_DATE_FORMAT = 'yyyy:mm:dd'; + EXIF_TIME_FORMAT = 'hh:nn:ss'; + EXIF_DATETIME_FORMAT = EXIF_DATE_FORMAT + ' ' + EXIF_TIME_FORMAT; + + //GpsFormat = gf_DMS_Short; + + ValidExifHeader: ansistring = 'Exif'#0; + + DEFAULT_THUMBNAIL_SIZE = 200; + + IPTC_MULTI_TAG_COUNT = $FFFF; + IPTC_MULTI_TAG_SEPARATOR = #1; + +var + fpExifDataSep : ansistring = ', '; + fpExifDecodeSep : string = ','; + fpExifLookupSep : string = ','; + fpExifLookupKeySep: string = ':'; + fpExifDelim : string = ' = '; + + // If Exif.ExportOptions contains eoTruncateBinary then exported binary tags + // show only this number of bytes + MaxBinaryBytes : Integer = 10; + + // FormatSettings for how to pass floating point values to dExif + fpExifFmtSettings : TFormatSettings; + + +implementation + +initialization + {$IFNDEF DELPHI7} + fpExifFmtSettings := FormatSettings; + {$ENDIF} + fpExifFmtSettings.DecimalSeparator := '.'; + fpExifFmtSettings.ListSeparator := ','; + fpExifFmtSettings.DateSeparator := '-'; + fpExifFmtSettings.TimeSeparator := ':'; + fpExifFmtSettings.ShortDateFormat := 'yyyy-mm-dd'; + fpExifFmtSettings.LongDateFormat := 'yyyy-mm-dd'; + fpExifFmtSettings.LongTimeFormat := 'hh:nn:ss'; + fpExifFmtSettings.ShortTimeFormat := 'hh:nn'; + +end. + + + + diff --git a/components/fpexif/fpeiptcdata.pas b/components/fpexif/fpeiptcdata.pas new file mode 100644 index 000000000..12202a8bc --- /dev/null +++ b/components/fpexif/fpeiptcdata.pas @@ -0,0 +1,674 @@ +unit fpeIptcData; + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I fpexif.inc} + +interface + +uses + Classes, SysUtils, Contnrs, + fpeGlobal, fpeTags; + +type + TIptcData = class + private + FTagList: TTagList; + FImageResourceBlocks: TObjectList; + function GetTagByID(ATagID: TTagID): TTag; + function GetTagByIndex(AIndex: Integer): TTag; + function GetTagByName(ATagName: String): TTag; + function GetTagCount: Integer; + procedure SetTagByID(ATagID: TTagID; const ATag: TTag); + procedure SetTagByIndex(AIndex: Integer; const ATag: TTag); + procedure SetTagByName(ATagName: String; const ATag: TTag); + protected + function IndexOfTagID(ATagID: TTagID): Integer; + function IndexOfTagName(ATagName: String): Integer; + function InternalAddTag(ATagDef: TTagDef): TTag; + public + constructor Create; + destructor Destroy; override; + procedure AddImageResourceBlock(AIdentifier: Word; AName: String; AData: TBytes); + function AddTag(ATag: TTag): Integer; + function AddTagByName(ATagName: String): TTag; + procedure AppendTagTo(ATag, AParentTag: TTag); + procedure Clear; + procedure ExportToStrings(AList: TStrings; AOptions: TExportOptions; + ASeparator: String = '='); + procedure GetImageResourceBlock(AIndex: Integer; out AIdentifier: Word; + out AName: String; out AData: TBytes); + function GetImageResourceBlockCount: Integer; + property TagbyID[ATagID: TTagID]: TTag + read GetTagByID write SetTagByID; + property TagByIndex[AIndex: Integer]: TTag + read GetTagByIndex write SetTagByIndex; + property TagByName[ATagName: String]: TTag + read GetTagByName write SetTagByName; + property TagCount: Integer + read GetTagCount; + end; + +type + TIptcStringTag = class(TStringTag) + private + FMaxLen: Integer; + public + constructor Create(ATagDef: TTagDef; AOptions: TTagOptions); override; + property MaxLength: Integer read FMaxLen; + end; + + TIptcMultiStringTag = class(TIptcStringTag) + protected + function GetAsString: String; override; + procedure SetAsString(const AValue: String); override; + public + procedure AddString(const AValue: String); virtual; + end; + + TIptcObjectAttrTag = class(TIptcMultiStringTag) + public + procedure AddString(const AValue: String); override; + end; + + TIptcUrgencyTag = class(TIptcStringTag) + protected + procedure SetAsString(const AValue: String); override; + end; + + TIptcDateTag = class(TIptcStringTag) + private + function GetFormat: String; + protected + function GetAsDate: TDateTime; + function GetAsString: String; override; + procedure SetAsDate(const AValue: TDateTime); + procedure SetAsString(const AValue: String); override; + public + property AsDate: TDateTime read GetAsDate write SetAsDate; + property FormatStr; // e.g. 'yyyy-mm-dd'; + end; + + TIptcTimeTag = class(TIptcStringTag) + private + function GetFormat: String; + protected + function GetAsString: String; override; + function GetAsTime: TDateTime; + procedure SetAsString(const AValue: String); override; + procedure SetAsTime(const AValue: TDateTime); + public + property AsTime: TDateTime read GetAsTime write SetAsTime; + property FormatStr; // e.g. 'hh:nn'; + end; + +procedure BuildIptcTagDefs; +procedure FreeIptcTagDefs; +function FindIptcTagDef(ATagID: TTagID): TTagDef; overload; +function FindIptcTagDef(ATagName: String): TTagDef; overload; + + +implementation + +uses + Math, DateUtils, StrUtils, Variants, + fpeStrConsts, fpeUtils; + +type + TAdobeImageResourceBlock = class + Identifier: Word; + Name: String; + Data: TBytes; + end; + +var + IptcTagDefs: TTagDefList = nil; + +procedure BuildIptcTagDefs; +const + I = DWord(TAGPARENT_IPTC); // for shorter lines... +begin + if IptcTagDefs = nil then + IptcTagDefs := TTagDefList.Create; + with IptcTagDefs do begin + Clear; + // NOTE: The Count field is "abused" as MaxLength of string value + AddStringTag(I+$015A {1:90}, 'CodedCharacterSet', 32, rsCodedCharSet, '', TIptcStringTag); + AddUShortTag(I+$0200 {2: 0}, 'RecordVersion', 1, rsRecordVersion); + AddStringTag(I+$0203 {2: 3}, 'ObjectType', 64, rsObjectType, '', TIptcStringTag); + AddStringTag(I+$0204 {2: 4}, 'ObjectAttr', 68, rsObjectAttr, '', TIptcObjectAttrTag); + AddStringTag(I+$0205 {2: 5}, 'ObjectName', 64, rsObjectName, '', TIptcStringTag); + AddStringTag(I+$0207 {2: 7}, 'EditStatus', 64, rsEditStatus, '', TIptcStringTag); + AddStringTag(I+$0208 {2: 8}, 'EditorialUpdate', 2, rsEditorialUpdate,'', TIptcStringTag); + AddStringTag(I+$020A {2:10}, 'Urgency', 1, rsUrgency, rsUrgencyLkUp, TIptcUrgencyTag); + AddStringTag(I+$020C {2:12}, 'SubRef', 236, rsSubjectRef, '', TIptcMultiStringTag); // Min 13 + AddStringTag(I+$020F {2:15}, 'Category', 3, rsCategory, '', TIptcStringTag); + AddStringTag(I+$0214 {2:20}, 'SuppCategory', 32, rsSuppCategory, '', TIptcMultiStringTag); + AddStringTag(I+$0216 {2:22}, 'FixtureID', 32, rsFixtureID, '', TIptcStringTag); + AddStringTag(I+$0219 {2:25}, 'KeyWords', 64, rsKeyWords, '', TIptcMultiStringTag); + AddStringTag(I+$021A {2:26}, 'ContentLocCode', 3, rsContentLocCode, '', TIptcMultiStringTag); + AddStringTag(I+$021B {2:27}, 'ContentLocName', 64, rsContentLocName, '', TIptcMultiStringTag); + AddStringTag(I+$021E {2:30}, 'ReleaseDate', 8, rsReleaseDate, '', TIptcDateTag); + AddStringTag(I+$0223 {2:35}, 'ReleaseTime', 11, rsReleaseTime, '', TIptcTimeTag); + AddStringTag(I+$0225 {2:37}, 'ExpireDate', 8, rsExpireDate, '', TIptcStringTag); + AddStringTag(I+$0226 {2:38}, 'ExpireTime', 11, rsExpireTime, '', TIptcStringTag); + AddStringTag(I+$0228 {2:40}, 'SpecialInstruct', 256, rsSpecialInstruct,'', TIptcStringTag); + AddStringTag(I+$022A {2:42}, 'ActionAdvised', 2, rsActionAdvised, '', TIptcStringTag); + AddStringTag(I+$022D {2:45}, 'RefService', $FFFF, rsRefService, '', TIptcMultiStringTag); + AddStringTag(I+$022F {2:47}, 'RefDate', $FFFF, rsRefDate, '', TIptcMultiStringTag); + AddStringTag(I+$0232 {2:50}, 'RefNumber', $FFFF, rsRefNumber, '', TIptcMultiStringTag); + AddStringTag(I+$0237 {2:55}, 'DateCreated', 8, rsDateCreated, '', TIptcDateTag); + AddStringTag(I+$023C {2:60}, 'TimeCreated', 11, rsTimeCreated, '', TIptcTimeTag); + AddStringTag(I+$023E {2:62}, 'DigitizeDate', 8, rsDigitizeDate, '', TIptcDateTag); + AddStringTag(I+$023F {2:63}, 'DigitizeTime', 11, rsDigitizeTime, '', TIptcTimeTag); + AddStringTag(I+$0241 {2:65}, 'OriginatingProgram',32, rsOriginatingProg,'', TIptcStringTag); + AddStringTag(I+$0246 {2:70}, 'ProgramVersion', 10, rsProgVersion, '', TIptcStringTag); + AddStringTag(I+$024B {2:75}, 'ObjectCycle', 1, rsObjectCycle, rsObjectCycleLkup, TIptcStringTag); + AddStringTag(I+$0250 {2:80}, 'ByLine', 32, rsByLine, '', TIptcMultiStringTag); + AddStringTag(I+$0255 {2:85}, 'ByLineTitle', 32, rsByLineTitle, '', TIptcMultiStringTag); + AddStringTag(I+$025A {2:90}, 'City', 32, rsCity, '', TIptcStringTag); + AddStringTag(I+$025C {2:92}, 'SubLocation', 32, rsSubLocation, '', TIptcStringTag); + AddStringTag(I+$025F {2:95}, 'State', 32, rsState, '', TIptcStringTag); + AddStringTag(I+$0264 {2:100}, 'LocationCode', 3, rsLocationCode, '', TIptcStringTag); + AddStringTag(I+$0265 {2:101}, 'LocationName', 64, rsLocationName, '', TIptcStringTag); + AddStringTag(I+$0267 {2:103}, 'TransmissionRef', 32, rsTransmissionRef,'', TIptcStringTag); + AddStringTag(I+$0269 {2:105}, 'ImageHeadline', 256, rsImgHeadline, '', TIptcStringTag); + AddStringTag(I+$026E {2:110}, 'ImageCredit', 32, rsImgCredit, '', TIptcStringTag); + AddStringTag(I+$0273 {2:115}, 'Source', 32, rsSource, '', TIptcStringTag); + AddStringTag(I+$0274 {2:116}, 'Copyright', 128, rsCopyright, '', TIptcStringTag); + AddStringTag(I+$0276 {2:118}, 'Contact', 128, rsContact, '', TIptcMultiStringTag); + AddStringTag(I+$0278 {2:120}, 'ImageCaption', 2000, rsImgCaption, '', TIptcStringTag); + AddStringTag(I+$027A {2:122}, 'ImageCaptionWriter',32, rsImgCaptionWriter,'', TIptcStringTag); + AddStringTag(I+$0282 {2:130}, 'ImageType', 2, rsImgType, '', TIptcStringTag); + AddStringTag(I+$0283 {2:131}, 'Orientation', 1, rsOrientation, rsIptcOrientationLkup, TIptcStringTag); + AddStringTag(I+$0287 {2:135}, 'LangID', 3, rsLangID, '', TIptcStringTag); + end; +end; + + +function FindIptcTagDef(ATagID: TTagID): TTagDef; +begin + if IptcTagDefs = nil then + BuildIptcTagDefs; + Result := IptcTagDefs.FindByID(ATagID); +end; + +function FindIptcTagDef(ATagName: String): TTagDef; +begin + if IptcTagDefs = nil then + BuildIptcTagDefs; + Result := IptcTagDefs.FindByName(ATagName); +end; + +procedure FreeIptcTagDefs; +begin + FreeAndNil(IptcTagDefs); +end; + + +//============================================================================== +// TIptcData +//============================================================================== + +constructor TIptcData.Create; +begin + BuildIptcTagDefs; + inherited Create; + FTagList := TTagList.Create; + FImageResourceBlocks := TObjectList.Create; +end; + +destructor TIptcData.Destroy; +begin + FImageResourceBlocks.Free; + FTagList.Free; + inherited; +end; + +procedure TIptcData.AddImageResourceBlock(AIdentifier: Word; AName: String; + AData: TBytes); +var + block: TAdobeImageResourceBlock; +begin + block := TAdobeImageResourceBlock.Create; + block.Identifier := AIdentifier; + block.Name := AName; + SetLength(block.Data, Length(AData)); + if Length(AData) > 0 then + Move(AData[0], block.Data[0], Length(AData)); + FImageResourceBlocks.Add(block); +end; + +function TIptcData.AddTag(ATag: TTag): Integer; +var + idx: Integer; +begin + idx := IndexOfTagID(ATag.TagID); + if idx <> -1 then begin + // Replace existing tag + FTagList.Delete(idx); + FTagList.Insert(idx, ATag); + end else + // Add the new tag + Result := FTagList.Add(ATag); +end; + +function TIptcData.AddTagByName(ATagName: String): TTag; +var + idx: Integer; + tagdef: TTagDef; +begin + idx := IndexOfTagName(ATagName); + if idx > -1 then + Result := FTagList[idx] + else begin + tagDef := FindIptcTagDef(ATagName); + Result := InternalAddTag(tagDef); + end; +end; + +{ Adds ATag to AParentTag } +procedure TIptcData.AppendTagTo(ATag, AParentTag: TTag); +begin + Assert(ATag <> nil); + Assert(AParentTag <> nil); + Assert(ATag.TagID = AParentTag.TagID); + Assert(ATag.TagType = AParentTag.TagType); + + if AParentTag is TIptcMultiStringTag then + TIptcMultiStringTag(AParentTag).AddString(ATag.AsString); +end; + +procedure TIptcData.Clear; +begin + FImageResourceBlocks.Clear; + FTagList.Clear; +end; + +procedure TIptcData.ExportToStrings(AList: TStrings; AOptions: TExportOptions; + ASeparator: String = '='); +var + i: Integer; + tag: TTag; + nam: String; + tagval: String; + usedExportOptions: TExportOptions; +begin + Assert(AList <> nil); + + if TagCount = 0 then + exit; + + if AList.Count > 0 then + AList.Add(''); + AList.Add('*** IPTC ***'); + + for i := 0 to TagCount-1 do begin + tag := TagByIndex[i]; + usedExportOptions := AOptions * [eoShowDecimalTagID, eoShowHexTagID]; + if usedExportOptions = [eoShowDecimalTagID] then + nam := Format('[%d] %s', [tag.TagID, tag.Description]) + else + if usedExportOptions = [eoShowHexTagID] then + nam := Format('[$%.4x] %s', [tag.TagID, tag.Description]) + else + nam := tag.Description; + tagval := tag.AsString; + if tagval <> '' then + AList.Add(nam + ASeparator + tagval); + end; +end; + +procedure TIptcData.GetImageResourceBlock(AIndex: Integer; out AIdentifier: Word; + out AName: String; out AData: TBytes); +var + block: TAdobeImageResourceBlock; +begin + block := TAdobeImageResourceBlock(FImageResourceBlocks[AIndex]); + AIdentifier := block.Identifier; + AName := block.Name; + SetLength(AData, Length(block.Data)); + Move(block.Data[0], AData[0], Length(AData)); +end; + +function TIptcData.GetImageResourceBlockCount: Integer; +begin + Result := FImageResourceBlocks.Count; +end; + +function TIptcData.GetTagByID(ATagID: TTagID): TTag; +var + idx: Integer; +begin + idx := IndexOfTagID(ATagID); + if idx = -1 then + Result := nil + else + Result := FTagList[idx]; +end; + +function TIptcData.GetTagByIndex(AIndex: Integer): TTag; +begin + Result := FTagList[AIndex]; +end; + +function TIptcData.GetTagByName(ATagName: String): TTag; +var + idx: Integer; +begin + idx := IndexOfTagName(ATagName); + if idx = -1 then + Result := nil + else + Result := FTagList[idx]; +end; + +function TIptcData.GetTagCount: Integer; +begin + Result := FTagList.Count; +end; + +function TIptcData.IndexOfTagID(ATagID: TTagID): Integer; +var + i: Integer; + tag: TTag; +begin + for i:=0 to FTagList.Count-1 do begin + tag := FTagList[i]; + if (tag.TagID = ATagID) then begin + Result := i; + exit; + end; + end; + Result := -1; +end; + +function TIptcData.IndexOfTagName(ATagName: String): Integer; +var + i: Integer; + tag: TTag; +begin + for i:=0 to FTagList.Count-1 do begin + tag := FTagList[i]; + if SameText(tag.Name, ATagName) then begin + Result := i; + exit; + end; + end; + Result := -1; +end; + +function TIptcData.InternalAddTag(ATagDef: TTagDef): TTag; +var + optns: TTagOptions; +begin + if ATagDef <> nil then begin + optns := [toBigEndian]; //ExportOptionsToTagOptions; + Result := ATagDef.TagClass.Create(ATagDef, optns); + AddTag(Result); + end else + Result := nil +end; + +procedure TIptcData.SetTagByID(ATagID: TTagID; const ATag: TTag); +var + idx: Integer; +begin + if (ATag <> nil) and ATag.ReadOnly then + exit; + + idx := IndexOfTagID(ATagID); + SetTagByIndex(idx, ATag); +end; + +procedure TIptcData.SetTagByIndex(AIndex: Integer; const ATag: TTag); +var + tag: TTag; +begin + if (ATag <> nil) and ATag.ReadOnly then + exit; + + if AIndex > -1 then begin + tag := FTagList[AIndex]; + if tag.ReadOnly then + exit; + FTagList.Delete(AIndex); + if ATag <> nil then + FTagList.Insert(AIndex, ATag); + end else + AddTag(ATag); +end; + +procedure TIptcData.SetTagByName(ATagName: String; const ATag: TTag); +var + idx: Integer; +begin + if (ATag <> nil) and ATag.ReadOnly then + exit; + + idx := IndexOfTagName(ATagName); + SetTagByIndex(idx, ATag); +end; + + +//============================================================================== +// TIptcStringTag +//============================================================================== +constructor TIptcStringTag.Create(ATagDef: TTagDef; AOptions: TTagOptions); +begin + inherited Create(ATagDef, AOptions); + FMaxLen := FCount; + FCount := 1; +end; + + +//============================================================================== +// TIptcMultiStringTag +//============================================================================== + +procedure TIptcMultiStringTag.AddString(const AValue: String); +var + s: String; + mxlen: Integer; +begin + s := inherited GetAsString; + mxlen := Min(MaxInt, FMaxLen); + if s = '' then + s := Copy(AValue, 1, mxlen) + else + s := s + IPTC_MULTI_TAG_SEPARATOR + Copy(AValue, 1, mxlen); + inherited SetAsString(s); +end; + +function TIptcMultiStringTag.GetAsString: String; +var + s: String; +begin + s := inherited GetAsString; + Result := StringReplace(s, IPTC_MULTI_TAG_SEPARATOR, fpExifDataSep, [rfReplaceAll]) +end; + +procedure TIptcMultiStringTag.SetAsString(const AValue: String); +var + sArr: TStringArray; + i: Integer; +begin + inherited SetAsString(''); + if AValue <> '' then begin + sArr := Split(AValue, fpExifDataSep); + for i:=0 to High(sArr) do + AddString(sArr[i]); + end; +end; + + +//============================================================================== +// TIptcObjectAttrTag +//============================================================================== + +procedure TIptcObjectAttrTag.AddString(const AValue: String); +begin + + + //!!!!!!!!!!!!!!! + (* + if (Length(AValue) < 4) or (AValue[4] <> ':') or not TryStrToInt(Copy(AValue, 1, 3), n) then + raise EFpExif.Create('Tag "ObjectAttr" must constist of 3 numeric characters, '+ + 'a colon and an optional description of max 64 characters'); + *) + + inherited AddString(AValue); +end; + + +//============================================================================== +// TIptcUrgencyTag +//============================================================================== + +procedure TIptcUrgencyTag.SetAsString(const AValue: String); +var + n: Integer; + ok: Boolean; +begin + if (AValue <> '') then begin + n := 0; + ok := TryStrToInt(AValue, n); + if (not ok) or (n < 0) or (n > 9) then + raise EFpExif.Create('Tag "Urgency" can only contain one numeric character 0..9'); + end; + inherited SetAsString(AValue); +end; + + +//============================================================================== +// TIptcDateTag +//============================================================================== + +function TIptcDateTag.GetAsDate: TDateTime; +var + s: String; + y, m, d: String; +begin + s := inherited GetAsString; + if Length(s) >= 8 then begin + y := Copy(s, 1, 4); + m := Copy(s, 5, 2); + d := Copy(s, 7, 2); + Result := EncodeDate(StrToInt(y), StrToInt(m), StrToInt(d)); + end else + Result := 0; +end; + +function TIptcDateTag.GetAsString: String; +begin + Result := FormatDateTime(GetFormat, GetAsDate); +end; + +function TIptcDateTag.GetFormat: String; +begin + Result := IfThen(FFormatStr = '', fpExifFmtSettings.ShortDateFormat, FFormatStr); +end; + +procedure TIptcDateTag.SetAsDate(const AValue: TDateTime); +begin + inherited SetAsString(FormatDateTime(IPTC_DATE_FORMAT, AValue)); +end; + +procedure TIptcDateTag.SetAsString(const AValue: String); +var + d: TDateTime; + fmt: String; +begin + fmt := GetFormat; + if fmt = IPTC_DATE_FORMAT then + d := IptcDateStrToDate(AValue) + else begin + {$IFDEF FPC} + d := ScanDateTime(fmt, AValue); + {$ELSE} + fs := fpExifFmtSettings; + fs.ShortDateFormat := fmt; + fs.LongDateFormat := fmt; + if pos(':', fmt) > 0 then + fs.DateSeparator := ':' + else if pos('.', fmt) > 0 then + fs.DateSeparator := '.' + else if pos('/', fmt) > 0 then + fs.DateSeparator := '/' + else if pos('-', fmt) > 0 then + fs.DateSeparator := '-' + else + fs.DateSeparator := ' '; + d := StrToDate(AValue, fs); + {$ENDIF} + end; + SetAsDate(d); +end; + + +//============================================================================== +// TIptcTimeTag +//============================================================================== + +function TIptcTimeTag.GetAsString: String; +begin + Result := FormatDateTime(GetFormat, GetAsTime); +end; + +function TIptcTimeTag.GetFormat: String; +begin + Result := IfThen(FFormatStr = '', fpExifFmtSettings.LongTimeformat, FFormatStr); +end; + +function TIptcTimeTag.GetAsTime: TDateTime; +var + s: String; + hr, mn, sec: String; +begin + s := inherited GetAsString; + if Length(s) >= 6 then begin + hr := Copy(s, 1, 2); + mn := Copy(s, 3, 2); + sec := Copy(s, 5, 2); + Result := EncodeTime(StrToInt(hr), StrToInt(mn), StrToInt(sec), 0); + end else + Result := 0; +end; + +procedure TIptcTimeTag.SetAsString(const AValue: String); +var + t: TDateTime; + fmt: String; +begin + fmt := GetFormat; + if fmt = IPTC_TIME_FORMAT then + t := IptcTimeStrToTime(AValue) + else begin + {$IFDEF FPC} + t := ScanDateTime(fmt, AValue); + {$ELSE} + fs := fpExifFmtSettings; + fs.LongTimeFormat := GetFormat; + t := StrToTime(AValue, fs); + {$ENDIF} + end; + SetAsTime(t); +end; + +procedure TIptcTimeTag.SetAsTime(const AValue: TDateTime); +var + s: String; +begin + s := FormatDateTime(IPTC_TIME_FORMAT, AValue) + LocalTimeZoneStr; + inherited SetAsString(s); +end; + + +initialization + +finalization + FreeIptcTagDefs; + +end. diff --git a/components/fpexif/fpeiptcreadwrite.pas b/components/fpexif/fpeiptcreadwrite.pas new file mode 100644 index 000000000..36d413306 --- /dev/null +++ b/components/fpexif/fpeiptcreadwrite.pas @@ -0,0 +1,485 @@ +{ + Reader and writer for IPTC data (Adobe image resource blocks) + + NOTE: Data is in Big-Endian format. + + Adobe Image Resource Block: + -------------------------- + https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1037504 + + Length Description + ------ ---------------------------------------------------------------- + 4 Signature: '8BIM' + 2 Unique identifier for the resource. + (var) Name: Pascal string, padded to make the size even + (a null name consists of two bytes of 0) + 4 Actual size of resource data that follows + (var) The resource data, described in the sections on the individual + resource types. It is padded to make the size even. + + The image resource block with unique identifier $0404 is the IPTC block. + + https://www.iptc.org/std/IIM/4.2/specification/IIMV4.2.pdf + + The IPTC block consists of several "records". + Every "record" constists of several "datasets". + Every "dataset" consists of a unique tag and a data field. + + There are two types of tags: + "Standard" tag: + - 1 byte: tag "marker" ($1C) + - 1 byte: record number + - 1 byte: dataset number + - 2 bytes: datafield byte count + + "Extended" tag (probably not used): + - 1 byte: tag "marker" ($1C) + - 1 byte: record number + - 1 byte: dataset number + - 2 bytes: count of bytes (n) used in "datafield byte count" field + (always has highest bit set) + - n bytes: datafield byte count +} + +unit fpeIptcReadWrite; + +{$IFDEF FPC} + {$MODE objfpc}{$H+} +{$ENDIF} + +interface + +uses + Classes, SysUtils, + fpeGlobal, fpeUtils, fpeTags, fpeMetadata, fpeIptcData; + +type + { TIPTCReader } + TIPTCReader = class(TBasicMetadataReader) + private + function ExtractTag(const ABuffer: TBytes; var AStart: Integer): TTag; + procedure ReadImageResourceBlock(AStream: TStream; out AID: Word; + out AName: String; out AData: TBytes); + protected + public + procedure ReadFromStream(AStream: TStream; AImgFormat: TImgFormat); override; + procedure ReadIPTCData(const ABuffer: TBytes); + end; + + { TIPTCWriter } + + TIPTCWriter = class(TBasicMetadataWriter) + private + FIPTCSegmentStartPos: Int64; + protected + procedure WriteEndOfDataResourceBlock(AStream: TStream); + procedure WriteImageResourceBlockHeader(AStream: TStream; AResourceID: Integer; + AResourceName: String); //; ABuffer: Pointer; ABufferSize: DWord); + procedure WriteIPTCHeader(AStream: TStream); + procedure WriteIPTCImageResourceBlock(AStream: TStream; AName: String); + procedure WriteTag(AStream: TStream; ATag: TTag); overload; + public + constructor Create(AImgInfo: TImgInfo); override; + procedure WriteToStream(AStream: TStream; AImgFormat: TImgFormat); override; + end; + +implementation + +uses + fpeStrConsts; + +type + // http://search.cpan.org/dist/Image-MetaData-JPEG/lib/Image/MetaData/JPEG/Structures.pod#Structure_of_an_IPTC_data_block + TIptcTag = packed record + TagMarker: Byte; // must be $1C + RecordNumber: Byte; // this is the number before the colon in the tag listing + DatasetNumber: Byte; // this is the number after the colon in the tag listing ("Tag") + Size: Word; // Size of data if < 32768, otherwise size of datalength element + // SizeOfDatasize: word --> if Size of data > 32767 + // Data: variable + end; + +const + IPTC_SIGNATURE: ansistring = 'Photoshop 3.0'#0; + RESOURCE_MARKER: ansistring = '8BIM'; + IPTC_IMAGERESOURCEID = $0404; + + +//------------------------------------------------------------------------------ +// TIptcReader +//------------------------------------------------------------------------------ + +function TIptcReader.ExtractTag(const ABuffer: TBytes; var AStart: Integer): TTag; +var + recordNo: Byte; + datasetNo: Byte; + len: DWord; + tagdef: TTagDef; + tagID: TTagID; + s: String; + w: Word; +begin + Result := nil; + + recordNo := ABuffer[AStart]; + datasetNo := ABuffer[AStart+1]; + len := BEtoN(PWord(@ABuffer[AStart+2])^); + inc(AStart, 4); + + // Take care of highest bit which indicates an Extended Dataset + if word(len) and $8000 <> 0 then + begin + len := word(len) and (not $8000); + if len = 2 then + begin + len := BEtoN(PWord(@ABuffer[AStart])^); + inc(AStart, 2); + end else + if len = 4 then + begin + len := BEtoN(PDWord(@ABuffer[AStart - len])^); + inc(AStart, 4); + end else + Error(Format(rsIptcExtendedDataSizeNotSupported, [len])); + end; + + if not (recordNo in [1, 2, 8]) then begin + AStart := AStart + Integer(len); + exit; + end; + + tagID := (recordNo shl 8) or datasetNo or TAGPARENT_IPTC; + tagdef := FindIPTCTagDef(tagID); + if tagdef <> nil then begin + Result := tagdef.TagClass.Create(tagdef, true); + case tagdef.TagType of + ttString: + begin + {$IFDEF FPC} + SetLength(s, len); + Move(ABuffer[AStart], s[1], len); + {$ELSE} + SetLength(sa,len); + Move(ABuffer[AStart], sa[1], len); + s := UTF8Decode(sa); + {$ENDIF} + if Result is TIptcDateTag then + with TIptcDateTag(Result) do begin + FormatStr := IPTC_DATE_FORMAT; + AsString := s; + FormatStr := ''; + end + else + if Result is TIptcTimeTag then + with TIptcTimeTag(Result) do begin + FormatStr := IPTC_TIME_FORMAT; + AsString := s; + FormatStr := ''; + end + else + (Result as TStringTag).AsString := s; + end; + ttUInt16: + begin + w := BEtoN(PWord(@ABuffer[AStart])^); + (Result as TIntegerTag).AsInteger := w; + end; + else + Warning(Format(rsTagTypeNotSupported, [tagDef.Name])); + end; + end else + begin + // to do: create a dummy tag for the unknown tagdef + end; + + AStart := AStart + Integer(len); +end; + +procedure TIptcReader.ReadFromStream(AStream: TStream; AImgFormat: TImgFormat); +const + MARKER_SIZE = 4; +var + marker: packed array[1..MARKER_SIZE] of ansichar; + lID: Word; // Image resoure ID + lName: String; + lData: TBytes; +begin + FImgFormat := AImgFormat; + + SetLength(lData, Length(IPTC_SIGNATURE)); // 'Photoshop 3.0' + if AStream.Read(lData[0], Length(lData)) <> Length(lData) then begin + Error(rsIncorrectFileStructure); + exit; + end; + if not CompareMem(@lData[0], @IPTC_SIGNATURE[1], Length(lData)) then begin + Error(rsNoValidIptcSignature); + exit; + end; + + while (AStream.Position < AStream.Size) do begin + AStream.Read({%H-}marker[1], MARKER_SIZE); + if AStream.Position >= AStream.Size then begin + Error(rsIncorrectFileStructure); + break; + end; + if not CompareMem(@marker[1], @RESOURCE_MARKER[1], MARKER_SIZE) then // '8BIM' + break; + ReadImageResourceBlock(AStream, lID, lName, lData); + if lID = IPTC_IMAGERESOURCEID then begin // $0404 + FImgInfo.IptcData.AddImageResourceBlock(lID, lName, nil); + ReadIptcData(lData); + end else + FImgInfo.IptcData.AddImageResourceBlock(lID, lName, lData); + end; +end; + +procedure TIptcReader.ReadImageResourceBlock(AStream: TStream; + out AID: Word; out AName: String; out AData: TBytes); +var + len: Byte; + s: Ansistring; + lSize: DWord; +begin + AID := BEtoN(ReadWord(AStream)); + len := ReadByte(AStream); + if len = 0 then begin + ReadByte(AStream); + AName := ''; + end else begin + SetLength(s, len); + AStream.Read(s[1], len); + if s[len] = #0 then SetLength(s, len-1); + AName := s; + end; + lSize := BEToN(ReadDWord(AStream)); + SetLength(AData, lSize); + AStream.Read(AData[0], lSize); +end; + +procedure TIptcReader.ReadIptcData(const ABuffer: TBytes); +var + tag, parentTag: TTag; + start: Integer; +begin + FImgInfo.IptcData.Clear; + if Length(ABuffer) = 0 then begin + Error(rsIptcDataExpected); + exit; + end; + + start := 0; + while (start < High(ABuffer) - 1) do + begin + if ABuffer[start] <> $1C then + Error(rsNoValidIptcFile); + + inc(start); + tag := ExtractTag(ABuffer, start); + if tag is TIptcMultiStringTag then begin +// if tag.Count = IPTC_MULTI_TAG_COUNT then begin + parentTag := FImgInfo.IptcData.TagByID[tag.TagID]; + if parentTag = nil then + FImgInfo.IptcData.AddTag(tag) + else begin + FImgInfo.IptcData.AppendTagTo(tag, parentTag); + tag.Free; + end; + end else + FImgInfo.IptcData.AddTag(tag); + end; +end; + + +//------------------------------------------------------------------------------ +// TIptcWriter +//------------------------------------------------------------------------------ + +constructor TIPTCWriter.Create(AImgInfo: TImgInfo); +begin + inherited; + FIPTCSegmentStartPos := -1; +end; + +procedure TIptcWriter.WriteEndOfDataResourceBlock(AStream: TStream); +begin + WriteImageResourceBlockHeader(AStream, $0B04, ''); //, nil, 0); +end; + +//------------------------------------------------------------------------------ +// Writes the IPTC header needed by JPEG files (Segment APP13 header) +// Call WriteToStream immediately afterwards +//------------------------------------------------------------------------------ +procedure TIPTCWriter.WriteIPTCHeader(AStream: TStream); +const + SEGMENT_MARKER: array[0..1] of byte = ($FF, $ED); +begin + FIPTCSegmentStartPos := AStream.Position; + AStream.WriteBuffer(SEGMENT_MARKER[0], 2); + + // Next two zero bytes are the size of the entire IPTC segiment, they will be + // replaced when the segment is completely written. For this, we store the + // offset to the begin of the IPTC segment in FIPTCSegmentStartPos. + WriteWord(AStream, 0); + AStream.WriteBuffer(IPTC_SIGNATURE[1], Length(IPTC_SIGNATURE)); +end; + +procedure TIPTCWriter.WriteIPTCImageResourceBlock(AStream: TStream; AName: String); +var + i: Integer; + tag: TTag; + ms: TMemoryStream; + dw: DWord; +begin + // Write the image resource header + WriteImageResourceBlockHeader(AStream, IPTC_IMAGERESOURCEID, AName); + + // Now, we must write the length of the ImageResourceBlock. + // Since we don't know this we write the tags to a memory stream first + ms := TMemoryStream.Create; + try + // Write the tags to the temporary memory stream + for i := 0 to FImgInfo.IptcData.TagCount-1 do begin + tag := FImgInfo.IptcData.TagByIndex[i]; + WriteTag(ms, tag); + end; + // Now the length of the data field is known (ms.Size). + // Write the length field to "real" stream + dw := ms.Size; + WriteDWord(AStream, NtoBE(dw)); + // Copy the tags from the memorystream to the "real" stream + ms.Position := 0; + AStream.Copyfrom(ms, ms.Size); + finally + ms.Free; + end; +end; + +procedure TIPTCWriter.WriteImageResourceBlockHeader(AStream: TStream; + AResourceID: Integer; AResourceName: String); +var + dw: DWord; + sa: ansistring; +begin + // Resource marker: 8BIM + AStream.WriteBuffer(RESOURCE_MARKER[1], Length(RESOURCE_MARKER)); + + // Resource ID + WriteWord(AStream, NtoBE(word(AResourceID))); + + // Resource name + if Length(AResourceName) = 0 then + WriteWord(AStream, 0) + else + begin + sa := AResourceName; + dw := Length(sa); + if dw > 255 then begin + dw := 255; + SetLength(sa, dw); + Warning(Format(rsImageResourceNameTooLong, [AResourceName])); + end; + if not odd(dw) then begin + inc(dw); + sa := sa + #0; + end; + WriteByte(AStream, byte(dw)); + AStream.WriteBuffer(sa[1], dw); + end; +end; + +procedure TIptcWriter.WriteTag(AStream: TStream; ATag: TTag); +const + TAG_MARKER = $1C; +var + iptcTag: TIptcTag; + len: DWord; +begin + iptcTag.TagMarker := byte(TAG_MARKER); + iptcTag.RecordNumber := byte((ATag.TagID and $FF00) shr 8); + iptctag.DatasetNumber := byte(ATag.TagID and $00FF); + case ATag.TagType of + ttUInt16: + begin + iptcTag.Size := NtoBE(2); + AStream.WriteBuffer(iptcTag, SizeOf(iptcTag)); + AStream.WriteBuffer(ATag.RawData[0], 2); + end; + ttString: + begin + len := Length(ATag.RawData); + if odd(len) then begin + inc(len); + end; + // "Standard" dataset + if len < 32768 then begin + iptcTag.Size := NtoBE(word(len)); + AStream.WriteBuffer(iptcTag, SizeOf(iptcTag)); + AStream.WriteBuffer(ATag.RawData[0], Length(ATag.RawData)); + end else + // "Extended" dataset + if len < 65536 then begin + // Size is 2, but we must set highest bit to mark tag as being extended. + iptcTag.Size := NtoBE($8002); + AStream.WriteBuffer(iptcTag, SizeOf(iptcTag)); + WriteWord(AStream, NtoBE(word(len))); + AStream.WriteBuffer(ATag.RawData[0], Length(ATag.RawData)); + end else begin + // Size is 4, but we must set highest bit to mark tag as being extended. + iptcTag.Size := $8004; + AStream.WriteBuffer(iptcTag, SizeOf(iptcTag)); + WriteDWord(AStream, NtoBE(len)); + AStream.WriteBuffer(ATag.RawData[0], Length(ATag.RawData)); + end; + if odd(Length(ATag.RawData)) then // zero-termination of string + WriteByte(AStream, 0); + end; + else + // I've never seen other tag types than USHORT and STRING... + Error(Format(rsTagTypeNotSupported, [ATag.Name])); + end; +end; + +procedure TIptcWriter.WriteToStream(AStream: TStream; AImgFormat: TImgFormat); +var + i: Integer; + lID: Word; + lName: String; + lData: TBytes; +begin + FImgFormat := AImgFormat; + case FImgFormat of + ifJpeg: + WriteIptcHeader(AStream); + else + Error(rsImageFormatNotSupported); + end; + + if (FImgInfo.IptcData.GetImageResourceBlockCount = 0) and + (FImgInfo.IptcData.TagCount > 0) + then + FImgInfo.IptcData.AddImageResourceBlock(IPTC_IMAGERESOURCEID, '', nil); + + for i := 0 to FImgInfo.IptcData.GetImageResourceBlockCount-1 do begin + FImgInfo.IptcData.GetImageResourceBlock(i, lID, lName, lData); + if lID = IPTC_IMAGERESOURCEID then + // Write the IPTC tags + WriteIptcImageResourceBlock(AStream, lName) + else begin + // Write the other image resource blocks. + WriteImageResourceBlockHeader(AStream, lID, lName); + if odd(Length(lData)) then begin + SetLength(lData, Length(lData) + 1); + lData[High(lData)] := 0; + end; + WriteDWord(AStream, NtoBE(Length(lData))); + AStream.Write(lData[0], Length(lData)); + end; + end; + + // If WriteToStream is called within a JPEG structure we must update the + // size of the IPTC segment. + UpdateSegmentSize(AStream, FIptcSegmentStartPos); +end; + +end. + diff --git a/components/fpexif/fpemakernote.pas b/components/fpexif/fpemakernote.pas new file mode 100644 index 000000000..72a31b545 --- /dev/null +++ b/components/fpexif/fpemakernote.pas @@ -0,0 +1,838 @@ +unit fpeMakerNote; + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I fpexif.inc} + +interface + +uses + Sysutils, Classes, + fpeGlobal, fpeTags, fpeExifReadWrite; + + +type + TCanonMakerNoteReader = class(TMakerNoteReader) + protected + function AddTag(AStream: TStream; const AIFDRecord: TIFDRecord; + const AData: TBytes; AParent: TTagID): Integer; override; + end; + + TCasioMakerNoteReader = class(TMakerNoteReader) + protected + FVersion: Integer; + function Prepare(AStream: TStream): Boolean; override; + end; + + TMinoltaMakerNoteReader = class(TMakerNoteReader) + protected + function AddTag(AStream: TStream; const AIFDRecord: TIFDRecord; + const AData: TBytes; AParent: TTagID): Integer; override; + end; + + TOlympusMakerNoteReader = class(TMinoltaMakerNoteReader) + protected + FVersion: Integer; + function Prepare(AStream: TStream): Boolean; override; + end; + +procedure BuildCanonTagDefs(AList: TTagDefList); +procedure BuildCasio1TagDefs(AList: TTagDefList); +procedure BuildCasio2TagDefs(AList: TTagDefList); +procedure BuildEpsonTagDefs(AList: TTagDefList); +procedure BuildFujiTagDefs(AList: TTagDefList); +procedure BuildMinoltaTagDefs(AList: TTagDefList); +procedure BuildNikon1TagDefs(AList: TTagDefList); +procedure BuildNikon2TagDefs(AList: TTagDefList); +procedure BuildOlympusTagDefs(AList: TTagDefList); +procedure BuildSanyoTagDefs(AList: TTagDefList); + + +implementation + +uses + fpeStrConsts, fpeUtils, fpeExifData; + + +//============================================================================== +// TCanonMakerNoteReader +//============================================================================== + +function TCanonMakerNoteReader.AddTag(AStream: TStream; + const AIFDRecord: TIFDRecord; const AData: TBytes; AParent: TTagID): Integer; +var + tagDef: TTagDef; + w: array of Word; + n,i: Integer; +begin + Result := -1; + + tagDef := FindTagDef(AIFDRecord.TagID or AParent); + if (tagDef = nil) then + exit; + + Result := inherited AddTag(AStream, AIFDRecord, AData, AParent); + + // We only handle 16-bit integer types here for further processing + if not (tagDef.TagType in [ttUInt16, ttSInt16]) then + exit; + + // Put binary data into a word array and fix endianness + n := Length(AData) div TagElementSize[ord(tagDef.TagType)]; + if FBigEndian then + for i:=0 to n-1 do AData[i] := BEtoN(AData[i]) + else + for i:=0 to n-1 do AData[i] := LEtoN(AData[i]); + SetLength(w, n); + Move(AData[0], w[0], Length(AData)); + + // This is a special treatment of array tags which will be added as + // separate "MakerNote" tags. + case AIFDRecord.TagID of + 1: // Exposure Info 1 + with FImgInfo.ExifData do begin + AddMakerNoteTag(1, 1, 'Macro mode', w[1], rsCanonMacroLkup); + AddMakerNoteTag(1, 2, 'Self-timer', w[2]/10, '%2:.1f s'); + AddMakerNoteTag(1, 3, 'Quality', w[3], rsCanonQualityLkup); + AddMakerNoteTag(1, 4, 'Flash mode', w[4], rsCanonFlashLkup); + AddMakerNoteTag(1, 5, 'Drive mode', w[5], rsSingleContinuous); + AddMakerNoteTag(1, 7, 'Focus mode', w[7], rsCanonFocusLkup); + AddMakerNoteTag(1, 9, 'Record mode', w[9], rsCanonRecLkup); + AddMakerNoteTag(1,10, 'Image size', w[10], rsCanonSizeLkup); + AddMakerNoteTag(1,11, 'Easy shoot', w[11], rsCanonEasyLkup); + AddMakerNoteTag(1,12, 'Digital zoom', w[12], rsCanonZoomLkup); + AddMakerNoteTag(1,13, 'Contrast', w[13], rsCanonGenLkup); + AddMakerNoteTag(1,14, 'Saturation', w[14], rsCanonGenLkup); + AddMakerNoteTag(1,15, 'Sharpness', w[15], rsCanonGenLkup); + AddMakerNoteTag(1,16, 'CCD ISO', w[16], rsCanonISOLkup); + AddMakerNoteTag(1,17, 'Metering mode', w[17], rsCanonMeterLkup); + AddMakerNoteTag(1,18, 'Focus type', w[18], rsCanonFocTypeLkup); + AddMakerNoteTag(1,19, 'AFPoint', w[19], rsCanonAFLkup); + AddMakerNoteTag(1,20, 'Exposure mode', w[20], rsCanonExposeLkup); + AddMakerNoteTag(1,24, 'Long focal', w[24]); + AddMakerNoteTag(1,25, 'Short focal', w[25]); + AddMakerNoteTag(1,26, 'Focal units', w[26]); + AddMakerNoteTag(1,28, 'Flash activity', w[28], rsCanonFlashActLkup); + AddMakerNoteTag(1,29, 'Flash details', w[29]); + AddMakerNoteTag(1,32, 'Focus mode', w[32], rsSingleContinuous); + AddMakerNoteTag(1,33, 'AESetting', w[33], rsCanonAELkup); + AddMakerNoteTag(1,34, 'Image stabilization', w[34], rsSingleContinuous); + end; + 2: // Focal length + with FImgInfo.ExifData do begin + AddMakerNoteTag(2, 0, 'FocalType', w[0], rsCanonFocalTypeLkup); + AddMakerNoteTag(2, 1, 'FocalLength', w[1]); + end; + 4: // ExposureInfo2 + with FImgInfo.ExifData do begin + AddMakerNoteTag(4, 7, 'WhiteBalance', w[7], rsCanonWhiteBalLkup); + AddMakerNoteTag(4, 8, 'Slow shutter', w[8], rsCanonSloShuttLkup); + AddMakerNoteTag(4, 9, 'SequenceNumber', w[9]); + AddMakerNoteTag(4,11, 'OpticalZoomStep', w[11]); + AddMakerNoteTag(4,12, 'Camera temperature', w[12]); + AddMakerNoteTag(4,14, 'AFPoint', w[14]); + AddMakerNoteTag(4,15, 'FlashBias', w[15], rsCanonBiasLkup); + AddMakerNoteTag(4,19, 'Distance', w[19]); + AddMakerNoteTag(4,21, 'FNumber', w[21]); + AddMakerNoteTag(4,22, 'Exposure time', w[22]); + AddMakerNoteTag(4,23, 'Measured EV2', w[23]); + AddMakerNoteTag(4,24, 'Bulb duration', w[24]); + AddMakerNoteTag(4,26, 'Camera type', w[26], rsCanonCamTypeLkup); + AddMakerNoteTag(4,27, 'Auto rotation', w[27], rsCanonAutoRotLkup); + AddMakerNoteTag(4,28, 'NDFilter', w[28], rsCanonGenLkup); + end; + 5: // Panorma + with FImgInfo.ExifData do begin + AddMakerNoteTag(5, 2, 'Panorama frame number', w[2]); + AddMakerNoteTag(5, 5, 'Panorama direction', w[5], rsCanonPanDirLkup); + end; + end; +end; + +//============================================================================== +// TCasioMakerNoteReader +//============================================================================== +function TCasioMakerNoteReader.Prepare(AStream: TStream): Boolean; +var + p: Int64; + hdr: Array[0..5] of ansichar; +begin + Result := false; + + p := AStream.Position; + AStream.Read({%H-}hdr[0], SizeOf(hdr)); + if (hdr[0] = 'Q') and (hdr[1] = 'V') and (hdr[2] = 'C') and + (hdr[3] = #0) and (hdr[4] = #0) and (hdr[5] = #0) + then begin + FVersion := 2; + BuildCasio2TagDefs(FTagDefs); + AStream.Position := p + SizeOf(hdr); + end else + begin + FVersion := 1; + BuildCasio1TagDefs(FTagDefs); + AStream.Position := p; + end; + + FBigEndian := true; + Result := true; +end; + + +//============================================================================== +// TMinoltaMakerNoteReader +//============================================================================== +function TMinoltaMakerNoteReader.AddTag(AStream: TStream; + const AIFDRecord: TIFDRecord; const AData: TBytes; AParent: TTagID): Integer; +var + tagDef: TTagDef; + v: array of DWord; + n, i: Integer; + t: TTagID; + d: Integer; + isDiMAGE7Hi: Boolean; + //p: PByte; +begin + Result := -1; + + tagDef := FindTagDef(AIFDRecord.TagID or AParent); + if (tagDef = nil) then + exit; + + Result := inherited AddTag(AStream, AIFDRecord, AData, AParent); + + // This is a special treatment of array tags which will be added as + // separate "MakerNote" tags. + // Ref: https://sno.phy.queensu.ca/~phil/exiftool/TagNames/Minolta.html#CameraSettings + t := AIFDRecord.TagID; + case AIFDRecord.TagID of + $0001, + $0003: // Minolta camera settings tags + // Contains an array of ULong values encoded in big-endian style, + // regardless of the byte order in the picture (i.e., even if the + // JPEG or TIFF itself is little-endian). + begin + // Put binary data into a DWord array and fix endianness + // ASSUMING HERE THAT DATA ARE ULONG HERE! + n := Length(AData) div TagElementSize[ord(ttUInt32)]; + SetLength(v, n); + Move(AData[0], v[0], Length(AData)); + for i:=0 to n-1 do + v[i] := BEtoN(v[i]); + // Fix problem with DiMAGE7Hi (http://www.dalibor.cz/software/minolta-makernote) + isDiMAGE7Hi := FModel = 'DiMAGE7Hi'; + if isDiMAGE7Hi then d := 1 else d := 0; + with FImgInfo.ExifData do begin + AddMakerNoteTag(t, 1, 'Exposure mode', v[1], rsMinoltaExposureModeLkup, '', ttUInt32); + AddMakerNoteTag(t, 2, 'Flash mode', v[2], rsMinoltaFlashModeLkup, '', ttUInt32); + AddMakerNoteTag(t, 3, 'White balance', v[3], '', '', ttUInt32); + AddMakerNoteTag(t, 4, 'Minolta image size', v[4], rsMinoltaImageSizeLkup1, '', ttUInt32); + AddMakerNoteTag(t, 5, 'Minolta quality', v[5], rsMinoltaQualityLkup, '', ttUInt32); + AddMakerNoteTag(t, 6, 'Drive mode', v[6], rsMinoltaDriveModeLkup, '', ttUInt32); + AddMakerNoteTag(t, 7, 'Metering mode', v[7], rsMinoltaMeteringModeLkup, '', ttUInt32); + AddMakerNoteTag(t, 8, 'ISO', v[8], '', '', ttUInt32); + AddMakerNoteTag(t, 9, 'Exposure time', v[9], '', '', ttUInt32); + AddMakerNoteTag(t,10, 'F number', v[10], '', '', ttUInt32); + AddMakerNoteTag(t,11, 'Macro mode', v[11], rsOffOn, '', ttUInt32); + AddMakerNoteTag(t,12, 'Digital zoom', v[12], rsMinoltaDigitalZoomLkup, '', ttUInt32); + AddMakerNoteTag(t,13, 'Exposure compensation', v[13], '', '', ttUInt32); + AddMakerNoteTag(t,14, 'Bracket step', v[14], rsMinoltaBracketStepLkup, '', ttUInt32); + AddMakerNoteTag(t,16, 'Interval length', v[16], '', '', ttUInt32); + AddMakerNoteTag(t,17, 'Interval number', v[17], '', '', ttUInt32); + AddMakerNoteTag(t,18, 'Focal length', v[18], '', '', ttUInt32); // crashes + AddMakerNoteTag(t,19, 'Focus distance', v[19], '', '', ttUInt32); + AddMakerNoteTag(t,20, 'Flash fired', v[20], rsNoYes, '', ttUInt32); + AddMakerNoteTag(t,21, 'Minolta date', v[21], '', '', ttUInt32); + AddMakerNoteTag(t,22, 'Minolta time', v[22], '', '', ttUInt32); + AddMakerNoteTag(t,23, 'Max aperture', v[23], '', '', ttUInt32); + AddMakerNoteTag(t,26, 'File number memory', v[26], rsOffOn, '', ttUInt32); + AddMakerNoteTag(t,27, 'Last file number', v[27], '', '', ttUInt32); + AddMakerNoteTag(t,28, 'Color balance red', v[28], '', '', ttUInt32); + AddMakerNoteTag(t,29, 'Color balance green', v[29], '', '', ttUInt32); + AddMakerNoteTag(t,30, 'Color balance blue', v[30], '', '', ttUInt32); + AddMakerNoteTag(t,31, 'Saturation', v[31], '', '', ttUInt32); + AddMakerNoteTag(t,32, 'Contrast', v[32], '', '', ttUInt32); + AddMakerNoteTag(t,33, 'Sharpness', v[33], rsMinoltaSharpnessLkup, '', ttUInt32); + AddMakerNoteTag(t,34, 'Subject program', v[34], rsMinoltaSubjectProgramLkup, '', ttUInt32); + AddMakerNoteTag(t,35, 'Flash exposure compensation', v[35], '', '', ttUInt32); + AddMakerNoteTag(t,36, 'AE setting', v[36], rsMinoltaIsoSettingLkup, '', ttUInt32); + AddMakerNoteTag(t,37, 'Minolta model ID', v[37], rsMinoltaModelIDLkup, '', ttUInt32); + AddMakerNoteTag(t,38, 'Interval mode', v[38], rsMinoltaIntervalModeLkup, '', ttUInt32); + AddMakerNoteTag(t,39, 'Folder name', v[39], rsMinoltaFolderNameLkup, '', ttUInt32); + AddMakerNoteTag(t,40, 'Color mode', v[40], rsMinoltaColorModeLkup, '', ttUInt32); + AddMakerNoteTag(t,41, 'Color filter', v[41], '', '', ttUInt32); + AddMakerNoteTag(t,42, 'BW filter', v[42], '', '', ttUInt32); + AddMakerNoteTag(t,43, 'Internal flash', v[43], rsMinoltaInternalFlashLkup, '', ttUInt32); + AddMakerNoteTag(t,44, 'Brightness', v[44], '', '', ttUInt32); + AddMakerNoteTag(t,45, 'Spot focus point X', v[45], '', '', ttUInt32); + AddMakerNoteTag(t,46, 'Spot focus point Y', v[46], '', '', ttUInt32); + AddMakerNoteTag(t,47, 'Wide focus zone', v[47], rsMinoltaWideFocusZoneLkup, '', ttUInt32); + AddMakerNoteTag(t,48, 'Focus mode', v[48], rsMinoltaFocusModeLkup, '', ttUInt32); + AddMakerNoteTag(t,49, 'Focus area', v[49], rsMinoltaFocusAreaLkup, '', ttUInt32); + AddMakerNoteTag(t,50, 'DEC position', v[50], rsMinoltaDECPositionLkup, '', ttUInt32); + if isDiMAGE7Hi then + AddMakerNoteTag(t,51, 'Color profile', v[51], rsMinoltaColorProfileLkup, '', ttUInt32); + AddMakerNoteTag(t,51+d, 'Data imprint', v[52], rsMinoltaDataImprintLkup, '', ttUInt32); + AddMakerNoteTag(t,63+d, 'Flash metering', v[63], rsMinoltaFlashMeteringLkup, '', ttUInt32); // or is the index 53? + end; + end; + $0010: // CameraInfoA100 + begin + //p := @AData[0]; + //... conversion stopped due to unclear documentation on + // https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Minolta.html#CameraInfoA100 + // --- Is there an index 0? + end; + end; +end; + + +//============================================================================== +// TOlympusMakerNoteReader +//============================================================================== + +{ Read the header and determine the version of the olympus makernotes: + - version 1: header OLYMP#0#1+0, offsets relative to EXIF + - version 2: header OLYMP#0#2#0, offsets relative to EXIF + - version 3: header OLYMPUS#0 + BOM (II or MM) + version (#3#0) + offsets relative to maker notes !!!! } +function TOlympusMakerNoteReader.Prepare(AStream: TStream): Boolean; +var + p: Int64; + hdr: packed array[0..11] of ansichar; +begin + Result := false; + + // Remember begin of makernotes tag. + p := AStream.Position; + + // Read header + AStream.Read(hdr{%H-}, 12); + + // The first 5 bytes must be 'OLYMP'; this is common to all versions + if not ((hdr[0] = 'O') and (hdr[1] = 'L') and (hdr[2] = 'Y') and (hdr[3] = 'M') and (hdr[4] = 'P')) then + exit; + + FVersion := 0; + // Version 1 or 2 if a #0 follows after the 'OLYMP' + if (hdr[5] = #0) then begin + if (hdr[6] = #1) and (hdr[7] = #0) then + FVersion := 1 + else + if (hdr[6] = #2) and (hdr[7] = #0) then + FVersion := 2; + end else + // Version 3 if the first 8 bytes are 'OLYMPUS'#0 + if (hdr[5] = 'U') and (hdr[6] = 'S') and (hdr[7] = #0) then begin + // Endianness marker, like in standard EXIF: 'II' or 'MM' + if (hdr[8] = 'I') and (hdr[9] = 'I') then + FBigEndian := false + else + if (hdr[8] = 'M') and (hdr[9] = 'M') then + FBigEndian := true; + if (hdr[10] = #3) then + FVersion := 3; + FStartPosition := p; // Offsets are relative to maker notes + end; + + // Jump to begin of IFD + case FVersion of + 1, 2: AStream.Position := p + 8; + 3 : AStream.Position := p + 12; + else exit; + end; + + BuildOlympusTagDefs(FTagDefs); + Result := true; +end; + + + (* +{ Read the header and determine the version of the olympus makernotes: + - version 1: header OLYMP#0#1+0, offsets relative to EXIF + - version 2: header OLYMP#0#2#0, offsets relative to EXIF + - version 3: header OLYMPUS#0 + BOM (II or MM) + version (#3#0) + offsets relative to maker notes !!!! } +procedure TOlympusMakerNoteReader.ReadIFD(AStream: TStream; AParent: TTagID); +//procedure TOlympusMakerNoteReader.ReadIFD(AStream: TStream; AGroup: TTagGroup); +var + p: Int64; + hdr: packed array[0..11] of ansichar; +begin + if TTagIDRec(AParent).Parent = TAG_MAKERNOTE then + begin +// if AGroup = tgExifMakerNote then + // Remember begin of makernotes tag. + p := AStream.Position; + + // Read header + AStream.Read(hdr, 12); + + // The first 5 bytes must be 'OLYMP'; this is common to all versions + if not ((hdr[0] = 'O') and (hdr[1] = 'L') and (hdr[2] = 'Y') and (hdr[3] = 'M') and (hdr[4] = 'P')) then + exit; + + FVersion := 0; + // Version 1 or 2 if a #0 follows after the 'OLYMP' + if (hdr[5] = #0) then begin + if (hdr[6] = #1) and (hdr[7] = #0) then + FVersion := 1 + else + if (hdr[6] = #2) and (hdr[7] = #0) then + FVersion := 2; + end else + // Version 3 if the first 8 bytes are 'OLYMPUS'#0 + if (hdr[5] = 'U') and (hdr[6] = 'S') and (hdr[7] = #0) then begin + // Endianness marker, like in standard EXIF: 'II' or 'MM' + if (hdr[8] = 'I') and (hdr[9] = 'I') then + FBigEndian := false + else + if (hdr[8] = 'M') and (hdr[9] = 'M') then + FBigEndian := true; + if (hdr[10] = #3) then + FVersion := 3; + FStartPosition := p; // Offsets are relative to maker notes + end; + + // Jump to begin of IFD + case FVersion of + 1, 2: AStream.Position := p + 8; + 3 : AStream.Position := p + 12; + else exit; + end; + + BuildOlympusTagDefs(FTagDefs) + end; + + inherited; +end; + *) + +//============================================================================== +// Tag definition lists +//============================================================================== + +const + M = DWord(TAGPARENT_MAKERNOTE); + +procedure BuildCanonTagDefs(AList: TTagDefList); +begin + Assert(AList <> nil); + with AList do begin + AddUShortTag(M+$0001, 'ExposureInfo1'); + AddUShortTag(M+$0002, 'Panorama'); + AddUShortTag(M+$0004, 'ExposureInfo2'); + AddStringTag(M+$0006, 'ImageType'); + AddStringTag(M+$0007, 'FirmwareVersion'); + AddULongTag (M+$0008, 'ImageNumber'); + AddStringTag(M+$0009, 'OwnerName'); + AddULongTag (M+$000C, 'CameraSerialNumber'); + AddUShortTag(M+$000F, 'CustomFunctions'); + end; +end; + +{ Casio Type 1 + Standard TIFF IFD Data using Casio Type 1 Tags but always uses + Motorola (Big-Endian) byte alignment + This makernote has no header - the IFD starts immediately + Ref.: http://www.ozhiker.com/electronics/pjmt/jpeg_info/casio_mn.html } +procedure BuildCasio1TagDefs(AList: TTagDefList); +begin + Assert(AList <> nil); + with AList do begin + AddUShortTag(M+$0001, 'RecordingMode', 1, '', rsCasioRecordingModeLkup); + AddUShortTag(M+$0002, 'Quality', 1, '', rsEconomyNormalFine1); + AddUShortTag(M+$0003, 'FocusingMode', 1, '', rsCasioFocusingModeLkup); + AddUShortTag(M+$0004, 'FlashMode', 1, '', rsCasioFlashModeLkup); + AddUShortTag(M+$0005, 'FlashIntensity', 1, '', rsCasioFlashIntensityLkup); + AddULongTag (M+$0006, 'ObjectDistance', 1, '', '', '%d mm'); + AddUShortTag(M+$0007, 'WhiteBalance', 1, '', rsCasioWhiteBalanceLkup); + AddULongTag (M+$000A, 'DigitalZoom', 1, '', rsCasioDigitalZoomLkup); + AddUShortTag(M+$000B, 'Sharpness', 1, '', rsNormalSoftHard); + AddUShortTag(M+$000C, 'Contrast', 1, '', rsNormalLowHigh); + AddUShortTag(M+$000D, 'Saturation', 1, '', rsNormalLowHigh); + AddUShortTag(M+$000A, 'DigitalZoom', 1, '', rsCasioDigitalZoomLkup); + AddUShortTag(M+$0014, 'CCDSensitivity', 1, '', rsCasioCCDSensitivityLkup); + end; +end; + +{ Case Type 2 + Header: 6 Bytes "QVC\x00\x00\x00" + IFD Data: Standard TIFF IFD Data using Casio Type 2 Tags but always uses + Motorola (Big-Endian) Byte Alignment. + All EXIF offsets are relative to the start of the TIFF header at the beginning of the EXIF segment + Ref.: http://www.ozhiker.com/electronics/pjmt/jpeg_info/casio_mn.html + http://www.exiv2.org/tags-casio.html + https://sno.phy.queensu.ca/~phil/exiftool/TagNames/Casio.html#Type2 +} +procedure BuildCasio2TagDefs(AList: TTagDefList); +begin + Assert(AList <> nil); + with AList do begin + AddUShortTag (M+$0002, 'PreviewImageSize', 2); // width and height, in pixels + AddULongTag (M+$0003, 'PreviewImageLength'); + AddULongTag (M+$0004, 'PreviewImageStart'); + AddUShortTag (M+$0008, 'QualityMode', 1, '', rsEconomyNormalFine); + AddUShortTag (M+$0009, 'ImageSize', 1, '', rsCasioImageSize2Lkup); + AddUShortTag (M+$000D, 'FocusMode', 1, '', rsCasioFocusMode2Lkup); + AddUShortTag (M+$0014, 'ISOSpeed', 1, '', rsCasioISOSpeed2Lkup); + AddUShortTag (M+$0019, 'WhiteBalance', 1, '', rsCasioWhiteBalance2Lkup); + AddURationalTag(M+$001D, 'FocalLength'); + AddUShortTag (M+$001F, 'Saturation', 1, '', rsLowNormalHigh); + AddUShortTag (M+$0020, 'Contrast', 1, '', rsLowNormalHigh); + AddUShortTag (M+$0021, 'Sharpness', 1, '', rsCasioSharpness2Lkup); + AddBinaryTag (M+$0E00, 'PrintIM'); + AddBinaryTag (M+$2000, 'PreviewImage'); + AddStringTag (M+$2001, 'FirwareDate', 18); + AddUShortTag (M+$2011, 'WhiteBalanceBias', 2); + AddUShortTag (M+$2012, 'WhiteBalance2', 2, '', rsCasioWhiteBalance22Lkup); + AddUShortTag (M+$2021, 'AFPointPosition', 4); + AddULongTag (M+$2022, 'ObjectDistance'); + AddUShortTag (M+$2034, 'FlashDistance'); + AddByteTag (M+$2076, 'SpecialEffectMode', 3); // to do: array lkup - should be: '0 0 0' = Off,'1 0 0' = Makeup,'2 0 0' = Mist Removal,'3 0 0' = Vivid Landscape + AddBinaryTag (M+$2089, 'FaceInfo'); + AddByteTag (M+$211C, 'FacesDetected'); + AddUShortTag (M+$3000, 'RecordMode', 1, '', rsCasioRecordMode2Lkup); + AddUShortTag (M+$3001, 'ReleaseMode', 1, '', rsCasioReleaseMode2Lkup); + AddUShortTag (M+$3002, 'Quality', 1, '', rsEconomyNormalFine1); + AddUShortTag (M+$3003, 'FocusMode2', 1, '', rsCasioFocusMode2Lkup); + AddStringTag (M+$3006, 'HometownCity'); + AddUShortTag (M+$3007, 'BestShotMode'); // Lkup depends severly on camera model + AddUShortTag (M+$3008, 'AutoISO', 1, '', rsCasioAutoIso2Lkup); + AddUShortTag (M+$3009, 'AFMode', 1, '', rsCasioAFMode2Lkup); + AddBinaryTag (M+$3011, 'Sharpness2'); + AddBinaryTag (M+$3012, 'Contrast2'); + AddBinaryTag (M+$3013, 'Saturation2'); + AddUShortTag (M+$3014, 'ISO'); + AddUShortTag (M+$3015, 'ColorMode', 1, '', rsCasioColorMode2Lkup); + AddUShortTag (M+$3016, 'Enhancement', 1, '', rsCasioEnhancement2Lkup); + AddUShortTag (M+$3017, 'ColorFilter', 1, '', rsCasioColorFilter2Lkup); + AddUShortTag (M+$301B, 'ArtMode', 1, '', rsCasioArtMode2Lkup); + AddUShortTag (M+$301C, 'SequenceNumber'); + AddUShortTag (M+$301D, 'BracketSequence', 2); + AddUShortTag (M+$3020, 'ImageStabilization', 1, '', rsCasioImageStabilization2Lkup); + AddUShortTag (M+$302A, 'LightingMode', 1, '', rsCasioLightingMode2Lkup); + AddUShortTag (M+$302B, 'PortraitRefiner', 1, '', rsCasioPortraitRefiner2Lkup); + AddUShortTag (M+$3030, 'SpecialEffectLevel'); + AddUShortTag (M+$3031, 'SpecialEffectSetting', 1, '', rsCasioSpecialEffectSetting2Lkup); + AddUShortTag (M+$3103, 'DriveMode', 1, '', rsCasioDriveMode2Lkup); + AddBinaryTag (M+$310B, 'ArtModeParameters', 3); + AddUShortTag (M+$4001, 'CaptureFrameRate'); + AddUShortTag (M+$4003, 'VideoQuality', 1, '', rsCasioVideoQuality2Lkup); + + // to do... + end; +end; + +procedure BuildEpsonTagDefs(AList: TTagDefList); +begin + Assert(AList <> nil); + with AList do begin + AddUShortTag(M+$0200, 'SpecialMode'); + AddUShortTag(M+$0201, 'JpegQuality'); + AddUShortTag(M+$0202, 'Macro'); + AddUShortTag(M+$0204, 'DigitalZoom'); + AddUShortTag(M+$0209, 'CameraID'); + AddStringTag(M+$020A, 'Comments'); + AddUShortTag(M+$020B, 'Width'); + AddUShortTag(M+$020C, 'Height'); + AddUShortTag(M+$020D, 'SoftRelease'); + end; +end; + +procedure BuildFujiTagDefs(AList: TTagDefList); +begin + Assert(AList <> nil); + with AList do begin + AddBinaryTag (M+$0000, 'Version'); + AddStringTag (M+$1000, 'Quality'); + AddUShortTag (M+$1001, 'Sharpness', 1, '', rsFujiSharpnessLkup); + AddUShortTag (M+$1002, 'WhiteBalance', 1, '', rsFujiWhiteBalLkup); + AddUShortTag (M+$1003, 'Saturation', 1, '', rsFujiSaturationLkup); + AddUShortTag (M+$1004, 'Contrast', 1, '', rsFujiContrastLkup); + AddUShortTag (M+$1005, 'ColorTemperature'); + AddUShortTag (M+$1006, 'Contrast', 1, '', rsFujiContrastLkup1); + AddURationalTag(M+$100A, 'WhiteBalanceFineTune'); + AddUShortTag (M+$100B, 'NoiseReduction', 1, '', rsFujiNoiseReductionLkup); + AddUShortTag (M+$100E, 'HighISONoiseReduction', 1, '', rsFujiHighIsoNoiseReductionLkup); + AddUShortTag (M+$1010, 'FlashMode', 1, '', rsFujiFlashModeLkup); + AddURationalTag(M+$1011, 'FlashStrength'); + AddUShortTag (M+$1020, 'Macro', 1, '', rsOffOn); + AddUShortTag (M+$1021, 'FocusMode', 1, '', rsAutoManual); + AddUShortTag (M+$1030, 'SlowSync', 1, '', rsOffOn); + AddUShortTag (M+$1031, 'PictureMode', 1, '', rsFujiPictureModeLkup); + AddUShortTag (M+$1032, 'ExposureCount'); + AddUShortTag (M+$1033, 'EXRAuto', 1, '', rsAutoManual); + AddUShortTag (M+$1034, 'EXRMode', 1, '', rsFujiEXRModeLkup); + AddSLongTag (M+$1040, 'ShadowTone', 1, '', rsFujiShadowHighlightLkup); + AddSLongTag (M+$1041, 'HighlightTone', 1, '', rsFujiShadowHighlightLkup); + AddULongTag (M+$1044, 'DigitalZoom'); + AddUShortTag (M+$1050, 'ShutterType', 1, '', rsFujiShutterTypeLkup); + AddUShortTag (M+$1100, 'AutoBracketing', 1, '', rsFujiAutoBracketingLkup); + AddUShortTag (M+$1101, 'SequenceNumber'); + AddUShortTag (M+$1153, 'PanoramaAngle'); + AddUShortTag (M+$1154, 'PanoramaDirection', 1, '', rsFujiPanoramaDirLkup); + AddULongTag (M+$1201, 'AdvancedFilter', 1, '', rsFujiAdvancedFilterLkup); + AddUShortTag (M+$1210, 'ColorMode', 1, '', rsFujiColorModeLkup); + AddUShortTag (M+$1300, 'BlurWarning', 1, '', rsFujiBlurWarningLkup); + AddUShortTag (M+$1301, 'FocusWarning', 1, '', rsFujiFocusWarningLkup); + AddUShortTag (M+$1302, 'ExposureWarning', 1, '', rsFujiExposureWarningLkup); + AddUShortTag (M+$1400, 'DynamicRange', 1, '', rsFujiDynamicRangeLkup); + AddURationalTag(M+$1404, 'MinFocalLength'); + AddURationalTag(M+$1405, 'MaxFocalLength'); + AddURationalTag(M+$1406, 'MaxApertureAtMinFocal'); + AddURationalTag(M+$1407, 'MaxApertureAtMaxFocal'); + AddUShortTag (M+$140B, 'AutoDynamicRange'); + AddUShortTag (M+$1422, 'ImageStabilization', 3); + AddUShortTag (M+$1425, 'SceneRecognition', 1, '', rsFujiSceneRecognLkup); + AddUShortTag (M+$1431, 'Rating'); + AddStringTag (M+$8000, 'FileSource'); + AddULongTag (M+$8002, 'OrderNumber'); + AddUShortTag (M+$8003, 'FrameNumber'); + end; +end; + +{ The Minolta MakerNote can be quite long, about 12 kB. In the beginning + of this tag there is a normal tag directory in usual format. + References: + - http://www.dalibor.cz/software/minolta-makernote + - https://sno.phy.queensu.ca/~phil/exiftool/TagNames/Minolta.html } +procedure BuildMinoltaTagDefs(AList: TTagDefList); +begin + Assert(AList <> nil); + with AList do begin + { This tag stores the string 'MLT0', not zero-terminated, as an identifier } + AddBinaryTag (M+$0000, 'Version', 4, '', '', '', TVersionTag); + + { Stores all settings which were in effect when taking the picture. + Details depend on camera. } + AddBinaryTag (M+$0001, 'MinoltaCameraSettingsOld'); // Camera D5, D7, S304, S404 + AddBinaryTag (M+$0003, 'MinoltaCameraSettings'); // Camera D7u, D7i, D7Hi + + // this is the size of the JPEG (compressed) or TIFF or RAW file. + AddULongTag (M+$0040, 'CompressedImageSize'); + + { Stores the thumbnail image (640×480). It is in normal JFIF format but the + first byte should be changed to 0xFF. Beware! Sometimes the thumbnail + is not stored in the file and this tag points beyond the end of the file. } + AddBinaryTag (M+$0081, 'ReviewImage'); + + { The cameras D7u, D7i and D7Hi no longer store the thumbnail inside the tag. + It has instead two tags describing the position of the thumbnail in the + file and its size } + AddULongTag (M+$0088, 'PreviewImageStart'); + AddULongTag (M+$0089, 'PreviewImageLength'); + + AddULongTag (M+$0100, 'SceneMode', 1, '', rsMinoltaSceneModeLkup); + AddULongTag (M+$0101, 'ColorMode', 1, '', rsMinoltaColorModeLkup); + AddULongtag (M+$0102, 'Quality', 1, '', rsMinoltaQualityLkup); + AddULongTag (M+$0103, 'ImageSize', 1, '', rsMinoltaImageSizeLkup); + AddSRationalTag(M+$0104, 'FlashExposureComp'); + AddULongTag (M+$0105, 'TeleConverter', 1, '', rsMinoltaTeleconverterLkup); + AddULongTag (M+$0107, 'ImageStabilization', 1, '', rsMinoltaImageStabLkup); + AddULongTag (M+$0109, 'RawAndJpegRecording', 1, '', rsOffOn); + AddULongTag (M+$010A, 'ZoneMatching', 1, '', rsMinoltaZoneMatchingLkup); + AddULongTag (M+$010B, 'ColorTemperature', 1); + AddULongTag (M+$010C, 'LensType', 1); + AddSLongTag (M+$0111, 'ColorCompensationFilter', 1); + AddULongTag (M+$0112, 'WhiteBalanceFileTune', 1); + AddULongTag (M+$0113, 'ImageStabilization', 1, '', rsOffOn); + AddULongTag (M+$0115, 'WhiteBalance', 1, '', rsMinoltaWhiteBalanceLkup); + AddBinaryTag (M+$0E00, 'PrintPIM'); + end; +end; + +// not tested +procedure BuildNikon1TagDefs(AList: TTagDefList); +begin + Assert(AList <> nil); + with AList do begin + AddUShortTag(M+$0002, 'FamilyID'); + AddUShortTag(M+$0003, 'Quality', 1, '', rsNikonQualityLkup); + AddUShortTag(M+$0004, 'ColorMode', 1, '', rsNikonColorModeLkup); + AddUShortTag(M+$0005, 'ImageAdjustment', 1, '', rsNikonImgAdjLkup); + AddUShortTag(M+$0006, 'ISOSpeed', 1, '', rsNikonISOLkup); + AddUShortTag(M+$0007, 'WhiteBalance', 1, '', rsNikonWhiteBalanceLkup); + AddUShortTag(M+$0008, 'Focus'); + AddUShortTag(M+$000A, 'DigitalZoom'); + AddUShortTag(M+$000B, 'Converter', 1, '', rsNikonConverterLkup); + end; +end; + +{ for Nikon D1, E880, E885, E990, E995, E2500, E5000 + Ref http://www.tawbaware.com/990exif.htm + https://sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html } +procedure BuildNikon2TagDefs(AList: TTagDefList); +begin + Assert(AList <> nil); + with AList do begin + AddBinaryTag (M+$0001, 'Version', 4, '', '', '', TVersionTag); + AddUShortTag (M+$0002, 'ISO', 2); + AddStringTag (M+$0003, 'ColorMode'); + AddStringTag (M+$0004, 'Quality'); + AddStringTag (M+$0005, 'WhiteBalance'); + AddStringtag (M+$0006, 'ImageSharpening'); + AddStringTag (M+$0007, 'FocusMode'); + AddStringTag (M+$0008, 'FlashSetting'); + AddStringTag (M+$0009, 'FlashType'); + AddURationalTag(M+$000A, 'UNKNOWN'); + AddStringTag (M+$000F, 'ISOSelection'); + AddStringTag (M+$0080, 'ImageAdjustment'); + AddStringTag (M+$0081, 'ToneComp'); + AddStringTag (M+$0082, 'AuxiliaryLens'); + AddURationalTag(M+$0085, 'ManualFocusDistance'); + AddURationalTag(M+$0086, 'DigitalZoom'); + AddBinaryTag (M+$0088, 'AFInfo'); + AddStringTag (M+$008D, 'ColorHue'); + AddStringTag (M+$008F, 'SceneMode'); + AddStringTag (M+$0090, 'LightSource'); + AddBinaryTag (M+$0010, 'DataDump'); + end; +end; + +// Most from https://sno.phy.queensu.ca/~phil/exiftool/TagNames/Olympus.html +// some from dExif + +const + E = $2010 shl 16; // Equipment version + C = $2011 shl 16; // Camera settings + +procedure BuildOlympusTagDefs(AList: TTagDefList); +begin + Assert(AList <> nil); + with AList do begin + AddBinaryTag (M+$0000, 'Version', 4, '', '', '', TVersionTag); + + { Stores all settings which were in effect when taking the picture. + Details depend on camera. } + AddBinaryTag (M+$0001, 'MinoltaCameraSettingsOld'); //, $FFFF, '', '', '', TSubIFDTag, true); + AddBinaryTag (M+$0003, 'MinoltaCameraSettings'); //, $FFFF, '', '', '', TSubIFDTag, false); + + // this is the size of the JPEG (compressed) or TIFF or RAW file. + AddULongTag (M+$0040, 'CompressedImageSize'); + + { Stores the thumbnail image (640×480). It is in normal JFIF format but the + first byte should be changed to 0xFF. Beware! Sometimes the thumbnail + is not stored in the file and this tag points beyond the end of the file. } + AddBinaryTag (M+$0081, 'ReviewImage'); + + { The cameras D7u, D7i and D7Hi no longer store the thumbnail inside the tag. + It has instead two tags describing the position of the thumbnail in the + file and its size } + AddULongTag (M+$0088, 'PreviewImageStart'); + AddULongTag (M+$0089, 'PreviewImageLength'); + + AddULongTag (M+$0200, 'SpecialMode', 3); + AddUShortTag (M+$0201, 'JpegQuality', 1, '', rsOlympusJpegQualLkup); + AddUShortTag (M+$0202, 'Macro', 1, '', rsOlympusMacroLkup); + AddURationalTag(M+$0204, 'DigitalZoom'); +// AddUShortTag (M+$0207, 'Firmware'); + AddStringTag (M+$9207, 'CameraType'); + AddStringTag (M+$0208, 'PictureInfo'); + AddStringTag (M+$0209, 'CameraID'); + AddUShortTag (M+$020B, 'EpsonImageWidth'); + AddUShortTag (M+$020C, 'EpsonImageHeight'); + AddStringTag (M+$020D, 'EpsonSoftware'); + AddUShortTag (M+$0403, 'SceneMode', 1, '', rsOlympusSceneModeLkup); + AddStringTag (M+$0404, 'SerialNumber'); + AddStringTag (M+$0405, 'Firmware'); + AddSRationalTag(M+$1000, 'ShutterSpeedValue'); + AddSRationalTag(M+$1001, 'ISOValue'); + AddSRationalTag(M+$1002, 'ApertureValue'); + AddSRationalTag(M+$1003, 'BrightnessValue'); + AddUShortTag (M+$1004, 'FlashMode', 1, '', rsOlympusFlashModeLkup); + AddUShortTag (M+$1005, 'FlashDevice', 1, '', rsOlympusFlashDevLkup); + AddURationalTag(M+$1006, 'Bracket'); + AddSShortTag (M+$1007, 'SensorTemperature'); + AddSShortTag (M+$1008, 'LensTemperature'); + AddUShortTag (M+$100B, 'FocusMode', 1, '', rsAutoManual); + AddURationalTag(M+$100C, 'FocusDistance'); + AddUShortTag (M+$100D, 'ZoomStepCount'); + AddUShortTag (M+$100E, 'FocusStepCount'); + AddUShortTag (M+$100F, 'Sharpness', 1, '', rsOlympusSharpnessLkup); + AddUShortTag (M+$1010, 'FlashChargeLevel'); + AddUShortTag (M+$1011, 'ColorMatrix', 9); + AddUShortTag (M+$1012, 'BlackLevel', 4); + AddUShortTag (M+$1015, 'WhiteBalanceMode', 2); + AddUShortTag (M+$1017, 'RedBalance', 2); + AddUShortTag (M+$1018, 'BlueBalance', 2); + AddStringTag (M+$101A, 'SerialNumber'); + AddURationalTag(M+$1023, 'FlashBias'); + AddUShortTag (M+$1029, 'Contrast', 1, '', rsOlympusContrastLkup); + AddUShortTag (M+$102A, 'SharpnessFactor'); + AddUShortTag (M+$102B, 'ColorControl', 6); + AddUShortTag (M+$102C, 'ValidBits', 2); + AddUShortTag (M+$102D, 'CoringFilter'); + AddULongTag (M+$102E, 'FinalWidth'); + AddULongTag (M+$102F, 'FinalHeight'); + AddUShortTag (M+$1030, 'SceneDetect'); + AddULongTag (M+$1031, 'SceneArea', 8); + AddURationalTag(M+$1034, 'CompressionRatio'); + AddUShortTag (M+$1038, 'AFResult'); + AddUShortTag (M+$1039, 'CCDScanMode', 1, '', rsOlympusCCDScanModeLkup); + AddUShortTag (M+$103A, 'NoiseReduction', 1, '', rsOffOn); + AddUShortTag (M+$103B, 'FocusStepInfinity'); + AddUShortTag (M+$103C, 'FocusStepNear'); + AddSRationalTag(M+$103D, 'LightValueCenter'); + AddSRationalTag(M+$103E, 'LightValuePeriphery'); + AddIFDTag (M+$2010, 'Equipment', '', TSubIFDTag); + AddIFDTag (M+$2011, 'CameraSettings', '', TSubIFDTag); + + // Olympus Equipment Tags + AddBinaryTag (E+$0000, 'EquipmentVersion', 4, '', '', '', TVersionTag); + AddStringTag (E+$0100, 'CameraType', 6); + AddStringTag (E+$0101, 'SerialNumber', 32); + AddStringTag (E+$0102, 'InternalSerialNumber', 32); + AddURationalTag(E+$0103, 'FocalPlaneDiagonal'); + AddULongTag (E+$0104, 'BodyFirmwareVersion'); + AddByteTag (E+$0201, 'LensType', 6); + AddStringTag (E+$0202, 'LensSerialNumber', 32); + AddStringTag (E+$0203, 'LensModel'); + AddULongTag (E+$0204, 'LensFirmwareVersion'); + AddUShortTag (E+$0205, 'MaxApertureAtMinFocal'); + AddUShortTag (E+$0206, 'MaxApertureAtMaxFocal'); + AddUShortTag (E+$0207, 'MinFocalLength'); + AddUShortTag (E+$0208, 'MaxFocalLength'); + AddUShortTag (E+$020A, 'MaxAperture'); + AddUShortTag (E+$020B, 'LensProperties'); + AddByteTag (E+$0301, 'Extender', 6); + AddStringTag (E+$0302, 'ExtenderSerialNumber', 32); + AddStringTag (E+$0303, 'ExtenderModel'); + AddULongTag (E+$0304, 'ExtenderFirmwareVersion'); + AddStringTag (E+$0403, 'ConversionLens'); + AddUShortTag (E+$1000, 'FlashType', 1, '', rsOlympusFlashTypeLkup); + AddUShortTag (E+$1001, 'FlashModel', 1, '', rsOlympusFlashModelLkup); + AddULongTag (E+$1002, 'FlashFirmwareVersion'); + AddStringTag (E+$1003, 'FlashSerialNumber', 32); + + // Olympus camera settings tags + AddBinaryTag (C+$0000, 'CameraSettingsVersion', 4, '', '', '', TVersionTag); + AddULongTag (C+$0100, 'PreviewImageValid', 1, rsOlympusPreviewImgValid, rsOffOn); + AddULongTag (C+$0101, 'PreviewImageStart', 1, rsOlympusPreviewImgStart); + AddULongTag (C+$0102, 'PreviewImageLength', 1, rsOlympusPreviewImgLength); + + end; +end; + +// from dExif. +procedure BuildSanyoTagDefs(AList: TTagDefList); +begin + Assert(AList <> nil); + with AList do begin + AddULongTag (M+$0200, 'SpecialMode', 3, rsSanyoSpecialMode); + AddUShortTag (M+$0201, 'Quality', 1, rsQuality, rsSanyoQualityLkup); + AddUShortTag (M+$0202, 'Macro', 1, rsMacro, rsSanyoMacroLkup); + AddURationalTag(M+$0204, 'DigitalZoom', 1, rsDigitalZoom); + end; +end; + + +initialization + RegisterMakerNoteReader(TCanonMakerNoteReader, 'Canon', ''); + RegisterMakerNoteReader(TCanonMakerNoteReader, 'Casio', ''); + RegisterMakerNoteReader(TMinoltaMakerNoteReader, 'Minolta', ''); + RegisterMakerNoteReader(TOlympusMakerNoteReader, 'Olympus', ''); + +end. diff --git a/components/fpexif/fpemetadata.pas b/components/fpexif/fpemetadata.pas new file mode 100644 index 000000000..d643bbe70 --- /dev/null +++ b/components/fpexif/fpemetadata.pas @@ -0,0 +1,701 @@ +unit fpeMetadata; + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I fpexif.inc} + +interface + +uses + Classes, SysUtils, + {$IFDEF FPC} + LazUTF8, + {$ENDIF} + fpeGlobal, + fpeExifData, fpeIptcData; + +type + TImgInfo = class; + + { TBasicMetadataReaderWriter } + TBasicMetadataReaderWriter = class + protected + FImgInfo: TImgInfo; + FImgFormat: TImgFormat; + procedure Warning(const AMsg: String); + public + constructor Create(AImgInfo: TImgInfo); virtual; + end; + + { TBasicMetadataReader } + TBasicMetadataReader = class(TBasicMetadataReaderWriter) + protected + procedure Error(const AMsg: String); virtual; + public + procedure ReadFromStream(AStream: TStream; AImgFormat: TImgFormat); virtual; + end; + + { TBasicMetadataWriter } + TBasicMetadataWriter = class(TBasicMetadataReaderWriter) + protected + procedure Error(const AMsg: String); virtual; + procedure UpdateSegmentSize(AStream: TStream; ASegmentStartPos: Int64); + public + procedure WriteToStream(AStream: TStream; AImgFormat: TImgFormat); virtual; + end; + + { TImgInfo } + TImgInfo = class + private + FFileName: String; + FFileDate: TDateTime; + FFileSize: Int64; + FImgFormat: TImgFormat; + FImgWidth: Integer; + FImgHeight: Integer; + FWarnings: TStrings; + FMetadataKinds: TMetadataKinds; + FHeaderSegment: TBytes; + FComment: String; + private + FExifData: TExifData; + FIptcData: TIptcData; + function GetComment: String; + function GetWarnings: String; + procedure SetComment(const AValue: String); + protected + procedure Error(const AMsg: String); + function ExtractImgFormat(AStream: TStream): TImgFormat; + procedure MergeToJpegStream(AInputStream, AOutputStream: TStream); + procedure ReadJpeg(AStream: TStream); + procedure ReadTiff(AStream: TStream); + procedure StoreFileInfo(const AFileName: String); + procedure WriteJpeg(AStream: TStream); + public + constructor Create; + destructor Destroy; override; + procedure LoadFromFile(const AFileName: String); + procedure LoadFromStream(AStream: TStream); + procedure SaveToFile(const AFileName: String; AImgFile: String = ''); + + function CreateExifData(ABigEndian: Boolean = false): TExifData; + function CreateIptcData: TIptcData; + + function HasComment: Boolean; + function HasExif: Boolean; + function HasIptc: Boolean; + function HasThumbnail: Boolean; + function HasWarnings: boolean; + + { Comment stored in the Jpeg COM segment } + property Comment: String read GetComment write SetComment; + { Name of the file processed } + property FileName: String read FFileName; + { Date when the file was created } + property FileDate: TDateTime read FFileDate; + { Size of the file in bytes } + property FileSize: Int64 read FFileSize; + { Image format, jpeg or tiff } + property ImgFormat: TImgFormat read FImgFormat; + { Image width } + property ImgWidth: Integer read FImgWidth; + { Image height } + property ImgHeight: Integer read FImgHeight; + { Selects which kind of metadata will be loaded } + property MetadataKinds: TMetadataKinds read FMetadataKinds write FMetadataKinds default mdkAll; + { Warning message - NOTE: Reading of warnings is erasing the warnings list! } + property Warnings: String read GetWarnings; + + property ExifData: TExifData read FExifData; + property IptcData: TIptcData read FIptcData; // to do: rename to IptcData + end; + + +implementation + +uses + Variants, + fpeStrConsts, fpeUtils, fpeExifReadWrite, fpeIptcReadWrite; + +type + TJpegJFIFSegment = packed record + Identifier: packed array[0..4] of AnsiChar; // 'JFIF'#0 + JFIFVersion: packed array[0..1] of Byte; // 01 02 + DensityUnit: Byte; // 0: aspect ratio, 1: inches, 2: cm + XDensity: Word; + YDensity: Word; + ThumbnailWidth: Byte; // Pixel count of thumbnail width... + ThumbnailHeight: Byte; // ... and height + end; + PJpegJFIFSegment = ^TJpegJFIFSegment; + + TJpegSOF0Segment = packed record + DataPrecision: Byte; + ImageHeight: Word; + ImageWidth: Word; + // and more..., not needed here. + end; + PJpegSOF0Segment = ^TJpegSOF0Segment; + +const + { JPEG markers consist of one or more $FF bytes, followed by a marker code + byte (which is not an FF). Here are the marker codes needed by fpExif: } + M_SOF0 = $C0; // Start Of Frame 0 + M_SOI = $D8; // Start Of Image (beginning of datastream) + M_EOI = $D9; // End Of Image (end of datastream) + M_SOS = $DA; // Start Of Scan (begins compressed data) + M_JFIF = $E0; // Jfif marker 224 + M_EXIF = $E1; // Exif marker 225 + M_IPTC = $ED; // IPTC - Photoshop 237 + M_COM = $FE; // Comment 254 + + +//============================================================================== +// TBasicMetaDataWriter +//============================================================================== + +constructor TBasicMetadataReaderWriter.Create(AImgInfo: TImgInfo); +begin + FImgInfo := AImgInfo; +end; + +procedure TBasicMetadataReaderWriter.Warning(const AMsg: String); +begin + FImgInfo.FWarnings.Add(AMsg); +end; + + +//============================================================================== +// TBasicMetaDataReader +//============================================================================== + +procedure TBasicMetadataReader.Error(const AMsg: String); +begin + raise EFpExifReader.Create(AMsg); +end; + +procedure TBasicMetadataReader.ReadFromStream(AStream: TStream; + AImgFormat: TImgFormat); +begin + Assert(AStream <> nil); + FImgFormat := AImgFormat; +end; + + +//============================================================================== +// TBasicMetaDataWriter +//============================================================================== + +procedure TBasicMetadataWriter.Error(const AMsg: String); +begin + raise EFpExifWriter.Create(AMsg); +end; + +procedure TBasicMetadataWriter.UpdateSegmentSize(AStream: TStream; + ASegmentStartPos: Int64); +var + startPos: Int64; + segmentSize: Word; + w: Word; +begin + // If the metadata structure is part of a jpeg file (e.g.) then the start + // position of the corresponding metadata segment has been stored in + // ASegmentStartPos. In other cases ASegmentStartPos is -1. + // This means: if ASegmentStartPos is > -1 then the segment size must be + // written to the segment start position. + if (ASegmentStartPos < 0) then + exit; + + // From the current stream position (at the end) and the position where + // the segment size must be written, we calculate the size of the segment + startPos := ASegmentStartPos + SizeOf(word); + segmentSize := AStream.Position - startPos; + + // Move the stream to where the segment size must be written... + AStream.Position := startPos; + + // ... and write the segment size. + w := BEToN(segmentSize); + AStream.WriteBuffer(w, SizeOf(w)); + + // Rewind stream to the end + AStream.Seek(0, soFromEnd); +end; + +procedure TBasicMetadataWriter.WriteToStream(AStream: TStream; + AImgFormat: TImgFormat); +begin + Assert(AStream <> nil); + FImgFormat := AImgFormat; +end; + + +//============================================================================== +// TImgInfo +//============================================================================== + +constructor TImgInfo.Create; +begin + FMetadataKinds := mdkAll; + FWarnings := TStringList.Create; +end; + +destructor TImgInfo.Destroy; +begin + FWarnings.Free; + FExifData.Free; + FIptcData.Free; + inherited; +end; + +function TImgInfo.CreateExifData(ABigEndian: Boolean = false): TExifData; +begin + FWarnings.Clear; + FExifData.Free; + FExifData := TExifData.Create(ABigEndian); + Result := FExifData; +end; + +function TImgInfo.CreateIptcData: TIptcData; +begin + FWarnings.Clear; + FIptcData.Free; + FIptcData := TIptcData.Create; + Result := FIptcData; +end; + +procedure TImgInfo.Error(const AMsg: String); +begin + raise EFpExif.Create(AMsg); +end; + +function TImgInfo.ExtractImgFormat(AStream: TStream): TImgFormat; +var + p: Int64; + hdr: array[0..SizeOf(TTiffHeader)-1] of byte; + tiffHdr: TTiffHeader absolute hdr; +begin + p := AStream.Position; + try + AStream.Read({%H-}hdr[0], SizeOf(hdr)); + // Test for jpeg signature + if (hdr[0] = $FF) and (hdr[1] = $D8) then begin + Result := ifJpeg; + exit; + end; + // Test for TIFF header + if (tiffHdr.BOM[0]='I') and (tiffHdr.BOM[1]='I') and (LEtoN(tiffHdr.Signature) = 42) + then begin + Result := ifTiff; + exit; + end; + if (tiffHdr.BOM[0]='M') and (tiffHdr.BOM[1]='M') and (BEtoN(tiffHdr.signature) = 42) + then begin + Result := ifTiff; + exit; + end; + Result := ifUnknown; + finally + AStream.Position := p; + end; +end; + +function TImgInfo.GetComment: String; +begin + Result := FComment; +end; + +function TImgInfo.GetWarnings: String; +begin + Result := FWarnings.Text; + FWarnings.Clear; +end; + +function TImgInfo.HasComment: Boolean; +begin + Result := FComment <> ''; +end; + +function TImgInfo.HasExif: Boolean; +begin + Result := (FExifData <> nil) and (FExifData.TagCount > 0); +end; + +function TImgInfo.HasIptc: Boolean; +begin + Result := (FIptcData <> nil) and (FIptcData.TagCount > 0); +end; + +function TImgInfo.HasThumbnail: boolean; +begin + Result := (FExifData <> nil) and FExifData.HasThumbnail; +end; + +function TImgInfo.HasWarnings: boolean; +begin + Result := FWarnings.Count > 0; +end; + +procedure TImgInfo.LoadFromFile(const AFileName: String); +var + stream: TStream; +begin + if not FileExists(AFileName) then + Error(Format(rsFileNotFoundError, [AFileName])); + + FWarnings.Clear; + StoreFileInfo(AFileName); + stream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone); + try + LoadFromStream(stream); + finally + stream.Free; + end; +end; + +procedure TImgInfo.LoadFromStream(AStream: TStream); +begin + FWarnings.Clear; + FImgFormat := ExtractImgFormat(AStream); + if FImgFormat = ifUnknown then + Error(rsUnknownImageFormat); + + case FImgFormat of + ifJpeg: + ReadJpeg(AStream); + ifTiff: + ReadTiff(AStream); + else + Error('TImgInfo.LoadFromStream: ' + rsImageFormatNotSupported); + end; +end; + +{ Reads the image data from AInputstream and replaces the meta data segments + by those of TImgInfo } +procedure TImgInfo.MergeToJpegStream(AInputStream, AOutputStream: TStream); +type + TSegmentHeader = packed record + Key: byte; + Marker: byte; + Size: Word; + end; +var + header: TSegmentHeader; + n, count: Int64; + savedPos: Int64; +begin + // Write the header segment and all metadata segments stored in TImgInfo + // to the beginning of the stream + AOutputStream.Position := 0; + WriteJpeg(AOutputStream); + + // Now write copy all other segments. + AInputStream.Position := 0; + while AInputStream.Position < AInputStream.Size do begin + savedPos := AInputStream.Position; // just for debugging + n := AInputStream.Read(header{%H-}, SizeOf(header)); + if n <> Sizeof(header) then + Error(rsIncompleteJpegSegmentHeader); + if header.Key <> $FF then + Error(rsJpegSegmentMarkerExpected); + header.Size := BEToN(header.Size); + + // Save stream position before segment size value. + savedPos := AInputStream.Position - 2; + case header.Marker of + M_SOI: + header.Size := 0; + M_JFIF, M_EXIF, M_IPTC, M_COM: // these segments were already written by WriteJpeg + ; + M_SOS: + begin + // this is the last segment before compressed data which don't have a marker + // --> just copy the rest of the file + count := AInputStream.Size - savedPos; + AInputStream.Position := savedPos; + AOutputStream.WriteBuffer(header, 2); + n := AOutputStream.CopyFrom(AInputStream, count); + if n <> count then + Error(rsJpegCompressedDataWriting); + break; + end; + else + AInputStream.Position := AInputStream.Position - 4; // go back to where the segment begins + n := AOutputStream.CopyFrom(AInputStream, Int64(header.Size) + 2); + if n <> Int64(header.Size) + 2 then + Error(rsJpegReadWriteErrorInSegment); + end; + AInputStream.Position := savedPos + header.Size; + end; +end; + +procedure TImgInfo.ReadJpeg(AStream: TStream); +var + marker: Byte; + size: Word; + streamsize: Int64; + p: Int64; + buf: TBytes; + reader: TBasicMetadataReader; + bigEndian: Boolean; +begin + p := AStream.Position; + streamsize := AStream.Size; + + if not ((ReadByte(AStream) = $FF) and (ReadByte(AStream) = M_SOI)) then + exit; + + while p < streamsize do begin + repeat + marker := ReadByte(AStream); + until marker <> $FF; + size := BEtoN(ReadWord(AStream)) - 2; + p := AStream.Position; + case marker of + M_EXIF: + if (mdkEXIF in FMetadataKinds) then begin + reader := TExifReader.Create(self); + try + if not TExifReader(reader).ReadExifHeader(AStream) then + exit; + if not TExifReader(reader).ReadTiffHeader(AStream, bigEndian) then + exit; + FExifData := CreateExifData(bigEndian); + try + reader.ReadFromStream(AStream, ifJpeg); + except + FreeAndNil(FExifData); + raise; + end; + finally + reader.Free; + end; + end; + M_IPTC: + if (mdkIPTC in FMetadataKinds) then begin + reader := TIptcReader.Create(self); + try + FIptcData := CreateIptcData; + try + reader.ReadFromStream(AStream, ifJpeg); + except + FreeAndNil(FIptcData); + raise; + end; + except + reader.Free; + end; + end; + M_COM: + if (mdkComment in FMetadataKinds) and (size > 0) then + begin + // JFIF comment is encoded as UTF8 according to + // http://mail.kde.org/pipermail/digikam-devel/2006-May/005000.html + {$IFDEF FPC} + SetLength(FComment, size); + AStream.Read(FComment[1], size); + {$ELSE} + SetLength(sa, size); + AStream.Read(sa[1], size); + {$IFDEF UNITCODE} + FComment := UTF8Decode(sa); + {$ELSE} + FComment := Utf8ToAnsi(sa); + {$ENDIF} + {$ENDIF} + end; + M_JFIF: + begin + SetLength(FHeaderSegment, size); + AStream.Read(FHeaderSegment[0], size); + with PJpegJFIFSegment(@FHeaderSegment[0])^ do begin + if not ( + (Identifier[0]='J') and (Identifier[1]='F') and + (Identifier[2]='I') and (Identifier[3]='F') and + (Identifier[4]=#0) ) + then + exit; + if (JFIFVersion[0] <> 1) then + exit; + end; + end; + M_SOF0: + begin + SetLength(buf, size); + AStream.Read(buf[0], size); + with PJpegSOF0Segment(@buf[0])^ do begin + FImgHeight := BEtoN(ImageHeight); + FImgWidth := BEtoN(ImageWidth); + end; + SetLength(buf, 0); + end; + M_EOI, M_SOS: + break; + end; + AStream.Position := p + size; + end; +end; + +procedure TImgInfo.ReadTiff(AStream: TStream); +var + reader: TExifReader; + bigEndian: Boolean; +begin + reader := TExifReader.Create(self); + try + if not TExifReader(reader).ReadTiffHeader(AStream, bigEndian) then + exit; + FExifData := CreateExifData(bigEndian); + try + reader.ReadFromStream(AStream, ifTiff); + except + FreeAndNil(FExifData); + raise; + end; + finally + reader.Free; + end; +end; + +procedure TImgInfo.SaveToFile(const AFileName: String; AImgFile: String = ''); +var + ms: TMemoryStream; + srcStream: TFileStream; +begin + if (AImgFile = '') then + AImgFile := FFileName; + + if AImgFile = '' then + Error(rsImageDataFileNotSpecified); + + if not FileExists(AImgFile) then + Error(Format(rsImageDataFileNotExisting, [AImgFile])); + + FWarnings.Clear; + ms := TMemoryStream.Create; + try + srcstream := TFileStream.Create(AImgFile, fmOpenRead + fmShareDenyNone); + try + if FImgFormat = ifUnknown then begin + FimgFormat := ExtractImgFormat(srcstream); + if FImgFormat = ifUnknown then + Error(rsCannotSaveToUnknownFileFormat); + end; + case FImgFormat of + ifJpeg: MergeToJpegStream(srcstream, ms); + ifTiff: Error(Format(rsWritingNotImplemented, ['TIFF'])); + else Error(rsImageFormatNotSupported); + end; + finally + // Destroy the srcStream before saving the memorystream to file to prevent + // an error if AImgFile = AFileName + srcStream.Free; + end; + ms.SaveToFile(AFileName) + finally + ms.Free; + end; +end; + +procedure TImgInfo.SetComment(const AValue: String); +begin + FComment := AValue; +end; + +procedure TImgInfo.StoreFileInfo(const AFileName: String); +var + rec: TSearchRec; + res: word; +begin + res := FindFirst(AFilename, faAnyFile, rec); + if res = 0 then + begin + FFilename := AFilename; + FFileDate := FileDateToDateTime(rec.Time); + FFileSize := rec.Size; + end; + FindClose(rec); +end; + +{ Writes all metadata-related segments to a stream. Note image data must be + written separately. } +procedure TImgInfo.WriteJpeg(AStream: TStream); +const + SOI_MARKER: array[0..1] of byte = ($FF, $D8); + COM_MARKER: array[0..1] of byte = ($FF, $FE); + JFIF_MARKER: array[0..1] of byte = ($FF, $E0); + JFIF: ansistring = 'JFIF'#0; +var + jfifSegment: TJpegJFIFSegment; + writer: TBasicMetadataWriter; +begin + // Write Start-of-image segment (SOI) + AStream.WriteBuffer(SOI_MARKER, SizeOf(SOI_MARKER)); + + // No Exif --> write an APP0 segment + if not HasExif or not (mdkExif in FMetadataKinds) then begin + if Length(FHeaderSegment) = 0 then begin + Move(JFIF[1], {%H-}JFIFSegment.Identifier[0], Length(JFIF)); + JFIFSegment.JFIFVersion[0] := 1; + JFIFSegment.JFIFVersion[1] := 2; + JFIFSegment.DensityUnit := 1; // inch + JFIFSegment.XDensity := NtoBE(72); // 72 ppi + JFIFSegment.YDensity := NtoBE(72); + JFIFSegment.ThumbnailWidth := 0; // no thumbnail in APP0 segment + JFIFSegment.ThumbnailHeight := 0; + AStream.WriteBuffer(JFIF_MARKER, SizeOf(JFIF_MARKER)); + WriteWord(AStream, NtoBE(Word(SizeOf(JFIFSegment) + 2))); + AStream.WriteBuffer(JFIFSegment, SizeOf(JFIFSegment)); + end else + begin + AStream.WriteBuffer(JFIF_MARKER, SizeOf(JFIF_MARKER)); + WriteWord(AStream, NtoBE(Word(Length(FHeaderSegment) + 2))); + AStream.WriteBuffer(FHeaderSegment[0], Length(FHeaderSegment)); + end; + end else + begin + // Exif --> Write APP1 segment + writer := TExifWriter.Create(Self); + try + TExifWriter(writer).BigEndian:= FExifData.BigEndian; + writer.WriteToStream(AStream, ifJpeg); + finally + writer.Free; + end; + end; + + // Write IPTCSegment (APP13) + if (mdkIPTC in FMetadataKinds) and HasIPTC then begin + writer := TIptcWriter.Create(Self); + try + TIptcWriter(writer).WriteToStream(AStream, ifJpeg); + finally + writer.Free; + end; + end; + + // Write comment segment + if (mdkComment in FMetadataKinds) and HasComment then begin + // JFIF Comment is encoded as utf8 + // according to http://mail.kde.org/pipermail/digikam-devel/2006-May/005000.html + AStream.WriteBuffer(COM_MARKER, SizeOf(COM_MARKER)); + {$IFDEF FPC} + WriteWord(AStream, NtoBE(Word(Length(FComment) + 2))); + AStream.WriteBuffer(FComment[1], Length(FComment)); + {$ELSE} + {$IFDEF UNICODE} + sa := UTF8Encode(FComment); + {$ELSE} + sa := AnsiToUTF8(FComment); + {$ENDIF} + WriteWord(AStream, NtoBE(Word(Length(sa) + 2))); + AStream.WriteBuffer(sa[1], Length(sa)); + {$ENDIF} + end; +end; + + +end. + diff --git a/components/fpexif/fpestrconsts.pas b/components/fpexif/fpestrconsts.pas new file mode 100644 index 000000000..fe302e147 --- /dev/null +++ b/components/fpexif/fpestrconsts.pas @@ -0,0 +1,660 @@ +unit fpeStrConsts; + +{$IFDEF FPC} + {$MODE DELPHI} +{$ENDIF} + +interface + +uses + Classes, SysUtils; + +resourcestring + + // *** Error messages *** + + rsCannotSaveToUnknownFileFormat = 'The metadata structure cannot be saved because '+ + 'the file format of the receiving file is not known or not supported.'; + rsFileNotFoundError = 'File "%s" not found.'; + rsImageDataFileNotExisting = 'File "%s" providing the image data does not exist.'; + rsImageDataFileNotSpecified = 'The metadata structure is not linked to an image. '+ + 'Specify the name of the file providing the image data.'; + rsImageFormatNotSupported = 'Image format not supported.'; + rsImageResourceNameTooLong = 'Image resource name "%s" too long.'; + rsIncompleteJpegSegmentHeader = 'Defective JPEG structure: Incomplete segment header'; + rsIncorrectFileStructure = 'Incorrect file structure'; + rsIncorrectTagType = 'Incorrect tag type %d: Index=%d, TagID=$%.04x, File:"%s"'; + rsIptcDataExpected = 'IPTC data expected, but not found.'; + rsIptcExtendedDataSizeNotSupported = 'Data size %d not supported for an IPTC extended dataset.'; + rsJpegCompressedDataWriting = 'Writing error of compressed data.'; + rsJpegSegmentMarkerExpected = 'Defective JPEG structure: Segment marker ($FF) expected.'; + rsJpegReadWriteErrorInSegment = 'Read/write error in segment $FF%.2x'; + rsMoreThumbnailTagsThanExpected = 'More thumbnail tags than expected.'; + rsNoValidIptcFile = 'No valid IPTC file'; + rsNoValidIptcSignature = 'No valid IPTC signature'; + rsRangeCheckError = 'Range check error.'; + rsReadIncompleteIFDRecord = 'Read incomplete IFD record at stream position %d.'; + rsTagTypeNotSupported = 'Tag "%s" has an unsupported type.'; + rsUnknownImageFormat = 'Unknown image format.'; + rsWritingNotImplemented = 'Writing of %s files not yet implemented.'; + + // general lookup values + rsAutoManual = '0:Auto,1:Manual'; + rsEconomyNormalFine = '0:Economy,1:Normal,2:Fine'; + rsEconomyNormalFine1 = '1:Economy,2:Normal,3:Fine'; + rsLowNormalHigh = '0:Low,1:Normal,2:High'; + rsNormalLowHigh = '0:Normal,1:Low,2:High'; + rsNormalSoftHard = '0:Normal,1:Soft,2:Hard'; + rsNoYes = '0:No,1:Yes'; + rsOffOn = '0:Off,1:On'; + rsSingleContinuous = '0:Single,1:Continuous'; + + // *** EXIF tags *** + + rsAcceleration = 'Acceleration'; +// rsActionAdvised = 'Action advised'; + rsAperturevalue = 'Aperture value'; + rsArtist = 'Artist'; + rsBitsPerSample = 'Bits per sample'; + rsBrightnessValue = 'Brightness value'; +// rsByLine = 'By-line'; +// rsByLineTitle = 'By-line title'; + rsCameraElevationAngle = 'Camera elevation angle'; +// rsCategory = 'Category'; + rsCellHeight = 'Cell height'; + rsCellWidth = 'Cell width'; + rsCFAPattern = 'CFA pattern'; +// rsCity = 'City'; +// rsCodedCharacterSet = 'Coded character set'; + rsColorSpace = 'Color space'; + rsColorSpaceLkup = '0:sBW,1:sRGB,2:Adobe RGB,65533:Wide Gamut RGB,65534:ICC Profile,65535:Uncalibrated'; + rsComponentsConfig = 'Components configuration'; + rsCompressedBitsPerPixel = 'Compressed bits per pixel'; + rsCompression = 'Compression'; + rsCompressionLkup = '1:Uncompressed,2:CCITT 1D,3:T4/Group 3 Fax,'+ + '4:T6/Group 4 Fax,5:LZW,6:JPEG (old-style),7:JPEG,8:Adobe Deflate,'+ + '9:JBIG B&W,10:JBIG Color,99:JPEG,262:Kodak 262,32766:Next,'+ + '32767:Sony ARW Compressed,32769:Packed RAW,32770:Samsung SRW Compressed,'+ + '32771:CCIRLEW,32772:Samsung SRW Compressed 2,32773:PackBits,'+ + '32809:Thunderscan,32867:Kodak KDC Compressed,32895:IT8CTPAD,'+ + '32896:IT8LW,32897:IT8MP,32898:IT8BL,32908:PixarFilm,32909:PixarLog,'+ + '32946:Deflate,32947:DCS,34661:JBIG,34676:SGILog,34677:SGILog24,'+ + '34712:JPEG 2000,34713:Nikon NEF Compressed,34715:JBIG2 TIFF FX,'+ + '34718:Microsoft Document Imaging (MDI) Binary Level Codec,'+ + '34719:Microsoft Document Imaging (MDI) Progressive Transform Codec,'+ + '34720:Microsoft Document Imaging (MDI) Vector,34892:Lossy JPEG,'+ + '65000:Kodak DCR Compressed,65535:Pentax PEF Compressed'; +// rsContact = 'Contact'; +// rsContentLocCode = 'Content location code'; +// rsContentLocName = 'Content location name'; + rsContrast = 'Contrast'; + rsCopyright = 'Copyright'; + rsCustomRendered = 'Custom rendered'; + rsCustomRenderedLkup = '0:Normal,1:Custom'; +// rsDateCreated = 'Date created'; + rsDateTime = 'Date/time'; + rsDateTimeOriginal = 'Date/time original'; + rsDateTimeDigitized = 'Date/time digitized'; + rsDeviceSettingDescription = 'Device setting description'; + rsDigitalZoom = 'Digital zoom'; + rsDigitalZoomRatio = 'Digital zoom ratio'; + rsDigitizeDate = 'Digital creation date'; + rsDigitizeTime = 'Digital creation time'; + rsDocumentName = 'Document name'; +// rsEditorialUpdate = 'Editorial update'; +// rsEditStatus = 'Edit status'; + rsExifImageHeight = 'EXIF image height'; + rsExifImageWidth = 'EXIF image width'; + rsExifOffset = 'EXIF offset'; + rsExifVersion = 'EXIF version'; +// rsExpireDate = 'Expiration date'; +// rsExpireTime = 'Expiration time'; + rsExposureBiasValue = 'Exposure bias value'; + rsExposureIndex = 'Exposure index'; + rsExposureMode = 'Exposure mode'; + rsExposureModeLkup = '0:Auto,1:Manual,2:Auto bracket'; + rsExposureProgram = 'Exposure program'; + rsExposureProgramLkup = '0:Not defined,1:Manual,2:Program AE,3:Aperture-priority AE,'+ + '4:Shutter speed priority AE,5:Creative (slow speed),6:Action (high speed),'+ + '7:Portrait,8:Landscape;9:Bulb'; + rsExposureTime = 'Exposure time'; + rsExtensibleMetadataPlatform = 'Extensible metadata platform'; + rsFileSource = 'File source'; + rsFileSourceLkup = '0:Unknown,1:Film scanner,2:Reflection print scanner,3:Digital camera'; + rsFillOrder = 'Fill order'; + rsFillOrderLkup = '1:Normal,2:Reversed'; +// rsFixtureID = 'Fixture ID'; + rsFlash = 'Flash'; + rsFlashEnergy = 'Flash energy'; + rsFlashLkup = '0:No flash,1:Fired,5:Fired; return not detected,'+ + '7:Fired; return detected,8:On; did not fire,9:On; fired,'+ + '13:On; return not detected,15:On; return detected,16:Off; did not fire,'+ + '20:Off; did not fire, return not detected,24:Auto; did not fire,'+ + '25:Auto; fired;29:Auto; fired; return not detected,31:Auto; fired; return detected,'+ + '32:No flash function,48:Off, no flash function,65:Fired; red-eye reduction,'+ + '69:Fired; red-eye reduction; return not detected,'+ + '71:Fired; red-eye reduction; return detected,73:On; red-eye reduction,'+ + '77:On; red-eye reduction, return not detected,'+ + '79:On, red-eye reduction, return detected,80:Off; red-eye reduction,'+ + '88:Auto; did not fire; red-eye reduction,89:Auto; fired; red-eye reduction,'+ + '93:Auto; fired; red-eye reduction; return not detected,'+ + '95:Auto; fired; red-eye reduction, return detected'; + rsFlashPixVersion = 'FlashPix version'; + rsFNumber = 'F number'; + rsFocalLength = 'Focal length'; + rsFocalLengthIn35mm = 'Focal length in 35 mm film'; + rsFocalPlaneResUnit = 'Focal plane resolution unit'; + rsFocalPlaneResUnitLkup = '1:None,2:inches,3:cm,4:mm,5:um'; + rsFocalPlaneXRes = 'Focal plane x resolution'; + rsFocalPlaneYRes = 'Focal plane y resolution'; + rsGainControl = 'Gain control'; + rsGainControlLkup = '0:None,1:Low gain up,2:High gain up,3:Low gain down,4:High gain down'; + rsGamma = 'Gamma'; + rsGPSAltitude = 'GPS altitude'; + rsGPSAltitudeRef = 'GPS altitude reference'; + rsGPSAltitudeRefLkup = '0: Above sea level,1:Below sea level'; + rsGPSAreaInformation = 'Area information'; + rsGPSDateDifferential = 'GPS date differential'; + rsGPSDateDifferentialLkup = '0:No correction,1:Differential corrected'; + rsGPSDateStamp = 'GPS date stamp'; + rsGPSDestBearing = 'GPS destination bearing'; + rsGPSDestBearingRef = 'GPS destination bearing reference'; + rsGPSDestDistance = 'GPS destination distance'; + rsGPSDestDistanceRef = 'GPS destination distance reference'; + rsGPSDestLatitude = 'GPS destination latitude'; + rsGPSDestLatitudeRef = 'GPS destination latitude reference'; + rsGPSDestLongitude = 'GPS destination longitude'; + rsGPSDestLongitudeRef = 'GPS destination longitude reference'; + rsGPSDistanceRefLkup = 'K:Kilometers,M:Miles,N:Nautical miles'; + rsGPSDOP = 'GPS DOP'; + rsGPSHPositioningError = 'GPS H positioning error'; + rsGPSImageDirection = 'GPS image direction'; + rsGPSImageDirectionRef = 'GPS image direction reference'; + rsGPSInfo = 'GPS info'; + rsGPSLatitude = 'GPS latitude'; + rsGPSLatitudeRef = 'GPS latitude reference'; + rsGPSLatitudeRefLkup = 'N:North,S:South'; + rsGPSLongitude = 'GPS longitude'; + rsGPSLongitudeRef = 'GPS longitude reference'; + rsGPSLongitudeRefLkup = 'E:East,W:West'; + rsGPSMapDatum = 'GPS map datum'; + rsGPSMeasureMode = 'GPS measurement mode'; + rsGPSMeasureModeLkup = '2:2-Dimensional Measurement,3:3-Dimensional Measurement'; + rsGPSProcessingMode = 'GPS processing mode'; + rsGPSSatellites = 'GPS satellites'; + rsGPSSpeed = 'GPS speed'; + rsGPSSpeedRef = 'GPS speed reference'; + rsGPSSpeedRefLkup = 'K:km/h,M:mph,N:knots'; + rsGPSStatus = 'GPS status'; + rsGPSTimeStamp = 'GPS time stamp'; + rsGPSTrack = 'GPS track'; + rsGPSTrackRef = 'GPS track reference'; + rsGPSTrackRefLkup = 'M:Magnetic north,T:True north'; + rsGPSVersionID = 'GPS version ID'; + rsHalftoneHints = 'Half-tone hints'; + rsHostComputer = 'Host computer'; + rsHumidity = 'Humidity'; +// rsImageCaption = 'Image caption'; +// rsImageCaptionWriter = 'Image caption writer'; +// rsImageCredit = 'Image credit'; + rsImageDescr = 'Image description'; +// rsImageHeadline = 'Image headline'; + rsImageHeight = 'Image height'; + rsImageHistory = 'Image history'; + rsImageNumber = 'Image number'; +// rsImageType = 'Image type'; + rsImageUniqueID = 'Unique image ID'; + rsImageWidth = 'Image width'; + rsInkSet = 'Ink set'; + rsInkSetLkup = '1:CMYK,2:Not CMYK'; + rsInteropIndex = 'Interoperabiliy index'; + rsInteropOffset = 'Interoperability offset'; + rsInteropVersion = 'Interoperability version'; + rsIPTCNAA = 'IPTC/NAA'; + rsISOSpeed = 'ISO speed'; + rsISOSpeedLatitudeYYY = 'ISO latitude yyy'; + rsISOSpeedLatitudeZZZ = 'ISO speed latitude zzz'; + rsISO = 'ISO'; + rsLensInfo = 'Lens info'; + rsLensMake = 'Lens make'; + rsLensModel = 'Lens model'; + rsLensSerialNumber = 'Lens serial number'; + rsLightSource = 'Light source'; + rsLightSourceLkup = '0:Unknown,1:Daylight,2:Fluorescent,3:Tungsten (incandescent),'+ + '4:Flash,9:Fine weather,10:Cloudy,11:Shade,12:Daylight fluorescent,'+ + '13:Day white fluorescent,14:Cool white fluorescent,15:White fluorescent,'+ + '16:Warm white fluorescent,17:Standard light A, 18:Standard light B,'+ + '19:Standard light C,20:D55,21:D65,22:D74,23:D50,24:ISO Studio tungsten,'+ + '255:Other'; + rsMacro = 'Macro'; + rsMake = 'Make'; + rsMakerNote = 'Maker note'; + rsMaxApertureValue = 'Max aperture value'; + rsMaxSampleValue = 'Max sample value'; + rsMeteringMode = 'Metering mode'; + rsMeteringModeLkup = '0:Unknown,1:Average,2:Center-weighted average,'+ + '3:Spot,4:Multi-spot,5:Multi-segment,6:Partial,255:Other'; + rsMinSampleValue = 'Min sample value'; + rsModel = 'Model'; + rsOffsetTime = 'Time zone for date/time'; + rsOffsetTimeOriginal = 'Time zone for date/time original'; + rsOffsetTimeDigitized = 'Time zone for date/time digitized'; + rsOrientation = 'Orientation'; + rsOrientationLkup = '1:Horizontal (normal),2:Mirror horizontal,3:Rotate 180,'+ + '4:Mirror vertical,5:Mirror horizontal and rotate 270 CW,6:Rotate 90 CW,'+ + '7:Mirror horizontal and rotate 90 CW,8:Rotate 270 CW'; + rsOwnerName = 'Owner name'; + rsPageName = 'Page name'; + rsPageNumber = 'Page number'; + rsPhotometricInt = 'Photometric interpretation'; + rsPhotometricIntLkup = '0:White is zero,1:Black is zero,2:RGB,3:RGB palette,'+ + '4:Transparency mask,5:CMYK,6:YCbCr,8:CIELab,9:ICCLab,10:ITULab,'+ + '32803:Color filter array,32844:Pixar LogL,32845:Pixar LogLuv,34892:Linear Raw'; + rsPlanarConfiguration = 'Planar configuration'; + rsPlanarConfigurationLkup = '1:Chunky,2:Planar'; + rsPredictor = 'Predictor'; + rsPredictorLkup = '1:None,2:Horizontal differencing'; + rsPressure = 'Pressure'; + rsPrimaryChromaticities = 'Primary chromaticities'; + rsQuality = 'Quality'; + rsRecExpIndex = 'Recommended exposure index'; + rsRefBlackWhite = 'Reference black & white'; + rsRelatedImageFileFormat = 'Related image file format'; + rsRelatedImageHeight = 'Related image height'; + rsRelatedImageWidth = 'Related image width'; + rsRelatedSoundFile = 'Related sound file'; + rsResolutionUnit = 'Resolution unit'; + rsResolutionUnitLkup = '1:None,2:inches,3:cm'; + rsRowsPerStrip = 'Rows per strip'; + rsSamplesPerPixel = 'Samples per pixel'; + rsSaturation = 'Saturation'; + rsSceneCaptureType = 'Scene capture type'; + rsSceneCaptureTypeLkup = '0:Standard,1:Landscape,2:Portrait,3:Night'; + rsSceneType = 'Scene type'; + rsSceneTypeLkup = '0:Unknown,1:Directly photographed'; + rsSecurityClassification = 'Security classification'; + rsSelfTimerMode = 'Self-timer mode'; + rsSEMInfo = 'SEM info'; + rsSensingMethod = 'Sensing method'; + rsSensingMethodLkup = '1:Not defined,2:One-chip color area,3:Two-chip color area,'+ + '4:Three-chip color area,5:Color sequential area,7:Trilinear,8:Color sequential linear'; + rsSensitivityType = 'Sensitivity type'; + rsSensitivityTypeLkup = '0:Unknown,1:Standard Output Sensitivity'+ + '2:Recommended exposure index,3:ISO speed,'+ + '4:Standard output sensitivity and recommended exposure index,'+ + '5:Standard output sensitivity and ISO Speed,6:Recommended exposure index and ISO speed,'+ + '7:Standard output sensitivity, recommended exposure index and ISO speed'; + rsSerialNumber = 'Serial number'; + rsSharpness = 'Sharpness'; + rsShutterSpeedValue = 'Shutter speed value'; + rsSoftware = 'Software'; + rsSpatialFrequResponse = 'Spatial frequency response'; + rsSpectralSensitivity = 'Spectral sensitivity'; + rsStdOutputSens = 'Standard output sensitivity'; + rsStripByteCounts = 'Strip byte counts'; + rsStripOffsets = 'Strip offsets'; + rsSubfileTypeLkup = + '0:Full-resolution image,'+ + '1:Reduced-resolution image,'+ + '2:Single page of multi-page image,'+ + '3:Single page of multi-page reduced-resolution image,'+ + '4:Transparency mask,'+ + '5:Transparency mask of reduced-resolution image,'+ + '6:Transparency mask of multi-page image,'+ + '7:Transparency mask of reduced-resolution multi-page image'; + rsSubjectArea = 'Subject area'; + rsSubjectDistance = 'Subject distance'; + rsSubjectDistanceRange = 'Subject distance range'; + rsSubjectDistanceRangeLkup = '0:Unknown,1:Macro,2:Close,3:Distant'; + rsSubjectLocation = 'Subject location'; + rsSubSecTime = 'Fractional seconds of date/time'; + rsSubSecTimeOriginal = 'Fractional seconds of date/time original'; + rsSubSecTimeDigitized = 'Fractional seconds of date/time digitized'; + rsTargetPrinter = 'Target printer'; + rsTemperature = 'Temperature'; + rsThresholding = 'Thresholding'; + rsThresholdingLkup = '1:No dithering or halftoning,2:Ordered dither or halftone,'+ + '3:Randomized dither'; + rsThumbnailHeight = 'Thumbnail height'; + rsThumbnailOffset = 'Thumbnail offset'; + rsThumbnailSize = 'Thumbnail size'; + rsThumbnailWidth = 'Thumbnail width'; + rsTileLength = 'Tile length'; + rsTileWidth = 'Tile width'; + rsTimeZoneOffset = 'Time zone offset'; + rsTransferFunction = 'Transfer function'; + rsTransmissionRef = 'Original transmission reference'; + rsUserComment = 'User comment'; + rsWhiteBalance = 'White balance'; + rsWaterDepth = 'Water depth'; + rsWhitePoint = 'White point'; + rsXPosition = 'X position'; + rsXResolution = 'X resolution'; + rsYCbCrCoefficients = 'YCbCr coefficients'; + rsYCbCrPositioning = 'YCbCr positioning'; + rsYCbCrPosLkup = '1:Centered,2:Co-sited'; + rsYCbCrSubsampling = 'YCbCr subsampling'; + rsYPosition = 'Y position'; + rsYResolution = 'Y resolution'; + + // *** MakerNote *** + + // Canon + rsCanonAELkup = '0:Normal AE,1:Exposure compensation,2:AE lock,'+ + '3:AE lock + Exposure compensation,4:No AE'; + { + rsCanonAFLkup = '12288:None (MF),12289:Auto-selected,12290:Right,12291:Center,'+ + '12292:Left'; + } + rsCanonAFLkup = '$2005:Manual AF point selection,$3000:None (MF),' + + '$3001:Auto AF point selection,$3002:Right,$3003:Center,$3004:Left,' + + '$4001:Auto AF point selection,$4006:Face Detect'; + rsCanonAutoRotLkup = '0:None,1:Rotate 90 CW,2:Rotate 180,3:Rotate 270 CW'; + rsCanonBiasLkup = '65472:-2 EV,65484:-1.67 EV,65488:-1.50 EV,65492:-1.33 EV,'+ + '65504:-1 EV,65516:-0.67 EV,65520:-0.50 EV,65524:-0.33 EV,0:0 EV,'+ + '12:0.33 EV,16:0.50 EV,20:0.67 EV,32:1 EV,44:1.33 EV,48:1.50 EV,'+ + '52:1.67 EV,64:2 EV'; + rsCanonCamTypeLkup = '248:EOS High-end,250:Compact,252:EOS Mid-range,255:DV Camera'; + rsCanonEasyLkup = '0:Full Auto,1:Manual,2:Landscape,3:Fast Shutter,4:Slow Shutter,'+ + '5:Night,6:Gray scale,7:Sepia,8:Portrait,9:Sports,10:Macro,11:Black & White,'+ + '12:Pan Focus,13:Vivid,14:Neutral,15:Flash off,16:Long shutter,'+ + '17:Super macro,18:Foliage,19:Indoor,20:Fireworks,21:Beach,22:Underwater,'+ + '23:Snow,24:Kids & Pets,25:Night snapshot,26:Digital macro,27:My colors,'+ + '28:Movie snap,29:Super macro 2,30:Color accent,31:Color swap,32:Aquarium,'+ + '33:ISO3200,34:ISO6400,35:Creative light effect,36:Easy,37:Quick shot,'+ + '38:Creative auto,39:Zoom blur,40:Low light,41:Nostalgic,42:Super vivid,'+ + '43:Poster effect,44:Face self-timer,45:Smile,46:Wink self-timer,'+ + '47:Fisheye effect,48:Miniature effect,49:High-speed burst,'+ + '50:Best image selection,51:High dynamic range,52:Handheld night scene,'+ + '53:Movie digest,54:Live view control,55:Discreet,56:Blur reduction,'+ + '57:Monochrome,58:Toy camera effect,59:Scene intelligent auto,'+ + '60:High-speed burst HQ,61:Smooth skin,62:Soft focus,257:Spotlight,'+ + '258:Night 2,259:Night+,260:Super night,261:Sunset,263:Night scene,'+ + '264:Surface,265:Low light 2'; + rsCanonExposeLkup = '0:Easy shooting,1:Program AE,2:Shutter speed priority AE,'+ + '3:Aperture priority AE,4:Manual,5:Depth-of-field AE,6:M-Dep,7:Bulb'; + rsCanonFlashActLkup = '0:Did not fire,1:Fired'; + rsCanonFlashLkup = '0:Not fired,1:Auto,2:On,3:Red-eye,4:Slow sync,'+ + '5:Auto+red-eye,6:On+red eye,16:External flash'; + rsCanonFocalTypeLkup = '1:Fixed,2:Zoom'; + rsCanonFocTypeLkup = '0:Manual,1:Auto,3:Close-up (macro),8:Locked (pan mode)'; + rsCanonFocusLkup = '0:One-Shot AF,1:AI Servo AF,2:AI Focus AF,3:Manual focus,'+ + '4:Single,5:Continuous,6:Manual focus,16:Pan focus,256:AF+MF,'+ + '512:Movie snap focus,519:Movie servo AF'; + rsCanonGenLkup = '65535:Low,0:Normal,1:High'; + rsCanonImgStabLkup = '0:Off,1:On,2:Shoot only,3:Panning,4:Dynamic,256:Off,'+ + '257:On,258:Shoot only,259:Panning,260:Dynamic'; + rsCanonISOLkup = '0:Not used,15:auto,16:50,17:100,18:200,19:400'; + rsCanonMacroLkup = '1:Macro,2:Normal'; + rsCanonMeterLkup = '0:Default,1:Spot,2:Average,3:Evaluative,4:Partial,'+ + '5:Center-weighted average'; + rsCanonPanDirLkup = '0:Left to right,1:Right to left,2:Bottom to top,'+ + '3:Top to bottom,4:2x2 Matrix (clockwise)'; + rsCanonQualityLkup = '65535:n/a,1:Economy,2:Normal,3:Fine,4:RAW,5:Superfine,'+ + '130:Normal Movie,131:Movie (2)'; + rsCanonRecLkup = '1:JPEG,2:CRW+THM,3:AVI+THM,4:TIF,5:TIF+JPEG,6:CR2,'+ + '7:CR2+JPEG,9:MOV,10:MP4'; + rsCanonSizeLkup = '65535:n/a,0:Large,1:Medium,2:Small,4:5 MPixel,5:2 MPixel,'+ + '6:1.5 MPixel,8:Postcard,9:Widescreen,10:Medium widescreen,14:Small 1,'+ + '15:Small 2,16:Small 3,128:640x480 movie,129:Medium movie,130:Small movie,'+ + '137:128x720 movie,142:1920x1080 movie'; + rsCanonSloShuttLkup = '65535:n/a,0:Off,1:Night scene,2:On,3:None'; + rsCanonWhiteBalLkup = '0:Auto,1:Daylight,2:Cloudy,3:Tungsten,4:Flourescent,'+ + '5:Flash,6:Custom,7:Black & white,8:Shade,9:Manual temperature (Kelvin),'+ + '14:Daylight fluorescent,17:Under water'; + rsCanonZoomLkup = '0:None,1:2x,2:4x,3:Other'; + + // Casio + rsCasioAFMode2Lkup = '0:Off,1:Spot,2:Multi,3:Face detection,4:Tracking,5:Intelligent'; + rsCasioArtMode2Lkup = '0:Normal,8:Silent movie,39:HDR,45:Premium auto,' + + '47:Painting,49:Crayon drawing,51:Panorama,52:Art HDR,62:High Speed night shot,'+ + '64:Monochrome,67:Toy camera,68:Pop art,69:Light tone'; + rsCasioAutoIso2Lkup = '1:On,2:Off,7:On (high sensitivity),8:On (anti-shake),'+ + '10:High Speed'; + rsCasioCCDSensitivityLkup = '64:Normal,125:+1.0,250:+2.0,244:+3.0,80:Normal,'+ + '100:High'; + rsCasioColorFilter2Lkup = '0:Off,1:Blue,3:Green,4:Yellow,5:Red,6:Purple,7:Pink'; + rsCasioColorMode2Lkup = '0:Off,2:Black & White,3:Sepia'; + rsCasioDigitalZoomLkup = '$10000:Off,$10001:2x Digital zoom,'+ + '$20000:2x digital zoom,$40000:4x digital zoom'; + rsCasioDriveMode2Lkup = '0:Single shot,1:Continuous shooting,'+ + '2:Continuous (2 fps),3:Continuous (3 fps),4:Continuous (4 fps),'+ + '5:Continuous (5 fps),6:Continuous (6 fps),7:Continuous (7 fps),'+ + '10:Continuous (10 fps),12:Continuous (12 fps),15:Continuous (15 fps),'+ + '20:Continuous (20 fps),30:Continuous (30 fps),40:Continuous (40 fps),'+ + '60:Continuous (60 fps),240:Auto-N'; + rsCasioEnhancement2Lkup = '0:Off,1:Scenery,3:Green,5:Underwater,9:Flesh tones'; + rsCasioFlashIntensityLkup = '11:Weak,13:Normal,15:Strong'; + rsCasioFlashModeLkup = '1:Auto,2:On,3:Off,4:Red-eye reduction'; + rsCasioFocusingModeLkup = '2:Macro,3:Auto focus,4:Manual focus,5:Infinity'; + rsCasioFocusMode2Lkup = '0:Normal,1:Macro'; + rsCasioFocusMode22Lkup = '0:Manual,1:Focus lock,2:Macro,3:Single-area auto focus,'+ + '5:Infinity,6:Multi-area auto focus,8:Super macro'; + rsCasioImageSize2Lkup = '0:640 x 480,4:1600 x 1200,5:2048 x 1536,'+ + '20:2288 x 1712,21:2592 x 1944,22:2304 x 1728,36:3008 x 2008'; + rsCasioImageStabilization2Lkup = '0:Off,1:On,2:Best shot,3:Movie anti-shake'; + rsCasioISOSpeed2Lkup = '3 = 50,4:64,6:100,9:200'; + rsCasioLightingMode2Lkup = '0:Off,1:High dynamic range,5:Shadow enhance low,'+ + '6:Shadow enhance high'; + rsCasioPortraitRefiner2Lkup = '0:Off,1:+1,2:+2'; + rsCasioRecordingModeLkup = '1:Single shutter,2:Panorama,3:Night scene,'+ + '4:Portrait,5:Landscape'; + rsCasioRecordMode2Lkup = '2:Program AE,3:Shutter priority,4:Aperture priority,'+ + '5:Manual,6:Best shot,17:Movie,19:Movie (19),20:YouTube Movie'; + rsCasioReleaseMode2Lkup = '1:Normal,3:AE Bracketing,11:WB Bracketing,'+ + '13 = Contrast Bracketing,19:High Speed Burst'; + rsCasioSharpness2Lkup = '0:Soft,1:Normal,2:Hard'; + rsCasioSpecialEffectSetting2Lkup = '0:Off,1:Makeup,2:Mist removal,'+ + '3:Vivid landscape,16:Art shot'; + rsCasioVideoQuality2Lkup = '1:Standard,3:HD (720p),4:Full HD (1080p),5:Low'; + rsCasioWhiteBalanceLkup = '1:Auto,2:Tungsten,3:Daylight,4:Fluorescent,'+ + '5:Shade,129:Manual'; + rsCasioWhiteBalance2Lkup = '0:Auto,1:Daylight,2:Shade,3:Tungsten,4:Fluorescent,5:Manual'; + rsCasioWhiteBalance22Lkup = '0:Manual,1:Daylight,2:Cloudy,3:Shade,4:Flash?,'+ + '6:Fluorescent,9:Tungsten?,10:Tungsten,12:Flash'; + + // Fuji + rsFujiSharpnessLkup = '0:-4 (softest),1:-3 (very soft),2:-2 (soft),3:0 (normal),' + + '4:+2 (hard),5:+3 (very hard),6:+4 (hardest),130:-1 (medium soft),'+ + '132:+1 (medium hard),32768:Film Simulation,65535:n/a'; + rsFujiWhiteBalLkup = '0:Auto,256:Daylight,512:Cloudy,768:Daylight Fluorescent,' + + '769:Day White Fluorescent,770:White Fluorescent,771:Warm White Fluorescent,'+ + '772:Living Room Warm White Fluorescent,1024:Incandescent,1280:Flash,'+ + '1536:Underwater,3840:Custom,3841:Custom2,3842:Custom3,3843:Custom4,'+ + '3844:Custom5,4080:Kelvin'; + rsFujiSaturationLkup = '0:0 (normal),128:+1 (medium high),192:+3 (very high),'+ + '224:+4 (highest),256:+2 (high),384:-1 (medium low),512:Low,768:None (B&W),'+ + '769:B&W Red Filter,770:B&W Yellow Filter,771:B&W Green Filter,'+ + '784:B&W Sepia,1024:-2 (low),1216:-3 (very low),1248:-4 (lowest),'+ + '1280:Acros,1281:Acros Red Filter,1282:Acros Yellow Filter,'+ + '1283:Acros Green Filter,32768:Film Simulation'; + rsFujiContrastLkup = '0:Normal,128:Medium High,256:High,384:Medium Low,'+ + '512:Low,32768:Film Simulation'; + rsFujiContrastLkup1 = '0:Normal,256:High,768:Low'; + rsFujiNoiseReductionLkup = '64:Low,128:Normal,256:n/a'; + rsFujiHighIsoNoiseReductionLkup = '0:0 (normal),256:+2 (strong),'+ + '384:+1 (medium strong),448:+3 (very strong),480:+4 (strongest)'+ + '512:-2 (weak),640:-1 (medium weak),704:-3 (very weak),736:-4 (weakest)'; + rsFujiFlashModeLkup = '0:Auto,1:On,2:Off,3:Red-eye reduction,4:External,'+ + '16:Commander,32768:Not Attached,33056:TTL,38976:Manual,39040:Multi-flash,'+ + '43296:1st Curtain (front),51488:2nd Curtain (rear),59680:High Speed Sync (HSS)'; + rsFujiPictureModeLkup = '0:Auto,1:Portrait,2:Landscape,3:Macro,4:Sports,'+ + '5:Night Scene,6:Program AE,7:Natural Light,8:Anti-blur,9:Beach & Snow,'+ + '10:Sunset,11:Museum,12:Party,13:Flower,14:Text,15:Natural Light & Flash,'+ + '16:Beach,17:Snow,18:Fireworks,19:Underwater,20:Portrait with Skin Correction,'+ + '22:Panorama,23:Night (tripod),24:Pro Low-light,25:Pro Focus,26:Portrait 2,'+ + '27:Dog Face Detection,28:Cat Face Detection,64:Advanced Filter,'+ + '256:Aperture-priority AE,512:Shutter speed priority AE,768:Manual'; + rsFujiEXRModeLkup = '128:HR (High Resolution),512:SN (Signal to Noise priority),'+ + '768:DR (Dynamic Range priority)'; + rsFujiShadowHighlightLkup = '-64:+4 (hardest),-48:+3 (very hard),'+ + '-32:+2 (hard),-16:+1 (medium hard)'; + rsFujiShutterTypeLkup = '0:Mechanical,1:Electronic'; + rsFujiAutoBracketingLkup = '0:Off,1:On,2:No flash & flash'; + rsFujiPanoramaDirLkup = '1:Right,2:Up,3:Left,4:Down'; + rsFujiAdvancedFilterLkup = '65536:Pop Color,131072:Hi Key,196608:Toy Camera,'+ + '262144:Miniature, 327680:Dynamic Tone,327681:Partial Color Red,'+ + '327682:Partial Color Yellow,327683:Partial Color Green,'+ + '327684:Partial Color Blue,327685:Partial Color Orange,'+ + '327686:Partial Color Purple,458752:Soft Focus,589824:Low Key'; + rsFujiColorModeLkup = '0:Standard,16:Chrome,48:B & W'; + rsFujiBlurWarningLkup = '0:None,1:Blur Warning'; + rsFujiFocusWarningLkup = '0:Good,1:Out of focus'; + rsFujiExposureWarningLkup = '0:Good,1:Bad exposure'; + rsFujiDynamicRangeLkup = '1:Standard,3:Wide'; + rsFujiSceneRecognLkup = '0:Unrecognized,256:Portrait Image,512:Landscape Image,'+ + '768:Night Scene,1024:Macro'; + + // Minolta + rsMinoltaBracketStepLkup = '0:1/3 EV,1:2/3 EV,2:1 EV'; + rsMinoltaColorModeLkup = '0:Natural color,1:Black & White,2:Vivid color,'+ + '3:Solarization,4:Adobe RGB,5:Sepia,9:Natural,12:Portrait,13:Natural sRGB,'+ + '14:Natural+ sRGB,15:Landscape,16:Evening,17:Night Scene,18:Night Portrait,'+ + '132:Embed Adobe RGB'; + rsMinoltaColorProfileLkup = '0:Not embedded,1:Embedded'; + rsMinoltaDataImprintLkup = '0;None,1:YYYY/MM/DD,2:MM/DD/HH:MM,3:Text,4:Text + ID#'; + rsMinoltaDECPositionLkup = '0:Exposure,1:Contrast,2:Saturation,3:Filter'; + rsMinoltaDigitalZoomLkup = '0:Off,1:Electronic magnification,2:2x'; + rsMinoltaDriveModeLkup = '0:Single,1:Continuous,2:Self-timer,4:Bracketing,'+ + '5:Interval,6:UHS continuous,7:HS continuous'; + rsMinoltaExposureModeLkup = '0:Program,1:Aperture priority,2:Shutter priority,3:Manual'; + rsMinoltaFocusAreaLkup = '0:Wide Focus (normal),1:Spot Focus'; + rsMinoltaFlashMeteringLkup = '0:ADI (Advanced Distance Integration),1:Pre-flash TTL,2:Manual flash control'; + rsMinoltaFlashModeLkup = '0:Fill flash,1:Red-eye reduction,2:Rear flash sync,3:Wireless,4:Off?'; + rsMinoltaFocusModeLkup = '0:AF,1:MF'; + rsMinoltaFolderNameLkup = '0:Standard Form,1:Data Form'; + rsMinoltaImageSizeLkup = '1:1600x1200,2:1280x960,3:640x480,5:2560x1920,6:2272x1704,7:2048x1536'; + rsMinoltaImageSizeLkup1 = '0:Full,1:1600x1200,2:1280x960,3:640x480,6:2080x1560,7:2560x1920,8;3264x2176'; + rsMinoltaImageStabLkup = '1:Off,5:On'; + rsMinoltaInternalFlashLkup = '0:No,1:Fired'; + rsMinoltaIntervalModeLkup = '0:Still image,1:Time-lapse movie'; + rsMinoltaIsoSettingLkup = '0:100,1:200,2:400,3:800,4:Auto,5:64'; + rsMinoltaMeteringModeLkup = '0:Multi-segment,1:Center-weighted average,2:Spot'; + rsMinoltaModelIDLkup = '0:DiMAGE 7/X1/X21 or X31,1:DiMAGE 5,2:DiMAGE S304,'+ + '3:DiMAGE S404,4:DiMAGE 7i,5:DiMAGE 7Hi,6:DiMAGE A1,7:DiMAGE A2 or S414'; + rsMinoltaQualityLkup = '0:Raw,1:Super Fine,2:Fine,3:Standard,4:Economy,5:Extra fine'; + rsMinoltaSceneModeLkup = '0:Standard,1:Portrait,2:Text,3:Night Scene,'+ + '4:Sunset,5:Sports,6:Landscape,7:Night Portrait,8:Macro,9:Super Macro,'+ + '16:Auto,17:Night View/Portrait,18:Sweep Panorama,19:Handheld Night Shot,'+ + '20:Anti Motion Blur,21:Cont. Priority AE,22:Auto+,23:3D Sweep Panorama,'+ + '24:Superior Auto,25:High Sensitivity,26:Fireworks,27:Food,28:Pet,33:HDR,'+ + '65535:n/a'; + rsMinoltaSharpnessLkup = '0:Hard,1:Normal,2:Soft'; + rsMinoltaSubjectProgramLkup = '0:None,1:Portrait,2:Text,3:Night portrait,4:Sunset,5:Sports action'; + rsMinoltaTeleconverterLkup = '$0:None,$4:Minolta/Sony AF 1.4x APO (D) (0x04),'+ + '$5:Minolta/Sony AF 2x APO (D) (0x05),$48 = Minolta/Sony AF 2x APO (D),'+ + '$50:Minolta AF 2x APO II,$60:Minolta AF 2x APO,$88:Minolta/Sony AF 1.4x APO (D),'+ + '$90 = Minolta AF 1.4x APO II,$A0 = Minolta AF 1.4x APO'; + rsMinoltaWhiteBalanceLkup = '$00:Auto,$01:Color Temperature/Color Filter,$10:Daylight,'+ + '$20:Cloudy,$30:Shade,$40:Tungsten,$50:Flash,$60:Fluorescent,$70:Custom'; + rsMinoltaWideFocusZoneLkup = '0:No zone,1:Center zone (horizontal orientation),'+ + '2:Center zone (vertical orientation),3:Left zone,4:Right zone'; + rsMinoltaZoneMatchingLkup = '0:ISO Setting Used,1:High Key,2:Low Key'; + + // Nikon + rsNikonColorModeLkup = '1:Color,2:Monochrome'; + rsNikonConverterLkup = '0:Not used,1:Used'; + rsNikonImgAdjLkup = '0:Normal,1:Bright+,2:Bright-,3:Contrast+,4:Contrast-'; + rsNikonISOLkup = '0:ISO80,2:ISO160,4:ISO320,5:ISO100'; + rsNikonQualityLkup = '1:Vga Basic,2:Vga Normal,3:Vga Fine,4:SXGA Basic,'+ + '5:SXGA Normal,6:SXGA Fine,10:2 Mpixel Basic,11:2 Mpixel Normal,'+ + '12:2 Mpixel Fine'; + rsNikonWhiteBalanceLkup = '0:Auto,1:Preset,2:Daylight,3:Incandescense,'+ + '4:Fluorescence,5:Cloudy,6:SpeedLight'; + + // Olympus + rsOlympusCCDScanModeLkup = '0:Interlaced,1:Progressive'; + rsOlympusContrastLkup = '0:High,1:Normal,2:Low'; + rsOlympusFlashDevLkup = '0:None,1:Internal,4:External,5:Internal + External'; + rsOlympusFlashModeLkup = '2:On,3;Off'; + rsOlympusFlashModelLkup = '0:None,1:FL-20,2:FL-50,3:RF-11,4:TF-22,5:FL-36,'+ + '6:FL-50R,7:FL-36R,9:FL-14,11:FL-600R'; + rsOlympusFlashTypeLkup = '0:None,2:Simple E-System,3:E-System'; + rsOlympusJpegQualLkup = '1:SQ,2:HQ,3:SHQ,4:Raw'; + rsOlympusMacroLkup = '0:Off,1:On,2:Super Macro'; + rsOlympusPreviewImgLength = 'Preview image length'; + rsOlympusPreviewImgStart = 'Preview image start'; + rsOlympusPreviewImgValid = 'Preview image valid'; + rsOlympusSharpnessLkup = '0:Normal,1:Hard,2:Soft'; + rsOlympusSceneModeLkup = '0:Normal,1:Standard,2:Auto,3:Intelligent Auto,' + + '4:Portrait,5:Landscape+Portrait,6:Landscape,7:Night Scene,8:Night+Portrait' + + '9:Sport,10:Self Portrait,11:Indoor,12:Beach & Snow,13:Beach,14:Snow,' + + '15:Self Portrait+Self Timer,16:Sunset,17:Cuisine,18:Documents,19:Candle,' + + '20:Fireworks,21:Available Light,22:Vivid,23:Underwater Wide1,24:Underwater Macro,' + + '25:Museum,26:Behind Glass,27:Auction,28:Shoot & Select1,29:Shoot & Select2,'+ + '30:Underwater Wide2,31:Digital Image Stabilization,32:Face Portrait,33:Pet,'+ + '34:Smile Shot,35:Quick Shutter,43:Hand-held Starlight,100:Panorama,'+ + '101:Magic Filter,103:HDR'; + + // Sanyo + rsSanyoMacroLkup = '0:Normal,1:Macro,2:View,3:Manual'; + rsSanyoQualityLkup = '0:Normal/Very Low,1:Normal/Low,2:Normal/Medium Low,'+ + '3:Normal/Medium,4:Normal/Medium High,5:Normal/High,6:Normal/Very High'+ + '7:Normal/Super High,256:Fine/Very Low,257:Fine/Low,258:Fine/Medium Low'+ + '259:Fine/Medium,260:Fine/Medium High,261:Fine/High,262:Fine/Very High'+ + '263:Fine/Super High,512:Super Fine/Very Low,513:Super Fine/Low,'+ + '514:Super Fine/Medium Low,515:Super Fine/Medium,516:Super Fine/Medium High,'+ + '517:Super Fine/High,518:Super Fine/Very High,519:Super Fine/Super High'; + rsSanyoSpecialMode = 'Special mode'; + + + // *** IPTC tags *** + + rsActionAdvised = 'Action advised'; + rsByLine = 'ByLine'; + rsByLineTitle = 'ByLine title'; + rsCategory = 'Category'; + rsCity = 'City'; + rsCodedCharSet = 'Coded character set'; + rsContact = 'Contact'; +// rsCopyright = 'Copyright notice'; + rsContentLocCode = 'Content location code'; + rsContentLocName = 'Content location name'; + rsDateCreated = 'Date created'; +// rsDigitizeDate = 'Digital creation date'; +// rsDigitizeTime = 'Digital creation time'; + rsEditorialUpdate = 'Editorial update'; + rsEditStatus = 'Edit status'; + rsExpireDate = 'Expiration date'; + rsExpireTime = 'Expiration time'; + rsFixtureID = 'Fixture ID'; + rsImgCaption = 'Image caption'; + rsImgCaptionWriter = 'Image caption writer'; + rsImgCredit = 'Image credit'; + rsImgHeadline = 'Image headline'; + rsImgType = 'Image type'; + rsIptcOrientationLkup = 'P:Portrait,L:Landscape,S:Square'; + rsKeywords = 'Keywords'; + rsLangID = 'Language ID'; + rsLocationCode = 'Country/primary location code'; + rsLocationName = 'Country/primary location name'; + rsObjectAttr = 'Object attribute reference'; + rsObjectCycle = 'Object cycle'; + rsObjectCycleLkup = 'a:morning,p:evening,b:both'; + rsObjectName = 'Object name'; + rsObjectType = 'Object type reference'; +// rsOrientation = 'Image orientation'; + rsOriginatingProg = 'Originating program'; + rsProgVersion = 'Program version'; + rsRecordVersion = 'Record version'; + rsRefDate = 'Reference date'; + rsRefNumber = 'Reference number'; + rsRefService = 'Reference service'; + rsReleaseDate = 'Release date'; + rsReleaseTime = 'Release time'; + rsSource = 'Source'; + rsSpecialInstruct = 'Special instructions'; + rsState = 'Province/State'; + rsSubjectRef = 'Subject reference'; + rsSubfile = 'Subfile'; + rsSubLocation = 'Sublocation'; + rsSuppCategory = 'Supplemental category'; + rsTimeCreated = 'Time created'; + rsUrgency = 'Urgency'; + rsUrgencyLkup = '0:reserved,1:most urgent,5:normal,8:least urgent,9:reserved'; + + +implementation + +end. + diff --git a/components/fpexif/fpetags.pas b/components/fpexif/fpetags.pas new file mode 100644 index 000000000..d56e12947 --- /dev/null +++ b/components/fpexif/fpetags.pas @@ -0,0 +1,1712 @@ +unit fpeTags; + +{$IFDEF FPC} + {$MODE DELPHI} + //{$mode objfpc}{$H+} +{$ENDIF} + +interface + +uses + Classes, SysUtils, Contnrs, + fpeGlobal; + +const + // Tag constants for subIFDs as defined by EXIF standard + TAG_EXIF_OFFSET = $8769; + TAG_GPS_OFFSET = $8825; + TAG_INTEROP_OFFSET = $A005; + TAG_SUBIFD_OFFSET = $014A; + TAG_IPTC = $83BB; + TAG_MAKERNOTE = $927C; + // Auxiliary tags to identity IFD0 and IFD1 as parents. + TAG_PRIMARY = $0001; + TAG_THUMBNAIL = $0002; + + // Using these TagIDs as ParentIDs + TAGPARENT_PRIMARY = TTagID(TAG_PRIMARY shl 16); // $00010000; + TAGPARENT_THUMBNAIL = TTagID(TAG_THUMBNAIL shl 16); // $00020000; + TAGPARENT_EXIF = TTagID(TAG_EXIF_OFFSET shl 16); // $87690000; + TAGPARENT_GPS = TTagID(TAG_GPS_OFFSET shl 16); // $88250000; + TAGPARENT_INTEROP = TTagID(TAG_INTEROP_OFFSET shl 16); // $A0050000; + TAGPARENT_MAKERNOTE = TTagID(TAG_MAKERNOTE shl 16); // $927C0000; + TAGPARENT_IPTC = TTagID(TAG_IPTC shl 16); // $83BB0000; + + // Full tagID: hi-word = tagID of parent, lo-word = tagID of tag. + // Parent's ID tag's ID + FULLTAG_EXIF_OFFSET = TTagID(TAGPARENT_PRIMARY or TAG_EXIF_OFFSET); + FULLTAG_IPTC = TTagID(TAGPARENT_PRIMARY or TAG_IPTC); + FULLTAG_GPS_OFFSET = TTagID(TAGPARENT_EXIF or TAG_GPS_OFFSET); + FULLTAG_INTEROP_OFFSET = TTagID(TAGPARENT_EXIF or TAG_INTEROP_OFFSET); + FULLTAG_MAKERNOTE = TTagID(TAGPARENT_EXIF or TAG_MAKERNOTE); + +type + TTag = class; + TTagClass = class of TTag; + + TTagDef = class + private + function GetTagID: TTagID; + procedure SetTagID(const AValue: TTagID); + public + TagIDRec: TTagIDRec; // ID of the tag + Group: TTagGroup; // Group to which the tag belongs + Name: String; // Name of the tag + Desc: String; // Tag description + TagType: TTagType; // Tag type + Count: Word; // Number of elements of which the tag consists + LkUpTbl: String; // Lookup table for enumerated values + FormatStr: String; // Format string + TagClass: TTagClass; // Class of the tag instance to be created from this definition + ReadOnly: Boolean; // true: tag cannot be edited by user + property TagID: TTagID read GetTagID write SetTagID; + end; + + TTagDefList = class(TObjectList) + private + function GetItem(AIndex: Integer): TTagDef; + procedure SetItem(AIndex: Integer; AValue: TTagDef); + protected + procedure AddTag(ATagID: TTagID; AName: String; AType: TTagType; + ACount: Word = 1; ADesc: String = ''; ALkUpTbl: String = ''; + AFormatStr: String = ''; AClass: TTagClass = nil; AReadOnly: Boolean = false); + function GetGroupOfTag(ATagID: TTagID): TTagGroup; virtual; + function IndexOfParentByID(ATagID: TTagID): Integer; + public + procedure AddBinaryTag(ATagID: TTagID; AName: String; ACount: Word = 1; + ADesc: String = ''; ALkupTbl: String = ''; AFormatStr: String = ''; + AClass: TTagClass = nil; AReadOnly: Boolean = false); + procedure AddByteTag(ATagID: TTagID; AName: String; ACount: Word = 1; + ADesc: String = ''; ALkupTbl: String = ''; AFormatStr: String = ''; + AClass: TTagClass = nil; AReadOnly: Boolean = false); + procedure AddIFDTag(ATagID: TTagID; AName: String; ADesc: String = ''; + AClass: TTagClass = nil; AReadOnly: Boolean = false); + procedure AddSLongTag(ATagID: TTagID; AName: String; ACount: Word = 1; + ADesc: String = ''; ALkupTbl: String = ''; AFormatStr: String = ''; + AClass: TTagClass = nil; AReadOnly: Boolean = false); + procedure AddSShortTag(ATagID: TTagID; AName: String; ACount: Word = 1; + ADesc: String = ''; ALkupTbl: String = ''; AFormatStr: String = ''; + AClass: TTagClass = nil; AReadOnly: Boolean = false); + procedure AddStringTag(ATagID: TTagID; AName: String; ACount: Word = 1; + ADesc: String = ''; ALkupTbl: String = ''; AClass: TTagClass = nil; + AReadOnly: Boolean = false); + procedure AddUShortTag(ATagID: TTagID; AName: String; ACount: Word = 1; + ADesc: String = ''; ALkupTbl: String = ''; AFormatStr: String = ''; + AClass: TTagClass = nil; AReadOnly: Boolean = false); + procedure AddULongTag(ATagID: TTagID; AName: String; ACount: Word = 1; + ADesc: String = ''; ALkupTbl: String = ''; AFormatStr: String = ''; + AClass: TTagClass = nil; AReadOnly: Boolean = false); + procedure AddURationalTag(ATagID: TTagID; AName: String; ACount: Word = 1; + ADesc: String = ''; ALkupTbl: String = ''; AFormatStr: String = ''; + AClass: TTagClass = nil; AReadOnly: Boolean = false); + procedure AddSRationalTag(ATagID: TTagID; AName: String; ACount: Word = 1; + ADesc: String = ''; ALkupTbl: String = ''; AFormatStr: String = ''; + AClass: TTagClass = nil; AReadOnly: Boolean = false); + + function FindByID(ATagID: TTagID): TTagDef; + function FindByIDWithoutParent(ATagID: Word): TTagDef; + function FindByName(AFullTagName: String): TTagDef; + + property Items[AIndex: Integer]: TTagDef read GetItem write SetItem; default; + end; + + TTag = class + private + function GetBigEndian: Boolean; + function GetBinaryAsASCII: Boolean; + function GetDecodeValue: Boolean; + function GetDescription: String; + function GetIsVolatile: Boolean; + function GetReadOnly: Boolean; + function GetTagIDRec: TTagIDRec; + function GetTruncBinary: Boolean; + procedure SetBinaryAsASCII(const AValue: Boolean); + procedure SetDecodeValue(const AValue: Boolean); + procedure SetRawData(const AValue: TBytes); + procedure SetTruncBinary(const AValue: Boolean); + protected + FTagID: TTagID; + FDesc: String; + FGroup: TTagGroup; + FName: String; + FType: TTagType; + FCount: Integer; + FRawData: TBytes; + FFormatStr: String; + FLkupTbl: String; + FListSeparator: Char; + FOptions: TTagOptions; + function GetAsFloat: Double; virtual; + function GetAsFloatArray: TExifDoubleArray; virtual; + function GetAsInteger: Integer; virtual; + function GetAsIntegerArray: TExifIntegerArray; virtual; + function GetAsRational: TExifRational; virtual; + function GetAsRationalArray: TExifRationalArray; virtual; + function GetAsString: String; virtual; + function Lookup(const AKey, ALookupTbl: String; ASameKeyFunc: TLookupCompareFunc): String; + function LookupValue(const AValue, ALookupTbl: String): String; + procedure SetAsFloat(const AValue: Double); virtual; + procedure SetAsFloatArray(const AValue: TExifDoubleArray); virtual; + procedure SetAsInteger(const AValue: Integer); virtual; + procedure SetAsIntegerArray(const AValue: TExifIntegerArray); virtual; + procedure SetAsRational(const AValue: TExifRational); virtual; + procedure SetAsRationalArray(const AValue: TExifRationalArray); virtual; + procedure SetAsString(const AValue: String); virtual; + property FormatStr: String read FFormatStr write FFormatStr; + property LkupTbl: String read FLkupTbl write FLkupTbl; + public + constructor Create(ATagDef: TTagDef; AIsBigEndian: Boolean); overload; + constructor Create(ATagDef: TTagDef; AOptions: TTagOptions); overload; virtual; +// procedure GetTagIDOfGroup(out ATagIDOfGroup: TTagID; out AGroupOfGroup: TTagGroup); + function HasData: Boolean; + { Tag value as a float value } + property AsFloat: Double read GetAsFloat write SetAsFloat; + { Tag value as a float array } + property AsFloatArray: TExifDoubleArray read GetAsFloatArray write SetAsFloatArray; + { Tag value as an integer } + property AsInteger: Integer read GetAsInteger write SetAsInteger; + { Tag value as an integer array (if Count > 1) } + property AsIntegerArray: TExifIntegerArray read GetAsIntegerArray write SetAsIntegerArray; + { Tag value as a rational value } + property AsRational: TExifRational read GetAsRational write SetAsRational; + { Tag value as a rational array } + property AsRationalArray: TExifRationalArray read GetAsRationalArray write SetAsRationalArray; + { Returns the tag value as a string } + property AsString: String read GetAsString write SetAsString; + { Make AsString return binary tag bytes as ASCII characters, otherwise as decimal numbers } + property BinaryAsASCII: Boolean read GetBinaryAsASCII write SetBinaryAsASCII; + { Returns the name of the tag. To be used when accessing the tag. } + property Name: String read FName; + { Returns a better-readable or even localized description of the tag } + property Description: String read GetDescription; + { Returns the numeric ID of the tag } + property TagID: TTagID read FTagID; + property TagIDRec: TTagIDRec read GetTagIDRec; + { Identifies the group to which the tag belongs. The tag is unique only within its group. } + property Group: TTagGroup read FGroup; + { Defines the data type of the tag } + property TagType: TTagType read FType write FType; + { Determines the number of elements of which the tag value consists } + property Count: Integer read FCount write FCount; + { Raw data of the tag value as read from the file or to be written to the file } + property RawData: TBytes read FRawData write SetRawData; + { Indicates wheter the raw data are in little endian or big endian byte order } + property BigEndian: Boolean read GetBigEndian; + { Determines whether the meaning of numberical values will be decoded. } + property DecodeValue: Boolean read GetDecodeValue write SetDecodeValue; + { Character used separate array elements, usually for binary tags } + property ListSeparator: Char read FListSeparator write FListSeparator; + { Is true when this tag cannot be altered by fpExif. } + property ReadOnly: Boolean read GetReadOnly; + { In AsString, return only the first MaxBinaryBytes of binary tag values } + property TruncateBinary: Boolean read GetTruncBinary write SetTruncBinary; + { Tag is not written to file } + property IsVolatile: Boolean read GetIsVolatile; + end; + + TNumericTag = class(TTag) + public + constructor Create(ATagDef: TTagDef; AOptions: TTagOptions); overload; override; + property FormatStr; + property LkupTbl; + end; + + TIntegerTag = class(TNumericTag) + protected + function GetAsFloat: Double; override; + function GetAsFloatArray: TExifDoubleArray; override; + function GetAsInteger: Integer; override; + function GetAsIntegerArray: TExifIntegerArray; override; + function GetAsRational: TExifRational; override; + function GetAsRationalArray: TExifRationalArray; override; + function GetAsString: String; override; + procedure SetAsInteger(const AValue: Integer); override; + procedure SetAsIntegerArray(const AValue: TExifIntegerArray); override; + procedure SetAsString(const AValue: String); override; + protected + function GetInteger(AIndex: Integer; out AValue: Integer): Boolean; + procedure SetInteger(const AIndex, AValue: Integer; + WithRangeCheck: Boolean = true); + end; + + TFloatTag = class(TNumericTag) + private +// FValidDigits: Integer; + protected + function GetAsFloat: Double; override; + function GetAsFloatArray: TExifDoubleArray; override; + function GetAsInteger: Integer; override; + function GetAsIntegerArray: TExifIntegerArray; override; + function GetAsRational: TExifRational; override; + function GetAsRationalArray: TExifRationalArray; override; + function GetAsString: String; override; + procedure SetAsFloat(const AValue: Double); override; + procedure SetAsFloatArray(const AValue: TExifDoubleArray); override; + procedure SetAsInteger(const AValue: Integer); override; + procedure SetAsIntegerArray(const AValue: TExifIntegerArray); override; + procedure SetAsRational(const AValue: TExifRational); override; + procedure SetAsRationalArray(const AValue: TExifRationalArray); override; + procedure SetAsString(const AValue: String); override; + protected + function GetFloat(AIndex: Integer; out AValue: Double): Boolean; virtual; + function GetRational(AIndex: Integer; out AValue: TExifRational): Boolean; virtual; + procedure InternalSetRational(AIndex: Integer; AValue: TExifRational); + function IsInt(AValue: Double): Boolean; + procedure SetFloat(AIndex: Integer; const AValue: Double); virtual; + procedure SetRational(AIndex: Integer; const AValue: TExifRational); virtual; + public +// constructor Create(ATagDef: TTagDef; AOptions: TTagOptions); override; + property AsFloat: Double read GetAsFloat write SetAsFloat; + property AsFloatArray: TExifDoubleArray read GetAsFloatArray write SetAsFloatArray; + property AsRational: TExifRational read GetAsRational write SetAsRational; + property AsRationalArray: TExifRationalArray read GetAsRationalArray write SetAsRationalArray; +// property ValidDigits: Integer read FValidDigits write FValidDigits; + end; + + TStringTag = class(TTag) + protected + function GetAsString: String; override; + procedure SetAsString(const AValue: String); override; + public + constructor Create(ATagDef: TTagDef; AOptions: TTagOptions); override; + property AsString: String read GetAsString write SetAsString; + property LkupTbl; + end; + + TBinaryTag = class(TTag) + protected + function GetAsString: String; override; + procedure SetAsString(const AValue: String); override; + public + property LkupTbl; + end; + + TOffsetTag = class(TIntegerTag) + private + FTiffHeaderOffset: Int64; + public + property TiffHeaderOffset: Int64 read FTiffHeaderOffset write FTiffHeaderOffset; + end; + + // Tag which contains the offset to a new sub-IFD (IFD = "image file directory") + TSubIFDTag = class(TOffsetTag) + public + constructor Create(ATagDef: TTagDef; AOptions: TTagOptions); overload; override; + end; + + TMakerNoteTag = class(TBinaryTag) + end; + + TTagList = class(TObjectList) + private + function GetItem(AIndex: Integer): TTag; + procedure SetItem(AIndex: integer; const AValue: TTag); + public + function GetGroupOfTag(ATagID: TTagID): TTagGroup; virtual; + function IndexOfParentByID(ATagID: TTagID): Integer; + function IndexOfTagByID(ATagID: TTagID): Integer; + property Items[AIndex: Integer]: TTag read GetItem write SetItem; default; + end; + + +const + DefaultTagClasses: array[TTagType] of TTagClass = ( + TIntegerTag, TIntegerTag, TIntegerTag, TFloatTag, // UInt8, UInt16, UInt32, URational + TIntegerTag, TIntegerTag, TIntegerTag, TFloatTag, // SInt8, SInt16, SInt32, SRational + TStringTag, // String + TBinaryTag, // Binary + TFloatTag, TFloatTag, // Single, Double + TSubIFDTag // IFD + ); + + (* +{ TTagGroups } + +function GetGroupFromGeneratingTagID(ATagID: TTagID): TTagGroup; + *) + +implementation + +uses + Math, StrUtils, + fpeUtils; + (* +type + TGroupRecord = record + TagID: TTagID; + Group: TTagGroup; + end; + +var + // This list collects in which IFD rags referring to subIFDs are found. } + TagsOfGroups: array[TTagGroup] of TGroupRecord = ( + (TagID:$FFFF; Group: tgUnknown), // tgUnknown + (TagID:$FFFF; Group: tgUnknown), // tgJFIF + (TagID:0; Group: tgExifPrimary), // tgExifPrimary + (TagID:1; Group: tgExifPrimary), // tgExifThumbnail + (TagID:TAG_EXIF_OFFSET; Group: tgExifPrimary), // tgExifSub + (TagID:Tag_INTEROP_OFFSET; Group: tgExifSub), // tgExifInterOp + (TagID:Tag_GPS_OFFSET; Group: tgExifPrimary), // tgExifGps + (TagID:Tag_MAKERNOTE; Group: tgExifSub), // tgExifMakerNote + (TagID:$FFFF; Group: tgExifMakerNote), // tgExifMakerNoteSub + (TagID:$FFFF; Group: tgUnknown) // tgIPTC + ); + *) +//============================================================================== +// Utilities +//============================================================================== +function SameIntegerFunc(AKey1, AKey2: String): Boolean; +var + k1, k2: Integer; +begin + Result := TryStrToInt(AKey1, k1) and TryStrToInt(AKey2, k2) and (k1 = k2); +end; + +function SameStringFunc(AKey1, AKey2: String): Boolean; +begin + Result := SameText(AKey1, AKey2); +end; + + +//============================================================================== +// TTagDef +//============================================================================== +function TTagDef.GetTagID: TTagID; +begin + Result := TTagID(TagIDRec); +end; + +procedure TTagDef.SetTagID(const AValue: TTagID); +begin + TagIDRec := TTagIDRec(AValue); +end; + +//============================================================================== +// TTagDefList +//============================================================================== + +procedure TTagDefList.AddBinaryTag(ATagID: TTagID; AName: String; + ACount: Word = 1; ADesc: String = ''; ALkupTbl: String = ''; + AFormatStr: String = ''; AClass: TTagClass = nil; AReadOnly: Boolean = false); +begin + AddTag(ATagID, AName, ttBinary, ACount, ADesc, ALkupTbl, AFormatStr, + AClass, AReadOnly); +end; + +procedure TTagDefList.AddByteTag(ATagID: TTagID; AName: String; + ACount: Word = 1; ADesc: String = ''; ALkupTbl: String = ''; + AFormatStr: String = ''; AClass: TTagClass = nil; AReadOnly: Boolean = false); +begin + AddTag(ATagID, AName, ttUInt8, ACount, ADesc, ALkupTbl, AFormatStr, + AClass, AReadOnly); +end; + +procedure TTagDefList.AddIFDTag(ATagID: TTagID; AName: String; + ADesc: String = ''; AClass: TTagClass = nil; AReadOnly: Boolean = false); +begin + AddTag(ATagID, AName, ttIFD, 1, ADesc, '', '', AClass, AReadOnly); +end; + +procedure TTagDefList.AddSLongTag(ATagID: TTagID; AName: String; + ACount: Word = 1; ADesc: String = ''; ALkupTbl: String = ''; + AFormatStr: String = ''; AClass: TTagClass = nil; AReadOnly: Boolean = false); +begin + AddTag(ATagID, AName, ttSInt32, ACount, ADesc, ALkupTbl, AFormatStr, + AClass, AReadOnly); +end; + +procedure TTagDefList.AddSRationalTag(ATagID: TTagID; AName: String; + ACount: Word =1; ADesc: String = ''; ALkupTbl: String = ''; + AFormatStr: String = ''; AClass: TTagClass = nil; AReadOnly: Boolean = false); +begin + AddTag(ATagID, AName, ttSRational, ACount, ADesc, ALkupTbl, AFormatStr, + AClass, AReadOnly); +end; + +procedure TTagDefList.AddSShortTag(ATagID: TTagID; AName: String; + ACount: Word = 1; ADesc: String = ''; ALkupTbl: String = ''; + AFormatStr: String = ''; AClass: TTagClass = nil; AReadOnly: Boolean = false); +begin + AddTag(ATagID, AName, ttSInt16, ACount, ADesc, ALkupTbl, AFormatStr, + AClass, AReadOnly); +end; + +procedure TTagDefList.AddStringTag(ATagID: TTagID; AName: String; + ACount: Word = 1; ADesc: String = ''; ALkupTbl: String = ''; + AClass: TTagClass = nil; AReadOnly: Boolean = false); +begin + AddTag(ATagID, AName, ttString, ACount, ADesc, ALkupTbl, '', + AClass, AReadOnly); +end; + +procedure TTagDefList.AddTag(ATagID: TTagID; AName: String; + AType: TTagType; ACount: Word = 1; ADesc: String = ''; ALkUpTbl: String = ''; + AFormatStr: String = ''; AClass: TTagClass = nil; AReadOnly: Boolean = false); +var + tagdef: TTagDef; +begin + tagdef := TTagDef.Create; + tagdef.TagIDRec := TTagIDRec(ATagID); + tagdef.Group := GetGroupOfTag(ATagID); + tagdef.TagType := AType; + tagdef.Count := ACount; + tagdef.Name := AName; + tagdef.Desc := ADesc; + tagdef.LkUpTbl := ALkUpTbl; + tagdef.FormatStr := AFormatStr; + tagdef.ReadOnly := AReadOnly; + if AClass = nil then + case AType of + ttUInt8, ttUInt16, ttUInt32, ttSInt8, ttSInt16, ttSInt32: + AClass := TIntegerTag; + ttURational, ttSRational: + AClass := TFloatTag; + ttString: + AClass := TStringTag; + ttBinary: + AClass := TBinaryTag; + ttIFD: + AClass := TSubIFDTag; + else + raise EFpExif.Create('[TTagDefList.AddTag] TagType not supported.'); + end; + tagdef.TagClass := AClass; + Add(tagdef); +end; + +procedure TTagDefList.AddUShortTag(ATagID: TTagID; AName: String; + ACount: Word = 1; ADesc: String = ''; ALkupTbl: String = ''; + AFormatStr: String = ''; AClass: TTagClass = nil; AReadOnly: Boolean = false); +begin + AddTag(ATagID, AName, ttUInt16, ACount, ADesc, ALkupTbl, AFormatStr, + AClass, AReadOnly); +end; + +procedure TTagDefList.AddULongTag(ATagID: TTagID; AName: String; + ACount: Word = 1; ADesc: String = ''; ALkupTbl: String = ''; + AFormatStr: String = ''; AClass: TTagClass = nil; AReadOnly: Boolean = false); +begin + AddTag(ATagID, AName, ttUInt32, ACount, ADesc, ALkupTbl, AFormatStr, + AClass, AReadOnly); +end; + +procedure TTagDefList.AddURationalTag( ATagID: TTagID; AName: String; + ACount: Word = 1; ADesc: String = ''; + ALkupTbl: String = ''; AFormatStr: String = ''; AClass: TTagClass = nil; + AReadOnly: Boolean = false); +begin + AddTag(ATagID, AName, ttURational, ACount, ADesc, ALkupTbl, AFormatStr, + AClass, AReadOnly); +end; + +function TTagDefList.FindByID(ATagID: TTagID): TTagDef; +var + i: Integer; +begin + for i:=0 to Count-1 do begin + Result := GetItem(i); + if TTagID(Result.TagIDRec) = ATagID then + exit; + end; + Result := nil; +end; + +{ Looks for the tag definition specified by the ID of the tag only, ignoring + the id of its parent. } +function TTagDefList.FindByIDWithoutParent(ATagID: word): TTagDef; +var + i: Integer; + tagdef: TTagDef; +begin + for i := 0 to Count-1 do begin + tagdef := GetItem(i); + if TTagIDRec(tagdef.TagID).Tag = ATagID then begin + Result := tagDef; + exit; + end; + end; + Result := nil; +end; + +function TTagDefList.FindByName(AFullTagName: String): TTagDef; +var + gname, tname: String; + p, i: Integer; +begin + p := pos('.', AFullTagName); + if p <> 0 then begin + gname := copy(AFullTagName, 1, p-1); + tname := copy(AFullTagName, p+1, MaxInt); + end else begin + gname := ''; + tname := AFullTagName; + end; + if gname = '' then + for i:=0 to Count-1 do begin + Result := GetItem(i); + if SameText(tname, Result.Name) then + exit; + end + else + for i:=0 to Count-1 do begin + Result := GetItem(i); + if SameText(tname, Result.Name) and + (SameText(gname, GroupNames[Result.Group]) or SameText(gname, NiceGroupNames[Result.Group])) + then + exit; + end; + Result := nil; +end; + +function TTagDefList.GetGroupOfTag(ATagID: TTagID): TTagGroup; +var + idx: Integer; + tagIDRec: TTagIDRec absolute ATagID; + tagDef: TTagDef; +begin + Result := tgUnknown; + case tagIDRec.Parent of + $0000 : ; + $0001 : Result := tgExifPrimary; + $0002 : Result := tgExifThumbnail; + TAG_GPS_OFFSET : Result := tgExifGPS; + TAG_INTEROP_OFFSET : Result := tgExifInterOp; + TAG_EXIF_OFFSET : Result := tgExifSub; + TAG_MAKERNOTE : Result := tgExifMakerNote + else + idx := IndexOfParentByID(ATagID); + if idx = -1 then + exit; + tagdef := GetItem(idx); + Result := GetGroupOfTag(TTagID(tagdef.TagID)); + end; +end; + +function TTagDefList.GetItem(AIndex: Integer): TTagDef; +begin + Result := TTagDef(inherited Items[AIndex]); +end; + +{ Finds the index of the tag which is the parent of the tag with the specified ID } +function TTagDefList.IndexOfParentByID(ATagID: TTagID): Integer; +var + tagDef: TTagDef; +begin + for Result := 0 to Count - 1 do begin + tagDef := GetItem(Result); + if TTagIDRec(tagDef.TagID).Tag = TTagIDRec(ATagID).Parent then + exit; + end; + Result := -1; +end; + +procedure TTagDefList.SetItem(AIndex: Integer; AValue: TTagDef); +begin + inherited Items[AIndex] := AValue; +end; + + +//============================================================================== +// TTag +//============================================================================== + +constructor TTag.Create(ATagDef: TTagDef; AIsBigEndian: Boolean); +var + optns: TTagOptions; +begin + optns := []; + if AIsBigEndian then + Include(optns, toBigEndian); + if ATagDef.ReadOnly then + Include(optns, toReadOnly); + Create(ATagDef, optns); +end; + +constructor TTag.Create(ATagDef: TTagDef; AOptions: TTagOptions); +begin + FTagID := ATagDef.TagID; + FGroup := ATagDef.Group; + FName := ATagDef.Name; + FDesc := ATagDef.Desc; + FType := ATagDef.TagType; + FCount := ATagDef.Count; + FFormatStr := ATagDef.FormatStr; + FLkupTbl := ATagDef.LkupTbl; + FOptions := AOptions; + FListSeparator := fpExifFmtSettings.ListSeparator; +end; + +function TTag.{%H-}GetAsInteger: Integer; +begin + raise EFpExif.CreateFmt('Tag "%s" does not return an integer', [FName]); +end; + +function TTag.{%H-}GetAsIntegerArray: TExifIntegerArray; +begin + raise EFpExif.CreateFmt('Tag "%s" does not return an integer array.', [FName]); +end; + +function TTag.{%H-}GetAsFloat: Double; +begin + raise EFpExif.CreateFmt('Tag "%s" does not return a float value', [FName]); +end; + +function TTag.{%H-}GetAsFloatArray: TExifDoubleArray; +begin + raise EFpExif.CreateFmt('Tag "%s" does not return a float array.', [FName]); +end; + +function TTag.{%H-}GetAsRational: TExifRational; +begin + raise EFpExif.CreateFmt('Tag "%s" does not return a rational value', [FName]); +end; + +function TTag.{%H-}GetAsRationalArray: TExifRationalArray; +begin + raise EFpExif.CreateFmt('Tag "%s" does not return a rational array.', [FName]); +end; + +function TTag.GetAsString: String; +begin + Result := ''; +end; + +function TTag.GetBigEndian: Boolean; +begin + Result := toBigEndian in FOptions; +end; + +function TTag.GetBinaryAsASCII: Boolean; +begin + Result := toBinaryAsAscii in FOptions; +end; + +function TTag.GetDecodeValue: Boolean; +begin + Result := toDecodeValue in FOptions; +end; + +function TTag.GetDescription: String; +begin + if FDesc = '' then begin + if FName = '' then + Result := 'Unknown' + else + Result := InsertSpaces(FName); + end + else + Result := FDesc; +end; + +function TTag.GetIsVolatile: Boolean; +begin + Result := toVolatile in FOptions; +end; + +function TTag.GetReadOnly: Boolean; +begin + Result := toReadOnly in FOptions; +end; + (* +{ Returns the ID and the group of the tag defining the group of the current tag. + Example: + The tag "FocalLength" belongs to group EXIF; this group is defined by tag + ATagIDOfGroup (--> ATagIDOfGroup) which itself resides in the primary group + (--> AGroupOfGroup = tgExifPrimary). } +procedure TTag.GetTagIDOfGroup(out ATagIDOfGroup: TTagID; + out AGroupOfGroup: TTagGroup); +begin + with TagsOfGroups[FGroup] do begin + ATagIDOfGroup := TagID; + AGroupOfGroup := Group; + end; +end; *) + +function TTag.GetTagIDRec: TTagIDRec; +begin + Result := TTagIDRec(FTagID); +end; + +{ Getter for property TruncateBinary. Returns whether the function AsString + should return at most MaxBinaryBytes bytes. } +function TTag.GetTruncBinary: Boolean; +begin + Result := toTruncateBinary in FOptions; +end; + +{ Checks if the tag has data. Tags without data can occur if a non-existing + tag has been accessed by its TExifData property. Such tags will not be written + to the stream. } +function TTag.HasData: Boolean; +begin + Result := Length(FRawData) > 0; +end; + +{ The lookup table to which ALookupTbl points is a comma-separated string + constisting of key:value pairs. Seeks for the provided key and returns the + corresponding value. The function SameKeyFunk is used to check that the + key is matched. } +function TTag.Lookup(const AKey, ALookupTbl: String; ASameKeyFunc: TLookupCompareFunc): String; +begin + Result := fpeUtils.LookupValue(AKey, ALookupTbl, ASameKeyFunc); +end; + +function TTag.LookupValue(const AValue, ALookupTbl: String): String; +begin + Result := fpeUtils.LookupKey(AValue, ALookupTbl, @SameStringFunc); +end; + +procedure TTag.SetAsFloat(const AValue: Double); +begin + Unused(AValue); + raise EFpExif.CreateFmt('Cannot assign a float value to tag "%s".', [FName]); +end; + +procedure TTag.SetAsFloatArray(const AValue: TExifDoubleArray); +begin + Unused(AValue); + raise EFpExif.CreateFmt('Cannot assign a float array to tag "%s".', [FName]); +end; + +procedure TTag.SetAsInteger(const AValue: Integer); +begin + Unused(AValue); + raise EFpExif.CreateFmt('Cannot assign an integer value to tag "%s".', [FName]); +end; + +procedure TTag.SetAsIntegerArray(const AValue: TExifIntegerArray); +begin + Unused(AValue); + raise EFpExif.CreateFmt('Cannot assign an integer array to tag "%s".', [FName]); +end; + +procedure TTag.SetAsRational(const AValue: TExifRational); +begin + Unused(AValue); + raise EFpExif.CreateFmt('Cannot assign an rational value to tag "%s".', [FName]); +end; + +procedure TTag.SetAsRationalArray(const AValue: TExifRationalArray); +begin + Unused(AValue); + raise EFpExif.CreateFmt('Cannot assign a rational array to tag "%s".', [FName]); +end; + +procedure TTag.SetAsString(const AValue: String); +begin + Unused(AValue); +end; + +procedure TTag.SetBinaryAsASCII(const AValue: Boolean); +begin + if AValue then + Include(FOptions, toBinaryAsAscii) + else + Exclude(FOptions, toBinaryAsAscii); +end; + +procedure TTag.SetDecodeValue(const AValue: Boolean); +begin + if AValue then + Include(FOptions, toDecodeValue) + else + Exclude(FOptions, toDecodeValue); +end; + +procedure TTag.SetRawData(const AValue: TBytes); +var + len: Integer; +begin + len := Length(AValue); + FCount := len div TagElementSize[ord(FType)]; + SetLength(FRawData, len); + Move(AValue[0], FRawData[0], len); +end; + +procedure TTag.SetTruncBinary(const AValue: Boolean); +begin + if AValue then + Include(FOptions, toTruncateBinary) + else + Exclude(FOptions, toTruncateBinary); +end; + + +//============================================================================== +// TNumericTag +//============================================================================== + +constructor TNumericTag.Create(ATagDef: TTagDef; AOptions: TTagOptions); +begin + inherited Create(ATagDef, AOptions); + FLkupTbl := ATagDef.LkupTbl; +end; + + +//============================================================================== +// TIntegerTag +//============================================================================== + +function TIntegerTag.GetAsFloat: Double; +var + intVal: Integer; +begin + if GetInteger(0, intVal) then + Result := intVal * 1.0 + else + Result := NaN; +end; + +function TIntegerTag.GetAsFloatArray: TExifDoubleArray; +var + intVal: TExifIntegerArray; + i: Integer; +begin + intval := GetAsIntegerArray; + SetLength(Result, Length(intVal)); + for i:=0 to High(intval) do + Result[i] := intval[i] * 1.0; +end; + +function TIntegerTag.GetAsInteger: Integer; +begin + if not GetInteger(0, Result) then + Result := -1; +end; + +function TIntegerTag.GetAsIntegerArray: TExifIntegerArray; +var + i: Integer; +begin + SetLength(Result, FCount); + for i:=0 to FCount-1 do + if not GetInteger(i, Result[i]) then begin + SetLength(Result, 0); + exit; + end; +end; + +function TIntegerTag.GetAsRational: TExifRational; +var + intval: Integer; +begin + if GetInteger(0, intval) then begin + Result.Numerator := intval; + Result.Denominator := 1; + end else begin + Result.Numerator := 1; + Result.Denominator := 0; + end; +end; + +function TIntegerTag.GetAsRationalArray: TExifRationalArray; +var + intval: TExifIntegerArray; + i: Integer; +begin + intval := GetAsIntegerArray; + SetLength(Result, Length(intval)); + for i:=0 to High(intval) do begin + Result[i].Numerator := intval[i]; + Result[i].Denominator := 1; + end; +end; + +function TIntegerTag.GetAsString: String; +var + intVal: Integer; + i: Integer; + s: String; +begin + Result := ''; + + for i:=0 to FCount-1 do begin + if not GetInteger(i, intVal) then begin + Result := ''; + exit; + end; + if FFormatStr <> '' then + s := Format(FFormatStr, [intVal]) + else + if (FLkupTbl <> '') and (toDecodeValue in FOptions) then + s := Lookup(IntToStr(intVal), FLkupTbl, @SameIntegerFunc) + else + s := IntToStr(intVal); + Result := IfThen(i = 0, s, Result + FListSeparator + s); + if (toTruncateBinary in FOptions) and (i >= MaxBinaryBytes) then begin + Result := Result + ' [...]'; + exit; + end; + end; +end; + +function TIntegerTag.GetInteger(AIndex: Integer; out AValue: Integer): Boolean; +var + byteIndex: Integer; +begin + Result := false; + byteIndex := AIndex * TagElementSize[ord(FType)]; + if byteIndex + TagElementSize[ord(FType)] > Length(FRawData) then + exit; + + case FType of + ttUInt8: + AValue := FRawData[byteIndex]; + ttUInt16: + if BigEndian then + AValue := BEtoN(Word(PWord(@FRawData[byteIndex])^)) + else + AValue := LEtoN(Word(PWord(@FRawData[byteIndex])^)); + ttUInt32: + if BigEndian then + AValue := BEtoN(DWord(PDWord(@FRawData[byteIndex])^)) + else + AValue := LEtoN(DWord(PDWord(@FRawData[byteIndex])^)); + ttSInt8: + AValue := ShortInt(FRawData[byteIndex]); + ttSInt16: + if BigEndian then + AValue := SmallInt(BEtoN(Word(PWord(@FRawData[byteIndex])^))) + else + AValue := SmallInt(LEtoN(Word(PWord(@FRawData[byteIndex])^))); + ttSInt32: + if BigEndian then + AValue := LongInt(BEtoN(DWord(PDWord(@FRawData[byteIndex])^))) + else + AValue := LongInt(BEtoN(DWord(PDWord(@FRawData[byteIndex])^))); + //else + // raise EFpExif.CreateFmt('TagType not allowed for TIntegerTag "%s"', [FName]); + end; + Result := true; +end; + +procedure TIntegerTag.SetAsInteger(const AValue: Integer); +begin + FCount := 1; + SetLength(FRawData, TagElementSize[ord(FType)]); + SetInteger(0, AValue); +end; + +procedure TIntegerTag.SetAsIntegerArray(const AValue: TExifIntegerArray); +var + i: Integer; +begin + FCount := Length(AValue); + SetLength(FRawData, FCount * TagElementSize[ord(FType)]); + for i := 0 to FCount-1 do + SetInteger(i, AValue[i]); +end; + +procedure TIntegerTag.SetAsString(const AValue: String); + + function SetString(AStr: String): Integer; + var + s: String; + begin + if TryStrToInt(AStr, Result) then + exit; + s := LookupValue(AStr, FLkupTbl); + if TryStrToInt(s, Result) then + exit; + raise EFpExif.CreateFmt('Unknown value in tag "%s"', [FName]); + end; + +var + i, j, n: Integer; + s: String; + intArr: TExifIntegerArray; +begin + if AValue = '' then begin + SetLength(FRawData, 0); + exit; + end; + + n := CountChar(FListSeparator, AValue); + SetLength(intArr, n+1); + + j := 0; + s := ''; + for i := 1 to Length(AValue) do begin + if (AValue[i] = FListSeparator) then begin + intArr[j] := SetString(s); + inc(j); + s := ''; + end else + s := s + AValue[i]; + end; + + if s <> '' then + intArr[j] := SetString(s); + + SetAsIntegerArray(intArr); +end; + + +// Assumes that Length(FValue) is already set up correctly. +procedure TIntegerTag.SetInteger(const AIndex, AValue: Integer; + WithRangeCheck: Boolean = true); +var + byteIndex: Integer; + w: Word; + dw: DWord; +begin + byteIndex := AIndex * TagElementSize[ord(FType)]; + case FType of + ttUInt8: + if not WithRangeCheck or ((AValue >= 0) and (AValue <= 255)) then + FRawData[AIndex] := PByte(@AValue)^ + else + raise EFpExif.CreateFmt('Value %d out of range for tag "%s"', [AValue, FName]); + ttUInt16: + if not WithRangeCheck or ((AValue >= 0) and (AValue <= 65535)) then begin + if BigEndian then + w := NtoBE(PWord(@AValue)^) + else + w := NtoLE(PWord(@AValue)^); + Move(w, FRawData[byteIndex], SizeOf(w)); + end else + raise EFpExif.CreateFmt('Value %d out of range for tag "%s"', [AValue, FName]); + ttUInt32, + ttIFD: + if not WithRangeCheck or (AValue >= 0) then begin + if BigEndian then + dw := NtoBE(PDWord(@AValue)^) + else + dw := NtoLE(PDWord(@AValue)^); + Move(dw, FRawData[byteIndex], SizeOf(dw)); + end else + raise EFpExif.CreateFmt('Value %d out of range for tag "%s"', [AValue, FName]); + ttSInt8: + if not WithRangeCheck or ((AValue >= -128) and (AValue <= 127)) then + FRawData[AIndex] := {%H-}PByte(AValue)^ + else + raise EFpExif.CreateFmt('Value %d out of range for tag "%s"', [AValue, FName]); + ttSInt16: + if not WithRangeCheck or ((AValue >= -32768) and (AValue <= 32767)) then begin + if BigEndian then + w := NtoBE(PWord(@AValue)^) + else + w := NtoLE(PWord(@AValue)^); + Move(w, FRawData[byteIndex], SizeOf(w)); + end else + raise EFpExif.CreateFmt('Value %d out of range for tag "%s"', [AValue, FName]); + ttSInt32: + { + if not WithRangeCheck or + ((AValue >= LongInt($80000000)) and (AValue <= LongInt($7FFFFFFF))) then + } + begin + if BigEndian then + dw := NtoBE(PDWord(@AValue)^) + else + dw := NtoLE(PDWord(@AValue)^); + Move(dw, FRawData[byteIndex], SizeOf(dw)); + end; + { + else + raise EFpExif.CreateFmt('Value %d out of range for tag "%s"', [AValue, FName]); + } + else + raise EFpExif.Create('TagType not allowed for TIntegerTag'); + end; +end; + + +//============================================================================== +// TFloatTag +//============================================================================== + { +constructor TFloatTag.Create(ATagDef: TTagDef; AOptions: TTagOptions); +begin + inherited Create(ATagDef, AOptions); + FValidDigits := 6; +end; + } +function TFloatTag.GetAsFloat: Double; +begin + if not GetFloat(0, Result) then + Result := NaN +end; + +function TFloatTag.GetAsFloatArray: TExifDoubleArray; +var + i, n: Integer; +begin + n := Length(FRawData) div TagElementSize[ord(FType)]; + SetLength(Result, n); + for i := 0 to n-1 do + if not GetFloat(i, Result[i]) then begin + SetLength(Result, 0); + exit; + end; +end; + +function TFloatTag.GetAsInteger: Integer; +var + f: Double; +begin + f := GetAsFloat; + if IsNaN(f) or (frac(f) <> 0.0) then + raise EFpExif.CreateFmt('Tag "%s" has a non-integer value.', [FName]) + else + Result := Round(f); +end; + +function TFloatTag.GetAsIntegerArray: TExifIntegerArray; +var + f: TExifDoubleArray; + i: Integer; +begin + f := GetAsFloatArray; + for i:=0 to High(f) do + if IsNaN(f[i]) or (frac(f[i]) <> 0) then + raise EFpExif.CreateFmt('Tag "%s" contains non-integer values.', [FName]); + SetLength(Result, Length(f)); + for i:=0 to High(f) do + Result[i] := Round(f[i]); +end; + +function TFloatTag.GetAsRational: TExifRational; +begin + if not GetRational(0, Result) then + Result.Denominator := 0; +end; + +function TFloatTag.GetAsRationalArray: TExifRationalArray; +var + i: Integer; + n: Integer; +begin + n := Length(FRawData) div TagElementSize[ord(FType)]; + SetLength(Result, n); + for i:=0 to n-1 do + if not GetRational(i, Result[i]) then begin + SetLength(Result, 0); + exit; + end; +end; + +function TFloatTag.GetAsString: String; +var + r: TExifRational; + i: Integer; + s: String; + fval: Double; +begin + for i:=0 to Count-1 do begin + if (not GetRational(i, r)) or (r.Denominator = 0) then begin + Result := 'undef'; + exit; + end; + + fVal := r.Numerator / r.Denominator; + if IsInt(fval) then + fVal := Round(fVal); + + if FFormatStr <> '' then + s := Format(FFormatStr, [r.Numerator, r.Denominator, fval], fpExifFmtSettings) + // NOTE: FFormatStr must contain an index to the parameter, + // e.g. '%0:d/%1:d = %2:f sec' --> '1/100 = 0.01 sec' + else + if (r.Numerator = 0) then + s := '0' + else + if abs(r.Denominator) = 1 then + s := IntToStr(r.Numerator * Sign(r.Denominator)) + else + s := FloatToStr(fval, fpExifFmtSettings); + + Result := IfThen(i = 0, s, Result + FListSeparator + s); + end +end; + +function TFloatTag.GetFloat(AIndex: Integer; out AValue: Double): Boolean; +var + r: TExifRational; +begin + Result := GetRational(AIndex, r); + if Result then begin + AValue := r.Numerator / r.Denominator; + if IsInt(AValue) then + AValue := Round(AValue); + end; +end; + +function TFloatTag.GetRational(AIndex: Integer; out AValue: TExifRational): Boolean; +var + byteIndex: Integer; + num, denom: DWord; +begin + Result := false; + byteIndex := AIndex * TagElementSize[ord(FType)]; + if byteIndex + TagElementSize[ord(FType)] > Length(FRawData) then + exit; + + case FType of + ttUInt32: + begin + if BigEndian then + AValue.Numerator := BEtoN(DWord(PDWord(@FRawData[byteIndex])^)) + else + AValue.Numerator := LEtoN(DWord(PDWord(@FRawData[byteIndex])^)); + AValue.Denominator := 1; + end; + ttURational, ttSRational: + begin + if BigEndian then begin + num := BEtoN(DWord(PDWord(@FRawData[byteIndex])^)); + denom := BEToN(DWord(PDWord(@FRawData[byteIndex + 4])^)); + end else + begin + num := LEtoN(DWord(PDWord(@FRawData[byteIndex])^)); + denom := LEtoN(DWord(PDWord(@FRawData[byteIndex + 4])^)); + end; + if FType = ttURational then begin + AValue.Numerator := LongInt(num); + AValue.Denominator := LongInt(denom); + end else begin + AValue.Numerator := LongInt(num); + AValue.Denominator := LongInt(denom); + end; + end; + else + raise EFpExif.Create('TagType not allowed for TFloatTag'); + end; + Result := true; +end; + +procedure TFloatTag.InternalSetRational(AIndex: Integer; AValue: TExifRational); +const + ndw = SizeOf(DWord); +var + byteIndex: Integer; + dw: DWord; +begin + byteindex := AIndex * TagElementSize[ord(FType)]; + case FType of + ttURational, ttSRational: + begin + if BigEndian then begin + dw := NtoBE(DWord(AValue.Numerator)); + Move(dw, FRawData[byteIndex], ndw); + dw := NtoBE(DWord(AValue.Denominator)); + Move(dw, FRawData[byteIndex + ndw], ndw); + end else + begin + dw := NtoLE(DWord(AValue.Numerator)); + Move(dw, FRawData[byteIndex], ndw); + dw := NtoLE(DWord(AValue.Denominator)); + Move(dw, FRawData[byteIndex + ndw], ndw); + end; + end; + else + raise EFpExif.CreateFmt('TagType not allowed for TFloatTag "%s"', [FName]); + end; +end; + +function TFloatTag.IsInt(AValue: Double): Boolean; +const + EPS = 1E-6; +begin + Result := abs(AValue - round(AValue)) < EPS; +end; + +procedure TFloatTag.SetAsFloat(const AValue: Double); +begin + FCount := 1; + SetLength(FRawData, SizeOf(TExifRational)); + SetFloat(0, AValue); +end; + +procedure TFloatTag.SetAsFloatArray(const AValue: TExifDoubleArray); +var + i: Integer; +begin + FCount := Length(AValue); + SetLength(FRawData, Length(AValue) * TagElementSize[ord(FType)]); + for i:=0 to FCount-1 do + SetFloat(i, AValue[i]); +end; + +procedure TFloatTag.SetAsInteger(const AValue: Integer); +var + r: TExifRational; +begin + FCount := 1; + SetLength(FRawData, SizeOf(TExifRational)); + r.Numerator := AValue; + r.Denominator := 1; + SetRational(0, r); +end; + +procedure TFloatTag.SetAsIntegerArray(const AValue: TExifIntegerArray); +var + i: Integer; + r: TExifRational; +begin + FCount := Length(AValue); + SetLength(FRawData, Length(AValue) * TagElementSize[ord(FType)]); + for i:=0 to FCount-1 do begin + r.Numerator := AValue[i]; + r.Denominator := 1; + SetRational(i, r); + end; +end; + +procedure TFloatTag.SetAsRational(const AValue: TExifRational); +begin + FCount := 1; + SetLength(FRawData, SizeOf(TExifRational)); + SetRational(0, AValue); +end; + +procedure TFloatTag.SetAsRationalArray(const AValue: TExifRationalArray); +var + i: Integer; +begin + FCount := Length(AValue); + SetLength(FRawData, Length(AValue) * TagElementSize[ord(FType)]); + for i:=0 to FCount-1 do + SetRational(i, AValue[i]); +end; + +procedure TFloatTag.SetAsString(const AValue: String); + + function SetString(AStr: String): Double; + var + p: Integer; + sNum, sDenom: String; + code: Integer; + begin + p := pos('/', AStr); + if p <> 0 then begin + sNum := Copy(AStr, 1, p-1); + sDenom := Copy(AStr, p+1, MaxInt); + Result := StrToInt(sNum) / StrToInt(sDenom); + end else + val(AStr, Result, code); + end; + +var + i, j, n: Integer; + s: String; + floatArr: TExifDoubleArray; +begin + if AValue = '' then begin + SetLength(FRawData, 0); + exit; + end; + + n := CountChar(FListSeparator, AValue); + SetLength(floatArr, n+1); + + s := ''; + j := 0; + for i:=1 to Length(AValue) do begin + if AValue[i] = FListSeparator then begin + floatArr[j] := SetString(s); + inc(j); + s := ''; + end else + if AValue[i] in ['0'..'9', '+', '-', '.', '/', 'e', 'E'] then + s := s + AValue[i] + else + if AValue[i] = ',' then + s := s + '.'; + end; + + if s <> '' then + floatArr[j] := SetString(s); + + SetAsFloatArray(floatArr); +end; + +procedure TFloatTag.SetFloat(AIndex: Integer; const AValue: Double); +var + r: TExifRational; +begin + if (AValue < 0) and (FType = ttURational) then + raise EFpExif.Create('No negative values for unsigned-rational tags.'); + + r := FloatToRational(AValue, 1E-9); + InternalSetRational(AIndex, r); +end; + +procedure TFloatTag.SetRational(AIndex: Integer; const AValue: TExifRational); +begin + InternalSetRational(AIndex, AValue); +end; + + +//============================================================================== +// TStringTag +//============================================================================== + +constructor TStringTag.Create(ATagDef: TTagDef; AOptions: TTagOptions); +begin + inherited Create(ATagDef, AOptions); + FLkupTbl := ATagDef.LkupTbl; +end; + +function TStringTag.GetAsString: String; +var + sa: ansistring; +begin + // FIXME: The next lines assume that FValue stores a string as ansistring + // which is true only for Exif, probably not for IPTC and XMP. + // Not sure what Delphi does when a unicodestring is put into FValue. + + Result := ''; + SetLength(sa, Length(FRawData)); + Move(FRawData[0], sa[1], Length(FRawData)); + while (sa <> '') and (sa[Length(sa)] = #0) do + Delete(sa, Length(sa), 1); + + Result := sa; + + if (FLkUpTbl <> '') and DecodeValue then + Result := Lookup(Result, FLkupTbl, @SameStringFunc); +end; + +procedure TStringTag.SetAsString(const AValue: String); +var + sa: Ansistring; +begin + if AValue = '' then + SetLength(FRawData, 0) + else + begin + sa := ansistring(AValue); + FCount := Length(sa); + SetLength(FRawData, FCount); + Move(sa[1], FRawData[0], FCount); + end; +end; + + +//============================================================================== +// TBinaryTag +//============================================================================== + +function TBinaryTag.GetAsString: String; +var + i: Integer; + mx: Integer; + isTruncated: Boolean; + s: String; + intVal: Integer; + + function MakeASCII(b: Byte): char; + begin + if (b >=32) and (b < 128) then + Result := Char(b) + else + Result := '.'; + end; + +begin + Result := ''; + if Length(FRawData) = 0 then + exit; + + mx := High(FRawData); + if (toTruncateBinary in FOptions) and (MaxBinaryBytes < Length(FRawData)) then + begin + mx := MaxBinaryBytes - 1; + isTruncated := true; + end else + isTruncated := false; + + if (toBinaryAsASCII in FOptions) then begin + for i:= 0 to mx do + Result := Result + MakeASCII(FRawData[i]); + end else + begin + for i := 0 to mx do begin + intVal := FRawData[i]; + if (FLkupTbl <> '') and (toDecodeValue in FOptions) then + s := Lookup(IntToStr(intVal), FLkupTbl, @SameIntegerFunc) + else + s := IntToStr(intVal); + Result := IfThen(i = 0, s, Result + FListSeparator + s); + end; + end; + + if isTruncated then + result := Result + FListSeparator + ' [...]'; +end; + +procedure TBinaryTag.SetAsString(const AValue: String); +var + L: TStringList; + i, n: Integer; +begin + if AValue = '' then begin + SetLength(FRawData, 0); + exit; + end; + + L := TStringList.Create; + try + L.Delimiter := FListSeparator; + L.DelimitedText := AValue; + FCount := L.Count; + SetLength(FRawData, FCount); + for i:=0 to L.Count-1 do + if TryStrToInt(L[i], n) and (n >= 0) and (n <= 255) then + FRawData[i] := byte(n) + else begin + SetLength(FRawData, 0); + FCount := 0; + end; + finally + L.Free; + end; +end; + + +//============================================================================== +// TSubIFTag +//============================================================================== +constructor TSubIFDTag.Create(ATagDef: TTagDef; AOptions: TTagOptions); +begin + inherited Create(ATagDef, AOptions + [toReadOnly]); + // The data value of the SubIFDTag is determined later during saving. + // The default constructor creates the tag without data (Length(FRawData) = 0). + // This would prevent the tag from being written to file. Therefore, we must + // set a dummy value. + AsInteger := 0; +end; + +//============================================================================== +// TTagList +//============================================================================== + +function TTagList.GetGroupOfTag(ATagID: TTagID): TTagGroup; +var + idx: Integer; + tagIDRec: TTagIDRec absolute ATagID; + tag: TTag; +begin + Result := tgUnknown; + case tagIDRec.Parent of + $0000 : ; + $0001 : Result := tgExifPrimary; + $0002 : Result := tgExifThumbnail; + TAG_GPS_OFFSET : Result := tgExifGPS; + TAG_INTEROP_OFFSET : Result := tgExifInterOp; + TAG_EXIF_OFFSET : Result := tgExifSub; + TAG_MAKERNOTE : Result := tgExifMakerNote + else + idx := IndexOfParentByID(ATagID); + if idx = -1 then + exit; + tag := GetItem(idx); + Result := GetGroupOfTag(tag.TagID); + end; +end; + +function TTagList.GetItem(AIndex: Integer): TTag; +begin + Result := TTag(inherited Items[AIndex]); +end; + +{ Finds the index of the tag which is the parent of the tag with the specified ID } +function TTagList.IndexOfParentByID(ATagID: TTagID): Integer; +var + tag: TTag; +begin + for Result := 0 to Count - 1 do begin + tag := GetItem(Result); + if TTagIDRec(tag.TagID).Tag = TTagIDRec(ATagID).Parent then + exit; + end; + Result := -1; +end; + +{ Finds the index of the tag which has the specified TagID } +function TTagList.IndexOfTagByID(ATagID: TTagID): Integer; +var + tag: TTag; +begin + for Result := 0 to Count - 1 do begin + tag := GetItem(Result); + if tag.TagID = ATagID then + exit; + end; + Result := -1; +end; + +procedure TTagList.SetItem(AIndex: Integer; const AValue: TTag); +begin + inherited Items[AIndex] := AValue; +end; + + +//============================================================================== +// Tag groups +//============================================================================== + +(* +{ Determines the tag group which is generated by the specified tag. + Example: ATagID = TAG_EXIF_OFFSET --> Result = tgExifSub } +function GetGroupFromGeneratingTagID(ATagID: TTagID): TTagGroup; +begin + for Result := Low(TTagGroup) to High(TTagGroup) do + if TagsOfGroups[Result].TagID = ATagID then + exit; + Result := tgUnknown; +end; +*) + (* +{ Returns true if the specified tag links to another image file directory (IFD). + Example: Tag $8769 (= TAG_EXIF_OFFSET) links to the EXIF-SubIFD. } +function IsGeneratingTag(ATagID: TTagID): Boolean; +begin + Result := GetGroupFromGeneratingTagID(ATagID) <> tgUnknown; +end; *) + + (* +//============================================================================== +// Tags which link to subdirectories (SubIFD) +//============================================================================== +var + SubIFDTags: Array of TTagID; + +function TagLinksToSubIFD(ATagID: TTagID): Boolean; +var + i: Integer; +begin + for i:=0 to High(SubIFDTags) do + if SubIFDTags[i] = ATagID then begin + Result := true; + exit; + end; + Result := false; +end; + +procedure RegisterSubIFDTag(ATagID: TTagID); +var + n: Integer; +begin + // Ignore if new tag is already registered; + if TagLinksToSubIFD(ATagID) then + exit; + + n := Length(SubIFDTags); + SetLength(SubIFDTags, n + 1); + SubIFDTags[n] := ATagID; +end; + + +initialization + SetLength(SubIFDTags, 3); + SubIFDTags[0] := TAG_EXIF_OFFSET; + SubIFDTags[1] := TAG_INTEROP_OFFSET; + SubIFDTags[2] := TAG_GPS_OFFSET; + +finalization + SubIFDTags := nil; +*) +end. + diff --git a/components/fpexif/fpeutils.pas b/components/fpexif/fpeutils.pas new file mode 100644 index 000000000..ff9e0308c --- /dev/null +++ b/components/fpexif/fpeutils.pas @@ -0,0 +1,1451 @@ +unit fpeUtils; + +{$IFDEF FPC} + {$mode ObjFPC}{$H+} +{$ENDIF} + +{$I fpExif.inc} + +interface + +uses + Classes, SysUtils, +{$IFDEF FPC} + fgl, +{$ELSE} + Windows, + {$IFNDEF dExifNoJpeg}Graphics, jpeg,{$ENDIF} +{$ENDIF} + fpeGlobal; + +type + {$IFDEF FPC} + TInt64List = specialize TFPGList; + {$ELSE} + TInt64List = class(TList) + private + function GetItem(AIndex: Integer): Int64; + procedure SetItem(AIndex: Integer; AValue: Int64); + public + destructor Destroy; override; + function Add(AValue: Int64): Integer; + procedure Clear; override; + property Items[AIndex: Integer]: Int64 read GetItem write SetItem; default; + end; + + TStringArray = array of string; + {$ENDIF} + + // Big endian/little endian utilities +function BEtoN(const AValue: WideString): WideString; overload; +function LEtoN(const AValue: WideString): WideString; overload; +function NtoBE(const AValue: WideString): WideString; overload; +function NtoLE(const AValue: WideString): WideString; overload; +{$IFNDEF FPC} +function NtoBE(const AValue: Word): Word; overload; +function NtoBE(const AValue: DWord): DWord; overload; + +function BEtoN(const AValue: Word): Word; overload; +function BEtoN(const AValue: DWord): DWord; overload; + +function NtoLE(const AValue: Word): Word; overload; +function NtoLE(const AValue: DWord): DWord; overload; + +function LEtoN(const AValue: Word): Word; overload; +function LEtoN(const AValue: DWord): DWord; overload; +{$ENDIF} + +// Delphi7 compatible stream access +function ReadByte(AStream: TStream): Byte; +function ReadWord(AStream: TStream): Word; +function ReadDWord(AStream: TStream): DWord; + +procedure WriteByte(AStream: TStream; AData: Byte); +procedure WriteWord(AStream: TStream; AData: Word); +procedure WriteDWord(AStream: TStream; AData: DWord); + +// GPS utils +{ +//function ExtractGPSPosition(const AValue: String; +// out ADeg, AMin, ASec: Double): Boolean; +} +procedure SplitGps(AValue: Double; out ADegs, AMins, ASecs: Double); overload; +procedure SplitGps(AValue: Double; out ADegs, AMins: Double); overload; +function TryStrToGps(const AValue: String; out ADeg: Double): Boolean; +{ +function GPSToStr(ACoord: Extended; ACoordType: TGpsCoordType; + AGpsFormat: TGpsFormat = gf_DMS_Short; ADecs: Integer = 0): String; +function StrToGPS(s: String): Extended; } + + +// String utils +function CountChar(AChar: Char; const AText: String): Integer; +function FirstWord(const AText: String): String; +function InsertSpaces(ACamelCaseText: String): String; +function LettersOnly(const AText: String): String; +function LookupValue(const AKey, ALookupTbl: String; ACompareFunc: TLookupCompareFunc): String; +function LookupKey(const AValue, ALookupTbl: String; ACompareFunc: TLookupCompareFunc): String; +function NumericOnly(const AText: String): String; +function Split(AText: String; ASeparator: String = #9): TStringArray; +{$IFNDEF FPC} +{$IFNDEF UNICODE} +function UTF8ToAnsi(const S: Ansistring): string; +{$ENDIF} +{$ENDIF} + +// Math utils +function FloatToRational(Value, Precision: Double): TExifRational; +function TryStrToRational(const AStr: String; out AValue: TExifRational): Boolean; +function StrToRational(const AStr: String): TExifRational; +//function GCD(a, b: integer): integer; + +// Image utils +function JPEGImageSize(AStream: TStream; out AWidth, AHeight: Integer): Boolean; +procedure JPEGScaleImage(ASrcStream, ADestStream: TStream; + ADestSize: Integer = DEFAULT_THUMBNAIL_SIZE); + +// Buffer utils +function PosInBytes(AText: ansistring; ABuffer: TBytes): Integer; + +// Date/time utils +function LocalTimeZoneStr: String; +function IPTCDateStrToDate(AValue: String): TDateTime; +function IPTCTimeStrToTime(AValue: String): TDateTime; + +{ For silencing the compiler... } +procedure Unused(const A1); +procedure Unused(const A1, A2); +procedure Unused(const A1, A2, A3); + + +implementation + +uses +{$IFDEF FPC} + fpreadjpeg, fpwritejpeg, fpimage, fpcanvas, fpimgcanv, +{$ELSE} +// EncdDecd, +{$ENDIF} + Math, DateUtils, + fpeStrConsts; + +{$IFNDEF FPC} +//------------------------------------------------------------------------------ +// Helper class: TInt64List - a list for 64-bit integers +//------------------------------------------------------------------------------ +type + TInt64 = record Value: Int64; end; + PInt64 = ^TInt64; + +destructor TInt64List.Destroy; +begin + Clear; + inherited; +end; + +procedure TInt64List.Clear; +var + i: Integer; + P: PInt64; +begin + for i:=0 to Count-1 do begin + P := inherited Items[i]; + Dispose(P); + end; + inherited Clear; +end; + +function TInt64List.Add(AValue: Int64): Integer; +var + P: PInt64; +begin + New(P); + P^.Value := AValue; + Result := inherited Add(P); +end; + +function TInt64List.GetItem(AIndex: Integer): Int64; +begin + Result := PInt64(inherited Items[AIndex])^.Value; +end; + +procedure TInt64List.SetItem(AIndex: Integer; AValue: Int64); +var + p: PInt64; +begin + p := inherited Items[AIndex]; + p^.Value := AValue; +end; + +{$IFNDEF UNICODE} +function UTF8ToWideString(const S: AnsiString): WideString; +var + BufSize: Integer; +begin + Result := ''; + if Length(S) = 0 then Exit; + BufSize := MultiByteToWideChar(CP_UTF8, 0, PAnsiChar(S), Length(S), nil, 0); + SetLength(result, BufSize); + MultiByteToWideChar(CP_UTF8, 0, PANsiChar(S), Length(S), PWideChar(Result), BufSize); +end; + +function UTF8ToAnsi(const S: Ansistring): string; +begin + Result := UTF8ToWideString(S); +end; +{$ENDIF} + +function SwapEndian(const AValue: Word): Word; overload; +begin + Result := Word((AValue shr 8) or (AValue shl 8)); +end; + +function SwapEndian(const AValue: DWord): DWord; overload; +begin + Result := ((AValue shl 8) and $FF00FF00) or ((AValue shr 8) and $00FF00FF); + Result := (Result shl 16) or (Result shr 16); +end; + +function BEtoN(const AValue: Word): Word; +begin + {$IFDEF ENDIAN_BIG} + Result := AValue; + {$ELSE} + Result := SwapEndian(AValue); + {$ENDIF} +end; + +function BEtoN(const AValue: DWord): DWord; +begin + {$IFDEF ENDIAN_BIG} + Result := AValue; + {$ELSE} + Result := SwapEndian(AValue); + {$ENDIF} +end; + +function NtoBE(const AValue: Word): Word; +begin + {$IFDEF ENDIAN_BIG} + Result := AValue; + {$ELSE} + Result := SwapEndian(AValue); + {$ENDIF} +end; + +function NtoBE(const AValue: DWord): DWord; +begin + {$IFDEF ENDIAN_BIG} + Result := AValue; + {$ELSE} + Result := SwapEndian(AValue); + {$ENDIF} +end; + + +function LEtoN(const AValue: Word): Word; +begin + {$IFDEF ENDIAN_BIG} + Result := SwapEndian(AValue); + {$ELSE} + Result := AValue; + {$ENDIF} +end; + +function LEtoN(const AValue: DWord): DWord; +begin + {$IFDEF ENDIAN_BIG} + Result := SwapEndian(AValue); + {$ELSE} + Result := AValue; + {$ENDIF} +end; + +function NtoLE(const AValue: Word): Word; +begin + {$IFDEF ENDIAN_BIG} + Result := SwapEndian(AValue); + {$ELSE} + Result := AValue; + {$ENDIF} +end; + +function NtoLE(const AValue: DWord): DWord; +begin + {$IFDEF ENDIAN_BIG} + Result := SwapEndian(AValue); + {$ELSE} + Result := AValue; + {$ENDIF} +end; + +{$ENDIF} + +function BEtoN(const AValue: WideString): WideString; +{$IFNDEF ENDIAN_BIG} +var + i: Integer; +{$ENDIF} +begin + {$IFDEF ENDIAN_BIG} + Result := AValue; + {$ELSE} + SetLength(Result, Length(AValue)); + for i:=1 to Length(AValue) do + Result[i] := WideChar(BEToN(PDWord(@AValue[i])^)); + {$ENDIF} +end; + +function LEtoN(const AValue: WideString): WideString; +{$IFDEF ENDIAN_BIG} +var + i: Integer; +{$ENDIF} +begin + {$IFDEF ENDIAN_BIG} + SetLength(Result, Length(AValue)); + for i:=1 to Length(AValue) do + Result[i] := WideChar(LEToN(PDWord(@AValue[i])^)); + {$ELSE} + Result := AValue; + {$ENDIF} +end; + +function NtoBE(const AValue: WideString): WideString; +var + i: Integer; +begin + {$IFDEF ENDIAN_BIG} + Result := AValue; + {$ELSE} + SetLength(Result, Length(AValue)); + for i:=1 to Length(AValue) do + Result[i] := WideChar(NtoBE(PDWord(@AValue[i])^)); + {$ENDIF} +end; + +function NtoLE(const AValue: WideString): WideString; +{$IFDEF ENDIAN_BIG} +var + i: Integer; +{$ENDIF} +begin + {$IFDEF ENDIAN_BIG} + SetLength(Result, Length(AValue)); + for i:=1 to Length(AValue) do + Result[i] := WideChar(NtoLE(PDWord(@AValue[i])^)); + {$ELSE} + Result := AValue; + {$ENDIF} +end; + +{ A simple Delphi-7 compatible way of reading a byte from a stream } +function ReadByte(AStream: TStream): Byte; +begin + AStream.Read(Result{%H-}, 1); +end; + +{ A simple Delphi-7 compatible way of reading two bytes from a stream } +function ReadWord(AStream: TStream): Word; +begin + AStream.Read(Result{%H-}, 2); +end; + +{ A simple Delphi-7 compatible way of reading four bytes from a stream } +function ReadDWord(AStream: TStream): DWord; +begin + AStream.Read(Result{%H-}, 4); +end; + +{ A simple Delphi-7 compatible way of writing a byte to a stream } +procedure WriteByte(AStream: TStream; AData: Byte); +begin + AStream.Write(AData, 1); +end; + +{ A simple Delphi-7 compatible way of writing two bytex to a stream } +procedure WriteWord(AStream: TStream; AData: Word); +begin + AStream.Write(AData, 2); +end; + +{ A simple Delphi-7 compatible way of writing four bytes to a stream } +procedure WriteDWord(AStream: TStream; AData: DWord); +begin + AStream.Write(AData, 4); +end; + +//============================================================================== +// GPS Utilities +//============================================================================== + (* +function ExtractGPSPosition(const AValue: String; + out ADeg, AMin, ASec: Double): Boolean; +const + NUMERIC_CHARS = ['0'..'9', '.', ',']; //, '-', '+']; +var + p, p0: PChar; + n: Integer; + s: String; + res: Integer; +begin + Result := false; + + ADeg := NaN; + AMin := NaN; + ASec := NaN; + + if AValue = '' then + exit; + + // skip leading non-numeric characters + p := @AValue[1]; + while (p <> nil) and not (p^ in NUMERIC_CHARS) do + inc(p); + + // extract first value: degrees + p0 := p; + n := 0; + while (p <> nil) and (p^ in NUMERIC_CHARS) do begin + if p^ = ',' then p^ := '.'; + inc(p); + inc(n); + end; + SetLength(s, n); + Move(p0^, s[1], n*SizeOf(Char)); + val(s, ADeg, res); + if res <> 0 then + exit; + + // skip non-numeric characters between degrees and minutes + while (p <> nil) and not (p^ in NUMERIC_CHARS) do + inc(p); + + // extract second value: minutes + p0 := p; + n := 0; + while (p <> nil) and (p^ in NUMERIC_CHARS) do begin + if p^ = ',' then p^ := '.'; + inc(p); + inc(n); + end; + SetLength(s, n); + Move(p0^, s[1], n*SizeOf(Char)); + val(s, AMin, res); + if res <> 0 then + exit; + + // skip non-numeric characters between minutes and seconds + while (p <> nil) and not (p^ in NUMERIC_CHARS) do + inc(p); + + // extract third value: seconds + p0 := p; + n := 0; + while (p <> nil) and (p^ in NUMERIC_CHARS) do begin + if p^ = ',' then p^ := '.'; + inc(p); + inc(n); + end; + SetLength(s, n); + Move(p0^, s[1], n*SizeOf(Char)); + val(s, ASec, res); + if res <> 0 then + exit; + + Result := (AMin >= 0) and (AMin < 60) and (ASec >= 0) and (ASec < 60); +end; *) + +procedure SplitGps(AValue: Double; out ADegs, AMins, ASecs: Double); +begin + SplitGps(AValue, ADegs, AMins); + ASecs := frac(AMins) * 60; + AMins := trunc(AMins); +end; + +procedure SplitGps(AValue: Double; out ADegs, AMins: Double); +begin + AValue := abs(AValue); + AMins := frac(AValue) * 60; + ADegs := trunc(AValue); +end; + +{ Combines up to three parts a GPS coordinate string (degrees, minutes, seconds) + to a floating-point degree value. The parts are separated by non-numeric + characters: + + three parts ---> d m s ---> d and m must be integer, s can be float + two parts ---> d m ---> d must be integer, s can be float + one part ---> d ---> d can be float + + Each part can exhibit a unit identifier, such as °, ', or ". BUT: they are + ignored. This means that an input string 50°30" results in the output value 50.5 + although the second part is marked as seconds, not minutes! } +function TryStrToGps(const AValue: String; out ADeg: Double): Boolean; +const + NUMERIC_CHARS = ['0'..'9', '.', ',', '-', '+']; +var + mins, secs: Double; + i, j, len: Integer; + n: Integer; + s: String; + res: Integer; +begin + Result := false; + + ADeg := NaN; + mins := 0; + secs := 0; + + if AValue = '' then + exit; + + // skip leading non-numeric characters + len := Length(AValue); + i := 1; + while (i <= len) and not (AValue[i] in NUMERIC_CHARS) do + inc(i); + + // extract first value: degrees + SetLength(s, len); + j := 1; + n := 0; + while (i <= len) and (AValue[i] in NUMERIC_CHARS) do begin + if AValue[i] = ',' then s[j] := '.' else s[j] := AValue[i]; + inc(i); + inc(j); + inc(n); + end; + if n > 0 then begin + SetLength(s, n); + val(s, ADeg, res); + if res <> 0 then + exit; + end; + + // skip non-numeric characters between degrees and minutes + while (i <= len) and not (AValue[i] in NUMERIC_CHARS) do + inc(i); + + // extract second value: minutes + SetLength(s, len); + j := 1; + n := 0; + while (i <= len) and (AValue[i] in NUMERIC_CHARS) do begin + if AValue[i] = ',' then s[j] := '.' else s[j] := AValue[i]; + inc(i); + inc(j); + inc(n); + end; + if n > 0 then begin + SetLength(s, n); + val(s, mins, res); + if (res <> 0) or (mins < 0) then + exit; + end; + + // skip non-numeric characters between minutes and seconds + while (i <= len) and not (AValue[i] in NUMERIC_CHARS) do + inc(i); + + // extract third value: seconds + SetLength(s, len); + j := 1; + n := 0; + while (i <= len) and (AValue[i] in NUMERIC_CHARS) do begin + if AValue[i] = ',' then s[j] := '.' else s[j] := AValue[i]; + inc(i); + inc(j); + inc(n); + end; + if n > 0 then begin + SetLength(s, n); + val(s, secs, res); + if (res <> 0) or (secs < 0) then + exit; + end; + + // If the string contains seconds then minutes and deegrees must be integers + if (secs <> 0) and ((frac(ADeg) > 0) or (frac(mins) > 0)) then + exit; + // If the string does not contain seconds then degrees must be integer. + if (secs = 0) and (mins <> 0) and (frac(ADeg) > 0) then + exit; + + // If the string contains minutes, but no seconds, then the degrees must be integer. + Result := (mins >= 0) and (mins < 60) and (secs >= 0) and (secs < 60); + + // A similar check should be made for the degrees range, but since this is + // different for latitude and longitude the check is skipped here. + if Result then + ADeg := abs(ADeg) + mins / 60 + secs / 3600; +end; + + (* +{ Converts a GPS coordinate (extended data type) to a string } +function GPSToStr(ACoord: Extended; ACoordType: TGpsCoordType; + AGpsFormat: TGpsFormat = gf_DMS_Short; ADecs: Integer = 0): String; +const + {$IFDEF FPC} + DEG_SYMBOL: string = '°'; + {$ELSE} + DEG_SYMBOL: ansistring = #176; + // Delphi 7 wants the degree symbol in ANSI, newer versions will convert + // it to a widechar automatically. + {$ENDIF} + RefStr: array[TGpsCoordType] of String[2] = ('NS', 'EW'); +var + idegs, imins: Integer; + floatval: Extended; + sgn: String; +begin + if IsNaN(ACoord) then begin + Result := ''; + exit; + end; + sgn := RefStr[ACoordType][1 + ord(ACoord < 0)]; + ACoord := abs(ACoord); + case AGpsFormat of + gf_DD, gf_DD_Short : + case AGpsFormat of + gf_DD: + Result := Format('%.*f degrees', [ADecs, ACoord], fpExifFmtSettings); + gf_DD_Short: + Result := Format('%.*f%s', [ADecs, ACoord, DEG_SYMBOL], fpExifFmtSettings); + end; + gf_DM, gf_DM_Short: + begin + idegs := trunc(ACoord); + floatVal := frac(ACoord) * 60; + case AGpsFormat of + gf_DM: + Result := Format('%d degrees %.*f minutes', + [idegs, ADecs, floatVal], fpExifFmtSettings); + gf_DM_Short: + Result := Format('%d%s %.*f''', + [idegs, DEG_SYMBOL, ADecs, floatVal], fpExifFmtSettings); + end; + end; + gf_DMS, gf_DMS_Short: + begin + idegs := trunc(ACoord); + imins := trunc(frac(ACoord)*60); + floatVal := frac(frac(ACoord)*60)*60; // seconds + case AGpsFormat of + gf_DMS: + Result := Format('%d degrees %d minutes %.*f seconds', + [idegs, imins, ADecs, floatVal], fpExifFmtSettings); + gf_DMS_Short: + Result := Format('%d%s %d'' %.*f"', + [idegs, DEG_SYMBOL, imins, ADecs, floatVal], fpExifFmtSettings); + end; + end; + end; + + Result := Result + ' ' + sgn; +end; + +{ Converts a string to a GPS extended number. The input string s must be + formatted as dd° mm' ss[.zzz]" E|W. Decimal places of seconds are optional. + Instead of seconds, the string can also contain a fractional part for minutes, + e.g. dd° m.mmmmmm', or for degress: d.ddddd° + E|W means: either E or W. } +function StrToGPS(s: String): Extended; +var + ds, ms, ss: String; + i: Integer; + tmp: String; + degs, mins, secs: Extended; + res: Integer; + scannedPart: Integer; // 0=degrees, 1=minutes, 2=seconds + isFloat: Array[-1..2] of Boolean; + sgn: Integer; +begin + if s = '' then begin + Result := NaN; + exit; + end; + i := 1; + tmp := ''; + scannedPart := 0; + isFloat[0] := false; + isFloat[1] := false; + isFloat[2] := false; + degs := 0; + mins := 0; + secs := 0; + sgn := +1; + while i <= Length(s) do begin + case s[i] of + '0'..'9': + tmp := tmp + s[i]; + '.', ',': + begin + tmp := tmp + '.'; + isFloat[scannedPart] := true; + end; + ' ': + if scannedPart = 0 then begin // in degrees par + val(tmp, degs, res); + if res > 0 then + raise EFpExif.Create('No numeric data in gps coordinate.'); + tmp := ''; + scannedPart := 1; + end; + '''': + if not isFloat[0] then begin // ignore minutes and seconds if degrees are floats + val(tmp, mins, res); + if res > 0 then + raise EFpExif.Create('No numeric data in gps coordinate.'); + tmp := ''; + scannedPart := 2; + end; + '"': + // ignore seconds of degrees or minutes are floating point values + if not (isFloat[0] or isFloat[1]) then begin + val(tmp, secs, res); + if res > 0 then + raise EFpExif.Create('No numerical data in gps coordinate.'); + tmp := ''; + scannedPart := -1; + end; + 'W', 'w', 'S', 's': + sgn := -1; + end; + inc(i); + end; + Result := (degs + mins/60 + secs/3600) * sgn; +end; + *) + + +//============================================================================== +// Image file utilities +//============================================================================== + +{ Extracts the width and height of a JPEG image from its data without loading + it into a TJpegImage. + Returns false if the stream does not contain a jpeg image. } +function JPEGImageSize(AStream: TStream; out AWidth, AHeight: Integer): Boolean; +type + TJPGHeader = array[0..1] of Byte; //FFD8 = StartOfImage (SOI) + TJPGRecord = packed record + Marker: Byte; + RecType: Byte; + RecSize: Word; + end; +var + n: integer; + hdr: TJPGHeader; + rec: TJPGRecord; + p: Int64; + savedPos: Int64; +begin + Result := false; + + AWidth := 0; + AHeight := 0; + + savedPos := AStream.Position; + try + // Check for SOI (start of image) record + n := AStream.Read(hdr{%H-}, SizeOf(hdr)); + if (n < SizeOf(hdr)) or (hdr[0] <> $FF) or (hdr[1] <> $D8) then + exit; + + rec.Marker := $FF; + while (AStream.Position < AStream.Size) and (rec.Marker = $FF) do begin + if AStream.Read(rec, SizeOf(rec)) < SizeOf(rec) then + exit; + rec.RecSize := BEToN(rec.RecSize); + p := AStream.Position - 2; + case rec.RecType of + $C0..$C3: + if (rec.RecSize >= 4) then // Start of frame markers + begin + AStream.Seek(1, soFromCurrent); // Skip "bits per sample" + AHeight := BEToN(ReadWord(AStream)); + AWidth := BEToN(ReadWord(AStream)); + Result := true; + exit; + end; + $D9: // end of image; + break; + end; + AStream.Position := p + rec.RecSize; + end; + finally + AStream.Position := savedPos; + end; +end; + +procedure JPEGScaleImage(ASrcStream, ADestStream: TStream; + ADestSize: Integer = DEFAULT_THUMBNAIL_SIZE); +{$IFDEF FPC} +var + srcImage, destImage: TFPCustomImage; + destCanvas: TFPImageCanvas; + reader: TFPCustomImageReader; + writer: TFPCustomImageWriter; + w, h: Integer; + f: Double; +begin + srcImage := TFPMemoryImage.Create(10, 10); + reader := TFPReaderJPEG.Create; + srcImage.LoadFromStream(ASrcStream, reader); + reader.Free; + + w := srcImage.Width; + h := srcImage.Height; + if w > h then f := ADestSize / w else f := ADestSize / h; + + destImage := TFPMemoryImage.Create(round(w*f), round(h*f)); + destCanvas := TFPImageCanvas.Create(destImage); + destCanvas.StretchDraw(0, 0, destImage.Width, destImage.Height, srcImage); + + writer := TFPWriterJPEG.Create; + destImage.SaveToStream(ADestStream, writer); + writer.Free; +end; +{$ELSE} +{$IFNDEF dExifNoJpeg} +var + jpeg: TJPegImage; + bmp: TBitmap; + w, h: Integer; + f: Double; +begin + jpeg := TJpegImage.Create; + try + jpeg.LoadfromStream(ASrcStream); + w := jpeg.Width; + h := jpeg.Height; + if w > h then f := ADestSize / w else f := ADestSize / h; + bmp := TBitmap.Create; + bmp.PixelFormat := pf24bit; + bmp.Width := round(w * f); + bmp.Height := round(h * f); + bmp.Canvas.StretchDraw(Rect(0, 0, bmp.Width, bmp.Height), jpeg); + jpeg.Free; + jpeg := TJpegImage.Create; + jpeg.Assign(bmp); + jpeg.SaveToStream(ADestStream); + finally + jpeg.Free; + bmp.Free; + end; +end; +{$ELSE} +begin + // CreateThumb will not work in delphi if dExifNoJpeg is defined. +end; +{$ENDIF} +{$ENDIF} + + +{ Formatting callbacks } + (* +Function GpsPosn(InStr: String): String; +const + {$IFDEF FPC} + DEGREES: string = '°'; + {$ELSE} + DEGREES: ansistring = #176; + {$ENDIF} +var + p, sl: integer; + s: string; + gDegree, gMin, gSec: double; +begin + sl := length(fpExifDataSep); + Result := instr; // if error return input string + p := Pos(fpExifDataSep, instr); + s := copy(InStr, 1, p-1); // get first irrational number + gDegree := CvtRational(s); // degrees + InStr := copy(InStr, p+sl, 64); + p := Pos(fpExifDataSep, instr); + s := copy(InStr, 1, p-1); // get second irrational number + gMin := CvtRational(s); // minutes + InStr := copy(InStr, p+sl, 64); + gSec := CvtRational(InStr); // seconds + if gSec = 0 then // camera encoded as decimal minutes + begin + gSec := ((gMin - trunc(gMin))*100); // seconds as a fraction of degrees + gSec := gSec * 0.6; // convert to seconds + gMin := trunc(gMin); // minutes is whole portion + end; + // Ok we'll send the result back as Degrees with + // Decimal Minutes. Alternatively send back as Degree + // Minutes, Seconds or Decimal Degrees. + case GpsFormat of + gf_DD: + Result := Format('%1.4f Decimal Degrees', [gDegree + (gMin + gSec/60)/60], fpExifFmtSettings); + gf_DD_Short: + Result := Format('%1.4f%s', [gDegree + (gmin + gSec/60)/60, DEGREES], fpExifFmtSettings); + gf_DM: + Result := Format('%0.0f Degrees %1.2f Minutes',[gDegree, gMin + gsec/60], fpExifFmtSettings); + gf_DM_Short: + Result := Format('%0.0f%s %1.2f''', [gDegree, DEGREES, gMin + gsec/60], fpExifFmtSettings); + gf_DMS: + Result := Format('%0.0f Degrees %0.0f Minutes %0.2f Seconds', [gDegree, gMin, gSec], fpExifFmtSettings); + gf_DMS_Short: + Result := Format('%0.0f%s %0.0f'' %0.2f"', [gDegree, DEGREES, gMin, gSec], fpExifFmtSettings); + end; +end; + +function GpsAltitude(InStr: string): String; +var + gAltitude: double; +begin + Result := InStr; // if error return input string + gAltitude := CvtRational(InStr); // meters/multiplier, e.g.. 110/10 + Result := Format('%1.2f m', [gAltitude]); +end; +*) + { +function GpsVersionID(AText: String): String; +var + i: Integer; + sep: Char; +begin + Result := ''; + sep := ','; + for i:=1 to Length(fpExifDataSep) do + if fpExifDataSep[i] <> ' ' then begin + sep := char(fpExifDataSep[i]); + break; + end; + + for i:=1 to Length(AText) do begin + if AText[i] = sep then + Result := Result + '.' + else if AText[i] <> ' ' then + Result := Result + AText[i]; + end; +end; + +function CompCfgCallback(AText: String): String; +var + i, ti: Integer; +begin + Result := ''; + for i := 1 to 4 do + if i <= Length(AText) then begin + ti := integer(AText[i]); + case ti of +// 0: Result := Result + '-'; + 1: Result := Result + 'Y'; + 2: Result := Result + 'Cb'; + 3: Result := Result + 'Cr'; + 4: Result := Result + 'R'; + 5: Result := Result + 'G'; + 6: Result := Result + 'B'; + end; + end; +end; + } + +//============================================================================== +// String utilities +//============================================================================== + +{ Counts how often the specified character is contained within a string } +function CountChar(AChar: Char; const AText: String): Integer; +var + i: Integer; +begin + Result := 0; + for i:=1 to Length(AText) do + if (AChar = AText[i]) then inc(Result); +end; + +function FirstWord(const AText: String): String; +var + i: Integer; +begin + Result := ''; + for i:=1 to Length(AText) do + if AText[i] in ['a'..'z', 'A'..'Z', '0'..'9'] then + Result := Result + AText[i] + else + exit; +end; + +{ Inserts spaces into a camel-case text, i.e. 'ShutterSpeed' --> 'Shutter Speed'} +function InsertSpaces(ACamelCaseText: String): String; + + function IsUpper(ch: char): boolean; + begin + Result := ((ch >= 'A') and (ch <= 'Z')) or (ch = #0) or (ch = '/'); + end; + +var + i: integer; + len: Integer; + ch, nextch, prevch: char; + s: String; +begin + len := Length(ACamelCaseText); + if len < 3 then begin + Result := ACamelCaseText; + exit; + end; + s := ACamelCaseText[1]; + prevch := ACamelCaseText[1]; + for i := 2 to len do + begin + ch := ACamelCaseText[i]; + if i < len then nextch := ACamelCaseText[i+1] else nextch := #0; + if IsUpper(ch) and + (not IsUpper(prevch) or not IsUpper(nextch)) and + (ch <> ' ') and (prevch <> ' ') and (nextch <> ' ') + then + s := s + ' ' + ch + else + s := s + ch; + prevch := ch; + end; + Result := s; +end; + +{ Removes all non-alpha characters ('a'..'z', 'A'..'Z') from a string } +function LettersOnly(const AText: String): string; +var + i: Integer; +begin + Result := ''; + for i:=1 to Length(AText) do + if AText[i] in ['a'..'z', 'A'..'Z'] then + Result := Result + AText[i]; +end; + + +//============================================================================== +// Lookup +//============================================================================== +type + TLookupMode = (lmKey, lmValue); + +function LookupHelper(const ASearchStr, ALookupTbl: String; + ACompareFunc: TLookupCompareFunc; AMode: TLookupMode; out AResultStr: String): Boolean; +var + i: Integer; + key, val: String; + inKey: Boolean; +begin + Result := false; + if ALookupTbl = '' then + exit; + + key := ''; + inKey := true; + + for i:=1 to Length(ALookupTbl) do begin + if ALookupTbl[i] = fpExifLookupKeySep then + begin + inKey := false; + val := ''; + end else + if (ALookupTbl[i] = fpExifLookupSep) then + begin + case AMode of + lmKey: + if ACompareFunc(key, ASearchStr) then begin + Result := true; + AResultStr := val; + exit; + end; + lmValue: + if ACompareFunc(val, ASearchStr) then begin + Result := true; + AResultStr := key; + exit; + end; + end; + inKey := true; + key := ''; + end else + if inKey then + key := key + ALookupTbl[i] + else + val := val + ALookupTbl[i]; + end; + + case AMode of + lmKey: + if ACompareFunc(key, ASearchStr) then begin + Result := true; + AResultStr := val; + end; + lmValue: + if ACompareFunc(val, ASearchStr) then begin + Result := true; + AResultStr := key; + end; + end; +end; + +function LookupValue(const AKey, ALookupTbl: String; + ACompareFunc: TLookupCompareFunc): String; +var + found: Boolean; +begin + found := LookupHelper(AKey, ALookupTbl, ACompareFunc, lmKey, Result); + if not found then + Result := AKey; +end; + +function LookupKey(const AValue, ALookupTbl: String; + ACompareFunc: TLookupCompareFunc): String; +var + found: Boolean; +begin + found := LookupHelper(AValue, ALookupTbl, ACompareFunc, lmValue, Result); + if not found then + Result := ''; +end; + +function NumericOnly(const AText: String): String; +var + i: Integer; +begin + Result := ''; + for i:=1 to Length(AText) do + if AText[i] in ['0'..'9'] then + Result := Result + AText[i]; +end; + +function Split(AText: String; ASeparator: String = #9): TStringArray; +const + BLOCK_SIZE = 20; +var + i, j, k, n, len: Integer; + s: String; + found: Boolean; +begin + Assert(ASeparator <> ''); + + if AText = '' then begin + SetLength(Result, 0); + exit; + end; + +// AText := AText + ASeparator; + len := Length(AText); + SetLength(Result, BLOCK_SIZE); + i := 1; + n := 0; + s := ''; + while (i <= len) do begin + if AText[i] = ASeparator[1] then begin + j := i; + k := 1; + found := true; + while (i <= len) and (k <= Length(ASeparator)) do begin + if ASeparator[k] <> AText[i] then begin + found := false; + break; + end; + inc(k); + inc(i); + end; + if found then begin + Result[n] := s; + inc(n); + if n mod BLOCK_SIZE = 0 then + SetLength(Result, Length(Result) + BLOCK_SIZE); + s := ''; + Continue; + end else + i := j; + end else + s := s + AText[i]; + inc(i); + end; +(* + if (AText[i] = ASeparator) or (i = len) then begin + Result[n] := Copy(AText, j, i-j); + inc(n); + if n mod BLOCK_SIZE = 0 then + SetLength(Result, Length(Result) + BLOCK_SIZE); + j := i+1; + end; + inc(i); + end; + *) + + Result[n] := s; + inc(n); + + SetLength(Result, n); +end; + +//============================================================================== +// Float to fraction converstion +// +// These routines are adapted from unit Fractions by Bart Boersma +// https://sourceforge.net/p/lazarus-ccr/svn/HEAD/tree/components/fractions/ +//============================================================================== +const + MaxInt32 = High(Int32); + MinInt32 = Low(Int32); + +function InRange32(Value: Double): Boolean; {$IFDEF FPC}inline;{$ENDIF} +begin + Result := not ((Value > MaxInt32) or (Value < MinInt32)); +end; + +procedure CheckRange(Value: Double); +begin + if not InRange32(Value) then + raise ERangeError.Create(rsRangeCheckError); +end; + +procedure AdjustPrecision(var Precision: Double; Value: Double); +const + MaxPrec: Double = 1.0 / MaxInt32; +begin + Precision := Abs(Precision); + if ((Abs(Value) / Precision) > 1E15) then + Precision := Abs(Value) / 1E16; + if (Precision < MaxPrec) then + Precision := MaxPrec; +end; + +function IsBorderlineValue(Value: Double; out F: TExifRational): Boolean; +const + MaxPrec: Double = 1.0 / MaxInt32; + ZeroBoundary: Double = 0.5 / MaxInt32; +begin + if (Abs(Value) <= MaxPrec) then + begin + Result := True; + if (Abs(Value) < ZeroBoundary) then + begin + F.Numerator := 0; + F.Denominator := 1; + end + else + begin + if (Value < 0) then + F.Numerator := -1 + else + F.Numerator := 1; + F.Denominator := MaxInt32; + end; + end + else + Result := False; +end; + +// Uses method of continued fractions +function FloatToRational(Value, Precision: Double): TExifRational; +var + H1, H2, K1, K2, A, NewA, tmp: Int32; + B, diff, test: Double; + PendingOverFlow, Found: Boolean; +begin + if IsNaN(Value) then begin + Result.Numerator := 1; + Result.Denominator := 0; + exit; + end; + + CheckRange(Value); + AdjustPrecision(Precision, Value); + + //Borderline cases + if IsBorderlineValue(Value, Result) then + Exit; + + H1 := 1; + H2 := 0; + K1 := 0; + K2 := 1; + b := Value; + NewA := Round(Floor(b)); + repeat + A := NewA; + tmp := H1; + H1 := (a * H1) + H2; + H2 := tmp; + tmp := K1; + K1 := (a * K1) + K2; + K2 := tmp; + test := H1 / K1; + diff := Abs(test - Value); + Found := (diff < Precision); + if not Found then + begin + if (Abs(B-A) < 1E-30) then + B := 1E30 //happens when H1/K2 exactly matches Value + else + B := 1 / (B - A); + PendingOverFlow := (((Double(B) * H1) + H2) > MaxInt32) or + (((Double(B) * K1) + K2) > MaxInt32) or + (B > MaxInt32); + if not PendingOverFlow then + NewA := Round(Floor(B)); + end; + until Found or PendingOverFlow; + Result.Numerator := H1; + Result.Denominator := K1; +end; + +function TryStrToRational(const AStr: String; out AValue: TExifRational): Boolean; +var + p: Integer; + snum, sdenom: String; +begin + Result := false; + + if AStr = '' then + exit; + + p := pos('/', AStr); + if p = 0 then begin + snum := AStr; + sdenom := '1'; + end else begin + snum := trim(Copy(AStr, 1, p-1)); + sdenom := trim(Copy(AStr, p+1, MaxInt)); + end; + + if (snum = '') or (sdenom = '') then + exit; + + Result := TryStrToInt(snum, AValue.Numerator) and TryStrToInt(sdenom, AValue.Denominator); +end; + +function StrToRational(const AStr: String): TExifRational; +begin + if not TryStrToRational(AStr, Result) then begin + Result.Numerator := 1; + Result.Denominator := 0; + end; +end; + +function GCD(a, b: integer): integer; +begin + if (a = 0) then + Result := abs(b) + else + if (b = 0) then + Result := abs(a) + else + if (b mod a) = 0 then + Result := a + else + Result := GCD(b, a mod b); +end; + + +//============================================================================== +// Buffer utilities +//============================================================================== + +function PosInBytes(AText: AnsiString; ABuffer: TBytes): Integer; +var + i, j: Integer; + found: Boolean; +begin + if (AText = '') or (ABuffer = nil) then begin + Result := -1; + exit; + end; + + for i:= 0 to High(ABuffer) do + if ABuffer[i] = ord(AText[1]) then begin + found := true; + for j := 2 to Length(AText) do + if ABuffer[i+j-1] <> ord(AText[j]) then begin + found := false; + break; + end; + if found then begin + Result := i; + exit; + end; + end; + + Result := -1; +end; + + +//============================================================================== +// Date/time utilities +//============================================================================== + +{$IFNDEF FPC} +function GetLocalTimeOffset: LongInt; +var + TZoneInfo: TTimeZoneInformation; +begin + GetTimeZoneInformation(TZoneInfo); + Result := TZoneInfo.Bias; +end; +{$ENDIF} + +function LocalTimeZoneStr: string; +var + bias: Integer; + h, m: Integer; +begin + bias := GetLocalTimeOffset; + if bias >= 0 then + Result := '+' + else + Result := '-'; + bias := Abs(bias); + h := bias div 60; + m := bias mod 60; + Result := Result + Format('%.2d%.2d', [h, m]); +end; + +function IPTCDateStrToDate(AValue: String): TDateTime; +var + yr, mon, day: Integer; +begin + Result := 0; + if (Length(AValue) >= 8) and + TryStrToInt(Copy(AValue, 1, 4), yr) and + TryStrToInt(Copy(AValue, 5, 2), mon) and (mon >= 1) and (mon <= 12) and + TryStrToInt(Copy(AValue, 7, 2), day) and (day >= 1) and (day <= DaysInAMonth(yr, mon)) + then + Result := EncodeDate(yr, mon, day); +end; + +function IPTCTimeStrToTime(AValue: String): TDateTime; +var + hr, mn, sc: Integer; +begin + Result := 0; + if (Length(AValue) >= 6) and + TryStrToInt(Copy(AValue, 1, 2), hr) and (hr >= 0) and (hr < 24) and + TryStrToInt(Copy(AValue, 3, 2), mn) and (mn >= 0) and (mn < 60) and + TryStrToInt(Copy(AValue, 5, 2), sc) and (sc >= 0) and (sc < 60) + then + Result := EncodeTime(hr, mn, sc, 0) +end; + + +//============================================================================== +// Silence compiler warnings due to unused parameters +// (code adapted from TAChart) +//============================================================================== +{$PUSH}{$HINTS OFF} +procedure Unused(const A1); +begin +end; + +procedure Unused(const A1, A2); +begin +end; + +procedure Unused(const A1, A2, A3); +begin +end; +{$POP} + +end. + diff --git a/components/fpexif/fpexif.inc b/components/fpexif/fpexif.inc new file mode 100644 index 000000000..d1c74c51e --- /dev/null +++ b/components/fpexif/fpexif.inc @@ -0,0 +1,34 @@ + +{$IFDEF FPC} + {------------------------------------------------------------------ + Defines for Lanzarus and FPC + ------------------------------------------------------------------} + ////// { Activate this define if an FPC version of at least 3.0 is used. } + { This define signals that an FPC version >= 3.0 is used } + {$DEFINE FPC3+} + {$I fpexif_fpc.inc} + + // Don't use jpeg units + {$DEFINE dExifNoJpeg} + +{$ELSE} + {------------------------------------------------------------------ + Defines for Delphi + ------------------------------------------------------------------} + {$UNDEF FPC3+} + + { Activate this define if a library other than Delphi's jpeg is + used for reading of jpeg files. + Is active by default for Lazarus/FPC } + {.$DEFINE dEXIFNoJpeg} + + { Activate the define ENDIAN_BIG if working on a Big-Endian machine } + {.$DEFINE ENDIAN_BIG} + + { I did not test all Delphi versions. Extend the list if needed... } + {$IFDEF VER150} + {$DEFINE DELPHI7} + {$ENDIF} + +{$ENDIF} + diff --git a/components/fpexif/fpexif_fpc.inc b/components/fpexif/fpexif_fpc.inc new file mode 100644 index 000000000..7434ebee2 --- /dev/null +++ b/components/fpexif/fpexif_fpc.inc @@ -0,0 +1,3 @@ +{$IF FPC_FullVersion < 30000} + {$UNDEF FPC3+} + {$ENDIF} \ No newline at end of file diff --git a/components/fpexif/fpexif_pkg.lpk b/components/fpexif/fpexif_pkg.lpk new file mode 100644 index 000000000..de621c76b --- /dev/null +++ b/components/fpexif/fpexif_pkg.lpk @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/fpexif/fpexif_pkg.pas b/components/fpexif/fpexif_pkg.pas new file mode 100644 index 000000000..191f397f8 --- /dev/null +++ b/components/fpexif/fpexif_pkg.pas @@ -0,0 +1,16 @@ +{ This file was automatically created by Lazarus. Do not edit! + This source is only used to compile and install the package. + } + +unit fpexif_pkg; + +{$warn 5023 off : no warning about unused units} +interface + +uses + fpeGlobal, fpeTags, fpeUtils, fpeMetadata, fpeExifReadWrite, + fpeIptcReadWrite, fpeExifData, fpeIptcData, fpeStrConsts, fpemakernote; + +implementation + +end. diff --git a/components/fpexif/languages/fpestrconsts.de.po b/components/fpexif/languages/fpestrconsts.de.po new file mode 100644 index 000000000..1d5709109 --- /dev/null +++ b/components/fpexif/languages/fpestrconsts.de.po @@ -0,0 +1,1693 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Project-Id-Version: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: de\n" +"X-Generator: Poedit 2.0.4\n" + +#: fpestrconsts.rsacceleration +msgid "Acceleration" +msgstr "Beschleunigung" + +#: fpestrconsts.rsactionadvised +msgid "Action advised" +msgstr "" + +#: fpestrconsts.rsaperturevalue +msgid "Aperture value" +msgstr "Blendenwert" + +#: fpestrconsts.rsartist +msgid "Artist" +msgstr "Künstler" + +#: fpestrconsts.rsautomanual +msgid "0:Auto,1:Manual" +msgstr "0:Auto,1:Manuell" + +#: fpestrconsts.rsbitspersample +msgid "Bits per sample" +msgstr "" + +#: fpestrconsts.rsbrightnessvalue +msgid "Brightness value" +msgstr "Helligkeitswert" + +#: fpestrconsts.rsbyline +msgid "ByLine" +msgstr "" + +#: fpestrconsts.rsbylinetitle +msgid "ByLine title" +msgstr "" + +#: fpestrconsts.rscameraelevationangle +msgid "Camera elevation angle" +msgstr "" + +#: fpestrconsts.rscannotsavetounknownfileformat +msgid "The metadata structure cannot be saved because the file format of the receiving file is not known or not supported." +msgstr "Die Metadaten-Struktur kann nicht gespeichert werden, weil das Format der Zieldatei nicht bekannt ist oder nicht unterstützt wird." + +#: fpestrconsts.rscanonaelkup +msgid "0:Normal AE,1:Exposure compensation,2:AE lock,3:AE lock + Exposure compensation,4:No AE" +msgstr "" + +#: fpestrconsts.rscanonaflkup +msgid "$2005:Manual AF point selection,$3000:None (MF),$3001:Auto AF point selection,$3002:Right,$3003:Center,$3004:Left,$4001:Auto AF point selection,$4006:Face Detect" +msgstr "" + +#: fpestrconsts.rscanonautorotlkup +msgid "0:None,1:Rotate 90 CW,2:Rotate 180,3:Rotate 270 CW" +msgstr "" + +#: fpestrconsts.rscanonbiaslkup +msgid "65472:-2 EV,65484:-1.67 EV,65488:-1.50 EV,65492:-1.33 EV,65504:-1 EV,65516:-0.67 EV,65520:-0.50 EV,65524:-0.33 EV,0:0 EV,12:0.33 EV,16:0.50 EV,20:0.67 EV,32:1 EV,44:1.33 EV,48:1.50 EV,52:1.67 EV,64:2 EV" +msgstr "" + +#: fpestrconsts.rscanoncamtypelkup +msgid "248:EOS High-end,250:Compact,252:EOS Mid-range,255:DV Camera" +msgstr "" + +#: fpestrconsts.rscanoneasylkup +msgid "0:Full Auto,1:Manual,2:Landscape,3:Fast Shutter,4:Slow Shutter,5:Night,6:Gray scale,7:Sepia,8:Portrait,9:Sports,10:Macro,11:Black & White,12:Pan Focus,13:Vivid,14:Neutral,15:Flash off,16:Long shutter,17:Super macro,18:Foliage,19:Indoor,20:Fireworks,21:Beach,22:Underwater,23:Snow,24:Kids & Pets,25:Night snapshot,26:Digital macro,27:My colors,28:Movie snap,29:Super macro 2,30:Color accent,31:Color swap,32:Aquarium,33:ISO3200,34:ISO6400,35:Creative light effect,36:Easy,37:Quick shot,38:Creative auto,39:Zoom blur,40:Low light,41:Nostalgic,42:Super vivid,43:Poster effect,44:Face self-timer,45:Smile,46:Wink self-timer,47:Fisheye effect,48:Miniature effect,49:High-speed burst,50:Best image selection,51:High dynamic range,52:Handheld night scene,53:Movie digest,54:Live view control,55:Discreet,56:Blur reduction,57:Monochrome,58:Toy camera effect,59:Scene intelligent auto,60:High-speed burst HQ,61:Smooth skin,62:Soft focus,257:Spotlight,258:Night 2,259:Night+,260:Super night,261:Sunset,263:Night scene,264:Surface,265:Low light 2" +msgstr "" + +#: fpestrconsts.rscanonexposelkup +msgid "0:Easy shooting,1:Program AE,2:Shutter speed priority AE,3:Aperture priority AE,4:Manual,5:Depth-of-field AE,6:M-Dep,7:Bulb" +msgstr "" + +#: fpestrconsts.rscanonflashactlkup +msgid "0:Did not fire,1:Fired" +msgstr "0:Hat nicht ausgelöst,1:Ausgelöst" + +#: fpestrconsts.rscanonflashlkup +msgid "0:Not fired,1:Auto,2:On,3:Red-eye,4:Slow sync,5:Auto+red-eye,6:On+red eye,16:External flash" +msgstr "" + +#: fpestrconsts.rscanonfocaltypelkup +msgid "1:Fixed,2:Zoom" +msgstr "" + +#: fpestrconsts.rscanonfoctypelkup +msgid "0:Manual,1:Auto,3:Close-up (macro),8:Locked (pan mode)" +msgstr "" + +#: fpestrconsts.rscanonfocuslkup +msgid "0:One-Shot AF,1:AI Servo AF,2:AI Focus AF,3:Manual focus,4:Single,5:Continuous,6:Manual focus,16:Pan focus,256:AF+MF,512:Movie snap focus,519:Movie servo AF" +msgstr "" + +#: fpestrconsts.rscanongenlkup +msgid "65535:Low,0:Normal,1:High" +msgstr "" + +#: fpestrconsts.rscanonimgstablkup +msgid "0:Off,1:On,2:Shoot only,3:Panning,4:Dynamic,256:Off,257:On,258:Shoot only,259:Panning,260:Dynamic" +msgstr "" + +#: fpestrconsts.rscanonisolkup +msgid "0:Not used,15:auto,16:50,17:100,18:200,19:400" +msgstr "" + +#: fpestrconsts.rscanonmacrolkup +msgid "1:Macro,2:Normal" +msgstr "1:Makro,2:Normal" + +#: fpestrconsts.rscanonmeterlkup +msgid "0:Default,1:Spot,2:Average,3:Evaluative,4:Partial,5:Center-weighted average" +msgstr "" + +#: fpestrconsts.rscanonpandirlkup +msgid "0:Left to right,1:Right to left,2:Bottom to top,3:Top to bottom,4:2x2 Matrix (clockwise)" +msgstr "" + +#: fpestrconsts.rscanonqualitylkup +msgid "65535:n/a,1:Economy,2:Normal,3:Fine,4:RAW,5:Superfine,130:Normal Movie,131:Movie (2)" +msgstr "" + +#: fpestrconsts.rscanonreclkup +msgid "1:JPEG,2:CRW+THM,3:AVI+THM,4:TIF,5:TIF+JPEG,6:CR2,7:CR2+JPEG,9:MOV,10:MP4" +msgstr "" + +#: fpestrconsts.rscanonsizelkup +msgid "65535:n/a,0:Large,1:Medium,2:Small,4:5 MPixel,5:2 MPixel,6:1.5 MPixel,8:Postcard,9:Widescreen,10:Medium widescreen,14:Small 1,15:Small 2,16:Small 3,128:640x480 movie,129:Medium movie,130:Small movie,137:128x720 movie,142:1920x1080 movie" +msgstr "" + +#: fpestrconsts.rscanonsloshuttlkup +msgid "65535:n/a,0:Off,1:Night scene,2:On,3:None" +msgstr "" + +#: fpestrconsts.rscanonwhiteballkup +msgid "0:Auto,1:Daylight,2:Cloudy,3:Tungsten,4:Flourescent,5:Flash,6:Custom,7:Black & white,8:Shade,9:Manual temperature (Kelvin),14:Daylight fluorescent,17:Under water" +msgstr "" + +#: fpestrconsts.rscanonzoomlkup +msgid "0:None,1:2x,2:4x,3:Other" +msgstr "0:Nichts,1:2x,2:4x,3:Anderer Wert" + +#: fpestrconsts.rscasioafmode2lkup +msgid "0:Off,1:Spot,2:Multi,3:Face detection,4:Tracking,5:Intelligent" +msgstr "" + +#: fpestrconsts.rscasioartmode2lkup +msgid "0:Normal,8:Silent movie,39:HDR,45:Premium auto,47:Painting,49:Crayon drawing,51:Panorama,52:Art HDR,62:High Speed night shot,64:Monochrome,67:Toy camera,68:Pop art,69:Light tone" +msgstr "" + +#: fpestrconsts.rscasioautoiso2lkup +msgid "1:On,2:Off,7:On (high sensitivity),8:On (anti-shake),10:High Speed" +msgstr "" + +#: fpestrconsts.rscasioccdsensitivitylkup +msgid "64:Normal,125:+1.0,250:+2.0,244:+3.0,80:Normal,100:High" +msgstr "" + +#: fpestrconsts.rscasiocolorfilter2lkup +msgid "0:Off,1:Blue,3:Green,4:Yellow,5:Red,6:Purple,7:Pink" +msgstr "" + +#: fpestrconsts.rscasiocolormode2lkup +msgid "0:Off,2:Black & White,3:Sepia" +msgstr "0:Aus,1:Schwarzweiß,2:Sepia" + +#: fpestrconsts.rscasiodigitalzoomlkup +msgid "$10000:Off,$10001:2x Digital zoom,$20000:2x digital zoom,$40000:4x digital zoom" +msgstr "" + +#: fpestrconsts.rscasiodrivemode2lkup +msgid "0:Single shot,1:Continuous shooting,2:Continuous (2 fps),3:Continuous (3 fps),4:Continuous (4 fps),5:Continuous (5 fps),6:Continuous (6 fps),7:Continuous (7 fps),10:Continuous (10 fps),12:Continuous (12 fps),15:Continuous (15 fps),20:Continuous (20 fps),30:Continuous (30 fps),40:Continuous (40 fps),60:Continuous (60 fps),240:Auto-N" +msgstr "" + +#: fpestrconsts.rscasioenhancement2lkup +msgid "0:Off,1:Scenery,3:Green,5:Underwater,9:Flesh tones" +msgstr "" + +#: fpestrconsts.rscasioflashintensitylkup +msgid "11:Weak,13:Normal,15:Strong" +msgstr "11:Schwach,13:Normal,15:Stark" + +#: fpestrconsts.rscasioflashmodelkup +msgid "1:Auto,2:On,3:Off,4:Red-eye reduction" +msgstr "1:Auto,2:Ein,3:Unterdrückung von roten Augen" + +#: fpestrconsts.rscasiofocusingmodelkup +msgid "2:Macro,3:Auto focus,4:Manual focus,5:Infinity" +msgstr "2:Makro,3:Auto-Fokus,4:Manuelle Entfernungseinstellung,5:Unendlich" + +#: fpestrconsts.rscasiofocusmode22lkup +msgid "0:Manual,1:Focus lock,2:Macro,3:Single-area auto focus,5:Infinity,6:Multi-area auto focus,8:Super macro" +msgstr "" + +#: fpestrconsts.rscasiofocusmode2lkup +msgid "0:Normal,1:Macro" +msgstr "0:Normal,1:Makro" + +#: fpestrconsts.rscasioimagesize2lkup +msgid "0:640 x 480,4:1600 x 1200,5:2048 x 1536,20:2288 x 1712,21:2592 x 1944,22:2304 x 1728,36:3008 x 2008" +msgstr "" + +#: fpestrconsts.rscasioimagestabilization2lkup +msgid "0:Off,1:On,2:Best shot,3:Movie anti-shake" +msgstr "" + +#: fpestrconsts.rscasioisospeed2lkup +msgid "3 = 50,4:64,6:100,9:200" +msgstr "" + +#: fpestrconsts.rscasiolightingmode2lkup +msgid "0:Off,1:High dynamic range,5:Shadow enhance low,6:Shadow enhance high" +msgstr "" + +#: fpestrconsts.rscasioportraitrefiner2lkup +msgid "0:Off,1:+1,2:+2" +msgstr "" + +#: fpestrconsts.rscasiorecordingmodelkup +msgid "1:Single shutter,2:Panorama,3:Night scene,4:Portrait,5:Landscape" +msgstr "" + +#: fpestrconsts.rscasiorecordmode2lkup +msgid "2:Program AE,3:Shutter priority,4:Aperture priority,5:Manual,6:Best shot,17:Movie,19:Movie (19),20:YouTube Movie" +msgstr "" + +#: fpestrconsts.rscasioreleasemode2lkup +msgid "1:Normal,3:AE Bracketing,11:WB Bracketing,13 = Contrast Bracketing,19:High Speed Burst" +msgstr "" + +#: fpestrconsts.rscasiosharpness2lkup +msgid "0:Soft,1:Normal,2:Hard" +msgstr "0:Weich,1:Normal,2:Hart" + +#: fpestrconsts.rscasiospecialeffectsetting2lkup +msgid "0:Off,1:Makeup,2:Mist removal,3:Vivid landscape,16:Art shot" +msgstr "" + +#: fpestrconsts.rscasiovideoquality2lkup +msgid "1:Standard,3:HD (720p),4:Full HD (1080p),5:Low" +msgstr "" + +#: fpestrconsts.rscasiowhitebalance22lkup +msgid "0:Manual,1:Daylight,2:Cloudy,3:Shade,4:Flash?,6:Fluorescent,9:Tungsten?,10:Tungsten,12:Flash" +msgstr "" + +#: fpestrconsts.rscasiowhitebalance2lkup +msgid "0:Auto,1:Daylight,2:Shade,3:Tungsten,4:Fluorescent,5:Manual" +msgstr "" + +#: fpestrconsts.rscasiowhitebalancelkup +msgid "1:Auto,2:Tungsten,3:Daylight,4:Fluorescent,5:Shade,129:Manual" +msgstr "1:Auto,2:Wolframlampe,3:Tagelicht,4:Leuchstoffröhre,5:Schatten,129:Manuell" + +#: fpestrconsts.rscategory +msgid "Category" +msgstr "Kategorie" + +#: fpestrconsts.rscellheight +msgid "Cell height" +msgstr "Zellhöhe" + +#: fpestrconsts.rscellwidth +msgid "Cell width" +msgstr "Zellbreite" + +#: fpestrconsts.rscfapattern +msgid "CFA pattern" +msgstr "" + +#: fpestrconsts.rscity +msgid "City" +msgstr "Stadt" + +#: fpestrconsts.rscodedcharset +msgid "Coded character set" +msgstr "Kodierter Zeichensatz" + +#: fpestrconsts.rscolorspace +msgid "Color space" +msgstr "Farbraum" + +#: fpestrconsts.rscolorspacelkup +msgid "0:sBW,1:sRGB,2:Adobe RGB,65533:Wide Gamut RGB,65534:ICC Profile,65535:Uncalibrated" +msgstr "" + +#: fpestrconsts.rscomponentsconfig +msgid "Components configuration" +msgstr "Komponentenkonfiguration" + +#: fpestrconsts.rscompressedbitsperpixel +msgid "Compressed bits per pixel" +msgstr "" + +#: fpestrconsts.rscompression +msgid "Compression" +msgstr "Kompression" + +#: fpestrconsts.rscompressionlkup +msgid "1:Uncompressed,2:CCITT 1D,3:T4/Group 3 Fax,4:T6/Group 4 Fax,5:LZW,6:JPEG (old-style),7:JPEG,8:Adobe Deflate,9:JBIG B&W,10:JBIG Color,99:JPEG,262:Kodak 262,32766:Next,32767:Sony ARW Compressed,32769:Packed RAW,32770:Samsung SRW Compressed,32771:CCIRLEW,32772:Samsung SRW Compressed 2,32773:PackBits,32809:Thunderscan,32867:Kodak KDC Compressed,32895:IT8CTPAD,32896:IT8LW,32897:IT8MP,32898:IT8BL,32908:PixarFilm,32909:PixarLog,32946:Deflate,32947:DCS,34661:JBIG,34676:SGILog,34677:SGILog24,34712:JPEG 2000,34713:Nikon NEF Compressed,34715:JBIG2 TIFF FX,34718:Microsoft Document Imaging (MDI) Binary Level Codec,34719:Microsoft Document Imaging (MDI) Progressive Transform Codec,34720:Microsoft Document Imaging (MDI) Vector,34892:Lossy JPEG,65000:Kodak DCR Compressed,65535:Pentax PEF Compressed" +msgstr "" + +#: fpestrconsts.rscontact +msgid "Contact" +msgstr "Kontakt" + +#: fpestrconsts.rscontentloccode +msgid "Content location code" +msgstr "" + +#: fpestrconsts.rscontentlocname +msgid "Content location name" +msgstr "" + +#: fpestrconsts.rscontrast +msgid "Contrast" +msgstr "Kontrast" + +#: fpestrconsts.rscopyright +msgid "Copyright" +msgstr "Copyright" + +#: fpestrconsts.rscustomrendered +msgid "Custom rendered" +msgstr "" + +#: fpestrconsts.rscustomrenderedlkup +msgid "0:Normal,1:Custom" +msgstr "" + +#: fpestrconsts.rsdatecreated +msgid "Date created" +msgstr "Erzeugungsdatum" + +#: fpestrconsts.rsdatetime +msgid "Date/time" +msgstr "Datum/Uhrzeit" + +#: fpestrconsts.rsdatetimedigitized +msgid "Date/time digitized" +msgstr "Datum/Uhrzeit der digitalen Erfassung" + +#: fpestrconsts.rsdatetimeoriginal +msgid "Date/time original" +msgstr "Original-Datum/Uhrzeit" + +#: fpestrconsts.rsdevicesettingdescription +msgid "Device setting description" +msgstr "" + +#: fpestrconsts.rsdigitalzoom +msgid "Digital zoom" +msgstr "Digitalzoom" + +#: fpestrconsts.rsdigitalzoomratio +msgid "Digital zoom ratio" +msgstr "Digitalzoom-Verhältnis" + +#: fpestrconsts.rsdigitizedate +msgid "Digital creation date" +msgstr "" + +#: fpestrconsts.rsdigitizetime +msgid "Digital creation time" +msgstr "" + +#: fpestrconsts.rsdocumentname +msgid "Document name" +msgstr "Dokument-Name" + +#: fpestrconsts.rseconomynormalfine +msgctxt "fpestrconsts.rseconomynormalfine" +msgid "0:Economy,1:Normal,2:Fine" +msgstr "" + +#: fpestrconsts.rseconomynormalfine1 +msgctxt "fpestrconsts.rseconomynormalfine1" +msgid "1:Economy,2:Normal,3:Fine" +msgstr "" + +#: fpestrconsts.rseditorialupdate +msgid "Editorial update" +msgstr "" + +#: fpestrconsts.rseditstatus +msgid "Edit status" +msgstr "Bearbeitungsstatus" + +#: fpestrconsts.rsexifimageheight +msgid "EXIF image height" +msgstr "EXIF Bildhöhe" + +#: fpestrconsts.rsexifimagewidth +msgid "EXIF image width" +msgstr "EXIF Bildbreite" + +#: fpestrconsts.rsexifoffset +msgid "EXIF offset" +msgstr "EXIF-Offset" + +#: fpestrconsts.rsexifversion +msgid "EXIF version" +msgstr "EXIF-Version" + +#: fpestrconsts.rsexpiredate +msgid "Expiration date" +msgstr "Ablaufdatum" + +#: fpestrconsts.rsexpiretime +msgid "Expiration time" +msgstr "Ablaufzeit" + +#: fpestrconsts.rsexposurebiasvalue +msgid "Exposure bias value" +msgstr "Belichtungskorrektur" + +#: fpestrconsts.rsexposureindex +msgid "Exposure index" +msgstr "Belichtungsindex" + +#: fpestrconsts.rsexposuremode +msgid "Exposure mode" +msgstr "Belichtungsmodus" + +#: fpestrconsts.rsexposuremodelkup +msgid "0:Auto,1:Manual,2:Auto bracket" +msgstr "0:Auto,1:Manuell,2:Automatische Belichtugnsreihe" + +#: fpestrconsts.rsexposureprogram +msgid "Exposure program" +msgstr "Belichtungsprogramm" + +#: fpestrconsts.rsexposureprogramlkup +msgid "0:Not defined,1:Manual,2:Program AE,3:Aperture-priority AE,4:Shutter speed priority AE,5:Creative (slow speed),6:Action (high speed),7:Portrait,8:Landscape;9:Bulb" +msgstr "" + +#: fpestrconsts.rsexposuretime +msgid "Exposure time" +msgstr "Belichtungszeit" + +#: fpestrconsts.rsextensiblemetadataplatform +msgid "Extensible metadata platform" +msgstr "" + +#: fpestrconsts.rsfilenotfounderror +msgid "File \"%s\" not found." +msgstr "Datei \"%s\" nicht gefunden." + +#: fpestrconsts.rsfilesource +msgid "File source" +msgstr "Dateiquelle" + +#: fpestrconsts.rsfilesourcelkup +msgid "0:Unknown,1:Film scanner,2:Reflection print scanner,3:Digital camera" +msgstr "0:Unbekannt,1:Durchlichtscanner,2:Auflichtscanner,3:Digitalkamera" + +#: fpestrconsts.rsfillorder +msgid "Fill order" +msgstr "" + +#: fpestrconsts.rsfillorderlkup +msgid "1:Normal,2:Reversed" +msgstr "1:Normal,2:Umgekehrt" + +#: fpestrconsts.rsfixtureid +msgid "Fixture ID" +msgstr "" + +#: fpestrconsts.rsflash +msgid "Flash" +msgstr "Blitz" + +#: fpestrconsts.rsflashenergy +msgid "Flash energy" +msgstr "Blitz-Energie" + +#: fpestrconsts.rsflashlkup +msgid "0:No flash,1:Fired,5:Fired; return not detected,7:Fired; return detected,8:On; did not fire,9:On; fired,13:On; return not detected,15:On; return detected,16:Off; did not fire,20:Off; did not fire, return not detected,24:Auto; did not fire,25:Auto; fired;29:Auto; fired; return not detected,31:Auto; fired; return detected,32:No flash function,48:Off, no flash function,65:Fired; red-eye reduction,69:Fired; red-eye reduction; return not detected,71:Fired; red-eye reduction; return detected,73:On; red-eye reduction,77:On; red-eye reduction, return not detected,79:On, red-eye reduction, return detected,80:Off; red-eye reduction,88:Auto; did not fire; red-eye reduction,89:Auto; fired; red-eye reduction,93:Auto; fired; red-eye reduction; return not detected,95:Auto; fired; red-eye reduction, return detected" +msgstr "" + +#: fpestrconsts.rsflashpixversion +msgid "FlashPix version" +msgstr "FlashPix-Version" + +#: fpestrconsts.rsfnumber +msgid "F number" +msgstr "Blendenzahl" + +#: fpestrconsts.rsfocallength +msgid "Focal length" +msgstr "Brennweite" + +#: fpestrconsts.rsfocallengthin35mm +msgid "Focal length in 35 mm film" +msgstr "Kleinbild-Brennweite" + +#: fpestrconsts.rsfocalplaneresunit +msgid "Focal plane resolution unit" +msgstr "" + +#: fpestrconsts.rsfocalplaneresunitlkup +msgid "1:None,2:inches,3:cm,4:mm,5:um" +msgstr "1:Nichts,2:Zoll,3:cm,4:mm,5:µm" + +#: fpestrconsts.rsfocalplanexres +msgid "Focal plane x resolution" +msgstr "" + +#: fpestrconsts.rsfocalplaneyres +msgid "Focal plane y resolution" +msgstr "" + +#: fpestrconsts.rsfujiadvancedfilterlkup +msgid "65536:Pop Color,131072:Hi Key,196608:Toy Camera,262144:Miniature, 327680:Dynamic Tone,327681:Partial Color Red,327682:Partial Color Yellow,327683:Partial Color Green,327684:Partial Color Blue,327685:Partial Color Orange,327686:Partial Color Purple,458752:Soft Focus,589824:Low Key" +msgstr "" + +#: fpestrconsts.rsfujiautobracketinglkup +msgid "0:Off,1:On,2:No flash & flash" +msgstr "" + +#: fpestrconsts.rsfujiblurwarninglkup +msgid "0:None,1:Blur Warning" +msgstr "0:Nichts,1:Unschärfewarnung" + +#: fpestrconsts.rsfujicolormodelkup +msgid "0:Standard,16:Chrome,48:B & W" +msgstr "" + +#: fpestrconsts.rsfujicontrastlkup +msgid "0:Normal,128:Medium High,256:High,384:Medium Low,512:Low,32768:Film Simulation" +msgstr "" + +#: fpestrconsts.rsfujicontrastlkup1 +msgid "0:Normal,256:High,768:Low" +msgstr "0:Normal,256:Hoch,768:Niedrig" + +#: fpestrconsts.rsfujidynamicrangelkup +msgid "1:Standard,3:Wide" +msgstr "1:Standard,3:Breit" + +#: fpestrconsts.rsfujiexposurewarninglkup +msgid "0:Good,1:Bad exposure" +msgstr "0:Gut,1:Schlecht belichtet" + +#: fpestrconsts.rsfujiexrmodelkup +msgid "128:HR (High Resolution),512:SN (Signal to Noise priority),768:DR (Dynamic Range priority)" +msgstr "" + +#: fpestrconsts.rsfujiflashmodelkup +msgid "0:Auto,1:On,2:Off,3:Red-eye reduction,4:External,16:Commander,32768:Not Attached,33056:TTL,38976:Manual,39040:Multi-flash,43296:1st Curtain (front),51488:2nd Curtain (rear),59680:High Speed Sync (HSS)" +msgstr "" + +#: fpestrconsts.rsfujifocuswarninglkup +msgid "0:Good,1:Out of focus" +msgstr "0:Gut,1:Unscharf" + +#: fpestrconsts.rsfujihighisonoisereductionlkup +msgid "0:0 (normal),256:+2 (strong),384:+1 (medium strong),448:+3 (very strong),480:+4 (strongest)512:-2 (weak),640:-1 (medium weak),704:-3 (very weak),736:-4 (weakest)" +msgstr "" + +#: fpestrconsts.rsfujinoisereductionlkup +msgid "64:Low,128:Normal,256:n/a" +msgstr "" + +#: fpestrconsts.rsfujipanoramadirlkup +msgid "1:Right,2:Up,3:Left,4:Down" +msgstr "1:Rechts,2:Oben,3:Links,4:Unten" + +#: fpestrconsts.rsfujipicturemodelkup +msgid "0:Auto,1:Portrait,2:Landscape,3:Macro,4:Sports,5:Night Scene,6:Program AE,7:Natural Light,8:Anti-blur,9:Beach & Snow,10:Sunset,11:Museum,12:Party,13:Flower,14:Text,15:Natural Light & Flash,16:Beach,17:Snow,18:Fireworks,19:Underwater,20:Portrait with Skin Correction,22:Panorama,23:Night (tripod),24:Pro Low-light,25:Pro Focus,26:Portrait 2,27:Dog Face Detection,28:Cat Face Detection,64:Advanced Filter,256:Aperture-priority AE,512:Shutter speed priority AE,768:Manual" +msgstr "" + +#: fpestrconsts.rsfujisaturationlkup +msgid "0:0 (normal),128:+1 (medium high),192:+3 (very high),224:+4 (highest),256:+2 (high),384:-1 (medium low),512:Low,768:None (B&W),769:B&W Red Filter,770:B&W Yellow Filter,771:B&W Green Filter,784:B&W Sepia,1024:-2 (low),1216:-3 (very low),1248:-4 (lowest),1280:Acros,1281:Acros Red Filter,1282:Acros Yellow Filter,1283:Acros Green Filter,32768:Film Simulation" +msgstr "" + +#: fpestrconsts.rsfujiscenerecognlkup +msgid "0:Unrecognized,256:Portrait Image,512:Landscape Image,768:Night Scene,1024:Macro" +msgstr "" + +#: fpestrconsts.rsfujishadowhighlightlkup +msgid "-64:+4 (hardest),-48:+3 (very hard),-32:+2 (hard),-16:+1 (medium hard)" +msgstr "" + +#: fpestrconsts.rsfujisharpnesslkup +msgid "0:-4 (softest),1:-3 (very soft),2:-2 (soft),3:0 (normal),4:+2 (hard),5:+3 (very hard),6:+4 (hardest),130:-1 (medium soft),132:+1 (medium hard),32768:Film Simulation,65535:n/a" +msgstr "" + +#: fpestrconsts.rsfujishuttertypelkup +msgid "0:Mechanical,1:Electronic" +msgstr "0:Mechanisch,1:Elektronisch" + +#: fpestrconsts.rsfujiwhiteballkup +msgid "0:Auto,256:Daylight,512:Cloudy,768:Daylight Fluorescent,769:Day White Fluorescent,770:White Fluorescent,771:Warm White Fluorescent,772:Living Room Warm White Fluorescent,1024:Incandescent,1280:Flash,1536:Underwater,3840:Custom,3841:Custom2,3842:Custom3,3843:Custom4,3844:Custom5,4080:Kelvin" +msgstr "" + +#: fpestrconsts.rsgaincontrol +msgid "Gain control" +msgstr "Verstärkungsregelung" + +#: fpestrconsts.rsgaincontrollkup +msgid "0:None,1:Low gain up,2:High gain up,3:Low gain down,4:High gain down" +msgstr "" + +#: fpestrconsts.rsgamma +msgid "Gamma" +msgstr "Gamma" + +#: fpestrconsts.rsgpsaltitude +msgid "GPS altitude" +msgstr "GPS-Höhe" + +#: fpestrconsts.rsgpsaltituderef +msgid "GPS altitude reference" +msgstr "GPS-Höhenbezug" + +#: fpestrconsts.rsgpsaltitudereflkup +msgid "0: Above sea level,1:Below sea level" +msgstr "0:Über Meereshöhe,1:Unter Meereshöhe" + +#: fpestrconsts.rsgpsareainformation +msgid "Area information" +msgstr "Flächeninformation" + +#: fpestrconsts.rsgpsdatedifferential +msgid "GPS date differential" +msgstr "GPS-Datumsdifferenz" + +#: fpestrconsts.rsgpsdatedifferentiallkup +msgid "0:No correction,1:Differential corrected" +msgstr "" + +#: fpestrconsts.rsgpsdatestamp +msgid "GPS date stamp" +msgstr "GPS-Datum" + +#: fpestrconsts.rsgpsdestbearing +msgid "GPS destination bearing" +msgstr "" + +#: fpestrconsts.rsgpsdestbearingref +msgid "GPS destination bearing reference" +msgstr "" + +#: fpestrconsts.rsgpsdestdistance +msgid "GPS destination distance" +msgstr "" + +#: fpestrconsts.rsgpsdestdistanceref +msgid "GPS destination distance reference" +msgstr "" + +#: fpestrconsts.rsgpsdestlatitude +msgid "GPS destination latitude" +msgstr "" + +#: fpestrconsts.rsgpsdestlatituderef +msgid "GPS destination latitude reference" +msgstr "" + +#: fpestrconsts.rsgpsdestlongitude +msgid "GPS destination longitude" +msgstr "" + +#: fpestrconsts.rsgpsdestlongituderef +msgid "GPS destination longitude reference" +msgstr "" + +#: fpestrconsts.rsgpsdistancereflkup +msgid "K:Kilometers,M:Miles,N:Nautical miles" +msgstr "K:Kilometer,M:Meilen,N:nautische Meilen" + +#: fpestrconsts.rsgpsdop +msgid "GPS DOP" +msgstr "" + +#: fpestrconsts.rsgpshpositioningerror +msgid "GPS H positioning error" +msgstr "" + +#: fpestrconsts.rsgpsimagedirection +msgid "GPS image direction" +msgstr "" + +#: fpestrconsts.rsgpsimagedirectionref +msgid "GPS image direction reference" +msgstr "" + +#: fpestrconsts.rsgpsinfo +msgid "GPS info" +msgstr "GPS-Info" + +#: fpestrconsts.rsgpslatitude +msgid "GPS latitude" +msgstr "GPS-Breite" + +#: fpestrconsts.rsgpslatituderef +msgid "GPS latitude reference" +msgstr "GPS-Breitenreferenz" + +#: fpestrconsts.rsgpslatitudereflkup +msgid "N:North,S:South" +msgstr "N:Nord,S:Süd" + +#: fpestrconsts.rsgpslongitude +msgid "GPS longitude" +msgstr "GPS-Länge" + +#: fpestrconsts.rsgpslongituderef +msgid "GPS longitude reference" +msgstr "GPS-Längenreferenz" + +#: fpestrconsts.rsgpslongitudereflkup +msgid "E:East,W:West" +msgstr "E:Ost,2:West" + +#: fpestrconsts.rsgpsmapdatum +msgid "GPS map datum" +msgstr "" + +#: fpestrconsts.rsgpsmeasuremode +msgid "GPS measurement mode" +msgstr "GPS-Messmodus" + +#: fpestrconsts.rsgpsmeasuremodelkup +msgid "2:2-Dimensional Measurement,3:3-Dimensional Measurement" +msgstr "2:2-dimensionale Messung,3:3-dimensionale Messung" + +#: fpestrconsts.rsgpsprocessingmode +msgid "GPS processing mode" +msgstr "GPS-Bearbeitungsmodus" + +#: fpestrconsts.rsgpssatellites +msgid "GPS satellites" +msgstr "GPS-Satelliten" + +#: fpestrconsts.rsgpsspeed +msgid "GPS speed" +msgstr "GPS-Geschwindigkeit" + +#: fpestrconsts.rsgpsspeedref +msgid "GPS speed reference" +msgstr "" + +#: fpestrconsts.rsgpsspeedreflkup +msgid "K:km/h,M:mph,N:knots" +msgstr "K:cm/h,M:Meilen,N:Knoten" + +#: fpestrconsts.rsgpsstatus +msgid "GPS status" +msgstr "GPS-Stsatus" + +#: fpestrconsts.rsgpstimestamp +msgid "GPS time stamp" +msgstr "" + +#: fpestrconsts.rsgpstrack +msgid "GPS track" +msgstr "" + +#: fpestrconsts.rsgpstrackref +msgid "GPS track reference" +msgstr "" + +#: fpestrconsts.rsgpstrackreflkup +msgid "M:Magnetic north,T:True north" +msgstr "M:Magnetischer Nordpol,T:Wahrer Nordpol" + +#: fpestrconsts.rsgpsversionid +msgid "GPS version ID" +msgstr "" + +#: fpestrconsts.rshalftonehints +msgid "Half-tone hints" +msgstr "" + +#: fpestrconsts.rshostcomputer +msgid "Host computer" +msgstr "" + +#: fpestrconsts.rshumidity +msgid "Humidity" +msgstr "Feuchte" + +#: fpestrconsts.rsimagedatafilenotexisting +msgid "File \"%s\" providing the image data does not exist." +msgstr "Datei \"%s\", die die Bilddaten zur Verfügung stellen sollte, existiert nicht." + +#: fpestrconsts.rsimagedatafilenotspecified +msgid "The metadata structure is not linked to an image. Specify the name of the file providing the image data." +msgstr "Die Metadatenstruktur ist nicht mit einem Bild verbunden. Geben Sie den Namen der Datei an, die die Bilddaten zur Verfügung stellt." + +#: fpestrconsts.rsimagedescr +msgid "Image description" +msgstr "Bildbeschreibung" + +#: fpestrconsts.rsimageformatnotsupported +msgid "Image format not supported." +msgstr "Bildformat nicht unterstützt." + +#: fpestrconsts.rsimageheight +msgid "Image height" +msgstr "Bildhöhe" + +#: fpestrconsts.rsimagehistory +msgid "Image history" +msgstr "Bildhistorie" + +#: fpestrconsts.rsimagenumber +msgid "Image number" +msgstr "Bildnummer" + +#: fpestrconsts.rsimageresourcenametoolong +msgid "Image resource name \"%s\" too long." +msgstr "" + +#: fpestrconsts.rsimageuniqueid +msgid "Unique image ID" +msgstr "Eindeutige Bildkennung" + +#: fpestrconsts.rsimagewidth +msgid "Image width" +msgstr "Bildbreite" + +#: fpestrconsts.rsimgcaption +msgid "Image caption" +msgstr "Bildtitel" + +#: fpestrconsts.rsimgcaptionwriter +msgid "Image caption writer" +msgstr "Autor des Bildtitels" + +#: fpestrconsts.rsimgcredit +msgid "Image credit" +msgstr "" + +#: fpestrconsts.rsimgheadline +msgid "Image headline" +msgstr "" + +#: fpestrconsts.rsimgtype +msgid "Image type" +msgstr "Bildtyp" + +#: fpestrconsts.rsincompletejpegsegmentheader +msgid "Defective JPEG structure: Incomplete segment header" +msgstr "Defekte JPEG-Struktur: Unvollständiger Segment-Header" + +#: fpestrconsts.rsincorrectfilestructure +msgid "Incorrect file structure" +msgstr "Falsche Dateistruktur" + +#: fpestrconsts.rsincorrecttagtype +msgid "Incorrect tag type %d: Index=%d, TagID=$%.04x, File:\"%s\"" +msgstr "Falscher Tag-Typ %d: Index=%d, TagID=$%.94x, Datei: \"%s\"" + +#: fpestrconsts.rsinkset +msgid "Ink set" +msgstr "" + +#: fpestrconsts.rsinksetlkup +msgid "1:CMYK,2:Not CMYK" +msgstr "1:CMYK,2:Nicht CMYK" + +#: fpestrconsts.rsinteropindex +msgid "Interoperabiliy index" +msgstr "" + +#: fpestrconsts.rsinteropoffset +msgid "Interoperability offset" +msgstr "" + +#: fpestrconsts.rsinteropversion +msgid "Interoperability version" +msgstr "" + +#: fpestrconsts.rsiptcdataexpected +msgid "IPTC data expected, but not found." +msgstr "IPTC-Daten erwartet, aber nicht gefunden." + +#: fpestrconsts.rsiptcextendeddatasizenotsupported +msgid "Data size %d not supported for an IPTC extended dataset." +msgstr "Datengröße %d wird für einen erweiterten IPTC Datensatz nicht unterstützt." + +#: fpestrconsts.rsiptcnaa +msgid "IPTC/NAA" +msgstr "IPTC/NAA" + +#: fpestrconsts.rsiptcorientationlkup +msgid "P:Portrait,L:Landscape,S:Square" +msgstr "P:Hochformat,L:Querformat,S:Quadratisch" + +#: fpestrconsts.rsiso +msgid "ISO" +msgstr "ISO" + +#: fpestrconsts.rsisospeed +msgid "ISO speed" +msgstr "ISO-Wert" + +#: fpestrconsts.rsisospeedlatitudeyyy +msgid "ISO latitude yyy" +msgstr "" + +#: fpestrconsts.rsisospeedlatitudezzz +msgid "ISO speed latitude zzz" +msgstr "" + +#: fpestrconsts.rsjpegcompresseddatawriting +msgid "Writing error of compressed data." +msgstr "Fehler beim Schreiben der komprimierten Daten." + +#: fpestrconsts.rsjpegreadwriteerrorinsegment +msgid "Read/write error in segment $FF%.2x" +msgstr "Lese-/Schreibfehler in Segment $FF%.2x" + +#: fpestrconsts.rsjpegsegmentmarkerexpected +msgid "Defective JPEG structure: Segment marker ($FF) expected." +msgstr "Defekte JPEG-Struktur: Segment-Marker ($FF) erwartet." + +#: fpestrconsts.rskeywords +msgid "Keywords" +msgstr "Schlüsselwörter" + +#: fpestrconsts.rslangid +msgid "Language ID" +msgstr "Sprach-Kennnummer" + +#: fpestrconsts.rslensinfo +msgid "Lens info" +msgstr "Objektiv-Info" + +#: fpestrconsts.rslensmake +msgid "Lens make" +msgstr "Objektivhersteller" + +#: fpestrconsts.rslensmodel +msgid "Lens model" +msgstr "Objektivmodell" + +#: fpestrconsts.rslensserialnumber +msgid "Lens serial number" +msgstr "Objektiv-Seriennummer" + +#: fpestrconsts.rslightsource +msgid "Light source" +msgstr "Lichtquelle" + +#: fpestrconsts.rslightsourcelkup +msgid "0:Unknown,1:Daylight,2:Fluorescent,3:Tungsten (incandescent),4:Flash,9:Fine weather,10:Cloudy,11:Shade,12:Daylight fluorescent,13:Day white fluorescent,14:Cool white fluorescent,15:White fluorescent,16:Warm white fluorescent,17:Standard light A, 18:Standard light B,19:Standard light C,20:D55,21:D65,22:D74,23:D50,24:ISO Studio tungsten,255:Other" +msgstr "" + +#: fpestrconsts.rslocationcode +msgid "Country/primary location code" +msgstr "Land / primärer Ortscode" + +#: fpestrconsts.rslocationname +msgid "Country/primary location name" +msgstr "Land / primärer Ortsname" + +#: fpestrconsts.rslownormalhigh +msgid "0:Low,1:Normal,2:High" +msgstr "0:Niedrig,1:Normal,2:Hoch" + +#: fpestrconsts.rsmacro +msgid "Macro" +msgstr "Makro" + +#: fpestrconsts.rsmake +msgid "Make" +msgstr "Hersteller" + +#: fpestrconsts.rsmakernote +msgid "Maker note" +msgstr "Hersteller-Notiz" + +#: fpestrconsts.rsmaxaperturevalue +msgid "Max aperture value" +msgstr "Maximaler Blendenwert" + +#: fpestrconsts.rsmaxsamplevalue +msgid "Max sample value" +msgstr "Maximaler Abtastwert" + +#: fpestrconsts.rsmeteringmode +msgid "Metering mode" +msgstr "Belichtungsmess-Modus" + +#: fpestrconsts.rsmeteringmodelkup +msgid "0:Unknown,1:Average,2:Center-weighted average,3:Spot,4:Multi-spot,5:Multi-segment,6:Partial,255:Other" +msgstr "" + +#: fpestrconsts.rsminoltabracketsteplkup +msgid "0:1/3 EV,1:2/3 EV,2:1 EV" +msgstr "0:1/3 EV,1:2/3 EV,2:1 EV" + +#: fpestrconsts.rsminoltacolormodelkup +msgid "0:Natural color,1:Black & White,2:Vivid color,3:Solarization,4:Adobe RGB,5:Sepia,9:Natural,12:Portrait,13:Natural sRGB,14:Natural+ sRGB,15:Landscape,16:Evening,17:Night Scene,18:Night Portrait,132:Embed Adobe RGB" +msgstr "" + +#: fpestrconsts.rsminoltacolorprofilelkup +msgid "0:Not embedded,1:Embedded" +msgstr "" + +#: fpestrconsts.rsminoltadataimprintlkup +msgid "0;None,1:YYYY/MM/DD,2:MM/DD/HH:MM,3:Text,4:Text + ID#" +msgstr "" + +#: fpestrconsts.rsminoltadecpositionlkup +msgid "0:Exposure,1:Contrast,2:Saturation,3:Filter" +msgstr "0:Belichtung,1:Kontrast,2:Sättigung,3:Filter" + +#: fpestrconsts.rsminoltadigitalzoomlkup +msgid "0:Off,1:Electronic magnification,2:2x" +msgstr "" + +#: fpestrconsts.rsminoltadrivemodelkup +msgid "0:Single,1:Continuous,2:Self-timer,4:Bracketing,5:Interval,6:UHS continuous,7:HS continuous" +msgstr "0:Einzeln,1:Ständig,2:Selbstauslöser,4:Belichtungsreihe,4:Intervall,6:UHS ständig,7:HS ständig" + +#: fpestrconsts.rsminoltaexposuremodelkup +msgid "0:Program,1:Aperture priority,2:Shutter priority,3:Manual" +msgstr "" + +#: fpestrconsts.rsminoltaflashmeteringlkup +msgid "0:ADI (Advanced Distance Integration),1:Pre-flash TTL,2:Manual flash control" +msgstr "" + +#: fpestrconsts.rsminoltaflashmodelkup +msgid "0:Fill flash,1:Red-eye reduction,2:Rear flash sync,3:Wireless,4:Off?" +msgstr "" + +#: fpestrconsts.rsminoltafocusarealkup +msgid "0:Wide Focus (normal),1:Spot Focus" +msgstr "" + +#: fpestrconsts.rsminoltafocusmodelkup +msgid "0:AF,1:MF" +msgstr "0:AF,1:MF" + +#: fpestrconsts.rsminoltafoldernamelkup +msgid "0:Standard Form,1:Data Form" +msgstr "" + +#: fpestrconsts.rsminoltaimagesizelkup +msgid "1:1600x1200,2:1280x960,3:640x480,5:2560x1920,6:2272x1704,7:2048x1536" +msgstr "1:1600x1200,2:1280x960,3:640x480,5:2560x1920,6:2272x1704,7:2048x1536" + +#: fpestrconsts.rsminoltaimagesizelkup1 +msgid "0:Full,1:1600x1200,2:1280x960,3:640x480,6:2080x1560,7:2560x1920,8;3264x2176" +msgstr "0:Full,1:1600x1200,2:1280x960,3:640x480,6:2080x1560,7:2560x1920,8;3264x2176" + +#: fpestrconsts.rsminoltaimagestablkup +msgid "1:Off,5:On" +msgstr "1:Aus,2:Ein" + +#: fpestrconsts.rsminoltainternalflashlkup +msgid "0:No,1:Fired" +msgstr "0:Nein,1:Ausgelöst" + +#: fpestrconsts.rsminoltaintervalmodelkup +msgid "0:Still image,1:Time-lapse movie" +msgstr "" + +#: fpestrconsts.rsminoltaisosettinglkup +msgid "0:100,1:200,2:400,3:800,4:Auto,5:64" +msgstr "0:100,1:200,2:400,3:800,4:Auto,5:64" + +#: fpestrconsts.rsminoltameteringmodelkup +msgid "0:Multi-segment,1:Center-weighted average,2:Spot" +msgstr "" + +#: fpestrconsts.rsminoltamodelidlkup +msgid "0:DiMAGE 7/X1/X21 or X31,1:DiMAGE 5,2:DiMAGE S304,3:DiMAGE S404,4:DiMAGE 7i,5:DiMAGE 7Hi,6:DiMAGE A1,7:DiMAGE A2 or S414" +msgstr "" + +#: fpestrconsts.rsminoltaqualitylkup +msgid "0:Raw,1:Super Fine,2:Fine,3:Standard,4:Economy,5:Extra fine" +msgstr "" + +#: fpestrconsts.rsminoltascenemodelkup +msgid "0:Standard,1:Portrait,2:Text,3:Night Scene,4:Sunset,5:Sports,6:Landscape,7:Night Portrait,8:Macro,9:Super Macro,16:Auto,17:Night View/Portrait,18:Sweep Panorama,19:Handheld Night Shot,20:Anti Motion Blur,21:Cont. Priority AE,22:Auto+,23:3D Sweep Panorama,24:Superior Auto,25:High Sensitivity,26:Fireworks,27:Food,28:Pet,33:HDR,65535:n/a" +msgstr "" + +#: fpestrconsts.rsminoltasharpnesslkup +msgid "0:Hard,1:Normal,2:Soft" +msgstr "0:Hart,1:Normal,2:Weich" + +#: fpestrconsts.rsminoltasubjectprogramlkup +msgid "0:None,1:Portrait,2:Text,3:Night portrait,4:Sunset,5:Sports action" +msgstr "" + +#: fpestrconsts.rsminoltateleconverterlkup +msgid "$0:None,$4:Minolta/Sony AF 1.4x APO (D) (0x04),$5:Minolta/Sony AF 2x APO (D) (0x05),$48 = Minolta/Sony AF 2x APO (D),$50:Minolta AF 2x APO II,$60:Minolta AF 2x APO,$88:Minolta/Sony AF 1.4x APO (D),$90 = Minolta AF 1.4x APO II,$A0 = Minolta AF 1.4x APO" +msgstr "" + +#: fpestrconsts.rsminoltawhitebalancelkup +msgid "$00:Auto,$01:Color Temperature/Color Filter,$10:Daylight,$20:Cloudy,$30:Shade,$40:Tungsten,$50:Flash,$60:Fluorescent,$70:Custom" +msgstr "" + +#: fpestrconsts.rsminoltawidefocuszonelkup +msgid "0:No zone,1:Center zone (horizontal orientation),2:Center zone (vertical orientation),3:Left zone,4:Right zone" +msgstr "" + +#: fpestrconsts.rsminoltazonematchinglkup +msgid "0:ISO Setting Used,1:High Key,2:Low Key" +msgstr "" + +#: fpestrconsts.rsminsamplevalue +msgid "Min sample value" +msgstr "Minimaler Abtastwert" + +#: fpestrconsts.rsmodel +msgid "Model" +msgstr "Modell" + +#: fpestrconsts.rsmorethumbnailtagsthanexpected +msgid "More thumbnail tags than expected." +msgstr "Mehr Vorschaubild-Tags als erwartet." + +#: fpestrconsts.rsnikoncolormodelkup +msgid "1:Color,2:Monochrome" +msgstr "1:Farbe,2:Monochrom" + +#: fpestrconsts.rsnikonconverterlkup +msgid "0:Not used,1:Used" +msgstr "0:Nicht benutzt,1:Benutzt" + +#: fpestrconsts.rsnikonimgadjlkup +msgid "0:Normal,1:Bright+,2:Bright-,3:Contrast+,4:Contrast-" +msgstr "" + +#: fpestrconsts.rsnikonisolkup +msgid "0:ISO80,2:ISO160,4:ISO320,5:ISO100" +msgstr "" + +#: fpestrconsts.rsnikonqualitylkup +msgid "1:Vga Basic,2:Vga Normal,3:Vga Fine,4:SXGA Basic,5:SXGA Normal,6:SXGA Fine,10:2 Mpixel Basic,11:2 Mpixel Normal,12:2 Mpixel Fine" +msgstr "" + +#: fpestrconsts.rsnikonwhitebalancelkup +msgid "0:Auto,1:Preset,2:Daylight,3:Incandescense,4:Fluorescence,5:Cloudy,6:SpeedLight" +msgstr "" + +#: fpestrconsts.rsnormallowhigh +msgctxt "fpestrconsts.rsnormallowhigh" +msgid "0:Normal,1:Low,2:High" +msgstr "0:Normal,1:Niedrig,2:Hoch" + +#: fpestrconsts.rsnormalsofthard +msgctxt "fpestrconsts.rsnormalsofthard" +msgid "0:Normal,1:Soft,2:Hard" +msgstr "" +"0:Normal,1:Weich,2:\n" +"Hart" + +#: fpestrconsts.rsnovalidiptcfile +msgid "No valid IPTC file" +msgstr "Ungültige IPTC-Datei" + +#: fpestrconsts.rsnovalidiptcsignature +msgid "No valid IPTC signature" +msgstr "Ungültige IPTC-Signatur" + +#: fpestrconsts.rsnoyes +msgctxt "fpestrconsts.rsnoyes" +msgid "0:No,1:Yes" +msgstr "0:Nein,1:Ja" + +#: fpestrconsts.rsobjectattr +msgid "Object attribute reference" +msgstr "" + +#: fpestrconsts.rsobjectcycle +msgid "Object cycle" +msgstr "Objekt-Zyklus" + +#: fpestrconsts.rsobjectcyclelkup +msgid "a:morning,p:evening,b:both" +msgstr "" + +#: fpestrconsts.rsobjectname +msgid "Object name" +msgstr "Objekt-Name" + +#: fpestrconsts.rsobjecttype +msgid "Object type reference" +msgstr "" + +#: fpestrconsts.rsoffon +msgctxt "fpestrconsts.rsoffon" +msgid "0:Off,1:On" +msgstr "0:Aus,1:Ein" + +#: fpestrconsts.rsoffsettime +msgid "Time zone for date/time" +msgstr "Zeitzone für Datum/Uhrzeit" + +#: fpestrconsts.rsoffsettimedigitized +msgid "Time zone for date/time digitized" +msgstr "Zeitzone für Datum/Uhrzeit der Digitalisierung" + +#: fpestrconsts.rsoffsettimeoriginal +msgid "Time zone for date/time original" +msgstr "Zeitzone für Original-Datum/Uhrzeit" + +#: fpestrconsts.rsolympusccdscanmodelkup +msgid "0:Interlaced,1:Progressive" +msgstr "" + +#: fpestrconsts.rsolympuscontrastlkup +msgid "0:High,1:Normal,2:Low" +msgstr "0:Hoch,1:Normal,2:Niedrig" + +#: fpestrconsts.rsolympusflashdevlkup +msgid "0:None,1:Internal,4:External,5:Internal + External" +msgstr "" + +#: fpestrconsts.rsolympusflashmodelkup +msgid "2:On,3;Off" +msgstr "2:Ein,3:Aus" + +#: fpestrconsts.rsolympusflashmodellkup +msgid "0:None,1:FL-20,2:FL-50,3:RF-11,4:TF-22,5:FL-36,6:FL-50R,7:FL-36R,9:FL-14,11:FL-600R" +msgstr "" + +#: fpestrconsts.rsolympusflashtypelkup +msgid "0:None,2:Simple E-System,3:E-System" +msgstr "" + +#: fpestrconsts.rsolympusjpegquallkup +msgid "1:SQ,2:HQ,3:SHQ,4:Raw" +msgstr "" + +#: fpestrconsts.rsolympusmacrolkup +msgid "0:Off,1:On,2:Super Macro" +msgstr "0:Aus,1:Ein,2:Super-Makro" + +#: fpestrconsts.rsolympuspreviewimglength +msgid "Preview image length" +msgstr "Vorschaubildhöhe" + +#: fpestrconsts.rsolympuspreviewimgstart +msgid "Preview image start" +msgstr "Vorschaubild-Start" + +#: fpestrconsts.rsolympuspreviewimgvalid +msgid "Preview image valid" +msgstr "Vorschaubild ungültig" + +#: fpestrconsts.rsolympusscenemodelkup +msgid "0:Normal,1:Standard,2:Auto,3:Intelligent Auto,4:Portrait,5:Landscape+Portrait,6:Landscape,7:Night Scene,8:Night+Portrait9:Sport,10:Self Portrait,11:Indoor,12:Beach & Snow,13:Beach,14:Snow,15:Self Portrait+Self Timer,16:Sunset,17:Cuisine,18:Documents,19:Candle,20:Fireworks,21:Available Light,22:Vivid,23:Underwater Wide1,24:Underwater Macro,25:Museum,26:Behind Glass,27:Auction,28:Shoot & Select1,29:Shoot & Select2,30:Underwater Wide2,31:Digital Image Stabilization,32:Face Portrait,33:Pet,34:Smile Shot,35:Quick Shutter,43:Hand-held Starlight,100:Panorama,101:Magic Filter,103:HDR" +msgstr "" + +#: fpestrconsts.rsolympussharpnesslkup +msgid "0:Normal,1:Hard,2:Soft" +msgstr "0:Normal,1:Hart,2:Weich" + +#: fpestrconsts.rsorientation +msgid "Orientation" +msgstr "Ausrichtung" + +#: fpestrconsts.rsorientationlkup +msgid "1:Horizontal (normal),2:Mirror horizontal,3:Rotate 180,4:Mirror vertical,5:Mirror horizontal and rotate 270 CW,6:Rotate 90 CW,7:Mirror horizontal and rotate 90 CW,8:Rotate 270 CW" +msgstr "" + +#: fpestrconsts.rsoriginatingprog +msgid "Originating program" +msgstr "Ursprungsprogramm" + +#: fpestrconsts.rsownername +msgid "Owner name" +msgstr "Name des Besitzers" + +#: fpestrconsts.rspagename +msgid "Page name" +msgstr "Seitenname" + +#: fpestrconsts.rspagenumber +msgid "Page number" +msgstr "Seitennummer" + +#: fpestrconsts.rsphotometricint +msgid "Photometric interpretation" +msgstr "" + +#: fpestrconsts.rsphotometricintlkup +msgid "0:White is zero,1:Black is zero,2:RGB,3:RGB palette,4:Transparency mask,5:CMYK,6:YCbCr,8:CIELab,9:ICCLab,10:ITULab,32803:Color filter array,32844:Pixar LogL,32845:Pixar LogLuv,34892:Linear Raw" +msgstr "" + +#: fpestrconsts.rsplanarconfiguration +msgid "Planar configuration" +msgstr "" + +#: fpestrconsts.rsplanarconfigurationlkup +msgid "1:Chunky,2:Planar" +msgstr "" + +#: fpestrconsts.rspredictor +msgid "Predictor" +msgstr "" + +#: fpestrconsts.rspredictorlkup +msgid "1:None,2:Horizontal differencing" +msgstr "" + +#: fpestrconsts.rspressure +msgid "Pressure" +msgstr "Druck" + +#: fpestrconsts.rsprimarychromaticities +msgid "Primary chromaticities" +msgstr "" + +#: fpestrconsts.rsprogversion +msgid "Program version" +msgstr "Programm-Version" + +#: fpestrconsts.rsquality +msgid "Quality" +msgstr "Qualität" + +#: fpestrconsts.rsrangecheckerror +msgid "Range check error." +msgstr "" + +#: fpestrconsts.rsreadincompleteifdrecord +msgid "Read incomplete IFD record at stream position %d." +msgstr "" + +#: fpestrconsts.rsrecexpindex +msgid "Recommended exposure index" +msgstr "Empfohlener Belichtungsindex" + +#: fpestrconsts.rsrecordversion +msgid "Record version" +msgstr "Datensatzversion" + +#: fpestrconsts.rsrefblackwhite +msgid "Reference black & white" +msgstr "" + +#: fpestrconsts.rsrefdate +msgid "Reference date" +msgstr "Referenzdatum" + +#: fpestrconsts.rsrefnumber +msgid "Reference number" +msgstr "Referenznummer" + +#: fpestrconsts.rsrefservice +msgid "Reference service" +msgstr "" + +#: fpestrconsts.rsrelatedimagefileformat +msgid "Related image file format" +msgstr "" + +#: fpestrconsts.rsrelatedimageheight +msgid "Related image height" +msgstr "" + +#: fpestrconsts.rsrelatedimagewidth +msgid "Related image width" +msgstr "" + +#: fpestrconsts.rsrelatedsoundfile +msgid "Related sound file" +msgstr "" + +#: fpestrconsts.rsreleasedate +msgid "Release date" +msgstr "Freigabe-Datum" + +#: fpestrconsts.rsreleasetime +msgid "Release time" +msgstr "Freigabe-Uhrzeit" + +#: fpestrconsts.rsresolutionunit +msgid "Resolution unit" +msgstr "Auflösungseinheit" + +#: fpestrconsts.rsresolutionunitlkup +msgid "1:None,2:inches,3:cm" +msgstr "1:Nichts,2:Zoll,3:cm" + +#: fpestrconsts.rsrowsperstrip +msgid "Rows per strip" +msgstr "" + +#: fpestrconsts.rssamplesperpixel +msgid "Samples per pixel" +msgstr "" + +#: fpestrconsts.rssanyomacrolkup +msgid "0:Normal,1:Macro,2:View,3:Manual" +msgstr "0:Normal,1.Makro,2:Ansicht,3:Manuell" + +#: fpestrconsts.rssanyoqualitylkup +msgid "0:Normal/Very Low,1:Normal/Low,2:Normal/Medium Low,3:Normal/Medium,4:Normal/Medium High,5:Normal/High,6:Normal/Very High7:Normal/Super High,256:Fine/Very Low,257:Fine/Low,258:Fine/Medium Low259:Fine/Medium,260:Fine/Medium High,261:Fine/High,262:Fine/Very High263:Fine/Super High,512:Super Fine/Very Low,513:Super Fine/Low,514:Super Fine/Medium Low,515:Super Fine/Medium,516:Super Fine/Medium High,517:Super Fine/High,518:Super Fine/Very High,519:Super Fine/Super High" +msgstr "" + +#: fpestrconsts.rssanyospecialmode +msgid "Special mode" +msgstr "Spezialmodus" + +#: fpestrconsts.rssaturation +msgid "Saturation" +msgstr "Sättigung" + +#: fpestrconsts.rsscenecapturetype +msgid "Scene capture type" +msgstr "" + +#: fpestrconsts.rsscenecapturetypelkup +msgid "0:Standard,1:Landscape,2:Portrait,3:Night" +msgstr "0:Standard,1:Landschaft,2:Porträt,3:Nachaufnahme" + +#: fpestrconsts.rsscenetype +msgid "Scene type" +msgstr "Motiv-Typ" + +#: fpestrconsts.rsscenetypelkup +msgid "0:Unknown,1:Directly photographed" +msgstr "0:Unbekannt,1:Direkt fotografiert" + +#: fpestrconsts.rssecurityclassification +msgid "Security classification" +msgstr "Sicherheitsklassifizierung" + +#: fpestrconsts.rsselftimermode +msgid "Self-timer mode" +msgstr "Selbstauslöser-Modus" + +#: fpestrconsts.rsseminfo +msgid "SEM info" +msgstr "" + +#: fpestrconsts.rssensingmethod +msgid "Sensing method" +msgstr "" + +#: fpestrconsts.rssensingmethodlkup +msgid "1:Not defined,2:One-chip color area,3:Two-chip color area,4:Three-chip color area,5:Color sequential area,7:Trilinear,8:Color sequential linear" +msgstr "" + +#: fpestrconsts.rssensitivitytype +msgid "Sensitivity type" +msgstr "" + +#: fpestrconsts.rssensitivitytypelkup +msgid "0:Unknown,1:Standard Output Sensitivity2:Recommended exposure index,3:ISO speed,4:Standard output sensitivity and recommended exposure index,5:Standard output sensitivity and ISO Speed,6:Recommended exposure index and ISO speed,7:Standard output sensitivity, recommended exposure index and ISO speed" +msgstr "" + +#: fpestrconsts.rsserialnumber +msgid "Serial number" +msgstr "Seriennummer" + +#: fpestrconsts.rssharpness +msgid "Sharpness" +msgstr "Schärfe" + +#: fpestrconsts.rsshutterspeedvalue +msgid "Shutter speed value" +msgstr "Verschlusszeit" + +#: fpestrconsts.rssinglecontinuous +msgctxt "fpestrconsts.rssinglecontinuous" +msgid "0:Single,1:Continuous" +msgstr "0:Einzeln,1:Ständig" + +#: fpestrconsts.rssoftware +msgid "Software" +msgstr "Software" + +#: fpestrconsts.rssource +msgid "Source" +msgstr "Quelle" + +#: fpestrconsts.rsspatialfrequresponse +msgid "Spatial frequency response" +msgstr "" + +#: fpestrconsts.rsspecialinstruct +msgid "Special instructions" +msgstr "Spezielle Anweisungen" + +#: fpestrconsts.rsspectralsensitivity +msgid "Spectral sensitivity" +msgstr "" + +#: fpestrconsts.rsstate +msgid "Province/State" +msgstr "Provinz/Staat" + +#: fpestrconsts.rsstdoutputsens +msgid "Standard output sensitivity" +msgstr "" + +#: fpestrconsts.rsstripbytecounts +msgid "Strip byte counts" +msgstr "" + +#: fpestrconsts.rsstripoffsets +msgid "Strip offsets" +msgstr "" + +#: fpestrconsts.rssubfile +msgid "Subfile" +msgstr "Unterdatei" + +#: fpestrconsts.rssubfiletypelkup +msgid "0:Full-resolution image,1:Reduced-resolution image,2:Single page of multi-page image,3:Single page of multi-page reduced-resolution image,4:Transparency mask,5:Transparency mask of reduced-resolution image,6:Transparency mask of multi-page image,7:Transparency mask of reduced-resolution multi-page image" +msgstr "" + +#: fpestrconsts.rssubjectarea +msgid "Subject area" +msgstr "Objektfläche" + +#: fpestrconsts.rssubjectdistance +msgid "Subject distance" +msgstr "Objektabstand" + +#: fpestrconsts.rssubjectdistancerange +msgid "Subject distance range" +msgstr "" + +#: fpestrconsts.rssubjectdistancerangelkup +msgid "0:Unknown,1:Macro,2:Close,3:Distant" +msgstr "0:Unbekannt,1:Makro,2:Nah,3:Fern" + +#: fpestrconsts.rssubjectlocation +msgid "Subject location" +msgstr "" + +#: fpestrconsts.rssubjectref +msgid "Subject reference" +msgstr "" + +#: fpestrconsts.rssublocation +msgid "Sublocation" +msgstr "" + +#: fpestrconsts.rssubsectime +msgid "Fractional seconds of date/time" +msgstr "" + +#: fpestrconsts.rssubsectimedigitized +msgid "Fractional seconds of date/time digitized" +msgstr "" + +#: fpestrconsts.rssubsectimeoriginal +msgid "Fractional seconds of date/time original" +msgstr "" + +#: fpestrconsts.rssuppcategory +msgid "Supplemental category" +msgstr "Zusatzkategorie" + +#: fpestrconsts.rstagtypenotsupported +msgid "Tag \"%s\" has an unsupported type." +msgstr "Der Typ von Tag \"%s\" wird nicht unterstützt." + +#: fpestrconsts.rstargetprinter +msgid "Target printer" +msgstr "" + +#: fpestrconsts.rstemperature +msgid "Temperature" +msgstr "Temperatur" + +#: fpestrconsts.rsthresholding +msgid "Thresholding" +msgstr "" + +#: fpestrconsts.rsthresholdinglkup +msgid "1:No dithering or halftoning,2:Ordered dither or halftone,3:Randomized dither" +msgstr "" + +#: fpestrconsts.rsthumbnailheight +msgid "Thumbnail height" +msgstr "Vorschaubild-Höhe" + +#: fpestrconsts.rsthumbnailoffset +msgid "Thumbnail offset" +msgstr "Vorschaubild-Offset" + +#: fpestrconsts.rsthumbnailsize +msgid "Thumbnail size" +msgstr "Vorschaubild-Größe" + +#: fpestrconsts.rsthumbnailwidth +msgid "Thumbnail width" +msgstr "Vorschaubild-Breite" + +#: fpestrconsts.rstilelength +msgid "Tile length" +msgstr "" + +#: fpestrconsts.rstilewidth +msgid "Tile width" +msgstr "" + +#: fpestrconsts.rstimecreated +msgid "Time created" +msgstr "" + +#: fpestrconsts.rstimezoneoffset +msgid "Time zone offset" +msgstr "Zeitzonenoffset" + +#: fpestrconsts.rstransferfunction +msgid "Transfer function" +msgstr "Übertragungsfunktion" + +#: fpestrconsts.rstransmissionref +msgid "Original transmission reference" +msgstr "" + +#: fpestrconsts.rsunknownimageformat +msgid "Unknown image format." +msgstr "Unbekanntes Bildformat." + +#: fpestrconsts.rsurgency +msgid "Urgency" +msgstr "Dringlichkeit" + +#: fpestrconsts.rsurgencylkup +msgid "0:reserved,1:most urgent,5:normal,8:least urgent,9:reserved" +msgstr "" + +#: fpestrconsts.rsusercomment +msgid "User comment" +msgstr "Benutzerkommentar" + +#: fpestrconsts.rswaterdepth +msgid "Water depth" +msgstr "Wassertiefe" + +#: fpestrconsts.rswhitebalance +msgid "White balance" +msgstr "Weißabgleich" + +#: fpestrconsts.rswhitepoint +msgid "White point" +msgstr "Weißpunkt" + +#: fpestrconsts.rswritingnotimplemented +msgid "Writing of %s files not yet implemented." +msgstr "" + +#: fpestrconsts.rsxposition +msgid "X position" +msgstr "X-Position" + +#: fpestrconsts.rsxresolution +msgid "X resolution" +msgstr "X-Auflösung" + +#: fpestrconsts.rsycbcrcoefficients +msgid "YCbCr coefficients" +msgstr "" + +#: fpestrconsts.rsycbcrpositioning +msgid "YCbCr positioning" +msgstr "" + +#: fpestrconsts.rsycbcrposlkup +msgid "1:Centered,2:Co-sited" +msgstr "" + +#: fpestrconsts.rsycbcrsubsampling +msgid "YCbCr subsampling" +msgstr "" + +#: fpestrconsts.rsyposition +msgid "Y position" +msgstr "Y-Position" + +#: fpestrconsts.rsyresolution +msgid "Y resolution" +msgstr "Y-Auflösung" diff --git a/components/fpexif/languages/fpestrconsts.po b/components/fpexif/languages/fpestrconsts.po new file mode 100644 index 000000000..5344e26bb --- /dev/null +++ b/components/fpexif/languages/fpestrconsts.po @@ -0,0 +1,1682 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8" + +#: fpestrconsts.rsacceleration +msgid "Acceleration" +msgstr "" + +#: fpestrconsts.rsactionadvised +msgid "Action advised" +msgstr "" + +#: fpestrconsts.rsaperturevalue +msgid "Aperture value" +msgstr "" + +#: fpestrconsts.rsartist +msgid "Artist" +msgstr "" + +#: fpestrconsts.rsautomanual +msgid "0:Auto,1:Manual" +msgstr "" + +#: fpestrconsts.rsbitspersample +msgid "Bits per sample" +msgstr "" + +#: fpestrconsts.rsbrightnessvalue +msgid "Brightness value" +msgstr "" + +#: fpestrconsts.rsbyline +msgid "ByLine" +msgstr "" + +#: fpestrconsts.rsbylinetitle +msgid "ByLine title" +msgstr "" + +#: fpestrconsts.rscameraelevationangle +msgid "Camera elevation angle" +msgstr "" + +#: fpestrconsts.rscannotsavetounknownfileformat +msgid "The metadata structure cannot be saved because the file format of the receiving file is not known or not supported." +msgstr "" + +#: fpestrconsts.rscanonaelkup +msgid "0:Normal AE,1:Exposure compensation,2:AE lock,3:AE lock + Exposure compensation,4:No AE" +msgstr "" + +#: fpestrconsts.rscanonaflkup +msgid "$2005:Manual AF point selection,$3000:None (MF),$3001:Auto AF point selection,$3002:Right,$3003:Center,$3004:Left,$4001:Auto AF point selection,$4006:Face Detect" +msgstr "" + +#: fpestrconsts.rscanonautorotlkup +msgid "0:None,1:Rotate 90 CW,2:Rotate 180,3:Rotate 270 CW" +msgstr "" + +#: fpestrconsts.rscanonbiaslkup +msgid "65472:-2 EV,65484:-1.67 EV,65488:-1.50 EV,65492:-1.33 EV,65504:-1 EV,65516:-0.67 EV,65520:-0.50 EV,65524:-0.33 EV,0:0 EV,12:0.33 EV,16:0.50 EV,20:0.67 EV,32:1 EV,44:1.33 EV,48:1.50 EV,52:1.67 EV,64:2 EV" +msgstr "" + +#: fpestrconsts.rscanoncamtypelkup +msgid "248:EOS High-end,250:Compact,252:EOS Mid-range,255:DV Camera" +msgstr "" + +#: fpestrconsts.rscanoneasylkup +msgid "0:Full Auto,1:Manual,2:Landscape,3:Fast Shutter,4:Slow Shutter,5:Night,6:Gray scale,7:Sepia,8:Portrait,9:Sports,10:Macro,11:Black & White,12:Pan Focus,13:Vivid,14:Neutral,15:Flash off,16:Long shutter,17:Super macro,18:Foliage,19:Indoor,20:Fireworks,21:Beach,22:Underwater,23:Snow,24:Kids & Pets,25:Night snapshot,26:Digital macro,27:My colors,28:Movie snap,29:Super macro 2,30:Color accent,31:Color swap,32:Aquarium,33:ISO3200,34:ISO6400,35:Creative light effect,36:Easy,37:Quick shot,38:Creative auto,39:Zoom blur,40:Low light,41:Nostalgic,42:Super vivid,43:Poster effect,44:Face self-timer,45:Smile,46:Wink self-timer,47:Fisheye effect,48:Miniature effect,49:High-speed burst,50:Best image selection,51:High dynamic range,52:Handheld night scene,53:Movie digest,54:Live view control,55:Discreet,56:Blur reduction,57:Monochrome,58:Toy camera effect,59:Scene intelligent auto,60:High-speed burst HQ,61:Smooth skin,62:Soft focus,257:Spotlight,258:Night 2,259:Night+,260:Super night,261:Sunset,263:Night scene,264:Surface,265:Low light 2" +msgstr "" + +#: fpestrconsts.rscanonexposelkup +msgid "0:Easy shooting,1:Program AE,2:Shutter speed priority AE,3:Aperture priority AE,4:Manual,5:Depth-of-field AE,6:M-Dep,7:Bulb" +msgstr "" + +#: fpestrconsts.rscanonflashactlkup +msgid "0:Did not fire,1:Fired" +msgstr "" + +#: fpestrconsts.rscanonflashlkup +msgid "0:Not fired,1:Auto,2:On,3:Red-eye,4:Slow sync,5:Auto+red-eye,6:On+red eye,16:External flash" +msgstr "" + +#: fpestrconsts.rscanonfocaltypelkup +msgid "1:Fixed,2:Zoom" +msgstr "" + +#: fpestrconsts.rscanonfoctypelkup +msgid "0:Manual,1:Auto,3:Close-up (macro),8:Locked (pan mode)" +msgstr "" + +#: fpestrconsts.rscanonfocuslkup +msgid "0:One-Shot AF,1:AI Servo AF,2:AI Focus AF,3:Manual focus,4:Single,5:Continuous,6:Manual focus,16:Pan focus,256:AF+MF,512:Movie snap focus,519:Movie servo AF" +msgstr "" + +#: fpestrconsts.rscanongenlkup +msgid "65535:Low,0:Normal,1:High" +msgstr "" + +#: fpestrconsts.rscanonimgstablkup +msgid "0:Off,1:On,2:Shoot only,3:Panning,4:Dynamic,256:Off,257:On,258:Shoot only,259:Panning,260:Dynamic" +msgstr "" + +#: fpestrconsts.rscanonisolkup +msgid "0:Not used,15:auto,16:50,17:100,18:200,19:400" +msgstr "" + +#: fpestrconsts.rscanonmacrolkup +msgid "1:Macro,2:Normal" +msgstr "" + +#: fpestrconsts.rscanonmeterlkup +msgid "0:Default,1:Spot,2:Average,3:Evaluative,4:Partial,5:Center-weighted average" +msgstr "" + +#: fpestrconsts.rscanonpandirlkup +msgid "0:Left to right,1:Right to left,2:Bottom to top,3:Top to bottom,4:2x2 Matrix (clockwise)" +msgstr "" + +#: fpestrconsts.rscanonqualitylkup +msgid "65535:n/a,1:Economy,2:Normal,3:Fine,4:RAW,5:Superfine,130:Normal Movie,131:Movie (2)" +msgstr "" + +#: fpestrconsts.rscanonreclkup +msgid "1:JPEG,2:CRW+THM,3:AVI+THM,4:TIF,5:TIF+JPEG,6:CR2,7:CR2+JPEG,9:MOV,10:MP4" +msgstr "" + +#: fpestrconsts.rscanonsizelkup +msgid "65535:n/a,0:Large,1:Medium,2:Small,4:5 MPixel,5:2 MPixel,6:1.5 MPixel,8:Postcard,9:Widescreen,10:Medium widescreen,14:Small 1,15:Small 2,16:Small 3,128:640x480 movie,129:Medium movie,130:Small movie,137:128x720 movie,142:1920x1080 movie" +msgstr "" + +#: fpestrconsts.rscanonsloshuttlkup +msgid "65535:n/a,0:Off,1:Night scene,2:On,3:None" +msgstr "" + +#: fpestrconsts.rscanonwhiteballkup +msgid "0:Auto,1:Daylight,2:Cloudy,3:Tungsten,4:Flourescent,5:Flash,6:Custom,7:Black & white,8:Shade,9:Manual temperature (Kelvin),14:Daylight fluorescent,17:Under water" +msgstr "" + +#: fpestrconsts.rscanonzoomlkup +msgid "0:None,1:2x,2:4x,3:Other" +msgstr "" + +#: fpestrconsts.rscasioafmode2lkup +msgid "0:Off,1:Spot,2:Multi,3:Face detection,4:Tracking,5:Intelligent" +msgstr "" + +#: fpestrconsts.rscasioartmode2lkup +msgid "0:Normal,8:Silent movie,39:HDR,45:Premium auto,47:Painting,49:Crayon drawing,51:Panorama,52:Art HDR,62:High Speed night shot,64:Monochrome,67:Toy camera,68:Pop art,69:Light tone" +msgstr "" + +#: fpestrconsts.rscasioautoiso2lkup +msgid "1:On,2:Off,7:On (high sensitivity),8:On (anti-shake),10:High Speed" +msgstr "" + +#: fpestrconsts.rscasioccdsensitivitylkup +msgid "64:Normal,125:+1.0,250:+2.0,244:+3.0,80:Normal,100:High" +msgstr "" + +#: fpestrconsts.rscasiocolorfilter2lkup +msgid "0:Off,1:Blue,3:Green,4:Yellow,5:Red,6:Purple,7:Pink" +msgstr "" + +#: fpestrconsts.rscasiocolormode2lkup +msgid "0:Off,2:Black & White,3:Sepia" +msgstr "" + +#: fpestrconsts.rscasiodigitalzoomlkup +msgid "$10000:Off,$10001:2x Digital zoom,$20000:2x digital zoom,$40000:4x digital zoom" +msgstr "" + +#: fpestrconsts.rscasiodrivemode2lkup +msgid "0:Single shot,1:Continuous shooting,2:Continuous (2 fps),3:Continuous (3 fps),4:Continuous (4 fps),5:Continuous (5 fps),6:Continuous (6 fps),7:Continuous (7 fps),10:Continuous (10 fps),12:Continuous (12 fps),15:Continuous (15 fps),20:Continuous (20 fps),30:Continuous (30 fps),40:Continuous (40 fps),60:Continuous (60 fps),240:Auto-N" +msgstr "" + +#: fpestrconsts.rscasioenhancement2lkup +msgid "0:Off,1:Scenery,3:Green,5:Underwater,9:Flesh tones" +msgstr "" + +#: fpestrconsts.rscasioflashintensitylkup +msgid "11:Weak,13:Normal,15:Strong" +msgstr "" + +#: fpestrconsts.rscasioflashmodelkup +msgid "1:Auto,2:On,3:Off,4:Red-eye reduction" +msgstr "" + +#: fpestrconsts.rscasiofocusingmodelkup +msgid "2:Macro,3:Auto focus,4:Manual focus,5:Infinity" +msgstr "" + +#: fpestrconsts.rscasiofocusmode22lkup +msgid "0:Manual,1:Focus lock,2:Macro,3:Single-area auto focus,5:Infinity,6:Multi-area auto focus,8:Super macro" +msgstr "" + +#: fpestrconsts.rscasiofocusmode2lkup +msgid "0:Normal,1:Macro" +msgstr "" + +#: fpestrconsts.rscasioimagesize2lkup +msgid "0:640 x 480,4:1600 x 1200,5:2048 x 1536,20:2288 x 1712,21:2592 x 1944,22:2304 x 1728,36:3008 x 2008" +msgstr "" + +#: fpestrconsts.rscasioimagestabilization2lkup +msgid "0:Off,1:On,2:Best shot,3:Movie anti-shake" +msgstr "" + +#: fpestrconsts.rscasioisospeed2lkup +msgid "3 = 50,4:64,6:100,9:200" +msgstr "" + +#: fpestrconsts.rscasiolightingmode2lkup +msgid "0:Off,1:High dynamic range,5:Shadow enhance low,6:Shadow enhance high" +msgstr "" + +#: fpestrconsts.rscasioportraitrefiner2lkup +msgid "0:Off,1:+1,2:+2" +msgstr "" + +#: fpestrconsts.rscasiorecordingmodelkup +msgid "1:Single shutter,2:Panorama,3:Night scene,4:Portrait,5:Landscape" +msgstr "" + +#: fpestrconsts.rscasiorecordmode2lkup +msgid "2:Program AE,3:Shutter priority,4:Aperture priority,5:Manual,6:Best shot,17:Movie,19:Movie (19),20:YouTube Movie" +msgstr "" + +#: fpestrconsts.rscasioreleasemode2lkup +msgid "1:Normal,3:AE Bracketing,11:WB Bracketing,13 = Contrast Bracketing,19:High Speed Burst" +msgstr "" + +#: fpestrconsts.rscasiosharpness2lkup +msgid "0:Soft,1:Normal,2:Hard" +msgstr "" + +#: fpestrconsts.rscasiospecialeffectsetting2lkup +msgid "0:Off,1:Makeup,2:Mist removal,3:Vivid landscape,16:Art shot" +msgstr "" + +#: fpestrconsts.rscasiovideoquality2lkup +msgid "1:Standard,3:HD (720p),4:Full HD (1080p),5:Low" +msgstr "" + +#: fpestrconsts.rscasiowhitebalance22lkup +msgid "0:Manual,1:Daylight,2:Cloudy,3:Shade,4:Flash?,6:Fluorescent,9:Tungsten?,10:Tungsten,12:Flash" +msgstr "" + +#: fpestrconsts.rscasiowhitebalance2lkup +msgid "0:Auto,1:Daylight,2:Shade,3:Tungsten,4:Fluorescent,5:Manual" +msgstr "" + +#: fpestrconsts.rscasiowhitebalancelkup +msgid "1:Auto,2:Tungsten,3:Daylight,4:Fluorescent,5:Shade,129:Manual" +msgstr "" + +#: fpestrconsts.rscategory +msgid "Category" +msgstr "" + +#: fpestrconsts.rscellheight +msgid "Cell height" +msgstr "" + +#: fpestrconsts.rscellwidth +msgid "Cell width" +msgstr "" + +#: fpestrconsts.rscfapattern +msgid "CFA pattern" +msgstr "" + +#: fpestrconsts.rscity +msgid "City" +msgstr "" + +#: fpestrconsts.rscodedcharset +msgid "Coded character set" +msgstr "" + +#: fpestrconsts.rscolorspace +msgid "Color space" +msgstr "" + +#: fpestrconsts.rscolorspacelkup +msgid "0:sBW,1:sRGB,2:Adobe RGB,65533:Wide Gamut RGB,65534:ICC Profile,65535:Uncalibrated" +msgstr "" + +#: fpestrconsts.rscomponentsconfig +msgid "Components configuration" +msgstr "" + +#: fpestrconsts.rscompressedbitsperpixel +msgid "Compressed bits per pixel" +msgstr "" + +#: fpestrconsts.rscompression +msgid "Compression" +msgstr "" + +#: fpestrconsts.rscompressionlkup +msgid "1:Uncompressed,2:CCITT 1D,3:T4/Group 3 Fax,4:T6/Group 4 Fax,5:LZW,6:JPEG (old-style),7:JPEG,8:Adobe Deflate,9:JBIG B&W,10:JBIG Color,99:JPEG,262:Kodak 262,32766:Next,32767:Sony ARW Compressed,32769:Packed RAW,32770:Samsung SRW Compressed,32771:CCIRLEW,32772:Samsung SRW Compressed 2,32773:PackBits,32809:Thunderscan,32867:Kodak KDC Compressed,32895:IT8CTPAD,32896:IT8LW,32897:IT8MP,32898:IT8BL,32908:PixarFilm,32909:PixarLog,32946:Deflate,32947:DCS,34661:JBIG,34676:SGILog,34677:SGILog24,34712:JPEG 2000,34713:Nikon NEF Compressed,34715:JBIG2 TIFF FX,34718:Microsoft Document Imaging (MDI) Binary Level Codec,34719:Microsoft Document Imaging (MDI) Progressive Transform Codec,34720:Microsoft Document Imaging (MDI) Vector,34892:Lossy JPEG,65000:Kodak DCR Compressed,65535:Pentax PEF Compressed" +msgstr "" + +#: fpestrconsts.rscontact +msgid "Contact" +msgstr "" + +#: fpestrconsts.rscontentloccode +msgid "Content location code" +msgstr "" + +#: fpestrconsts.rscontentlocname +msgid "Content location name" +msgstr "" + +#: fpestrconsts.rscontrast +msgid "Contrast" +msgstr "" + +#: fpestrconsts.rscopyright +msgid "Copyright" +msgstr "" + +#: fpestrconsts.rscustomrendered +msgid "Custom rendered" +msgstr "" + +#: fpestrconsts.rscustomrenderedlkup +msgid "0:Normal,1:Custom" +msgstr "" + +#: fpestrconsts.rsdatecreated +msgid "Date created" +msgstr "" + +#: fpestrconsts.rsdatetime +msgid "Date/time" +msgstr "" + +#: fpestrconsts.rsdatetimedigitized +msgid "Date/time digitized" +msgstr "" + +#: fpestrconsts.rsdatetimeoriginal +msgid "Date/time original" +msgstr "" + +#: fpestrconsts.rsdevicesettingdescription +msgid "Device setting description" +msgstr "" + +#: fpestrconsts.rsdigitalzoom +msgid "Digital zoom" +msgstr "" + +#: fpestrconsts.rsdigitalzoomratio +msgid "Digital zoom ratio" +msgstr "" + +#: fpestrconsts.rsdigitizedate +msgid "Digital creation date" +msgstr "" + +#: fpestrconsts.rsdigitizetime +msgid "Digital creation time" +msgstr "" + +#: fpestrconsts.rsdocumentname +msgid "Document name" +msgstr "" + +#: fpestrconsts.rseconomynormalfine +msgctxt "fpestrconsts.rseconomynormalfine" +msgid "0:Economy,1:Normal,2:Fine" +msgstr "" + +#: fpestrconsts.rseconomynormalfine1 +msgctxt "fpestrconsts.rseconomynormalfine1" +msgid "1:Economy,2:Normal,3:Fine" +msgstr "" + +#: fpestrconsts.rseditorialupdate +msgid "Editorial update" +msgstr "" + +#: fpestrconsts.rseditstatus +msgid "Edit status" +msgstr "" + +#: fpestrconsts.rsexifimageheight +msgid "EXIF image height" +msgstr "" + +#: fpestrconsts.rsexifimagewidth +msgid "EXIF image width" +msgstr "" + +#: fpestrconsts.rsexifoffset +msgid "EXIF offset" +msgstr "" + +#: fpestrconsts.rsexifversion +msgid "EXIF version" +msgstr "" + +#: fpestrconsts.rsexpiredate +msgid "Expiration date" +msgstr "" + +#: fpestrconsts.rsexpiretime +msgid "Expiration time" +msgstr "" + +#: fpestrconsts.rsexposurebiasvalue +msgid "Exposure bias value" +msgstr "" + +#: fpestrconsts.rsexposureindex +msgid "Exposure index" +msgstr "" + +#: fpestrconsts.rsexposuremode +msgid "Exposure mode" +msgstr "" + +#: fpestrconsts.rsexposuremodelkup +msgid "0:Auto,1:Manual,2:Auto bracket" +msgstr "" + +#: fpestrconsts.rsexposureprogram +msgid "Exposure program" +msgstr "" + +#: fpestrconsts.rsexposureprogramlkup +msgid "0:Not defined,1:Manual,2:Program AE,3:Aperture-priority AE,4:Shutter speed priority AE,5:Creative (slow speed),6:Action (high speed),7:Portrait,8:Landscape;9:Bulb" +msgstr "" + +#: fpestrconsts.rsexposuretime +msgid "Exposure time" +msgstr "" + +#: fpestrconsts.rsextensiblemetadataplatform +msgid "Extensible metadata platform" +msgstr "" + +#: fpestrconsts.rsfilenotfounderror +msgid "File \"%s\" not found." +msgstr "" + +#: fpestrconsts.rsfilesource +msgid "File source" +msgstr "" + +#: fpestrconsts.rsfilesourcelkup +msgid "0:Unknown,1:Film scanner,2:Reflection print scanner,3:Digital camera" +msgstr "" + +#: fpestrconsts.rsfillorder +msgid "Fill order" +msgstr "" + +#: fpestrconsts.rsfillorderlkup +msgid "1:Normal,2:Reversed" +msgstr "" + +#: fpestrconsts.rsfixtureid +msgid "Fixture ID" +msgstr "" + +#: fpestrconsts.rsflash +msgid "Flash" +msgstr "" + +#: fpestrconsts.rsflashenergy +msgid "Flash energy" +msgstr "" + +#: fpestrconsts.rsflashlkup +msgid "0:No flash,1:Fired,5:Fired; return not detected,7:Fired; return detected,8:On; did not fire,9:On; fired,13:On; return not detected,15:On; return detected,16:Off; did not fire,20:Off; did not fire, return not detected,24:Auto; did not fire,25:Auto; fired;29:Auto; fired; return not detected,31:Auto; fired; return detected,32:No flash function,48:Off, no flash function,65:Fired; red-eye reduction,69:Fired; red-eye reduction; return not detected,71:Fired; red-eye reduction; return detected,73:On; red-eye reduction,77:On; red-eye reduction, return not detected,79:On, red-eye reduction, return detected,80:Off; red-eye reduction,88:Auto; did not fire; red-eye reduction,89:Auto; fired; red-eye reduction,93:Auto; fired; red-eye reduction; return not detected,95:Auto; fired; red-eye reduction, return detected" +msgstr "" + +#: fpestrconsts.rsflashpixversion +msgid "FlashPix version" +msgstr "" + +#: fpestrconsts.rsfnumber +msgid "F number" +msgstr "" + +#: fpestrconsts.rsfocallength +msgid "Focal length" +msgstr "" + +#: fpestrconsts.rsfocallengthin35mm +msgid "Focal length in 35 mm film" +msgstr "" + +#: fpestrconsts.rsfocalplaneresunit +msgid "Focal plane resolution unit" +msgstr "" + +#: fpestrconsts.rsfocalplaneresunitlkup +msgid "1:None,2:inches,3:cm,4:mm,5:um" +msgstr "" + +#: fpestrconsts.rsfocalplanexres +msgid "Focal plane x resolution" +msgstr "" + +#: fpestrconsts.rsfocalplaneyres +msgid "Focal plane y resolution" +msgstr "" + +#: fpestrconsts.rsfujiadvancedfilterlkup +msgid "65536:Pop Color,131072:Hi Key,196608:Toy Camera,262144:Miniature, 327680:Dynamic Tone,327681:Partial Color Red,327682:Partial Color Yellow,327683:Partial Color Green,327684:Partial Color Blue,327685:Partial Color Orange,327686:Partial Color Purple,458752:Soft Focus,589824:Low Key" +msgstr "" + +#: fpestrconsts.rsfujiautobracketinglkup +msgid "0:Off,1:On,2:No flash & flash" +msgstr "" + +#: fpestrconsts.rsfujiblurwarninglkup +msgid "0:None,1:Blur Warning" +msgstr "" + +#: fpestrconsts.rsfujicolormodelkup +msgid "0:Standard,16:Chrome,48:B & W" +msgstr "" + +#: fpestrconsts.rsfujicontrastlkup +msgid "0:Normal,128:Medium High,256:High,384:Medium Low,512:Low,32768:Film Simulation" +msgstr "" + +#: fpestrconsts.rsfujicontrastlkup1 +msgid "0:Normal,256:High,768:Low" +msgstr "" + +#: fpestrconsts.rsfujidynamicrangelkup +msgid "1:Standard,3:Wide" +msgstr "" + +#: fpestrconsts.rsfujiexposurewarninglkup +msgid "0:Good,1:Bad exposure" +msgstr "" + +#: fpestrconsts.rsfujiexrmodelkup +msgid "128:HR (High Resolution),512:SN (Signal to Noise priority),768:DR (Dynamic Range priority)" +msgstr "" + +#: fpestrconsts.rsfujiflashmodelkup +msgid "0:Auto,1:On,2:Off,3:Red-eye reduction,4:External,16:Commander,32768:Not Attached,33056:TTL,38976:Manual,39040:Multi-flash,43296:1st Curtain (front),51488:2nd Curtain (rear),59680:High Speed Sync (HSS)" +msgstr "" + +#: fpestrconsts.rsfujifocuswarninglkup +msgid "0:Good,1:Out of focus" +msgstr "" + +#: fpestrconsts.rsfujihighisonoisereductionlkup +msgid "0:0 (normal),256:+2 (strong),384:+1 (medium strong),448:+3 (very strong),480:+4 (strongest)512:-2 (weak),640:-1 (medium weak),704:-3 (very weak),736:-4 (weakest)" +msgstr "" + +#: fpestrconsts.rsfujinoisereductionlkup +msgid "64:Low,128:Normal,256:n/a" +msgstr "" + +#: fpestrconsts.rsfujipanoramadirlkup +msgid "1:Right,2:Up,3:Left,4:Down" +msgstr "" + +#: fpestrconsts.rsfujipicturemodelkup +msgid "0:Auto,1:Portrait,2:Landscape,3:Macro,4:Sports,5:Night Scene,6:Program AE,7:Natural Light,8:Anti-blur,9:Beach & Snow,10:Sunset,11:Museum,12:Party,13:Flower,14:Text,15:Natural Light & Flash,16:Beach,17:Snow,18:Fireworks,19:Underwater,20:Portrait with Skin Correction,22:Panorama,23:Night (tripod),24:Pro Low-light,25:Pro Focus,26:Portrait 2,27:Dog Face Detection,28:Cat Face Detection,64:Advanced Filter,256:Aperture-priority AE,512:Shutter speed priority AE,768:Manual" +msgstr "" + +#: fpestrconsts.rsfujisaturationlkup +msgid "0:0 (normal),128:+1 (medium high),192:+3 (very high),224:+4 (highest),256:+2 (high),384:-1 (medium low),512:Low,768:None (B&W),769:B&W Red Filter,770:B&W Yellow Filter,771:B&W Green Filter,784:B&W Sepia,1024:-2 (low),1216:-3 (very low),1248:-4 (lowest),1280:Acros,1281:Acros Red Filter,1282:Acros Yellow Filter,1283:Acros Green Filter,32768:Film Simulation" +msgstr "" + +#: fpestrconsts.rsfujiscenerecognlkup +msgid "0:Unrecognized,256:Portrait Image,512:Landscape Image,768:Night Scene,1024:Macro" +msgstr "" + +#: fpestrconsts.rsfujishadowhighlightlkup +msgid "-64:+4 (hardest),-48:+3 (very hard),-32:+2 (hard),-16:+1 (medium hard)" +msgstr "" + +#: fpestrconsts.rsfujisharpnesslkup +msgid "0:-4 (softest),1:-3 (very soft),2:-2 (soft),3:0 (normal),4:+2 (hard),5:+3 (very hard),6:+4 (hardest),130:-1 (medium soft),132:+1 (medium hard),32768:Film Simulation,65535:n/a" +msgstr "" + +#: fpestrconsts.rsfujishuttertypelkup +msgid "0:Mechanical,1:Electronic" +msgstr "" + +#: fpestrconsts.rsfujiwhiteballkup +msgid "0:Auto,256:Daylight,512:Cloudy,768:Daylight Fluorescent,769:Day White Fluorescent,770:White Fluorescent,771:Warm White Fluorescent,772:Living Room Warm White Fluorescent,1024:Incandescent,1280:Flash,1536:Underwater,3840:Custom,3841:Custom2,3842:Custom3,3843:Custom4,3844:Custom5,4080:Kelvin" +msgstr "" + +#: fpestrconsts.rsgaincontrol +msgid "Gain control" +msgstr "" + +#: fpestrconsts.rsgaincontrollkup +msgid "0:None,1:Low gain up,2:High gain up,3:Low gain down,4:High gain down" +msgstr "" + +#: fpestrconsts.rsgamma +msgid "Gamma" +msgstr "" + +#: fpestrconsts.rsgpsaltitude +msgid "GPS altitude" +msgstr "" + +#: fpestrconsts.rsgpsaltituderef +msgid "GPS altitude reference" +msgstr "" + +#: fpestrconsts.rsgpsaltitudereflkup +msgid "0: Above sea level,1:Below sea level" +msgstr "" + +#: fpestrconsts.rsgpsareainformation +msgid "Area information" +msgstr "" + +#: fpestrconsts.rsgpsdatedifferential +msgid "GPS date differential" +msgstr "" + +#: fpestrconsts.rsgpsdatedifferentiallkup +msgid "0:No correction,1:Differential corrected" +msgstr "" + +#: fpestrconsts.rsgpsdatestamp +msgid "GPS date stamp" +msgstr "" + +#: fpestrconsts.rsgpsdestbearing +msgid "GPS destination bearing" +msgstr "" + +#: fpestrconsts.rsgpsdestbearingref +msgid "GPS destination bearing reference" +msgstr "" + +#: fpestrconsts.rsgpsdestdistance +msgid "GPS destination distance" +msgstr "" + +#: fpestrconsts.rsgpsdestdistanceref +msgid "GPS destination distance reference" +msgstr "" + +#: fpestrconsts.rsgpsdestlatitude +msgid "GPS destination latitude" +msgstr "" + +#: fpestrconsts.rsgpsdestlatituderef +msgid "GPS destination latitude reference" +msgstr "" + +#: fpestrconsts.rsgpsdestlongitude +msgid "GPS destination longitude" +msgstr "" + +#: fpestrconsts.rsgpsdestlongituderef +msgid "GPS destination longitude reference" +msgstr "" + +#: fpestrconsts.rsgpsdistancereflkup +msgid "K:Kilometers,M:Miles,N:Nautical miles" +msgstr "" + +#: fpestrconsts.rsgpsdop +msgid "GPS DOP" +msgstr "" + +#: fpestrconsts.rsgpshpositioningerror +msgid "GPS H positioning error" +msgstr "" + +#: fpestrconsts.rsgpsimagedirection +msgid "GPS image direction" +msgstr "" + +#: fpestrconsts.rsgpsimagedirectionref +msgid "GPS image direction reference" +msgstr "" + +#: fpestrconsts.rsgpsinfo +msgid "GPS info" +msgstr "" + +#: fpestrconsts.rsgpslatitude +msgid "GPS latitude" +msgstr "" + +#: fpestrconsts.rsgpslatituderef +msgid "GPS latitude reference" +msgstr "" + +#: fpestrconsts.rsgpslatitudereflkup +msgid "N:North,S:South" +msgstr "" + +#: fpestrconsts.rsgpslongitude +msgid "GPS longitude" +msgstr "" + +#: fpestrconsts.rsgpslongituderef +msgid "GPS longitude reference" +msgstr "" + +#: fpestrconsts.rsgpslongitudereflkup +msgid "E:East,W:West" +msgstr "" + +#: fpestrconsts.rsgpsmapdatum +msgid "GPS map datum" +msgstr "" + +#: fpestrconsts.rsgpsmeasuremode +msgid "GPS measurement mode" +msgstr "" + +#: fpestrconsts.rsgpsmeasuremodelkup +msgid "2:2-Dimensional Measurement,3:3-Dimensional Measurement" +msgstr "" + +#: fpestrconsts.rsgpsprocessingmode +msgid "GPS processing mode" +msgstr "" + +#: fpestrconsts.rsgpssatellites +msgid "GPS satellites" +msgstr "" + +#: fpestrconsts.rsgpsspeed +msgid "GPS speed" +msgstr "" + +#: fpestrconsts.rsgpsspeedref +msgid "GPS speed reference" +msgstr "" + +#: fpestrconsts.rsgpsspeedreflkup +msgid "K:km/h,M:mph,N:knots" +msgstr "" + +#: fpestrconsts.rsgpsstatus +msgid "GPS status" +msgstr "" + +#: fpestrconsts.rsgpstimestamp +msgid "GPS time stamp" +msgstr "" + +#: fpestrconsts.rsgpstrack +msgid "GPS track" +msgstr "" + +#: fpestrconsts.rsgpstrackref +msgid "GPS track reference" +msgstr "" + +#: fpestrconsts.rsgpstrackreflkup +msgid "M:Magnetic north,T:True north" +msgstr "" + +#: fpestrconsts.rsgpsversionid +msgid "GPS version ID" +msgstr "" + +#: fpestrconsts.rshalftonehints +msgid "Half-tone hints" +msgstr "" + +#: fpestrconsts.rshostcomputer +msgid "Host computer" +msgstr "" + +#: fpestrconsts.rshumidity +msgid "Humidity" +msgstr "" + +#: fpestrconsts.rsimagedatafilenotexisting +msgid "File \"%s\" providing the image data does not exist." +msgstr "" + +#: fpestrconsts.rsimagedatafilenotspecified +msgid "The metadata structure is not linked to an image. Specify the name of the file providing the image data." +msgstr "" + +#: fpestrconsts.rsimagedescr +msgid "Image description" +msgstr "" + +#: fpestrconsts.rsimageformatnotsupported +msgid "Image format not supported." +msgstr "" + +#: fpestrconsts.rsimageheight +msgid "Image height" +msgstr "" + +#: fpestrconsts.rsimagehistory +msgid "Image history" +msgstr "" + +#: fpestrconsts.rsimagenumber +msgid "Image number" +msgstr "" + +#: fpestrconsts.rsimageresourcenametoolong +msgid "Image resource name \"%s\" too long." +msgstr "" + +#: fpestrconsts.rsimageuniqueid +msgid "Unique image ID" +msgstr "" + +#: fpestrconsts.rsimagewidth +msgid "Image width" +msgstr "" + +#: fpestrconsts.rsimgcaption +msgid "Image caption" +msgstr "" + +#: fpestrconsts.rsimgcaptionwriter +msgid "Image caption writer" +msgstr "" + +#: fpestrconsts.rsimgcredit +msgid "Image credit" +msgstr "" + +#: fpestrconsts.rsimgheadline +msgid "Image headline" +msgstr "" + +#: fpestrconsts.rsimgtype +msgid "Image type" +msgstr "" + +#: fpestrconsts.rsincompletejpegsegmentheader +msgid "Defective JPEG structure: Incomplete segment header" +msgstr "" + +#: fpestrconsts.rsincorrectfilestructure +msgid "Incorrect file structure" +msgstr "" + +#: fpestrconsts.rsincorrecttagtype +msgid "Incorrect tag type %d: Index=%d, TagID=$%.04x, File:\"%s\"" +msgstr "" + +#: fpestrconsts.rsinkset +msgid "Ink set" +msgstr "" + +#: fpestrconsts.rsinksetlkup +msgid "1:CMYK,2:Not CMYK" +msgstr "" + +#: fpestrconsts.rsinteropindex +msgid "Interoperabiliy index" +msgstr "" + +#: fpestrconsts.rsinteropoffset +msgid "Interoperability offset" +msgstr "" + +#: fpestrconsts.rsinteropversion +msgid "Interoperability version" +msgstr "" + +#: fpestrconsts.rsiptcdataexpected +msgid "IPTC data expected, but not found." +msgstr "" + +#: fpestrconsts.rsiptcextendeddatasizenotsupported +msgid "Data size %d not supported for an IPTC extended dataset." +msgstr "" + +#: fpestrconsts.rsiptcnaa +msgid "IPTC/NAA" +msgstr "" + +#: fpestrconsts.rsiptcorientationlkup +msgid "P:Portrait,L:Landscape,S:Square" +msgstr "" + +#: fpestrconsts.rsiso +msgid "ISO" +msgstr "" + +#: fpestrconsts.rsisospeed +msgid "ISO speed" +msgstr "" + +#: fpestrconsts.rsisospeedlatitudeyyy +msgid "ISO latitude yyy" +msgstr "" + +#: fpestrconsts.rsisospeedlatitudezzz +msgid "ISO speed latitude zzz" +msgstr "" + +#: fpestrconsts.rsjpegcompresseddatawriting +msgid "Writing error of compressed data." +msgstr "" + +#: fpestrconsts.rsjpegreadwriteerrorinsegment +msgid "Read/write error in segment $FF%.2x" +msgstr "" + +#: fpestrconsts.rsjpegsegmentmarkerexpected +msgid "Defective JPEG structure: Segment marker ($FF) expected." +msgstr "" + +#: fpestrconsts.rskeywords +msgid "Keywords" +msgstr "" + +#: fpestrconsts.rslangid +msgid "Language ID" +msgstr "" + +#: fpestrconsts.rslensinfo +msgid "Lens info" +msgstr "" + +#: fpestrconsts.rslensmake +msgid "Lens make" +msgstr "" + +#: fpestrconsts.rslensmodel +msgid "Lens model" +msgstr "" + +#: fpestrconsts.rslensserialnumber +msgid "Lens serial number" +msgstr "" + +#: fpestrconsts.rslightsource +msgid "Light source" +msgstr "" + +#: fpestrconsts.rslightsourcelkup +msgid "0:Unknown,1:Daylight,2:Fluorescent,3:Tungsten (incandescent),4:Flash,9:Fine weather,10:Cloudy,11:Shade,12:Daylight fluorescent,13:Day white fluorescent,14:Cool white fluorescent,15:White fluorescent,16:Warm white fluorescent,17:Standard light A, 18:Standard light B,19:Standard light C,20:D55,21:D65,22:D74,23:D50,24:ISO Studio tungsten,255:Other" +msgstr "" + +#: fpestrconsts.rslocationcode +msgid "Country/primary location code" +msgstr "" + +#: fpestrconsts.rslocationname +msgid "Country/primary location name" +msgstr "" + +#: fpestrconsts.rslownormalhigh +msgid "0:Low,1:Normal,2:High" +msgstr "" + +#: fpestrconsts.rsmacro +msgid "Macro" +msgstr "" + +#: fpestrconsts.rsmake +msgid "Make" +msgstr "" + +#: fpestrconsts.rsmakernote +msgid "Maker note" +msgstr "" + +#: fpestrconsts.rsmaxaperturevalue +msgid "Max aperture value" +msgstr "" + +#: fpestrconsts.rsmaxsamplevalue +msgid "Max sample value" +msgstr "" + +#: fpestrconsts.rsmeteringmode +msgid "Metering mode" +msgstr "" + +#: fpestrconsts.rsmeteringmodelkup +msgid "0:Unknown,1:Average,2:Center-weighted average,3:Spot,4:Multi-spot,5:Multi-segment,6:Partial,255:Other" +msgstr "" + +#: fpestrconsts.rsminoltabracketsteplkup +msgid "0:1/3 EV,1:2/3 EV,2:1 EV" +msgstr "" + +#: fpestrconsts.rsminoltacolormodelkup +msgid "0:Natural color,1:Black & White,2:Vivid color,3:Solarization,4:Adobe RGB,5:Sepia,9:Natural,12:Portrait,13:Natural sRGB,14:Natural+ sRGB,15:Landscape,16:Evening,17:Night Scene,18:Night Portrait,132:Embed Adobe RGB" +msgstr "" + +#: fpestrconsts.rsminoltacolorprofilelkup +msgid "0:Not embedded,1:Embedded" +msgstr "" + +#: fpestrconsts.rsminoltadataimprintlkup +msgid "0;None,1:YYYY/MM/DD,2:MM/DD/HH:MM,3:Text,4:Text + ID#" +msgstr "" + +#: fpestrconsts.rsminoltadecpositionlkup +msgid "0:Exposure,1:Contrast,2:Saturation,3:Filter" +msgstr "" + +#: fpestrconsts.rsminoltadigitalzoomlkup +msgid "0:Off,1:Electronic magnification,2:2x" +msgstr "" + +#: fpestrconsts.rsminoltadrivemodelkup +msgid "0:Single,1:Continuous,2:Self-timer,4:Bracketing,5:Interval,6:UHS continuous,7:HS continuous" +msgstr "" + +#: fpestrconsts.rsminoltaexposuremodelkup +msgid "0:Program,1:Aperture priority,2:Shutter priority,3:Manual" +msgstr "" + +#: fpestrconsts.rsminoltaflashmeteringlkup +msgid "0:ADI (Advanced Distance Integration),1:Pre-flash TTL,2:Manual flash control" +msgstr "" + +#: fpestrconsts.rsminoltaflashmodelkup +msgid "0:Fill flash,1:Red-eye reduction,2:Rear flash sync,3:Wireless,4:Off?" +msgstr "" + +#: fpestrconsts.rsminoltafocusarealkup +msgid "0:Wide Focus (normal),1:Spot Focus" +msgstr "" + +#: fpestrconsts.rsminoltafocusmodelkup +msgid "0:AF,1:MF" +msgstr "" + +#: fpestrconsts.rsminoltafoldernamelkup +msgid "0:Standard Form,1:Data Form" +msgstr "" + +#: fpestrconsts.rsminoltaimagesizelkup +msgid "1:1600x1200,2:1280x960,3:640x480,5:2560x1920,6:2272x1704,7:2048x1536" +msgstr "" + +#: fpestrconsts.rsminoltaimagesizelkup1 +msgid "0:Full,1:1600x1200,2:1280x960,3:640x480,6:2080x1560,7:2560x1920,8;3264x2176" +msgstr "" + +#: fpestrconsts.rsminoltaimagestablkup +msgid "1:Off,5:On" +msgstr "" + +#: fpestrconsts.rsminoltainternalflashlkup +msgid "0:No,1:Fired" +msgstr "" + +#: fpestrconsts.rsminoltaintervalmodelkup +msgid "0:Still image,1:Time-lapse movie" +msgstr "" + +#: fpestrconsts.rsminoltaisosettinglkup +msgid "0:100,1:200,2:400,3:800,4:Auto,5:64" +msgstr "" + +#: fpestrconsts.rsminoltameteringmodelkup +msgid "0:Multi-segment,1:Center-weighted average,2:Spot" +msgstr "" + +#: fpestrconsts.rsminoltamodelidlkup +msgid "0:DiMAGE 7/X1/X21 or X31,1:DiMAGE 5,2:DiMAGE S304,3:DiMAGE S404,4:DiMAGE 7i,5:DiMAGE 7Hi,6:DiMAGE A1,7:DiMAGE A2 or S414" +msgstr "" + +#: fpestrconsts.rsminoltaqualitylkup +msgid "0:Raw,1:Super Fine,2:Fine,3:Standard,4:Economy,5:Extra fine" +msgstr "" + +#: fpestrconsts.rsminoltascenemodelkup +msgid "0:Standard,1:Portrait,2:Text,3:Night Scene,4:Sunset,5:Sports,6:Landscape,7:Night Portrait,8:Macro,9:Super Macro,16:Auto,17:Night View/Portrait,18:Sweep Panorama,19:Handheld Night Shot,20:Anti Motion Blur,21:Cont. Priority AE,22:Auto+,23:3D Sweep Panorama,24:Superior Auto,25:High Sensitivity,26:Fireworks,27:Food,28:Pet,33:HDR,65535:n/a" +msgstr "" + +#: fpestrconsts.rsminoltasharpnesslkup +msgid "0:Hard,1:Normal,2:Soft" +msgstr "" + +#: fpestrconsts.rsminoltasubjectprogramlkup +msgid "0:None,1:Portrait,2:Text,3:Night portrait,4:Sunset,5:Sports action" +msgstr "" + +#: fpestrconsts.rsminoltateleconverterlkup +msgid "$0:None,$4:Minolta/Sony AF 1.4x APO (D) (0x04),$5:Minolta/Sony AF 2x APO (D) (0x05),$48 = Minolta/Sony AF 2x APO (D),$50:Minolta AF 2x APO II,$60:Minolta AF 2x APO,$88:Minolta/Sony AF 1.4x APO (D),$90 = Minolta AF 1.4x APO II,$A0 = Minolta AF 1.4x APO" +msgstr "" + +#: fpestrconsts.rsminoltawhitebalancelkup +msgid "$00:Auto,$01:Color Temperature/Color Filter,$10:Daylight,$20:Cloudy,$30:Shade,$40:Tungsten,$50:Flash,$60:Fluorescent,$70:Custom" +msgstr "" + +#: fpestrconsts.rsminoltawidefocuszonelkup +msgid "0:No zone,1:Center zone (horizontal orientation),2:Center zone (vertical orientation),3:Left zone,4:Right zone" +msgstr "" + +#: fpestrconsts.rsminoltazonematchinglkup +msgid "0:ISO Setting Used,1:High Key,2:Low Key" +msgstr "" + +#: fpestrconsts.rsminsamplevalue +msgid "Min sample value" +msgstr "" + +#: fpestrconsts.rsmodel +msgid "Model" +msgstr "" + +#: fpestrconsts.rsmorethumbnailtagsthanexpected +msgid "More thumbnail tags than expected." +msgstr "" + +#: fpestrconsts.rsnikoncolormodelkup +msgid "1:Color,2:Monochrome" +msgstr "" + +#: fpestrconsts.rsnikonconverterlkup +msgid "0:Not used,1:Used" +msgstr "" + +#: fpestrconsts.rsnikonimgadjlkup +msgid "0:Normal,1:Bright+,2:Bright-,3:Contrast+,4:Contrast-" +msgstr "" + +#: fpestrconsts.rsnikonisolkup +msgid "0:ISO80,2:ISO160,4:ISO320,5:ISO100" +msgstr "" + +#: fpestrconsts.rsnikonqualitylkup +msgid "1:Vga Basic,2:Vga Normal,3:Vga Fine,4:SXGA Basic,5:SXGA Normal,6:SXGA Fine,10:2 Mpixel Basic,11:2 Mpixel Normal,12:2 Mpixel Fine" +msgstr "" + +#: fpestrconsts.rsnikonwhitebalancelkup +msgid "0:Auto,1:Preset,2:Daylight,3:Incandescense,4:Fluorescence,5:Cloudy,6:SpeedLight" +msgstr "" + +#: fpestrconsts.rsnormallowhigh +msgctxt "fpestrconsts.rsnormallowhigh" +msgid "0:Normal,1:Low,2:High" +msgstr "" + +#: fpestrconsts.rsnormalsofthard +msgctxt "fpestrconsts.rsnormalsofthard" +msgid "0:Normal,1:Soft,2:Hard" +msgstr "" + +#: fpestrconsts.rsnovalidiptcfile +msgid "No valid IPTC file" +msgstr "" + +#: fpestrconsts.rsnovalidiptcsignature +msgid "No valid IPTC signature" +msgstr "" + +#: fpestrconsts.rsnoyes +msgctxt "fpestrconsts.rsnoyes" +msgid "0:No,1:Yes" +msgstr "" + +#: fpestrconsts.rsobjectattr +msgid "Object attribute reference" +msgstr "" + +#: fpestrconsts.rsobjectcycle +msgid "Object cycle" +msgstr "" + +#: fpestrconsts.rsobjectcyclelkup +msgid "a:morning,p:evening,b:both" +msgstr "" + +#: fpestrconsts.rsobjectname +msgid "Object name" +msgstr "" + +#: fpestrconsts.rsobjecttype +msgid "Object type reference" +msgstr "" + +#: fpestrconsts.rsoffon +msgctxt "fpestrconsts.rsoffon" +msgid "0:Off,1:On" +msgstr "" + +#: fpestrconsts.rsoffsettime +msgid "Time zone for date/time" +msgstr "" + +#: fpestrconsts.rsoffsettimedigitized +msgid "Time zone for date/time digitized" +msgstr "" + +#: fpestrconsts.rsoffsettimeoriginal +msgid "Time zone for date/time original" +msgstr "" + +#: fpestrconsts.rsolympusccdscanmodelkup +msgid "0:Interlaced,1:Progressive" +msgstr "" + +#: fpestrconsts.rsolympuscontrastlkup +msgid "0:High,1:Normal,2:Low" +msgstr "" + +#: fpestrconsts.rsolympusflashdevlkup +msgid "0:None,1:Internal,4:External,5:Internal + External" +msgstr "" + +#: fpestrconsts.rsolympusflashmodelkup +msgid "2:On,3;Off" +msgstr "" + +#: fpestrconsts.rsolympusflashmodellkup +msgid "0:None,1:FL-20,2:FL-50,3:RF-11,4:TF-22,5:FL-36,6:FL-50R,7:FL-36R,9:FL-14,11:FL-600R" +msgstr "" + +#: fpestrconsts.rsolympusflashtypelkup +msgid "0:None,2:Simple E-System,3:E-System" +msgstr "" + +#: fpestrconsts.rsolympusjpegquallkup +msgid "1:SQ,2:HQ,3:SHQ,4:Raw" +msgstr "" + +#: fpestrconsts.rsolympusmacrolkup +msgid "0:Off,1:On,2:Super Macro" +msgstr "" + +#: fpestrconsts.rsolympuspreviewimglength +msgid "Preview image length" +msgstr "" + +#: fpestrconsts.rsolympuspreviewimgstart +msgid "Preview image start" +msgstr "" + +#: fpestrconsts.rsolympuspreviewimgvalid +msgid "Preview image valid" +msgstr "" + +#: fpestrconsts.rsolympusscenemodelkup +msgid "0:Normal,1:Standard,2:Auto,3:Intelligent Auto,4:Portrait,5:Landscape+Portrait,6:Landscape,7:Night Scene,8:Night+Portrait9:Sport,10:Self Portrait,11:Indoor,12:Beach & Snow,13:Beach,14:Snow,15:Self Portrait+Self Timer,16:Sunset,17:Cuisine,18:Documents,19:Candle,20:Fireworks,21:Available Light,22:Vivid,23:Underwater Wide1,24:Underwater Macro,25:Museum,26:Behind Glass,27:Auction,28:Shoot & Select1,29:Shoot & Select2,30:Underwater Wide2,31:Digital Image Stabilization,32:Face Portrait,33:Pet,34:Smile Shot,35:Quick Shutter,43:Hand-held Starlight,100:Panorama,101:Magic Filter,103:HDR" +msgstr "" + +#: fpestrconsts.rsolympussharpnesslkup +msgid "0:Normal,1:Hard,2:Soft" +msgstr "" + +#: fpestrconsts.rsorientation +msgid "Orientation" +msgstr "" + +#: fpestrconsts.rsorientationlkup +msgid "1:Horizontal (normal),2:Mirror horizontal,3:Rotate 180,4:Mirror vertical,5:Mirror horizontal and rotate 270 CW,6:Rotate 90 CW,7:Mirror horizontal and rotate 90 CW,8:Rotate 270 CW" +msgstr "" + +#: fpestrconsts.rsoriginatingprog +msgid "Originating program" +msgstr "" + +#: fpestrconsts.rsownername +msgid "Owner name" +msgstr "" + +#: fpestrconsts.rspagename +msgid "Page name" +msgstr "" + +#: fpestrconsts.rspagenumber +msgid "Page number" +msgstr "" + +#: fpestrconsts.rsphotometricint +msgid "Photometric interpretation" +msgstr "" + +#: fpestrconsts.rsphotometricintlkup +msgid "0:White is zero,1:Black is zero,2:RGB,3:RGB palette,4:Transparency mask,5:CMYK,6:YCbCr,8:CIELab,9:ICCLab,10:ITULab,32803:Color filter array,32844:Pixar LogL,32845:Pixar LogLuv,34892:Linear Raw" +msgstr "" + +#: fpestrconsts.rsplanarconfiguration +msgid "Planar configuration" +msgstr "" + +#: fpestrconsts.rsplanarconfigurationlkup +msgid "1:Chunky,2:Planar" +msgstr "" + +#: fpestrconsts.rspredictor +msgid "Predictor" +msgstr "" + +#: fpestrconsts.rspredictorlkup +msgid "1:None,2:Horizontal differencing" +msgstr "" + +#: fpestrconsts.rspressure +msgid "Pressure" +msgstr "" + +#: fpestrconsts.rsprimarychromaticities +msgid "Primary chromaticities" +msgstr "" + +#: fpestrconsts.rsprogversion +msgid "Program version" +msgstr "" + +#: fpestrconsts.rsquality +msgid "Quality" +msgstr "" + +#: fpestrconsts.rsrangecheckerror +msgid "Range check error." +msgstr "" + +#: fpestrconsts.rsreadincompleteifdrecord +msgid "Read incomplete IFD record at stream position %d." +msgstr "" + +#: fpestrconsts.rsrecexpindex +msgid "Recommended exposure index" +msgstr "" + +#: fpestrconsts.rsrecordversion +msgid "Record version" +msgstr "" + +#: fpestrconsts.rsrefblackwhite +msgid "Reference black & white" +msgstr "" + +#: fpestrconsts.rsrefdate +msgid "Reference date" +msgstr "" + +#: fpestrconsts.rsrefnumber +msgid "Reference number" +msgstr "" + +#: fpestrconsts.rsrefservice +msgid "Reference service" +msgstr "" + +#: fpestrconsts.rsrelatedimagefileformat +msgid "Related image file format" +msgstr "" + +#: fpestrconsts.rsrelatedimageheight +msgid "Related image height" +msgstr "" + +#: fpestrconsts.rsrelatedimagewidth +msgid "Related image width" +msgstr "" + +#: fpestrconsts.rsrelatedsoundfile +msgid "Related sound file" +msgstr "" + +#: fpestrconsts.rsreleasedate +msgid "Release date" +msgstr "" + +#: fpestrconsts.rsreleasetime +msgid "Release time" +msgstr "" + +#: fpestrconsts.rsresolutionunit +msgid "Resolution unit" +msgstr "" + +#: fpestrconsts.rsresolutionunitlkup +msgid "1:None,2:inches,3:cm" +msgstr "" + +#: fpestrconsts.rsrowsperstrip +msgid "Rows per strip" +msgstr "" + +#: fpestrconsts.rssamplesperpixel +msgid "Samples per pixel" +msgstr "" + +#: fpestrconsts.rssanyomacrolkup +msgid "0:Normal,1:Macro,2:View,3:Manual" +msgstr "" + +#: fpestrconsts.rssanyoqualitylkup +msgid "0:Normal/Very Low,1:Normal/Low,2:Normal/Medium Low,3:Normal/Medium,4:Normal/Medium High,5:Normal/High,6:Normal/Very High7:Normal/Super High,256:Fine/Very Low,257:Fine/Low,258:Fine/Medium Low259:Fine/Medium,260:Fine/Medium High,261:Fine/High,262:Fine/Very High263:Fine/Super High,512:Super Fine/Very Low,513:Super Fine/Low,514:Super Fine/Medium Low,515:Super Fine/Medium,516:Super Fine/Medium High,517:Super Fine/High,518:Super Fine/Very High,519:Super Fine/Super High" +msgstr "" + +#: fpestrconsts.rssanyospecialmode +msgid "Special mode" +msgstr "" + +#: fpestrconsts.rssaturation +msgid "Saturation" +msgstr "" + +#: fpestrconsts.rsscenecapturetype +msgid "Scene capture type" +msgstr "" + +#: fpestrconsts.rsscenecapturetypelkup +msgid "0:Standard,1:Landscape,2:Portrait,3:Night" +msgstr "" + +#: fpestrconsts.rsscenetype +msgid "Scene type" +msgstr "" + +#: fpestrconsts.rsscenetypelkup +msgid "0:Unknown,1:Directly photographed" +msgstr "" + +#: fpestrconsts.rssecurityclassification +msgid "Security classification" +msgstr "" + +#: fpestrconsts.rsselftimermode +msgid "Self-timer mode" +msgstr "" + +#: fpestrconsts.rsseminfo +msgid "SEM info" +msgstr "" + +#: fpestrconsts.rssensingmethod +msgid "Sensing method" +msgstr "" + +#: fpestrconsts.rssensingmethodlkup +msgid "1:Not defined,2:One-chip color area,3:Two-chip color area,4:Three-chip color area,5:Color sequential area,7:Trilinear,8:Color sequential linear" +msgstr "" + +#: fpestrconsts.rssensitivitytype +msgid "Sensitivity type" +msgstr "" + +#: fpestrconsts.rssensitivitytypelkup +msgid "0:Unknown,1:Standard Output Sensitivity2:Recommended exposure index,3:ISO speed,4:Standard output sensitivity and recommended exposure index,5:Standard output sensitivity and ISO Speed,6:Recommended exposure index and ISO speed,7:Standard output sensitivity, recommended exposure index and ISO speed" +msgstr "" + +#: fpestrconsts.rsserialnumber +msgid "Serial number" +msgstr "" + +#: fpestrconsts.rssharpness +msgid "Sharpness" +msgstr "" + +#: fpestrconsts.rsshutterspeedvalue +msgid "Shutter speed value" +msgstr "" + +#: fpestrconsts.rssinglecontinuous +msgctxt "fpestrconsts.rssinglecontinuous" +msgid "0:Single,1:Continuous" +msgstr "" + +#: fpestrconsts.rssoftware +msgid "Software" +msgstr "" + +#: fpestrconsts.rssource +msgid "Source" +msgstr "" + +#: fpestrconsts.rsspatialfrequresponse +msgid "Spatial frequency response" +msgstr "" + +#: fpestrconsts.rsspecialinstruct +msgid "Special instructions" +msgstr "" + +#: fpestrconsts.rsspectralsensitivity +msgid "Spectral sensitivity" +msgstr "" + +#: fpestrconsts.rsstate +msgid "Province/State" +msgstr "" + +#: fpestrconsts.rsstdoutputsens +msgid "Standard output sensitivity" +msgstr "" + +#: fpestrconsts.rsstripbytecounts +msgid "Strip byte counts" +msgstr "" + +#: fpestrconsts.rsstripoffsets +msgid "Strip offsets" +msgstr "" + +#: fpestrconsts.rssubfile +msgid "Subfile" +msgstr "" + +#: fpestrconsts.rssubfiletypelkup +msgid "0:Full-resolution image,1:Reduced-resolution image,2:Single page of multi-page image,3:Single page of multi-page reduced-resolution image,4:Transparency mask,5:Transparency mask of reduced-resolution image,6:Transparency mask of multi-page image,7:Transparency mask of reduced-resolution multi-page image" +msgstr "" + +#: fpestrconsts.rssubjectarea +msgid "Subject area" +msgstr "" + +#: fpestrconsts.rssubjectdistance +msgid "Subject distance" +msgstr "" + +#: fpestrconsts.rssubjectdistancerange +msgid "Subject distance range" +msgstr "" + +#: fpestrconsts.rssubjectdistancerangelkup +msgid "0:Unknown,1:Macro,2:Close,3:Distant" +msgstr "" + +#: fpestrconsts.rssubjectlocation +msgid "Subject location" +msgstr "" + +#: fpestrconsts.rssubjectref +msgid "Subject reference" +msgstr "" + +#: fpestrconsts.rssublocation +msgid "Sublocation" +msgstr "" + +#: fpestrconsts.rssubsectime +msgid "Fractional seconds of date/time" +msgstr "" + +#: fpestrconsts.rssubsectimedigitized +msgid "Fractional seconds of date/time digitized" +msgstr "" + +#: fpestrconsts.rssubsectimeoriginal +msgid "Fractional seconds of date/time original" +msgstr "" + +#: fpestrconsts.rssuppcategory +msgid "Supplemental category" +msgstr "" + +#: fpestrconsts.rstagtypenotsupported +msgid "Tag \"%s\" has an unsupported type." +msgstr "" + +#: fpestrconsts.rstargetprinter +msgid "Target printer" +msgstr "" + +#: fpestrconsts.rstemperature +msgid "Temperature" +msgstr "" + +#: fpestrconsts.rsthresholding +msgid "Thresholding" +msgstr "" + +#: fpestrconsts.rsthresholdinglkup +msgid "1:No dithering or halftoning,2:Ordered dither or halftone,3:Randomized dither" +msgstr "" + +#: fpestrconsts.rsthumbnailheight +msgid "Thumbnail height" +msgstr "" + +#: fpestrconsts.rsthumbnailoffset +msgid "Thumbnail offset" +msgstr "" + +#: fpestrconsts.rsthumbnailsize +msgid "Thumbnail size" +msgstr "" + +#: fpestrconsts.rsthumbnailwidth +msgid "Thumbnail width" +msgstr "" + +#: fpestrconsts.rstilelength +msgid "Tile length" +msgstr "" + +#: fpestrconsts.rstilewidth +msgid "Tile width" +msgstr "" + +#: fpestrconsts.rstimecreated +msgid "Time created" +msgstr "" + +#: fpestrconsts.rstimezoneoffset +msgid "Time zone offset" +msgstr "" + +#: fpestrconsts.rstransferfunction +msgid "Transfer function" +msgstr "" + +#: fpestrconsts.rstransmissionref +msgid "Original transmission reference" +msgstr "" + +#: fpestrconsts.rsunknownimageformat +msgid "Unknown image format." +msgstr "" + +#: fpestrconsts.rsurgency +msgid "Urgency" +msgstr "" + +#: fpestrconsts.rsurgencylkup +msgid "0:reserved,1:most urgent,5:normal,8:least urgent,9:reserved" +msgstr "" + +#: fpestrconsts.rsusercomment +msgid "User comment" +msgstr "" + +#: fpestrconsts.rswaterdepth +msgid "Water depth" +msgstr "" + +#: fpestrconsts.rswhitebalance +msgid "White balance" +msgstr "" + +#: fpestrconsts.rswhitepoint +msgid "White point" +msgstr "" + +#: fpestrconsts.rswritingnotimplemented +msgid "Writing of %s files not yet implemented." +msgstr "" + +#: fpestrconsts.rsxposition +msgid "X position" +msgstr "" + +#: fpestrconsts.rsxresolution +msgid "X resolution" +msgstr "" + +#: fpestrconsts.rsycbcrcoefficients +msgid "YCbCr coefficients" +msgstr "" + +#: fpestrconsts.rsycbcrpositioning +msgid "YCbCr positioning" +msgstr "" + +#: fpestrconsts.rsycbcrposlkup +msgid "1:Centered,2:Co-sited" +msgstr "" + +#: fpestrconsts.rsycbcrsubsampling +msgid "YCbCr subsampling" +msgstr "" + +#: fpestrconsts.rsyposition +msgid "Y position" +msgstr "" + +#: fpestrconsts.rsyresolution +msgid "Y resolution" +msgstr "" + diff --git a/components/fpexif/tests/multiread/MultiRead_D7.cfg b/components/fpexif/tests/multiread/MultiRead_D7.cfg new file mode 100644 index 000000000..db998d59c --- /dev/null +++ b/components/fpexif/tests/multiread/MultiRead_D7.cfg @@ -0,0 +1,40 @@ +-$A8 +-$B- +-$C- +-$D+ +-$E- +-$F- +-$G+ +-$H+ +-$I+ +-$J- +-$K- +-$L+ +-$M- +-$N+ +-$O+ +-$P+ +-$Q- +-$R- +-$S- +-$T- +-$U- +-$V+ +-$W- +-$X+ +-$Y+ +-$Z1 +-cg +-AWinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; +-H+ +-W+ +-M +-$M16384,1048576 +-K$42200000 +-E"D:\Prog_Lazarus\wp-laz\fpexif\tests\multiread" +-N"D:\Prog_Lazarus\wp-laz\fpexif\tests\multiread\output\dcu\Delphi7" +-LE"d:\programme\borland\delphi7\Projects\Bpl" +-LN"d:\programme\borland\delphi7\Projects\Bpl" +-w-UNSAFE_TYPE +-w-UNSAFE_CODE +-w-UNSAFE_CAST diff --git a/components/fpexif/tests/multiread/MultiRead_D7.dof b/components/fpexif/tests/multiread/MultiRead_D7.dof new file mode 100644 index 000000000..6202c761f --- /dev/null +++ b/components/fpexif/tests/multiread/MultiRead_D7.dof @@ -0,0 +1,151 @@ +[FileVersion] +Version=7.0 +[Compiler] +A=8 +B=0 +C=0 +D=1 +E=0 +F=0 +G=1 +H=1 +I=1 +J=0 +K=0 +L=1 +M=0 +N=1 +O=1 +P=1 +Q=0 +R=0 +S=0 +T=0 +U=0 +V=1 +W=0 +X=1 +Y=2 +Z=1 +ShowHints=1 +ShowWarnings=1 +UnitAliases=WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; +NamespacePrefix= +SymbolDeprecated=1 +SymbolLibrary=1 +SymbolPlatform=1 +UnitLibrary=1 +UnitPlatform=1 +UnitDeprecated=1 +HResultCompat=1 +HidingMember=1 +HiddenVirtual=1 +Garbage=1 +BoundsError=1 +ZeroNilCompat=1 +StringConstTruncated=1 +ForLoopVarVarPar=1 +TypedConstVarPar=1 +AsgToTypedConst=1 +CaseLabelRange=1 +ForVariable=1 +ConstructingAbstract=1 +ComparisonFalse=1 +ComparisonTrue=1 +ComparingSignedUnsigned=1 +CombiningSignedUnsigned=1 +UnsupportedConstruct=1 +FileOpen=1 +FileOpenUnitSrc=1 +BadGlobalSymbol=1 +DuplicateConstructorDestructor=1 +InvalidDirective=1 +PackageNoLink=1 +PackageThreadVar=1 +ImplicitImport=1 +HPPEMITIgnored=1 +NoRetVal=1 +UseBeforeDef=1 +ForLoopVarUndef=1 +UnitNameMismatch=1 +NoCFGFileFound=1 +MessageDirective=1 +ImplicitVariants=1 +UnicodeToLocale=1 +LocaleToUnicode=1 +ImagebaseMultiple=1 +SuspiciousTypecast=1 +PrivatePropAccessor=1 +UnsafeType=0 +UnsafeCode=0 +UnsafeCast=0 +[Linker] +MapFile=0 +OutputObjs=0 +ConsoleApp=1 +DebugInfo=0 +RemoteSymbols=0 +MinStackSize=16384 +MaxStackSize=1048576 +ImageBase=1109393408 +ExeDescription=TeeChart 2014 Components +[Directories] +OutputDir=D:\Prog_Lazarus\wp-laz\fpexif\tests\multiread +UnitOutputDir=D:\Prog_Lazarus\wp-laz\fpexif\tests\multiread\output\dcu\Delphi7 +PackageDLLOutputDir= +PackageDCPOutputDir= +SearchPath= +Packages=Tee97;TeeUI97;TeeDB97;TeePro97;TeeGL97;TeeImage97;TeeLanguage97;TeeWorld97 +Conditionals= +DebugSourceDirs= +UsePackages=0 +[Parameters] +RunParams= +HostApplication= +Launcher= +UseLauncher=0 +DebugCWD= +[Language] +ActiveLang= +ProjectLang= +RootDir=D:\Programme\Borland\Delphi7\Bin\ +[Version Info] +IncludeVerInfo=1 +AutoIncBuild=0 +MajorVer=9 +MinorVer=0 +Release=11 +Build=0 +Debug=0 +PreRelease=0 +Special=0 +Private=0 +DLL=0 +Locale=1033 +CodePage=1252 +[Version Info Keys] +CompanyName=Steema Software +FileDescription= +FileVersion=9.0.0.0 +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename= +ProductName= +ProductVersion=9.0.0.0 +[Excluded Packages] +D:\Prog_Delphi\common\Components\3rdParty\TeeChart\Sources\Compiled\Delphi7\Bin\DclTeeMaker17.bpl=TeeMaker +D:\Programme\Borland\Delphi7\Lib\HelpCtxD7.bpl=HelpScribble HelpContext Property Editor for Delphi 7 +[HistoryLists\hlUnitAliases] +Count=1 +Item0=WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; +[HistoryLists\hlSearchPath] +Count=1 +Item0=D:\Prog_Lazarus\git\dexif-afriess-master +[HistoryLists\hlUnitOutputDirectory] +Count=1 +Item0=D:\Prog_Lazarus\wp-laz\fpexif\tests\multiread\output\dcu\Delphi7 +[HistoryLists\hlOutputDirectorry] +Count=2 +Item0=D:\Prog_Lazarus\wp-laz\fpexif\tests\multiread +Item1=D:\Prog_Lazarus\wp-laz\fpexif\tests\multiread\Delphi diff --git a/components/fpexif/tests/multiread/MultiRead_D7.dpr b/components/fpexif/tests/multiread/MultiRead_D7.dpr new file mode 100644 index 000000000..5b7e9e57e --- /dev/null +++ b/components/fpexif/tests/multiread/MultiRead_D7.dpr @@ -0,0 +1,24 @@ +program MultiRead_D7; + +uses + Forms, + mrtmain in 'common\mrtmain.pas', + fpeStrConsts in '..\..\fpestrconsts.pas', + fpeGlobal in '..\..\fpeglobal.pas', + fpeTags in '..\..\fpetags.pas', + fpeUtils in '..\..\fpeutils.pas', + fpeExifData in '..\..\fpeexifdata.pas', + fpeIptcData in '..\..\fpeiptcdata.pas', + fpeExifReadWrite in '..\..\fpeexifreadwrite.pas', + fpeMakerNote in '..\..\fpemakernote.pas', + fpeIptcReadWrite in '..\..\fpeiptcreadwrite.pas', + fpeMetadata in '..\..\fpemetadata.pas'; + +{$R *.res} + +begin + Application.Initialize; + Application.CreateForm(TMainForm, MainForm); + Application.Run; +end. + diff --git a/components/fpexif/tests/multiread/MultiRead_D7.res b/components/fpexif/tests/multiread/MultiRead_D7.res new file mode 100644 index 0000000000000000000000000000000000000000..196f8148eda9774345f2effa46fb9d34dfe20236 GIT binary patch literal 1536 zcmZuw&ubGw7=1|=nzB+;5ZYVAQd;q#4cki*Y^AYMP%Bn?Ez!2C*rs8PS-faU4=&`; zeRNz#r=~_qiPT{PzTIm9L1^0tsueD+nai>?w&$U`X zuw%GOe?E$WXRYRtM^O}d!JgSrj8(<(RvJJlf0}OVhvjpL9!xMDMPB52W{ zS^nXS?pc!$k|1QjKTg6W;M9EN-Rh>CM>6F_(X2{&vC}K%Jr8awAg>sg6)>Q1SEYhj z$$`2`sfN+<&?z7XdUCwC4QF1!T!*iEK81?=IrDvP6fPS&oGTTpo&-|rV~6)a>!iG! zP~MUFU0aY4u`b#|m98aX zo2<+N@Jwv!L23v&*4S0HI$+Ld9?%~cuXNI24{{O#UT|k(Nu49zQz~lM;Xdgs#4A=} zEzxeIG2}!guRT*?_H=BGkEuOmv^VU%d8tD1oTcxJn#7vfs_o>XD_qg-J`3C}0D^dUe literal 0 HcmV?d00001 diff --git a/components/fpexif/tests/multiread/MultiRead_Delphi.dpr b/components/fpexif/tests/multiread/MultiRead_Delphi.dpr new file mode 100644 index 000000000..260637ed4 --- /dev/null +++ b/components/fpexif/tests/multiread/MultiRead_Delphi.dpr @@ -0,0 +1,24 @@ +program MultiRead_Delphi; + +uses + Forms, + mrtmain in 'common\mrtmain.pas', + fpeStrConsts in '..\..\fpestrconsts.pas', + fpeGlobal in '..\..\fpeglobal.pas', + fpeTags in '..\..\fpetags.pas', + fpeUtils in '..\..\fpeutils.pas', + fpeExifData in '..\..\fpeexifdata.pas', + fpeIptcData in '..\..\fpeiptcdata.pas', + fpeExifReadWrite in '..\..\fpeexifreadwrite.pas', + fpeMakerNote in '..\..\fpemakernote.pas', + fpeIptcReadWrite in '..\..\fpeiptcreadwrite.pas', + fpeMetadata in '..\..\fpemetadata.pas'; + +{$R *.res} + +begin + Application.Initialize; + Application.CreateForm(TMainForm, MainForm); + Application.Run; +end. + diff --git a/components/fpexif/tests/multiread/MultiRead_Delphi.dproj b/components/fpexif/tests/multiread/MultiRead_Delphi.dproj new file mode 100644 index 000000000..6b8955e81 --- /dev/null +++ b/components/fpexif/tests/multiread/MultiRead_Delphi.dproj @@ -0,0 +1,142 @@ + + + {935C648F-D321-4462-B3C2-5CD89F17CD4F} + MultiRead_Delphi.dpr + True + Debug + 1 + Application + VCL + 18.2 + Win32 + + + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + false + false + false + true + false + 42200000 + false + 1 + TeeChart 2014 Components + D:\Prog_Lazarus\wp-laz\fpexif\tests\multiread + D:\Prog_Lazarus\wp-laz\fpexif\tests\multiread\output\dcu\Delphi + Tee97;TeeUI97;TeeDB97;TeePro97;TeeGL97;TeeImage97;TeeLanguage97;TeeWorld97;$(DCC_UsePackage) + MultiRead_Delphi + Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Data;Datasnap;Web;Soap;Winapi;$(DCC_Namespace) + true + 9 + 11 + 1033 + CompanyName=Steema Software;FileDescription=;FileVersion=9.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=9.0.0.0 + + + System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + $(BDS)\bin\default_app.manifest + MultiRead_D7_Icon.ico + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + true + true + + + DEBUG;$(DCC_Define) + false + true + + + true + true + 1 + 0 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + $(BDS)\bin\delphi_PROJECTICON.ico + + + + MainSource + + + + + + + + + + + + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + + + + Delphi.Personality.12 + + + + + MultiRead_Delphi.dpr + + + + True + + + 12 + + + + diff --git a/components/fpexif/tests/multiread/MultiRead_Delphi.res b/components/fpexif/tests/multiread/MultiRead_Delphi.res new file mode 100644 index 0000000000000000000000000000000000000000..f60b4d824863d4d8afe858f244b7092081d4e0bd GIT binary patch literal 59460 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 G`+orYGy**U literal 0 HcmV?d00001 diff --git a/components/fpexif/tests/multiread/MultiRead_Laz.lpi b/components/fpexif/tests/multiread/MultiRead_Laz.lpi new file mode 100644 index 000000000..7a41a02c1 --- /dev/null +++ b/components/fpexif/tests/multiread/MultiRead_Laz.lpi @@ -0,0 +1,122 @@ + + + + + + + + + + <ResourceType Value="res"/> + <UseXPManifest Value="True"/> + <Icon Value="0"/> + </General> + <BuildModes Count="1"> + <Item1 Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + </PublishOptions> + <RunParams> + <local> + <FormatVersion Value="1"/> + </local> + </RunParams> + <RequiredPackages Count="1"> + <Item1> + <PackageName Value="LCL"/> + </Item1> + </RequiredPackages> + <Units Count="13"> + <Unit0> + <Filename Value="MultiRead_Laz.lpr"/> + <IsPartOfProject Value="True"/> + </Unit0> + <Unit1> + <Filename Value="common\mrtmain.pas"/> + <IsPartOfProject Value="True"/> + <ComponentName Value="MainForm"/> + <HasResources Value="True"/> + <ResourceBaseClass Value="Form"/> + </Unit1> + <Unit2> + <Filename Value="..\..\fpexif.inc"/> + <IsPartOfProject Value="True"/> + </Unit2> + <Unit3> + <Filename Value="..\..\fpeexifreadwrite.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeExifReadWrite"/> + </Unit3> + <Unit4> + <Filename Value="..\..\fpeglobal.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeGlobal"/> + </Unit4> + <Unit5> + <Filename Value="..\..\fpetags.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeTags"/> + </Unit5> + <Unit6> + <Filename Value="..\..\fpeutils.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeUtils"/> + </Unit6> + <Unit7> + <Filename Value="..\..\fpexif_fpc.inc"/> + <IsPartOfProject Value="True"/> + </Unit7> + <Unit8> + <Filename Value="..\..\fpeexifdata.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeExifData"/> + </Unit8> + <Unit9> + <Filename Value="..\..\fpeiptcdata.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeIptcData"/> + </Unit9> + <Unit10> + <Filename Value="..\..\fpeiptcreadwrite.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeIptcReadWrite"/> + </Unit10> + <Unit11> + <Filename Value="..\..\fpemakernote.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeMakerNote"/> + </Unit11> + <Unit12> + <Filename Value="..\..\fpestrconsts.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeStrConsts"/> + </Unit12> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <PathDelim Value="\"/> + <Target> + <Filename Value="MultiRead_Laz"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir);..\.."/> + <OtherUnitFiles Value="common;..\.."/> + <UnitOutputDirectory Value="output\ppu\$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + </CompilerOptions> + <Debugging> + <Exceptions Count="3"> + <Item1> + <Name Value="EAbort"/> + </Item1> + <Item2> + <Name Value="ECodetoolError"/> + </Item2> + <Item3> + <Name Value="EFOpenError"/> + </Item3> + </Exceptions> + </Debugging> +</CONFIG> diff --git a/components/fpexif/tests/multiread/MultiRead_Laz.lpr b/components/fpexif/tests/multiread/MultiRead_Laz.lpr new file mode 100644 index 000000000..17863dd7f --- /dev/null +++ b/components/fpexif/tests/multiread/MultiRead_Laz.lpr @@ -0,0 +1,21 @@ +program MultiReadTest; + +{$mode objfpc}{$H+} + +uses + {$IFDEF UNIX}{$IFDEF UseCThreads} + cthreads, + {$ENDIF}{$ENDIF} + Interfaces, // this includes the LCL widgetset + Forms, mrtmain + { you can add units after this }; + +{$R *.res} + +begin + RequireDerivedFormResource := True; + Application.Initialize; + Application.CreateForm(TMainForm, MainForm); + Application.Run; +end. + diff --git a/components/fpexif/tests/multiread/MultiRead_Laz.res b/components/fpexif/tests/multiread/MultiRead_Laz.res new file mode 100644 index 0000000000000000000000000000000000000000..f6e8499568f21121986c715951291349244aeef3 GIT binary patch literal 139052 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 literal 0 HcmV?d00001 diff --git a/components/fpexif/tests/multiread/common/mrtmain.dfm b/components/fpexif/tests/multiread/common/mrtmain.dfm new file mode 100644 index 000000000..636c1e1c1 --- /dev/null +++ b/components/fpexif/tests/multiread/common/mrtmain.dfm @@ -0,0 +1,601 @@ +object MainForm: TMainForm + Left = 326 + Top = 138 + Width = 1040 + Height = 599 + ActiveControl = BtnRunTest + Caption = 'Multi read test' + Color = clBtnFace + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'MS Sans Serif' + Font.Style = [] + OldCreateOrder = True + OnCreate = FormCreate + PixelsPerInch = 96 + TextHeight = 13 + object Splitter1: TSplitter + Left = 403 + Top = 33 + Width = 5 + Height = 527 + end + object Panel1: TPanel + Left = 0 + Top = 0 + Width = 1024 + Height = 33 + Align = alTop + AutoSize = True + BevelOuter = bvNone + BorderWidth = 4 + TabOrder = 0 + DesignSize = ( + 1024 + 33) + object BtnReadFiles: TButton + Left = 945 + Top = 4 + Width = 75 + Height = 25 + Caption = 'Read files' + TabOrder = 0 + OnClick = BtnReadFilesClick + end + object EdImageDir: TEdit + Left = 4 + Top = 4 + Width = 931 + Height = 21 + Anchors = [akLeft, akTop, akRight] + TabOrder = 1 + Text = '..\pictures\originals' + end + end + object Panel2: TPanel + Left = 0 + Top = 33 + Width = 403 + Height = 527 + Align = alLeft + BevelOuter = bvNone + BorderWidth = 4 + Caption = 'Panel2' + TabOrder = 1 + object Bevel1: TBevel + Left = 4 + Top = 494 + Width = 395 + Height = 4 + Align = alBottom + Shape = bsSpacer + end + object FileTreeView: TTreeView + Left = 4 + Top = 4 + Width = 395 + Height = 490 + Align = alClient + Images = ImageList1 + Indent = 19 + ReadOnly = True + StateImages = StateImages + TabOrder = 0 + OnClick = FileTreeViewClick + end + object Panel3: TPanel + Left = 4 + Top = 498 + Width = 395 + Height = 25 + Align = alBottom + AutoSize = True + BevelOuter = bvNone + TabOrder = 1 + object BtnCreateTxtFiles: TButton + Left = 0 + Top = 0 + Width = 100 + Height = 25 + Caption = 'Create txt files' + TabOrder = 0 + OnClick = BtnCreateTxtFilesClick + end + object BtnUncheckAll: TButton + Left = 104 + Top = 0 + Width = 75 + Height = 25 + Caption = 'Uncheck all' + TabOrder = 1 + OnClick = BtnUncheckAllClick + end + object BtnCheckAll: TButton + Left = 184 + Top = 0 + Width = 75 + Height = 25 + Caption = 'Check all' + TabOrder = 2 + OnClick = BtnUncheckAllClick + end + end + end + object Panel4: TPanel + Left = 408 + Top = 33 + Width = 616 + Height = 527 + Align = alClient + BevelOuter = bvNone + BorderWidth = 4 + TabOrder = 2 + object Bevel2: TBevel + Left = 4 + Top = 494 + Width = 608 + Height = 4 + Align = alBottom + Shape = bsSpacer + end + object Panel5: TPanel + Left = 4 + Top = 498 + Width = 608 + Height = 25 + Align = alBottom + AutoSize = True + BevelOuter = bvNone + TabOrder = 0 + object MismatchInfo: TLabel + Left = 0 + Top = 0 + Width = 361 + Height = 25 + Align = alLeft + AutoSize = False + Caption = 'MismatchInfo' + Color = clBtnFace + ParentColor = False + Layout = tlCenter + end + object BtnRunTest: TButton + Left = 539 + Top = 0 + Width = 69 + Height = 25 + Caption = 'Run test' + TabOrder = 0 + OnClick = BtnRunTestClick + end + end + object Memo: TMemo + Left = 4 + Top = 4 + Width = 608 + Height = 490 + Align = alClient + ScrollBars = ssBoth + TabOrder = 1 + end + end + object ImageList1: TImageList + Left = 99 + Top = 111 + Bitmap = { + 494C010104000900300010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600 + 0000000000003600000028000000400000002000000001002000000000000020 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000000000000000000000000000D7CCC400A57E5E00B2805600AF7E + 5200A47A5900CFC2B70000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000003F3D + ED413B38EB08000000000000000000000000000000000000000000000000211F + E3081E1CE2410000000000000000000000000000000000000000000000000000 + 00000000000000000000E6E0DA00A9876A00B2815800CBAB8900D1B49500BB8E + 6300B5875A00AB774D00A3806300E1D9D4000000000000000000000000000000 + 000000000000317A360A2D753207000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000077130900000000000000000000 + 00000000000000000000000000000000000000000000000000004A47F0414F4C + F2FF403EEDFD3C39EB08000000000000000000000000000000002725E5082422 + E4FC312FEAFF1F1DE24100000000000000000000000000000000000000000000 + 0000F3F6F400FAFBFA00AD805700D5BB9F00D6BB9E00D3B89C00D1B39400B789 + 5D00BA8E6200B88D6100B2815600A8764E000000000000000000000000000000 + 00003985400A37833DFF317B37FB2E7633070000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000007D21F5037B1EFF00791521000000000000 + 000000000000000000000000000000000000000000005451F3415856F5FF6361 + FAFF5855F6FF413FEDFC3D3AEC080000000000000000302DE7082C2AE6FC413F + F1FF4C4AF6FF312FEAFF1F1DE241000000000000000000000000C6D4C700689A + 6C0063A26A0061A16900B17E5200E1CDB800D8C0A500D8C0A700D4BA9D00B88C + 6000B78A6000B88D6100BA8E6200B17E52000000000000000000000000004292 + 490A408E47FF54A35CFF4F9F57FF327C38FE2E77340800000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000000000000000000001832BF543A15FFF007B1FE4007919270000 + 000000000000000000000000000000000000000000005956F52B5B58F6FF6562 + FAFF7170FFFF5956F6FF4240EEFC3E3BEC083937EB083532E9FC4745F2FF6362 + FFFF4A48F4FF2F2DE9FF2220E32B00000000DCE4DD0076A07A0066A36C0093C0 + 99009EC7A40071AC7800AF7E5100E3D0BC00DAC3AB00D3B89E00C7A37D00C198 + 6F00B6895C00B78A6000BA8E6200B180540000000000000000004B9E530A499A + 51FF5BAC64FF77CA82FF74C87EFF51A059FF337D39FE2F783508000000000000 + 0000000000000000000000000000000000000000000000000000000000002197 + 51FE1B9149FE158F43FE0F8B3BFE3A9F5EFF80C196FF46A362FF007D1FE70079 + 192A0000000000000000000000000000000000000000000000005A57F52B5B59 + F6FF6663FAFF7471FFFF5A58F6FF4341EEFC3E3CECFD504DF4FF6867FFFF504E + F5FF3634EBFF2A27E52B0000000000000000649F6C00A9CDAF00A6CCAC00A2C9 + A90099C59F006BA97400AE7C4F00DCC8B000BF9F8100B88D6500D1B38F00D1B3 + 8F00BB906600BC916800B78A6000B17E52000000000053A95C0A51A65AFF63B5 + 6DFF7ECE89FF7BCC87FF76CA81FF76C981FF52A25AFF347E3AFE307935080000 + 000000000000000000000000000000000000000000000000000000000000299B + 5BFF90CAA9FF8DC8A5FF8AC6A1FF88C59EFF6AB685FF82C297FF48A566FF007D + 21EA00791B300000000000000000000000000000000000000000000000005B58 + F62B5C5AF6FF6764FAFF7472FFFF7370FFFF706EFFFF6E6CFFFF5755F7FF3F3D + EEFF3230E82B00000000000000000000000062A16900C0DAC500ADD0B300ABCE + B1009EC8A6006DAA7600957B7E005A61C8005058E3004F56E000585FC8009078 + 8400BB906600D1B38F00C6A27B00A97950005AB4650959B063FF6BBD76FF84D2 + 90FF7AC985FF60B26AFF63B46DFF78C983FF78CB82FF53A35CFF347F3AFD317A + 360800000000000000000000000000000000000000000000000000000000319F + 63FF94CDADFF6FBA8EFF6BB889FF66B685FF61B380FF67B582FF83C298FF3CA0 + 5CFF007F25FC0000000000000000000000000000000000000000000000000000 + 00005C59F62B5D5BF7FF7976FFFF5956FFFF5754FFFF7270FFFF4846F0FF3C39 + EB2B0000000000000000000000000000000060A06800C5DEC900B4D4B900A4C9 + AA0081AB9A00616DC3005058E0006668EB009393F4006163EA00585BE4004952 + DC006063BE00A6897F00C19A7100B89F8B005EB969465BB566E479C986FF80CE + 8DFF51A65AFC4DA1566F499C518B5CAD67FF7CCC86FF79CB85FF54A45DFF3580 + 3BFC317B370800000000000000000000000000000000000000000000000037A3 + 6BFF96CEB0FF94CDADFF91CBAAFF90CBA8FF74BC90FF8AC7A1FF46A568FF0787 + 35FD01832D0F0000000000000000000000000000000000000000000000000000 + 0000615EF8085D5AF6FD7D79FFFF5E5BFFFF5B58FFFF7674FFFF4643EFFD413F + ED08000000000000000000000000000000005D9F6500B9D6BE0087BA8F0071AC + 78005359DC00666AEB009896F4009191F300898AF0005B5FE7005F62E9005D61 + E8005158E4004A55D800E3DDDB00FCFAFA00000000005FBA6A3C5CB666E66DC0 + 79FF55AC5F6F00000000000000004A9D52915EAE68FF7DCD89FF7CCD87FF56A5 + 5FFF36813CFC327C380800000000000000000000000000000000000000003DA5 + 6FFF37A36DFD33A167FD2F9D61FD55AF7CFF91CBAAFF4FAB74FF178F45FD118B + 3D0C000000000000000000000000000000000000000000000000000000006967 + FB086663F9FC706DFBFF807EFFFF7E7BFFFF7C79FFFF7977FFFF5E5CF7FF4744 + EFFC4240EE0800000000000000000000000065A06C0086BA8F0099C6A20074AD + 7C004F57E200B4B1F9009796F4009393F4008C8DF0005C60E8005C61E7005D61 + E8005F62E9004F57E200E4E5F2000000000000000000000000005FBB6A435CB7 + 6765000000000000000000000000000000004B9E53915FAF69FF7FCE8AFF7ECE + 89FF57A660FF37823DFC337D3908000000000000000000000000000000000000 + 0000000000000000000000000000319F63F55AB381FF289857FF1F954F090000 + 0000000000000000000000000000000000000000000000000000716EFD086E6B + FCFC7774FDFF8682FFFF7673FCFF6462F8FF605DF7FF6D6AFAFF7B79FFFF605D + F7FF4845EFFC4341EE08000000000000000093B397007CB4850076AF7E006FAB + 78004E54E100B4B1F9009596F500666AEB006F71EC006E72EC005A5CE5005C61 + E7005F62E9005158E200E4E5F200000000000000000000000000000000000000 + 000000000000000000000000000000000000000000004B9F549160B06AFF81CF + 8DFF7FCF8BFF58A761FF398540FF347E3A080000000000000000000000000000 + 000000000000000000000000000037A36BF5319F65FF2D9D5F09000000000000 + 000000000000000000000000000000000000000000007673FF087471FEFD7D7A + FEFF8A87FFFF7C79FDFF6C69FBFF6361F92B5F5CF72B615EF8FF6E6CFAFF7D7A + FFFF615FF7FF4946F0FC4441EE0500000000FAFBFA00DFE6DF00CBD7CC006EA8 + 77004C52E000A2A2F4006A6CEC006163EA009793F7009793F7006468E9006566 + EA005C61E7004F57E200E4E5F200000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000000000004CA0559162B2 + 6CFF82D18FFF7AC885FF57A660FF38843F7B0000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000000000000000000000000000000000007774FF1F7A77FFFF817E + FFFF817EFEFF7471FDFF6C69FB2B0000000000000000605DF72B625FF8FF6F6D + FBFF7E7CFFFF625FF8FF4A47F06F4542EE020000000000000000000000000000 + 0000555BDB007C7CF2009793F7006468E9005258E3005258E3006468E9009793 + F7007C7CF2004E57D900E9EAF500000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000004DA1 + 569163B36DFF5FAF69FF41914979000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000000000007774FF1F7A77 + FFFF7976FEFF726FFD2B00000000000000000000000000000000615EF82B6461 + F8FF6A68F9FF5451F3A84F4DF229000000000000000000000000000000000000 + 00009195D9006E6FEC006668EB005F62E9007878F0007474F0005F62E900696B + EB006F71EC009094D70000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00004EA257914A9D527F00000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000007774 + FF1F7774FF2B000000000000000000000000000000000000000000000000625F + F82B5D5BF76F5956F53E00000000000000000000000000000000000000000000 + 0000FAFAFD00DFE1F100CBCDE7006163E3005157E2005157E2005F62E300C9CC + E600DFE0F100FAFAFD0000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00006360F80A0000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000000000000000000000000000424D3E000000000000003E000000 + 2800000040000000200000000100010000000000000100000000000000000000 + 000000000000000000000000FFFFFF0000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000FFFFFFFFFF03FFFFFFFFE7E7FC00F9FF + FF7FC3C3F000F0FFFE3F8181C000E07FFE1F80010000C03FE00FC0030000801F + E007E0070000000FE007F00F00000007E007F00F00008603E00FE0070001CF01 + FE1FC0030001FF80FE3F80010001FFC0FFFF8180F001FFE1FFFFC3C1F003FFF3 + FFFFE7E3F003FFFFFFFFFFF7FFFFFFFF00000000000000000000000000000000 + 000000000000} + end + object StateImages: TImageList + Left = 99 + Top = 176 + Bitmap = { + 494C010103003800480010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600 + 0000000000003600000028000000400000001000000001002000000000000010 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000001919 + 1926151515661515157515151575151515751515157515151575151515751515 + 1566191919260000000000000000000000000000000000000000000000001919 + 1926151515661515157515151575151515751515157515151575151515751515 + 1566191919260000000000000000000000000000000000000000000000001919 + 1926151515661515157515151575151515751515157515151575151515751515 + 1566191919260000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000003838 + 3862CCCCCCD6E9E9E9FFE8E8E8FFE8E8E8FFE8E8E8FFE8E8E8FFE9E9E9FFCCCC + CCD6383838620000000000000000000000000000000000000000000000003838 + 3862CCCCCCD6E9E9E9FFE8E8E8FFE8E8E8FFE8E8E8FFE8E8E8FFE9E9E9FFCCCC + CCD6383838620000000000000000000000000000000000000000000000003838 + 3862CCCCCCD6E9E9E9FFE8E8E8FFE8E8E8FFE8E8E8FFE8E8E8FFE9E9E9FFCCCC + CCD6383838620000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000004949 + 496EEBEBEBFFE3E3E3FFE3E3E3FFE3E3E3FFE3E3E3FFE3E3E3FFE3E3E3FFEBEB + EBFF4949496E0000000000000000000000000000000000000000000000004949 + 496EEBEBEBFFE3E3E3FFD3D3D3FF5C5C5CFFD3D3D3FFE3E3E3FFE3E3E3FFEBEB + EBFF4949496E0000000000000000000000000000000000000000000000004949 + 496EEBEBEBFFE3E3E3FFE3E3E3FFE3E3E3FFE3E3E3FFE3E3E3FFE3E3E3FFEBEB + EBFF4949496E0000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000005252 + 526DEEEEEEFFE8E8E8FFE8E8E8FFE8E8E8FFE8E8E8FFE8E8E8FFE8E8E8FFEEEE + EEFF5252526D0000000000000000000000000000000000000000000000005252 + 526DEEEEEEFFD9D9D9FF6B6B6BFF6B6B6BFF6B6B6BFFD9D9D9FFE8E8E8FFEEEE + EEFF5252526D0000000000000000000000000000000000000000000000005252 + 526DEEEEEEFFE8E8E8FFE8E8E8FFE8E8E8FFE8E8E8FFE8E8E8FFE8E8E8FFEEEE + EEFF5252526D0000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000005C5C + 5C6CF1F1F1FFEDEDEDFFEDEDEDFFEDEDEDFFEDEDEDFFEDEDEDFFEDEDEDFFF1F1 + F1FF5C5C5C6C0000000000000000000000000000000000000000000000005C5C + 5C6CF1F1F1FF7C7C7CFF7C7C7CFFD0D0D0FF7C7C7CFF7C7C7CFFDFDFDFFFF1F1 + F1FF5C5C5C6C0000000000000000000000000000000000000000000000005C5C + 5C6CF1F1F1FFEDEDEDFFEDEDEDFFEDEDEDFFEDEDEDFFEDEDEDFFEDEDEDFFF1F1 + F1FF5C5C5C6C0000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000006464 + 646AF6F6F6FFF2F2F2FFF2F2F2FFF2F2F2FFF2F2F2FFF2F2F2FFF2F2F2FFF6F6 + F6FF6464646A0000000000000000000000000000000000000000000000006464 + 646AF6F6F6FF8C8C8CFFE5E5E5FFF2F2F2FFE5E5E5FF8C8C8CFF8C8C8CFFF6F6 + F6FF6464646A0000000000000000000000000000000000000000000000006464 + 646AF6F6F6FFF2F2F2FFF2F2F2FFF2F2F2FFF2F2F2FFF2F2F2FFF2F2F2FFF6F6 + F6FF6464646A0000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000006C6C + 6C69F9F9F9FFF7F7F7FFF7F7F7FFF7F7F7FFF7F7F7FFF7F7F7FFF7F7F7FFF9F9 + F9FF6C6C6C690000000000000000000000000000000000000000000000006C6C + 6C69F9F9F9FFF7F7F7FFF7F7F7FFF7F7F7FFF7F7F7FFEBEBEBFF979797FFF9F9 + F9FF6C6C6C690000000000000000000000000000000000000000000000006C6C + 6C69F9F9F9FFF7F7F7FFF7F7F7FFF7F7F7FFF7F7F7FFF7F7F7FFF7F7F7FFF9F9 + F9FF6C6C6C690000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000007474 + 7468FDFDFDFFFBFBFBFFFBFBFBFFFBFBFBFFFBFBFBFFFBFBFBFFFBFBFBFFFDFD + FDFF747474680000000000000000000000000000000000000000000000007474 + 7468FDFDFDFFFBFBFBFFFBFBFBFFFBFBFBFFFBFBFBFFFBFBFBFFE3E3E3FFFDFD + FDFF747474680000000000000000000000000000000000000000000000007474 + 7468FDFDFDFFFBFBFBFFFBFBFBFFFBFBFBFFFBFBFBFFFBFBFBFFFBFBFBFFFDFD + FDFF747474680000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000007A7A + 7A5AE9E9E9D3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9E9 + E9D37A7A7A5A0000000000000000000000000000000000000000000000007A7A + 7A5AE9E9E9D3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9E9 + E9D37A7A7A5A0000000000000000000000000000000000000000000000007A7A + 7A5AE9E9E9D3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9E9 + E9D37A7A7A5A0000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000007F7F + 7F227F7F7F597F7F7F667F7F7F667F7F7F667F7F7F667F7F7F667F7F7F667F7F + 7F597F7F7F220000000000000000000000000000000000000000000000007F7F + 7F227F7F7F597F7F7F667F7F7F667F7F7F667F7F7F667F7F7F667F7F7F667F7F + 7F597F7F7F220000000000000000000000000000000000000000000000007F7F + 7F227F7F7F597F7F7F667F7F7F667F7F7F667F7F7F667F7F7F667F7F7F667F7F + 7F597F7F7F220000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000000000000000000000000000424D3E000000000000003E000000 + 2800000040000000100000000100010000000000800000000000000000000000 + 000000000000000000000000FFFFFF00FFFFFFFFFFFF0000FFFFFFFFFFFF0000 + FFFFFFFFFFFF0000E007E007E0070000E007E007E0070000E007E007E0070000 + E007E007E0070000E007E007E0070000E007E007E0070000E007E007E0070000 + E007E007E0070000E007E007E0070000E007E007E0070000FFFFFFFFFFFF0000 + FFFFFFFFFFFF0000FFFFFFFFFFFF000000000000000000000000000000000000 + 000000000000} + end +end diff --git a/components/fpexif/tests/multiread/common/mrtmain.lfm b/components/fpexif/tests/multiread/common/mrtmain.lfm new file mode 100644 index 000000000..cfdbcb7be --- /dev/null +++ b/components/fpexif/tests/multiread/common/mrtmain.lfm @@ -0,0 +1,425 @@ +object MainForm: TMainForm + Left = 326 + Height = 560 + Top = 138 + Width = 1024 + ActiveControl = BtnRunTest + Caption = 'Multi read test' + ClientHeight = 560 + ClientWidth = 1024 + OnCreate = FormCreate + LCLVersion = '1.9.0.0' + object Panel1: TPanel + Left = 0 + Height = 33 + Top = 0 + Width = 1024 + Align = alTop + AutoSize = True + BevelOuter = bvNone + BorderWidth = 4 + ClientHeight = 33 + ClientWidth = 1024 + TabOrder = 0 + object BtnReadFiles: TButton + Left = 945 + Height = 25 + Top = 4 + Width = 75 + Align = alRight + Caption = 'Read files' + OnClick = BtnReadFilesClick + TabOrder = 0 + end + object EdImageDir: TEdit + Left = 4 + Height = 23 + Top = 4 + Width = 931 + Anchors = [akTop, akLeft, akRight] + TabOrder = 1 + Text = '..\pictures\originals' + end + end + object Panel2: TPanel + Left = 0 + Height = 527 + Top = 33 + Width = 403 + Align = alLeft + BevelOuter = bvNone + BorderWidth = 4 + Caption = 'Panel2' + ClientHeight = 527 + ClientWidth = 403 + TabOrder = 1 + object FileTreeView: TTreeView + Left = 4 + Height = 490 + Top = 4 + Width = 395 + Align = alClient + Images = ImageList1 + ReadOnly = True + StateImages = StateImages + TabOrder = 0 + OnClick = FileTreeViewClick + Options = [tvoAutoItemHeight, tvoHideSelection, tvoKeepCollapsedNodes, tvoReadOnly, tvoShowButtons, tvoShowLines, tvoShowRoot, tvoToolTips, tvoThemedDraw] + end + object Panel3: TPanel + Left = 4 + Height = 25 + Top = 498 + Width = 395 + Align = alBottom + AutoSize = True + BevelOuter = bvNone + ClientHeight = 25 + ClientWidth = 395 + TabOrder = 1 + object BtnCreateTxtFiles: TButton + Left = 0 + Height = 25 + Top = 0 + Width = 100 + AutoSize = True + Caption = 'Create txt files' + OnClick = BtnCreateTxtFilesClick + TabOrder = 0 + end + object BtnUncheckAll: TButton + Left = 104 + Height = 25 + Top = 0 + Width = 75 + Caption = 'Uncheck all' + OnClick = BtnUncheckAllClick + TabOrder = 1 + end + object BtnCheckAll: TButton + Left = 184 + Height = 25 + Top = 0 + Width = 75 + Caption = 'Check all' + OnClick = BtnUncheckAllClick + TabOrder = 2 + end + end + object Bevel1: TBevel + Left = 4 + Height = 4 + Top = 494 + Width = 395 + Align = alBottom + Shape = bsSpacer + end + end + object Splitter1: TSplitter + Left = 403 + Height = 527 + Top = 33 + Width = 5 + end + object Panel4: TPanel + Left = 408 + Height = 527 + Top = 33 + Width = 616 + Align = alClient + BevelOuter = bvNone + BorderWidth = 4 + ClientHeight = 527 + ClientWidth = 616 + TabOrder = 3 + object Panel5: TPanel + Left = 4 + Height = 25 + Top = 498 + Width = 608 + Align = alBottom + AutoSize = True + BevelOuter = bvNone + ClientHeight = 25 + ClientWidth = 608 + TabOrder = 0 + object BtnRunTest: TButton + Left = 539 + Height = 25 + Top = 0 + Width = 69 + Align = alRight + AutoSize = True + Caption = 'Run test' + OnClick = BtnRunTestClick + TabOrder = 0 + end + object MismatchInfo: TLabel + Left = 0 + Height = 25 + Top = 0 + Width = 266 + Align = alLeft + AutoSize = False + Caption = 'MismatchInfo' + Layout = tlCenter + ParentColor = False + end + end + object Memo: TMemo + Left = 4 + Height = 490 + Top = 4 + Width = 608 + Align = alClient + ScrollBars = ssAutoBoth + TabOrder = 1 + end + object Bevel2: TBevel + Left = 4 + Height = 4 + Top = 494 + Width = 608 + Align = alBottom + Shape = bsSpacer + end + end + object ImageList1: TImageList + left = 99 + top = 111 + Bitmap = { + 4C69040000001000000010000000FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF0037A36BF5319F65FF2D9D5F09FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00319F63F55AB381FF289857FF1F954F09FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF003DA56FFF37A3 + 6DFD33A167FD2F9D61FD55AF7CFF91CBAAFF4FAB74FF178F45FD118B3D0CFFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0037A36BFF96CE + B0FF94CDADFF91CBAAFF90CBA8FF74BC90FF8AC7A1FF46A568FF078735FD0183 + 2D0FFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00319F63FF94CD + ADFF6FBA8EFF6BB889FF66B685FF61B380FF67B582FF83C298FF3CA05CFF007F + 25FCFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00299B5BFF90CA + A9FF8DC8A5FF8AC6A1FF88C59EFF6AB685FF82C297FF48A566FF007D21EA0079 + 1B30FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00219751FE1B91 + 49FE158F43FE0F8B3BFE3A9F5EFF80C196FF46A362FF007D1FE70079192AFFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF0001832BF543A15FFF007B1FE400791927FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00007D21F5037B1EFF00791521FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF0000771309FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF006360 + F80AFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF007774FF1F7774 + FF2BFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00625FF82B5D5B + F76F5956F53EFFFFFF00FFFFFF00FFFFFF00FFFFFF007774FF1F7A77FFFF7976 + FEFF726FFD2BFFFFFF00FFFFFF00FFFFFF00FFFFFF00615EF82B6461F8FF6A68 + F9FF5451F3A84F4DF229FFFFFF00FFFFFF007774FF1F7A77FFFF817EFFFF817E + FEFF7471FDFF6C69FB2BFFFFFF00FFFFFF00605DF72B625FF8FF6F6DFBFF7E7C + FFFF625FF8FF4A47F06F4542EE02FFFFFF007673FF087471FEFD7D7AFEFF8A87 + FFFF7C79FDFF6C69FBFF6361F92B5F5CF72B615EF8FF6E6CFAFF7D7AFFFF615F + F7FF4946F0FC4441EE05FFFFFF00FFFFFF00FFFFFF00716EFD086E6BFCFC7774 + FDFF8682FFFF7673FCFF6462F8FF605DF7FF6D6AFAFF7B79FFFF605DF7FF4845 + EFFC4341EE08FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF006967FB086663 + F9FC706DFBFF807EFFFF7E7BFFFF7C79FFFF7977FFFF5E5CF7FF4744EFFC4240 + EE08FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00615E + F8085D5AF6FD7D79FFFF5E5BFFFF5B58FFFF7674FFFF4643EFFD413FED08FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF005C59 + F62B5D5BF7FF7976FFFF5956FFFF5754FFFF7270FFFF4846F0FF3C39EB2BFFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF005B58F62B5C5A + F6FF6764FAFF7472FFFF7370FFFF706EFFFF6E6CFFFF5755F7FF3F3DEEFF3230 + E82BFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF005A57F52B5B59F6FF6663 + FAFF7471FFFF5A58F6FF4341EEFC3E3CECFD504DF4FF6867FFFF504EF5FF3634 + EBFF2A27E52BFFFFFF00FFFFFF00FFFFFF005956F52B5B58F6FF6562FAFF7170 + FFFF5956F6FF4240EEFC3E3BEC083937EB083532E9FC4745F2FF6362FFFF4A48 + F4FF2F2DE9FF2220E32BFFFFFF00FFFFFF005451F3415856F5FF6361FAFF5855 + F6FF413FEDFC3D3AEC08FFFFFF00FFFFFF00302DE7082C2AE6FC413FF1FF4C4A + F6FF312FEAFF1F1DE241FFFFFF00FFFFFF00FFFFFF004A47F0414F4CF2FF403E + EDFD3C39EB08FFFFFF00FFFFFF00FFFFFF00FFFFFF002725E5082422E4FC312F + EAFF1F1DE241FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF003F3DED413B38 + EB08FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00211FE3081E1C + E241FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00C2880100C2880100C2880100C2880100C389 + 014EC38901DEC38901DEC38901DEC38901DEC38901DEC389014EC2880100C288 + 0100C2880100C2880100FFFFFF00BF840100BF840100BF840100BF840100BF84 + 01BCE7B236F3FECB54FFFECB54FFFECB54FFE7B236F3BF8401BCBF840100BF84 + 0100BF840100BF840100FFFFFF005D406000B97E0100B97E0100B97E0100B97E + 01DCEBB841FFDDAA33FFDDAA33FFDDAA33FFEBB841FFB97E01DCB97E0100B97E + 0100B97E0100B97E0100FFFFFF000000BF002D1E9000B1760100B1760100B176 + 01DBE7B43DFFDAA730FFDAA730FFDAA730FFE7B43DFFB17601DBB1760100B176 + 0100B17601005A912B00FFFFFF000000BF000000BF002B1C9000A96E0100A96E + 01D9E1AE38FFD6A32CFFD6A32CFFD6A32CFFE1AE38FFA96E01D9A96E0100A96E + 01002B9C400000AA5500FFFFFF000000BF000000BF000000BF00291A9000A165 + 01D7DCA932FFD19E27FFD19E27FFD19E27FFDCA932FFA16501D7A1650100299A + 400000AA550000AA5500FFFFFF000000BE480000BECC0000BECC0000BECC5030 + 5AE6DCA932FFD7A42DFFD7A42DFFD7A42DFFDCA932FF975A01D52797400000AA + 550000AA550000AA5500FFFFFF000000BAAE4C4BE6ED7675FEFF7675FEFF7962 + A3FF7B5055FF7B5055FF594528EF595D19EF595D19EF3F742BE500A954CC00A9 + 54CC00A954CC00A95448FFFFFF000000B4CC4635F2FF2000E9FF2000E9FF2000 + E9FF2000E9FF2000E9FF005282CC44CC88FF65EDA9FF65EDA9FF65EDA9FF65ED + A9FF41D389ED00A450AEFFFFFF000000AECC3E2DECFF1C00E3FF1C00E3FF1C00 + E3FF1C00E3FF1C00E3FF004E7CCC42CA86FF51D995FF42CA86FF42CA86FF42CA + 86FF51D995FF009C4ACCFFFFFF000000A6CC3625E4FF1900DBFF1900DBFF1900 + DBFF1900DBFF1900DBFF004A75CC3EC682FF4DD591FF3EC682FF3EC682FF3EC6 + 82FF4DD591FF009444CCFFFFFF0000009ECC2D1CDEFF1500D5FF1500D5FF1500 + D5FF1500D5FF1500D5FF00466DCC3BC37FFF48CF8CFF3BC37FFF3BC37FFF3BC3 + 7FFF48CF8CFF008B3CCCFFFFFF00000095CC2F1EDCFF2513D8FF2513D8FF2513 + D8FF2513D8FF2513D8FF004165CC37BF7BFF42CA86FF37BF7BFF37BF7BFF37BF + 7BFF42CA86FF008134CCFFFFFF0000007E9900007ACC00007ACC00007ACC0000 + 7ACC00007ACC00007ACC003B53CC35BD79FF43CB87FF3EC682FF3EC682FF3EC6 + 82FF43CB87FF00752BCCFFFFFF0000007A000000730000007300000073000000 + 7300000073000000730000591499005510CC005510CC005510CC005510CC0055 + 10CC005510CC00591499FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF004EA2 + 57914A9D527FFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF004DA1569163B3 + 6DFF5FAF69FF41914979FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF004CA0559162B26CFF82D1 + 8FFF7AC885FF57A660FF38843F7BFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF004B9F549160B06AFF81CF8DFF7FCF + 8BFF58A761FF398540FF347E3A08FFFFFF00FFFFFF005FBB6A435CB76765FFFF + FF00FFFFFF00FFFFFF00FFFFFF004B9E53915FAF69FF7FCE8AFF7ECE89FF57A6 + 60FF37823DFC337D3908FFFFFF00FFFFFF005FBA6A3C5CB666E66DC079FF55AC + 5F6FFFFFFF00FFFFFF004A9D52915EAE68FF7DCD89FF7CCD87FF56A55FFF3681 + 3CFC327C3808FFFFFF00FFFFFF005EB969465BB566E479C986FF80CE8DFF51A6 + 5AFC4DA1566F499C518B5CAD67FF7CCC86FF79CB85FF54A45DFF35803BFC317B + 3708FFFFFF00FFFFFF00FFFFFF005AB4650959B063FF6BBD76FF84D290FF7AC9 + 85FF60B26AFF63B46DFF78C983FF78CB82FF53A35CFF347F3AFD317A3608FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0053A95C0A51A65AFF63B56DFF7ECE + 89FF7BCC87FF76CA81FF76C981FF52A25AFF347E3AFE30793508FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF004B9E530A499A51FF5BAC + 64FF77CA82FF74C87EFF51A059FF337D39FE2F783508FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF004292490A408E + 47FF54A35CFF4F9F57FF327C38FE2E773408FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF003985 + 400A37833DFF317B37FB2E763307FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00317A360A2D753207FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00 + } + end + object StateImages: TImageList + left = 99 + top = 176 + Bitmap = { + 4C69030000001000000010000000B8A596EFB5A291FFB39F8FFFB39F8EFFB39F + 8EFFB39F8EFFB39F8EFFB39F8EFFB39F8EFFB39F8EFFB39F8EFFB39F8EFFB39F + 8FFFB5A291FFB8A596EFFFFFFF00B5A291FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFB5A291FFFFFFFF00B4A090FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFB4A090FFFFFFFF00B4A090FFFEFEFCFFFEFCFBFFFCFAF9FFFCFA + F9FFFCFAF9FFFCFAF9FFFCFAF9FFFCFAF9FFFCFAF9FFFCFAF9FFFCFAF9FFFEFC + FBFFFEFEFCFFB4A090FFFFFFFF00B4A090FFFBF9F5FFFCF9F7FFFAF7F5FFFAF7 + F5FFFAF7F5FFFAF7F5FFFAF7F5FFFAF7F5FFFAF7F5FFFAF7F5FFFAF7F5FFFCF9 + F7FFFBF9F5FFB4A090FFFFFFFF00B4A091FFF7F4EFFFFBF7F4FFF9F5F2FFF9F5 + F2FFF9F5F2FFF9F5F2FFF9F5F2FFF9F5F2FFF9F5F2FFF9F5F2FFF9F5F2FFFBF7 + F4FFF7F4EFFFB4A091FFFFFFFF00B4A191FFF4F0E9FFF9F4F0FFF7F2EEFFF7F2 + EEFFF7F2EEFFF7F2EEFFF7F2EEFFF7F2EEFFF7F2EEFFF7F2EEFFF7F2EEFFF9F4 + F0FFF4F0E9FFB4A191FFFFFFFF00B5A191FFF0ECE4FFF7F1EEFFF5EFEBFFF5EF + EBFFF5EFEBFFF5EFEBFFF5EFEBFFF5EFEBFFF5EFEBFFF5EFEBFFF5EFEBFFF7F1 + EEFFF0ECE4FFB5A191FFFFFFFF00B5A292FFEDE7DEFFF5EFEAFFF3EDE7FFF3ED + E7FFF3EDE7FFF3EDE7FFF3EDE7FFF3EDE7FFF3EDE7FFF3EDE7FFF3EDE7FFF5EF + EAFFEDE7DEFFB5A292FFFFFFFF00B5A293FFE9E3D9FFF4ECE6FFF2EAE3FFF2EA + E3FFF2EAE3FFF2EAE3FFF2EAE3FFF2EAE3FFF2EAE3FFF2EAE3FFF2EAE3FFF4EC + E6FFE9E3D9FFB5A293FFFFFFFF00B5A393FFE6DED3FFF2E9E3FFF0E7E0FFF0E7 + E0FFF0E7E0FFF0E7E0FFF0E7E0FFF0E7E0FFF0E7E0FFF0E7E0FFF0E7E0FFF2E9 + E3FFE6DED3FFB5A393FFFFFFFF00B6A394FFE3D9CDFFF0E6DFFFEFE4DDFFEEE4 + DCFFEEE4DCFFEEE4DCFFEEE4DCFFEEE4DCFFEEE4DCFFEEE4DCFFEFE4DDFFF0E6 + DFFFE3D9CDFFB6A394FFFFFFFF00B6A394FFE1D5C9FFF0E6DEFFEEE5DCFFEEE4 + DCFFEEE4DCFFEEE4DCFFEEE4DCFFEEE4DCFFEEE4DCFFEEE4DCFFEEE5DCFFF0E6 + DEFFE1D5C9FFB6A394FFFFFFFF00B7A596FFE0D5C6FFDDD2C3FFDDD1C2FFDDD1 + C2FFDDD1C2FFDDD1C2FFDDD1C2FFDDD1C2FFDDD1C2FFDDD1C2FFDDD1C2FFDDD2 + C3FFE0D5C6FFB7A596FFFFFFFF00AA998BC0B7A596FFB7A495FFB6A394FFB6A3 + 94FFB6A394FFB6A394FFB6A394FFB6A394FFB6A394FFB6A394FFB6A394FFB7A4 + 95FFB7A596FFB3A293E1FFFFFF00000000000000003300000033000000330000 + 0033000000330000003300000033000000330000003300000033000000330000 + 0033000000330000002CFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF007F7F7F227F7F + 7F597F7F7F667F7F7F667F7F7F667F7F7F667F7F7F667F7F7F667F7F7F597F7F + 7F22FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF007A7A7A5AE9E9 + E9D3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9E9E9D37A7A + 7A5AFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0074747468FDFD + FDFFFBFBFBFFFBFBFBFFFBFBFBFFFBFBFBFFFBFBFBFFE3E3E3FFFDFDFDFF7474 + 7468FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF006C6C6C69F9F9 + F9FFF7F7F7FFF7F7F7FFF7F7F7FFF7F7F7FFEBEBEBFF979797FFF9F9F9FF6C6C + 6C69FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF006464646AF6F6 + F6FF8C8C8CFFE5E5E5FFF2F2F2FFE5E5E5FF8C8C8CFF8C8C8CFFF6F6F6FF6464 + 646AFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF005C5C5C6CF1F1 + F1FF7C7C7CFF7C7C7CFFD0D0D0FF7C7C7CFF7C7C7CFFDFDFDFFFF1F1F1FF5C5C + 5C6CFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF005252526DEEEE + EEFFD9D9D9FF6B6B6BFF6B6B6BFF6B6B6BFFD9D9D9FFE8E8E8FFEEEEEEFF5252 + 526DFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF004949496EEBEB + EBFFE3E3E3FFD3D3D3FF5C5C5CFFD3D3D3FFE3E3E3FFE3E3E3FFEBEBEBFF4949 + 496EFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0038383862CCCC + CCD6E9E9E9FFE8E8E8FFE8E8E8FFE8E8E8FFE8E8E8FFE9E9E9FFCCCCCCD63838 + 3862FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00191919261515 + 1566151515751515157515151575151515751515157515151575151515661919 + 1926FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF007F7F7F227F7F + 7F597F7F7F667F7F7F667F7F7F667F7F7F667F7F7F667F7F7F667F7F7F597F7F + 7F22FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF007A7A7A5AE9E9 + E9D3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9E9E9D37A7A + 7A5AFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0074747468FDFD + FDFFFBFBFBFFFBFBFBFFFBFBFBFFFBFBFBFFFBFBFBFFFBFBFBFFFDFDFDFF7474 + 7468FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF006C6C6C69F9F9 + F9FFF7F7F7FFF7F7F7FFF7F7F7FFF7F7F7FFF7F7F7FFF7F7F7FFF9F9F9FF6C6C + 6C69FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF006464646AF6F6 + F6FFF2F2F2FFF2F2F2FFF2F2F2FFF2F2F2FFF2F2F2FFF2F2F2FFF6F6F6FF6464 + 646AFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF005C5C5C6CF1F1 + F1FFEDEDEDFFEDEDEDFFEDEDEDFFEDEDEDFFEDEDEDFFEDEDEDFFF1F1F1FF5C5C + 5C6CFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF005252526DEEEE + EEFFE8E8E8FFE8E8E8FFE8E8E8FFE8E8E8FFE8E8E8FFE8E8E8FFEEEEEEFF5252 + 526DFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF004949496EEBEB + EBFFE3E3E3FFE3E3E3FFE3E3E3FFE3E3E3FFE3E3E3FFE3E3E3FFEBEBEBFF4949 + 496EFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0038383862CCCC + CCD6E9E9E9FFE8E8E8FFE8E8E8FFE8E8E8FFE8E8E8FFE9E9E9FFCCCCCCD63838 + 3862FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00191919261515 + 1566151515751515157515151575151515751515157515151575151515661919 + 1926FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00 + } + end +end diff --git a/components/fpexif/tests/multiread/common/mrtmain.pas b/components/fpexif/tests/multiread/common/mrtmain.pas new file mode 100644 index 000000000..0ce0fbf3b --- /dev/null +++ b/components/fpexif/tests/multiread/common/mrtmain.pas @@ -0,0 +1,526 @@ +unit mrtmain; + +{$IFDEF FPC} + {$mode objfpc}{$H+} +{$ENDIF} + +interface + +uses + {$IFDEF FPC} + FileUtil, + {$ELSE} + Windows, ImgList, {$IFDEF UNICODE}ImageList,{$ENDIF} + {$ENDIF} + Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, + ComCtrls, ExtCtrls, + fpeMetaData; + +type + + { TMainForm } + + TMainForm = class(TForm) + Bevel1: TBevel; + Bevel2: TBevel; + BtnReadFiles: TButton; + BtnCreateTxtFiles: TButton; + BtnRunTest: TButton; + BtnUncheckAll: TButton; + BtnCheckAll: TButton; + EdImageDir: TEdit; + StateImages: TImageList; + MismatchInfo: TLabel; + Memo: TMemo; + Panel1: TPanel; + Panel2: TPanel; + Panel3: TPanel; + FileTreeView: TTreeView; + Panel4: TPanel; + Panel5: TPanel; + Splitter1: TSplitter; + ImageList1: TImageList; + procedure BtnReadFilesClick(Sender: TObject); + procedure BtnRunTestClick(Sender: TObject); + procedure BtnCreateTxtFilesClick(Sender: TObject); + procedure BtnUncheckAllClick(Sender: TObject); + procedure FileTreeViewClick(Sender: TObject); + procedure FormCreate(Sender: TObject); + private + FTotalCount: Integer; + FMismatchCount: Integer; + function CreateRefTags(ANode: TTreeNode; AFileName: String): Boolean; + function ExtractRefTags(ANode: TTreeNode; AList: TStringList): Boolean; + function GetImageDir: String; + procedure Log(AMsg: String); + procedure RunTest(ANode: TTreeNode); + public + + end; + +var + MainForm: TMainForm; + +implementation + +{$IFDEF FPC} + {$R *.lfm} +{$ELSE} + {$R *.dfm} +{$ENDIF} + +uses + {$IFDEF FPC} + Process, StrUtils, + {$ELSE} + ShellApi, + {$ENDIF} + fpeGlobal, fpeUtils, fpeTags, fpeExifData; + +{ TMainForm } + +const + EXIFTOOL_CMD = '..\..\tools\exiftool.exe'; + + IMG_INDEX_WORKING = 0; + IMG_INDEX_FAIL = 1; + IMG_INDEX_IGNORE = 1; + IMG_INDEX_EXIF = 2; + IMG_INDEX_SUCCESS = 3; + + IMG_UNCHECKED = 2; //0; + IMG_CHECKED = 1; + +{ Finds all image files in the image folder (--> GetImageDir). For every image + there is a text file containing the meta data written by ExifTool. Reads this + reference file and stores the meta data in the nodes af the FileTreeView. } +procedure TMainForm.BtnReadFilesClick(Sender: TObject); +var + info: TSearchRec; + imgDir: String; + node: TTreeNode; + tagFile: String; + L: TStringList; + s: String; +begin + FileTreeView.Items.Clear; + imgDir := GetImageDir; + if FindFirst(imgDir + '*.jpg', faAnyFile and faDirectory, info) = 0 then + begin + repeat + if (info.Name <> '.') and (info.Name <> '..') and (info.Attr and faDirectory = 0) then + begin + node := FileTreeview.Items.AddChild(nil, ExtractFileName(info.Name)); + node.ImageIndex := IMG_INDEX_IGNORE; + tagFile := ChangeFileExt(imgDir + info.Name, '.txt'); + if FileExists(tagFile) then + begin + L := TStringList.Create; + try + L.LoadFromFile(tagFile); + // Note: ExifTool wrote the file in UTF8 --> We must convert this for Delphi + {$IFNDEF FPC} + {$IFDEF UNICODE} + L.Text := UTF8Decode(L.Text); + {$ELSE} + s := L.Text; +// s := UTF8Decode(s); + s := fpeUtils.UTF8ToAnsi(s); + L.Text := s; + {$ENDIF} + {$ENDIF} + if ExtractRefTags(node, L) then begin + node.ImageIndex := IMG_INDEX_EXIF; + node.StateIndex := IMG_CHECKED; + end; + finally + L.Free; + end; + end; + node.SelectedIndex := node.ImageIndex; + end; + until FindNext(info) <> 0; + end; + FindClose(info); +end; + +procedure TMainForm.BtnRunTestClick(Sender: TObject); +var + node: TTreeNode; +begin + Memo.Lines.Clear; + FMismatchCount := 0; + FTotalCount := 0; + node := FileTreeView.Items.GetFirstNode; + while node <> nil do begin + RunTest(node); + node := node.GetNextSibling; + end; + MismatchInfo.Caption := Format('%d mismatches out of %d tests (%.0f%%)', [ + FMismatchCount, FTotalCount, FMismatchCount/FTotalCount*100]); + MismatchInfo.Show; +end; + +procedure TMainForm.BtnUncheckAllClick(Sender: TObject); +var + node: TTreeNode; + checkNode: Boolean; +begin + checkNode := (Sender = BtnCheckAll); + node := FileTreeView.Items.GetFirstNode; + while node <> nil do begin + if checkNode and (node.StateIndex = IMG_UNCHECKED) then + node.StateIndex := IMG_CHECKED + else if not checkNode and (node.StateIndex = IMG_CHECKED) then + node.StateIndex := IMG_UNCHECKED; + node := node.GetNextSibling; + end; +end; + +procedure TMainForm.BtnCreateTxtFilesClick(Sender: TObject); +var + imgDir: String; + node: TTreeNode; +begin + if not FileExists(EXIFTOOL_CMD) then + begin + MessageDlg(Format('Program "ExifTool" not found in folder "%s".', [ + ExtractFileDir(ExpandFilename(EXIFTOOL_CMD)) + ]), mtError, [mbOK], 0 + ); + exit; + end; + + imgDir := GetImageDir; + + node := FileTreeView.Items.GetFirstNode; + while (node <> nil) do begin + node.DeleteChildren; + node.ImageIndex := -1; + node := node.GetNextSibling; + end; + + node := FileTreeView.Items.GetFirstNode; + while (node <> nil) do begin + node.ImageIndex := IMG_INDEX_WORKING; + Application.ProcessMessages; + if not CreateRefTags(node, imgDir + node.Text) then begin + node.ImageIndex := IMG_INDEX_IGNORE; + end else + node.ImageIndex := IMG_INDEX_EXIF; + node.SelectedIndex := node.ImageIndex; + node := node.GetNextSibling; + end; +end; + +function TMainForm.CreateRefTags(ANode: TTreeNode; AFileName: String): Boolean; +var + destFile: String; + output: String; + L: TStringList; +{$IFNDEF FPC} + params: String; + res: Integer; + s: String; +const + DEG_SYMBOL: ansistring = #176; +{$ENDIF} +begin + Result := false; + destFile := ChangeFileExt(AFileName, '.txt'); + + {$IFDEF FPC} + if RunCommand(EXIFTOOL_CMD, ['-a', '-H', '-s', '-G', '-c', '"%d° %d'' %.2f"\"', AFileName], output) then + // -a ... extract all tags, also duplicates. + // -H ... extract hex tag id if possible + // -s ... short tag name (hopefully this is the dExif tag name) + // -G ... print group name for each tag + // -c ... format for GPS coordinates + begin + if (output = '') then + exit; + + L := TStringList.Create; + try + L.Text := output; + if ExtractReftags(ANode, L) then + ANode.ImageIndex := IMG_INDEX_EXIF else + ANode.ImageIndex := IMG_INDEX_IGNORE; + ANode.SelectedIndex := ANode.ImageIndex; + L.SaveToFile(destFile); + Result := true; + finally + L.Free; + end; + end; + {$ELSE} +// params := '/c ' + EXIFTOOL_CMD + ' -a -H -s -G -c "%d' + DEG_SYMBOL + ' %d'' %.2f"\"' + AFileName + ' > ' + destFile; + params := '/c ' + EXIFTOOL_CMD + ' -a -H -s -G -c "%d° %d'' %.2f"\"' + AFileName + ' > ' + destFile; + res := ShellExecute(Application.Handle, 'open', PChar('cmd'), PChar(params), '', SW_HIDE); + if (res <= 32) or not FileExists(destFile) then + exit; + L := TStringList.Create; + try + L.LoadFromFile(destFile); + // Note: ExifTool wrote the file in UTF8 --> We must convert this for Delphi + {$IFDEF UNICODE} + L.Text := UTF8Decode(L.Text); + {$ELSE} + s := UTF8ToAnsi(L.Text); + L.Text := s; + {$ENDIF} + if ExtractRefTags(ANode, L) then + ANode.ImageIndex := IMG_INDEX_EXIF else + ANode.ImageIndex := IMG_INDEX_IGNORE; + ANode.SelectedIndex := ANode.ImageIndex; + Result := true; + finally + L.Free; + end; + {$ENDIF} +end; + +function TMainForm.ExtractRefTags(ANode: TTreeNode; AList: TStringList): Boolean; +const + GROUP_START = 1; + GROUP_LEN = 15; + TAGID_START = 19; + TAGID_LEN = 4; + NAME_START = 24; + NAME_LEN = 32; + VALUE_START = 58; +var + i: Integer; + p: Integer; + s: String; + sGroup: String; + sTagID: String; + sTagName: String; + sTagValue: String; + tagID: Word; + node: TTreeNode; +begin + Result := false; + for i:=0 to AList.Count-1 do begin + s := AList[i]; + sGroup := trim(Copy(s, GROUP_START, GROUP_LEN)); + sTagID := trim(Copy(s, TAGID_START, TAGID_LEN)); + sTagName := trim(Copy(s, NAME_START, NAME_LEN)); + sTagValue := trim(Copy(s, VALUE_START, MaxInt)); + + if sTagID = '-' then + Continue; + + // So far, consider only EXIF-Tag + if sGroup <> '[EXIF]' then + Continue; + + tagID := StrToInt('$' + sTagID); + node := ANode.Owner.AddChild(ANode, sTagName + ': ' + sTagValue); + node.Data := Pointer(PtrInt(tagID)); + end; + Result := ANode.Count > 0; +end; + +procedure TMainForm.FileTreeViewClick(Sender: TObject); +var + P: TPoint; + ht: THitTests; + node: TTreeNode; +begin + P := FileTreeView.ScreenToClient(Mouse.CursorPos); + ht := FileTreeView.GetHitTestInfoAt(P.X, P.Y); + if htOnStateIcon in ht then begin + node := FileTreeView.GetNodeAt(P.X, P.Y); + if node.StateIndex = IMG_CHECKED then + node.StateIndex := IMG_UNCHECKED else + node.StateIndex := IMG_CHECKED; + end; +end; + +procedure TMainForm.FormCreate(Sender: TObject); +begin + fpExifFmtSettings.ListSeparator := ' '; + if EdImageDir.Text <> '' then + BtnReadFilesClick(nil); +end; + +function TMainForm.GetImageDir: String; +begin + Result := IncludeTrailingPathDelimiter(ExpandFilename(EdImageDir.Text)); + Caption := Result; +end; + +procedure TMainForm.Log(AMsg: String); +begin + Memo.Lines.Add(AMsg); + Memo.SelStart := Length(Memo.Lines.Text); +end; + +{ Loads the image file represented by the specified node, reads the meta data, + and compares with the reference file. } +procedure TMainForm.RunTest(ANode: TTreeNode); +const + {$IFDEF FPC} + GPS_MASK = '%0:.0f° %1:.0f'' %2:.2f"'; + {$ELSE} + {$IFDEF UNICODE} + GPS_MASK = '%0:.0f° %1:.0f'' %2:.2f"'; + {$ELSE} + GPS_MASK = '%0:.0f'#176' %1:.0f'' %2:.2f"'; + {$ENDIF} + {$ENDIF} +var + imgInfo: TImgInfo; + tagName: String; + uctagname: String; + expectedTagValue: String; + currTagValue: String; + s: String; + p: Integer; + node: TTreeNode; + tagID: TTagID; + lTag: TTag; + lTagDef: TTagDef; +// v: Variant; + offs: Int64; + localMismatchCount: Integer; +begin + if ANode.StateIndex = IMG_UNCHECKED then + exit; + + if ANode.Count = 0 then begin + Log('Skipping image "' + ANode.Text + '":'); + Log(' No EXIF data found by ExifTool.'); + Log(''); + exit; + end; + + localMismatchCount := 0; + Log('Testing image "' + ANode.Text + '":'); + imgInfo := TImgInfo.Create; + try + imgInfo.LoadFromFile(GetImageDir + ANode.Text); + if not imgInfo.HasExif then begin + Log('Skipping "' + ANode.Text + '":'); + Log(' No EXIF data found by fpExif.'); + Log(''); + exit; + end; + + node := ANode.GetFirstChild; + while node <> nil do begin + s := node.Text; + p := pos(':', s); + if p = 0 then begin + node := node.GetNextSibling; + Log(' Skipping tag "' + s + '": Has no value'); + continue; + end; + + tagName := trim(Copy(s, 1, p-1)); + uctagName := Uppercase(tagName); + + lTagDef := FindExifTagDefWithoutParent(PtrInt(node.Data)); + if lTagDef = nil then begin + Log(' Skipping tag "' + tagName + '": tag definition not found.'); + node := node.GetNextSibling; + Continue; + end; + tagID := lTagDef.TagID; + + if (tagID = TAGPARENT_EXIF + $EA1C) then begin // "Padding" + Log(Format(' Skipping tag "%s" ($%.4x): no useful data', [tagName, TTagIDRec(tagID).Tag])); + node := node.GetNextSibling; + Continue; + end; + + lTag := imgInfo.ExifData.FindTagByID(tagID); + if lTag = nil then begin + Log(Format('Tag "%s% (ID $%.04x) not found.', [tagName, TTagIDRec(tagID).Tag])); + node := node.GetNextSibling; + continue; + end; + + // Modify fpExif's tag format to match that used by ExifTool. + case lTag.TagID of + TAGPARENT_GPS + $0000: // GPSVersionID + lTag.ListSeparator := '.'; + TAGPARENT_EXIF + $9102, // CompressedBitsPerPixel + TAGPARENT_EXIF + $A20E, // FocalPlaneXResolution + TAGPARENT_EXIF + $A20F: // FocalPlaneYResolution + if lTag is TFloatTag then TFloatTag(lTag).FormatStr := '%2:.3f'; + TAGPARENT_EXIF + $A405: // FocalLengthIn35mmFilm + if lTag is TIntegerTag then TFloatTag(lTag).FormatStr := '%d mm'; + else + if lTag is TDateTimeTag then + TDateTimeTag(lTag).FormatStr := EXIF_DATETIME_FORMAT + else + if lTag is TGpsPositionTag then + TGpsPositionTag(lTag).FormatStr := GPS_MASK + else + if ltag is TExposureTimeTag then + TExposureTimeTag(ltag).FormatStr := '1/%.0f;%.0f' // to do: use rational values + else + if (lTag is TFloatTag) and + ((ucTagName = 'FNUMBER') or (pos('APERTURE', ucTagName) > 0)) + then + TFloatTag(lTag).FormatStr := '%2:.1f'; + end; + + currTagValue := trim(lTag.AsString); + expectedTagvalue := Copy(s, p+1, MaxInt); + p := pos(' -->', expectedTagValue); + if p > 0 then SetLength(expectedTagValue, p); + expectedTagValue := trim(expectedTagValue); + + case lTag.TagID of + TAGPARENT_INTEROP + $0001: // InteropIndex + if pos('INTEROP', ucTagName) <> 0 then + expectedTagValue := FirstWord(expectedTagValue); + TAGPARENT_EXIF + $9101: // ComponentsConfiguration + expectedTagValue := LettersOnly(expectedTagValue); + TAGPARENT_EXIF + $9102, // CompressedBitsPerPixel + TAGPARENT_EXIF + $A20E, // FocalPlaneXResolution + TAGPARENT_EXIF + $A20F: // FocalPlaneYResolution + expectedTagValue := Format('%.3f', [StrToFloat(expectedTagValue, fpExifFmtSettings)], fpExifFmtSettings); + else + if (lTag is TIntegerTag) and (pos(';', currTagValue) > 0) then +// currTagValue := ReplaceText(currTagValue, ';', ',') + currTagValue := StringReplace(currTagValue, ';', ',', [rfReplaceAll]) + else + if (lTag is TOffsetTag) then begin + offs := StrToInt(currTagValue); + currTagValue := IntToStr(offs + TOffsetTag(lTag).TiffHeaderOffset); + end; + end; + + if SameText(expectedTagValue, currTagValue) then + node.ImageIndex := IMG_INDEX_SUCCESS + else begin + Log(' Tag mismatch "' + Format('[$%.4x] %s', [TTagIDRec(tagID).Tag, tagName]) + '"'); + Log(' expected: ' + expectedTagValue); + Log(' found: ' + currTagValue); + node.ImageIndex := IMG_INDEX_FAIL; + node.Text := tagname + ': ' + expectedTagValue + ' --> found: ' + currTagValue; + inc(FMismatchCount); + inc(localMismatchCount); + end; + node.SelectedIndex := node.ImageIndex; + + node := node.GetNextSibling; + inc(FTotalCount); + end; + if localMismatchCount = 0 then + Log(' All tags matching'); + finally + Log(''); + imgInfo.Free; + end; + + FileTreeView.Invalidate; + +end; + +end. + diff --git a/components/fpexif/tests/pictures/originals/ExThBE_Nokia.jpg b/components/fpexif/tests/pictures/originals/ExThBE_Nokia.jpg new file mode 100644 index 0000000000000000000000000000000000000000..60dd6af864f44cb92634f0d4a9d8ae8ccd5489bf GIT binary patch literal 32589 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 literal 0 HcmV?d00001 diff --git a/components/fpexif/tests/pictures/originals/no_metadata.jpg b/components/fpexif/tests/pictures/originals/no_metadata.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d1d99296a8c7b0c8aa2a4b812d3cb0b6660c18ea GIT binary patch literal 5505 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 E00p-@SO5S3 literal 0 HcmV?d00001 diff --git a/components/fpexif/tests/pictures/originals/with_exif.jpg b/components/fpexif/tests/pictures/originals/with_exif.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3acfde6c21eaed75abe342b2d3cca7565ab7f265 GIT binary patch literal 5078 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 literal 0 HcmV?d00001 diff --git a/components/fpexif/tests/pictures/originals/with_exif.tif b/components/fpexif/tests/pictures/originals/with_exif.tif new file mode 100644 index 0000000000000000000000000000000000000000..82876f43c0042ea4f2c2e65529e43f4f452cf3f9 GIT binary patch literal 90406 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 literal 0 HcmV?d00001 diff --git a/components/fpexif/tests/pictures/originals/with_iptc.jpg b/components/fpexif/tests/pictures/originals/with_iptc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d505d4e34c26625d95932782da5613bd111863bb GIT binary patch literal 4594 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 literal 0 HcmV?d00001 diff --git a/components/fpexif/tests/pictures/originals/with_iptc.tif b/components/fpexif/tests/pictures/originals/with_iptc.tif new file mode 100644 index 0000000000000000000000000000000000000000..63e82f57abc362fc15ce874ae16e2a593560e26e GIT binary patch literal 90856 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 literal 0 HcmV?d00001 diff --git a/components/fpexif/tests/readwrite/ReadWriteTest_D7.cfg b/components/fpexif/tests/readwrite/ReadWriteTest_D7.cfg new file mode 100644 index 000000000..139fe116f --- /dev/null +++ b/components/fpexif/tests/readwrite/ReadWriteTest_D7.cfg @@ -0,0 +1,39 @@ +-$A8 +-$B- +-$C- +-$D+ +-$E- +-$F- +-$G+ +-$H+ +-$I+ +-$J- +-$K- +-$L+ +-$M- +-$N+ +-$O+ +-$P+ +-$Q- +-$R- +-$S- +-$T- +-$U- +-$V+ +-$W- +-$X+ +-$Y+ +-$Z1 +-cg +-AWinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; +-H+ +-W+ +-M +-$M16384,1048576 +-K$42200000 +-N"D:\Prog_Lazarus\wp-laz\fpexif\tests\readwrite\output\dcu\Delphi7" +-LE"d:\programme\borland\delphi7\Projects\Bpl" +-LN"d:\programme\borland\delphi7\Projects\Bpl" +-w-UNSAFE_TYPE +-w-UNSAFE_CODE +-w-UNSAFE_CAST diff --git a/components/fpexif/tests/readwrite/ReadWriteTest_D7.dof b/components/fpexif/tests/readwrite/ReadWriteTest_D7.dof new file mode 100644 index 000000000..c706fd444 --- /dev/null +++ b/components/fpexif/tests/readwrite/ReadWriteTest_D7.dof @@ -0,0 +1,151 @@ +[FileVersion] +Version=7.0 +[Compiler] +A=8 +B=0 +C=0 +D=1 +E=0 +F=0 +G=1 +H=1 +I=1 +J=0 +K=0 +L=1 +M=0 +N=1 +O=1 +P=1 +Q=0 +R=0 +S=0 +T=0 +U=0 +V=1 +W=0 +X=1 +Y=2 +Z=1 +ShowHints=1 +ShowWarnings=1 +UnitAliases=WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; +NamespacePrefix= +SymbolDeprecated=1 +SymbolLibrary=1 +SymbolPlatform=1 +UnitLibrary=1 +UnitPlatform=1 +UnitDeprecated=1 +HResultCompat=1 +HidingMember=1 +HiddenVirtual=1 +Garbage=1 +BoundsError=1 +ZeroNilCompat=1 +StringConstTruncated=1 +ForLoopVarVarPar=1 +TypedConstVarPar=1 +AsgToTypedConst=1 +CaseLabelRange=1 +ForVariable=1 +ConstructingAbstract=1 +ComparisonFalse=1 +ComparisonTrue=1 +ComparingSignedUnsigned=1 +CombiningSignedUnsigned=1 +UnsupportedConstruct=1 +FileOpen=1 +FileOpenUnitSrc=1 +BadGlobalSymbol=1 +DuplicateConstructorDestructor=1 +InvalidDirective=1 +PackageNoLink=1 +PackageThreadVar=1 +ImplicitImport=1 +HPPEMITIgnored=1 +NoRetVal=1 +UseBeforeDef=1 +ForLoopVarUndef=1 +UnitNameMismatch=1 +NoCFGFileFound=1 +MessageDirective=1 +ImplicitVariants=1 +UnicodeToLocale=1 +LocaleToUnicode=1 +ImagebaseMultiple=1 +SuspiciousTypecast=1 +PrivatePropAccessor=1 +UnsafeType=0 +UnsafeCode=0 +UnsafeCast=0 +[Linker] +MapFile=0 +OutputObjs=0 +ConsoleApp=1 +DebugInfo=0 +RemoteSymbols=0 +MinStackSize=16384 +MaxStackSize=1048576 +ImageBase=1109393408 +ExeDescription=TeeChart 2014 Components +[Directories] +OutputDir= +UnitOutputDir=D:\Prog_Lazarus\wp-laz\fpexif\tests\readwrite\output\dcu\Delphi7 +PackageDLLOutputDir= +PackageDCPOutputDir= +SearchPath= +Packages=Tee97;TeeUI97;TeeDB97;TeePro97;TeeGL97;TeeImage97;TeeLanguage97;TeeWorld97 +Conditionals= +DebugSourceDirs= +UsePackages=0 +[Parameters] +RunParams= +HostApplication= +Launcher= +UseLauncher=0 +DebugCWD= +[Language] +ActiveLang= +ProjectLang= +RootDir=D:\Programme\Borland\Delphi7\Bin\ +[Version Info] +IncludeVerInfo=1 +AutoIncBuild=0 +MajorVer=9 +MinorVer=0 +Release=11 +Build=0 +Debug=0 +PreRelease=0 +Special=0 +Private=0 +DLL=0 +Locale=1033 +CodePage=1252 +[Version Info Keys] +CompanyName= +FileDescription=ReadWriteTest +FileVersion=9.0.11.0 +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename= +ProductName=ReadWriteTest +ProductVersion=1.0.0.0 +ProgramID=com.embarcadero.ReadWriteTest +[Excluded Packages] +D:\Prog_Delphi\common\Components\3rdParty\TeeChart\Sources\Compiled\Delphi7\Bin\DclTeeMaker17.bpl=TeeMaker +D:\Programme\Borland\Delphi7\Lib\HelpCtxD7.bpl=HelpScribble HelpContext Property Editor for Delphi 7 +[HistoryLists\hlUnitAliases] +Count=1 +Item0=WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; +[HistoryLists\hlSearchPath] +Count=1 +Item0=D:\Prog_Lazarus\git\dexif-afriess-master +[HistoryLists\hlUnitOutputDirectory] +Count=4 +Item0=D:\Prog_Lazarus\wp-laz\fpexif\tests\readwrite\output\dcu\Delphi7 +Item1=D:\Prog_Lazarus\wp-laz\fpexif\tests\readwrite\output\tpu\Delphi7 +Item2=D:\Prog_Lazarus\wp-laz\fpexif\output\tpu\D7 +Item3=D:\Prog_Lazarus\wp-laz\fpexif\output\ppu\D7 diff --git a/components/fpexif/tests/readwrite/ReadWriteTest_D7.dpr b/components/fpexif/tests/readwrite/ReadWriteTest_D7.dpr new file mode 100644 index 000000000..4acf926f6 --- /dev/null +++ b/components/fpexif/tests/readwrite/ReadWriteTest_D7.dpr @@ -0,0 +1,25 @@ +program ReadWriteTest_D7; + +uses + Forms, + rwMain in 'common\rwMain.pas', + fpeGlobal in '..\..\fpeglobal.pas', + fpeExifReadWrite in '..\..\fpeexifreadwrite.pas', + fpeTags in '..\..\fpetags.pas', + fpeUtils in '..\..\fpeutils.pas', + fpeMetadata in '..\..\fpemetadata.pas', + fpeIptcReadWrite in '..\..\fpeiptcreadwrite.pas', + fpeexifdata in '..\..\fpeexifdata.pas', + fpeIptcData in '..\..\fpeiptcdata.pas', + fpeMakerNote in '..\..\fpemakernote.pas', + fpeStrConsts in '..\..\fpestrconsts.pas'; + +{$R *.res} + +begin + Application.Initialize; + Application.CreateForm(TMainForm, MainForm); + MainForm.BeforeRun; + Application.Run; +end. + diff --git a/components/fpexif/tests/readwrite/ReadWriteTest_D7.res b/components/fpexif/tests/readwrite/ReadWriteTest_D7.res new file mode 100644 index 0000000000000000000000000000000000000000..85b53583b786c44fabe8080240fe17ecbe8c972c GIT binary patch literal 1648 zcmaJ=O=}ZT6g^1>nle&T5ZaXuLutiD?3iqdVk^c<L9Mi<x-03|SZvb}V+J>x(uD(A z^slsJBRqt-@^1)s;m+(TSbU!QW+op+y~&;T&fIf9-#h@wQne11+X4BWjO{6M62_Lv z3|TSh1~*yiJT-ynHGTdr4S;_%8sWkxx6}sO?KTZkge*E-_zdgPu-#FS46R0Nm8ZB} zoDQ1uqKhFYKN{14APAH??O{W3VHJzfyQXTL!dj@;qZh<39(mqQecmvFX3I@Z)$5+O zWw1?q()YcMdaa9n->*8}j&Vo_$~Cwi1yCwKPIGKWjHg0Am|)!Z9p7<`KhjKa>{qAz z@rQGoCrv&`f+_?4vO+8Y$EJPf)=7jpk}=2kCsl;g%~mGqIItrEv1?2*fdK{EDk20* z25Kv%Du&0QlRyl#WW09_W?sM?M_#q^Br5uIm(SBtusAfiS1M2~38d7=CeMS?jPOYa zc}8Nl<AMZ;oS>1=I@WedYtc^XNOwk{J6G_&lg8XnQD8#!qECNwPn{Fx=OsF!508#@ zLx0iji<EA^OX#onuF^C_O;Hc2ZR%(0H?8&gHC8c0)p`U^a370!N_~JOuCesr3%Lym zWV4pJ6>^IbIP2b}Oy6A^j*pM_0z<7~1?zl1#4>k_Cd0bPx4^x~C+i1Wk-IodwY%#a zcOnu{to>3ncX9<aRI!UPJS=eC!!{$Av4aDWvxYN+0y~|h7FdH+<S<%-y&hm66_m(- z%dSgA+uamJjF1z@EYk8Cn<iI<Id|z>Hr!=eO4!14W2+IZ8g7l&+w7+oV~y{tjMDR+ zMUGYF;PNLsk+3%DL1I<8me|!`byKGP$FzIKDxFl=gG|E1OL~TK^Ac~B45@XSotC(N z#cE;+xl2(DnW*HA`xn%k&RsK`XXG9*+8fTLHc;XJW>P?!RSo)Ho=iB6a5gfP7r5T_ z@k|f<JmsP(ebby`&h)fly4vJZdX(yN{O10BK4FA4;}v(>`+9&$^W}w8D|1$RWcHu^ Iuytnr0kmH}`2YX_ literal 0 HcmV?d00001 diff --git a/components/fpexif/tests/readwrite/ReadWriteTest_Delphi.dpr b/components/fpexif/tests/readwrite/ReadWriteTest_Delphi.dpr new file mode 100644 index 000000000..8f5c473ea --- /dev/null +++ b/components/fpexif/tests/readwrite/ReadWriteTest_Delphi.dpr @@ -0,0 +1,25 @@ +program ReadWriteTest_Delphi; + +uses + Forms, + rwMain in 'common\rwMain.pas', + fpeexifreadwrite in '..\..\fpeexifreadwrite.pas', + fpeglobal in '..\..\fpeglobal.pas', + fpetags in '..\..\fpetags.pas', + fpemetadata in '..\..\fpemetadata.pas', + fpeexifdata in '..\..\fpeexifdata.pas', + fpeiptcdata in '..\..\fpeiptcdata.pas', + fpeiptcreadwrite in '..\..\fpeiptcreadwrite.pas', + fpemakernote in '..\..\fpemakernote.pas', + fpestrconsts in '..\..\fpestrconsts.pas', + fpeutils in '..\..\fpeutils.pas'; + +{$R *.res} + +begin + Application.Initialize; + Application.CreateForm(TMainForm, MainForm); + MainForm.BeforeRun; + Application.Run; +end. + diff --git a/components/fpexif/tests/readwrite/ReadWriteTest_Delphi.dproj b/components/fpexif/tests/readwrite/ReadWriteTest_Delphi.dproj new file mode 100644 index 000000000..4d94915d5 --- /dev/null +++ b/components/fpexif/tests/readwrite/ReadWriteTest_Delphi.dproj @@ -0,0 +1,141 @@ +<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <ProjectGuid>{6C35BA62-BF64-4BCB-8BAA-3B554EC4D396}</ProjectGuid> + <MainSource>ReadWriteTest_Delphi.dpr</MainSource> + <Base>True</Base> + <Config Condition="'$(Config)'==''">Debug</Config> + <TargetedPlatforms>1</TargetedPlatforms> + <AppType>Application</AppType> + <FrameworkType>VCL</FrameworkType> + <ProjectVersion>18.2</ProjectVersion> + <Platform Condition="'$(Platform)'==''">Win32</Platform> + </PropertyGroup> + <PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''"> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Base)'=='true') or '$(Base_Win32)'!=''"> + <Base_Win32>true</Base_Win32> + <CfgParent>Base</CfgParent> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_1)'!=''"> + <Cfg_1>true</Cfg_1> + <CfgParent>Base</CfgParent> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win32)'!=''"> + <Cfg_1_Win32>true</Cfg_1_Win32> + <CfgParent>Cfg_1</CfgParent> + <Cfg_1>true</Cfg_1> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_2)'!=''"> + <Cfg_2>true</Cfg_2> + <CfgParent>Base</CfgParent> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_2)'=='true') or '$(Cfg_2_Win32)'!=''"> + <Cfg_2_Win32>true</Cfg_2_Win32> + <CfgParent>Cfg_2</CfgParent> + <Cfg_2>true</Cfg_2> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="'$(Base)'!=''"> + <DCC_UnitSearchPath>D:\Prog_Lazarus\git\dexif-afriess-master;$(DCC_UnitSearchPath)</DCC_UnitSearchPath> + <DCC_F>false</DCC_F> + <VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo> + <DCC_E>false</DCC_E> + <VerInfo_MajorVer>9</VerInfo_MajorVer> + <DCC_ImageBase>42200000</DCC_ImageBase> + <SanitizedProjectName>ReadWriteTest_Delphi</SanitizedProjectName> + <DCC_N>true</DCC_N> + <DCC_Namespace>Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Data;Datasnap;Web;Soap;Winapi;$(DCC_Namespace)</DCC_Namespace> + <DCC_UsePackage>Tee97;TeeUI97;TeeDB97;TeePro97;TeeGL97;TeeImage97;TeeLanguage97;TeeWorld97;$(DCC_UsePackage)</DCC_UsePackage> + <DCC_S>false</DCC_S> + <VerInfo_Keys>CompanyName=Steema Software;FileDescription=;FileVersion=9.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=9.0.0.0</VerInfo_Keys> + <DCC_K>false</DCC_K> + <VerInfo_Release>11</VerInfo_Release> + <VerInfo_Locale>1033</VerInfo_Locale> + <DCC_AssertionsAtRuntime>false</DCC_AssertionsAtRuntime> + <DCC_Description>TeeChart 2014 Components</DCC_Description> + <DCC_DcuOutput>D:\Prog_Lazarus\wp-laz\fpexif\tests\readwrite\output\dcu\Delphi\</DCC_DcuOutput> + </PropertyGroup> + <PropertyGroup Condition="'$(Base_Win32)'!=''"> + <VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo> + <DCC_Namespace>System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)</DCC_Namespace> + <Manifest_File>$(BDS)\bin\default_app.manifest</Manifest_File> + <VerInfo_Locale>1033</VerInfo_Locale> + <AppEnableRuntimeThemes>true</AppEnableRuntimeThemes> + <Icon_MainIcon>Delphi\ReadWriteTest_Icon.ico</Icon_MainIcon> + <VerInfo_Keys>CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName)</VerInfo_Keys> + <UWP_DelphiLogo44>$(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png</UWP_DelphiLogo44> + <UWP_DelphiLogo150>$(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png</UWP_DelphiLogo150> + </PropertyGroup> + <PropertyGroup Condition="'$(Cfg_1)'!=''"> + <DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols> + <DCC_Define>RELEASE;$(DCC_Define)</DCC_Define> + <DCC_DebugInformation>0</DCC_DebugInformation> + </PropertyGroup> + <PropertyGroup Condition="'$(Cfg_1_Win32)'!=''"> + <AppEnableHighDPI>true</AppEnableHighDPI> + <AppEnableRuntimeThemes>true</AppEnableRuntimeThemes> + <Icon_MainIcon>Delphi\Delphi\ReadWriteTest_Icon.ico</Icon_MainIcon> + </PropertyGroup> + <PropertyGroup Condition="'$(Cfg_2)'!=''"> + <DCC_Define>DEBUG;$(DCC_Define)</DCC_Define> + <DCC_GenerateStackFrames>true</DCC_GenerateStackFrames> + <DCC_Optimize>false</DCC_Optimize> + <DCC_RangeChecking>true</DCC_RangeChecking> + <DCC_IntegerOverflowCheck>true</DCC_IntegerOverflowCheck> + </PropertyGroup> + <PropertyGroup Condition="'$(Cfg_2_Win32)'!=''"> + <Icon_MainIcon>$(BDS)\bin\delphi_PROJECTICON.ico</Icon_MainIcon> + <VerInfo_Keys>CompanyName=;FileVersion=9.0.11.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName)</VerInfo_Keys> + <AppEnableHighDPI>true</AppEnableHighDPI> + <AppEnableRuntimeThemes>true</AppEnableRuntimeThemes> + </PropertyGroup> + <ItemGroup> + <DelphiCompile Include="$(MainSource)"> + <MainSource>MainSource</MainSource> + </DelphiCompile> + <DCCReference Include="common\rwMain.pas"/> + <DCCReference Include="..\..\fpeexifreadwrite.pas"/> + <DCCReference Include="..\..\fpeglobal.pas"/> + <DCCReference Include="..\..\fpetags.pas"/> + <DCCReference Include="..\..\fpemetadata.pas"/> + <DCCReference Include="..\..\fpeexifdata.pas"/> + <DCCReference Include="..\..\fpeiptcdata.pas"/> + <DCCReference Include="..\..\fpeiptcreadwrite.pas"/> + <DCCReference Include="..\..\fpemakernote.pas"/> + <DCCReference Include="..\..\fpestrconsts.pas"/> + <DCCReference Include="..\..\fpeutils.pas"/> + <BuildConfiguration Include="Debug"> + <Key>Cfg_2</Key> + <CfgParent>Base</CfgParent> + </BuildConfiguration> + <BuildConfiguration Include="Base"> + <Key>Base</Key> + </BuildConfiguration> + <BuildConfiguration Include="Release"> + <Key>Cfg_1</Key> + <CfgParent>Base</CfgParent> + </BuildConfiguration> + </ItemGroup> + <ProjectExtensions> + <Borland.Personality>Delphi.Personality.12</Borland.Personality> + <Borland.ProjectType/> + <BorlandProject> + <Delphi.Personality> + <Source> + <Source Name="MainSource">ReadWriteTest_Delphi.dpr</Source> + </Source> + </Delphi.Personality> + <Platforms> + <Platform value="Win32">True</Platform> + </Platforms> + </BorlandProject> + <ProjectFileVersion>12</ProjectFileVersion> + </ProjectExtensions> + <Import Project="$(BDS)\Bin\CodeGear.Delphi.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Delphi.Targets')"/> + <Import Project="$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj" Condition="Exists('$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj')"/> +</Project> diff --git a/components/fpexif/tests/readwrite/ReadWriteTest_Delphi.res b/components/fpexif/tests/readwrite/ReadWriteTest_Delphi.res new file mode 100644 index 0000000000000000000000000000000000000000..8ccaa1c0515bbcc54afe2c7c7d61fbeb12ef45d5 GIT binary patch literal 59488 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 literal 0 HcmV?d00001 diff --git a/components/fpexif/tests/readwrite/ReadWriteTest_Laz.lpi b/components/fpexif/tests/readwrite/ReadWriteTest_Laz.lpi new file mode 100644 index 000000000..e6454b702 --- /dev/null +++ b/components/fpexif/tests/readwrite/ReadWriteTest_Laz.lpi @@ -0,0 +1,139 @@ +<?xml version="1.0" encoding="UTF-8"?> +<CONFIG> + <ProjectOptions> + <Version Value="10"/> + <PathDelim Value="\"/> + <General> + <SessionStorage Value="InProjectDir"/> + <MainUnit Value="0"/> + <Title Value="ReadWriteTest_Laz"/> + <ResourceType Value="res"/> + <UseXPManifest Value="True"/> + </General> + <BuildModes Count="1"> + <Item1 Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + </PublishOptions> + <RunParams> + <local> + <FormatVersion Value="1"/> + </local> + </RunParams> + <RequiredPackages Count="1"> + <Item1> + <PackageName Value="LCL"/> + </Item1> + </RequiredPackages> + <Units Count="14"> + <Unit0> + <Filename Value="ReadWriteTest_Laz.lpr"/> + <IsPartOfProject Value="True"/> + </Unit0> + <Unit1> + <Filename Value="common\rwmain.lfm"/> + <IsPartOfProject Value="True"/> + </Unit1> + <Unit2> + <Filename Value="common\rwMain.pas"/> + <IsPartOfProject Value="True"/> + <ComponentName Value="MainForm"/> + <HasResources Value="True"/> + <ResourceBaseClass Value="Form"/> + </Unit2> + <Unit3> + <Filename Value="..\..\fpeglobal.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeGlobal"/> + </Unit3> + <Unit4> + <Filename Value="..\..\fpeutils.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeUtils"/> + </Unit4> + <Unit5> + <Filename Value="..\..\fpeexifreadwrite.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeExifReadWrite"/> + </Unit5> + <Unit6> + <Filename Value="..\..\fpetags.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeTags"/> + </Unit6> + <Unit7> + <Filename Value="..\..\fpexif_fpc.inc"/> + <IsPartOfProject Value="True"/> + </Unit7> + <Unit8> + <Filename Value="..\..\fpeexifdata.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeExifData"/> + </Unit8> + <Unit9> + <Filename Value="..\..\fpeiptcdata.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeIptcData"/> + </Unit9> + <Unit10> + <Filename Value="..\..\fpeiptcreadwrite.pas"/> + <IsPartOfProject Value="True"/> + </Unit10> + <Unit11> + <Filename Value="..\..\fpemakernote.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeMakerNote"/> + </Unit11> + <Unit12> + <Filename Value="..\..\fpemetadata.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeMetadata"/> + </Unit12> + <Unit13> + <Filename Value="..\..\fpestrconsts.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeStrConsts"/> + </Unit13> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <PathDelim Value="\"/> + <Target> + <Filename Value="ReadWriteTest_Laz"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir);..\.."/> + <OtherUnitFiles Value="common;..\.."/> + <UnitOutputDirectory Value="output\ppu\$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <CodeGeneration> + <Checks> + <IOChecks Value="True"/> + <RangeChecks Value="True"/> + <OverflowChecks Value="True"/> + <StackChecks Value="True"/> + </Checks> + <VerifyObjMethodCallValidity Value="True"/> + </CodeGeneration> + <Linking> + <Debugging> + <UseExternalDbgSyms Value="True"/> + </Debugging> + </Linking> + </CompilerOptions> + <Debugging> + <Exceptions Count="3"> + <Item1> + <Name Value="EAbort"/> + </Item1> + <Item2> + <Name Value="ECodetoolError"/> + </Item2> + <Item3> + <Name Value="EFOpenError"/> + </Item3> + </Exceptions> + </Debugging> +</CONFIG> diff --git a/components/fpexif/tests/readwrite/ReadWriteTest_Laz.lpr b/components/fpexif/tests/readwrite/ReadWriteTest_Laz.lpr new file mode 100644 index 000000000..b93b0c256 --- /dev/null +++ b/components/fpexif/tests/readwrite/ReadWriteTest_Laz.lpr @@ -0,0 +1,19 @@ +program ReadWriteTest_Laz; + +{$mode objfpc}{$H+} + +uses + Interfaces, // this includes the LCL widgetset + Forms, + rwMain; + +{$R *.res} + +begin + RequireDerivedFormResource := True; + Application.Initialize; + Application.CreateForm(TMainForm, MainForm); + MainForm.BeforeRun; + Application.Run; +end. + diff --git a/components/fpexif/tests/readwrite/ReadWriteTest_Laz.res b/components/fpexif/tests/readwrite/ReadWriteTest_Laz.res new file mode 100644 index 0000000000000000000000000000000000000000..0ad004b9d97928b9d3a42fa745f31fc0da4dfdb5 GIT binary patch literal 1779 zcmbVNO^@3)5ZzvSC{Q5B9$6j>P~Vc}g}i|gB!JPR4X|m79w~CPD^wy?lJdqV`m6c_ zlhKF0+F;jP)PQV~Lp{EE^Eke^xVRwreEvK=IDGK=_uFIq2i)_`7<=*JeoOK4Q`?X| z7+Y(#p`jO00!nCEtD6n|?ZX?EQ(_%gk~dnxh91Dumsj6^S8!{gEt>;D0A)9{H|o-g zTWGmuZ7qzpx^hhD_LAGy+lQ32T-6m=_i=d|LXi{JPJ(i^J3Jud1p}$hXaUx0vo+$j zb|74Dkj_t(DBj}Wm2Nw(4!_{UdvCPtg&W7)t{Z+Safc4K-PJ0Ns6U=6!3tA%4l}>{ zL-&U8u4`(+hXt|-dWFB3&98Q#kgPZiQ&LEn=59NyGA=)o*pkJ}{M}J3@Aq0seP=z) zrPYeP(#jd#Sb8r<Hi&Uvj;=2;iJ#%{LH`L@(kxx4WtN0VB19F+ijsz_o1Wi{>WxZ} zA;CuyxL=;~ES%}B+o?+b$D+q{lE;GR4c2v5#lT-Z`T*p6Q}62rZop0vQk+7sKjAw= zCEmgw8qy5k8_MlY?J+MyNndZp;D`;acw=FzDVD-NL41W?_)8jve@bRJyFjCMSoyMU z2LC<l=?B{fcTb5f95m<pBV%N)EBRRCO(KlVA=`JIHYmfd*JLL*^lylQFig@F3)3WH zNi0&v(+XG+<Prd48CStSluR`v#S(gT1~yK^I4{?nrE-n1RVi5xkz_ndk|2uGY8~?@ zVDmGu36H8MhnS^txn@a{gskLi!DP0U2~T(iasCvn7oGtQt8A5tRmSoFLY74FnxTbD zmd7%vG8ss|dI~rU&fw+{R4X1vtgOlyb%30qDk)3yN~Qr1Q@&p6;D24r!hcZp(KguI z_VMLn=VYCs=k4QXo8s|)Oeol!hU<R~&SCepM-$#U*IoMl+*`d<Z}pIkfBkOfM?sMK zv&W7vPkzM4)YaQNZs6+8a7pk&ALnD+@*h2Tw(^+S|K}8DPjGtX<YFeZ&=d&`tJD9@ F)fdt*E~@|l literal 0 HcmV?d00001 diff --git a/components/fpexif/tests/readwrite/common/rwMain.dfm b/components/fpexif/tests/readwrite/common/rwMain.dfm new file mode 100644 index 000000000..fe1f5bf65 --- /dev/null +++ b/components/fpexif/tests/readwrite/common/rwMain.dfm @@ -0,0 +1,308 @@ +object MainForm: TMainForm + Left = 329 + Top = 127 + Width = 1041 + Height = 620 + Caption = 'EXIF read/write test' + Color = clBtnFace + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'MS Sans Serif' + Font.Style = [] + OldCreateOrder = True + OnCreate = FormCreate + OnDestroy = FormDestroy + DesignSize = ( + 1025 + 581) + PixelsPerInch = 96 + TextHeight = 13 + object BtnTest1: TSpeedButton + Left = 916 + Top = 8 + Width = 48 + Height = 22 + Anchors = [akTop, akRight] + Caption = 'Test 1' + OnClick = BtnTest1Click + end + object BtnTest2: TSpeedButton + Left = 969 + Top = 8 + Width = 48 + Height = 22 + Anchors = [akTop, akRight] + Caption = 'Test 2' + OnClick = BtnTest1Click + end + object BtnBrowse: TSpeedButton + Left = 888 + Top = 8 + Width = 23 + Height = 22 + Anchors = [akTop, akRight] + Caption = '...' + OnClick = BtnBrowseClick + end + object Panel1: TPanel + Left = 8 + Top = 40 + Width = 1009 + Height = 482 + Anchors = [akLeft, akTop, akRight, akBottom] + BevelOuter = bvNone + TabOrder = 0 + object Splitter1: TSplitter + Left = 730 + Top = 0 + Width = 5 + Height = 482 + Align = alRight + end + object ListView: TListView + Left = 0 + Top = 0 + Width = 730 + Height = 482 + Align = alClient + Columns = < + item + AutoSize = True + Caption = 'Tag/property' + end + item + AutoSize = True + Caption = 'Current value' + end + item + AutoSize = True + Caption = 'Value to be written' + end + item + Caption = 'Value read-back' + Width = 441 + end> + SmallImages = ImageList1 + TabOrder = 0 + ViewStyle = vsReport + end + object ExifTabControl: TTabControl + Left = 735 + Top = 0 + Width = 274 + Height = 482 + Align = alRight + TabOrder = 1 + Tabs.Strings = ( + 'Original file' + 'Modified') + TabIndex = 0 + OnChange = ExifTabControlChange + object ExifListView: TListView + Left = 4 + Top = 24 + Width = 266 + Height = 454 + Align = alClient + Columns = < + item + Caption = 'Tag name' + Width = 120 + end + item + Caption = 'Value' + Width = 146 + end> + RowSelect = True + TabOrder = 0 + ViewStyle = vsReport + end + end + end + object Panel2: TPanel + Left = 0 + Top = 520 + Width = 1025 + Height = 61 + Align = alBottom + AutoSize = True + BevelOuter = bvNone + BorderWidth = 8 + TabOrder = 1 + object Label1: TLabel + Left = 8 + Top = 8 + Width = 1009 + Height = 45 + Align = alClient + Caption = + 'Buttons "Test 1" and "Test 2" read test conditions from files "t' + + 'estcases1.txt" and "testcases2.txt", respectively.'#13#10'- Test value' + + 's expressed as fractions (e.g. exposure times) are evaluated by ' + + 'the program.'#13#10'- Enumerated values can be shown by their index, o' + + 'r by their text followd by | and their index.' + Color = clBtnFace + ParentColor = False + end + end + object CbTestfile: TComboBox + Left = 8 + Top = 8 + Width = 872 + Height = 21 + Anchors = [akLeft, akTop, akRight] + ItemHeight = 13 + TabOrder = 2 + end + object ImageList1: TImageList + Left = 400 + Top = 117 + Bitmap = { + 494C010102000800140010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600 + 0000000000003600000028000000400000001000000001002000000000000010 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000317A360A2D753207000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000003F3D + ED413B38EB08000000000000000000000000000000000000000000000000211F + E3081E1CE2410000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00003985400A37833DFF317B37FB2E7633070000000000000000000000000000 + 00000000000000000000000000000000000000000000000000004A47F0414F4C + F2FF403EEDFD3C39EB08000000000000000000000000000000002725E5082422 + E4FC312FEAFF1F1DE24100000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000004292 + 490A408E47FF54A35CFF4F9F57FF327C38FE2E77340800000000000000000000 + 000000000000000000000000000000000000000000005451F3415856F5FF6361 + FAFF5855F6FF413FEDFC3D3AEC080000000000000000302DE7082C2AE6FC413F + F1FF4C4AF6FF312FEAFF1F1DE241000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000000000004B9E530A499A + 51FF5BAC64FF77CA82FF74C87EFF51A059FF337D39FE2F783508000000000000 + 000000000000000000000000000000000000000000005956F52B5B58F6FF6562 + FAFF7170FFFF5956F6FF4240EEFC3E3BEC083937EB083532E9FC4745F2FF6362 + FFFF4A48F4FF2F2DE9FF2220E32B000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000053A95C0A51A65AFF63B5 + 6DFF7ECE89FF7BCC87FF76CA81FF76C981FF52A25AFF347E3AFE307935080000 + 00000000000000000000000000000000000000000000000000005A57F52B5B59 + F6FF6663FAFF7471FFFF5A58F6FF4341EEFC3E3CECFD504DF4FF6867FFFF504E + F5FF3634EBFF2A27E52B00000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000005AB4650959B063FF6BBD76FF84D2 + 90FF7AC985FF60B26AFF63B46DFF78C983FF78CB82FF53A35CFF347F3AFD317A + 3608000000000000000000000000000000000000000000000000000000005B58 + F62B5C5AF6FF6764FAFF7472FFFF7370FFFF706EFFFF6E6CFFFF5755F7FF3F3D + EEFF3230E82B0000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000005EB969465BB566E479C986FF80CE + 8DFF51A65AFC4DA1566F499C518B5CAD67FF7CCC86FF79CB85FF54A45DFF3580 + 3BFC317B37080000000000000000000000000000000000000000000000000000 + 00005C59F62B5D5BF7FF7976FFFF5956FFFF5754FFFF7270FFFF4846F0FF3C39 + EB2B000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000000000000000000000000000000000005FBA6A3C5CB666E66DC0 + 79FF55AC5F6F00000000000000004A9D52915EAE68FF7DCD89FF7CCD87FF56A5 + 5FFF36813CFC327C380800000000000000000000000000000000000000000000 + 0000615EF8085D5AF6FD7D79FFFF5E5BFFFF5B58FFFF7674FFFF4643EFFD413F + ED08000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000000000005FBB6A435CB7 + 6765000000000000000000000000000000004B9E53915FAF69FF7FCE8AFF7ECE + 89FF57A660FF37823DFC337D3908000000000000000000000000000000006967 + FB086663F9FC706DFBFF807EFFFF7E7BFFFF7C79FFFF7977FFFF5E5CF7FF4744 + EFFC4240EE080000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000000000000000000000000000000000004B9F549160B06AFF81CF + 8DFF7FCF8BFF58A761FF398540FF347E3A080000000000000000716EFD086E6B + FCFC7774FDFF8682FFFF7673FCFF6462F8FF605DF7FF6D6AFAFF7B79FFFF605D + F7FF4845EFFC4341EE0800000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000000000004CA0559162B2 + 6CFF82D18FFF7AC885FF57A660FF38843F7B000000007673FF087471FEFD7D7A + FEFF8A87FFFF7C79FDFF6C69FBFF6361F92B5F5CF72B615EF8FF6E6CFAFF7D7A + FFFF615FF7FF4946F0FC4441EE05000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000004DA1 + 569163B36DFF5FAF69FF4191497900000000000000007774FF1F7A77FFFF817E + FFFF817EFEFF7471FDFF6C69FB2B0000000000000000605DF72B625FF8FF6F6D + FBFF7E7CFFFF625FF8FF4A47F06F4542EE020000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00004EA257914A9D527F000000000000000000000000000000007774FF1F7A77 + FFFF7976FEFF726FFD2B00000000000000000000000000000000615EF82B6461 + F8FF6A68F9FF5451F3A84F4DF229000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000007774 + FF1F7774FF2B000000000000000000000000000000000000000000000000625F + F82B5D5BF76F5956F53E00000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00006360F80A0000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000000000000000000000000000424D3E000000000000003E000000 + 2800000040000000100000000100010000000000800000000000000000000000 + 000000000000000000000000FFFFFF00FFFFFFFF00000000F9FFE7E700000000 + F0FFC3C300000000E07F818100000000C03F800100000000801FC00300000000 + 000FE007000000000007F00F000000008603F00F00000000CF01E00700000000 + FF80C00300000000FFC0800100000000FFE1818000000000FFF3C3C100000000 + FFFFE7E300000000FFFFFFF70000000000000000000000000000000000000000 + 000000000000} + end + object OpenDialog: TOpenDialog + DefaultExt = '.*.jpg' + Filter = 'JPEG files (*.jpg; *.jpeg)|*.jpg;*.jpeg' + Title = 'Open image file' + Left = 400 + Top = 178 + end +end diff --git a/components/fpexif/tests/readwrite/common/rwMain.pas b/components/fpexif/tests/readwrite/common/rwMain.pas new file mode 100644 index 000000000..d0b820250 --- /dev/null +++ b/components/fpexif/tests/readwrite/common/rwMain.pas @@ -0,0 +1,630 @@ +unit rwMain; + +{$I ..\..\..\fpExif.inc} + +{$IFDEF FPC} + {$MODE DELPHI} +{$ENDIF} + +interface + +uses + {$IFDEF FPC} + LazUtf8, + {$ELSE} + Windows, Messages, ImgList, jpeg, + {$IFDEF UNICODE} + System.ImageList, + {$ENDIF} + {$ENDIF} + Classes, SysUtils, Forms, Controls, Graphics, Dialogs, + StdCtrls, Buttons, ComCtrls, ExtCtrls, Variants, + fpeGlobal, fpeTags, fpeMetadata; + +type + + { TMainForm } + + TMainForm = class(TForm) + BtnTest1: TSpeedButton; + BtnTest2: TSpeedButton; + CbTestfile: TComboBox; + ImageList1: TImageList; + Label1: TLabel; + ListView: TListView; + OpenDialog: TOpenDialog; + Panel1: TPanel; + Panel2: TPanel; + ExifListView: TListView; + ExifTabControl: TTabControl; + BtnBrowse: TSpeedButton; + Splitter1: TSplitter; + procedure CbTestfileEditingDone(Sender: TObject); + procedure FormCreate(Sender: TObject); + procedure FormDestroy(Sender: TObject); + procedure BtnTest1Click(Sender: TObject); + procedure ExifTabControlChange(Sender: TObject); + procedure BtnBrowseClick(Sender: TObject); + private + ImgInfo: TImgInfo; + OutFile: String; + procedure ExecTest(const AParamsFile: String); + procedure ExifToListview(AImgInfo: TImgInfo; AListView: TListView); + function ReadTagValue(ATagName: String; out ATag: TTag): String; overload; + function ReadTagValue(ATagName: String): String; overload; + function Success(ATag: TTag; ACurrValue, AExpectedValue: String): Boolean; + procedure WriteTagValue(ATagName, ATagValue: String); + + procedure AddToHistory(AFilename: String); + procedure ReadFromIni; + procedure WriteToIni; + + public + procedure BeforeRun; + + end; + +var + MainForm: TMainForm; + +implementation + +{$IFDEF FPC} + {$R *.lfm} +{$ELSE} + {$R *.dfm} +{$ENDIF} + +uses + StrUtils, Math, IniFiles, + fpeUtils, fpeExifData; + +const + IMGINDEX_SUCCESS = 0; + IMGINDEX_FAIL = 1; + + TESTCASES_DIR = 'common\'; + +type + TStringArray = array of string; + +function Split(s: String; AMinCount: Integer; Separator: Char = #9): TStringArray; +const + BLOCK_SIZE = 20; +var + i, j, n, L: Integer; +begin + if s = '' then begin + SetLength(Result, 0); + exit; + end; + + s := s + Separator; + L := Length(s); + SetLength(Result, BLOCK_SIZE); + i := 1; + j := 1; + n := 0; + while (i <= L) do begin + if (s[i] = Separator) or (i = L) then begin + Result[n] := Copy(s, j, i-j); + inc(n); + if n mod BLOCK_SIZE = 0 then + SetLength(Result, Length(Result) + BLOCK_SIZE); + j := i+1; + end; + inc(i); + end; + while n < AMinCount do begin + Result[n] := ''; + inc(n); + if n mod BLOCK_SIZE = 0 then + SetLength(Result, Length(Result) + BLOCK_SIZE); + end; + SetLength(Result, n); +end; + +{ The date/time string is expected in the ISO format "yyyy-mm-dd hh:nn:ss" } +function ExtractDateTime(AValue: String): TDateTime; +var + p: Integer; + yr, mn, dy, h, m, s: Integer; +begin + Result := 0; + p := pos('-', AValue); + if p = 0 then + raise Exception.Create('ISO date/time format expected: "yyyy-mm-dd hh:nn:ss"'); + yr := StrToInt(copy(AValue, 1, p-1)); + Delete(AValue, 1, p); + p := pos('-', AValue); + if p = 0 then + raise Exception.Create('ISO date/time format expected: "yyyy-mm-dd hh:nn:ss"'); + mn := StrToInt(copy(AValue, 1, p-1)); + Delete(AValue, 1, p); + p := pos(' ', AValue); + if p = 0 then begin + dy := StrToInt(AValue); + Result := EncodeDate(yr, mn, dy); + exit; + end; + dy := StrToInt(copy(AValue, 1, p-1)); + Delete(AValue, 1, p); + p := pos(':', AValue); + if p = 0 then + raise Exception.Create('ISO date/time format expected: "yyyy-mm-dd hh:nn:ss"'); + h := StrToInt(copy(AValue, 1, p-1)); + Delete(AValue, 1, p); + p := pos(':', AValue); + if p = 0 then begin + m := StrToInt(AValue); + s := 0; + end else begin + m := StrToInt(copy(AValue, 1, p-1)); + s := StrToInt(copy(AValue, p+1, MaxInt)); + end; + Result := EncodeDate(yr, mn, dy) + EncodeTime(h, m, s, 0); +end; + +function DecimalSep: Char; +begin + {$IFDEF FPC} + Result := FormatSettings.DecimalSeparator; + {$ELSE} + {$IFDEF VER150} // Delphi 7 + Result := DecimalSeparator; + {$ELSE} + Result := FormatSettings.DecimalSeparator; + {$ENDIF} + {$ENDIF} +end; + +function CleanFloatStr(AText: String): String; +var + i: Integer; +begin + Result := ''; + i := 1; + while i <= Length(AText) do begin + // case aperture value, e.g. "F/2.8" + if (i < Length(AText)) and (AText[i] in ['f', 'F']) and (AText[i+1] = '/') then + inc(i) + else + if AText[i] in ['0'..'9', '.', '/'] then + Result := Result + AText[i] + else if AText[i] = ',' then + Result := Result + '.'; + inc(i); + end; +end; + + +{ TMainForm } + +procedure TMainForm.AddToHistory(AFileName: String); +var + i: Integer; +begin + if (AFileName = '') or (not FileExists(AFileName)) then + exit; + + i := CbTestFile.Items.Indexof(AFileName); + if i > -1 then + CbTestfile.Items.Delete(i); + CbTestFile.Items.Insert(0, AFileName); + CbTestFile.ItemIndex := 0; +end; + +procedure TMainForm.BeforeRun; +begin + ReadFromIni; +end; + +procedure TMainForm.FormCreate(Sender: TObject); +begin + ImgInfo := TImgInfo.Create; +end; + +procedure TMainForm.FormDestroy(Sender: TObject); +begin + WriteToIni; + ImgInfo.Free; +end; + +procedure TMainForm.BtnTest1Click(Sender: TObject); +begin + AddToHistory(CbTestFile.Text); + if Sender = BtnTest1 then + ExecTest(TESTCASES_DIR + 'testcases1.txt') + else if Sender = BtnTest2 then + ExecTest(TESTCASES_DIR + 'testcases2.txt') + else + raise Exception.Create('BtnTextClick: Unexpected Sender'); +end; + +procedure TMainForm.CbTestfileEditingDone(Sender: TObject); +begin + AddToHistory(CbTestFile.Text); +end; + +procedure TMainForm.ExecTest(const AParamsFile: String); +var + testCases: TStringList; + i, j, n, p: Integer; + s: String; + testdata: TStringArray; + listitem: TListItem; + lTag: TTag; + tagName: String; + currTagValue: String; + newTagValue: String; + newTagValues: TStringArray; + jpeg: TJpegImage; + {$IFDEF FPC} + stream: TMemorystream; + {$ELSE} + stream: TMemoryStream; + a: ansistring; + {$ENDIF} +begin + Listview.Items.Clear; + + if not FileExists(AParamsFile) then begin + showMessage('Parameter file "' + AParamsFile + '" not found.'); + exit; + end; + if not FileExists(CbTestfile.Text) then begin + ShowMessage('Test picture file "' + CbTestfile.Text + '" not found.'); + exit; + end; + + // Read test parameters + testCases := TStringList.Create; + try + + {$IFDEF FPC} + // The testcases text files are encoded in ANSI for Delphi7 compatibility + // In Lazarus we must convert to UTF8 } + testCases.LoadFromFile(AParamsFile); + s := testCases.Text; + {$IFDEF FPC3+} + testCases.Text := WinCPToUTF8(s); + {$ELSE} + testCases.Text := AnsiToUTF8(s); + {$ENDIF} + {$ELSE} + stream := TMemoryStream.Create; + try + stream.LoadFromFile(AParamsFile); + SetLength(a, stream.Size); + stream.Read(a[1], Length(a)); + testcases.Text := a; + finally + stream.Free; + end; + {$ENDIF} + + // Read EXIF tags from image file + ImgInfo.LoadFromFile(CbTestfile.Text); + if not ImgInfo.HasExif then + ImgInfo.CreateExifData(false); + + OutFile := 'test-image.jpg'; // File name of the modified test image + + ListView.Items.BeginUpdate; + try + j := 0; + n := testCases.Count; + for i:=0 to n-1 do begin + if (testCases[i] = ':quit') then + break; + + if (testCases[i] = '') or (testCases[i][1] = ';') then + Continue; + + // Extract test parameters + testdata := Split(testCases[i], 2); + tagName := testdata[0]; + newTagValue := testdata[1]; + newTagValues := Split(newTagValue, 2, '|'); + if Length(newTagValues) =0 then begin + SetLength(newTagValues, 1); + newTagValues[0] := ''; + end; + + // Add test to listview + listitem := ListView.Items.Add; + listItem.Caption := tagname; + + // Read current tag value + currTagValue := ReadTagValue(tagName, lTag); + listItem.SubItems.Add(currTagValue); + listItem.Data := lTag; + + // Write new tag value into ExifObj + WriteTagValue(tagName, newTagValues[0]); + listItem.SubItems.Add(newTagValue); + end; + finally + ListView.Items.EndUpdate; + end; + + // Write new tags to file + ImgInfo.SaveToFile(OutFile); + + // read back + ImgInfo.LoadFromFile(OutFile); + if not ImgInfo.HasExif then + raise Exception.Create('No EXIF structure detected in "' + Outfile + '"'); + + j := 0; + for i:=0 to testCases.Count-1 do begin + if (testcases[i] = ':quit') then + break; + if (testcases[i] = '') or (testcases[i][1] = ';') then + Continue; + testdata := Split(testCases[i], 2); + tagname := testdata[0]; + newTagValue := testdata[1]; + currTagValue := ReadTagValue(tagname, lTag); + listItem := ListView.Items[j]; + listItem.SubItems.Add(currTagValue); + if Success(lTag, currTagValue, newTagValue) then + listItem.ImageIndex := IMGINDEX_SUCCESS else + listItem.ImageIndex := IMGINDEX_FAIL; + inc(j); + end; + + jpeg := TJpegImage.Create; + try + try + jpeg.LoadFromFile(OutFile); + listitem := ListView.Items.Add; + listItem.Caption := 'Successfully loaded'; + listItem.ImageIndex := IMGINDEX_SUCCESS; + except + listitem := ListView.Items.Add; + listItem.Caption := 'Loading failed.'; + listItem.ImageIndex := IMGINDEX_FAIL; + end; + finally + jpeg.Free; + end; + + finally + testCases.Free; + end; + + ExifTabControlChange(nil); +end; + +procedure TMainForm.BtnBrowseClick(Sender: TObject); +var + olddir: String; +begin + olddir := GetCurrentDir; + OpenDialog.FileName := ''; + if OpenDialog.Execute then + AddToHistory(OpenDialog.Filename); + SetCurrentDir(oldDir); +end; + +function TMainForm.Success(ATag: TTag; ACurrValue, AExpectedValue: String): Boolean; +const + relEPS = 1E-3; +var + p: Integer; + snum, sdenom: String; + valexp, valcurr: Double; + decode: Boolean; + currVal, expVal: String; +begin + Result := ACurrValue = AExpectedValue; + if Result then + exit; + + if (ACurrValue = '') or (AExpectedValue = '') then begin + Result := false; + exit; + end; + (* + { Check for alternative expected value } + p := pos('|', AExpectedValue); + if p > 0 then begin + expected2 := Copy(AExpectedValue, p+1, MaxInt);; + expected1 := Copy(AExpectedValue, 1, p-1); + Result := (ACurrValue = expected1); + if Result then + exit; + Result := (ACurrValue = expected2); + if Result then + exit; + end; *) + + { Check for float values, e.g. 12.0 vs 12 } + if (ATag is TFloatTag) then begin + currVal := CleanFloatStr(ACurrValue); + expVal := CleanFloatStr(AExpectedValue); + + Result := currVal = expval; + if Result then + exit; + + { Check for fractional result, e.g. exposure time } + p := pos('/', currVal); + if p > 0 then begin + snum := Copy(currVal, 1, p-1); + sdenom := Copy(currVal, p+1, MaxInt); + valcurr := StrToInt(snum) / StrToInt(sdenom); + end else + valcurr := StrToFloat(currVal, fpExifFmtSettings); + + p := pos('/', expVal); + if p > 0 then begin + snum := Copy(expval, 1, p-1); + sdenom := Copy(currval, p+1, MaxInt); + valexp := StrToInt(snum) / StrToInt(sdenom); + end else + valexp := StrToFloat(expval, fpExifFmtSettings); + + Result := SameValue(valcurr, valexp, relEPS * valexp); + if Result then + exit; + end; + + if (ATag is TIntegerTag) then begin + decode := ATag.DecodeValue; + ATag.DecodeValue := not decode; + currVal := ATag.AsString; + ATag.DecodeValue := decode; + Result := (currVal = AExpectedValue); + if Result then + exit; + end; +end; + +procedure TMainForm.ExifToListview(AImgInfo: TImgInfo; AListView: TListView); +var + i: Integer; + lTag: TTag; + item: TListItem; +begin + AListview.Items.BeginUpdate; + try + AListview.Items.Clear; + if not AImgInfo.HasExif then + exit; + for i:=0 to AImgInfo.ExifData.TagCount-1 do begin + lTag := AImgInfo.ExifData.TagByIndex[i]; + if lTag = nil then + Continue; + item := AListView.Items.Add; + with item do begin + Caption := lTag.Description; + SubItems.Add(lTag.AsString); + end; + end; + AListView.AlphaSort; + finally + AListview.Items.EndUpdate; + end; +end; + +function TMainForm.ReadTagValue(ATagName: String): String; +var + lTag: TTag; +begin + Result := ReadTagValue(ATagName, lTag); +end; + +function TMainForm.ReadTagValue(ATagName: String; out ATag: TTag): String; +begin + if ATagName = 'Comment' then begin + Result := ImgInfo.Comment; + ATag := nil; + end else + begin + ATag := ImgInfo.ExifData.FindTagByName(ATagName); + if ATag = nil then + Result := '' + else + Result := ATag.AsString; + end; +end; + +procedure TMainForm.ExifTabControlChange(Sender: TObject); +var + data: TImgInfo; +begin + data := TImgInfo.Create; + try + case ExifTabControl.TabIndex of + 0: data.LoadFromFile(CbTestfile.Text); + 1: data.LoadFromFile(OutFile); + end; + ExifToListView(data, ExifListView); + finally + data.Free; + end; +end; + +procedure TMainForm.WriteTagValue(ATagName, ATagValue: String); +var + lTag: TTag; +begin + if ATagName = 'Comment' then + ImgInfo.Comment := ATagValue + else begin + lTag := ImgInfo.ExifData.TagByName[ATagName]; + if lTag = nil then + lTag := ImgInfo.ExifData.AddTagByName(ATagName); + lTag.AsString := ATagvalue; + end; +end; + +function CreateIni: TCustomIniFile; +begin + Result := TMemIniFile.Create(ChangeFileExt(Application.ExeName, '.ini')); +end; + +procedure TMainForm.ReadFromIni; +var + ini: TCustomIniFile; + list: TStrings; + i: Integer; + W, H, L, T: Integer; + R: TRect; +begin + ini := CreateIni; + try + list := TStringList.Create; + try + if WindowState = wsNormal then begin + W := ini.ReadInteger('MainForm', 'Width', Width); + H := ini.ReadInteger('MainForm', 'Height', Height); + L := ini.ReadInteger('MainForm', 'Left', Left); + T := ini.ReadInteger('MainForm', 'Top', Top); + R := Screen.DesktopRect; + if W > R.Right - R.Left then W := R.Right - R.Left; + if L+W > R.Right then L := R.Right - W; + if L < R.Left then L := R.Left; + if H > R.Bottom - R.Top then H := R.Bottom - R.Top; + if T+H > R.Bottom then T := R.Bottom - H; + if T < R.Top then T := R.Top; + SetBounds(L, T, W, H); + end; + + ini.ReadSection('History', list); + for i:=list.Count-1 downto 0 do // count downward because AddToHistory adds to the beginning of the list + AddToHistory(ini.ReadString('History', list[i], '')); + CbTestFile.ItemIndex := 0; + finally + list.Free; + end; + finally + ini.Free; + end; +end; + +procedure TMainForm.WriteToIni; +var + ini: TCustomIniFile; + i: Integer; +begin + ini := CreateIni; + try + ini.WriteInteger('MainForm', 'Left', Left); + ini.WriteInteger('MainForm', 'Top', Top); + ini.WriteInteger('MainForm', 'Width', Width); + ini.WriteInteger('MainForm', 'Height', Height); + + for i:=0 to CbTestFile.Items.Count-1 do + if (CbTestFile.Items[i] <> '') and FileExists(CbTestFile.Items[i]) then + ini.WriteString('History', 'Item'+IntToStr(i+1), CbTestFile.Items[i]); + ini.UpdateFile; + finally + ini.Free; + end; +end; + +end. + diff --git a/components/fpexif/tests/readwrite/common/rwmain.lfm b/components/fpexif/tests/readwrite/common/rwmain.lfm new file mode 100644 index 000000000..2e38c5f34 --- /dev/null +++ b/components/fpexif/tests/readwrite/common/rwmain.lfm @@ -0,0 +1,234 @@ +object MainForm: TMainForm + Left = 329 + Height = 581 + Top = 127 + Width = 1025 + Caption = 'EXIF read/write test' + ClientHeight = 581 + ClientWidth = 1025 + OnCreate = FormCreate + OnDestroy = FormDestroy + LCLVersion = '1.9.0.0' + object BtnTest1: TSpeedButton + Left = 916 + Height = 22 + Top = 8 + Width = 48 + Anchors = [akTop, akRight] + Caption = 'Test 1' + OnClick = BtnTest1Click + end + object BtnTest2: TSpeedButton + Left = 969 + Height = 22 + Top = 8 + Width = 48 + Anchors = [akTop, akRight] + Caption = 'Test 2' + OnClick = BtnTest1Click + end + object Panel1: TPanel + Left = 8 + Height = 482 + Top = 40 + Width = 1009 + Anchors = [akTop, akLeft, akRight, akBottom] + BevelOuter = bvNone + ClientHeight = 482 + ClientWidth = 1009 + TabOrder = 0 + object ListView: TListView + Left = 0 + Height = 482 + Top = 0 + Width = 730 + Align = alClient + AutoWidthLastColumn = True + Columns = < + item + AutoSize = True + Caption = 'Tag/property' + Width = 85 + end + item + AutoSize = True + Caption = 'Current value' + Width = 86 + end + item + AutoSize = True + Caption = 'Value to be written' + Width = 114 + end + item + Caption = 'Value read-back' + Width = 441 + end> + SmallImages = ImageList1 + TabOrder = 0 + ViewStyle = vsReport + end + object Splitter1: TSplitter + Left = 730 + Height = 482 + Top = 0 + Width = 5 + Align = alRight + ResizeAnchor = akRight + end + object ExifTabControl: TTabControl + Left = 735 + Height = 482 + Top = 0 + Width = 274 + OnChange = ExifTabControlChange + TabIndex = 0 + Tabs.Strings = ( + 'Original file' + 'Modified' + ) + Align = alRight + TabOrder = 2 + object ExifListView: TListView + Left = 2 + Height = 457 + Top = 23 + Width = 270 + Align = alClient + AutoWidthLastColumn = True + Columns = < + item + Caption = 'Tag name' + Width = 120 + end + item + Caption = 'Value' + Width = 146 + end> + RowSelect = True + TabOrder = 1 + ViewStyle = vsReport + end + end + end + object Panel2: TPanel + Left = 0 + Height = 61 + Top = 520 + Width = 1025 + Align = alBottom + AutoSize = True + BevelOuter = bvNone + BorderWidth = 8 + ClientHeight = 61 + ClientWidth = 1025 + TabOrder = 1 + object Label1: TLabel + Left = 8 + Height = 45 + Top = 8 + Width = 1009 + Align = alClient + Caption = 'Buttons "Test 1" and "Test 2" read test conditions from files "testcases1.txt" and "testcases2.txt", respectively.'#13#10'- Test values expressed as fractions (e.g. exposure times) are evaluated by the program.'#13#10'- Enumerated values can be shown by their index, or by their text followd by | and their index.' + ParentColor = False + end + end + object CbTestfile: TComboBox + Left = 8 + Height = 23 + Top = 8 + Width = 872 + Anchors = [akTop, akLeft, akRight] + ItemHeight = 15 + OnEditingDone = CbTestfileEditingDone + TabOrder = 2 + end + object BtnBrowse: TSpeedButton + Left = 888 + Height = 22 + Top = 8 + Width = 23 + Anchors = [akTop, akRight] + Caption = '...' + OnClick = BtnBrowseClick + end + object ImageList1: TImageList + left = 400 + top = 117 + Bitmap = { + 4C69020000001000000010000000FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF004EA2 + 57914A9D527FFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF004DA1569163B3 + 6DFF5FAF69FF41914979FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF004CA0559162B26CFF82D1 + 8FFF7AC885FF57A660FF38843F7BFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF004B9F549160B06AFF81CF8DFF7FCF + 8BFF58A761FF398540FF347E3A08FFFFFF00FFFFFF005FBB6A435CB76765FFFF + FF00FFFFFF00FFFFFF00FFFFFF004B9E53915FAF69FF7FCE8AFF7ECE89FF57A6 + 60FF37823DFC337D3908FFFFFF00FFFFFF005FBA6A3C5CB666E66DC079FF55AC + 5F6FFFFFFF00FFFFFF004A9D52915EAE68FF7DCD89FF7CCD87FF56A55FFF3681 + 3CFC327C3808FFFFFF00FFFFFF005EB969465BB566E479C986FF80CE8DFF51A6 + 5AFC4DA1566F499C518B5CAD67FF7CCC86FF79CB85FF54A45DFF35803BFC317B + 3708FFFFFF00FFFFFF00FFFFFF005AB4650959B063FF6BBD76FF84D290FF7AC9 + 85FF60B26AFF63B46DFF78C983FF78CB82FF53A35CFF347F3AFD317A3608FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0053A95C0A51A65AFF63B56DFF7ECE + 89FF7BCC87FF76CA81FF76C981FF52A25AFF347E3AFE30793508FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF004B9E530A499A51FF5BAC + 64FF77CA82FF74C87EFF51A059FF337D39FE2F783508FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF004292490A408E + 47FF54A35CFF4F9F57FF327C38FE2E773408FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF003985 + 400A37833DFF317B37FB2E763307FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00317A360A2D753207FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF006360 + F80AFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF007774FF1F7774 + FF2BFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00625FF82B5D5B + F76F5956F53EFFFFFF00FFFFFF00FFFFFF00FFFFFF007774FF1F7A77FFFF7976 + FEFF726FFD2BFFFFFF00FFFFFF00FFFFFF00FFFFFF00615EF82B6461F8FF6A68 + F9FF5451F3A84F4DF229FFFFFF00FFFFFF007774FF1F7A77FFFF817EFFFF817E + FEFF7471FDFF6C69FB2BFFFFFF00FFFFFF00605DF72B625FF8FF6F6DFBFF7E7C + FFFF625FF8FF4A47F06F4542EE02FFFFFF007673FF087471FEFD7D7AFEFF8A87 + FFFF7C79FDFF6C69FBFF6361F92B5F5CF72B615EF8FF6E6CFAFF7D7AFFFF615F + F7FF4946F0FC4441EE05FFFFFF00FFFFFF00FFFFFF00716EFD086E6BFCFC7774 + FDFF8682FFFF7673FCFF6462F8FF605DF7FF6D6AFAFF7B79FFFF605DF7FF4845 + EFFC4341EE08FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF006967FB086663 + F9FC706DFBFF807EFFFF7E7BFFFF7C79FFFF7977FFFF5E5CF7FF4744EFFC4240 + EE08FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00615E + F8085D5AF6FD7D79FFFF5E5BFFFF5B58FFFF7674FFFF4643EFFD413FED08FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF005C59 + F62B5D5BF7FF7976FFFF5956FFFF5754FFFF7270FFFF4846F0FF3C39EB2BFFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF005B58F62B5C5A + F6FF6764FAFF7472FFFF7370FFFF706EFFFF6E6CFFFF5755F7FF3F3DEEFF3230 + E82BFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF005A57F52B5B59F6FF6663 + FAFF7471FFFF5A58F6FF4341EEFC3E3CECFD504DF4FF6867FFFF504EF5FF3634 + EBFF2A27E52BFFFFFF00FFFFFF00FFFFFF005956F52B5B58F6FF6562FAFF7170 + FFFF5956F6FF4240EEFC3E3BEC083937EB083532E9FC4745F2FF6362FFFF4A48 + F4FF2F2DE9FF2220E32BFFFFFF00FFFFFF005451F3415856F5FF6361FAFF5855 + F6FF413FEDFC3D3AEC08FFFFFF00FFFFFF00302DE7082C2AE6FC413FF1FF4C4A + F6FF312FEAFF1F1DE241FFFFFF00FFFFFF00FFFFFF004A47F0414F4CF2FF403E + EDFD3C39EB08FFFFFF00FFFFFF00FFFFFF00FFFFFF002725E5082422E4FC312F + EAFF1F1DE241FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF003F3DED413B38 + EB08FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00211FE3081E1C + E241FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00 + } + end + object OpenDialog: TOpenDialog + Title = 'Open image file' + DefaultExt = '.*.jpg' + Filter = 'JPEG files (*.jpg; *.jpeg)|*.jpg;*.jpeg' + Options = [ofFileMustExist, ofEnableSizing, ofViewDetail] + left = 400 + top = 178 + end +end diff --git a/components/fpexif/tests/readwrite/common/testcases1.txt b/components/fpexif/tests/readwrite/common/testcases1.txt new file mode 100644 index 000000000..e64846fbd --- /dev/null +++ b/components/fpexif/tests/readwrite/common/testcases1.txt @@ -0,0 +1,32 @@ +; IMPORTANT: This file must be stored in ANSI encoding +; Use a TAB character between the two columns + +Artist Ansel Adams +Comment This text is in the COMMENT segment. +Artist Ansel Adams +ImageDescription A nice image +UserComment Nice comment! +Make My camera make +Model My camera model +Copyright (c) wp +DateTimeOriginal 2017-01-01 09:00:00 +DateTimeDigitized 2017-01-01 12:00:00 +DateTime 2017-01-01 18:00:00 +ImageWidth 2000 +ImageHeight 1000 +ExifImageWidth 2000 +ExifImageHeight 1000 +Orientation 2 +FNumber 5.6 +ApertureValue 11 +MaxApertureValue 1.8 +ExposureBiasValue -1 +ShutterSpeedValue 1/60 +ExposureTime 1/25 +ISO 800 +ExposureProgram 1 +MeteringMode 3 +LightSource 3 + +;GPSLatitude 12° 34' 56" N +;GPSLongitude 1° 23' 45" E diff --git a/components/fpexif/tests/readwrite/common/testcases2.txt b/components/fpexif/tests/readwrite/common/testcases2.txt new file mode 100644 index 000000000..c176bf2a8 --- /dev/null +++ b/components/fpexif/tests/readwrite/common/testcases2.txt @@ -0,0 +1,16 @@ +;IMPORTANT: This file must be stored in ANSI encoding +; Use a TAB character between the two columns + +Comment ÄÖÜ in the comment segment. +Artist Hansi Müller +ImageDescription Hübsches Bild +UserComment Schöner Kommentar! +Make ÄÖÜ camera make +Model äöü camera model +Copyright © wp +Orientation Mirror horizontal +ExposureProgram Manual +MeteringMode Spot +LightSource Cloudy +GPSLatitude +GPSLongitude diff --git a/components/fpexif/tests/unittest/common/fetexifbe.pas b/components/fpexif/tests/unittest/common/fetexifbe.pas new file mode 100644 index 000000000..bb1cbc6dc --- /dev/null +++ b/components/fpexif/tests/unittest/common/fetexifbe.pas @@ -0,0 +1,470 @@ +unit fetExifBE; + +{$IFDEF FPC} + {$mode objfpc}{$H+} +{$ENDIF} + + {$I fpexif.inc} + +interface + +uses + Classes, SysUtils, + {$ifdef FPC} + fpcunit, testutils, testregistry; + {$else} + fetTestUtils, TestFrameWork; + {$endif} + +const + // Picture with Exif data (Big Endian) + ExifPic = '..\pictures\originals\ExThBE_Nokia.jpg'; + WorkFile_WithExif = 'pictures\with_exif.jpg'; + + // Picture without Exif data + NoExifPic = '..\pictures\originals\no_metadata.jpg'; + WorkFile_NoExif = 'pictures\no_exif.jpg'; + +type + TstExifBE = class(TTestCase) + protected + procedure SetUp; override; + procedure TearDown; override; + procedure Internal_CheckHasExif(AFileName: String; ExpectExif: Boolean); + published + procedure CheckForPictures; + procedure CheckCreateImgInfo; + procedure CheckHasExif; + procedure ReadExifTest; + procedure CreateExifTest; + procedure WriteExifTest; + procedure ValidFileTest; + end; + +implementation + +uses + {$IFDEF FPC} + FileUtil, + {$ELSE} + Jpeg, + {$ENDIF} + Graphics, + fpeGlobal, fpeTags, fpeExifData, fpeMetadata; + +procedure TstExifBE.SetUp; +begin + if FileExists(Workfile_WithExif) then + DeleteFile(WorkFile_WithExif); + if FileExists(Workfile_NoExif) then + DeleteFile(Workfile_NoExif); + + if not FileExists(WorkFile_WithExif) then + if FileExists(ExifPic) then + CopyFile(ExifPic, WorkFile_WithExif); + if not FileExists(WorkFile_NoExif) then + if FileExists(NoExifPic) then + CopyFile(NoExifPic, WorkFile_NoExif); +end; + +procedure TstExifBE.TearDown; +begin + if FileExists(WorkFile_NoExif) then + DeleteFile(WorkFile_NoExif); + if FileExists(WorkFile_WithExif) then + DeleteFile(WorkFile_WithExif); +end; + +procedure TstExifBE.CheckForPictures; +begin + CheckTrue(FileExists(ExifPic), 'Original test picture file "' + ExifPic + '" does not exist'); + CheckTrue(FileExists(NoExifPic), 'Original test picture file "' + NoExifPic + '" does not exist'); + + CheckTrue(FileExists(WorkFile_WithExif), 'Test picture file "' + WorkFile_WithExif + '" does not exist'); + CheckTrue(FileExists(WorkFile_NoExif), 'Test picture file "' + WorkFile_NoExif + '" does not exist'); +end; + +procedure TstExifBE.CheckCreateImgInfo; +var + imgInfo: TImgInfo; +begin + imgInfo := TImgInfo.Create(); + try + CheckIs(imgInfo, TImgInfo,'Is not TImgInfo'); + finally + imgInfo.Free; + end; +end; + +procedure TstExifBE.Internal_CheckHasExif(AFileName: String; ExpectExif: Boolean); +var + imgInfo: TImgInfo; +begin + imgInfo := TImgInfo.Create; + try + imgInfo.LoadFromFile(AFileName); + if ExpectExif then + CheckTrue(imgInfo.HasExif, 'Failure to detect EXIF in test picture file "' + AFileName + '"') + else + CheckFalse(imgInfo.HasExif, 'Unexpected EXIF in test picture file "' + AFileName + '" detected'); + finally + imgInfo.Free; + end; +end; + +procedure TstExifBE.CheckHasExif; +begin + Internal_CheckHasExif(WorkFile_WithExif, true); + Internal_CheckHasExif(WorkFile_NoExif, false); +end; + +procedure TstExifBE.ReadExifTest; +{ EXIF-related Output of ExifTool for the test image with exif using this + commandline: + exiftool -G -H -s with_exif.jpg > with_exif.txt + + These values are checked + | +[File] - ExifByteOrder : Big-endian (Motorola, MM) +[EXIF] 0x010f Make : Nokia +[EXIF] 0x0110 Model : 6300 +[EXIF] 0x0112 Orientation : Horizontal (normal) +[EXIF] 0x011a XResolution : 72 +[EXIF] 0x011b YResolution : 72 +[EXIF] 0x0128 ResolutionUnit : inches +[EXIF] 0x0131 Software : V 07.21 +[EXIF] 0x0213 YCbCrPositioning : Centered +[EXIF] 0x9000 ExifVersion : 0220 +[EXIF] 0x9101 ComponentsConfiguration : Y, Cb, Cr, - +[EXIF] 0xa000 FlashpixVersion : 0100 +[EXIF] 0xa001 ColorSpace : sRGB +[EXIF] 0xa002 ExifImageWidth : 200 <-- +[EXIF] 0xa003 ExifImageHeight : 267 <-- +[EXIF] 0x0103 Compression : JPEG (old-style) <-- called ThumbnailOffset by fpExif +[EXIF] 0x011a XResolution : 72 <-- called ThumbnailXResolution by fpExif +[EXIF] 0x011b YResolution : 72 <-- called ThumbnailYResolution by fpExif +[EXIF] 0x0128 ResolutionUnit : inches <-- called ThumbnailResolutionUnit by fpExif +[EXIF] 0x0201 ThumbnailOffset : 359 <-- +[EXIF] 0x0202 ThumbnailLength : 12025 <-- called ThumbnailSize by fpExif +} + +var + imgInfo: TImgInfo; + lTag: TTag; + offs: Integer; +begin + imgInfo := TImgInfo.Create; + try + imgInfo.LoadFromFile(WorkFile_WithExif); + + // This is general information stored within imgImfInfo + CheckEquals('with_exif.jpg', ExtractFileName(imgInfo.FileName), 'Filename mismatch'); + + // The following pieces of information are obtained from the EXIF segment + CheckTrue(imgInfo.ExifData.BigEndian, 'Exif byte order detection error'); + + lTag := imgInfo.ExifData.TagByName['Make']; + CheckTrue(lTag <> nil, 'Tag "Make" not found'); + CheckEquals('Nokia', lTag.AsString, 'Value mismatch of tag "Make"'); + + lTag := imgInfo.ExifData.TagByName['Model']; + CheckTrue(lTag <> nil, 'Tag "Model" not found'); + CheckEquals('6300', lTag.AsString, 'Value mismatch of tag "Model"'); + + lTag := imgInfo.ExifData.TagByName['Orientation']; + CheckTrue(lTag <> nil, 'Tag "Orientation" not found'); + CheckEquals('Horizontal (normal)', lTag.AsString, 'Value mismatch of tag "Orientation"'); + + lTag := imgInfo.ExifData.TagByName['XResolution']; + CheckTrue(lTag <> nil, 'Tag "XResolution" not found'); + CheckEquals('72', lTag.AsString, 'Value mismatch of tag "XResolution"'); + CheckTrue(lTag is TNumericTag, 'Tag "XResolution" is no TNumericTag'); + CheckEquals(72, lTag.AsInteger, 'Integer value mismatch of tag "XResolution"'); + CheckTrue(lTag is TFloatTag, 'Tag "XResolution" is no TNumericTag'); + CheckEquals(72.0, lTag.AsFloat, 'Float value mismatch of tag "XResolution"'); + + lTag := imgInfo.ExifData.TagByName['YResolution']; + CheckTrue(lTag <> nil, 'Tag "YResolution" not found'); + CheckEquals('72', lTag.AsString, 'Value mismatch of tag "YResolution"'); + + lTag := imgInfo.ExifData.TagByName['ResolutionUnit']; + CheckTrue(lTag <> nil, 'Tag "ResolutionUnit" not found'); + CheckEquals('inches', lTag.AsString, 'Value mismatch of tag "ResolutionUnit"'); + + lTag := imgInfo.ExifData.TagByName['Software']; + CheckTrue(lTag <> nil, 'Tag "Software" not found'); + CheckEquals('V 07.21', lTag.AsString, 'Value mismatch of tag "Software"'); + + lTag := imgInfo.ExifData.TagByName['YCbCrPositioning']; + CheckTrue(lTag <> nil, 'Tag "YCbCrPositioning" not found'); + CheckEquals('Centered', lTag.AsString, 'Value mismatch of tag "YCbCrPositioning"'); + + lTag := imgInfo.ExifData.TagByName['ExifVersion']; + CheckTrue(lTag <> nil, 'Tag "ExifVersion" not found'); + CheckTrue(lTag is TVersionTag, 'Tag "ExifVersion" is not TVersionTag'); + CheckEquals('0220', lTag.AsString, 'Value mismatch of tag "ExifVersion"'); + + lTag := imgInfo.ExifData.TagByName['ComponentsConfiguration']; + CheckTrue(lTag <> nil, 'Tag "ComponentsConfiguration" not found'); + CheckEquals('YCbCr', lTag.AsString, 'Value mismatch of tag "ComponentsConfiguration"'); + // Expected value manually edited from "Y, Cb, Cr, -" to "YCbCr" + + lTag := imgInfo.ExifData.TagByName['FlashPixVersion']; + CheckTrue(lTag <> nil, 'Tag "FlashPixVersion" not found'); + CheckEquals('0100', lTag.AsString, 'Value mismatch of tag "FlashPixVersion"'); + + lTag := imgInfo.ExifData.TagByName['ColorSpace']; + CheckTrue(lTag <> nil, 'Tag "ColorSpace" not found'); + CheckEquals('sRGB', lTag.AsString, 'Value mismatch of tag "ColorSpace"'); + + lTag := imgInfo.ExifData.TagByName['ExifImageWidth']; + CheckTrue(lTag <> nil, 'Tag "ExifImageWidth" not found'); + CheckEquals(200, lTag.AsInteger, 'Value mismatch of tag "ExifImageWidth"'); + + lTag := imgInfo.ExifData.TagByName['ExifImageHeight']; + CheckTrue(lTag <> nil, 'Tag "ExifImageHeight" not found'); + CheckEquals(267, lTag.AsInteger, 'Value mismatch of tag "ExifImageHeight"'); + + lTag := imgInfo.ExifData.TagByName['ThumbnailCompression']; + CheckTrue(lTag <> nil, 'Tag "ThumbnailCompression" not found'); + CheckEquals('JPEG (old-style)', lTag.AsString, 'Value mismatch of tag "ThumbnailCompression"'); + + lTag := imgInfo.ExifData.TagByName['ThumbnailXResolution']; + CheckTrue(lTag <> nil, 'Tag "ThumbnailXResolution" not found'); + CheckEquals(72, lTag.AsInteger, 'Value mismatch of tag "ThumbnailXResolution"'); + + lTag := imgInfo.ExifData.TagByName['ThumbnailYResolution']; + CheckTrue(lTag <> nil, 'Tag "ThumbnailYResolution" not found'); + CheckEquals(72, lTag.AsInteger, 'Value mismatch of tag "ThumbnailYResolution"'); + + lTag := imgInfo.ExifData.TagByName['ThumbnailResolutionUnit']; + CheckTrue(lTag <> nil, 'Tag "ThumbnailResolutionUnit" not found'); + CheckEquals('inches', lTag.AsString, 'Value mismatch of tag "ThumbnailResolutionUnit"'); + + lTag := imgInfo.ExifData.TagByName['ThumbnailOffset']; + CheckTrue(lTag <> nil, 'Tag "ThumbnailOffset" not found'); + CheckTrue(lTag is TOffsettag, 'Tag "ThumbnailOffset" is not a TOffsetTag'); + offs := lTag.AsInteger + TOffsetTag(lTag).TiffHeaderOffset; + // Note: fpExif offset are relativ to the beginning of the TiffHeader, + // ExifTool offsets are relative to the beginning of the file. + CheckEquals(359, offs, 'Value mismatch of tag "ThumbnailOffset"'); + + lTag := imgInfo.ExifData.TagByName['ThumbnailSize']; + CheckTrue(lTag <> nil, 'Tag "ThumbnailSize" not found'); + CheckEquals(12025, lTag.AsInteger, 'Value mismatch of tag "ThumbnailSize"'); + + finally + imgInfo.Free; + end; +end; + +{ This test creates a new empty exif structure, but does not write anything to + file. } +procedure TstExifBE.CreateExifTest; +var + imgInfo: TImgInfo; +begin + imgInfo := TImgInfo.Create; + try + CheckTrue(imgInfo.ExifData = nil, 'EXIF found, but not expected.'); + imgInfo.CreateExifData; + CheckTrue(imgInfo.ExifData <> nil, 'EXIF not found.'); + finally + imgInfo.Free; + end; +end; + +{ This test creates an empty EXIF structure, fills it with some data and saves + it to the No_exif file. After writing the file is read back and compared + with the written data. } +procedure TstExifBE.WriteExifTest; +var + imgInfo: TImgInfo; + lTag: TTag; +begin + imgInfo := TImgInfo.Create; + try + // Create empty EXIF + imgInfo.CreateExifData; + + // Add tags + lTag := imgInfo.ExifData.AddTagByName('Make'); + CheckTrue(lTag <> nil, 'Tag "Make" not found for writing'); + lTag.AsString := 'Nokia'; + + lTag := imgInfo.ExifData.AddTagByName('Model'); + CheckTrue(lTag <> nil, 'Tag "Model" not found for writing'); + lTag.AsString := '6300'; + + lTag := imgInfo.ExifData.AddTagByName('Orientation'); + CheckTrue(lTag <> nil, 'Tag "Orientation" not found for writing'); + lTag.AsString := 'Horizontal (normal)'; + + lTag := imgInfo.ExifData.AddTagByName('XResolution'); + CheckTrue(lTag <> nil, 'Tag "XResolution" not found for writing'); + lTag.AsInteger := 72; + + lTag := imgInfo.ExifData.AddTagByName('YResolution'); + CheckTrue(lTag <> nil, 'Tag "YResolution" not found for writing'); + lTag.AsInteger := 72; + + lTag := imgInfo.ExifData.AddTagByName('ResolutionUnit'); + CheckTrue(lTag <> nil, 'Tag "ResolutionUnit" not found for writing'); + ltag.AsString := 'inches'; + + lTag := imgInfo.ExifData.AddTagByName('Software'); + CheckTrue(lTag <> nil, 'Tag "Software" not found for writing'); + lTag.AsString := 'FPC/fpExif'; + + lTag := imgInfo.ExifData.AddTagByName('YCbCrPositioning'); + CheckTrue(lTag <> nil, 'Tag "YCbCrPositioning" not found'); + lTag.AsString := 'Centered'; + + lTag := imgInfo.ExifData.AddTagByName('ExifVersion'); + CheckTrue(lTag <> nil, 'Tag "ExifVersion" not found for writing'); + CheckTrue(lTag is TVersionTag, 'Tag "ExifVersion" is not TVersionTag'); + TVersionTag(lTag).AsString := '0220'; + + lTag := imgInfo.ExifData.AddTagByName('ComponentsConfiguration'); + CheckTrue(lTag <> nil, 'Tag "ComponentsConfiguration" not found'); + lTag.AsString := 'YCbCr'; + + lTag := imgInfo.ExifData.AddTagByName('FlashPixVersion'); + CheckTrue(lTag <> nil, 'Tag "FlashPixVersion" not found'); + lTag.AsString := '0100'; + + lTag := imgInfo.ExifData.AddTagByName('ColorSpace'); + CheckTrue(lTag <> nil, 'Tag "ColorSpace" not found'); + lTag.AsString := 'sRGB'; + + lTag := imgInfo.ExifData.AddTagByName('ExifImageWidth'); + CheckTrue(lTag <> nil, 'Tag "ExifImageWidth" not found for writing'); + lTag.AsInteger := 200; + + lTag := imgInfo.ExifData.AddTagByName('ExifImageHeight'); + CheckTrue(lTag <> nil, 'Tag "ExifImageHeight" not found for writing'); + lTag.AsInteger := 267; + + // Save to file; + // Takes the image data from WorkFile_WithExif, replaces its EXIF with the + // current EXIF structure and writes to WorkFile_NoExif. + imgInfo.SaveToFile(WorkFile_NoExif, Workfile_WithExif); + finally + imgInfo.Free; + end; + + // Read written file and check EXIF + imgInfo := TImgInfo.Create; + try + imgInfo.LoadFromFile(Workfile_NoExif); + // Now there should be EXIF + CheckTrue(imgInfo.ExifData <> nil, 'EXIF not found.'); + + lTag := imgInfo.ExifData.TagByName['Make']; + CheckTrue(lTag <> nil, 'Tag "Make" not found'); + CheckEquals('Nokia', lTag.AsString, 'Value mismatch of tag "Make"'); + + lTag := imgInfo.ExifData.TagByName['Model']; + CheckTrue(lTag <> nil, 'Tag "Model" not found'); + CheckEquals('6300', lTag.AsString, 'Value mismatch of tag "Model"'); + + lTag := imgInfo.ExifData.TagByName['Orientation']; + CheckTrue(lTag <> nil, 'Tag "Orientation" not found'); + CheckEquals('Horizontal (normal)', lTag.AsString, 'Value mismatch of tag "Orientation"'); + + lTag := imgInfo.ExifData.TagByName['XResolution']; + CheckTrue(lTag <> nil, 'Tag "XResolution" not found'); + CheckEquals('72', lTag.AsString, 'Value mismatch of tag "XResolution"'); + CheckTrue(lTag is TNumericTag, 'Tag "XResolution" is no TNumericTag'); + CheckEquals(72, lTag.AsInteger, 'Integer value mismatch of tag "XResolution"'); + CheckTrue(lTag is TFloatTag, 'Tag "XResolution" is no TNumericTag'); + CheckEquals(72.0, lTag.AsFloat, 'Float value mismatch of tag "XResolution"'); + + lTag := imgInfo.ExifData.TagByName['YResolution']; + CheckTrue(lTag <> nil, 'Tag "YResolution" not found'); + CheckEquals('72', lTag.AsString, 'Value mismatch of tag "YResolution"'); + + lTag := imgInfo.ExifData.TagByName['ResolutionUnit']; + CheckTrue(lTag <> nil, 'Tag "ResolutionUnit" not found'); + CheckEquals('inches', lTag.AsString, 'Value mismatch of tag "ResolutionUnit"'); + + lTag := imgInfo.ExifData.TagByName['Software']; + CheckTrue(lTag <> nil, 'Tag "Software" not found'); + CheckEquals('FPC/fpExif', lTag.AsString, 'Value mismatch of tag "Software"'); + + lTag := imgInfo.ExifData.TagByName['YCbCrPositioning']; + CheckTrue(lTag <> nil, 'Tag "YCbCrPositioning" not found'); + CheckEquals('Centered', lTag.AsString, 'Value mismatch of tag "YCbCrPositioning"'); + + lTag := imgInfo.ExifData.TagByName['ExifVersion']; + CheckTrue(lTag <> nil, 'Tag "ExifVersion" not found'); + CheckTrue(lTag is TVersionTag, 'Tag "ExifVersion" is not TVersionTag'); + CheckEquals('0220', lTag.AsString, 'Value mismatch of tag "ExifVersion"'); + + lTag := imgInfo.ExifData.TagByName['ComponentsConfiguration']; + CheckTrue(lTag <> nil, 'Tag "ComponentsConfiguration" not found'); + CheckEquals('YCbCr', lTag.AsString, 'Value mismatch of tag "ComponentsConfiguration"'); + // Expected value manually edited from "Y, Cb, Cr, -" to "YCbCr" + + lTag := imgInfo.ExifData.TagByName['FlashPixVersion']; + CheckTrue(lTag <> nil, 'Tag "FlashPixVersion" not found'); + CheckEquals('0100', lTag.AsString, 'Value mismatch of tag "FlashPixVersion"'); + + lTag := imgInfo.ExifData.TagByName['ColorSpace']; + CheckTrue(lTag <> nil, 'Tag "ColorSpace" not found'); + CheckEquals('sRGB', lTag.AsString, 'Value mismatch of tag "ColorSpace"'); + + lTag := imgInfo.ExifData.TagByName['ExifImageWidth']; + CheckTrue(lTag <> nil, 'Tag "ExifImageWidth" not found'); + CheckEquals(200, lTag.AsInteger, 'Value mismatch of tag "ExifImageWidth"'); + + lTag := imgInfo.ExifData.TagByName['ExifImageHeight']; + CheckTrue(lTag <> nil, 'Tag "ExifImageHeight" not found'); + CheckEquals(267, lTag.AsInteger, 'Value mismatch of tag "ExifImageHeight"'); + + // No thumbnail in dest file! + + finally + imgInfo.Free; + end; +end; + +procedure TstExifBE.ValidFileTest; +var + jpg: TJpegImage; + fn: string; + bmp: TBitmap; + success: Boolean; +begin + // Modify the EXIF structure of WorkFile_WithExif; + fn := Workfile_WithExif; + WriteExifTest; + success := false; + jpg := TJpegImage.Create; + try + jpg.LoadFromFile(fn); + bmp := TBitmap.Create; + try + bmp.Width := jpg.Width; + bmp.Height := jpg.Height; + bmp.Canvas.Draw(0, 0, jpg); + success := true; + finally + bmp.Free; + CheckTrue(success, 'Non-readable file'); + end; + finally + jpg.Free; + end; +end; + + +initialization + {$IFDEF FPC} + RegisterTest(TstExifBE); + {$ELSE} + TestFramework.RegisterTest(TstExifBE.Suite); + {$ENDIF} + +end. + diff --git a/components/fpexif/tests/unittest/common/fetexifle.pas b/components/fpexif/tests/unittest/common/fetexifle.pas new file mode 100644 index 000000000..226fbdc61 --- /dev/null +++ b/components/fpexif/tests/unittest/common/fetexifle.pas @@ -0,0 +1,727 @@ +unit fetExifLE; + +{$IFDEF FPC} +{$mode objfpc}{$H+} +{$ENDIF} + +interface + +uses + Classes, SysUtils, + {$ifdef FPC} + fpcunit, testutils, testregistry; + {$else} + fetTestUtils, TestFrameWork; + {$endif} + +const + // JPEG picture with Exif data + ExifJpegPic = '..\pictures\originals\with_exif.jpg'; + WorkFile_JpegWithExif = '.\pictures\with_exif.jpg'; + + // TIFF pPicture with Exif data + ExifTiffPic = '..\pictures\originals\with_exif.tif'; + WorkFile_TiffWithExif = '.\pictures\with_exif.tif'; + + // Picture without Exif data + NoExifPic = '..\pictures\originals\no_metadata.jpg'; + WorkFile_NoExif = '.\pictures\no_exif.jpg'; + +type + TstExifLE = class(TTestCase) + protected + procedure SetUp; override; + procedure TearDown; override; + procedure Internal_CheckHasExif(AFileName: String; ExpectExif: Boolean); + published + procedure CheckForPictures; + procedure CheckCreateImgInfo; + procedure CheckHasExif; + procedure ReadExifTest_Jpeg; + procedure ReadExifTest_Tiff; + procedure ReadGPSTest; + procedure CreateExifTest; + procedure WriteExifTest_Jpeg; + procedure WriteGPSTest_Jpeg; + procedure ValidFileTest_Jpeg; + procedure CreateThumbnail_Jpeg; + end; + +implementation + +uses + {$IFDEF FPC} + Graphics, FileUtil, + {$ELSE} + Graphics, Jpeg, + {$ENDIF} + Math, + fpeGlobal, fpeTags, fpeUtils, fpeExifData, fpeMetadata; + +procedure TstExifLE.SetUp; +var + dir: String; +begin + if FileExists(Workfile_JpegWithExif) then + DeleteFile(WorkFile_JpegWithExif); + if FileExists(Workfile_TiffWithExif) then + DeleteFile(WorkFile_TiffWithExif); + if FileExists(Workfile_NoExif) then + DeleteFile(Workfile_NoExif); + + dir := ExtractFileDir(WorkFile_JpegWithExif); + if not DirectoryExists(dir) then + ForceDirectories(dir); + + if not FileExists(WorkFile_JpegWithExif) then + if FileExists(ExifJpegPic) then + CopyFile(ExifJpegPic, WorkFile_JpegWithExif); + if not FileExists(WorkFile_TiffWithExif) then + if FileExists(ExifTiffPic) then + CopyFile(ExifTiffPic, WorkFile_TiffWithExif); + if not FileExists(WorkFile_NoExif) then + if FileExists(NoExifPic) then + CopyFile(NoExifPic, WorkFile_NoExif); +end; + +procedure TstExifLE.TearDown; +begin + if FileExists(WorkFile_NoExif) then + DeleteFile(WorkFile_NoExif); + if FileExists(WorkFile_JpegWithExif) then + DeleteFile(WorkFile_JpegWithExif); + if FileExists(WorkFile_TiffWithExif) then + DeleteFile(WorkFile_TiffWithExif); +end; + +procedure TstExifLE.CheckForPictures; +begin + CheckTrue(FileExists(ExifJpegPic), 'Original test picture file "' + ExifJpegPic + '" does not exist'); + CheckTrue(FileExists(ExifTiffPic), 'Original test picture file "' + ExifTiffPic + '" does not exist'); + CheckTrue(FileExists(NoExifPic), 'Original test picture file "' + NoExifPic + '" does not exist'); + + CheckTrue(FileExists(WorkFile_JpegWithExif), 'Test picture file "' + WorkFile_JpegWithExif + '" does not exist'); + CheckTrue(FileExists(WorkFile_TiffWithExif), 'Test picture file "' + WorkFile_TiffWithExif + '" does not exist'); + CheckTrue(FileExists(WorkFile_NoExif), 'Test picture file "' + WorkFile_NoExif + '" does not exist'); +end; + +procedure TstExifLE.CheckCreateImgInfo; +var + imgInfo: TImgInfo; +begin + imgInfo := TImgInfo.Create(); + try + CheckIs(imgInfo, TImgInfo,'Is not TImgInfo'); + finally + imgInfo.Free; + end; +end; + +procedure TstExifLE.Internal_CheckHasExif(AFileName: String; ExpectExif: Boolean); +var + imgInfo: TImgInfo; +begin + imgInfo := TImgInfo.Create; + try + imgInfo.LoadFromFile(AFileName); + if ExpectExif then + CheckTrue(imgInfo.HasExif, 'Failure to detect EXIF in test picture file "' + AFileName + '"') + else + CheckFalse(imgInfo.HasExif, 'Unexpected EXIF in test picture file "' + AFileName + '" detected'); + finally + imgInfo.Free; + end; +end; + +procedure TstExifLE.CheckHasExif; +begin + Internal_CheckHasExif(WorkFile_JpegWithExif, true); + Internal_CheckHasExif(WorkFile_TiffWithExif, true); + Internal_CheckHasExif(WorkFile_NoExif, false); +end; + +procedure TstExifLE.ReadExifTest_Jpeg; +{ Output of ExifTool for the jpeg test image with exif using this commandline: + exiftool -G -H -s with_exif.jpg > with_exif.txt + + These values are checked + | +[ExifTool] - ExifToolVersion : 10.60 +[File] - FileName : with_exif.jpg <-- +[File] - Directory : . +[File] - FileSize : 5.0 kB <-- +[File] - FileModifyDate : 2017:10:16 19:35:01+02:00 +[File] - FileAccessDate : 2017:10:16 19:35:01+02:00 +[File] - FileCreateDate : 2017:10:16 19:34:46+02:00 +[File] - FilePermissions : rw-rw-rw- +[File] - FileType : JPEG +[File] - FileTypeExtension : jpg +[File] - MIMEType : image/jpeg +[File] - ExifByteOrder : Little-endian (Intel, II) <-- +[File] - ImageWidth : 200 <-- +[File] - ImageHeight : 150 <-- +[File] - EncodingProcess : Baseline DCT, Huffman coding +[File] - BitsPerSample : 8 +[File] - ColorComponents : 3 +[File] - YCbCrSubSampling : YCbCr4:2:0 (2 2) +[EXIF] 0x010d DocumentName : Test image <-- +[EXIF] 0x010e ImageDescription : This is just a test image <-- +[EXIF] 0x0112 Orientation : Horizontal (normal) <-- +[EXIF] 0x011a XResolution : 72 <-- +[EXIF] 0x011b YResolution : 72 <-- +[EXIF] 0x0128 ResolutionUnit : inches <-- +[EXIF] 0x0131 Software : PhotoFiltre 7 <-- +[EXIF] 0x0132 ModifyDate : 2017:10:14 23:35:07 <-- +[EXIF] 0x9000 ExifVersion : 0210 <-- +[EXIF] 0xa002 ExifImageWidth : 200 <-- +[EXIF] 0xa003 ExifImageHeight : 150 <-- +[EXIF] 0x0000 GPSVersionID : 2.3.0.0 <-- +[EXIF] 0x0001 GPSLatitudeRef : South <-- +[EXIF] 0x0003 GPSLongitudeRef : West <-- +[Composite] - GPSLatitude : 51 deg 33' 48.28" S <-- fpExif coordinates without the S +[Composite] - GPSLongitude : 59 deg 49' 53.55" W <-- fpExif coordinates without the W +[Composite] - GPSPosition : 51 deg 33' 48.28" S, 59 deg 49' 53.55" W +[Composite] - ImageSize : 200x150 +[Composite] - Megapixels : 0.030 +} +var + imgInfo: TImgInfo; + lTag: TTag; +begin + imgInfo := TImgInfo.Create; + try + imgInfo.LoadFromFile(WorkFile_JpegWithExif); + + // This is general information stored within imgImfInfo + CheckEquals('with_exif.jpg', ExtractFileName(imgInfo.FileName), 'Filename mismatch'); + CheckEquals('5.0', Format('%.1f', [imgInfo.FileSize/1024], fpExifFmtSettings), 'File size mismatch'); +// CheckEquals('2017:10:14 23:57:49', FormatDateTime(EXIF_DATETIME_FORMAT, imgInfo.FileDate), 'File access date mismatch'); + CheckEquals(200, imgInfo.ImgWidth, 'jpeg image width mismatch'); + CheckEquals(150, imgInfo.ImgHeight, 'jpeg image height mismatch'); + + // The following pieces of information are obtained from the EXIF segment + CheckFalse(imgInfo.ExifData.BigEndian, 'Exif byte order detection error'); + + lTag := imgInfo.ExifData.TagByName['DocumentName']; + CheckTrue(lTag <> nil, 'Tag "DocumentName" not found'); + CheckEquals('Test image', lTag.AsString, 'Value mismatch of tag "DocumentName"'); + + lTag := imgInfo.ExifData.TagByName['ImageDescription']; + CheckTrue(lTag <> nil, 'Tag "ImageDescription" not found'); + CheckEquals('This is just a test image', lTag.AsString, 'Value mismatch of tag "ImageDescription"'); + + lTag := imgInfo.ExifData.TagByName['Orientation']; + CheckTrue(lTag <> nil, 'Tag "Orientation" not found'); + CheckEquals('Horizontal (normal)', lTag.AsString, 'Value mismatch of tag "Orientation"'); + + lTag := imgInfo.ExifData.TagByName['XResolution']; + CheckTrue(lTag <> nil, 'Tag "XResolution" not found'); + CheckEquals('72', lTag.AsString, 'Value mismatch of tag "XResolution"'); + CheckTrue(lTag is TNumericTag, 'Tag "XResolution" is no TNumericTag'); + CheckEquals(72, lTag.AsInteger, 'Integer value mismatch of tag "XResolution"'); + CheckTrue(lTag is TFloatTag, 'Tag "XResolution" is no TNumericTag'); + CheckEquals(72.0, lTag.AsFloat, 'Float value mismatch of tag "XResolution"'); + + lTag := imgInfo.ExifData.TagByName['YResolution']; + CheckTrue(lTag <> nil, 'Tag "YResolution" not found'); + CheckEquals('72', lTag.AsString, 'Value mismatch of tag "YResolution"'); + + lTag := imgInfo.ExifData.TagByName['ResolutionUnit']; + CheckTrue(lTag <> nil, 'Tag "ResolutionUnit" not found'); + CheckEquals('inches', lTag.AsString, 'Value mismatch of tag "ResolutionUnit"'); + + lTag := imgInfo.ExifData.TagByName['Software']; + CheckTrue(lTag <> nil, 'Tag "Software" not found'); + CheckEquals('PhotoFiltre 7', lTag.AsString, 'Value mismatch of tag "Software"'); + + lTag := imgInfo.ExifData.TagByName['DateTime']; + CheckTrue(lTag <> nil, 'Tag "DateTime" not found'); + CheckTrue(lTag is TDateTimeTag, 'Tag "DateTime" is no TDateTimeTag'); + TDateTimeTag(lTag).FormatStr := EXIF_DATETIME_FORMAT; + CheckEquals('2017:10:14 23:35:07', lTag.AsString, 'Value mismatch of tag "DateTime"'); + + lTag := imgInfo.ExifData.TagByName['ExifVersion']; + CheckTrue(lTag <> nil, 'Tag "ExifVersion" not found'); + CheckTrue(lTag is TVersionTag, 'Tag "ExifVersion" is not TVersionTag'); + CheckEquals('0210', lTag.AsString, 'Value mismatch of tag "ExifVersion"'); + + lTag := imgInfo.ExifData.TagByName['ExifImageWidth']; + CheckTrue(lTag <> nil, 'Tag "ExifImageWidth" not found'); + CheckEquals('200', lTag.AsString, 'Value mismatch of tag "ExifImageWidth"'); + + lTag := imgInfo.ExifData.TagByName['ExifImageHeight']; + CheckTrue(lTag <> nil, 'Tag "ExifImageHeight" not found'); + CheckEquals('150', lTag.AsString, 'Value mismatch of tag "ExifImageHeight"'); + + finally + imgInfo.Free; + end; +end; + +procedure TstExifLE.ReadExifTest_Tiff; +{ Output of ExifTool for the tiff test image with exif using this commandline: + exiftool -G -H -s with_exif.tif > with_exif_tif.txt + + These values are checked + | +[ExifTool] - ExifToolVersion : 10.60 +[File] - FileName : with_exif.tif +[File] - Directory : . +[File] - FileSize : 88 kB +[File] - FileModifyDate : 2017:10:16 10:07:38+02:00 +[File] - FileAccessDate : 2017:10:16 10:07:38+02:00 +[File] - FileCreateDate : 2017:10:16 10:07:38+02:00 +[File] - FilePermissions : rw-rw-rw- +[File] - FileType : TIFF +[File] - FileTypeExtension : tif +[File] - MIMEType : image/tiff +[File] - ExifByteOrder : Little-endian (Intel, II) +[EXIF] 0x0100 ImageWidth : 200 +[EXIF] 0x0101 ImageHeight : 150 +[EXIF] 0x0102 BitsPerSample : 8 8 8 +[EXIF] 0x0103 Compression : Uncompressed +[EXIF] 0x0106 PhotometricInterpretation : RGB +[EXIF] 0x0111 StripOffsets : (Binary data 68 bytes, use -b option to extract) +[EXIF] 0x0115 SamplesPerPixel : 3 +[EXIF] 0x0116 RowsPerStrip : 13 +[EXIF] 0x0117 StripByteCounts : (Binary data 59 bytes, use -b option to extract) +[EXIF] 0x011a XResolution : 72 +[EXIF] 0x011b YResolution : 72 +[EXIF] 0x011c PlanarConfiguration : Chunky +[EXIF] 0x0128 ResolutionUnit : inches +[EXIF] 0x0131 Software : LIBFORMAT (c) Pierre-e Gougelet +[EXIF] 0x0132 ModifyDate : 2017:10:14 23:35:07 +[EXIF] 0x9000 ExifVersion : 0210 +[EXIF] 0xa002 ExifImageWidth : 200 +[Composite] - ImageSize : 200x150 +[Composite] - Megapixels : 0.030 } +var + imgInfo: TImgInfo; + lTag: TTag; +begin + imgInfo := TImgInfo.Create; + try + imgInfo.LoadFromFile(WorkFile_TiffWithExif); + + // This is general information stored within imgImfInfo + CheckEquals('with_exif.tif', ExtractFileName(imgInfo.FileName), 'Filename mismatch'); + CheckEquals('88', Format('%.0f', [imgInfo.FileSize/1024], fpExifFmtSettings), 'File size mismatch'); + + // The following pieces of information are obtained from the EXIF segment + CheckFalse(imgInfo.ExifData.BigEndian, 'Exif byte order detection error'); + + lTag := imgInfo.ExifData.TagByName['ImageWidth']; + CheckTrue(lTag <> nil, 'Tag "ImageWidth" not found'); + CheckEquals(200, lTag.AsInteger, 'Value mismatch of tag "ImageWidth"'); + + lTag := imgInfo.ExifData.TagByName['ImageHeight']; + CheckTrue(lTag <> nil, 'Tag "ImageHeight" not found'); + CheckEquals(150, lTag.AsInteger, 'Value mismatch of tag "ImageHeight"'); + + lTag := imgInfo.ExifData.TagByName['BitsPerSample']; + CheckTrue(lTag <> nil, 'Tag "BitsPerSample" not found'); + CheckEquals('8,8,8', lTag.AsString, 'Value mismatch of tag "BitsPerSample"'); + + lTag := imgInfo.ExifData.TagByName['Compression']; + CheckTrue(lTag <> nil, 'Tag "Compression" not found'); + CheckEquals('Uncompressed', lTag.AsString, 'Value mismatch of tag "Compression"'); + + lTag := imgInfo.ExifData.TagByName['PhotometricInterpretation']; + CheckTrue(lTag <> nil, 'Tag "PhotometricInterpretation" not found'); + CheckEquals('RGB', lTag.AsString, 'Value mismatch of tag "PhotometricInterpretation"'); + + lTag := imgInfo.ExifData.TagByName['SamplesPerPixel']; + CheckTrue(lTag <> nil, 'Tag "SamplesPerPixel" not found'); + CheckEquals(3, lTag.AsInteger, 'Value mismatch of tag "SamplesPerPixel"'); + + lTag := imgInfo.ExifData.TagByName['RowsPerStrip']; + CheckTrue(lTag <> nil, 'Tag "RowsPerStrip" not found'); + CheckEquals(13, lTag.AsInteger, 'Value mismatch of tag "RowsPerStrip"'); + + lTag := imgInfo.ExifData.TagByName['XResolution']; + CheckTrue(lTag <> nil, 'Tag "XResolution" not found'); + CheckEquals(72, lTag.AsInteger, 'Integer value mismatch of tag "XResolution"'); + + lTag := imgInfo.ExifData.TagByName['YResolution']; + CheckTrue(lTag <> nil, 'Tag "YResolution" not found'); + CheckEquals(72, lTag.AsInteger, 'Value mismatch of tag "YResolution"'); + + lTag := imgInfo.ExifData.TagByName['PlanarConfiguration']; + CheckTrue(lTag <> nil, 'Tag "PlanarConfiguration" not found'); + CheckEquals('Chunky', lTag.AsString, 'Value mismatch of tag "PlanarConfiguration"'); + + lTag := imgInfo.ExifData.TagByName['ResolutionUnit']; + CheckTrue(lTag <> nil, 'Tag "ResolutionUnit" not found'); + CheckEquals('inches', lTag.AsString, 'Value mismatch of tag "ResolutionUnit"'); + + lTag := imgInfo.ExifData.TagByName['Software']; + CheckTrue(lTag <> nil, 'Tag "Software" not found'); + CheckEquals('LIBFORMAT (c) Pierre-e Gougelet', lTag.AsString, 'Value mismatch of tag "Software"'); + + lTag := imgInfo.ExifData.TagByName['DateTime']; + CheckTrue(lTag <> nil, 'Tag "DateTime" not found'); + CheckTrue(lTag is TDateTimeTag, 'Tag "DateTime" is no TDateTimeTag'); + TDateTimeTag(lTag).FormatStr := EXIF_DATETIME_FORMAT; + CheckEquals('2017:10:14 23:35:07', lTag.AsString, 'Value mismatch of tag "DateTime"'); + + lTag := imgInfo.ExifData.TagByName['ExifVersion']; + CheckTrue(lTag <> nil, 'Tag "ExifVersion" not found'); + CheckTrue(lTag is TVersionTag, 'Tag "ExifVersion" is not TVersionTag'); + CheckEquals('0210', lTag.AsString, 'Value mismatch of tag "ExifVersion"'); + + lTag := imgInfo.ExifData.TagByName['ExifImageWidth']; + CheckTrue(lTag <> nil, 'Tag "ExifImageWidth" not found'); + CheckEquals(200, lTag.AsInteger, 'Value mismatch of tag "ExifImageWidth"'); + + finally + imgInfo.Free; + end; +end; + + +{ This test read the GPS data contained in file "with_exif.jpg". The GPS + data were written there using the service https://www.geoimgr.com/ + See expected values in comment of "ReadExifTest". } +procedure TstExifLE.ReadGPSTest; +var + imgInfo: TImgInfo; + lTag: TTag; +begin + imgInfo := TImgInfo.Create; + try + imgInfo.LoadFromFile(WorkFile_JpegWithExif); + + lTag := imgInfo.ExifData.TagByName['GPSVersionID']; + CheckTrue(lTag <> nil, 'Tag "GPSVersionID" not found'); + CheckTrue(lTag is TVersionTag, 'Tag "GPSVersionID" is not TVersionTag'); + TVersionTag(lTag).Separator := '.'; + CheckEquals('2.3.0.0', lTag.AsString, 'Value mismatch of tag "GPSVersionID"'); + + lTag := imgInfo.ExifData.TagByName['GPSLatitude']; + CheckTrue(lTag <> nil, 'Tag "GPSLatitude" not found'); + CheckTrue(lTag is TGPSPositionTag, 'Tag "GPSLatitude" is not a TGpsPositionTag'); + TGpsPositionTag(lTag).FormatStr := '%0:.0f deg %1:.0f'' %2:.2f"'; + CheckEquals('51 deg 33'' 48.28"', lTag.AsString, 'Value mismatch of tag "GPSLatitude"'); + + lTag := imgInfo.ExifData.TagByName['GPSLatitudeRef']; + CheckTrue(lTag <> nil, 'Tag "GPSLatitudeRef" not found'); + CheckEquals('South', lTag.AsString, 'Value mismatch of tag "GPSLatitudeRef"'); + + lTag := imgInfo.ExifData.TagByName['GPSLongitude']; + CheckTrue(lTag <> nil, 'Tag "GPSLongitude" not found'); + CheckTrue(lTag is TGPSPositionTag, 'Tag "GPSLongitude" is not a TGpsPositionTag'); + TGpsPositionTag(lTag).FormatStr := '%0:.0f deg %1:.0f'' %2:.2f"'; + CheckEquals('59 deg 49'' 53.55"', lTag.AsString, 'Value mismatch of tag "GPSLongitude"'); + + lTag := imgInfo.ExifData.TagByName['GPSLongitudeRef']; + CheckTrue(lTag <> nil, 'Tag "GPSLongitudeRef" not found'); + CheckEquals('West', lTag.AsString, 'Value mismatch of tag "GPSLongitudeRef"'); + + finally + imgInfo.Free; + end; +end; + +{ This test creates a new empty exif structure, but does not write anything to + file. } +procedure TstExifLE.CreateExifTest; +var + imgInfo: TImgInfo; +begin + imgInfo := TImgInfo.Create; + try + CheckTrue(imgInfo.ExifData = nil, 'EXIF found, but not expected.'); + imgInfo.CreateExifData; + CheckTrue(imgInfo.ExifData <> nil, 'EXIF not found.'); + finally + imgInfo.Free; + end; +end; + +{ This test creates an empty EXIF structure, fills it with some data and saves + it to the No_exif file. After writing the file is read back and compared + with the written data. } +procedure TstExifLE.WriteExifTest_Jpeg; +var + imgInfo: TImgInfo; + lTag: TTag; +begin + imgInfo := TImgInfo.Create; + try + // Create empty EXIF + imgInfo.CreateExifData; + + // Add tags + lTag := imgInfo.ExifData.AddTagByName('Primary.DocumentName'); + lTag.AsString := 'Test image'; + + lTag := imgInfo.ExifData.AddTagByName('ImageDescription'); + lTag.AsString := 'This is just a test image'; + + lTag := imgInfo.ExifData.AddTagByName('Orientation'); + CheckTrue(lTag <> nil, 'Tag "Orientation" not found for writing'); + lTag.AsString := 'Horizontal (normal)'; + + lTag := imgInfo.ExifData.AddTagByName('XResolution'); + CheckTrue(lTag <> nil, 'Tag "XResolution" not found for writing'); + lTag.AsInteger := 72; + + lTag := imgInfo.ExifData.AddTagByName('YResolution'); + CheckTrue(lTag <> nil, 'Tag "YResolution" not found for writing'); + lTag.AsInteger := 72; + + lTag := imgInfo.ExifData.AddTagByName('ResolutionUnit'); + CheckTrue(lTag <> nil, 'Tag "ResolutionUnit" not found for writing'); + ltag.AsString := 'inches'; + + lTag := imgInfo.ExifData.AddTagByName('Software'); + CheckTrue(lTag <> nil, 'Tag "Software" not found for writing'); + lTag.AsString := 'FPC/fpExif'; + + lTag := imgInfo.ExifData.AddTagByName('DateTime'); + CheckTrue(lTag <> nil, 'Tag "DateTime" not found for writing'); + CheckTrue(lTag is TDateTimeTag, 'Tag "DateTime" is no TDateTimeTag'); + TDateTimeTag(lTag).AsDateTime := EncodeDate(2017,10,14) + EncodeTime(23,35,07,0); + + lTag := imgInfo.ExifData.AddTagByName('ExifVersion'); + CheckTrue(lTag <> nil, 'Tag "ExifVersion" not found for writing'); + CheckTrue(lTag is TVersionTag, 'Tag "ExifVersion" is not TVersionTag'); + TVersionTag(lTag).AsString := '0210'; + + lTag := imgInfo.ExifData.AddTagByName('ExifImageWidth'); + CheckTrue(lTag <> nil, 'Tag "ExifImageWidth" not found for writing'); + lTag.AsInteger := 200; + + lTag := imgInfo.ExifData.AddTagByName('ExifImageHeight'); + CheckTrue(lTag <> nil, 'Tag "ExifImageHeight" not found for writing'); + lTag.AsInteger := 150; + + // Save to file; + // Takes the image data from WorkFile_WithExif, replaces its EXIF with the + // current EXIF structure and writes to WorkFile_NoExif. + imgInfo.SaveToFile(WorkFile_NoExif, Workfile_JpegWithExif); + finally + imgInfo.Free; + end; + + // Read written file and check EXIF + imgInfo := TImgInfo.Create; + try + imgInfo.LoadFromFile(Workfile_NoExif); + // Now there should be EXIF + CheckTrue(imgInfo.ExifData <> nil, 'EXIF not found.'); + + lTag := imgInfo.ExifData.TagByName['DocumentName']; + CheckTrue(lTag <> nil, 'Tag "DocumentName" not found for reading'); + CheckEquals('Test image', lTag.AsString, 'Value mismatch of tag "DocumentName"'); + + lTag := imgInfo.ExifData.TagByName['ImageDescription']; + CheckTrue(lTag <> nil, 'Tag "ImageDescription" not found for reading'); + CheckEquals('This is just a test image', lTag.AsString, 'Value mismatch of tag "ImageDescription"'); + + lTag := imgInfo.ExifData.TagByName['Orientation']; + CheckTrue(lTag <> nil, 'Tag "Orientation" not found for reading'); + CheckEquals('Horizontal (normal)', lTag.AsString, 'Value mismatch of tag "Orientation"'); + + lTag := imgInfo.ExifData.TagByName['XResolution']; + CheckTrue(lTag <> nil, 'Tag "XResolution" not found for reading'); + CheckEquals(72, lTag.AsInteger, 'Integer value mismatch of tag "XResolution"'); + + lTag := imgInfo.ExifData.TagByName['YResolution']; + CheckTrue(lTag <> nil, 'Tag "YResolution" not found for reading'); + CheckEquals('72', lTag.AsString, 'Value mismatch of tag "YResolution"'); + + lTag := imgInfo.ExifData.TagByName['ResolutionUnit']; + CheckTrue(lTag <> nil, 'Tag "ResolutionUnit" not found for reading'); + CheckEquals('inches', lTag.AsString, 'Value mismatch of tag "ResolutionUnit"'); + + lTag := imgInfo.ExifData.TagByName['Software']; + CheckTrue(lTag <> nil, 'Tag "Software" not found for reading'); + CheckEquals('FPC/fpExif', lTag.AsString, 'Value mismatch of tag "Software"'); + + lTag := imgInfo.ExifData.TagByName['DateTime']; + CheckTrue(lTag <> nil, 'Tag "DateTime" not found for reading'); + CheckTrue(lTag is TDateTimeTag, 'Tag "DateTime" is no TDateTimeTag'); + TDateTimeTag(lTag).FormatStr := ISO_DATETIME_FORMAT; + CheckEquals('2017-10-14 23:35:07', lTag.AsString, 'Value mismatch of tag "DateTime"'); + + lTag := imgInfo.ExifData.TagByName['ExifVersion']; + CheckTrue(lTag <> nil, 'Tag "ExifVersion" not found for reading'); + CheckTrue(lTag is TVersionTag, 'Tag "ExifVersion" is not TVersionTag'); + CheckEquals('0210', lTag.AsString, 'Value mismatch of tag "ExifVersion"'); + + lTag := imgInfo.ExifData.TagByName['ExifImageWidth']; + CheckTrue(lTag <> nil, 'Tag "ExifImageWidth" not found for reading'); + CheckEquals('200', lTag.AsString, 'Value mismatch of tag "ExifImageWidth"'); + + lTag := imgInfo.ExifData.TagByName['ExifImageHeight']; + CheckTrue(lTag <> nil, 'Tag "ExifImageHeight" not found for reading'); + CheckEquals('150', lTag.AsString, 'Value mismatch of tag "ExifImageHeight"'); + + finally + imgInfo.Free; + end; +end; + +{ This test loads the With_Exif and changes the GPS data, saves to the same file, + reads back and checks validity of the GPS data. } +procedure TstExifLE.WriteGpsTest_Jpeg; +var + imgInfo: TImgInfo; + lTag: TTag; +begin + imgInfo := TImgInfo.Create; + try + imgInfo.LoadfromFile(Workfile_JpegWithExif); + // In spite of its name, the file must contain EXIF now, written in prev test. + CheckTrue(imgInfo.ExifData <> nil, 'EXIF not found.'); + + // Add tags + lTag := imgInfo.ExifData.AddTagByName('GPSVersionID'); + CheckTrue(lTag <> nil, 'Tag "GPSVersionID" not found'); + CheckTrue(lTag is TVersionTag, 'Tag "GPSVersionID" is not TVersionTag'); + TVersionTag(lTag).Separator := '.'; + lTag.AsString := '2.3.1.1'; + + lTag := imgInfo.ExifData.AddTagByName('GPSLatitude'); + CheckTrue(lTag <> nil, 'Tag "GPSLatitude" not found'); + CheckTrue(lTag is TGPSPositionTag, 'Tag "GPSLatitude" is not a TGpsPositionTag'); + TGpsPositionTag(lTag).FormatStr := '%0:.0f deg %1:.0f'' %2:.2f"'; + lTag.AsString := '45 deg 30'' 15.32"'; + + lTag := imgInfo.ExifData.AddTagByName('GPSLatitudeRef'); + CheckTrue(lTag <> nil, 'Tag "GPSLatitudeRef" not found'); + lTag.AsString := 'North'; + + lTag := imgInfo.ExifData.AddTagByName('GPSLongitude'); + CheckTrue(lTag <> nil, 'Tag "GPSLongitude" not found'); + CheckTrue(lTag is TGPSPositionTag, 'Tag "GPSLongitude" is not a TGpsPositionTag'); + TGpsPositionTag(lTag).FormatStr := '%0:.0f deg %1:.0f'' %2:.2f"'; + lTag.AsString := '15 deg 16'' 17.18"'; + + lTag := imgInfo.ExifData.AddTagByName('GPSLongitudeRef'); + CheckTrue(lTag <> nil, 'Tag "GPSLongitudeRef" not found'); + lTag.AsString := 'East'; + + // Save to file + imgInfo.SaveToFile(WorkFile_JpegWithExif); + finally + imgInfo.Free; + end; + + imgInfo := TImgInfo.Create; + try + imgInfo.LoadFromFile(Workfile_JpegWithExif); + CheckTrue(imgInfo.ExifData <> nil, 'EXIF not found after writing.'); + + lTag := imgInfo.ExifData.TagByName['GPSVersionID']; + CheckTrue(lTag <> nil, 'Tag "GPSVersionID" not found'); + CheckTrue(lTag is TVersionTag, 'Tag "GPSVersionID" is not TVersionTag'); + TVersionTag(lTag).Separator := '.'; + CheckEquals('2.3.1.1', lTag.AsString, 'Value mismatch of tag "GPSVersionID"'); + + lTag := imgInfo.ExifData.TagByName['GPSLatitude']; + CheckTrue(lTag <> nil, 'Tag "GPSLatitude" not found'); + CheckTrue(lTag is TGPSPositionTag, 'Tag "GPSLatitude" is not a TGpsPositionTag'); + TGpsPositionTag(lTag).FormatStr := '%0:.0f deg %1:.0f'' %2:.2f"'; + CheckEquals('45 deg 30'' 15.32"', lTag.AsString, 'Value mismatch of tag "GPSLatitude"'); + + lTag := imgInfo.ExifData.TagByName['GPSLatitudeRef']; + CheckTrue(lTag <> nil, 'Tag "GPSLatitudeRef" not found'); + CheckEquals('North', lTag.AsString, 'Value mismatch of tag "GPSLatitudeRef"'); + + lTag := imgInfo.ExifData.TagByName['GPSLongitude']; + CheckTrue(lTag <> nil, 'Tag "GPSLongitude" not found'); + CheckTrue(lTag is TGPSPositionTag, 'Tag "GPSLongitude" is not a TGpsPositionTag'); + TGpsPositionTag(lTag).FormatStr := '%0:.0f deg %1:.0f'' %2:.2f"'; + CheckEquals('15 deg 16'' 17.18"', lTag.AsString, 'Value mismatch of tag "GPSLongitude"'); + + lTag := imgInfo.ExifData.TagByName['GPSLongitudeRef']; + CheckTrue(lTag <> nil, 'Tag "GPSLongitudeRef" not found'); + CheckEquals('East', lTag.AsString, 'Value mismatch of tag "GPSLongitudeRef"'); + + finally + imgInfo.Free; + end; +end; + +procedure TstExifLE.ValidFileTest_Jpeg; +var + jpg: TJpegImage; + fn: string; + bmp: TBitmap; + success: Boolean; +begin + // Modify the EXIF structure of WorkFile_WithExif; + fn := Workfile_JpegWithExif; + WriteExifTest_Jpeg; + success := false; + jpg := TJpegImage.Create; + try + jpg.LoadFromFile(fn); + bmp := TBitmap.Create; + try + bmp.Width := jpg.Width; + bmp.Height := jpg.Height; + bmp.Canvas.Draw(0, 0, jpg); + success := true; + finally + bmp.Free; + CheckTrue(success, 'Non-readable file'); + end; + finally + jpg.Free; + end; +end; + +procedure TstExifLE.CreateThumbnail_Jpeg; +const + THUMBSIZE = 120; +var + imgInfo: TImgInfo; + srcStream, destStream: TMemoryStream; + destStreamSize, currentThumbSize: Int64; + w, h: Integer; +begin + imgInfo := TImgInfo.Create; + try + imgInfo.LoadFromFile(Workfile_JpegWithExif); + CheckTrue(imgInfo.ExifData <> nil, 'EXIF not found.'); + CheckFalse(imgInfo.ExifData.HasThumbnail, 'Presence of thumbnail not expected'); + + srcStream := TMemoryStream.Create; + destStream := TMemoryStream.Create; + try + srcStream.LoadFromFile(Workfile_JpegWithExif); + JPEGScaleImage(srcStream, destStream, THUMBSIZE); + destStreamSize := destStream.Size; + destStream.Position := 0; + imgInfo.ExifData.LoadThumbnailFromStream(deststream); + finally + destStream.Free; + srcStream.Free; + end; + + CheckTrue(imgInfo.ExifData.HasThumbnail, 'Thumbnail not found.'); + w := imgInfo.ExifData.TagByName['ThumbnailWidth'].AsInteger; + h := imgInfo.ExifData.TagByName['ThumbnailHeight'].AsInteger; + currentThumbSize := imgInfo.ExifData.TagByName['ThumbnailSize'].AsInteger; + CheckEquals(THUMBSIZE, Max(w, h), 'Thumbnailsize mismatch'); + CheckEquals(destStreamSize, currentThumbSize, 'Thumbnail size mismatch'); + + finally + imgInfo.Free; + end; +end; + + + +initialization + {$IFDEF FPC} + RegisterTest(TstExifLE); + {$ELSE} + TestFramework.RegisterTest(TstExifLE.Suite); + {$ENDIF} + +end. + diff --git a/components/fpexif/tests/unittest/common/fetiptc.pas b/components/fpexif/tests/unittest/common/fetiptc.pas new file mode 100644 index 000000000..ac620f4b3 --- /dev/null +++ b/components/fpexif/tests/unittest/common/fetiptc.pas @@ -0,0 +1,815 @@ +unit fetIptc; + +{$IFDEF FPC} + {$mode objfpc}{$H+} +{$ENDIF} + +interface + +uses + Classes, SysUtils, + {$ifdef FPC} + fpcunit, testutils, testregistry; + {$else} + fetTestUtils, TestFrameWork; + {$endif} + +const + // Picture with Exif data, jpeg and tiff + IptcJpegPic = '..\pictures\originals\with_iptc.jpg'; + IptcTiffPic = '..\pictures\originals\with_iptc.tif'; + WorkFile_JpegWithIptc = 'pictures\with_iptc.jpg'; + WorkFile_TiffWithIptc = 'pictures\with_iptc.tif'; + + // Picture without Iptc data + NoIptcPic = '..\pictures\originals\no_metadata.jpg'; + WorkFile_NoIptc = 'pictures\no_iptc.jpg'; + +type + TstIptc = class(TTestCase) + protected + procedure SetUp; override; + procedure TearDown; override; + procedure Internal_CheckHasIptc(AFileName: String; ExpectIptc: Boolean); + published + procedure CheckForPictures; + procedure CheckCreateImgInfo; + procedure CheckHasIptc; + procedure ReadIptcTest_Jpeg; + procedure ReadIptcTest_Tiff; + procedure CreateIptcTest; + procedure WriteIptcTest_Jpeg; + end; + +implementation + +uses + {$IFDEF FPC} + Graphics, FileUtil, + {$ELSE} + Graphics, Jpeg, + {$ENDIF} + fpeGlobal, fpeUtils, fpeTags, fpeIptcData, fpeMetadata; + +procedure TstIptc.SetUp; +var + dir: String; +begin + if FileExists(WorkFile_NoIptc) then + DeleteFile(WorkFile_NoIptc); + if FileExists(WorkFile_JpegWithIptc) then + DeleteFile(WorkFile_JpegWithIptc); + if FileExists(WorkFile_TiffWithIptc) then + DeleteFile(WorkFile_TiffWithIptc); + + dir := ExtractFileDir(WorkFile_JpegWithIptc); + if not DirectoryExists(dir) then + ForceDirectories(dir); + + if not FileExists(WorkFile_JpegWithIptc) then + if FileExists(IptcJpegPic) then + CopyFile(IptcJPegPic, WorkFile_JpegWithIptc); + if not FileExists(WorkFile_TiffWithIptc) then + if FileExists(IptcTiffPic) then + CopyFile(IptcTiffPic, WorkFile_TiffWithIptc); + if not FileExists(WorkFile_NoIptc) then + if FileExists(NoIptcPic) then + CopyFile(NoIptcPic, WorkFile_NoIptc); +end; + +procedure TstIptc.TearDown; +begin + if FileExists(WorkFile_NoIptc) then + DeleteFile(WorkFile_NoIptc); + if FileExists(WorkFile_JpegWithIptc) then + DeleteFile(WorkFile_JpegWithIptc); + if FileExists(WorkFile_TiffWithIptc) then + DeleteFile(WorkFile_TiffWithIptc); +end; + +procedure TstIptc.CheckForPictures; +begin + CheckTrue(FileExists(IptcJPegPic), 'Original test picture file "' + IptcJpegPic + '" does not exist'); + CheckTrue(FileExists(IptcTiffPic), 'Original test picture file "' + IptcTiffPic + '" does not exist'); + CheckTrue(FileExists(NoIptcPic), 'Original test picture file "' + NoIptcPic + '" does not exist'); + + CheckTrue(FileExists(WorkFile_JpegWithIptc), 'Test picture file "' + WorkFile_JpegWithIptc + '" does not exist'); + CheckTrue(FileExists(WorkFile_TiffWithIptc), 'Test picture file "' + WorkFile_TiffWithIptc + '" does not exist'); + CheckTrue(FileExists(WorkFile_NoIptc), 'Test picture file "' + WorkFile_NoIptc + '" does not exist'); +end; + +procedure TstIptc.CheckCreateImgInfo; +var + imgInfo: TImgInfo; +begin + imgInfo := TImgInfo.Create(); + try + CheckIs(imgInfo, TImgInfo, 'Is not TImgInfo'); + finally + imgInfo.Free; + end; +end; + +procedure TstIptc.Internal_CheckHasIptc(AFileName: String; ExpectIptc: Boolean); +var + imgInfo: TImgInfo; +begin + imgInfo := TImgInfo.Create; + try + imgInfo.LoadFromFile(AFileName); + if ExpectIptc then + CheckTrue(imgInfo.HasIptc, 'Failure to detect IPTC in test picture file "' + AFileName + '"') + else + CheckFalse(imgInfo.HasIptc, 'Unexected IPTC in test picture file "' + AFileName + '" detected'); + finally + imgInfo.Free; + end; +end; + +procedure TstIptc.CheckHasIptc; +begin + Internal_CheckHasIptc(WorkFile_JpegWithIptc, true); + Internal_CheckHasIptc(WorkFile_TiffWithIptc, true); + Internal_CheckHasIptc(WorkFile_NoIptc, false); +end; + +procedure TstIptc.ReadIptcTest_Jpeg; +{ Output of ExifTool for the test image with exif (using parameters -G -H -s): + (All these values are checked) + +[IPTC] 0x0005 ObjectName Title of the test image <-- ok +[IPTC] 0x0007 EditStatus finished <-- ok +[IPTC] 0x000a Urgency 5 (normal urgency) <-- ok +[IPTC] 0x000f Category TST <-- ok +[IPTC] 0x0016 FixtureIdentifier JobID_1 <-- is named "FixtureID" by fpExif +[IPTC] 0x0019 Keywords yellow, red, blue, green, rectangles <-- ok +[IPTC] 0x001a ContentLocationCode USA <-- ok +[IPTC] 0x001e ReleaseDate 2017:10:15 <-- ok +[IPTC] 0x0023 ReleaseTime 22:34:47 <-- ok +[IPTC] 0x0028 SpecialInstructions No other comments <-- is named "SpecialInstruct" by fpExif +[IPTC] 0x0037 DateCreated 2017:10:15 <-- ok +[IPTC] 0x003c TimeCreated 12:11:59 <-- ok +[IPTC] 0x0041 OriginatingProgram PhotoFiltre <-- ok +[IPTC] 0x0046 ProgramVersion 7 <-- ok +[IPTC] 0x004b ObjectCycle Both Morning and Evening <-- value is encoded as "both" by fpExif +[IPTC] 0x0050 By-line wp <-- ok +[IPTC] 0x0055 By-lineTitle Staff <-- ok +[IPTC] 0x005a City My hometown <-- ok +[IPTC] 0x005c Sub-location My suburb <-- is named "SubLocation" by fpExif +[IPTC] 0x005f Province-State My province <-- is named "State" by fpexif +[IPTC] 0x0064 Country-PrimaryLocationCode USA <-- is named "LocationCode" by fpExif +[IPTC] 0x0065 Country-PrimaryLocationName My country <-- is named "LocationName" by fpExif +[IPTC] 0x0067 OriginalTransmissionReference requested by myself <-- is named "TransmissionRef" by fpExif +[IPTC] 0x0069 Headline Test image <-- ok +[IPTC] 0x006e Credit FPC <-- is named "ImageCredit" by fpExif +[IPTC] 0x0073 Source self-made <-- ok +[IPTC] 0x0074 CopyrightNotice (c) wp <-- is named "Copyright" by fpExif +[IPTC] 0x0076 Contact w.p@wp.com, +123 4567890 <-- ok +[IPTC] 0x0078 Caption-Abstract Test image <-- is named "ImageCaption" by fpExif +[IPTC] 0x007a Writer-Editor wp <-- is named "ImageCaptionWriter by fpExif +} +var + imgInfo: TImgInfo; + lTag: TTag; +begin + imgInfo := TImgInfo.Create; + try + imgInfo.LoadFromFile(WorkFile_JpegWithIptc); + CheckTrue(imgInfo.HasIptc, 'IPTC in test picture file "' + WorkFile_JpegWithIptc + '" not found'); + + // The following pieces of information are obtained from the IPTC segment + + lTag := imgInfo.IptcData.TagByName['ObjectName']; + CheckTrue(lTag <> nil, 'Tag "ObjectName" not found'); + CheckEquals('Title of the test image', lTag.AsString, 'Value mismatch in tag "ObjectName"'); + + lTag := imgInfo.IptcData.TagByName['EditStatus']; + CheckTrue(lTag <> nil, 'Tag "EditStatus" not found'); + CheckEquals('finished', lTag.AsString, 'Value mismatch in tag "EditStatus"'); + + lTag := imgInfo.IptcData.TagByName['Urgency']; + CheckTrue(lTag <> nil, 'Tag "Urgency" not found'); + lTag.DecodeValue:= false; + CheckEquals('5', lTag.AsString, 'Value mismatch in tag "Urgency"'); + + lTag := imgInfo.IptcData.TagByName['Category']; + CheckTrue(lTag <> nil, 'Tag "Category" not found'); + CheckEquals('TST', lTag.AsString, 'Value mismatch in tag "Category"'); + + lTag := imgInfo.IptcData.TagByName['FixtureID']; + CheckTrue(lTag <> nil, 'Tag "FixtureID" not found'); + CheckEquals('JobID_1', lTag.AsString, 'Value mismatch in tag "FixtureID"'); + + lTag := imgInfo.IptcData.TagByName['Keywords']; + CheckTrue(lTag <> nil, 'Tag "Keywords" not found'); + CheckEquals('yellow, red, blue, green, rectangles', lTag.AsString, 'Value mismatch of tag "Keywords"'); + + lTag := imgInfo.IptcData.TagByName['ContentLocCode']; + CheckTrue(lTag <> nil, 'Tag "ContentLocCode" not found'); + CheckEquals('USA', lTag.AsString, 'Value mismatch of tag "ContentLocCode"'); + + lTag := imgInfo.IptcData.TagByName['ReleaseDate']; + CheckTrue(lTag <> nil, 'Tag "ReleaseDate" not found'); + CheckEquals(TIptcDateTag, lTag.ClassType, 'Tag "ReleaseDate" is not a TIptcDateTag.'); + TIptcDateTag(lTag).FormatStr := EXIF_DATE_FORMAT; + CheckEquals('2017:10:15', TIptcDateTag(lTag).AsString, 'Value mismatch of tag "ReleaseDate"'); + + lTag := imgInfo.IptcData.TagByName['ReleaseTime']; + CheckTrue(lTag <> nil, 'Tag "ReleaseTime" not found'); + CheckEquals(TIptcTimeTag, lTag.ClassType, 'Tag "ReleaseTime" is not a TIptcTimeTag'); + TIptcTimeTag(lTag).FormatStr := EXIF_TIME_FORMAT; + CheckEquals('22:34:47', TIptcTimeTag(lTag).AsString, 'Value mismatch of tag "ReleaseTime"'); + + lTag := imgInfo.IptcData.TagByName['SpecialInstruct']; + CheckTrue(lTag <> nil, 'Tag "SpecialInstruct" not found'); + CheckEquals('No other comments', lTag.AsString, 'Value mismatch in tag "SpecialInstruct"'); + + lTag := imgInfo.IptcData.TagByName['DateCreated']; + CheckTrue(lTag <> nil, 'Tag "DateCreated" not found'); + CheckEquals(TIptcDateTag, lTag.ClassType, 'Tag "DateCreated" is not a TIptcDateTag.'); + TIptcDateTag(lTag).FormatStr := EXIF_DATE_FORMAT; + CheckEquals('2017:10:15', TIptcDateTag(lTag).AsString, 'Value mismatch of tag "DateCreated"'); + + lTag := imgInfo.IptcData.TagByName['TimeCreated']; + CheckTrue(lTag <> nil, 'Tag "TimeCreated" not found'); + CheckEquals(TIptcTimeTag, lTag.ClassType, 'Tag "TimeCreated" is not a TIptcTimeTag'); + TIptcTimeTag(lTag).FormatStr := EXIF_TIME_FORMAT; + CheckEquals('12:11:59', TIptcTimeTag(lTag).AsString, 'Value mismatch of tag "TimeCreated"'); + + lTag := imgInfo.IptcData.TagByName['OriginatingProgram']; + CheckTrue(lTag <> nil, 'Tag "OriginatingProgram" not found'); + CheckEquals('PhotoFiltre', lTag.AsString, 'Value mismatch of tag "OriginatingProgram"'); + + lTag := imgInfo.IptcData.TagByName['ProgramVersion']; + CheckTrue(lTag <> nil, 'Tag "ProgramVersion" not found'); + CheckEquals('7', lTag.AsString, 'Value mismatch of tag "ProgramVersion"'); + + lTag := imgInfo.IptcData.TagByName['ObjectCycle']; + CheckTrue(lTag <> nil, 'Tag "ObjectCycle" not found'); + lTag.DecodeValue := true; + CheckEquals('both', lTag.AsString, 'Value mismatch of tag "ObjectCycle"'); + + lTag := imgInfo.IptcData.TagByName['ByLine']; + CheckTrue(lTag <> nil, 'Tag "ByLine" not found'); + CheckEquals('wp', lTag.AsString, 'Value mismatch of tag "ByLine"'); + + lTag := imgInfo.IptcData.TagByName['ByLineTitle']; + CheckTrue(lTag <> nil, 'Tag "ByLineTitle" not found'); + CheckEquals('Staff', lTag.AsString, 'Value mismatch of tag "ByLineTitle"'); + + lTag := imgInfo.IptcData.TagByName['City']; + CheckTrue(lTag <> nil, 'Tag "City" not found'); + CheckEquals('My hometown', lTag.AsString, 'Value mismatch of tag "City"'); + + lTag := imgInfo.IptcData.TagByName['SubLocation']; + CheckTrue(lTag <> nil, 'Tag "SubLocation" not found'); + CheckEquals('My suburb', lTag.AsString, 'Value mismatch of tag "SubLocation"'); + + lTag := imgInfo.IptcData.TagByName['State']; + CheckTrue(lTag <> nil, 'Tag "State" not found'); + CheckEquals('My province', lTag.AsString, 'Value mismatch of tag "State"'); + + lTag := imgInfo.IptcData.TagByName['LocationCode']; + CheckTrue(lTag <> nil, 'Tag "LocationCode" not found'); + CheckEquals('USA', lTag.AsString, 'Value mismatch of tag "LocationCode"'); + + lTag := imgInfo.IptcData.TagByName['LocationName']; + CheckTrue(lTag <> nil, 'Tag "LocationName" not found'); + CheckEquals('My country', lTag.AsString, 'Value mismatch of tag "LocationName"'); + + lTag := imgInfo.IptcData.TagByName['TransmissionRef']; + CheckTrue(lTag <> nil, 'Tag "TransmissionRef" not found'); + CheckEquals('requested by myself', lTag.AsString, 'Value mismatch of tag "TransmissionRef"'); + + lTag := imgInfo.IptcData.TagByName['ImageHeadline']; + CheckTrue(lTag <> nil, 'Tag "ImageHeadline" not found'); + CheckEquals('Test image', lTag.AsString, 'Value mismatch of tag "ImageHeadline"'); + + lTag := imgInfo.IptcData.TagByName['ImageCredit']; + CheckTrue(lTag <> nil, 'Tag "ImageCredit" not found'); + CheckEquals('FPC', lTag.AsString, 'Value mismatch of tag "ImageCredit"'); + + lTag := imgInfo.IptcData.TagByName['Source']; + CheckTrue(lTag <> nil, 'Tag "Source" not found'); + CheckEquals('self-made', lTag.AsString, 'Value mismatch of tag "Source"'); + + lTag := imgInfo.IptcData.TagByName['Copyright']; + CheckTrue(lTag <> nil, 'Tag "Copyright" not found'); + CheckEquals('(c) wp', lTag.AsString, 'Value mismatch of tag "Copyright"'); + + lTag := imgInfo.IptcData.TagByName['Contact']; + CheckTrue(lTag <> nil, 'Tag "Contact" not found'); + CheckEquals('w.p@wp.com, +123 4567890', lTag.AsString, 'Value mismatch of tag "Contact"'); + + lTag := imgInfo.IptcData.TagByName['ImageCaption']; + CheckTrue(lTag <> nil, 'Tag "ImageCaption" not found'); + CheckEquals('Test image', lTag.AsString, 'Value mismatch of tag "ImageCaption"'); + + lTag := imgInfo.IptcData.TagByName['ImageCaptionWriter']; + CheckTrue(lTag <> nil, 'Tag "ImageCaptionWriter" not found'); + CheckEquals('wp', lTag.AsString, 'Value mismatch of tag "ImageCaptionWriter"'); + + finally + imgInfo.Free; + end; +end; + +procedure TstIptc.ReadIptcTest_Tiff; +{ Output of ExifTool for the test image with IPTC + + exiftool -G -H -s with_iptc.tif > with_iptc_tif.txt + + (All these values are checked) + + [IPTC] 0x0005 ObjectName : Title of the test image + [IPTC] 0x0007 EditStatus : finished + [IPTC] 0x000a Urgency : 5 (normal urgency) + [IPTC] 0x000f Category : TST + [IPTC] 0x0016 FixtureIdentifier : JobID_1 + [IPTC] 0x0019 Keywords : yellow, red, blue, green, rectangles + [IPTC] 0x001a ContentLocationCode : USA + [IPTC] 0x001e ReleaseDate : 2017:10:15 + [IPTC] 0x0023 ReleaseTime : 22:34:47 + [IPTC] 0x0028 SpecialInstructions : No other comments + [IPTC] 0x0037 DateCreated : 2017:10:15 + [IPTC] 0x003c TimeCreated : 12:11:59 + [IPTC] 0x0041 OriginatingProgram : PhotoFiltre + [IPTC] 0x0046 ProgramVersion : 7 + [IPTC] 0x004b ObjectCycle : Both Morning and Evening + [IPTC] 0x0050 By-line : wp + [IPTC] 0x0055 By-lineTitle : Staff + [IPTC] 0x005a City : My hometown + [IPTC] 0x005c Sub-location : My suburb + [IPTC] 0x005f Province-State : My province + [IPTC] 0x0064 Country-PrimaryLocationCode : USA + [IPTC] 0x0065 Country-PrimaryLocationName : My country + [IPTC] 0x0067 OriginalTransmissionReference : requested by myself + [IPTC] 0x0069 Headline : Test image + [IPTC] 0x006e Credit : FPC + [IPTC] 0x0073 Source : self-made + [IPTC] 0x0074 CopyrightNotice : (c) wp + [IPTC] 0x0076 Contact : w.p@wp.com, +123 4567890 + [IPTC] 0x0078 Caption-Abstract : Test image + [IPTC] 0x007a Writer-Editor : wp +} +var + imgInfo: TImgInfo; + lTag: TTag; +begin + imgInfo := TImgInfo.Create; + try + imgInfo.LoadFromFile(WorkFile_TiffWithIptc); + CheckTrue(imgInfo.HasIptc, 'IPTC in test picture file "' + WorkFile_TiffWithIptc + '" not found'); + + // The following pieces of information are obtained from the IPTC segment + + lTag := imgInfo.IptcData.TagByName['ObjectName']; + CheckTrue(lTag <> nil, 'Tag "ObjectName" not found'); + CheckEquals('Title of the test image', lTag.AsString, 'Value mismatch in tag "ObjectName"'); + + lTag := imgInfo.IptcData.TagByName['EditStatus']; + CheckTrue(lTag <> nil, 'Tag "EditStatus" not found'); + CheckEquals('finished', lTag.AsString, 'Value mismatch in tag "EditStatus"'); + + lTag := imgInfo.IptcData.TagByName['Urgency']; + CheckTrue(lTag <> nil, 'Tag "Urgency" not found'); + lTag.DecodeValue:= false; + CheckEquals('5', lTag.AsString, 'Value mismatch in tag "Urgency"'); + + lTag := imgInfo.IptcData.TagByName['Category']; + CheckTrue(lTag <> nil, 'Tag "Category" not found'); + CheckEquals('TST', lTag.AsString, 'Value mismatch in tag "Category"'); + + lTag := imgInfo.IptcData.TagByName['FixtureID']; + CheckTrue(lTag <> nil, 'Tag "FixtureID" not found'); + CheckEquals('JobID_1', lTag.AsString, 'Value mismatch in tag "FixtureID"'); + + lTag := imgInfo.IptcData.TagByName['Keywords']; + CheckTrue(lTag <> nil, 'Tag "Keywords" not found'); + CheckEquals('yellow, red, blue, green, rectangles', lTag.AsString, 'Value mismatch of tag "Keywords"'); + + lTag := imgInfo.IptcData.TagByName['ContentLocCode']; + CheckTrue(lTag <> nil, 'Tag "ContentLocCode" not found'); + CheckEquals('USA', lTag.AsString, 'Value mismatch of tag "ContentLocCode"'); + + lTag := imgInfo.IptcData.TagByName['ReleaseDate']; + CheckTrue(lTag <> nil, 'Tag "ReleaseDate" not found'); + CheckEquals(TIptcDateTag, lTag.ClassType, 'Tag "ReleaseDate" is not a TIptcDateTag.'); + TIptcDateTag(lTag).FormatStr := EXIF_DATE_FORMAT; + CheckEquals('2017:10:15', TIptcDateTag(lTag).AsString, 'Value mismatch of tag "ReleaseDate"'); + + lTag := imgInfo.IptcData.TagByName['ReleaseTime']; + CheckTrue(lTag <> nil, 'Tag "ReleaseTime" not found'); + CheckEquals(TIptcTimeTag, lTag.ClassType, 'Tag "ReleaseTime" is not a TIptcTimeTag'); + TIptcTimeTag(lTag).FormatStr := EXIF_TIME_FORMAT; + CheckEquals('22:34:47', TIptcTimeTag(lTag).AsString, 'Value mismatch of tag "ReleaseTime"'); + + lTag := imgInfo.IptcData.TagByName['SpecialInstruct']; + CheckTrue(lTag <> nil, 'Tag "SpecialInstruct" not found'); + CheckEquals('No other comments', lTag.AsString, 'Value mismatch in tag "SpecialInstruct"'); + + lTag := imgInfo.IptcData.TagByName['DateCreated']; + CheckTrue(lTag <> nil, 'Tag "DateCreated" not found'); + CheckEquals(TIptcDateTag, lTag.ClassType, 'Tag "DateCreated" is not a TIptcDateTag.'); + TIptcDateTag(lTag).FormatStr := EXIF_DATE_FORMAT; + CheckEquals('2017:10:15', TIptcDateTag(lTag).AsString, 'Value mismatch of tag "DateCreated"'); + + lTag := imgInfo.IptcData.TagByName['TimeCreated']; + CheckTrue(lTag <> nil, 'Tag "TimeCreated" not found'); + CheckEquals(TIptcTimeTag, lTag.ClassType, 'Tag "TimeCreated" is not a TIptcTimeTag'); + TIptcTimeTag(lTag).FormatStr := EXIF_TIME_FORMAT; + CheckEquals('12:11:59', TIptcTimeTag(lTag).AsString, 'Value mismatch of tag "TimeCreated"'); + + lTag := imgInfo.IptcData.TagByName['OriginatingProgram']; + CheckTrue(lTag <> nil, 'Tag "OriginatingProgram" not found'); + CheckEquals('PhotoFiltre', lTag.AsString, 'Value mismatch of tag "OriginatingProgram"'); + + lTag := imgInfo.IptcData.TagByName['ProgramVersion']; + CheckTrue(lTag <> nil, 'Tag "ProgramVersion" not found'); + CheckEquals('7', lTag.AsString, 'Value mismatch of tag "ProgramVersion"'); + + lTag := imgInfo.IptcData.TagByName['ObjectCycle']; + CheckTrue(lTag <> nil, 'Tag "ObjectCycle" not found'); + lTag.DecodeValue := true; + CheckEquals('both', lTag.AsString, 'Value mismatch of tag "ObjectCycle"'); + + lTag := imgInfo.IptcData.TagByName['ByLine']; + CheckTrue(lTag <> nil, 'Tag "ByLine" not found'); + CheckEquals('wp', lTag.AsString, 'Value mismatch of tag "ByLine"'); + + lTag := imgInfo.IptcData.TagByName['ByLineTitle']; + CheckTrue(lTag <> nil, 'Tag "ByLineTitle" not found'); + CheckEquals('Staff', lTag.AsString, 'Value mismatch of tag "ByLineTitle"'); + + lTag := imgInfo.IptcData.TagByName['City']; + CheckTrue(lTag <> nil, 'Tag "City" not found'); + CheckEquals('My hometown', lTag.AsString, 'Value mismatch of tag "City"'); + + lTag := imgInfo.IptcData.TagByName['SubLocation']; + CheckTrue(lTag <> nil, 'Tag "SubLocation" not found'); + CheckEquals('My suburb', lTag.AsString, 'Value mismatch of tag "SubLocation"'); + + lTag := imgInfo.IptcData.TagByName['State']; + CheckTrue(lTag <> nil, 'Tag "State" not found'); + CheckEquals('My province', lTag.AsString, 'Value mismatch of tag "State"'); + + lTag := imgInfo.IptcData.TagByName['LocationCode']; + CheckTrue(lTag <> nil, 'Tag "LocationCode" not found'); + CheckEquals('USA', lTag.AsString, 'Value mismatch of tag "LocationCode"'); + + lTag := imgInfo.IptcData.TagByName['LocationName']; + CheckTrue(lTag <> nil, 'Tag "LocationName" not found'); + CheckEquals('My country', lTag.AsString, 'Value mismatch of tag "LocationName"'); + + lTag := imgInfo.IptcData.TagByName['TransmissionRef']; + CheckTrue(lTag <> nil, 'Tag "TransmissionRef" not found'); + CheckEquals('requested by myself', lTag.AsString, 'Value mismatch of tag "TransmissionRef"'); + + lTag := imgInfo.IptcData.TagByName['ImageHeadline']; + CheckTrue(lTag <> nil, 'Tag "ImageHeadline" not found'); + CheckEquals('Test image', lTag.AsString, 'Value mismatch of tag "ImageHeadline"'); + + lTag := imgInfo.IptcData.TagByName['ImageCredit']; + CheckTrue(lTag <> nil, 'Tag "ImageCredit" not found'); + CheckEquals('FPC', lTag.AsString, 'Value mismatch of tag "ImageCredit"'); + + lTag := imgInfo.IptcData.TagByName['Source']; + CheckTrue(lTag <> nil, 'Tag "Source" not found'); + CheckEquals('self-made', lTag.AsString, 'Value mismatch of tag "Source"'); + + lTag := imgInfo.IptcData.TagByName['Copyright']; + CheckTrue(lTag <> nil, 'Tag "Copyright" not found'); + CheckEquals('(c) wp', lTag.AsString, 'Value mismatch of tag "Copyright"'); + + lTag := imgInfo.IptcData.TagByName['Contact']; + CheckTrue(lTag <> nil, 'Tag "Contact" not found'); + CheckEquals('w.p@wp.com, +123 4567890', lTag.AsString, 'Value mismatch of tag "Contact"'); + + lTag := imgInfo.IptcData.TagByName['ImageCaption']; + CheckTrue(lTag <> nil, 'Tag "ImageCaption" not found'); + CheckEquals('Test image', lTag.AsString, 'Value mismatch of tag "ImageCaption"'); + + lTag := imgInfo.IptcData.TagByName['ImageCaptionWriter']; + CheckTrue(lTag <> nil, 'Tag "ImageCaptionWriter" not found'); + CheckEquals('wp', lTag.AsString, 'Value mismatch of tag "ImageCaptionWriter"'); + + finally + imgInfo.Free; + end; +end; + +procedure TstIptc.CreateIptcTest; +var + imgInfo: TImgInfo; +begin + imgInfo := TImgInfo.Create; + try + CheckTrue(imgInfo.IptcData = nil, 'IPTC found, but not expected.'); + imgInfo.CreateIptcData; + CheckTrue(imgInfo.IptcData <> nil, 'IPTC not found.'); + finally + imgInfo.Free; + end; +end; + +procedure TstIptc.WriteIptcTest_Jpeg; +var + imgInfo: TImgInfo; + lTag: TTag; +begin + imgInfo := TImgInfo.Create; + try + // Create empty IPTC + imgInfo.CreateIptcData; + + // Add tags + lTag := imgInfo.IptcData.AddTagByName('ObjectName'); + CheckTrue(lTag <> nil, 'Tag "ObjectName" not found for writing'); + lTag.AsString := 'Title of the test image'; + + lTag := imgInfo.IptcData.AddTagByName('EditStatus'); + CheckTrue(lTag <> nil, 'Tag "EditStatus" not found for writing'); + lTag.AsString := 'finished'; + + lTag := imgInfo.IptcData.AddTagByName('Urgency'); + CheckTrue(lTag <> nil, 'Tag "Urgency" not found'); + lTag.DecodeValue:= false; + lTag.AsString := '5'; + + lTag := imgInfo.IptcData.AddTagByName('Category'); + CheckTrue(lTag <> nil, 'Tag "Category" not found'); + lTag.AsString := 'TST'; + + lTag := imgInfo.IptcData.AddTagByName('FixtureID'); + CheckTrue(lTag <> nil, 'Tag "FixtureID" not found'); + lTag.AsString := 'JobID_1'; + + lTag := imgInfo.IptcData.AddTagByName('Keywords'); + CheckTrue(lTag <> nil, 'Tag "Keywords" not found'); + lTag.AsString := 'yellow, red, blue, green, rectangles'; + + lTag := imgInfo.IptcData.AddTagByName('ContentLocCode'); + CheckTrue(lTag <> nil, 'Tag "ContentLocCode" not found'); + lTag.AsString := 'USA'; + + lTag := imgInfo.IptcData.AddTagByName('ReleaseDate'); + CheckTrue(lTag <> nil, 'Tag "ReleaseDate" not found'); + CheckEquals(TIptcDateTag, lTag.ClassType, 'Tag "ReleaseDate" is not a TIptcDateTag.'); + TIptcDateTag(lTag).FormatStr := EXIF_DATE_FORMAT; + lTag.AsString := '2017:10:15'; + + lTag := imgInfo.IptcData.AddTagByName('ReleaseTime'); + CheckTrue(lTag <> nil, 'Tag "ReleaseTime" not found'); + CheckEquals(TIptcTimeTag, lTag.ClassType, 'Tag "ReleaseTime" is not a TIptcTimeTag'); + TIptcTimeTag(lTag).FormatStr := EXIF_TIME_FORMAT; + lTag.AsString := '22:34:47'; + + lTag := imgInfo.IptcData.AddTagByName('SpecialInstruct'); + CheckTrue(lTag <> nil, 'Tag "SpecialInstruct" not found'); + lTag.AsString := 'No other comments'; + + lTag := imgInfo.IptcData.AddTagByName('DateCreated'); + CheckTrue(lTag <> nil, 'Tag "DateCreated" not found'); + CheckEquals(TIptcDateTag, lTag.ClassType, 'Tag "DateCreated" is not a TIptcDateTag.'); + TIptcDateTag(lTag).FormatStr := EXIF_DATE_FORMAT; + lTag.AsString := '2017:10:15'; + + lTag := imgInfo.IptcData.AddTagByName('TimeCreated'); + CheckTrue(lTag <> nil, 'Tag "TimeCreated" not found'); + CheckEquals(TIptcTimeTag, lTag.ClassType, 'Tag "TimeCreated" is not a TIptcTimeTag'); + TIptcTimeTag(lTag).FormatStr := EXIF_TIME_FORMAT; + lTag.AsString := '12:11:59'; + + lTag := imgInfo.IptcData.AddTagByName('OriginatingProgram'); + CheckTrue(lTag <> nil, 'Tag "OriginatingProgram" not found'); + lTag.AsString := 'PhotoFiltre'; + + lTag := imgInfo.IptcData.AddTagByName('ProgramVersion'); + CheckTrue(lTag <> nil, 'Tag "ProgramVersion" not found'); + lTag.AsString := '7'; + + lTag := imgInfo.IptcData.AddTagByName('ObjectCycle'); + CheckTrue(lTag <> nil, 'Tag "ObjectCycle" not found'); + lTag.DecodeValue := true; + lTag.AsString := 'both'; + + lTag := imgInfo.IptcData.AddTagByName('ByLine'); + CheckTrue(lTag <> nil, 'Tag "ByLine" not found'); + lTag.AsString := 'wp'; + + lTag := imgInfo.IptcData.AddTagByName('ByLineTitle'); + CheckTrue(lTag <> nil, 'Tag "ByLineTitle" not found'); + lTag.AsString := 'Staff'; + + lTag := imgInfo.IptcData.AddTagByName('City'); + CheckTrue(lTag <> nil, 'Tag "City" not found'); + lTag.AsString := 'My hometown'; + + lTag := imgInfo.IptcData.AddTagByName('SubLocation'); + CheckTrue(lTag <> nil, 'Tag "SubLocation" not found'); + lTag.AsString := 'My suburb'; + + lTag := imgInfo.IptcData.AddTagByName('State'); + CheckTrue(lTag <> nil, 'Tag "State" not found'); + lTag.AsString := 'My province'; + + lTag := imgInfo.IptcData.AddTagByName('LocationCode'); + CheckTrue(lTag <> nil, 'Tag "LocationCode" not found'); + lTag.AsString := 'USA'; + + lTag := imgInfo.IptcData.AddTagByName('LocationName'); + CheckTrue(lTag <> nil, 'Tag "LocationName" not found'); + lTag.AsString := 'My country'; + + lTag := imgInfo.IptcData.AddTagByName('TransmissionRef'); + CheckTrue(lTag <> nil, 'Tag "TransmissionRef" not found'); + lTag.AsString := 'requested by myself'; + + lTag := imgInfo.IptcData.AddTagByName('ImageHeadline'); + CheckTrue(lTag <> nil, 'Tag "ImageHeadline" not found'); + lTag.AsString := 'Test image'; + + lTag := imgInfo.IptcData.AddTagByName('ImageCredit'); + CheckTrue(lTag <> nil, 'Tag "ImageCredit" not found'); + lTag.AsString := 'FPC'; + + lTag := imgInfo.IptcData.AddTagByName('Source'); + CheckTrue(lTag <> nil, 'Tag "Source" not found'); + lTag.AsString := 'self-made'; + + lTag := imgInfo.IptcData.AddTagByName('Copyright'); + CheckTrue(lTag <> nil, 'Tag "Copyright" not found'); + lTag.AsString := '(c) wp'; + + lTag := imgInfo.IptcData.AddTagByName('Contact'); + CheckTrue(lTag <> nil, 'Tag "Contact" not found'); + lTag.AsString := 'w.p@wp.com, +123 4567890'; + + lTag := imgInfo.IptcData.AddTagByName('ImageCaption'); + CheckTrue(lTag <> nil, 'Tag "ImageCaption" not found'); + lTag.AsString := 'Test image'; + + lTag := imgInfo.IptcData.AddTagByName('ImageCaptionWriter'); + CheckTrue(lTag <> nil, 'Tag "ImageCaptionWriter" not found'); + lTag.AsString := 'wp'; + + // Save to file; + // Takes the image data from WorkFile_WithIptc, replaces its IPTC with the + // current IPTC structure and writes to WorkFile_NoIptc. + imgInfo.SaveToFile(WorkFile_NoIptc, Workfile_JpegWithIptc); + finally + imgInfo.Free; + end; + + // Read written file and check IPTC + imgInfo := TImgInfo.Create; + try + imgInfo.LoadFromFile(Workfile_NoIptc); + // Now there should be IPTC + CheckTrue(imgInfo.IptcData <> nil, 'IPTC not found.'); + + lTag := imgInfo.IptcData.TagByName['ObjectName']; + CheckTrue(lTag <> nil, 'Tag "ObjectName" not found for reading'); + CheckEquals('Title of the test image', lTag.AsString, 'Value mismatch in tag "ObjectName"'); + + lTag := imgInfo.IptcData.TagByName['EditStatus']; + CheckTrue(lTag <> nil, 'Tag "EditStatus" not found for reading'); + CheckEquals('finished', lTag.AsString, 'Value mismatch in tag "EditStatus"'); + + lTag := imgInfo.IptcData.TagByName['Urgency']; + CheckTrue(lTag <> nil, 'Tag "Urgency" not found'); + lTag.DecodeValue:= false; + CheckEquals('5', lTag.AsString, 'Value mismatch in tag "Urgency"'); + + lTag := imgInfo.IptcData.TagByName['Category']; + CheckTrue(lTag <> nil, 'Tag "Category" not found'); + CheckEquals('TST', lTag.AsString, 'Value mismatch in tag "Category"'); + + lTag := imgInfo.IptcData.TagByName['FixtureID']; + CheckTrue(lTag <> nil, 'Tag "FixtureID" not found'); + CheckEquals('JobID_1', lTag.AsString, 'Value mismatch in tag "FixtureID"'); + + lTag := imgInfo.IptcData.TagByName['Keywords']; + CheckTrue(lTag <> nil, 'Tag "Keywords" not found'); + CheckEquals('yellow, red, blue, green, rectangles', lTag.AsString, 'Value mismatch of tag "Keywords"'); + + lTag := imgInfo.IptcData.TagByName['ContentLocCode']; + CheckTrue(lTag <> nil, 'Tag "ContentLocCode" not found'); + CheckEquals('USA', lTag.AsString, 'Value mismatch of tag "ContentLocCode"'); + + lTag := imgInfo.IptcData.TagByName['ReleaseDate']; + CheckTrue(lTag <> nil, 'Tag "ReleaseDate" not found'); + CheckEquals(TIptcDateTag, lTag.ClassType, 'Tag "ReleaseDate" is not a TIptcDateTag.'); + TIptcDateTag(lTag).FormatStr := EXIF_DATE_FORMAT; + CheckEquals('2017:10:15', TIptcDateTag(lTag).AsString, 'Value mismatch of tag "ReleaseDate"'); + + lTag := imgInfo.IptcData.TagByName['ReleaseTime']; + CheckTrue(lTag <> nil, 'Tag "ReleaseTime" not found'); + CheckEquals(TIptcTimeTag, lTag.ClassType, 'Tag "ReleaseTime" is not a TIptcTimeTag'); + TIptcTimeTag(lTag).FormatStr := EXIF_TIME_FORMAT; + CheckEquals('22:34:47', TIptcTimeTag(lTag).AsString, 'Value mismatch of tag "ReleaseTime"'); + + lTag := imgInfo.IptcData.TagByName['SpecialInstruct']; + CheckTrue(lTag <> nil, 'Tag "SpecialInstruct" not found'); + CheckEquals('No other comments', lTag.AsString, 'Value mismatch in tag "SpecialInstruct"'); + + lTag := imgInfo.IptcData.TagByName['DateCreated']; + CheckTrue(lTag <> nil, 'Tag "DateCreated" not found'); + CheckEquals(TIptcDateTag, lTag.ClassType, 'Tag "DateCreated" is not a TIptcDateTag.'); + TIptcDateTag(lTag).FormatStr := EXIF_DATE_FORMAT; + CheckEquals('2017:10:15', TIptcDateTag(lTag).AsString, 'Value mismatch of tag "DateCreated"'); + + lTag := imgInfo.IptcData.TagByName['TimeCreated']; + CheckTrue(lTag <> nil, 'Tag "TimeCreated" not found'); + CheckEquals(TIptcTimeTag, lTag.ClassType, 'Tag "TimeCreated" is not a TIptcTimeTag'); + TIptcTimeTag(lTag).FormatStr := EXIF_TIME_FORMAT; + CheckEquals('12:11:59', TIptcTimeTag(lTag).AsString, 'Value mismatch of tag "TimeCreated"'); + + lTag := imgInfo.IptcData.TagByName['OriginatingProgram']; + CheckTrue(lTag <> nil, 'Tag "OriginatingProgram" not found'); + CheckEquals('PhotoFiltre', lTag.AsString, 'Value mismatch of tag "OriginatingProgram"'); + + lTag := imgInfo.IptcData.TagByName['ProgramVersion']; + CheckTrue(lTag <> nil, 'Tag "ProgramVersion" not found'); + CheckEquals('7', lTag.AsString, 'Value mismatch of tag "ProgramVersion"'); + + lTag := imgInfo.IptcData.TagByName['ObjectCycle']; + CheckTrue(lTag <> nil, 'Tag "ObjectCycle" not found'); + lTag.DecodeValue := true; + CheckEquals('both', lTag.AsString, 'Value mismatch of tag "ObjectCycle"'); + + lTag := imgInfo.IptcData.TagByName['ByLine']; + CheckTrue(lTag <> nil, 'Tag "ByLine" not found'); + CheckEquals('wp', lTag.AsString, 'Value mismatch of tag "ByLine"'); + + lTag := imgInfo.IptcData.TagByName['ByLineTitle']; + CheckTrue(lTag <> nil, 'Tag "ByLineTitle" not found'); + CheckEquals('Staff', lTag.AsString, 'Value mismatch of tag "ByLineTitle"'); + + lTag := imgInfo.IptcData.TagByName['City']; + CheckTrue(lTag <> nil, 'Tag "City" not found'); + CheckEquals('My hometown', lTag.AsString, 'Value mismatch of tag "City"'); + + lTag := imgInfo.IptcData.TagByName['SubLocation']; + CheckTrue(lTag <> nil, 'Tag "SubLocation" not found'); + CheckEquals('My suburb', lTag.AsString, 'Value mismatch of tag "SubLocation"'); + + lTag := imgInfo.IptcData.TagByName['State']; + CheckTrue(lTag <> nil, 'Tag "State" not found'); + CheckEquals('My province', lTag.AsString, 'Value mismatch of tag "State"'); + + lTag := imgInfo.IptcData.TagByName['LocationCode']; + CheckTrue(lTag <> nil, 'Tag "LocationCode" not found'); + CheckEquals('USA', lTag.AsString, 'Value mismatch of tag "LocationCode"'); + + lTag := imgInfo.IptcData.TagByName['LocationName']; + CheckTrue(lTag <> nil, 'Tag "LocationName" not found'); + CheckEquals('My country', lTag.AsString, 'Value mismatch of tag "LocationName"'); + + lTag := imgInfo.IptcData.TagByName['TransmissionRef']; + CheckTrue(lTag <> nil, 'Tag "TransmissionRef" not found'); + CheckEquals('requested by myself', lTag.AsString, 'Value mismatch of tag "TransmissionRef"'); + + lTag := imgInfo.IptcData.TagByName['ImageHeadline']; + CheckTrue(lTag <> nil, 'Tag "ImageHeadline" not found'); + CheckEquals('Test image', lTag.AsString, 'Value mismatch of tag "ImageHeadline"'); + + lTag := imgInfo.IptcData.TagByName['ImageCredit']; + CheckTrue(lTag <> nil, 'Tag "ImageCredit" not found'); + CheckEquals('FPC', lTag.AsString, 'Value mismatch of tag "ImageCredit"'); + + lTag := imgInfo.IptcData.TagByName['Source']; + CheckTrue(lTag <> nil, 'Tag "Source" not found'); + CheckEquals('self-made', lTag.AsString, 'Value mismatch of tag "Source"'); + + lTag := imgInfo.IptcData.TagByName['Copyright']; + CheckTrue(lTag <> nil, 'Tag "Copyright" not found'); + CheckEquals('(c) wp', lTag.AsString, 'Value mismatch of tag "Copyright"'); + + lTag := imgInfo.IptcData.TagByName['Contact']; + CheckTrue(lTag <> nil, 'Tag "Contact" not found'); + CheckEquals('w.p@wp.com, +123 4567890', lTag.AsString, 'Value mismatch of tag "Contact"'); + + lTag := imgInfo.IptcData.TagByName['ImageCaption']; + CheckTrue(lTag <> nil, 'Tag "ImageCaption" not found'); + CheckEquals('Test image', lTag.AsString, 'Value mismatch of tag "ImageCaption"'); + + lTag := imgInfo.IptcData.TagByName['ImageCaptionWriter']; + CheckTrue(lTag <> nil, 'Tag "ImageCaptionWriter" not found'); + CheckEquals('wp', lTag.AsString, 'Value mismatch of tag "ImageCaptionWriter"'); + + finally + imgInfo.Free; + end; +end; + + +initialization + {$IFDEF FPC} + RegisterTest(TstIptc); + {$ELSE} + TestFramework.RegisterTest(TstIptc.Suite); + {$ENDIF} + +end. + diff --git a/components/fpexif/tests/unittest/common/fettestutils.pas b/components/fpexif/tests/unittest/common/fettestutils.pas new file mode 100644 index 000000000..0cb9f3b62 --- /dev/null +++ b/components/fpexif/tests/unittest/common/fettestutils.pas @@ -0,0 +1,33 @@ +unit fetTestUtils; + +{$IFDEF FPC} + {$mode objfpc}{$H+} +{$ENDIF} + +interface + +uses + {$IFDEF FPC} + {$ELSE} + Windows, + {$ENDIF} + Classes, SysUtils; + + +{$IFDEF FPC} +{$ELSE} +function CopyFile(AFilename1, AFileName2: String): Boolean; +{$ENDIF} + +implementation + +{$IFDEF FPC} +{$ELSE} +function CopyFile(AFileName1, AFileName2: String): Boolean; +begin + Result := Windows.CopyFile(PChar(AFilename1), PChar(AFilename2), true); +end; +{$ENDIF} + +end. + diff --git a/components/fpexif/tests/unittest/common/fetutils.pas b/components/fpexif/tests/unittest/common/fetutils.pas new file mode 100644 index 000000000..200944648 --- /dev/null +++ b/components/fpexif/tests/unittest/common/fetutils.pas @@ -0,0 +1,327 @@ +unit fetUtils; + +{$IFDEF FPC} + {$mode delphi} //objfpc}{$H+} +{$ENDIF} + +interface + +uses + Classes, SysUtils, + {$IFDEF FPC} + fpcunit, testutils, testregistry; + {$ELSE} + TestFrameWork; + {$ENDIF} + +type + TstUtils = class(TTestCase) + published + procedure TestCountChar; + procedure TestFloatToRational; + procedure TestInsertSpaces; + procedure TestLookup; + procedure TestSplit; + procedure TestSplitGPS; + procedure TestStrToGPS; + procedure TestStrToRational; + end; + +implementation + +uses + math, + fpeGlobal, fpeUtils; + +type + TCountCharParam = record + TestString: String; + ch: Char; + Count: Integer; + end; + + TInsertSpacesParam = record + TestString: String; + ResultString: String; + end; + + TFloatToRationalParam = record + Value, Precision: Double; + Num, Denom: Integer; + Error: Integer; // 0:ok, 1: Num is wrong, 2: Denom is wrong + end; + + TLookupParam = record + SearchForKey: Boolean; + SearchStr: String; + ResultStr: String; + end; + + TSplitGpsParam = record + Value: Double; + Degs: Double; + Mins: Double; + Secs: Double + end; + + TSplitParam = record + Text: String; + Sep: String; + NumParts: Integer; + Parts: Array[0..2] of string; + end; + + TStrToGpsParam = record + Text: String; + Degs: Double; + Valid: Boolean; + end; + + TStrToRationalParam = record + Value: String; + Num, Denom: Integer; + end; + +const + CountCharParams: array[0..5] of TCountCharParam = ( + (TestString:''; ch:'a'; Count:0), + (TestString:'a'; ch:'a'; Count:1), + (TestString:'aa'; ch:'a'; Count:2), + (TestString:'b'; ch:'a'; Count:0), + (TestString:'ab'; ch:'a'; Count:1), + (TestString:'ba'; ch:'a'; Count:1) + ); + + InsertSpacesParams: array[0..14] of TInsertSpacesParam = ( + (TestString: 'Artist'; ResultString: 'Artist'), + (TestString: 'ShutterSpeed'; ResultString: 'Shutter Speed'), + (TestString: 'ThumbnailXResolution'; ResultString: 'Thumbnail X Resolution'), + (TestString: 'YCbCrPositioning'; ResultString: 'Y Cb Cr Positioning'), + (TestString: 'ISO'; ResultString: 'ISO'), + (TestString: 'GPSInfo'; ResultString: 'GPS Info'), + (TestString: 'IPTC/NAA'; ResultString: 'IPTC/NAA'), + (TestString: 'XPTitle'; ResultString: 'XP Title'), + (TestString: 'PrintIM'; ResultString: 'Print IM'), + (TestString: 'ResolutionX'; ResultString: 'Resolution X'), + (TestString: 'XResolution'; ResultString: 'X Resolution'), + (TestString: 'CCD ISO'; ResultString: 'CCD ISO'), + (TestString: 'AE setting'; ResultString: 'AE setting'), + (TestString: 'abc ABC'; ResultString: 'abc ABC'), + (TestString: 'abc Abc'; ResultString: 'abc Abc') + ); + + FloatToRationalParams: array[0..8] of TFloatToRationalParam = ( + (Value:0.0; Precision: 1E-6; Num:0; Denom:1; Error:0), // 0 + (Value:1.0; Precision: 1E-6; Num:1; Denom:1; Error:0), // 1 + (Value:0.5; Precision: 1E-6; Num:1; Denom:2; Error:0), // 2 + (Value:0.01; Precision: 1E-6; Num:1; Denom:100; Error:0), // 3 + (Value:0.333333333; Precision: 1E-6; Num:1; Denom:3; Error:0), // 4 + (value:1.166666667; Precision: 1E-6; Num:7; Denom:6; Error:0), // 5 + (Value:NaN; Precision: 1E-6; Num:1; Denom:0; Error:0), // 6 + (Value:0.3333; Precision: 1E-6; Num:1; Denom:3; Error:2), // 7 + (Value:0.1; Precision: 1E-6; Num:1; Denom:3; Error:2) // 8 + ); + + LkupTbl: String = '0:Zero,1:One,2:Two'; + LookupParams: array[0..8] of TLookupParam = ( + (SearchForKey:true; SearchStr:'0'; ResultStr:'Zero'), + (SearchForKey:true; SearchStr:'1'; ResultStr:'One'), + (SearchForKey:true; SearchStr:'2'; ResultStr:'Two'), + (SearchForKey:true; SearchStr:'$2'; ResultStr:'Two'), + (SearchForKey:true; SearchStr:'3'; ResultStr:'3'), + (SearchForKey:false; SearchStr:'Zero'; ResultStr:'0'), + (SearchForKey:false; SearchStr:'One'; ResultStr:'1'), + (SearchForKey:false; SearchStr:'Two'; ResultStr:'2'), + (SearchForKey:false; SearchStr:'Three'; ResultStr:'') + ); + + SplitGpsParams: array[0..3] of TSplitGpsParam = ( + (Value:0.5; Degs: 0; Mins:30; Secs: 0), + (Value:2.777777E-4; Degs: 0; Mins: 0; Secs: 1), + (Value:50.2527777777777; Degs:50; Mins:15; Secs:10), + (Value:50.2583333333333; Degs:50; Mins:15; Secs:30) + ); + + SplitParams: array[0..3] of TSplitParam = ( + (Text:'One'; Sep: ';'; NumParts: 1; Parts:('One', '', '')), + (Text:'One,Two'; Sep: ','; NumParts: 2; Parts:('One', 'Two', '')), + (Text:'One, Two'; Sep: ', '; NumParts: 2; Parts:('One', 'Two', '')), + (Text:'One'#0'Two'; Sep: #0; NumParts: 2; Parts:('One', 'Two', '')) + ); + + // 1/3600 = 2.77777777777E-4, 1/60 = 0,01666666666666667 + StrToGpsParams: array[0..11] of TStrToGpsParam = ( + (Text:'0 deg 30'' 0"'; Degs: 0.5; Valid: true), + (Text:'0 deg 0'' 1"'; Degs: 2.777777E-4; Valid: true), + (Text:'50 deg 15'' 10"'; Degs: 50.2527777777777; Valid: true), + (Text:'50 deg 15'' 30"'; Degs: 50.2583333333333; Valid: true), + (Text:'50 deg 15.5'''; Degs: 50.2583333333333; Valid: true), + (Text:'50 deg 60'' 30"'; Degs: NaN; Valid: false), + (Text:'50 deg 15'' 70"'; Degs: NaN; Valid: false), + (Text:'50.1° 15'' 70"'; Degs: NaN; Valid: false), + (Text:'50 deg 15.3'' 50"'; Degs: NaN; Valid: false), + (Text:'50 deg -15'' 50"'; Degs: NaN; Valid: false), + (Text:'50 deg 15'' -50"'; Degs: NaN; Valid: false), + (Text:'-50 deg 15'' 30"'; Degs: 50.2583333333333; Valid: true) + ); + + StrToRationalParams: array[0..9] of TStrToRationalParam = ( + (Value:'0'; Num:0; Denom:1), // 0 + (Value:'1'; Num:1; Denom:1), // 1 + (Value:'1/2'; Num:1; Denom:2), // 2 + (Value:'1/ 2'; Num:1; Denom:2), // 3 + (Value:'1 /2'; Num:1; Denom:2), // 4 + (Value:'1 / 2'; Num:1; denom:2), // 5 + (Value:' 1/2'; Num:1; Denom:2), // 6 + (Value:'1/2 '; Num:1; Denom:2), // 7 + (Value:' 1/2 '; Num:1; Denom:2), // 8 + (value:''; Num:1; Denom:0) // 9 + ); + +procedure TstUtils.TestCountChar; +var + currCount: Integer; + i: Integer; +begin + for i:=Low(CountCharParams) to High(CountCharParams) do begin + currCount := CountChar(CountCharParams[i].ch, CountCharParams[i].TestString); + CheckEquals(CountCharParams[i].Count, currCount, + 'CountChar mismatch, test case ' + IntToStr(i)); + end; +end; + +procedure TstUtils.TestFloatToRational; +var + currR: TExifRational; + i: Integer; +begin + for i:=Low(FloatToRationalParams) to High(FloatToRationalParams) do + with FloatToRationalParams[i] do begin + currR := FloatToRational(Value, Precision); + case Error of + 0: begin + CheckEquals(currR.Numerator, Num, + 'FloatToRational numerator mismatch, test case ' + IntToStr(i)); + CheckEquals(currR.Denominator, Denom, + 'FloatToRational denominator mismatch, test case ' + IntToStr(i)); + end; + 1: CheckNotEquals(currR.Numerator, Num, + 'Unexpected FloatToRational numerator match, test case ' + IntToStr(i)); + 2: CheckNotEquals(currR.Denominator, Denom, + 'Unexpected FloatToRational denominator match, test case ' + IntToStr(i)); + end; + end; +end; + +procedure TstUtils.TestInsertSpaces; +var + currStr: String; + i: Integer; +begin + for i:=Low(InsertSpacesParams) to High(InsertSpacesParams) do begin + currStr := InsertSpaces(InsertSpacesParams[i].TestString); + CheckEquals(InsertSpacesParams[i].ResultString, currStr, + 'InsertSpaces mismatch, test case ' + IntToStr(i)); + end; +end; + +function SameIntegerKey(AKey1, AKey2: String): Boolean; +var + k1, k2: Integer; +begin + Result := TryStrToInt(AKey1, k1) and TryStrToInt(AKey2, k2) and (k1 = k2); +end; + +function SameStringKey(AKey1, AKey2: String): Boolean; +begin + Result := SameText(AKey1, AKey2); +end; + +procedure TstUtils.TestLookup; +var + currResult: String; + i: Integer; +begin + for i:=Low(LookupParams) to High(LookupParams) do + with LookupParams[i] do begin + if SearchForKey then + currResult := LookupValue(SearchStr, LkupTbl, @SameIntegerKey) + else + currResult := LookupKey(SearchStr, LkupTbl, @SameStringKey); + CheckEquals(ResultStr, currResult, + 'Lookup mismatch, test case ' + IntToStr(i)); + end; +end; + +procedure TstUtils.TestSplit; +var + currResult: TStringArray; + i, j: Integer; +begin + for i:=Low(SplitParams) to High(SplitParams) do + with SplitParams[i] do begin + currResult := Split(Text, Sep); + CheckEquals(NumParts, Length(currResult), 'Split count mismatch'); + for j:=0 to NumParts-1 do + CheckEquals(Parts[j], currResult[j], 'Split mismatch in array element #' + IntToStr(j)); + end; +end; + +procedure TstUtils.TestSplitGPS; +const + EPS = 1E-6; +var + currDeg, currMin, currSec: Double; + i: Integer; +begin + for i:=Low(SplitGPSParams) to High(SplitGPSParams) do + with SplitGPSParams[i] do begin + SplitGPS(Value, currDeg, currMin, currSec); + CheckEquals(Degs, currDeg, EPS, 'Degree value mismatch, test case ' + IntToStr(i)); + CheckEquals(Mins, currMin, EPS, 'Minutes mismatch, test case ' + IntToStr(i)); + CheckEquals(Secs, currSec, EPS, 'Seconds value mismatch, test case ' + IntToStr(i)); + end; +end; + +procedure TstUtils.TestStrToGPS; +const + EPS = 1E-8; +var + currDeg: Double; + i: Integer; + currOK: Boolean; +begin + for i:=Low(StrToGpsParams) to High(StrToGpsParams) do begin + with StrToGpsParams[i] do begin + currOK := TryStrToGps(Text, currDeg); + CheckEquals(Valid, currOK, 'GPS result validity mismatch, test case ' + IntToStr(i)); + if Valid then + CheckEquals(Degs, currDeg, EPS, 'GPS degress mismatch, test case ' + IntToStr(i)); + end; + end; +end; + +procedure TstUtils.TestStrToRational; +var + currR: TExifRational; + i: Integer; +begin + for i:=Low(StrToRationalParams) to High(StrToRationalParams) do + with StrToRationalParams[i] do begin + currR := StrToRational(Value); + CheckEquals(currR.Numerator, Num, + 'StrToRational numerator mismatch, test case ' + IntToStr(i)); + CheckEquals(currR.Denominator, Denom, + 'StrToRational denominator mismatch, test case ' + IntToStr(i)); + end; +end; + +initialization + {$IFDEF FPC} + RegisterTest(TstUtils); + {$ELSE} + TestFramework.RegisterTest(TstUtils.Suite); + {$ENDIF} + +end. + diff --git a/components/fpexif/tests/unittest/dunit/readme.txt b/components/fpexif/tests/unittest/dunit/readme.txt new file mode 100644 index 000000000..8ad7f509e --- /dev/null +++ b/components/fpexif/tests/unittest/dunit/readme.txt @@ -0,0 +1,3 @@ +For Delphi 7: +Download dunit from http://dunit.sourceforge.net/ +and copy the files into this folder. \ No newline at end of file diff --git a/components/fpexif/tests/unittest/fpExifTests.ico b/components/fpexif/tests/unittest/fpExifTests.ico new file mode 100644 index 0000000000000000000000000000000000000000..0341321b5d952e1662a3d9444a73cf9f42a7db37 GIT binary patch literal 137040 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 G`+orYGy**U literal 0 HcmV?d00001 diff --git a/components/fpexif/tests/unittest/fpExifTests.lpi b/components/fpexif/tests/unittest/fpExifTests.lpi new file mode 100644 index 000000000..ff606370b --- /dev/null +++ b/components/fpexif/tests/unittest/fpExifTests.lpi @@ -0,0 +1,163 @@ +<?xml version="1.0" encoding="UTF-8"?> +<CONFIG> + <ProjectOptions> + <Version Value="10"/> + <PathDelim Value="\"/> + <General> + <SessionStorage Value="InProjectDir"/> + <MainUnit Value="0"/> + <Title Value="fpExifTests"/> + <ResourceType Value="res"/> + <UseXPManifest Value="True"/> + <Icon Value="0"/> + </General> + <BuildModes Count="1"> + <Item1 Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + </PublishOptions> + <RunParams> + <local> + <FormatVersion Value="1"/> + </local> + </RunParams> + <RequiredPackages Count="3"> + <Item1> + <PackageName Value="fpcunittestrunner"/> + </Item1> + <Item2> + <PackageName Value="LCL"/> + </Item2> + <Item3> + <PackageName Value="FCL"/> + </Item3> + </RequiredPackages> + <Units Count="18"> + <Unit0> + <Filename Value="fpExifTests.lpr"/> + <IsPartOfProject Value="True"/> + </Unit0> + <Unit1> + <Filename Value="..\..\fpeexifdata.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeExifData"/> + </Unit1> + <Unit2> + <Filename Value="..\..\fpeexifreadwrite.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeExifReadWrite"/> + </Unit2> + <Unit3> + <Filename Value="..\..\fpeglobal.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeGlobal"/> + </Unit3> + <Unit4> + <Filename Value="..\..\fpeiptcdata.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeIptcData"/> + </Unit4> + <Unit5> + <Filename Value="..\..\fpeiptcreadwrite.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeIptcReadWrite"/> + </Unit5> + <Unit6> + <Filename Value="..\..\fpemakernote.pas"/> + <IsPartOfProject Value="True"/> + </Unit6> + <Unit7> + <Filename Value="..\..\fpemetadata.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeMetadata"/> + </Unit7> + <Unit8> + <Filename Value="..\..\fpestrconsts.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeStrConsts"/> + </Unit8> + <Unit9> + <Filename Value="..\..\fpetags.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeTags"/> + </Unit9> + <Unit10> + <Filename Value="..\..\fpeutils.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fpeUtils"/> + </Unit10> + <Unit11> + <Filename Value="..\..\fpexif.inc"/> + <IsPartOfProject Value="True"/> + </Unit11> + <Unit12> + <Filename Value="..\..\fpexif_fpc.inc"/> + <IsPartOfProject Value="True"/> + </Unit12> + <Unit13> + <Filename Value="common\fetexifbe.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fetExifBE"/> + </Unit13> + <Unit14> + <Filename Value="common\fetexifle.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fetExifLE"/> + </Unit14> + <Unit15> + <Filename Value="common\fetiptc.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fetIptc"/> + </Unit15> + <Unit16> + <Filename Value="common\fetutils.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fetUtils"/> + </Unit16> + <Unit17> + <Filename Value="common\fettestutils.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="fetTestUtils"/> + </Unit17> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <PathDelim Value="\"/> + <Target> + <Filename Value="fpExifTests"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir);..\.."/> + <OtherUnitFiles Value="..\..;common"/> + <UnitOutputDirectory Value="output\dcu\$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Linking> + <Options> + <Win32> + <GraphicApplication Value="True"/> + </Win32> + </Options> + </Linking> + </CompilerOptions> + <Debugging> + <Exceptions Count="5"> + <Item1> + <Name Value="EAbort"/> + </Item1> + <Item2> + <Name Value="ECodetoolError"/> + </Item2> + <Item3> + <Name Value="EFOpenError"/> + </Item3> + <Item4> + <Name Value="EAssertionFailedError"/> + </Item4> + <Item5> + <Name Value="EConvertError"/> + </Item5> + </Exceptions> + </Debugging> +</CONFIG> diff --git a/components/fpexif/tests/unittest/fpExifTests.lpr b/components/fpexif/tests/unittest/fpExifTests.lpr new file mode 100644 index 000000000..369d8bac0 --- /dev/null +++ b/components/fpexif/tests/unittest/fpExifTests.lpr @@ -0,0 +1,19 @@ +program fpExifTests; + +{$mode objfpc}{$H+} + +uses + Interfaces, Forms, GuiTestRunner, + fetutils, + fetexifle, + fetexifbe, + fetiptc; + +{$R *.res} + +begin + Application.Initialize; + Application.CreateForm(TGuiTestRunner, TestRunner); + Application.Run; +end. + diff --git a/components/fpexif/tests/unittest/fpExifTests_Delphi.dpr b/components/fpexif/tests/unittest/fpExifTests_Delphi.dpr new file mode 100644 index 000000000..c1e453964 --- /dev/null +++ b/components/fpexif/tests/unittest/fpExifTests_Delphi.dpr @@ -0,0 +1,30 @@ +program fpExifTests_Delphi; + +uses + TestFramework, + Forms, + GUITestRunner, + TextTestRunner, + fpeexifdata in '..\..\fpeexifdata.pas', + fpeExifReadWrite in '..\..\fpeexifreadwrite.pas', + fpeGlobal in '..\..\fpeglobal.pas', + fpeIptcData in '..\..\fpeiptcdata.pas', + fpeIptcReadWrite in '..\..\fpeiptcreadwrite.pas', + fpeMakerNote in '..\..\fpemakernote.pas', + fpeMetadata in '..\..\fpemetadata.pas', + fpeStrConsts in '..\..\fpestrconsts.pas', + fpeTags in '..\..\fpetags.pas', + fpeUtils in '..\..\fpeUtils.pas', + fetExifBE in 'common\fetexifbe.pas', + fetExifLE in 'common\fetexifle.pas', + fetIptc in 'common\fetiptc.pas', + fetUtils in 'common\fetutils.pas'; + +{$R *.res} + +begin + Application.Initialize; + GUITestRunner.RunRegisteredTests; +end. + + diff --git a/components/fpexif/tests/unittest/fpExifTests_Delphi.dproj b/components/fpexif/tests/unittest/fpExifTests_Delphi.dproj new file mode 100644 index 000000000..9285c9e61 --- /dev/null +++ b/components/fpexif/tests/unittest/fpExifTests_Delphi.dproj @@ -0,0 +1,141 @@ +<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <ProjectGuid>{2DB98C6D-00C5-4E53-BB9F-D5F4788A994D}</ProjectGuid> + <MainSource>fpExifTests_Delphi.dpr</MainSource> + <Base>True</Base> + <Config Condition="'$(Config)'==''">Debug</Config> + <TargetedPlatforms>1</TargetedPlatforms> + <AppType>Application</AppType> + <FrameworkType>VCL</FrameworkType> + <ProjectVersion>18.2</ProjectVersion> + <Platform Condition="'$(Platform)'==''">Win32</Platform> + </PropertyGroup> + <PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''"> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Base)'=='true') or '$(Base_Win32)'!=''"> + <Base_Win32>true</Base_Win32> + <CfgParent>Base</CfgParent> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_1)'!=''"> + <Cfg_1>true</Cfg_1> + <CfgParent>Base</CfgParent> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win32)'!=''"> + <Cfg_1_Win32>true</Cfg_1_Win32> + <CfgParent>Cfg_1</CfgParent> + <Cfg_1>true</Cfg_1> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_2)'!=''"> + <Cfg_2>true</Cfg_2> + <CfgParent>Base</CfgParent> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_2)'=='true') or '$(Cfg_2_Win32)'!=''"> + <Cfg_2_Win32>true</Cfg_2_Win32> + <CfgParent>Cfg_2</CfgParent> + <Cfg_2>true</Cfg_2> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="'$(Base)'!=''"> + <DCC_E>false</DCC_E> + <DCC_F>false</DCC_F> + <DCC_K>false</DCC_K> + <DCC_N>true</DCC_N> + <DCC_S>false</DCC_S> + <DCC_ImageBase>42200000</DCC_ImageBase> + <DCC_AssertionsAtRuntime>false</DCC_AssertionsAtRuntime> + <DCC_DebugInformation>1</DCC_DebugInformation> + <DCC_Description>TeeChart 2014 Components</DCC_Description> + <DCC_UnitSearchPath>D:\Prog_Lazarus\wp-laz\fpexif;D:\Prog_Lazarus\wp-laz\fpexif\tests\unittest\common;$(DCC_UnitSearchPath)</DCC_UnitSearchPath> + <DCC_UsePackage>Tee97;TeeUI97;TeeDB97;TeePro97;TeeGL97;TeeImage97;TeeLanguage97;TeeWorld97;$(DCC_UsePackage)</DCC_UsePackage> + <SanitizedProjectName>fpExifTests_Delphi</SanitizedProjectName> + <DCC_Namespace>Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Data;Datasnap;Web;Soap;Winapi;$(DCC_Namespace)</DCC_Namespace> + <VerInfo_MajorVer>0</VerInfo_MajorVer> + <VerInfo_Locale>1033</VerInfo_Locale> + <VerInfo_Keys>CompanyName=;FileDescription=;FileVersion=0.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=</VerInfo_Keys> + </PropertyGroup> + <PropertyGroup Condition="'$(Base_Win32)'!=''"> + <DCC_Namespace>System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)</DCC_Namespace> + <VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo> + <VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName)</VerInfo_Keys> + <VerInfo_Locale>1033</VerInfo_Locale> + <Manifest_File>$(BDS)\bin\default_app.manifest</Manifest_File> + <Icon_MainIcon>fpExifTests_Delphi7_Icon.ico</Icon_MainIcon> + <AppEnableRuntimeThemes>true</AppEnableRuntimeThemes> + <UWP_DelphiLogo44>$(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png</UWP_DelphiLogo44> + <UWP_DelphiLogo150>$(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png</UWP_DelphiLogo150> + </PropertyGroup> + <PropertyGroup Condition="'$(Cfg_1)'!=''"> + <DCC_Define>RELEASE;$(DCC_Define)</DCC_Define> + <DCC_DebugInformation>0</DCC_DebugInformation> + <DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols> + <DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo> + </PropertyGroup> + <PropertyGroup Condition="'$(Cfg_1_Win32)'!=''"> + <AppEnableRuntimeThemes>true</AppEnableRuntimeThemes> + <AppEnableHighDPI>true</AppEnableHighDPI> + </PropertyGroup> + <PropertyGroup Condition="'$(Cfg_2)'!=''"> + <DCC_Define>DEBUG;$(DCC_Define)</DCC_Define> + <DCC_Optimize>false</DCC_Optimize> + <DCC_GenerateStackFrames>true</DCC_GenerateStackFrames> + </PropertyGroup> + <PropertyGroup Condition="'$(Cfg_2_Win32)'!=''"> + <AppEnableRuntimeThemes>true</AppEnableRuntimeThemes> + <AppEnableHighDPI>true</AppEnableHighDPI> + <VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo> + <VerInfo_MajorVer>1</VerInfo_MajorVer> + <VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName)</VerInfo_Keys> + </PropertyGroup> + <ItemGroup> + <DelphiCompile Include="$(MainSource)"> + <MainSource>MainSource</MainSource> + </DelphiCompile> + <DCCReference Include="..\..\fpeexifdata.pas"/> + <DCCReference Include="..\..\fpeexifreadwrite.pas"/> + <DCCReference Include="..\..\fpeglobal.pas"/> + <DCCReference Include="..\..\fpeiptcdata.pas"/> + <DCCReference Include="..\..\fpeiptcreadwrite.pas"/> + <DCCReference Include="..\..\fpemakernote.pas"/> + <DCCReference Include="..\..\fpemetadata.pas"/> + <DCCReference Include="..\..\fpestrconsts.pas"/> + <DCCReference Include="..\..\fpetags.pas"/> + <DCCReference Include="..\..\fpeUtils.pas"/> + <DCCReference Include="common\fetexifbe.pas"/> + <DCCReference Include="common\fetexifle.pas"/> + <DCCReference Include="common\fetiptc.pas"/> + <DCCReference Include="common\fetutils.pas"/> + <BuildConfiguration Include="Debug"> + <Key>Cfg_2</Key> + <CfgParent>Base</CfgParent> + </BuildConfiguration> + <BuildConfiguration Include="Base"> + <Key>Base</Key> + </BuildConfiguration> + <BuildConfiguration Include="Release"> + <Key>Cfg_1</Key> + <CfgParent>Base</CfgParent> + </BuildConfiguration> + </ItemGroup> + <ProjectExtensions> + <Borland.Personality>Delphi.Personality.12</Borland.Personality> + <Borland.ProjectType/> + <BorlandProject> + <Delphi.Personality> + <Source> + <Source Name="MainSource">fpExifTests_Delphi.dpr</Source> + </Source> + </Delphi.Personality> + <Platforms> + <Platform value="Win32">True</Platform> + </Platforms> + </BorlandProject> + <ProjectFileVersion>12</ProjectFileVersion> + </ProjectExtensions> + <Import Project="$(BDS)\Bin\CodeGear.Delphi.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Delphi.Targets')"/> + <Import Project="$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj" Condition="Exists('$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj')"/> +</Project> diff --git a/components/fpexif/tests/unittest/fpExifTests_Delphi.res b/components/fpexif/tests/unittest/fpExifTests_Delphi.res new file mode 100644 index 0000000000000000000000000000000000000000..60e829e5932673eefd8619b6a729cdf607b5aa03 GIT binary patch literal 3272 zcmbtX-EZ4e6hCPzXje8mfwaBNm}x^uj_ue<laaQo_N9n*n`jx2lVe|}7Kt5fCs~Dn zPJ5tIANI}*|H7ueAX!M|iGKqL9(ZGKLjqxb_r`Y8YSX6ew#UBb^LOsK=h_YtkpxQ9 zF=2XSG-q=C5^PFBr_sV(Zq%GHF}+KhbPv~S^ciL?x=pvhw{X3VIUhgI{q-La_aDc| z?`uL!!~^D<Ohi9GP(r+zLh{I`4x)L~C5Fg8xfH-~C~Pz`NuvhHL=HToTl5}$bNj3C ziO3dl`iSSj*B3A@_2F?J5|_4wRSe5}#67yuZ9~I{M)Ong?m`>FZW2eQGHh}pAandX zunNeb0kC7_WKj>Ee7q{IDR7U*IgWlDyG<V!*d1iRQCnCMA2#yoAy0;D8=4Rj3mNkD z1dzXKUOZX}JI*8IRi5k$(Rs0>TLRrjrMj@3uKU8=A9H^ey)|3(*3#0Xw>|=f1iB0= ziFla6=lNro2>-OZJHFwHDQK4t4-cW>4bE@r_=fAa=TlnLPxG+^tr&}mfuR@t{N6qn z6g+XxIGvjl?d|O)$&(2$40M{idNq^nCkIbx?)q?;3Bc=gqtkJRmxaaN{((MoVL0q` z9KkO_KQkJ2+QYcuN25_FcU<8xznAEOA7%lP#5fCeuBV*8Ft#TEoE?qikt_>;ng_t6 zQFw8h|Na@&=LH^dfDi%ynH%#w;F*gfS$&-GD9IUlG&-MT{QUmG(u^$AVrBqd7pmL< z0Xe^zWCnW)FX&>DBz<9rqQd|@(E%^-CxQnqk=PC+IWS<Dz#lWifFtL*!#?IovX>n2 zKuPk`KK27+KjR;dS?m$lFXjnxhL6XzTT0WM4rkMp)6?09>C;n`K7EJ2dpb{Re{&)_ z$LZfG!b9yGXpIxvi_^LE!NW)CZu)C_`0s3b_-!fuZS`HK-UiKr9)J!(zkq&E)AZT< z$U+09=_%Tvb@AM6(G~F&g;)%2>Rj;CP$Eg16Q_coq7wc17KyWxre}+@bmTd`cE8t^ zhAfP{z+aK|vL;K+w*$xXJ1g?%Ti2DkEJd;9J61RF*^0cwB6)4~<cX#gMXcBE?nnsW zM=SCm^e;vBHtSiD((~*vhypiO?4WnaihAXtE=xVj_gogmcaNY6cWyH@UK+%Zfh<YD zhV><@-|u?16(jRiNDKMM^PON6ZL&DVDxy5W?KtjVQq?TSa*<;hIjWl*Ew!R)RrQOb z&hj`iVmp0rePo4fbt@b&_0C39EhJ-bb)3~Pbnf5GRMQEIbvUfgd<XU3$rg|c;`OY$ z1$<ua7R+NW-VvC5(eX|n+X7UhA{Rp6>Or`XZ$mj-RryNL>jl1YCGg`g=tdKVq62DW zjsHzF{XrX5{*>)(1z$3>NUhqawri$tnzrrgjytjJT7G9>bpZVFMA#1ln?+F&u7~!v z7c)B^gu+72maIN^&8Y6AHlkK>IEdnw?*{pPzNF#ME8~5Mm^}zlv7(eA`)YtTWX_$? z8+u*VVNtHbUKQGF_u<H!>n0nrZc$oE>T-TXwxX6lL`}lUVkK$dt=oLPE3#{KBQ{ZG z%N1>^78u7fXsXlMHq~O${KTP8`{?$z*TrgHUWFs(`~a>>Cr>Oir<;%q7MdR@%GNfM zT34ku>jonUCx9O-7D=#`qCvkOgt6oXp_H|ebl1ZpK~fZC1^>)~Ikz{Zma`&%%_^F% zo7H7SubMT*H0-KkRb8fNTHRrc8IIv<-^i-S=XKzhUkq(jb)(*HSW4At0NQOkN}W|4 z#j2R5R;g6ohGB8&*Iao0#mJ^raVvFZC{?4~P)yU*mA2Ke6{ps4Ov|)t%&5N!a#?>d zw(i!JYxZ(YscTGE%!<)aa7f!q-EcIw=4g(!{3h7C_G0Kd)7)iCuPAM|ZJ^++uAux? z#jLwdRkQS})er?=SXkiQrygs+rh24Jd7I;5IexQ%5ubHUyy?>P1iqSY;w`v^@5<Zw aJ+Xn=I_@{{oxX)%1KIm<>NiLUqW=N!13`NL literal 0 HcmV?d00001 diff --git a/components/fpexif/tests/unittest/fpExifTests_Delphi7.cfg b/components/fpexif/tests/unittest/fpExifTests_Delphi7.cfg new file mode 100644 index 000000000..542cddcc2 --- /dev/null +++ b/components/fpexif/tests/unittest/fpExifTests_Delphi7.cfg @@ -0,0 +1,42 @@ +-$A8 +-$B- +-$C- +-$D+ +-$E- +-$F- +-$G+ +-$H+ +-$I+ +-$J- +-$K- +-$L+ +-$M- +-$N+ +-$O+ +-$P+ +-$Q- +-$R- +-$S- +-$T- +-$U- +-$V+ +-$W- +-$X+ +-$Y+ +-$Z1 +-cg +-AWinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; +-H+ +-W+ +-M +-$M16384,1048576 +-K$42200000 +-LE"d:\programme\borland\delphi7\Projects\Bpl" +-LN"d:\programme\borland\delphi7\Projects\Bpl" +-U"D:\Prog_Lazarus\wp-laz\fpexif;D:\Prog_Lazarus\wp-laz\fpexif\tests\unittest\common;D:\Prog_Lazarus\wp-laz\fpexif\tests\unittest\dunit" +-O"D:\Prog_Lazarus\wp-laz\fpexif;D:\Prog_Lazarus\wp-laz\fpexif\tests\unittest\common;D:\Prog_Lazarus\wp-laz\fpexif\tests\unittest\dunit" +-I"D:\Prog_Lazarus\wp-laz\fpexif;D:\Prog_Lazarus\wp-laz\fpexif\tests\unittest\common;D:\Prog_Lazarus\wp-laz\fpexif\tests\unittest\dunit" +-R"D:\Prog_Lazarus\wp-laz\fpexif;D:\Prog_Lazarus\wp-laz\fpexif\tests\unittest\common;D:\Prog_Lazarus\wp-laz\fpexif\tests\unittest\dunit" +-w-UNSAFE_TYPE +-w-UNSAFE_CODE +-w-UNSAFE_CAST diff --git a/components/fpexif/tests/unittest/fpExifTests_Delphi7.dof b/components/fpexif/tests/unittest/fpExifTests_Delphi7.dof new file mode 100644 index 000000000..8755155a2 --- /dev/null +++ b/components/fpexif/tests/unittest/fpExifTests_Delphi7.dof @@ -0,0 +1,145 @@ +[FileVersion] +Version=7.0 +[Compiler] +A=8 +B=0 +C=0 +D=1 +E=0 +F=0 +G=1 +H=1 +I=1 +J=0 +K=0 +L=1 +M=0 +N=1 +O=1 +P=1 +Q=0 +R=0 +S=0 +T=0 +U=0 +V=1 +W=0 +X=1 +Y=2 +Z=1 +ShowHints=1 +ShowWarnings=1 +UnitAliases=WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; +NamespacePrefix= +SymbolDeprecated=1 +SymbolLibrary=1 +SymbolPlatform=1 +UnitLibrary=1 +UnitPlatform=1 +UnitDeprecated=1 +HResultCompat=1 +HidingMember=1 +HiddenVirtual=1 +Garbage=1 +BoundsError=1 +ZeroNilCompat=1 +StringConstTruncated=1 +ForLoopVarVarPar=1 +TypedConstVarPar=1 +AsgToTypedConst=1 +CaseLabelRange=1 +ForVariable=1 +ConstructingAbstract=1 +ComparisonFalse=1 +ComparisonTrue=1 +ComparingSignedUnsigned=1 +CombiningSignedUnsigned=1 +UnsupportedConstruct=1 +FileOpen=1 +FileOpenUnitSrc=1 +BadGlobalSymbol=1 +DuplicateConstructorDestructor=1 +InvalidDirective=1 +PackageNoLink=1 +PackageThreadVar=1 +ImplicitImport=1 +HPPEMITIgnored=1 +NoRetVal=1 +UseBeforeDef=1 +ForLoopVarUndef=1 +UnitNameMismatch=1 +NoCFGFileFound=1 +MessageDirective=1 +ImplicitVariants=1 +UnicodeToLocale=1 +LocaleToUnicode=1 +ImagebaseMultiple=1 +SuspiciousTypecast=1 +PrivatePropAccessor=1 +UnsafeType=0 +UnsafeCode=0 +UnsafeCast=0 +[Linker] +MapFile=0 +OutputObjs=0 +ConsoleApp=1 +DebugInfo=0 +RemoteSymbols=0 +MinStackSize=16384 +MaxStackSize=1048576 +ImageBase=1109393408 +ExeDescription=TeeChart 2014 Components +[Directories] +OutputDir= +UnitOutputDir= +PackageDLLOutputDir= +PackageDCPOutputDir= +SearchPath=D:\Prog_Lazarus\wp-laz\fpexif;D:\Prog_Lazarus\wp-laz\fpexif\tests\unittest\common;D:\Prog_Lazarus\wp-laz\fpexif\tests\unittest\dunit +Packages=Tee97;TeeUI97;TeeDB97;TeePro97;TeeGL97;TeeImage97;TeeLanguage97;TeeWorld97 +Conditionals= +DebugSourceDirs= +UsePackages=0 +[Parameters] +RunParams= +HostApplication= +Launcher= +UseLauncher=0 +DebugCWD= +[Language] +ActiveLang= +ProjectLang= +RootDir=D:\Programme\Borland\Delphi7\Bin\ +[Version Info] +IncludeVerInfo=0 +AutoIncBuild=0 +MajorVer=0 +MinorVer=0 +Release=0 +Build=0 +Debug=0 +PreRelease=0 +Special=0 +Private=0 +DLL=0 +Locale=1033 +CodePage=1252 +[Version Info Keys] +CompanyName= +FileDescription= +FileVersion=0.0.0.0 +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename= +ProductName= +ProductVersion= +[Excluded Packages] +D:\Prog_Delphi\common\Components\3rdParty\TeeChart\Sources\Compiled\Delphi7\Bin\DclTeeMaker17.bpl=TeeMaker +D:\Programme\Borland\Delphi7\Lib\HelpCtxD7.bpl=HelpScribble HelpContext Property Editor for Delphi 7 +[HistoryLists\hlUnitAliases] +Count=1 +Item0=WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; +[HistoryLists\hlSearchPath] +Count=2 +Item0=D:\Prog_Lazarus\wp-laz\fpexif;D:\Prog_Lazarus\wp-laz\fpexif\tests\unittest\common;D:\Prog_Lazarus\wp-laz\fpexif\tests\unittest\dunit +Item1=D:\Prog_Lazarus\wp-laz\fpexif;D:\Prog_Lazarus\wp-laz\fpexif\tests\unittest\common diff --git a/components/fpexif/tests/unittest/fpExifTests_Delphi7.dpr b/components/fpexif/tests/unittest/fpExifTests_Delphi7.dpr new file mode 100644 index 000000000..77ff2903d --- /dev/null +++ b/components/fpexif/tests/unittest/fpExifTests_Delphi7.dpr @@ -0,0 +1,30 @@ +program fpExifTests_Delphi7; + +uses + TestFramework, + Forms, + GUITestRunner, + TextTestRunner, + fpeexifdata in '..\..\fpeexifdata.pas', + fpeExifReadWrite in '..\..\fpeexifreadwrite.pas', + fpeGlobal in '..\..\fpeglobal.pas', + fpeIptcData in '..\..\fpeiptcdata.pas', + fpeIptcReadWrite in '..\..\fpeiptcreadwrite.pas', + fpeMakerNote in '..\..\fpemakernote.pas', + fpeMetadata in '..\..\fpemetadata.pas', + fpeStrConsts in '..\..\fpestrconsts.pas', + fpeTags in '..\..\fpetags.pas', + fpeUtils in '..\..\fpeUtils.pas', + fetExifBE in 'common\fetexifbe.pas', + fetExifLE in 'common\fetexifle.pas', + fetIptc in 'common\fetiptc.pas', + fetUtils in 'common\fetutils.pas'; + +{$R *.res} + +begin + Application.Initialize; + GUITestRunner.RunRegisteredTests; +end. + + diff --git a/components/fpexif/tests/unittest/fpExifTests_Delphi7.dproj b/components/fpexif/tests/unittest/fpExifTests_Delphi7.dproj new file mode 100644 index 000000000..4c5147fdc --- /dev/null +++ b/components/fpexif/tests/unittest/fpExifTests_Delphi7.dproj @@ -0,0 +1,138 @@ +<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <ProjectGuid>{2DB98C6D-00C5-4E53-BB9F-D5F4788A994D}</ProjectGuid> + <MainSource>fpExifTests_Delphi7.dpr</MainSource> + <Base>True</Base> + <Config Condition="'$(Config)'==''">Debug</Config> + <TargetedPlatforms>1</TargetedPlatforms> + <AppType>Application</AppType> + <FrameworkType>VCL</FrameworkType> + <ProjectVersion>18.2</ProjectVersion> + <Platform Condition="'$(Platform)'==''">Win32</Platform> + </PropertyGroup> + <PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''"> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Base)'=='true') or '$(Base_Win32)'!=''"> + <Base_Win32>true</Base_Win32> + <CfgParent>Base</CfgParent> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_1)'!=''"> + <Cfg_1>true</Cfg_1> + <CfgParent>Base</CfgParent> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win32)'!=''"> + <Cfg_1_Win32>true</Cfg_1_Win32> + <CfgParent>Cfg_1</CfgParent> + <Cfg_1>true</Cfg_1> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_2)'!=''"> + <Cfg_2>true</Cfg_2> + <CfgParent>Base</CfgParent> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_2)'=='true') or '$(Cfg_2_Win32)'!=''"> + <Cfg_2_Win32>true</Cfg_2_Win32> + <CfgParent>Cfg_2</CfgParent> + <Cfg_2>true</Cfg_2> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="'$(Base)'!=''"> + <DCC_E>false</DCC_E> + <DCC_F>false</DCC_F> + <DCC_K>false</DCC_K> + <DCC_N>true</DCC_N> + <DCC_S>false</DCC_S> + <DCC_ImageBase>42200000</DCC_ImageBase> + <DCC_AssertionsAtRuntime>false</DCC_AssertionsAtRuntime> + <DCC_DebugInformation>1</DCC_DebugInformation> + <DCC_Description>TeeChart 2014 Components</DCC_Description> + <DCC_UnitSearchPath>D:\Prog_Lazarus\wp-laz\fpexif;D:\Prog_Lazarus\wp-laz\fpexif\tests\unittest\common;D:\Prog_Lazarus\wp-laz\fpexif\tests\unittest\dunit;$(DCC_UnitSearchPath)</DCC_UnitSearchPath> + <DCC_UsePackage>Tee97;TeeUI97;TeeDB97;TeePro97;TeeGL97;TeeImage97;TeeLanguage97;TeeWorld97;$(DCC_UsePackage)</DCC_UsePackage> + <SanitizedProjectName>fpExifTests_Delphi7</SanitizedProjectName> + <DCC_Namespace>Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Data;Datasnap;Web;Soap;Winapi;$(DCC_Namespace)</DCC_Namespace> + <VerInfo_MajorVer>0</VerInfo_MajorVer> + <VerInfo_Locale>1033</VerInfo_Locale> + <VerInfo_Keys>CompanyName=;FileDescription=;FileVersion=0.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=</VerInfo_Keys> + </PropertyGroup> + <PropertyGroup Condition="'$(Base_Win32)'!=''"> + <DCC_Namespace>Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)</DCC_Namespace> + <VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo> + <VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName)</VerInfo_Keys> + <VerInfo_Locale>1033</VerInfo_Locale> + <Manifest_File>$(BDS)\bin\default_app.manifest</Manifest_File> + <Icon_MainIcon>fpExifTests_Delphi7_Icon.ico</Icon_MainIcon> + <AppEnableRuntimeThemes>true</AppEnableRuntimeThemes> + <UWP_DelphiLogo44>$(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png</UWP_DelphiLogo44> + <UWP_DelphiLogo150>$(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png</UWP_DelphiLogo150> + </PropertyGroup> + <PropertyGroup Condition="'$(Cfg_1)'!=''"> + <DCC_Define>RELEASE;$(DCC_Define)</DCC_Define> + <DCC_DebugInformation>0</DCC_DebugInformation> + <DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols> + <DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo> + </PropertyGroup> + <PropertyGroup Condition="'$(Cfg_1_Win32)'!=''"> + <AppEnableRuntimeThemes>true</AppEnableRuntimeThemes> + <AppEnableHighDPI>true</AppEnableHighDPI> + </PropertyGroup> + <PropertyGroup Condition="'$(Cfg_2)'!=''"> + <DCC_Define>DEBUG;$(DCC_Define)</DCC_Define> + <DCC_Optimize>false</DCC_Optimize> + <DCC_GenerateStackFrames>true</DCC_GenerateStackFrames> + </PropertyGroup> + <PropertyGroup Condition="'$(Cfg_2_Win32)'!=''"> + <AppEnableRuntimeThemes>true</AppEnableRuntimeThemes> + <AppEnableHighDPI>true</AppEnableHighDPI> + </PropertyGroup> + <ItemGroup> + <DelphiCompile Include="$(MainSource)"> + <MainSource>MainSource</MainSource> + </DelphiCompile> + <DCCReference Include="..\..\fpeexifdata.pas"/> + <DCCReference Include="..\..\fpeexifreadwrite.pas"/> + <DCCReference Include="..\..\fpeglobal.pas"/> + <DCCReference Include="..\..\fpeiptcdata.pas"/> + <DCCReference Include="..\..\fpeiptcreadwrite.pas"/> + <DCCReference Include="..\..\fpemakernote.pas"/> + <DCCReference Include="..\..\fpemetadata.pas"/> + <DCCReference Include="..\..\fpestrconsts.pas"/> + <DCCReference Include="..\..\fpetags.pas"/> + <DCCReference Include="..\..\fpeUtils.pas"/> + <DCCReference Include="common\fetexifbe.pas"/> + <DCCReference Include="common\fetexifle.pas"/> + <DCCReference Include="common\fetiptc.pas"/> + <DCCReference Include="common\fetutils.pas"/> + <BuildConfiguration Include="Debug"> + <Key>Cfg_2</Key> + <CfgParent>Base</CfgParent> + </BuildConfiguration> + <BuildConfiguration Include="Base"> + <Key>Base</Key> + </BuildConfiguration> + <BuildConfiguration Include="Release"> + <Key>Cfg_1</Key> + <CfgParent>Base</CfgParent> + </BuildConfiguration> + </ItemGroup> + <ProjectExtensions> + <Borland.Personality>Delphi.Personality.12</Borland.Personality> + <Borland.ProjectType/> + <BorlandProject> + <Delphi.Personality> + <Source> + <Source Name="MainSource">fpExifTests_Delphi7.dpr</Source> + </Source> + </Delphi.Personality> + <Platforms> + <Platform value="Win32">True</Platform> + </Platforms> + </BorlandProject> + <ProjectFileVersion>12</ProjectFileVersion> + </ProjectExtensions> + <Import Project="$(BDS)\Bin\CodeGear.Delphi.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Delphi.Targets')"/> + <Import Project="$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj" Condition="Exists('$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj')"/> +</Project> diff --git a/components/fpexif/tests/unittest/fpExifTests_Delphi7.res b/components/fpexif/tests/unittest/fpExifTests_Delphi7.res new file mode 100644 index 0000000000000000000000000000000000000000..1e4099ba3d5b244035f1f4e2788cda692b773db3 GIT binary patch literal 876 zcmZuwv1;5v5PdocERb=|5U@KR!r18&p)wFV#5lM}{0|{mZ8(CqOJrO`pu)d$MonNE zrOIyv(zv|NftZZntfb2YGThV7+xKR6*}VmT8KK&P=|6Eh8TF8!z-!a#Ml_n9Fqsj@ zM1WzMHUEza;15;R5BxGEDs){(!8T!o_5;8Dz7$EeZ84?lBaxhut$18FHnHL`mT!-C zpss6?o4?!}GDIgQ!(1cn4WiSs945#!ye^81^4L4-rp+b?Wmy#OJx{1loGadyA6MKt zH&2TTUl7-ld0q|;MDktAqmGdu^nP%GUFTAlroJ9A7r1uw!*%}Uj`F@=54OOZ4Sz>H zw}v~1E`7QfIFDo}b#7k<9yjfDlBP%o19|49(ZGhr6B!J(*nuV@^1=If=roW6tsU=k z&$%w(uOpW>r%_nXEYDeJ+zJ}zBDJ*ENaSn7`(QK!U-XoBWO_1g$cV)PRiHYe3snuv z@Jg3k(B&pRFJm12G5~vq`!)PwP8=Ed_cHA1<<+%T`dz#GP`gio{&?|-(gqO{mqbT= pBYvvt-DB?J8KJs`H+YFNyd_@YoZ}el@4{X~fY~haufkqYfCrhd(P97q literal 0 HcmV?d00001 diff --git a/components/fpexif/tools/readme.txt b/components/fpexif/tools/readme.txt new file mode 100644 index 000000000..c29dcfdd1 --- /dev/null +++ b/components/fpexif/tools/readme.txt @@ -0,0 +1,2 @@ +Place the program exiftool.exe into this directory, it is needed by the +test program "multiread". \ No newline at end of file