/ccr-exif/CCR.Exif.pas

https://bitbucket.org/sas_team/sas.requires · Pascal · 7404 lines · 6459 code · 690 blank · 255 comment · 544 complexity · 4c8faa505b5c20b70482dd69f01ec26b MD5 · raw file

  1. {**************************************************************************************}
  2. { }
  3. { CCR Exif - Delphi class library for reading and writing image metadata }
  4. { Version 1.5.3 }
  5. { }
  6. { The contents of this file are subject to the Mozilla Public License Version 1.1 }
  7. { (the "License"); you may not use this file except in compliance with the License. }
  8. { You may obtain a copy of the License at http://www.mozilla.org/MPL/ }
  9. { }
  10. { Software distributed under the License is distributed on an "AS IS" basis, WITHOUT }
  11. { WARRANTY OF ANY KIND, either express or implied. See the License for the specific }
  12. { language governing rights and limitations under the License. }
  13. { }
  14. { The Original Code is CCR.Exif.pas. }
  15. { }
  16. { The Initial Developer of the Original Code is Chris Rolliston. Portions created by }
  17. { Chris Rolliston are Copyright (C) 2009-2014 Chris Rolliston. All Rights Reserved. }
  18. { }
  19. {**************************************************************************************}
  20. {$I CCR.Exif.inc}
  21. unit CCR.Exif;
  22. {
  23. Notes:
  24. - In Exif-jargon, we have 'tags' and 'IFDs' (including 'sub-IFDs'). In the jargon of
  25. this unit, we have 'tags' and 'sections'.
  26. - The basic usage pattern is this: construct a TExifData instance; call LoadFromGraphic,
  27. which has file name, TStream and TGraphic/TBitmap overloads; read and write
  28. the published tag properties as you so wish; and call SaveToGraphic to persist the
  29. changes made. Supported graphic types are JPEG, PSD and TIFF. LoadFromGraphic (which
  30. is a function) returns True if the source was of a supported graphic type, False
  31. otherwise. In contrast, SaveToGraphic will simply raise an exception if the
  32. destination isn't of a supported type.
  33. - The idea is that, in general, if your sole concern is reading and/or writing Exif
  34. metadata, you only need to explicitly add CCR.Exif to your uses clause. This unit
  35. does still itself use the other ones (CCR.Exif.BaseUtils etc.) though.
  36. - You enumerate the tags of a section with the for-in syntax. No traditional indexing
  37. is provided so as to avoid confusing tag IDs with tag indices.
  38. - The enumerator implementation for TExifSection allows calling Delete on a tag while
  39. you are enumerating the container section.
  40. - When tags are loaded from a graphic, any associated XMP packet is loaded too, though
  41. XMP data is only actually parsed when required.
  42. - When setting a tag property, the default behaviour is for the loaded XMP packet
  43. to be updated if the equivalent XMP tag already exists. This can be changed however
  44. by setting the XMPWritePolicy property of TExifData.
  45. - Maker note rewriting is *not* supported in TExifData. While you can make changes to
  46. the loaded maker note tags, these changes won't ever be persisted.
  47. - When compiling in XE2+, you need to set a 'FMX' global define for this unit to work
  48. properly in a FireMonkey application.
  49. }
  50. interface
  51. uses
  52. Types, SysUtils, Classes, TypInfo, CCR.Exif.BaseUtils, CCR.Exif.IPTC,
  53. {$IFDEF HasGenerics}Generics.Collections, Generics.Defaults,{$ENDIF}
  54. {$IFDEF VCL}Graphics, Jpeg,{$ENDIF}
  55. {$IFDEF FMX}FMX.Types,{$IF CompilerVersion >= 26}FMX.Graphics, FMX.Surfaces,{$IFEND}{$ENDIF}
  56. CCR.Exif.StreamHelper, CCR.Exif.TagIDs, CCR.Exif.TiffUtils, CCR.Exif.XMPUtils;
  57. const
  58. SmallEndian = CCR.Exif.StreamHelper.SmallEndian;
  59. BigEndian = CCR.Exif.StreamHelper.BigEndian;
  60. xwAlwaysUpdate = CCR.Exif.XMPUtils.xwAlwaysUpdate;
  61. xwUpdateIfExists = CCR.Exif.XMPUtils.xwUpdateIfExists;
  62. xwRemove = CCR.Exif.XMPUtils.xwRemove;
  63. type
  64. EInvalidJPEGHeader = CCR.Exif.BaseUtils.EInvalidJPEGHeader;
  65. ECCRExifException = CCR.Exif.BaseUtils.ECCRExifException;
  66. EInvalidTiffData = CCR.Exif.TiffUtils.EInvalidTiffData;
  67. TEndianness = CCR.Exif.StreamHelper.TEndianness;
  68. const
  69. tdExifFraction = tdLongWordFraction;
  70. tdExifSignedFraction = tdLongIntFraction;
  71. leBadOffset = CCR.Exif.BaseUtils.leBadOffset;
  72. leBadTagCount = CCR.Exif.BaseUtils.leBadTagCount;
  73. leBadTagHeader = CCR.Exif.BaseUtils.leBadTagHeader;
  74. tdUndefined = CCR.Exif.TiffUtils.tdUndefined;
  75. StandardExifThumbnailWidth = 160;
  76. StandardExifThumbnailHeight = 120;
  77. type
  78. {$IFDEF FMX}
  79. TGraphic = TBitmap;
  80. TJPEGImage = class(TBitmap, IStreamPersist)
  81. public
  82. constructor Create; reintroduce;
  83. procedure SaveToStream(Stream: TStream);
  84. property Empty: Boolean read IsEmpty;
  85. end;
  86. {$ENDIF}
  87. {$IF NOT Declared(TJPEGImage)}
  88. {$DEFINE DummyTJpegImage}
  89. TJPEGImage = class(TInterfacedPersistent, IStreamPersist)
  90. strict private
  91. FData: TMemoryStream;
  92. FWidth, FHeight: Integer;
  93. FOnChange: TNotifyEvent;
  94. function GetWidth: Integer;
  95. function GetHeight: Integer;
  96. procedure SizeFieldsNeeded;
  97. protected
  98. procedure AssignTo(Dest: TPersistent); override;
  99. procedure Changed; virtual;
  100. function GetEmpty: Boolean;
  101. public
  102. constructor Create;
  103. destructor Destroy; override;
  104. procedure Assign(Source: TPersistent); override;
  105. procedure LoadFromStream(Stream: TStream);
  106. procedure SaveToStream(Stream: TStream);
  107. property Empty: Boolean read GetEmpty;
  108. property Width: Integer read GetWidth;
  109. property Height: Integer read GetHeight;
  110. property OnChange: TNotifyEvent read FOnChange write FOnChange;
  111. end;
  112. {$IFEND}
  113. ENotOnlyASCIIError = class(EInvalidTiffData);
  114. TExifTag = class;
  115. TExifSection = class;
  116. TExtendableExifSection = class;
  117. TCustomExifData = class;
  118. TExifData = class;
  119. TExifTagID = TTiffTagID;
  120. TExifDataType = TTiffDataType;
  121. TExifDataTypes = TTiffDataTypes;
  122. PExifFraction = ^TExifFraction;
  123. TExifFraction = TTiffLongWordFraction;
  124. TExifSignedFraction = TTiffLongIntFraction;
  125. {$Z2}
  126. TWindowsStarRating = (urUndefined, urOneStar, urTwoStars, urThreeStars,
  127. urFourStars, urFiveStars);
  128. {$Z1}
  129. TExifTagChangeType = (tcData, tcDataSize, tcID);
  130. TExifPaddingTagSize = 2..High(LongInt);
  131. TExifTag = class(TNoRefCountInterfacedObject, IMetadataBlock, ITiffTag)
  132. strict private
  133. FAsStringCache: string;
  134. FData: Pointer;
  135. FDataStream: TUserMemoryStream;
  136. FDataType: TExifDataType;
  137. FElementCount: LongInt;
  138. FID: TExifTagID;
  139. FOriginalDataOffset: Int64;
  140. FOriginalDataSize: Integer;
  141. FWellFormed: Boolean;
  142. function GetAsString: string;
  143. procedure SetAsString(const Value: string);
  144. function GetDataSize: Integer; inline;
  145. procedure SetDataType(const NewDataType: TExifDataType);
  146. function GetElementAsString(Index: Integer): string;
  147. procedure SetElementCount(const NewCount: LongInt);
  148. procedure SetID(const Value: TExifTagID);
  149. protected
  150. { IMetadataBlock }
  151. function GetData: TCustomMemoryStream;
  152. function IsExifBlock(CheckID: Boolean = True): Boolean;
  153. function IsIPTCBlock(CheckID: Boolean = True): Boolean;
  154. function IsXMPBlock(CheckID: Boolean = True): Boolean;
  155. { ITiffTag }
  156. function GetDataType: TTiffDataType;
  157. function GetElementCount: Integer;
  158. function GetID: TTiffTagID;
  159. function GetOriginalDataOffset: LongWord;
  160. function GetParent: ITiffDirectory;
  161. protected
  162. {$IFDEF WEAKREF}[Weak]{$ENDIF}FSection: TExifSection;
  163. procedure Changing(NewID: TExifTagID; NewDataType: TExifDataType;
  164. NewElementCount: LongInt; NewData: Boolean);
  165. procedure Changed(ChangeType: TExifTagChangeType); overload;
  166. procedure WriteHeader(Stream: TStream; Endianness: TEndianness; DataOffset: LongInt);
  167. procedure WriteOffsettedData(Stream: TStream; Endianness: TEndianness);
  168. constructor Create(Section: TExifSection; const Directory: IFoundTiffDirectory;
  169. Index: Integer); overload;
  170. property DataStream: TUserMemoryStream read FDataStream;
  171. public
  172. constructor Create(const Section: TExifSection; const ID: TExifTagID;
  173. DataType: TExifDataType; ElementCount: LongInt); overload;
  174. destructor Destroy; override;
  175. procedure Assign(Source: TExifTag);
  176. procedure Changed; overload; //call this if Data is modified directly
  177. procedure Delete; inline;
  178. function HasWindowsStringData: Boolean;
  179. function IsPadding: Boolean;
  180. procedure SetAsPadding(Size: TExifPaddingTagSize);
  181. procedure UpdateData(NewDataType: TExifDataType; NewElementCount: LongInt;
  182. const NewData); overload;
  183. procedure UpdateData(const NewData); overload;
  184. function ReadFraction(Index: Integer; const Default: TExifFraction): TExifFraction;
  185. function ReadLongWord(Index: Integer; const Default: LongWord): LongWord;
  186. function ReadWord(Index: Integer; const Default: Word): Word;
  187. {$IFDEF HasToString}
  188. function ToString: string; override;
  189. {$ENDIF}
  190. property AsString: string read GetAsString write SetAsString;
  191. property ElementAsString[Index: Integer]: string read GetElementAsString;
  192. property Data: Pointer read FData;
  193. property DataSize: Integer read GetDataSize;
  194. property DataType: TExifDataType read FDataType write SetDataType; //this needs to be loaded before AsString
  195. property ElementCount: LongInt read FElementCount write SetElementCount;
  196. property ID: TExifTagID read FID write SetID;
  197. property OriginalDataOffset: Int64 read FOriginalDataOffset;
  198. property OriginalDataSize: Integer read FOriginalDataSize;
  199. property Section: TExifSection read FSection;
  200. property WellFormed: Boolean read FWellFormed;
  201. end;
  202. TExifSectionLoadError = TMetadataLoadError;
  203. TExifSectionLoadErrors = TMetadataLoadErrors;
  204. TExifSectionKindEx = (esUserDefined, esGeneral, esDetails, esInterop, esGPS,
  205. esThumbnail, esMakerNote);
  206. TExifSectionKind = esGeneral..esMakerNote;
  207. TExifSection = class(TNoRefCountInterfacedObject, ITiffDirectory)
  208. private type
  209. {$IFDEF HasGenerics}
  210. TTagList = class(TList<TExifTag>)
  211. constructor Create;
  212. end;
  213. {$ELSE}
  214. TTagList = class(TList)
  215. public
  216. procedure Sort;
  217. end;
  218. {$ENDIF}
  219. public type
  220. TEnumerator = class sealed(TInterfacedObject, ITiffDirectoryEnumerator)
  221. private
  222. FCurrent: TExifTag;
  223. FIndex: Integer;
  224. FTags: TTagList;
  225. constructor Create(ATagList: TTagList);
  226. function GetCurrent: ITiffTag;
  227. public
  228. function MoveNext: Boolean;
  229. property Current: TExifTag read FCurrent;
  230. end;
  231. strict private class var
  232. LastSetDateTimeValue: TDateTime;
  233. LastSetDateTimeMainStr, LastSetDateTimeSubSecStr: string;
  234. strict private
  235. FFirstTagHeaderOffset: Int64;
  236. FKind: TExifSectionKindEx;
  237. FLoadErrors: TExifSectionLoadErrors;
  238. FModified: Boolean;
  239. {$IFDEF WEAKREF}[Weak]{$ENDIF}FOwner: TCustomExifData;
  240. FTagList: TTagList;
  241. procedure DoSetFractionValue(TagID: TExifTagID; Index: Integer;
  242. DataType: TExifDataType; const Value);
  243. protected
  244. { ITiffDirectory }
  245. function FindTag(TagID: TTiffTagID; out ParsedTag: ITiffTag): Boolean;
  246. function GetEnumeratorIntf: ITiffDirectoryEnumerator;
  247. function ITiffDirectory.GetEnumerator = GetEnumeratorIntf;
  248. function GetIndex: Integer;
  249. function GetParent: ITiffDirectory;
  250. function GetTagCount: Integer;
  251. function LoadSubDirectory(OffsetTagID: TTiffTagID): ITiffDirectory;
  252. { other }
  253. constructor Create(const AOwner: TCustomExifData; AKind: TExifSectionKindEx);
  254. function Add(ID: TExifTagID; DataType: TExifDataType; ElementCount: LongInt): TExifTag;
  255. procedure Changed;
  256. function CheckExtendable: TExtendableExifSection;
  257. procedure DoDelete(TagIndex: Integer; FreeTag: Boolean);
  258. function EnforceASCII: Boolean;
  259. function FindIndex(ID: TExifTagID; var TagIndex: Integer): Boolean;
  260. function ForceSetElement(ID: TExifTagID; DataType: TExifDataType;
  261. Index: Integer; const Value): TExifTag;
  262. procedure Load(const Directory: IFoundTiffDirectory; TiffImageSource: Boolean);
  263. procedure TagChanging(Tag: TExifTag; NewID: TExifTagID;
  264. NewDataType: TExifDataType; NewElementCount: LongInt; NewData: Boolean);
  265. procedure TagChanged(Tag: TExifTag; ChangeType: TExifTagChangeType);
  266. procedure TagDeleting(Tag: TExifTag);
  267. property FirstTagHeaderOffset: Int64 read FFirstTagHeaderOffset;
  268. public
  269. destructor Destroy; override;
  270. function GetEnumerator: TEnumerator;
  271. procedure Clear;
  272. function Find(ID: TExifTagID; out Tag: TExifTag): Boolean;
  273. function GetByteValue(TagID: TExifTagID; Index: Integer; Default: Byte;
  274. MinValue: Byte = 0; MaxValue: Byte = High(Byte)): Byte;
  275. function GetDateTimeValue(MainID, SubSecsID: TExifTagID): TDateTimeTagValue;
  276. function GetFractionValue(TagID: TExifTagID; Index: Integer): TExifFraction; overload;
  277. function GetFractionValue(TagID: TExifTagID; Index: Integer;
  278. const Default: TExifFraction): TExifFraction; overload;
  279. function GetLongIntValue(TagID: TExifTagID; Index: Integer): TLongIntTagValue; overload;
  280. function GetLongIntValue(TagID: TExifTagID; Index: Integer; Default: LongInt): LongInt; overload;
  281. function GetLongWordValue(TagID: TExifTagID; Index: Integer): TLongWordTagValue; overload;
  282. function GetLongWordValue(TagID: TExifTagID; Index: Integer; Default: LongWord): LongWord; overload;
  283. function GetSmallIntValue(TagID: TExifTagID; Index: Integer; Default: SmallInt;
  284. MinValue: SmallInt = Low(SmallInt); MaxValue: SmallInt = High(SmallInt)): SmallInt;
  285. function GetStringValue(TagID: TExifTagID; const Default: string = ''): string;
  286. function GetWindowsStringValue(TagID: TExifTagID; const Default: UnicodeString = ''): UnicodeString;
  287. function GetWordValue(TagID: TExifTagID; Index: Integer): TWordTagValue; overload;
  288. function GetWordValue(TagID: TExifTagID; Index: Integer; Default: Word;
  289. MinValue: Word = 0; MaxValue: Word = High(Word)): Word; overload;
  290. function IsExtendable: Boolean; inline;
  291. function Remove(ID: TExifTagID): Boolean; overload; //returns True if contained a tag with the specified ID
  292. procedure Remove(const IDs: array of TExifTagID); overload;
  293. function RemovePaddingTag: Boolean; //returns True if contained a padding tag
  294. function SetByteValue(TagID: TExifTagID; Index: Integer; Value: Byte): TExifTag;
  295. procedure SetDateTimeValue(MainID, SubSecsID: TExifTagID; const DateTime: TDateTimeTagValue);
  296. procedure SetFractionValue(TagID: TExifTagID; Index: Integer; const Value: TExifFraction);
  297. function SetLongWordValue(TagID: TExifTagID; Index: Integer; Value: LongWord): TExifTag;
  298. procedure SetSignedFractionValue(TagID: TExifTagID; Index: Integer;
  299. const Value: TExifSignedFraction);
  300. procedure SetStringValue(TagID: TExifTagID; const Value: string);
  301. procedure SetWindowsStringValue(TagID: TExifTagID; const Value: UnicodeString);
  302. function SetWordValue(TagID: TExifTagID; Index: Integer; Value: Word): TExifTag;
  303. function TagExists(ID: TExifTagID; ValidDataTypes: TExifDataTypes =
  304. [Low(TExifDataType)..High(TExifDataType)]; MinElementCount: LongInt = 1;
  305. MaxElementCount: LongInt = MaxLongInt): Boolean;
  306. function TryGetByteValue(TagID: TExifTagID; Index: Integer; var Value): Boolean;
  307. function TryGetLongWordValue(TagID: TExifTagID; Index: Integer; var Value): Boolean;
  308. function TryGetWordValue(TagID: TExifTagID; Index: Integer; var Value): Boolean;
  309. function TryGetStringValue(TagID: TExifTagID; var Value: string): Boolean;
  310. function TryGetWindowsStringValue(TagID: TExifTagID; var Value: UnicodeString): Boolean;
  311. property Count: Integer read GetTagCount;
  312. property Kind: TExifSectionKindEx read FKind;
  313. property LoadErrors: TExifSectionLoadErrors read FLoadErrors write FLoadErrors;
  314. property Modified: Boolean read FModified write FModified;
  315. property Owner: TCustomExifData read FOwner;
  316. end;
  317. TExifSectionClass = class of TExifSection;
  318. TExtendableExifSection = class(TExifSection)
  319. public
  320. function Add(ID: TExifTagID; DataType: TExifDataType;
  321. ElementCount: LongInt = 1): TExifTag;
  322. function AddOrUpdate(ID: TExifTagID; DataType: TExifDataType;
  323. ElementCount: LongInt): TExifTag; overload;
  324. function AddOrUpdate(ID: TExifTagID; DataType: TExifDataType;
  325. ElementCount: LongInt; const Data): TExifTag; overload;
  326. function AddOrUpdate(ID: TExifTagID; DataType: TExifDataType;
  327. const Source: IStreamPersist): TExifTag; overload;
  328. procedure Assign(Source: TExifSection);
  329. procedure CopyTags(Section: TExifSection);
  330. end;
  331. EInvalidMakerNoteFormat = class(EInvalidTiffData);
  332. TObjectTagValue = class(TInterfacedPersistent)
  333. strict private
  334. {$IFDEF WEAKREF}[Weak]{$ENDIF}FOwner: TCustomExifData;
  335. protected
  336. constructor Create(const AOwner: TCustomExifData);
  337. function GetOwner: TPersistent; override;
  338. property Owner: TCustomExifData read FOwner;
  339. public
  340. function MissingOrInvalid: Boolean; virtual; abstract;
  341. {$IFNDEF HasToString}
  342. function ToString: string; virtual;
  343. {$ENDIF}
  344. end;
  345. IEnumToCharMapper = interface
  346. ['{B068C720-1832-4A3F-97F1-0321809EC33B}']
  347. function EnumValueToChar(OrdValue: Integer): AnsiChar;
  348. function CharToEnumValue(Ch: AnsiChar): Integer;
  349. function GetEnumTypeInfo: PTypeInfo;
  350. property EnumTypeInfo: PTypeInfo read GetEnumTypeInfo;
  351. end;
  352. IEnumToCharMapperEx = interface(IEnumToCharMapper)
  353. ['{7911A192-BEA4-42A8-B1A7-EB5749972FB0}']
  354. function GetEnumName(OrdValue: Integer): string;
  355. function GetMinEnumValue: Integer;
  356. function GetMaxEnumValue: Integer;
  357. property MinEnumValue: Integer read GetMinEnumValue;
  358. property MaxEnumValue: Integer read GetMaxEnumValue;
  359. end;
  360. TEnumObjectTagValue = class(TObjectTagValue)
  361. strict private
  362. FValidCharsToAssign: TSysCharSet;
  363. function GetValidCharsToAssign: TSysCharSet;
  364. protected
  365. property ValidCharsToAssign: TSysCharSet read GetValidCharsToAssign;
  366. end;
  367. TExifFlashMode = (efUnknown, efCompulsoryFire, efCompulsorySuppression, efAuto);
  368. TExifStrobeLight = (esNoDetectionFunction, esUndetected, esDetected);
  369. TWordBitEnum = 0..SizeOf(Word) * 8 - 1;
  370. TWordBitSet = set of TWordBitEnum;
  371. TExifFlashInfo = class(TObjectTagValue)
  372. strict private const
  373. FiredBit = 0;
  374. NotPresentBit = 5;
  375. RedEyeReductionBit = 6;
  376. strict private
  377. function GetBitSet: TWordBitSet;
  378. procedure SetBitSet(const Value: TWordBitSet);
  379. function GetFired: Boolean;
  380. procedure SetFired(Value: Boolean);
  381. function GetMode: TExifFlashMode;
  382. procedure SetMode(const Value: TExifFlashMode);
  383. function GetPresent: Boolean;
  384. procedure SetPresent(Value: Boolean);
  385. function GetRedEyeReduction: Boolean;
  386. procedure SetRedEyeReduction(Value: Boolean);
  387. function GetStrobeEnergy: TExifFraction;
  388. procedure SetStrobeEnergy(const Value: TExifFraction);
  389. function GetStrobeLight: TExifStrobeLight;
  390. procedure SetStrobeLight(const Value: TExifStrobeLight);
  391. public
  392. procedure Assign(Source: TPersistent); override;
  393. function MissingOrInvalid: Boolean; override;
  394. property BitSet: TWordBitSet read GetBitSet write SetBitSet stored False;
  395. published
  396. property Fired: Boolean read GetFired write SetFired stored False;
  397. property Mode: TExifFlashMode read GetMode write SetMode stored False;
  398. property Present: Boolean read GetPresent write SetPresent stored False;
  399. property RedEyeReduction: Boolean read GetRedEyeReduction write SetRedEyeReduction stored False;
  400. property StrobeEnergy: TExifFraction read GetStrobeEnergy write SetStrobeEnergy stored False;
  401. property StrobeLight: TExifStrobeLight read GetStrobeLight write SetStrobeLight stored False;
  402. end;
  403. TExifVersionElement = 0..9;
  404. TCustomExifVersion = class abstract(TObjectTagValue)
  405. strict private
  406. function GetAsString: string;
  407. procedure SetAsString(const Value: string);
  408. function GetMajor: TExifVersionElement;
  409. procedure SetMajor(Value: TExifVersionElement);
  410. function GetMinor: TExifVersionElement;
  411. procedure SetMinor(Value: TExifVersionElement);
  412. function GetRelease: TExifVersionElement;
  413. procedure SetRelease(Value: TExifVersionElement);
  414. protected
  415. FMajorIndex: Integer;
  416. FSectionKind: TExifSectionKind;
  417. FStoreAsChar: Boolean;
  418. FTagID: TExifTagID;
  419. FTiffDataType: TTiffDataType;
  420. constructor Create(const AOwner: TCustomExifData);
  421. procedure Initialize; virtual; abstract;
  422. function GetValue(Index: Integer): TExifVersionElement; virtual;
  423. procedure SetValue(Index: Integer; Value: TExifVersionElement); virtual;
  424. public
  425. procedure Assign(Source: TPersistent); override;
  426. procedure Clear; virtual;
  427. function MissingOrInvalid: Boolean; override;
  428. function ToString: string; override;
  429. property AsString: string read GetAsString write SetAsString;
  430. published
  431. property Major: TExifVersionElement read GetMajor write SetMajor stored False;
  432. property Minor: TExifVersionElement read GetMinor write SetMinor stored False;
  433. property Release: TExifVersionElement read GetRelease write SetRelease stored False;
  434. end;
  435. TExifVersion = class(TCustomExifVersion)
  436. strict private
  437. FValues: array[1..3] of TExifVersionElement;
  438. protected
  439. procedure Initialize; override;
  440. function GetValue(Index: Integer): TExifVersionElement; override;
  441. procedure SetValue(Index: Integer; Value: TExifVersionElement); override;
  442. public
  443. constructor Create(const AOwner: TCustomExifData); overload;
  444. constructor Create; overload;
  445. procedure Clear; override;
  446. end;
  447. TFlashPixVersion = class(TCustomExifVersion)
  448. protected
  449. procedure Initialize; override;
  450. end;
  451. TGPSVersion = class(TCustomExifVersion)
  452. protected
  453. procedure Initialize; override;
  454. end;
  455. TInteropVersion = class(TCustomExifVersion)
  456. protected
  457. procedure Initialize; override;
  458. end;
  459. {$Z2}
  460. TTiffOrientation = (toUndefined, toTopLeft, toTopRight, toBottomRight,
  461. toBottomLeft, toLeftTop{i.e., rotated}, toRightTop, toRightBottom, toLeftBottom);
  462. TExifOrientation = TTiffOrientation;
  463. TTiffResolutionUnit = (trNone = 1, trInch, trCentimetre);
  464. TExifResolutionUnit = TTiffResolutionUnit;
  465. TExifColorSpace = (csTagMissing = 0, csRGB = 1, csAdobeRGB = 2, csWideGamutRGB = $FFFD,
  466. csICCProfile = $FFFE, csUncalibrated = $FFFF);
  467. TExifContrast = (cnTagMissing = -1, cnNormal, cnSoft, cnHard);
  468. TExifExposureMode = (exTagMissing = -1, exAuto, exManual, exAutoBracket);
  469. TExifExposureProgram = (eeTagMissing = -1, eeUndefined, eeManual, eeNormal,
  470. eeAperturePriority, eeShutterPriority, eeCreative, eeAction, eePortrait, eeLandscape);
  471. TExifGainControl = (egTagMissing = -1, egNone, egLowGainUp, egHighGainUp, egLowGainDown, egHighGainDown);
  472. TExifLightSource = (elTagMissing = -1, elUnknown, elDaylight, elFluorescent,
  473. elTungsten, elFlash, elFineWeather = 9, elCloudyWeather, elShade, elDaylightFluorescent,
  474. elDayWhiteFluorescent, elCoolWhiteFluorescent, elWhiteFluorescent,
  475. elStandardLightA = 17, elStandardLightB, elStandardLightC, elD55, elD65,
  476. elD75, elD50, elISOStudioTungsten, elOther = 255);
  477. TExifMeteringMode = (emTagMissing = -1, emUnknown, emAverage, emCenterWeightedAverage,
  478. emSpot, emMultiSpot, emPattern, emPartial);
  479. TExifRendering = (erTagMissing = -1, erNormal, erCustom);
  480. TExifSaturation = (euTagMissing = -1, euNormal, euLow, euHigh);
  481. TExifSceneCaptureType = (ecTagMissing = -1, ecStandard, ecLandscape, ecPortrait, ecNightScene);
  482. TExifSensingMethod = (esTagMissing = -1, esMonochrome = 1, esOneChip, esTwoChip,
  483. esThreeChip, esColorSequential, esTrilinear = 7, esColorSequentialLinear); //esMonochrome was esUndefined before 0.9.7
  484. TExifSharpness = (ehTagMissing = -1, ehNormal, ehSoft, ehHard);
  485. TExifSubjectDistanceRange = (edTagMissing = -1, edUnknown, edMacro, edClose, edDistant);
  486. TExifWhiteBalanceMode = (ewTagMissing = -1, ewAuto, ewManual);
  487. {$Z1}
  488. TCustomExifResolution = class(TObjectTagValue)
  489. strict private
  490. FSchema: TXMPNamespace;
  491. FSection: TExifSection;
  492. FXTagID, FYTagID, FUnitTagID: TExifTagID;
  493. FXName, FYName, FUnitName: UnicodeString;
  494. protected
  495. function GetUnit: TExifResolutionUnit; virtual;
  496. function GetX: TExifFraction; virtual;
  497. function GetY: TExifFraction; virtual;
  498. procedure SetUnit(const Value: TExifResolutionUnit); virtual;
  499. procedure SetX(const Value: TExifFraction); virtual;
  500. procedure SetY(const Value: TExifFraction); virtual;
  501. procedure GetTagInfo(var Section: TExifSectionKind;
  502. var XTag, YTag, UnitTag: TExifTagID; var Schema: TXMPNamespace;
  503. var XName, YName, UnitName: UnicodeString); virtual; abstract;
  504. public
  505. constructor Create(const AOwner: TCustomExifData);
  506. procedure Assign(Source: TPersistent); override;
  507. function MissingOrInvalid: Boolean; override;
  508. function ToString: string; override;
  509. property Section: TExifSection read FSection;
  510. published
  511. property X: TExifFraction read GetX write SetX stored False;
  512. property Y: TExifFraction read GetY write SetY stored False;
  513. property Units: TExifResolutionUnit read GetUnit write SetUnit stored False;
  514. end;
  515. TExifResolution = class(TCustomExifResolution) //standalone
  516. strict private
  517. FX, FY: TExifFraction;
  518. FUnit: TExifResolutionUnit;
  519. protected
  520. function GetUnit: TExifResolutionUnit; override;
  521. function GetX: TExifFraction; override;
  522. function GetY: TExifFraction; override;
  523. procedure SetUnit(const Value: TExifResolutionUnit); override;
  524. procedure SetX(const Value: TExifFraction); override;
  525. procedure SetY(const Value: TExifFraction); override;
  526. procedure GetTagInfo(var Section: TExifSectionKind;
  527. var XTag, YTag, UnitTag: TExifTagID; var Schema: TXMPNamespace;
  528. var XName, YName, UnitName: UnicodeString); override;
  529. public
  530. constructor Create;
  531. procedure Assign(Source: TPersistent); override;
  532. function MissingOrInvalid: Boolean; override;
  533. end;
  534. TImageResolution = class(TCustomExifResolution)
  535. protected
  536. procedure GetTagInfo(var Section: TExifSectionKind;
  537. var XTag, YTag, UnitTag: TExifTagID; var Schema: TXMPNamespace;
  538. var XName, YName, UnitName: UnicodeString); override;
  539. end;
  540. TFocalPlaneResolution = class(TCustomExifResolution)
  541. protected
  542. procedure GetTagInfo(var Section: TExifSectionKind;
  543. var XTag, YTag, UnitTag: TExifTagID; var Schema: TXMPNamespace;
  544. var XName, YName, UnitName: UnicodeString); override;
  545. end;
  546. TThumbnailResolution = class(TCustomExifResolution)
  547. protected
  548. procedure GetTagInfo(var Section: TExifSectionKind;
  549. var XTag, YTag, UnitTag: TExifTagID; var Schema: TXMPNamespace;
  550. var XName, YName, UnitName: UnicodeString); override;
  551. end;
  552. TISOSpeedRatings = class(TObjectTagValue)
  553. strict private const
  554. XMPSchema = xsExif;
  555. XMPKind = xpSeqArray;
  556. XMPName = UnicodeString('ISOSpeedRatings');
  557. strict private
  558. function GetAsString: string;
  559. procedure SetAsString(const Value: string);
  560. function GetCount: Integer;
  561. function GetItem(Index: Integer): Word;
  562. procedure SetCount(const Value: Integer);
  563. procedure SetItem(Index: Integer; const Value: Word);
  564. protected
  565. procedure Clear;
  566. function FindTag(VerifyDataType: Boolean; out Tag: TExifTag): Boolean;
  567. public
  568. procedure Assign(Source: TPersistent); override;
  569. function MissingOrInvalid: Boolean; override;
  570. function ToString: string; override;
  571. property Items[Index: Integer]: Word read GetItem write SetItem; default;
  572. published
  573. property AsString: string read GetAsString write SetAsString stored False;
  574. property Count: Integer read GetCount write SetCount stored False;
  575. end;
  576. TExifFileSource = (fsUnknown, fsFilmScanner, fsReflectionPrintScanner, fsDigitalCamera);
  577. TExifSceneType = (esUnknown, esDirectlyPhotographed);
  578. TGPSLatitudeRef = (ltMissingOrInvalid, ltNorth, ltSouth);
  579. TGPSLongitudeRef = (lnMissingOrInvalid, lnWest, lnEast);
  580. TGPSAltitudeRef = (alTagMissing = -1, alAboveSeaLevel, alBelowSeaLevel); //!!!corrected v1.5.2
  581. TGPSStatus = (stMissingOrInvalid, stMeasurementActive, stMeasurementVoid);
  582. TGPSMeasureMode = (mmUnknown, mm2D, mm3D);
  583. TGPSSpeedRef = (srMissingOrInvalid, srKilometresPerHour, srMilesPerHour, srKnots); //Exif spec makes KM/h the default value
  584. TGPSDirectionRef = (drMissingOrInvalid, drTrueNorth, drMagneticNorth);
  585. TGPSDistanceRef = (dsMissingOrInvalid, dsKilometres, dsMiles, dsKnots);
  586. {$Z2}
  587. TGPSDifferential = (dfTagMissing = -1, dfWithoutCorrection, dfCorrectionApplied);
  588. {$Z1}
  589. TCustomGPSFraction = class(TEnumObjectTagValue)
  590. strict private
  591. FMainTagID, FRefTagID: TExifTagID;
  592. function GetDenominator: LongWord;
  593. function GetNumerator: LongWord;
  594. function GetQuotient: Extended;
  595. protected
  596. constructor Create(const AOwner: TCustomExifData;
  597. AMainTagID, ARefTagID: TExifTagID); overload;
  598. function GetValue: TExifFraction; virtual;
  599. procedure SetValue(const Value: TExifFraction); virtual;
  600. function GetRefChar: AnsiChar; virtual;
  601. procedure SetRefChar(const Value: AnsiChar); virtual;
  602. property MainTagID: TExifTagID read FMainTagID;
  603. property RefTagID: TExifTagID read FRefTagID;
  604. public
  605. procedure Assign(Source: TPersistent); override;
  606. function MissingOrInvalid: Boolean; override;
  607. function ToString: string; override;
  608. property Numerator: LongWord read GetNumerator;
  609. property Denominator: LongWord read GetDenominator;
  610. property Quotient: Extended read GetQuotient;
  611. property Ref: AnsiChar read GetRefChar write SetRefChar;
  612. property Value: TExifFraction read GetValue write SetValue;
  613. end;
  614. TGPSFraction = class(TCustomGPSFraction) //standalone
  615. strict private
  616. FRefChar: AnsiChar;
  617. FValue: TExifFraction;
  618. protected
  619. function GetValue: TExifFraction; override;
  620. procedure SetValue(const NewValue: TExifFraction); override;
  621. function GetRefChar: AnsiChar; override;
  622. procedure SetRefChar(const Value: AnsiChar); override;
  623. public
  624. constructor Create;
  625. end;
  626. TGPSAltitude = class(TCustomGPSFraction, IEnumToCharMapper, IEnumToCharMapperEx)
  627. strict private
  628. function GetRef: TGPSAltitudeRef;
  629. procedure SetRef(const Value: TGPSAltitudeRef);
  630. protected
  631. function GetRefChar: AnsiChar; override;
  632. procedure SetRefChar(const Value: AnsiChar); override;
  633. { IEnumToCharMapper/Ex - no RTTI, so need to provide min and max ordinal values explicitly }
  634. function EnumValueToChar(OrdValue: Integer): AnsiChar;
  635. function CharToEnumValue(Ch: AnsiChar): Integer;
  636. function GetEnumTypeInfo: PTypeInfo;
  637. function GetMinEnumValue: Integer;
  638. function GetMaxEnumValue: Integer;
  639. function GetEnumName(OrdValue: Integer): string;
  640. public
  641. function ToString: string; override;
  642. property Ref: TGPSAltitudeRef read GetRef write SetRef;
  643. end;
  644. TGPSSpeed = class(TCustomGPSFraction, IEnumToCharMapper)
  645. strict private
  646. function GetRef: TGPSSpeedRef; inline;
  647. procedure SetRef(const Value: TGPSSpeedRef); inline;
  648. protected
  649. { IEnumToCharMapper }
  650. function EnumValueToChar(OrdValue: Integer): AnsiChar;
  651. function CharToEnumValue(Ch: AnsiChar): Integer;
  652. function GetEnumTypeInfo: PTypeInfo;
  653. public
  654. function ToString: string; override;
  655. property Ref: TGPSSpeedRef read GetRef write SetRef;
  656. end;
  657. TCustomGPSFractionWithDirection = class(TCustomGPSFraction, IEnumToCharMapper)
  658. strict private
  659. function GetRef: TGPSDirectionRef; inline;
  660. procedure SetRef(const Value: TGPSDirectionRef); inline;
  661. protected
  662. { IEnumToCharMapper }
  663. function EnumValueToChar(OrdValue: Integer): AnsiChar;
  664. function CharToEnumValue(Ch: AnsiChar): Integer;
  665. function GetEnumTypeInfo: PTypeInfo;
  666. public
  667. function ToString: string; override;
  668. property Ref: TGPSDirectionRef read GetRef write SetRef;
  669. end;
  670. TGPSTrack = class(TCustomGPSFractionWithDirection)
  671. end;
  672. TGPSImgDirection = class(TCustomGPSFractionWithDirection)
  673. end;
  674. TGPSDestBearing = class(TCustomGPSFractionWithDirection)
  675. end;
  676. TGPSDestDistance = class(TCustomGPSFraction, IEnumToCharMapper)
  677. strict private
  678. function GetRef: TGPSDistanceRef; inline;
  679. procedure SetRef(const Value: TGPSDistanceRef); inline;
  680. protected
  681. { IEnumToCharMapper }
  682. function EnumValueToChar(OrdValue: Integer): AnsiChar;
  683. function CharToEnumValue(Ch: AnsiChar): Integer;
  684. function GetEnumTypeInfo: PTypeInfo;
  685. public
  686. function ToString: string; override;
  687. property Ref: TGPSDistanceRef read GetRef write SetRef;
  688. end;
  689. TCustomGPSCoordinate = class(TEnumObjectTagValue)
  690. strict private
  691. FRefTagID, FTagID: TExifTagID;
  692. FXMPName: UnicodeString;
  693. procedure AssignCoordinate(Source: TCustomGPSCoordinate);
  694. protected
  695. constructor Create(const AOwner: TCustomExifData; const ATagID: TExifTagID); overload;
  696. procedure Assign(const ADegrees, AMinutes, ASeconds: TExifFraction;
  697. ADirectionChar: AnsiChar); reintroduce; overload; virtual;
  698. function GetDirectionChar: AnsiChar; virtual;
  699. function GetValue(Index: Integer): TExifFraction; virtual;
  700. procedure SetDirectionChar(NewChar: AnsiChar); virtual;
  701. function TryGetTag(out Tag: TExifTag): Boolean;
  702. property RefTagID: TExifTagID read FRefTagID;
  703. property TagID: TExifTagID read FTagID;
  704. property XMPName: UnicodeString read FXMPName;
  705. public
  706. procedure Assign(Source: TPersistent); overload; override;
  707. function MissingOrInvalid: Boolean; override;
  708. function ToString: string; override;
  709. property Degrees: TExifFraction index 0 read GetValue;
  710. property Minutes: TExifFraction index 1 read GetValue;
  711. property Seconds: TExifFraction index 2 read GetValue;
  712. property Direction: AnsiChar read GetDirectionChar write SetDirectionChar; //not DirectionChar so that it gets hidden by properly typed version in descendant classes
  713. published
  714. property AsString: string read ToString;
  715. end;
  716. TGPSCoordinate = class(TCustomGPSCoordinate) //standalone
  717. strict private
  718. FDirectionChar: AnsiChar;
  719. FValues: array[0..2] of TExifFraction;
  720. protected
  721. procedure AssignTo(Dest: TPersistent); override;
  722. function GetDirectionChar: AnsiChar; override;
  723. function GetValue(Index: Integer): TExifFraction; override;
  724. procedure SetDirectionChar(NewChar: AnsiChar); override;
  725. public
  726. constructor Create;
  727. procedure Assign(const ADegrees, AMinutes, ASeconds: TExifFraction;
  728. ADirectionChar: AnsiChar); override;
  729. property Degrees write FValues[0];
  730. property Minutes write FValues[1];
  731. property Seconds write FValues[2];
  732. end;
  733. TGPSLatitude = class(TCustomGPSCoordinate, IEnumToCharMapper)
  734. strict private
  735. function GetDirection: TGPSLatitudeRef; inline;
  736. protected
  737. { IEnumToCharMapper }
  738. function EnumValueToChar(OrdValue: Integer): AnsiChar;
  739. function CharToEnumValue(Ch: AnsiChar): Integer;
  740. function GetEnumTypeInfo: PTypeInfo;
  741. public
  742. procedure Assign(const ADegrees, AMinutes, ASeconds: TExifFraction;
  743. ADirection: TGPSLatitudeRef); reintroduce; overload;
  744. procedure Assign(ADegrees, AMinutes: LongWord; const ASeconds: TExifFraction;
  745. ADirection: TGPSLatitudeRef); reintroduce; overload; inline;
  746. procedure Assign(ADegrees, AMinutes: LongWord; const ASeconds: Currency;
  747. ADirection: TGPSLatitudeRef); reintroduce; overload; inline;
  748. procedure Assign(ADegrees, AMinutes, ASeconds: LongWord;
  749. ADirection: TGPSLatitudeRef); reintroduce; overload; inline;
  750. property Direction: TGPSLatitudeRef read GetDirection;
  751. end;
  752. TGPSLongitude = class(TCustomGPSCoordinate, IEnumToCharMapper)
  753. strict private
  754. function GetDirection: TGPSLongitudeRef; inline;
  755. protected
  756. { IEnumToCharMapper }
  757. function EnumValueToChar(OrdValue: Integer): AnsiChar;
  758. function CharToEnumValue(Ch: AnsiChar): Integer;
  759. function GetEnumTypeInfo: PTypeInfo;
  760. public
  761. procedure Assign(const ADegrees, AMinutes, ASeconds: TExifFraction;
  762. ADirection: TGPSLongitudeRef); reintroduce; overload;
  763. procedure Assign(ADegrees, AMinutes: LongWord; const ASeconds: TExifFraction;
  764. ADirection: TGPSLongitudeRef); reintroduce; overload; inline;
  765. procedure Assign(ADegrees, AMinutes: LongWord; const ASeconds: Currency;
  766. ADirection: TGPSLongitudeRef); reintroduce; overload; inline;
  767. procedure Assign(ADegrees, AMinutes, ASeconds: LongWord;
  768. ADirection: TGPSLongitudeRef); reintroduce; overload; inline;
  769. property Direction: TGPSLongitudeRef read GetDirection;
  770. end;
  771. { To add support for a different MakerNote format, you need to write a descendent of
  772. TExifMakerNote, implementing the protected version of FormatIsOK and probably
  773. GetIFDInfo too, before registering it via TExifData.RegisterMakerNoteType. }
  774. TExifDataOffsetsType = (doFromExifStart, doFromMakerNoteStart, doFromIFDStart,
  775. doCustomFormat); //!!!added doCustomFormat v1.5.0
  776. TExifMakerNote = class abstract
  777. strict private
  778. FDataOffsetsType: TExifDataOffsetsType;
  779. FEndianness: TEndianness;
  780. FTags: TExifSection;
  781. protected
  782. class function FormatIsOK(SourceTag: TExifTag;
  783. out HeaderSize: Integer): Boolean; overload; virtual; abstract;
  784. procedure GetIFDInfo(SourceTag: TExifTag; var ProbableEndianness: TEndianness;
  785. var DataOffsetsType: TExifDataOffsetsType); virtual;
  786. function GetFractionValue(TagID: Integer): TExifFraction;
  787. function GetTagAsString(TagID: Integer): string;
  788. //procedure RewriteSourceTag(Tag: TExifTag); virtual;
  789. //procedure WriteHeader(Stream: TStream); virtual; abstract;
  790. //procedure SaveToStream(Stream: TStream; const StartPos: Int64);
  791. public
  792. constructor Create(ASection: TExifSection);
  793. class function FormatIsOK(SourceTag: TExifTag): Boolean; overload;
  794. property DataOffsetsType: TExifDataOffsetsType read FDataOffsetsType;
  795. property Endianness: TEndianness read FEndianness;
  796. property Tags: TExifSection read FTags;
  797. end;
  798. TExifMakerNoteClass = class of TExifMakerNote;
  799. TUnrecognizedMakerNote = class sealed(TExifMakerNote)
  800. protected
  801. class function FormatIsOK(SourceTag: TExifTag; out HeaderSize: Integer): Boolean; override;
  802. end;
  803. THeaderlessMakerNote = class(TExifMakerNote) //special type tried as a last resort; also serves as a
  804. protected //nominal base class for a few of the concrete implementations
  805. class function FormatIsOK(SourceTag: TExifTag; out HeaderSize: Integer): Boolean; override;
  806. end;
  807. TAppleMakerNote = class(TExifMakerNote)
  808. protected
  809. const Header: array[0..13] of AnsiChar = 'Apple iOS'#0#0#1'MM';
  810. class function FormatIsOK(SourceTag: TExifTag; out HeaderSize: Integer): Boolean; override;
  811. procedure GetIFDInfo(SourceTag: TExifTag; var ProbableEndianness: TEndianness;
  812. var DataOffsetsType: TExifDataOffsetsType); override;
  813. end;
  814. TCanonMakerNote = class(THeaderlessMakerNote)
  815. protected
  816. class function FormatIsOK(SourceTag: TExifTag; out HeaderSize: Integer): Boolean; override;
  817. procedure GetIFDInfo(SourceTag: TExifTag; var ProbableEndianness: TEndianness;
  818. var DataOffsetsType: TExifDataOffsetsType); override;
  819. end;
  820. TCasioMakerNote = class(THeaderlessMakerNote)
  821. protected
  822. class function FormatIsOK(SourceTag: TExifTag; out HeaderSize: Integer): Boolean; override;
  823. end;
  824. TCasio2MakerNote = class(TExifMakerNote)
  825. protected
  826. const Header: array[0..5] of AnsiChar = 'QVC';
  827. class function FormatIsOK(SourceTag: TExifTag; out HeaderSize: Integer): Boolean; override;
  828. end;
  829. TKodakMakerNote = class(TExifMakerNote) //!!!work in very early progress
  830. public type
  831. TTagSpec = record
  832. DataType: TTiffDataType;
  833. ElementCount: Byte;
  834. constructor Create(ADataType: TTiffDataType; AElementCount: Byte = 1);
  835. end;
  836. class var TagSpecs: array of TTagSpec;
  837. class procedure InitializeTagSpecs; static;
  838. protected
  839. const HeaderSize = 8;
  840. const BigEndianHeader: array[0..HeaderSize - 1] of AnsiChar = 'KDK INFO';
  841. class function FormatIsOK(SourceTag: TExifTag; out HeaderSize: Integer): Boolean; override;
  842. procedure GetIFDInfo(SourceTag: TExifTag; var Endianness: TEndianness;
  843. var DataOffsetsType: TExifDataOffsetsType); override;
  844. end experimental;
  845. TKonicaMinoltaMakerNote = class(THeaderlessMakerNote)
  846. protected
  847. class function FormatIsOK(SourceTag: TExifTag; out HeaderSize: Integer): Boolean; override;
  848. end;
  849. TPanasonicMakerNote = class(TExifMakerNote)
  850. protected
  851. const Header: array[0..11] of AnsiChar = 'Panasonic';
  852. class function FormatIsOK(SourceTag: TExifTag; out HeaderSize: Integer): Boolean; override;
  853. procedure GetIFDInfo(SourceTag: TExifTag; var ProbableEndianness: TEndianness;
  854. var DataOffsetsType: TExifDataOffsetsType); override;
  855. end;
  856. TPentaxMakerNote = class(TExifMakerNote) //won't actually parse the structure, just identify it
  857. protected
  858. const Header: array[0..3] of AnsiChar = 'AOC'#0;
  859. class function FormatIsOK(SourceTag: TExifTag; out HeaderSize: Integer): Boolean; override;
  860. end;
  861. TNikonType1MakerNote = class(TExifMakerNote)
  862. protected
  863. const Header: array[0..7] of AnsiChar = 'Nikon'#0#1#0;
  864. class function FormatIsOK(SourceTag: TExifTag; out HeaderSize: Integer): Boolean; override;
  865. end;
  866. TNikonType2MakerNote = class(THeaderlessMakerNote)
  867. protected
  868. class function FormatIsOK(SourceTag: TExifTag; out HeaderSize: Integer): Boolean; override;
  869. end;
  870. TNikonType3MakerNote = class(TExifMakerNote)
  871. protected
  872. const HeaderStart: array[0..6] of AnsiChar = 'Nikon'#0#2;
  873. class function FormatIsOK(SourceTag: TExifTag; out HeaderSize: Integer): Boolean; override;
  874. procedure GetIFDInfo(SourceTag: TExifTag; var ProbableEndianness: TEndianness;
  875. var DataOffsetsType: TExifDataOffsetsType); override;
  876. public
  877. property ColorMode: string index ttNikonType3ColorMode read GetTagAsString;
  878. property Quality: string index ttNikonType3Quality read GetTagAsString;
  879. property WhiteBalance: string index ttNikonType3WhiteBalance read GetTagAsString;
  880. property Sharpening: string index ttNikonType3Sharpening read GetTagAsString;
  881. property FocusMode: string index ttNikonType3FocusMode read GetTagAsString;
  882. property FlashSetting: string index ttNikonType3FlashSetting read GetTagAsString;
  883. property AutoFlashMode: string index ttNikonType3AutoFlashMode read GetTagAsString;
  884. property MiscRatio: TExifFraction index ttNikonType3MiscRatio read GetFractionValue;
  885. property ISOSelection: string index ttNikonType3ISOSelection read GetTagAsString;
  886. property AutoExposureBracketComp: TExifFraction index ttNikonType3AutoExposureBracketComp read GetFractionValue;
  887. property SerialNumber: string index ttNikonType3SerialNumber read GetTagAsString;
  888. property ImageAdjustment: string index ttNikonType3ImageAdjustment read GetTagAsString;
  889. property ToneComp: string index ttNikonType3ToneComp read GetTagAsString;
  890. property AuxiliaryLens: string index ttNikonType3AuxiliaryLens read GetTagAsString;
  891. property DigitalZoom: TExifFraction index ttNikonType3DigitalZoom read GetFractionValue;
  892. property SceneMode: string index ttNikonType3SceneMode read GetTagAsString;
  893. property LightSource: string index ttNikonType3LightSource read GetTagAsString;
  894. property NoiseReduction: string index ttNikonType3NoiseReduction read GetTagAsString;
  895. property SceneAssist: string index ttNikonType3SceneAssist read GetTagAsString;
  896. property CameraSerialNumber: string index ttNikonType3CameraSerialNumber read GetTagAsString;
  897. property Saturation: string index ttNikonType3Saturation read GetTagAsString;
  898. property DigitalVarProgram: string index ttNikonType3DigitalVarProg read GetTagAsString;
  899. property ImageStabilization: string index ttNikonType3ImageStabilization read GetTagAsString;
  900. property AFResponse: string index ttNikonType3AFResponse read GetTagAsString;
  901. property CaptureVersion: string index ttNikonType3CaptureVersion read GetTagAsString;
  902. end;
  903. TSonyMakerNote = class(TExifMakerNote)
  904. protected
  905. const Header: array[0..7] of AnsiChar = 'SONY DSC';
  906. class function FormatIsOK(SourceTag: TExifTag; out HeaderSize: Integer): Boolean; override;
  907. procedure GetIFDInfo(SourceTag: TExifTag; var ProbableEndianness: TEndianness;
  908. var DataOffsetsType: TExifDataOffsetsType); override;
  909. end;
  910. EInvalidExifData = class(ECCRExifException);
  911. TJPEGMetaDataKind = (mkExif, mkIPTC, mkXMP);
  912. TJPEGMetadataKinds = set of TJPEGMetadataKind;
  913. TCustomExifData = class(TComponent)
  914. public type
  915. TEnumerator = record
  916. strict private
  917. FClient: TCustomExifData;
  918. FDoneFirst: Boolean;
  919. FSection: TExifSectionKind;
  920. function GetCurrent: TExifSection; {$IFDEF CanInline}inline;{$ENDIF}
  921. public
  922. constructor Create(AClient: TCustomExifData);
  923. function MoveNext: Boolean;
  924. property Current: TExifSection read GetCurrent;
  925. end;
  926. TMakerNoteTypePriority = (mtTestForLast, mtTestForFirst);
  927. strict private class var
  928. Diag35mm: Extended;
  929. FMakerNoteClasses: TClassList;
  930. private
  931. class procedure InitializeClass(const MakerNoteClasses: array of TExifMakerNoteClass);
  932. class procedure FinalizeClass;
  933. procedure SetGPSDestBearing(const Value: TGPSDestBearing);
  934. procedure SetGPSImgDirection(const Value: TGPSImgDirection);
  935. procedure SetGPSTrack(const Value: TGPSTrack);
  936. strict private
  937. FAlwaysWritePreciseTimes: Boolean;
  938. FChangedWhileUpdating: Boolean;
  939. FEmbeddedIPTC: TIPTCData;
  940. FEndianness: TEndianness;
  941. FEnforceASCII: Boolean;
  942. FEnsureEnumsInRange: Boolean;
  943. FExifVersion: TCustomExifVersion;
  944. FFlash: TExifFlashInfo;
  945. FFlashPixVersion: TCustomExifVersion;
  946. FFocalPlaneResolution: TCustomExifResolution;
  947. FGPSAltitude: TGPSAltitude;
  948. FGPSDestBearing: TGPSDestBearing;
  949. FGPSDestDistance: TGPSDestDistance;
  950. FGPSImgDirection: TGPSImgDirection;
  951. FGPSLatitude, FGPSDestLatitude: TGPSLatitude;
  952. FGPSLongitude, FGPSDestLongitude: TGPSLongitude;
  953. FGPSSpeed: TGPSSpeed;
  954. FGPSTrack: TGPSTrack;
  955. FGPSVersion: TCustomExifVersion;
  956. FInteropVersion: TCustomExifVersion;
  957. FISOSpeedRatings: TISOSpeedRatings;
  958. FMakerNoteType: TExifMakerNoteClass;
  959. FMakerNoteValue: TExifMakerNote;
  960. FOffsetBase: Int64;
  961. FModified: Boolean;
  962. FResolution: TCustomExifResolution;
  963. FSections: array[TExifSectionKind] of TExifSection;
  964. FThumbnailOrNil: TJPEGImage;
  965. FThumbnailResolution: TCustomExifResolution;
  966. FUpdateCount: Integer;
  967. FXMPPacket: TXMPPacket;
  968. FOnChange: TNotifyEvent;
  969. procedure SetEndianness(Value: TEndianness);
  970. function GetMakerNote: TExifMakerNote;
  971. function GetSection(Section: TExifSectionKind): TExifSection; //inline;
  972. procedure SetModified(Value: Boolean);
  973. function GetThumbnail: TJPEGImage;
  974. procedure SetThumbnail(Value: TJPEGImage);
  975. procedure ThumbnailChanged(Sender: TObject);
  976. function GetDateTime: TDateTimeTagValue;
  977. procedure SetDateTime(const Value: TDateTimeTagValue);
  978. function GetGeneralString(TagID: Integer): string;
  979. procedure SetGeneralString(TagID: Integer; const Value: string);
  980. function GetGeneralWinString(TagID: Integer): UnicodeString;
  981. procedure SetGeneralWinString(TagID: Integer; const Value: UnicodeString);
  982. function GetDetailsDateTime(TagID: Integer): TDateTimeTagValue;
  983. procedure SetDetailsDateTime(TagID: Integer; const Value: TDateTimeTagValue);
  984. function GetDetailsFraction(TagID: Integer): TExifFraction;
  985. procedure SetDetailsFraction(TagID: Integer; const Value: TExifFraction);
  986. function GetDetailsSFraction(TagID: Integer): TExifSignedFraction;
  987. procedure SetDetailsSFraction(TagID: Integer; const Value: TExifSignedFraction);
  988. function GetOffsetSchema: TLongIntTagValue;
  989. procedure SetOffsetSchema(const Value: TLongIntTagValue);
  990. function GetDetailsLongWord(TagID: Integer): TLongWordTagValue;
  991. function GetDetailsString(TagID: Integer): string;
  992. procedure SetDetailsString(TagID: Integer; const Value: string);
  993. function GetAuthor: UnicodeString;
  994. procedure SetAuthor(const Value: UnicodeString);
  995. function GetComments: UnicodeString;
  996. procedure SetComments(const Value: UnicodeString);
  997. function GetUserRating: TWindowsStarRating;
  998. procedure SetUserRating(const Value: TWindowsStarRating);
  999. procedure SetFlash(Value: TExifFlashInfo);
  1000. procedure SetFocalPlaneResolution(Value: TCustomExifResolution);
  1001. procedure SetResolution(Value: TCustomExifResolution);
  1002. procedure SetThumbnailResolution(Value: TCustomExifResolution);
  1003. procedure SetGPSVersion(Value: TCustomExifVersion);
  1004. procedure SetGPSAltitude(const Value: TGPSAltitude);
  1005. procedure SetGPSLatitude(Value: TGPSLatitude);
  1006. procedure SetGPSLongitude(Value: TGPSLongitude);
  1007. function GetGPSDateTimeUTC: TDateTimeTagValue;
  1008. procedure SetGPSDateTimeUTC(const Value: TDateTimeTagValue);
  1009. function GetGPSTimeStamp(const Index: Integer): TExifFraction;
  1010. procedure SetGPSTimeStamp(const Index: Integer; const Value: TExifFraction);
  1011. function GetGPSString(TagID: Integer): string;
  1012. procedure SetGPSString(TagID: Integer; const Value: string);
  1013. function GetGPSStatus: TGPSStatus;
  1014. procedure SetGPSStatus(const Value: TGPSStatus);
  1015. function GetGPSMeasureMode: TGPSMeasureMode;
  1016. procedure SetGPSMeasureMode(const Value: TGPSMeasureMode);
  1017. procedure SetGPSSpeed(const Value: TGPSSpeed);
  1018. procedure SetGPSDestLatitude(Value: TGPSLatitude);
  1019. procedure SetGPSDestLongitude(Value: TGPSLongitude);
  1020. procedure SetGPSDestDistance(const Value: TGPSDestDistance);
  1021. function GetGPSDifferential: TGPSDifferential;
  1022. procedure SetGPSDifferential(Value: TGPSDifferential);
  1023. function GetColorSpace: TExifColorSpace;
  1024. procedure SetColorSpace(Value: TExifColorSpace);
  1025. function GetContrast: TExifContrast;
  1026. procedure SetContrast(Value: TExifContrast);
  1027. function GetOrientation(SectionKind: Integer): TExifOrientation;
  1028. procedure SetOrientation(SectionKind: Integer; Value: TExifOrientation);
  1029. procedure SetExifVersion(Value: TCustomExifVersion);
  1030. procedure SetFlashPixVersion(Value: TCustomExifVersion);
  1031. procedure SetInteropVersion(Value: TCustomExifVersion);
  1032. function GetExposureProgram: TExifExposureProgram;
  1033. procedure SetExposureProgram(const Value: TExifExposureProgram);
  1034. function GetFileSource: TExifFileSource;
  1035. procedure SetFileSource(const Value: TExifFileSource);
  1036. function GetLightSource: TExifLightSource;
  1037. procedure SetLightSource(const Value: TExifLightSource);
  1038. function GetMeteringMode: TExifMeteringMode;
  1039. procedure SetMeteringMode(const Value: TExifMeteringMode);
  1040. function GetSaturation: TExifSaturation;
  1041. procedure SetSaturation(Value: TExifSaturation);
  1042. function GetSceneType: TExifSceneType;
  1043. procedure SetSceneType(Value: TExifSceneType);
  1044. function GetSensingMethod: TExifSensingMethod;
  1045. procedure SetSensingMethod(const Value: TExifSensingMethod);
  1046. function GetSharpness: TExifSharpness;
  1047. procedure SetSharpness(Value: TExifSharpness);
  1048. function GetSubjectLocation: TSmallPoint;
  1049. procedure SetSubjectLocation(const Value: TSmallPoint);
  1050. function GetRendering: TExifRendering;
  1051. function GetFocalLengthIn35mmFilm: TWordTagValue;
  1052. function GetExposureMode: TExifExposureMode;
  1053. function GetSceneCaptureType: TExifSceneCaptureType;
  1054. function GetWhiteBalance: TExifWhiteBalanceMode;
  1055. procedure SetRendering(const Value: TExifRendering);
  1056. procedure SetFocalLengthIn35mmFilm(const Value: TWordTagValue);
  1057. procedure SetExposureMode(const Value: TExifExposureMode);
  1058. procedure SetSceneCaptureType(const Value: TExifSceneCaptureType);
  1059. procedure SetWhiteBalance(const Value: TExifWhiteBalanceMode);
  1060. function GetGainControl: TExifGainControl;
  1061. procedure SetGainControl(const Value: TExifGainControl);
  1062. function GetSubjectDistanceRange: TExifSubjectDistanceRange;
  1063. procedure SetSubjectDistanceRange(Value: TExifSubjectDistanceRange);
  1064. procedure SetDetailsByteEnum(ID: TExifTagID; const XMPName: UnicodeString; const Value);
  1065. procedure SetDetailsWordEnum(ID: TExifTagID; const XMPName: UnicodeString; const Value);
  1066. procedure SetExifImageSize(ID: Integer; const NewValue: TLongWordTagValue);
  1067. function GetInteropTypeName: string;
  1068. procedure SetInteropTypeName(const Value: string);
  1069. procedure SetISOSpeedRatings(Value: TISOSpeedRatings);
  1070. function GetXMPWritePolicy: TXMPWritePolicy;
  1071. procedure SetXMPWritePolicy(Value: TXMPWritePolicy);
  1072. strict protected
  1073. FMetadataInSource: TJPEGMetadataKinds;
  1074. FXMPSegmentPosition, FXMPPacketSizeInSource: Int64;
  1075. property MetadataInSource: TJPEGMetadataKinds read FMetadataInSource; //set in LoadFromGraphic
  1076. protected
  1077. const MaxThumbnailSize = $F000;
  1078. class function SectionClass: TExifSectionClass; virtual;
  1079. procedure AddFromStream(Stream: TStream; TiffImageSource: Boolean = False);
  1080. procedure Changed(Section: TExifSection); virtual;
  1081. function GetEmpty: Boolean;
  1082. function GetGPSFraction(TagID: Integer): TExifFraction;
  1083. procedure SetGPSFraction(TagID: Integer; const Value: TExifFraction);
  1084. function LoadFromGraphic(Stream: TStream): Boolean;
  1085. procedure ResetMakerNoteType;
  1086. property OffsetBase: Int64 read FOffsetBase;
  1087. property Thumbnail: TJPEGImage read GetThumbnail write SetThumbnail stored False;
  1088. public
  1089. class procedure RegisterMakerNoteType(AClass: TExifMakerNoteClass;
  1090. Priority: TMakerNoteTypePriority = mtTestForFirst);
  1091. class procedure RegisterMakerNoteTypes(const AClasses: array of TExifMakerNoteClass;
  1092. Priority: TMakerNoteTypePriority = mtTestForFirst);
  1093. class procedure UnregisterMakerNoteType(AClass: TExifMakerNoteClass);
  1094. public
  1095. constructor Create(AOwner: TComponent = nil); overload; override;
  1096. destructor Destroy; override;
  1097. function GetEnumerator: TEnumerator;
  1098. procedure Clear(XMPPacketToo: Boolean = True);
  1099. procedure BeginUpdate;
  1100. procedure EndUpdate;
  1101. procedure GetKeywords(Dest: TStrings); overload;
  1102. procedure SetKeywords(const NewWords: array of UnicodeString); overload;
  1103. procedure SetKeywords(NewWords: TStrings); overload;
  1104. function HasMakerNote: Boolean;
  1105. function HasThumbnail: Boolean;
  1106. procedure Rewrite;
  1107. procedure SetAllDateTimeValues(const NewValue: TDateTimeTagValue);
  1108. function ShutterSpeedInMSecs: Extended;
  1109. function Updating: Boolean; reintroduce; inline;
  1110. property EmbeddedIPTC: TIPTCData read FEmbeddedIPTC;
  1111. property Endianness: TEndianness read FEndianness write SetEndianness;
  1112. property MakerNote: TExifMakerNote read GetMakerNote;
  1113. property Modified: Boolean read FModified write SetModified;
  1114. property Sections[Section: TExifSectionKind]: TExifSection read GetSection; default;
  1115. property XMPPacket: TXMPPacket read FXMPPacket;
  1116. published
  1117. property AlwaysWritePreciseTimes: Boolean read FAlwaysWritePreciseTimes write FAlwaysWritePreciseTimes default False;
  1118. property Empty: Boolean read GetEmpty;
  1119. property EnforceASCII: Boolean read FEnforceASCII write FEnforceASCII default True;
  1120. property EnsureEnumsInRange: Boolean read FEnsureEnumsInRange write FEnsureEnumsInRange default True;
  1121. property XMPWritePolicy: TXMPWritePolicy read GetXMPWritePolicy write SetXMPWritePolicy default xwUpdateIfExists;
  1122. property OnChange: TNotifyEvent read FOnChange write FOnChange;
  1123. { main dir tags }
  1124. property CameraMake: string index ttMake read GetGeneralString write SetGeneralString stored False;
  1125. property CameraModel: string index ttModel read GetGeneralString write SetGeneralString stored False;
  1126. property Copyright: string index ttCopyright read GetGeneralString write SetGeneralString stored False;
  1127. property DateTime: TDateTimeTagValue read GetDateTime write SetDateTime stored False;
  1128. property ImageDescription: string index ttImageDescription read GetGeneralString write SetGeneralString stored False;
  1129. property Orientation: TExifOrientation index Ord(esGeneral) read GetOrientation write SetOrientation stored False;
  1130. property Resolution: TCustomExifResolution read FResolution write SetResolution stored False;
  1131. property Software: string index ttSoftware read GetGeneralString write SetGeneralString stored False;
  1132. { main dir tags set by Windows Explorer (XP+) }
  1133. property Author: UnicodeString read GetAuthor write SetAuthor stored False; //falls back to ttArtist if nec
  1134. property Comments: UnicodeString read GetComments write SetComments stored False; //falls back to ttUserComment in the Exif IFD if necessary
  1135. property Keywords: UnicodeString index ttWindowsKeywords read GetGeneralWinString write SetGeneralWinString stored False; //see also Get/SetKeywords
  1136. property Subject: UnicodeString index ttWindowsSubject read GetGeneralWinString write SetGeneralWinString stored False;
  1137. property Title: UnicodeString index ttWindowsTitle read GetGeneralWinString write SetGeneralWinString stored False;
  1138. property UserRating: TWindowsStarRating read GetUserRating write SetUserRating stored False;
  1139. { sub dir tags }
  1140. property ApertureValue: TExifFraction index ttApertureValue read GetDetailsFraction write SetDetailsFraction stored False;
  1141. property BodySerialNumber: string index ttBodySerialNumber read GetDetailsString write SetDetailsString stored False;
  1142. property BrightnessValue: TExifSignedFraction index ttBrightnessValue read GetDetailsSFraction write SetDetailsSFraction stored False;
  1143. property CameraOwnerName: string index ttCameraOwnerName read GetDetailsString write SetDetailsString stored False;
  1144. property ColorSpace: TExifColorSpace read GetColorSpace write SetColorSpace stored False;
  1145. property Contrast: TExifContrast read GetContrast write SetContrast stored False;
  1146. property CompressedBitsPerPixel: TExifFraction index ttCompressedBitsPerPixel read GetDetailsFraction write SetDetailsFraction stored False;
  1147. property DateTimeOriginal: TDateTimeTagValue index ttDateTimeOriginal read GetDetailsDateTime write SetDetailsDateTime stored False;
  1148. property DateTimeDigitized: TDateTimeTagValue index ttDateTimeDigitized read GetDetailsDateTime write SetDetailsDateTime stored False;
  1149. property DigitalZoomRatio: TExifFraction index ttDigitalZoomRatio read GetDetailsFraction write SetDetailsFraction stored False;
  1150. property ExifVersion: TCustomExifVersion read FExifVersion write SetExifVersion stored False;
  1151. property ExifImageWidth: TLongWordTagValue index ttExifImageWidth read GetDetailsLongWord write SetExifImageSize stored False;
  1152. property ExifImageHeight: TLongWordTagValue index ttExifImageHeight read GetDetailsLongWord write SetExifImageSize stored False;
  1153. property ExposureBiasValue: TExifSignedFraction index ttExposureBiasValue read GetDetailsSFraction write SetDetailsSFraction stored False;
  1154. property ExposureIndex: TExifFraction index ttExposureIndex read GetDetailsFraction write SetDetailsFraction stored False; //old Kodak camera tag
  1155. property ExposureMode: TExifExposureMode read GetExposureMode write SetExposureMode stored False;
  1156. property ExposureProgram: TExifExposureProgram read GetExposureProgram write SetExposureProgram stored False;
  1157. property ExposureTime: TExifFraction index ttExposureTime read GetDetailsFraction write SetDetailsFraction stored False; //in secs
  1158. property FileSource: TExifFileSource read GetFileSource write SetFileSource stored False;
  1159. property Flash: TExifFlashInfo read FFlash write SetFlash stored False;
  1160. property FlashPixVersion: TCustomExifVersion read FFlashPixVersion write SetFlashPixVersion stored False;
  1161. property FNumber: TExifFraction index ttFNumber read GetDetailsFraction write SetDetailsFraction stored False;
  1162. property FocalLength: TExifFraction index ttFocalLength read GetDetailsFraction write SetDetailsFraction stored False;
  1163. property FocalLengthIn35mmFilm: TWordTagValue read GetFocalLengthIn35mmFilm write SetFocalLengthIn35mmFilm stored False;
  1164. property FocalPlaneResolution: TCustomExifResolution read FFocalPlaneResolution write SetFocalPlaneResolution stored False;
  1165. property GainControl: TExifGainControl read GetGainControl write SetGainControl stored False;
  1166. property ImageUniqueID: string index ttImageUniqueID read GetDetailsString write SetDetailsString stored False;
  1167. property ISOSpeedRatings: TISOSpeedRatings read FISOSpeedRatings write SetISOSpeedRatings;
  1168. property LensMake: string index ttLensMake read GetDetailsString write SetDetailsString stored False;
  1169. property LensModel: string index ttLensModel read GetDetailsString write SetDetailsString stored False;
  1170. property LensSerialNumber: string index ttLensSerialNumber read GetDetailsString write SetDetailsString stored False;
  1171. property LightSource: TExifLightSource read GetLightSource write SetLightSource stored False;
  1172. property MaxApertureValue: TExifFraction index ttMaxApertureValue read GetDetailsFraction write SetDetailsFraction stored False;
  1173. property MeteringMode: TExifMeteringMode read GetMeteringMode write SetMeteringMode stored False;
  1174. property OffsetSchema: TLongIntTagValue read GetOffsetSchema write SetOffsetSchema stored False;
  1175. property RelatedSoundFile: string index ttRelatedSoundFile read GetDetailsString write SetDetailsString stored False;
  1176. property Rendering: TExifRendering read GetRendering write SetRendering stored False;
  1177. property Saturation: TExifSaturation read GetSaturation write SetSaturation stored False;
  1178. property SceneCaptureType: TExifSceneCaptureType read GetSceneCaptureType write SetSceneCaptureType stored False;
  1179. property SceneType: TExifSceneType read GetSceneType write SetSceneType stored False;
  1180. property SensingMethod: TExifSensingMethod read GetSensingMethod write SetSensingMethod stored False;
  1181. property Sharpness: TExifSharpness read GetSharpness write SetSharpness stored False;
  1182. property ShutterSpeedValue: TExifSignedFraction index ttShutterSpeedValue read GetDetailsSFraction write SetDetailsSFraction stored False; //in APEX; for display, you may well prefer to use ShutterSpeedInMSecs
  1183. property SpectralSensitivity: string index ttSpectralSensitivity read GetDetailsString write SetDetailsString;
  1184. property SubjectDistance: TExifFraction index ttSubjectDistance read GetDetailsFraction write SetDetailsFraction stored False;
  1185. property SubjectDistanceRange: TExifSubjectDistanceRange read GetSubjectDistanceRange write SetSubjectDistanceRange stored False;
  1186. property SubjectLocation: TSmallPoint read GetSubjectLocation write SetSubjectLocation stored False;
  1187. property WhiteBalanceMode: TExifWhiteBalanceMode read GetWhiteBalance write SetWhiteBalance stored False;
  1188. { sub dir tags whose data are rolled into the DateTime properties, so don't display them for the sake of it }
  1189. property SubsecTime: string index ttSubsecTime read GetDetailsString write SetDetailsString stored False;
  1190. property SubsecTimeOriginal: string index ttSubsecTimeOriginal read GetDetailsString write SetDetailsString stored False;
  1191. property SubsecTimeDigitized: string index ttSubsecTimeDigitized read GetDetailsString write SetDetailsString stored False;
  1192. { Interop }
  1193. property InteropTypeName: string read GetInteropTypeName write SetInteropTypeName stored False;
  1194. property InteropVersion: TCustomExifVersion read FInteropVersion write SetInteropVersion stored False;
  1195. { GPS }
  1196. property GPSVersion: TCustomExifVersion read FGPSVersion write SetGPSVersion stored False;
  1197. property GPSLatitude: TGPSLatitude read FGPSLatitude write SetGPSLatitude stored False;
  1198. property GPSLongitude: TGPSLongitude read FGPSLongitude write SetGPSLongitude stored False;
  1199. property GPSAltitude: TGPSAltitude read FGPSAltitude write SetGPSAltitude stored False;
  1200. property GPSSatellites: string index ttGPSSatellites read GetGPSString write SetGPSString stored False;
  1201. property GPSStatus: TGPSStatus read GetGPSStatus write SetGPSStatus stored False;
  1202. property GPSMeasureMode: TGPSMeasureMode read GetGPSMeasureMode write SetGPSMeasureMode stored False;
  1203. property GPSDOP: TExifFraction index ttGPSDOP read GetGPSFraction write SetGPSFraction stored False;
  1204. property GPSSpeed: TGPSSpeed read FGPSSpeed write SetGPSSpeed stored False;
  1205. property GPSTrack: TGPSTrack read FGPSTrack write SetGPSTrack stored False;
  1206. property GPSImgDirection: TGPSImgDirection read FGPSImgDirection write SetGPSImgDirection stored False;
  1207. property GPSMapDatum: string index ttGPSMapDatum read GetGPSString write SetGPSString stored False;
  1208. property GPSDestLatitude: TGPSLatitude read FGPSDestLatitude write SetGPSDestLatitude stored False;
  1209. property GPSDestLongitude: TGPSLongitude read FGPSDestLongitude write SetGPSDestLongitude stored False;
  1210. property GPSDestBearing: TGPSDestBearing read FGPSDestBearing write SetGPSDestBearing stored False;
  1211. property GPSDestDistance: TGPSDestDistance read FGPSDestDistance write SetGPSDestDistance stored False;
  1212. property GPSDifferential: TGPSDifferential read GetGPSDifferential write SetGPSDifferential stored False;
  1213. property GPSDateTimeUTC: TDateTimeTagValue read GetGPSDateTimeUTC write SetGPSDateTimeUTC stored False;
  1214. { GPS tags whose data are rolled into the GPSDataTimeUTC property, so don't display them for the sake of it }
  1215. property GPSDateStamp: string index ttGPSDateStamp read GetGPSString write SetGPSString stored False;
  1216. property GPSTimeStampHour: TExifFraction index 0 read GetGPSTimeStamp write SetGPSTimeStamp stored False;
  1217. property GPSTimeStampMinute: TExifFraction index 1 read GetGPSTimeStamp write SetGPSTimeStamp stored False;
  1218. property GPSTimeStampSecond: TExifFraction index 2 read GetGPSTimeStamp write SetGPSTimeStamp stored False;
  1219. { thumbnail tags }
  1220. property ThumbnailOrientation: TExifOrientation index Ord(esThumbnail) read GetOrientation write SetOrientation stored False;
  1221. property ThumbnailResolution: TCustomExifResolution read FThumbnailResolution
  1222. write SetThumbnailResolution stored False;
  1223. end;
  1224. EExifDataPatcherError = class(ECCRExifException);
  1225. ENoExifFileOpenError = class(EExifDataPatcherError);
  1226. EIllegalEditOfExifData = class(EExifDataPatcherError);
  1227. TExifDataPatcher = class(TCustomExifData) //only supports patching the Exif data in JPEG files
  1228. strict private
  1229. FOriginalEndianness: TEndianness;
  1230. FPreserveFileDate: Boolean;
  1231. FStream: TFileStream;
  1232. function GetFileDateTime: TDateTime;
  1233. procedure SetFileDateTime(const Value: TDateTime);
  1234. function GetFileName: string;
  1235. protected
  1236. procedure CheckFileIsOpen;
  1237. property Stream: TFileStream read FStream;
  1238. public
  1239. constructor Create(const AFileName: string); reintroduce; overload;
  1240. destructor Destroy; override;
  1241. { the following two methods originally had params typed to TJpegImage; these
  1242. have been made more weakly typed for FMX compatibility }
  1243. {$IF CompilerVersion >= 22}
  1244. procedure GetImage<T: TPersistent, IStreamPersist>(const Dest: T);
  1245. procedure GetThumbnail<T: TPersistent, IStreamPersist>(const Dest: T);
  1246. {$ELSE}
  1247. procedure GetImage(const Dest: IStreamPersist);
  1248. procedure GetThumbnail(Dest: TPersistent);
  1249. {$IFEND}
  1250. procedure OpenFile(const JPEGFileName: string);
  1251. procedure UpdateFile;
  1252. procedure CloseFile(SaveChanges: Boolean = False);
  1253. property FileDateTime: TDateTime read GetFileDateTime write SetFileDateTime;
  1254. published
  1255. property FileName: string read GetFileName write OpenFile;
  1256. property PreserveFileDate: Boolean read FPreserveFileDate write FPreserveFileDate default False;
  1257. end;
  1258. TExifData = class(TCustomExifData, IStreamPersist, IStreamPersistEx, ITiffRewriteCallback)
  1259. strict private
  1260. FRemovePaddingTagsOnSave: Boolean;
  1261. procedure GetGraphicSaveMethod(Stream: TStream; var Method: TGraphicSaveMethod);
  1262. function GetSection(Section: TExifSectionKind): TExtendableExifSection; inline;
  1263. protected
  1264. procedure DefineProperties(Filer: TFiler); override;
  1265. procedure DoSaveToJPEG(InStream, OutStream: TStream);
  1266. procedure DoSaveToPSD(InStream, OutStream: TStream);
  1267. procedure DoSaveToTIFF(InStream, OutStream: TStream);
  1268. class function SectionClass: TExifSectionClass; override;
  1269. { ITiffRewriteCallback }
  1270. procedure AddNewTags(Rewriter: TTiffDirectoryRewriter);
  1271. procedure RewritingOldTag(const Source: ITiffDirectory; TagID: TTiffTagID;
  1272. DataType: TTiffDataType; var Rewrite: Boolean);
  1273. public
  1274. constructor Create(AOwner: TComponent = nil); override;
  1275. procedure Assign(Source: TPersistent); override;
  1276. {$IF Declared(TGraphic)}
  1277. procedure CreateThumbnail(Source: TGraphic;
  1278. ThumbnailWidth: Integer = StandardExifThumbnailWidth;
  1279. ThumbnailHeight: Integer = StandardExifThumbnailHeight);
  1280. procedure StandardizeThumbnail;
  1281. {$IFEND}
  1282. class function IsSupportedGraphic(Stream: TStream): Boolean; overload;
  1283. class function IsSupportedGraphic(const FileName: string): Boolean; overload;
  1284. {$IFDEF FMX}
  1285. function LoadFromBitmap(const Bitmap: TBitmap): Boolean; overload; inline;
  1286. function LoadFromBitmap(const FileName: string): Boolean; overload; inline;
  1287. procedure SaveToBitmap(const Bitmap: TBitmap); overload;
  1288. procedure SaveToBitmap(const FileName: string); overload;
  1289. {$ENDIF FMX}
  1290. function LoadFromGraphic(Stream: TStream): Boolean; overload; inline;
  1291. function LoadFromGraphic(const Graphic: IStreamPersist): Boolean; overload;
  1292. function LoadFromGraphic(const FileName: string): Boolean; overload;
  1293. procedure LoadFromStream(Stream: TStream);
  1294. procedure RemoveMakerNote;
  1295. procedure RemovePaddingTags;
  1296. procedure SaveToGraphic(const FileName: string); overload;
  1297. procedure SaveToGraphic(const Graphic: IStreamPersist); overload;
  1298. procedure SaveToGraphic(const InMemoryGraphic: TCustomMemoryStream); overload;
  1299. procedure SaveToStream(Stream: TStream);
  1300. property Sections[Section: TExifSectionKind]: TExtendableExifSection read GetSection; default;
  1301. published
  1302. property RemovePaddingTagsOnSave: Boolean read FRemovePaddingTagsOnSave write
  1303. FRemovePaddingTagsOnSave default True;
  1304. property Thumbnail;
  1305. end;
  1306. {$IFDEF VCL}
  1307. TJPEGImageEx = class(TJPEGImage)
  1308. public type
  1309. TAssignOptions = set of (jaPreserveMetadata);
  1310. strict private
  1311. FChangedSinceLastLoad: Boolean;
  1312. FExifData: TExifData;
  1313. FIPTCData: TIPTCData;
  1314. function GetXMPPacket: TXMPPacket;
  1315. procedure ReloadTags;
  1316. protected
  1317. procedure Changed(Sender: TObject); override;
  1318. procedure ReadData(Stream: TStream); override;
  1319. public
  1320. constructor Create; override;
  1321. destructor Destroy; override;
  1322. procedure Assign(Source: TPersistent); overload; override;
  1323. procedure Assign(Source: TBitmap; Options: TAssignOptions); reintroduce; overload;
  1324. procedure CreateThumbnail(ThumbnailWidth, ThumbnailHeight: Integer); overload;
  1325. procedure CreateThumbnail; overload; inline;
  1326. procedure LoadFromStream(Stream: TStream); override;
  1327. procedure SaveToStream(Stream: TStream); override;
  1328. function RemoveMetadata(Kinds: TJPEGMetadataKinds): TJPEGMetadataKinds; inline;
  1329. function RemoveSegments(Markers: TJPEGMarkers): TJPEGMarkers; inline;
  1330. function Segments(MarkersToLookFor: TJPEGMarkers = TJPEGSegment.AllMarkers): IJPEGHeaderParser; inline;
  1331. property ExifData: TExifData read FExifData;
  1332. property IPTCData: TIPTCData read FIPTCData;
  1333. property XMPPacket: TXMPPacket read GetXMPPacket; //just a shortcut for ExifData.XMPPacket
  1334. end;
  1335. {$ENDIF}
  1336. const
  1337. stMeasurementInProgress = stMeasurementActive;
  1338. stMeasurementInInterop = stMeasurementVoid;
  1339. function BinToHexStr(Data: Pointer; StartPos, Size: Integer): string; overload;
  1340. function BinToHexStr(MemStream: TCustomMemoryStream): string; overload;
  1341. function ContainsOnlyASCII(const S: UnicodeString): Boolean; overload;
  1342. function ContainsOnlyASCII(const S: RawByteString): Boolean; overload;
  1343. function DateTimeToExifString(const DateTime: TDateTime): string;
  1344. function TryExifStringToDateTime(const S: string; var DateTime: TDateTime): Boolean; overload;
  1345. function ProportionallyResizeExtents(const Width, Height: Integer;
  1346. const MaxWidth, MaxHeight: Integer): TSize;
  1347. const
  1348. AllJPEGMetaDataKinds = [Low(TJPEGMetaDataKind)..High(TJPEGMetaDataKind)];
  1349. function RemoveMetadataFromJPEG(const JPEGFileName: string;
  1350. Kinds: TJPEGMetadataKinds = AllJPEGMetaDataKinds): TJPEGMetadataKinds; overload;
  1351. function RemoveMetadataFromJPEG(JPEGImage: TJPEGImage;
  1352. Kinds: TJPEGMetadataKinds = AllJPEGMetaDataKinds): TJPEGMetadataKinds; overload;
  1353. function GetEnumToCharMapperEx(const RegularMapper: IEnumToCharMapper): IEnumToCharMapperEx; //queries for IEnumToCharMapperEx, and if not supported, creates a generic implementation
  1354. implementation
  1355. uses
  1356. {$IFDEF POSIX}Posix.Unistd,{$ENDIF} //to quell braindead H2443 compiler hint (to make it worse, the XE3 impl involves upteen function calls - one more isn't going to make a significant difference!)
  1357. {$IFDEF BrokenFMXJpegExport}System.Diagnostics, System.IOUtils,{$ENDIF}
  1358. SysConst, RTLConsts, Math, DateUtils, StrUtils, CCR.Exif.Consts;
  1359. type
  1360. PExifFractionArray = ^TExifFractionArray;
  1361. TExifFractionArray = array[0..High(TLongWordArray) div 2] of TExifFraction;
  1362. const
  1363. NullFraction: TExifFraction = (PackedValue: 0);
  1364. { general helper routines }
  1365. function BinToHexStr(Data: Pointer; StartPos, Size: Integer): string;
  1366. begin
  1367. Result := BinToHexStr(@PAnsiChar(Data)[StartPos], Size);
  1368. end;
  1369. function BinToHexStr(MemStream: TCustomMemoryStream): string;
  1370. begin
  1371. Result := BinToHexStr(MemStream.Memory, MemStream.Size);
  1372. end;
  1373. function ContainsOnlyASCII(const S: UnicodeString): Boolean;
  1374. var
  1375. Ch: WideChar;
  1376. begin
  1377. Result := True;
  1378. for Ch in S do
  1379. if Ord(Ch) > 128 then
  1380. begin
  1381. Result := False;
  1382. Break;
  1383. end;
  1384. end;
  1385. function ContainsOnlyASCII(const S: RawByteString): Boolean;
  1386. var
  1387. Ch: AnsiChar;
  1388. begin
  1389. Result := True;
  1390. for Ch in S do
  1391. if Ord(Ch) > 128 then
  1392. begin
  1393. Result := False;
  1394. Break;
  1395. end;
  1396. end;
  1397. {$IFDEF HasFormatSettings}
  1398. function DecimalSeparator: Char; inline; //avoid compiler warning about deprecated symbol
  1399. begin
  1400. Result := FormatSettings.DecimalSeparator;
  1401. end;
  1402. {$ENDIF}
  1403. {$IF Declared(TGraphic)}
  1404. function IsGraphicEmpty(AGraphic: TGraphic): Boolean; inline;
  1405. begin
  1406. {$IFDEF VCL}
  1407. Result := (AGraphic = nil) or AGraphic.Empty;
  1408. {$ELSE}
  1409. Result := (AGraphic = nil) or AGraphic.IsEmpty;
  1410. {$ENDIF}
  1411. end;
  1412. procedure StretchDrawGraphic(AGraphic: TGraphic; ADest: TCanvas; const ARect: TRect);
  1413. begin
  1414. if IsGraphicEmpty(AGraphic) then Exit;
  1415. {$IFDEF VCL}
  1416. ADest.StretchDraw(ARect, AGraphic);
  1417. {$ELSE}
  1418. ADest.BeginScene;
  1419. ADest.DrawBitmap(AGraphic, RectF(0, 0, AGraphic.Width, AGraphic.Height),
  1420. RectF(ARect.Left, ARect.Top, ARect.Right, ARect.Bottom), 1);
  1421. ADest.EndScene;
  1422. {$ENDIF}
  1423. end;
  1424. {$ELSE}
  1425. function IsGraphicEmpty(const AGraphic: TJPEGImage): Boolean; inline;
  1426. begin
  1427. Result := (AGraphic = nil) or AGraphic.Empty;
  1428. end;
  1429. {$IFEND}
  1430. function GetGPSTagXMPName(TagID: TExifTagID): UnicodeString;
  1431. begin
  1432. case TagID of
  1433. ttGPSVersionID: Result := 'GPSVersionID';
  1434. ttGPSLatitude: Result := 'GPSLatitude'; //includes ttGPSLatitudeRef
  1435. ttGPSLongitude: Result := 'GPSLongitude'; //includes ttGPSLongitudeRef
  1436. ttGPSAltitudeRef: Result := 'GPSAltitudeRef';
  1437. ttGPSAltitude: Result := 'GPSAltitude';
  1438. ttGPSTimeStamp: Result := 'GPSTimeStamp'; //includes GPSDateStamp
  1439. ttGPSSatellites: Result := 'GPSSatellites';
  1440. ttGPSStatus: Result := 'GPSStatus';
  1441. ttGPSMeasureMode: Result :='GPSMeasureMode';
  1442. ttGPSDOP: Result := 'GPSDOP';
  1443. ttGPSSpeedRef: Result := 'GPSSpeedRef';
  1444. ttGPSSpeed: Result := 'GPSSpeed';
  1445. ttGPSTrackRef: Result := 'GPSTrackRef';
  1446. ttGPSTrack: Result := 'GPSTrack';
  1447. ttGPSImgDirectionRef: Result := 'GPSImgDirectionRef';
  1448. ttGPSImgDirection: Result := 'GPSImgDirection';
  1449. ttGPSMapDatum: Result := 'GPSMapDatum';
  1450. ttGPSDestBearingRef: Result := 'GPSDestBearingRef';
  1451. ttGPSDestBearing: Result := 'GPSDestBearing';
  1452. ttGPSDestDistance: Result := 'GPSDestDistance';
  1453. ttGPSDestLatitude: Result := 'GPSDestLatitude'; //includes ttGPSDestLatitudeRef
  1454. ttGPSDestLongitude: Result := 'GPSDestLongitude'; //includes ttGPSDestLongitudeRef
  1455. ttGPSDifferential: Result := 'GPSDifferential';
  1456. else Result := '';
  1457. end;
  1458. end;
  1459. function FindGPSTagXMPName(TagID: TExifTagID; out PropName: string): Boolean;
  1460. begin
  1461. PropName := GetGPSTagXMPName(TagID);
  1462. Result := (PropName <> '');
  1463. end;
  1464. function IsKnownExifTagInMainIFD(ID: TTiffTagID; DataType: TTiffDataType): Boolean; overload;
  1465. begin
  1466. Result := False;
  1467. case ID of
  1468. ttImageDescription, ttMake, ttModel, ttOrientation, ttXResolution,
  1469. ttYResolution, ttResolutionUnit, ttSoftware, ttDateTime, ttArtist,
  1470. ttWhitePoint, ttPrimaryChromaticities, ttYCbCrCoefficients,
  1471. ttYCbCrPositioning, ttReferenceBlackWhite, ttCopyright, ttIPTC, ttExifOffset,
  1472. ttGPSOffset, ttPrintIM: Result := True;
  1473. ttWindowsTitle, ttWindowsComments, ttWindowsAuthor, ttWindowsKeywords,
  1474. ttWindowsSubject, ttWindowsRating, ttWindowsPadding:
  1475. if DataType = tdByte then Result := True;
  1476. end;
  1477. end;
  1478. function IsKnownExifTagInMainIFD(const TagInfo: TTiffTagInfo): Boolean; overload; inline;
  1479. begin
  1480. Result := IsKnownExifTagInMainIFD(TagInfo.ID, TagInfo.DataType);
  1481. end;
  1482. function ProportionallyResizeExtents(const Width, Height: Integer;
  1483. const MaxWidth, MaxHeight: Integer): TSize;
  1484. var
  1485. XYAspect: Double;
  1486. begin
  1487. if (Width = 0) or (Height = 0) then
  1488. begin
  1489. Result.cx := 0;
  1490. Result.cy := 0;
  1491. Exit;
  1492. end;
  1493. Result.cx := Width;
  1494. Result.cy := Height;
  1495. XYAspect := Width / Height;
  1496. if Width > Height then
  1497. begin
  1498. Result.cx := MaxWidth;
  1499. Result.cy := Round(MaxWidth / XYAspect);
  1500. if Result.cy > MaxHeight then
  1501. begin
  1502. Result.cy := MaxHeight;
  1503. Result.cx := Round(MaxHeight * XYAspect);
  1504. end;
  1505. end
  1506. else
  1507. begin
  1508. Result.cy := MaxHeight;
  1509. Result.cx := Round(MaxHeight * XYAspect);
  1510. if Result.cx > MaxWidth then
  1511. begin
  1512. Result.cx := MaxWidth;
  1513. Result.cy := Round(MaxWidth / XYAspect);
  1514. end;
  1515. end;
  1516. end;
  1517. {$IF Declared(TBitmap)}
  1518. function CreateNewBitmap(const AWidth, AHeight: Integer): TBitmap;
  1519. begin
  1520. {$IFDEF VCL}
  1521. Result := TBitmap.Create;
  1522. Result.SetSize(AWidth, AHeight);
  1523. {$ELSE}
  1524. Result := TBitmap.Create(AWidth, AHeight);
  1525. {$ENDIF}
  1526. end;
  1527. procedure CreateExifThumbnail(Source: TGraphic; Dest: TJPEGImage;
  1528. MaxWidth: Integer = StandardExifThumbnailWidth;
  1529. MaxHeight: Integer = StandardExifThumbnailHeight);
  1530. var
  1531. Bitmap: TBitmap;
  1532. R: TRect;
  1533. begin
  1534. with ProportionallyResizeExtents(Source.Width, Source.Height, MaxWidth, MaxHeight) do
  1535. R := Rect(0, 0, cx, cy);
  1536. Bitmap := CreateNewBitmap(R.Right, R.Bottom);
  1537. try
  1538. StretchDrawGraphic(Source, Bitmap.Canvas, R);
  1539. Dest.Assign(Bitmap);
  1540. finally
  1541. Bitmap.Free;
  1542. end;
  1543. end;
  1544. {$IFEND}
  1545. function DoRemoveMetaDataFromJPEG(InStream, OutStream: TStream;
  1546. KindsToRemove: TJPEGMetadataKinds): TJPEGMetadataKinds;
  1547. var
  1548. Segment: IFoundJPEGSegment;
  1549. StartCopyFrom: Int64;
  1550. procedure DoCopyFrom(const EndPos, NextStartPosOffset: Int64);
  1551. begin
  1552. InStream.Position := StartCopyFrom;
  1553. if (EndPos - StartCopyFrom) > 0 then
  1554. OutStream.CopyFrom(InStream, EndPos - StartCopyFrom);
  1555. StartCopyFrom := EndPos + NextStartPosOffset;
  1556. end;
  1557. var
  1558. Block: IAdobeResBlock;
  1559. IsIPTCBlock: Boolean;
  1560. MarkersToLookFor: TJPEGMarkers;
  1561. SavedBlocks: IInterfaceList;
  1562. begin
  1563. MarkersToLookFor := [jmApp1];
  1564. if mkIPTC in KindsToRemove then Include(MarkersToLookFor, jmApp13);
  1565. StartCopyFrom := InStream.Position;
  1566. for Segment in JPEGHeader(InStream, MarkersToLookFor) do
  1567. begin
  1568. if (mkExif in KindsToRemove) and Segment.IsExifBlock then
  1569. Include(Result, mkExif)
  1570. else if (mkXMP in KindsToRemove) and Segment.IsXMPBlock then
  1571. Include(Result, mkXMP)
  1572. else
  1573. begin
  1574. IsIPTCBlock := False;
  1575. SavedBlocks := nil;
  1576. if mkIPTC in KindsToRemove then
  1577. for Block in Segment do
  1578. if Block.IsIPTCBlock then
  1579. IsIPTCBlock := True
  1580. else
  1581. begin
  1582. if SavedBlocks = nil then SavedBlocks := TInterfaceList.Create;
  1583. SavedBlocks.Add(Block);
  1584. end;
  1585. if IsIPTCBlock then
  1586. begin
  1587. Include(Result, mkIPTC);
  1588. DoCopyFrom(Segment.Offset, Segment.TotalSize);
  1589. if SavedBlocks <> nil then
  1590. WriteJPEGSegment(OutStream, CreateAdobeApp13Segment(SavedBlocks));
  1591. end;
  1592. Continue;
  1593. end;
  1594. DoCopyFrom(Segment.Offset, Segment.TotalSize);
  1595. end;
  1596. DoCopyFrom(InStream.Size, 0);
  1597. end;
  1598. function RemoveMetadataFromJPEG(const JPEGFileName: string;
  1599. Kinds: TJPEGMetadataKinds = AllJPEGMetaDataKinds): TJPEGMetadataKinds; overload;
  1600. var
  1601. InStream: TMemoryStream;
  1602. OutStream: TFileStream;
  1603. begin
  1604. if Kinds = [] then Exit;
  1605. OutStream := nil;
  1606. InStream := TMemoryStream.Create;
  1607. try
  1608. InStream.LoadFromFile(JPEGFileName);
  1609. OutStream := TFileStream.Create(JPEGFileName, fmCreate);
  1610. Result := DoRemoveMetaDataFromJPEG(InStream, OutStream, Kinds);
  1611. finally
  1612. OutStream.Free;
  1613. InStream.Free;
  1614. end;
  1615. end;
  1616. function RemoveMetaDataFromJPEG(JPEGImage: TJPEGImage;
  1617. Kinds: TJPEGMetadataKinds = AllJPEGMetaDataKinds): TJPEGMetadataKinds; overload;
  1618. var
  1619. InStream, OutStream: TMemoryStream;
  1620. begin
  1621. if Kinds = [] then Exit;
  1622. OutStream := nil;
  1623. InStream := TMemoryStream.Create;
  1624. try
  1625. JPEGImage.SaveToStream(InStream);
  1626. InStream.Position := 0;
  1627. OutStream := TMemoryStream.Create;
  1628. Result := DoRemoveMetaDataFromJPEG(InStream, OutStream, Kinds);
  1629. if Result <> [] then
  1630. begin
  1631. OutStream.Position := 0;
  1632. JPEGImage.LoadFromStream(OutStream);
  1633. end;
  1634. finally
  1635. OutStream.Free;
  1636. InStream.Free;
  1637. end;
  1638. end;
  1639. { GetEnumToCharMapperEx }
  1640. type
  1641. TEnumToCharMapperEx = class(TInterfacedObject, IEnumToCharMapperEx)
  1642. strict private
  1643. FTypeInfo: PTypeInfo;
  1644. FRegularMapper: IEnumToCharMapper;
  1645. protected
  1646. function GetEnumName(OrdValue: Integer): string;
  1647. function GetMinEnumValue: Integer;
  1648. function GetMaxEnumValue: Integer;
  1649. function CharToEnumValue(Ch: AnsiChar): Integer;
  1650. function EnumValueToChar(OrdValue: Integer): AnsiChar;
  1651. function GetEnumTypeInfo: PTypeInfo;
  1652. public
  1653. constructor Create(const RegularMapper: IEnumToCharMapper);
  1654. end;
  1655. constructor TEnumToCharMapperEx.Create(const RegularMapper: IEnumToCharMapper);
  1656. begin
  1657. inherited Create;
  1658. FRegularMapper := RegularMapper;
  1659. FTypeInfo := RegularMapper.EnumTypeInfo;
  1660. if FTypeInfo = nil then
  1661. raise EInvalidOperation.Create('IEnumToCharMapper.EnumTypeInfo must not return nil');
  1662. end;
  1663. function TEnumToCharMapperEx.CharToEnumValue(Ch: AnsiChar): Integer;
  1664. begin
  1665. Result := FRegularMapper.CharToEnumValue(Ch);
  1666. end;
  1667. function TEnumToCharMapperEx.EnumValueToChar(OrdValue: Integer): AnsiChar;
  1668. begin
  1669. Result := FRegularMapper.EnumValueToChar(OrdValue);
  1670. end;
  1671. function TEnumToCharMapperEx.GetEnumName(OrdValue: Integer): string;
  1672. begin
  1673. Result := TypInfo.GetEnumName(FTypeInfo, OrdValue)
  1674. end;
  1675. function TEnumToCharMapperEx.GetEnumTypeInfo: PTypeInfo;
  1676. begin
  1677. Result := FTypeInfo;
  1678. end;
  1679. function TEnumToCharMapperEx.GetMinEnumValue: Integer;
  1680. begin
  1681. Result := GetTypeData(FTypeInfo).MinValue;
  1682. end;
  1683. function TEnumToCharMapperEx.GetMaxEnumValue: Integer;
  1684. begin
  1685. Result := GetTypeData(FTypeInfo).MaxValue;
  1686. end;
  1687. function GetEnumToCharMapperEx(const RegularMapper: IEnumToCharMapper): IEnumToCharMapperEx;
  1688. begin
  1689. if not Supports(RegularMapper, IEnumToCharMapperEx, Result) then
  1690. Result := TEnumToCharMapperEx.Create(RegularMapper);
  1691. end;
  1692. { TJPEGImage }
  1693. {$IFDEF FMX}
  1694. constructor TJPEGImage.Create;
  1695. begin
  1696. inherited Create(0, 0);
  1697. end;
  1698. procedure TJPEGImage.SaveToStream(Stream: TStream);
  1699. {$IF DEFINED(VER230)}
  1700. var
  1701. Codec: TBitmapCodec;
  1702. begin
  1703. Codec := DefaultBitmapCodecClass.Create;
  1704. try
  1705. Codec.SaveToStream(Stream, TBitmap(Self), 'jpeg');
  1706. finally
  1707. Codec.Free;
  1708. end;
  1709. end;
  1710. {$ELSEIF DEFINED(BrokenFMXJpegExport)} //QC108621
  1711. var
  1712. FileStream: TFileStream;
  1713. TempFN: string;
  1714. begin
  1715. FileStream := nil;
  1716. TempFN := IncludeTrailingPathDelimiter(TPath.GetTempPath) + IntToStr(TStopwatch.GetTimeStamp) + '.jpg';
  1717. try
  1718. TBitmapCodecManager.SaveToFile(TempFN, Self);
  1719. FileStream := TFileStream.Create(TempFN, fmOpenRead or fmShareDenyWrite);
  1720. Stream.CopyFrom(FileStream, 0);
  1721. finally
  1722. FileStream.Free;
  1723. DeleteFile(TempFN);
  1724. end;
  1725. end;
  1726. {$ELSEIF Declared(TBitmapSurface)}
  1727. var
  1728. Surf: TBitmapSurface;
  1729. begin
  1730. Surf := TBitmapSurface.Create;
  1731. try
  1732. Surf.Assign(Self);
  1733. TBitmapCodecManager.SaveToStream(Stream, Surf, '.jpg');
  1734. finally
  1735. Surf.Free;
  1736. end;
  1737. end;
  1738. {$ELSE}
  1739. begin
  1740. TBitmapCodecManager.SaveToStream(Stream, Self, 'jpg');
  1741. end;
  1742. {$IFEND}
  1743. {$ENDIF}
  1744. { segment header checking }
  1745. function HasExifHeader(Stream: TStream; MovePosOnSuccess: Boolean = False): Boolean;
  1746. begin
  1747. Result := Stream.TryReadHeader(TJPEGSegment.ExifHeader, SizeOf(TJPEGSegment.ExifHeader),
  1748. not MovePosOnSuccess);
  1749. end;
  1750. { Exif date/time strings }
  1751. function GetExifSubSecsString(const MSecs: Word): string; overload;
  1752. begin
  1753. Result := Copy(Format('%d', [MSecs]), 1, 3);
  1754. end;
  1755. function GetExifSubSecsString(const DateTime: TDateTime): string; overload;
  1756. begin
  1757. Result := GetExifSubSecsString(MilliSecondOf(DateTime));
  1758. end;
  1759. function DateTimeToExifString(const DateTime: TDateTime): string;
  1760. var
  1761. Year, Month, Day, Hour, Minute, Second, MilliSecond: Word;
  1762. begin
  1763. if DateTime = 0 then
  1764. Result := StringOfChar(' ', 19)
  1765. else
  1766. begin
  1767. DecodeDateTime(DateTime, Year, Month, Day, Hour, Minute, Second, MilliSecond);
  1768. FmtStr(Result, '%.4d:%.2d:%.2d %.2d:%.2d:%.2d', [Year, Month, Day, Hour, Minute, Second]);
  1769. end;
  1770. end;
  1771. function TryExifStringToDateTime(const S: string; var DateTime: TDateTime): Boolean;
  1772. var
  1773. Year, Month, Day, Hour, Min, Sec: Integer;
  1774. begin //'2007:09:02 02:30:49'
  1775. Result := (Length(S) = 19) and (S[5] = ':') and (S[8] = ':') and
  1776. TryStrToInt(Copy(S, 1, 4), Year) and
  1777. TryStrToInt(Copy(S, 6, 2), Month) and
  1778. TryStrToInt(Copy(S, 9, 2), Day) and
  1779. TryStrToInt(Copy(S, 12, 2), Hour) and
  1780. TryStrToInt(Copy(S, 15, 2), Min) and
  1781. TryStrToInt(Copy(S, 18, 2), Sec) and
  1782. TryEncodeDateTime(Year, Month, Day, Hour, Min, Sec, 0, DateTime);
  1783. end;
  1784. { TExifTag }
  1785. constructor TExifTag.Create(const Section: TExifSection;
  1786. const ID: TExifTagID; DataType: TExifDataType; ElementCount: Integer);
  1787. begin
  1788. inherited Create;
  1789. FDataType := DataType;
  1790. FID := ID;
  1791. FSection := Section;
  1792. FElementCount := ElementCount;
  1793. if FElementCount < 0 then FElementCount := 0;
  1794. FData := AllocMem(DataSize);
  1795. FDataStream := TUserMemoryStream.Create(FData, DataSize);
  1796. FOriginalDataSize := DataSize;
  1797. FWellFormed := True;
  1798. end;
  1799. constructor TExifTag.Create(Section: TExifSection;
  1800. const Directory: IFoundTiffDirectory; Index: Integer);
  1801. var
  1802. Info: TTiffTagInfo;
  1803. begin
  1804. Info := Directory.TagInfo[Index];
  1805. if Info.IsWellFormed then
  1806. Create(Section, Info.ID, Info.DataType, Info.ElementCount)
  1807. else
  1808. Create(Section, Info.ID, tdUndefined, 0);
  1809. FOriginalDataOffset := Info.DataOffset;
  1810. FWellFormed := Info.IsWellFormed;
  1811. if Info.ElementCount > 0 then Directory.Parser.LoadTagData(Info, FData^);
  1812. end;
  1813. destructor TExifTag.Destroy;
  1814. begin
  1815. if Section <> nil then Section.TagDeleting(Self);
  1816. if FData <> nil then FreeMem(FData);
  1817. FDataStream.Free;
  1818. inherited;
  1819. end;
  1820. procedure TExifTag.Assign(Source: TExifTag);
  1821. begin
  1822. if Source = nil then
  1823. ElementCount := 0
  1824. else
  1825. begin
  1826. if (Source.Section <> Section) or (Section = nil) then
  1827. ID := Source.ID;
  1828. UpdateData(Source.DataType, Source.ElementCount, Source.Data^);
  1829. end;
  1830. end;
  1831. procedure TExifTag.Changing(NewID: TExifTagID; NewDataType: TExifDataType;
  1832. NewElementCount: LongInt; NewData: Boolean);
  1833. begin
  1834. if Section <> nil then
  1835. Section.TagChanging(Self, NewID, NewDataType, NewElementCount, NewData);
  1836. end;
  1837. procedure TExifTag.Changed(ChangeType: TExifTagChangeType);
  1838. begin
  1839. if ChangeType in [tcData, tcDataSize] then FAsStringCache := '';
  1840. if Section <> nil then Section.TagChanged(Self, ChangeType);
  1841. end;
  1842. procedure TExifTag.Changed;
  1843. begin
  1844. Changed(tcData);
  1845. end;
  1846. function TExifSection.CheckExtendable: TExtendableExifSection;
  1847. begin
  1848. if Self is TExtendableExifSection then
  1849. Result := TExtendableExifSection(Self)
  1850. else
  1851. raise EIllegalEditOfExifData.CreateRes(@SIllegalEditOfExifData);
  1852. end;
  1853. procedure TExifTag.Delete;
  1854. begin
  1855. {$IFDEF NEXTGEN}
  1856. DisposeOf;
  1857. {$ELSE}
  1858. Free;
  1859. {$ENDIF}
  1860. end;
  1861. function NextElementStr(DataType: TExifDataType; var SeekPtr: PAnsiChar): string;
  1862. begin
  1863. case DataType of
  1864. tdAscii: Result := string(AnsiString(SeekPtr^));
  1865. tdByte: Result := IntToStr(PByte(SeekPtr)^);
  1866. tdWord: Result := IntToStr(PWord(SeekPtr)^);
  1867. tdLongWord: Result := IntToStr(PLongWord(SeekPtr)^);
  1868. tdShortInt: Result := IntToStr(PShortInt(SeekPtr)^);
  1869. tdSmallInt: Result := IntToStr(PSmallInt(SeekPtr)^);
  1870. tdLongInt, tdSubDirectory: Result := IntToStr(PLongInt(SeekPtr)^);
  1871. tdSingle: Result := FloatToStr(PSingle(SeekPtr)^);
  1872. tdDouble: Result := FloatToStr(PDouble(SeekPtr)^);
  1873. tdLongWordFraction, tdLongIntFraction: Result := PExifFraction(SeekPtr).ToString;
  1874. end;
  1875. Inc(SeekPtr, TiffElementSizes[DataType]);
  1876. end;
  1877. function TExifTag.GetAsString: string;
  1878. var
  1879. TiffStr: TiffString;
  1880. I: Integer;
  1881. SeekPtr: PAnsiChar;
  1882. begin
  1883. if (FAsStringCache = '') and (ElementCount <> 0) then
  1884. case DataType of
  1885. tdAscii:
  1886. begin
  1887. SetString(TiffStr, PAnsiChar(FData), ElementCount - 1);
  1888. FAsStringCache := string(TiffStr);
  1889. end;
  1890. tdUndefined: FAsStringCache := BinToHexStr(FData, DataSize);
  1891. else
  1892. if HasWindowsStringData then
  1893. FAsStringCache := WideCharLenToString(FData, ElementCount div 2 - 1)
  1894. else
  1895. begin
  1896. SeekPtr := FData;
  1897. if ElementCount = 1 then
  1898. FAsStringCache := NextElementStr(DataType, SeekPtr)
  1899. else
  1900. with TStringList.Create do
  1901. try
  1902. for I := 0 to ElementCount - 1 do
  1903. Add(NextElementStr(DataType, SeekPtr));
  1904. FAsStringCache := CommaText;
  1905. finally
  1906. Free;
  1907. end;
  1908. end;
  1909. end;
  1910. Result := FAsStringCache;
  1911. end;
  1912. procedure TExifTag.SetAsString(const Value: string);
  1913. var
  1914. Bytes: TBytes;
  1915. Buffer: TiffString;
  1916. S: string;
  1917. List: TStringList;
  1918. SeekPtr: PAnsiChar;
  1919. UnicodeStr: UnicodeString;
  1920. begin
  1921. if Length(Value) = 0 then
  1922. ElementCount := 0
  1923. else
  1924. case DataType of
  1925. tdAscii:
  1926. begin
  1927. if (Section <> nil) and Section.EnforceASCII and not ContainsOnlyASCII(Value) then
  1928. raise ENotOnlyASCIIError.CreateRes(@STagCanContainOnlyASCII);
  1929. Buffer := TiffString(Value);
  1930. UpdateData(tdAscii, Length(Buffer) + 1, PAnsiChar(Buffer)^); //ascii tag data includes null terminator
  1931. end;
  1932. tdUndefined:
  1933. begin
  1934. Bytes := HexStrToBin(Value);
  1935. if Bytes <> nil then
  1936. UpdateData(tdUndefined, Length(Bytes), Bytes[0])
  1937. else
  1938. UpdateData(tdUndefined, 0, Pointer(nil)^)
  1939. end;
  1940. else
  1941. if HasWindowsStringData then
  1942. begin
  1943. UnicodeStr := Value;
  1944. UpdateData(tdByte, Length(UnicodeStr) * 2 + 1, UnicodeStr[1]);
  1945. end
  1946. else
  1947. begin
  1948. List := TStringList.Create;
  1949. try
  1950. List.CommaText := Value;
  1951. SetLength(Buffer, List.Count * TiffElementSizes[DataType]);
  1952. SeekPtr := PAnsiChar(Buffer);
  1953. for S in List do
  1954. begin
  1955. {$RANGECHECKS ON}
  1956. case DataType of
  1957. tdByte: PByte(SeekPtr)^ := StrToInt(S);
  1958. tdWord: PWord(SeekPtr)^ := StrToInt(S);
  1959. tdLongWord: PLongWord(SeekPtr)^ := StrToInt64(S);
  1960. tdShortInt: PShortInt(SeekPtr)^ := StrToInt(S);
  1961. tdSmallInt: PSmallInt(SeekPtr)^ := StrToInt(S);
  1962. tdLongInt, tdSubDirectory: PLongInt(SeekPtr)^ := StrToInt(S);
  1963. tdLongWordFraction, tdLongIntFraction: PExifFraction(SeekPtr)^ :=
  1964. TExifFraction.CreateFromString(S);
  1965. tdSingle: PSingle(SeekPtr)^ := StrToFloat(S);
  1966. tdDouble: PDouble(SeekPtr)^ := StrToFloat(S);
  1967. end;
  1968. {$IFDEF RangeCheckingOff}{$RANGECHECKS OFF}{$ENDIF}
  1969. Inc(SeekPtr, TiffElementSizes[DataType]);
  1970. end;
  1971. finally
  1972. List.Free;
  1973. end;
  1974. UpdateData(DataType, Length(Buffer), Pointer(Buffer)^);
  1975. end;
  1976. end;
  1977. FAsStringCache := Value;
  1978. end;
  1979. {$IFDEF HasToString}
  1980. function TExifTag.ToString: string;
  1981. begin
  1982. Result := AsString;
  1983. end;
  1984. {$ENDIF}
  1985. function TExifTag.GetDataSize: Integer;
  1986. begin
  1987. Result := ElementCount * TiffElementSizes[DataType]
  1988. end;
  1989. function TExifTag.GetElementAsString(Index: Integer): string;
  1990. var
  1991. SeekPtr: PAnsiChar;
  1992. begin
  1993. if (Index < 0) or (Index >= ElementCount) then
  1994. raise EListError.CreateFmt(SListIndexError, [Index]);
  1995. SeekPtr := FData;
  1996. Inc(SeekPtr, Index * TiffElementSizes[DataType]);
  1997. Result := NextElementStr(DataType, SeekPtr);
  1998. end;
  1999. function TExifTag.HasWindowsStringData: Boolean;
  2000. begin
  2001. Result := False;
  2002. if (DataType = tdByte) and (Section <> nil) and (Section.Kind = esGeneral) then
  2003. case ID of
  2004. ttWindowsTitle, ttWindowsComments, ttWindowsAuthor, ttWindowsKeywords,
  2005. ttWindowsSubject: Result := True;
  2006. end;
  2007. end;
  2008. procedure TExifTag.SetDataType(const NewDataType: TExifDataType);
  2009. begin
  2010. if NewDataType <> FDataType then
  2011. UpdateData(NewDataType, ElementCount, PByte(nil)^);
  2012. end;
  2013. procedure TExifTag.SetElementCount(const NewCount: LongInt);
  2014. begin
  2015. if NewCount <> FElementCount then
  2016. UpdateData(DataType, NewCount, PByte(nil)^);
  2017. end;
  2018. procedure TExifTag.SetID(const Value: TExifTagID);
  2019. begin
  2020. if Value = FID then Exit;
  2021. Changing(Value, DataType, ElementCount, False);
  2022. FID := Value;
  2023. Changed(tcID);
  2024. end;
  2025. function TExifTag.IsPadding: Boolean;
  2026. begin
  2027. Result := (ID = ttWindowsPadding) and (DataType = tdUndefined) and
  2028. (ElementCount >= 2) and (PWord(Data)^ = ttWindowsPadding);
  2029. end;
  2030. function TExifTag.ReadFraction(Index: Integer; const Default: TExifFraction): TExifFraction;
  2031. begin
  2032. if (DataType in [tdLongWordFraction, tdLongIntFraction]) and (Index >= 0) and
  2033. (Index < ElementCount) then
  2034. Result := PExifFractionArray(FData)[Index]
  2035. else
  2036. Result := Default;
  2037. end;
  2038. function TExifTag.ReadLongWord(Index: Integer; const Default: LongWord): LongWord;
  2039. begin
  2040. if (Index < 0) or (Index >= ElementCount) then
  2041. Result := Default
  2042. else
  2043. case DataType of
  2044. tdByte, tdShortInt: Result := PByteArray(FData)[Index];
  2045. tdWord, tdSmallInt: Result := PWordArray(FData)[Index];
  2046. tdLongWord, tdLongInt: Result := PLongWordArray(FData)[Index];
  2047. else Result := Default;
  2048. end;
  2049. end;
  2050. function TExifTag.ReadWord(Index: Integer; const Default: Word): Word;
  2051. begin
  2052. if (Index < 0) or (Index >= ElementCount) then
  2053. Result := Default
  2054. else
  2055. case DataType of
  2056. tdByte, tdShortInt: Result := PByteArray(FData)[Index];
  2057. tdWord, tdSmallInt: Result := PWordArray(FData)[Index];
  2058. else Result := Default;
  2059. end;
  2060. end;
  2061. procedure TExifTag.SetAsPadding(Size: TExifPaddingTagSize);
  2062. begin
  2063. ID := ttWindowsPadding;
  2064. UpdateData(tdUndefined, Size, Pointer(nil)^);
  2065. PWord(Data)^ := ttWindowsPadding;
  2066. if Size > 2 then
  2067. FillChar(PWordArray(Data)[1], Size - 2, 0);
  2068. end;
  2069. procedure TExifTag.UpdateData(const NewData);
  2070. begin
  2071. Changing(ID, DataType, ElementCount, True);
  2072. Move(NewData, FData^, DataSize);
  2073. Changed(tcData);
  2074. end;
  2075. procedure TExifTag.UpdateData(NewDataType: TExifDataType;
  2076. NewElementCount: Integer; const NewData);
  2077. const
  2078. IntDataTypes = [tdByte, tdWord, tdLongWord, tdShortInt, tdSmallInt, tdLongWord];
  2079. var
  2080. OldDataSize, NewDataSize, I: Integer;
  2081. OldIntVals: array of LongWord;
  2082. begin
  2083. if NewElementCount < 0 then NewElementCount := 0;
  2084. if (@NewData = nil) and (NewDataType = DataType) and (NewElementCount = ElementCount) then
  2085. Exit;
  2086. OldDataSize := GetDataSize;
  2087. NewDataSize := NewElementCount * TiffElementSizes[NewDataType];
  2088. Changing(ID, NewDataType, NewElementCount, (@NewData <> nil));
  2089. if (@NewData = nil) and (NewDataSize <> OldDataSize) and (DataType in IntDataTypes) and
  2090. (NewDataType in IntDataTypes) and (ElementCount <> 0) and (NewElementCount <> 0) then
  2091. begin
  2092. SetLength(OldIntVals, FElementCount);
  2093. for I := 0 to Min(FElementCount, NewElementCount) - 1 do
  2094. Move(PByteArray(FData)[I * TiffElementSizes[DataType]], OldIntVals[I],
  2095. TiffElementSizes[DataType]);
  2096. end;
  2097. ReallocMem(FData, NewDataSize);
  2098. FDataStream.ChangeMemory(FData, NewDataSize);
  2099. if NewDataSize > OldDataSize then
  2100. FillChar(PByteArray(FData)[OldDataSize], NewDataSize - OldDataSize, 0);
  2101. if @NewData <> nil then
  2102. Move(NewData, FData^, NewDataSize)
  2103. else if TiffElementSizes[FDataType] <> TiffElementSizes[NewDataType] then
  2104. begin
  2105. FillChar(FData^, Min(OldDataSize, NewDataSize), 0);
  2106. if OldIntVals <> nil then
  2107. for I := 0 to High(OldIntVals) do
  2108. Move(OldIntVals[I], PByteArray(FData)[I * TiffElementSizes[DataType]],
  2109. TiffElementSizes[DataType]);
  2110. end;
  2111. FDataType := NewDataType;
  2112. FElementCount := NewElementCount;
  2113. if NewDataSize <> OldDataSize then
  2114. Changed(tcDataSize)
  2115. else
  2116. Changed(tcData);
  2117. end;
  2118. procedure TExifTag.WriteHeader(Stream: TStream; Endianness: TEndianness;
  2119. DataOffset: LongInt);
  2120. var
  2121. I: Integer;
  2122. begin
  2123. Stream.WriteWord(ID, Endianness);
  2124. Stream.WriteWord(Ord(DataType), Endianness);
  2125. Stream.WriteLongInt(ElementCount, Endianness);
  2126. if DataSize > 4 then
  2127. Stream.WriteLongInt(DataOffset, Endianness)
  2128. else
  2129. begin
  2130. case TiffElementSizes[DataType] of
  2131. 1: Stream.WriteBuffer(Data^, ElementCount);
  2132. 2: for I := 0 to ElementCount - 1 do
  2133. Stream.WriteWord(PWordArray(Data)[I], Endianness);
  2134. 4: Stream.WriteLongWord(PLongWord(Data)^, Endianness);
  2135. end;
  2136. for I := 3 downto DataSize do
  2137. Stream.WriteByte(0);
  2138. end;
  2139. end;
  2140. procedure TExifTag.WriteOffsettedData(Stream: TStream; Endianness: TEndianness);
  2141. var
  2142. I: Integer;
  2143. begin
  2144. if DataSize <= 4 then Exit;
  2145. if DataType = tdDouble then
  2146. for I := 0 to ElementCount - 1 do
  2147. Stream.WriteDouble(PDoubleArray(Data)[I], Endianness)
  2148. else
  2149. case TiffElementSizes[DataType] of
  2150. 1: Stream.WriteBuffer(Data^, ElementCount);
  2151. 2: for I := 0 to ElementCount - 1 do
  2152. Stream.WriteWord(PWordArray(Data)[I], Endianness);
  2153. else
  2154. for I := 0 to DataSize div 4 - 1 do
  2155. Stream.WriteLongWord(PLongWordArray(Data)[I], Endianness)
  2156. end;
  2157. end;
  2158. { TExifTag.IMetadataBlock }
  2159. function TExifTag.GetData: TCustomMemoryStream;
  2160. begin
  2161. Result := FDataStream;
  2162. end;
  2163. function TExifTag.IsExifBlock(CheckID: Boolean = True): Boolean;
  2164. begin
  2165. Result := False;
  2166. end;
  2167. function TExifTag.IsIPTCBlock(CheckID: Boolean = True): Boolean;
  2168. var
  2169. Header: TIPTCTagInfo;
  2170. begin
  2171. FDataStream.Seek(0, soFromBeginning);
  2172. Result := (not CheckID or (ID = ttIPTC)) and
  2173. TAdobeResBlock.TryReadIPTCHeader(FDataStream, Header, True);
  2174. end;
  2175. function TExifTag.IsXMPBlock(CheckID: Boolean = True): Boolean;
  2176. begin
  2177. Result := False;
  2178. end;
  2179. { TExifTag.ITiffTag }
  2180. function TExifTag.GetDataType: TTiffDataType;
  2181. begin
  2182. Result := FDataType;
  2183. end;
  2184. function TExifTag.GetElementCount: Integer;
  2185. begin
  2186. Result := FElementCount;
  2187. end;
  2188. function TExifTag.GetID: TTiffTagID;
  2189. begin
  2190. Result := FID;
  2191. end;
  2192. function TExifTag.GetOriginalDataOffset: LongWord;
  2193. begin
  2194. Result := FOriginalDataOffset;
  2195. end;
  2196. function TExifTag.GetParent: ITiffDirectory;
  2197. begin
  2198. Result := FSection;
  2199. end;
  2200. { TExifSection.TEnumerator }
  2201. constructor TExifSection.TEnumerator.Create(ATagList: TTagList);
  2202. begin
  2203. FCurrent := nil;
  2204. FIndex := 0;
  2205. FTags := ATagList;
  2206. end;
  2207. function TExifSection.TEnumerator.GetCurrent: ITiffTag;
  2208. begin
  2209. Result := Current;
  2210. end;
  2211. function TExifSection.TEnumerator.MoveNext: Boolean;
  2212. begin //allow deleting a tag when enumerating
  2213. if (FCurrent <> nil) and (FIndex < FTags.Count) and (FCurrent = FTags[FIndex]) then
  2214. Inc(FIndex);
  2215. Result := FIndex < FTags.Count;
  2216. if Result then FCurrent := TExifTag(FTags[FIndex]);
  2217. end;
  2218. { TExifSection.TTagList }
  2219. {$IFDEF HasGenerics}
  2220. constructor TExifSection.TTagList.Create;
  2221. begin
  2222. inherited Create(TComparer<TExifTag>.Construct(
  2223. function(const Left, Right: TExifTag): Integer
  2224. begin
  2225. Result := Left.ID - Right.ID;
  2226. end));
  2227. end;
  2228. {$ELSE}
  2229. function CompareIDs(Item1, Item2: TExifTag): Integer;
  2230. begin
  2231. Result := Item1.ID - Item2.ID;
  2232. end;
  2233. procedure TExifSection.TTagList.Sort;
  2234. begin
  2235. inherited Sort(@CompareIDs);
  2236. end;
  2237. {$ENDIF}
  2238. { TExifSection }
  2239. constructor TExifSection.Create(const AOwner: TCustomExifData; AKind: TExifSectionKindEx);
  2240. begin
  2241. inherited Create;
  2242. FOwner := AOwner;
  2243. FTagList := TTagList.Create;
  2244. FKind := AKind;
  2245. end;
  2246. destructor TExifSection.Destroy;
  2247. var
  2248. I: Integer;
  2249. begin
  2250. for I := FTagList.Count - 1 downto 0 do
  2251. with TExifTag(FTagList[I]) do
  2252. begin
  2253. FSection := nil;
  2254. Free;
  2255. end;
  2256. FTagList.Free;
  2257. inherited;
  2258. end;
  2259. function TExifSection.Add(ID: TExifTagID; DataType: TExifDataType;
  2260. ElementCount: Integer): TExifTag;
  2261. var
  2262. I: Integer;
  2263. Tag: TExifTag;
  2264. begin
  2265. CheckExtendable;
  2266. for I := 0 to FTagList.Count - 1 do
  2267. begin
  2268. Tag := TExifTag(FTagList[I]);
  2269. if Tag.ID = ID then
  2270. raise ETagAlreadyExists.CreateResFmt(@STagAlreadyExists, [ID]);
  2271. if Tag.ID > ID then
  2272. begin
  2273. Result := TExifTag.Create(Self, ID, DataType, ElementCount);
  2274. FTagList.Insert(I, Result);
  2275. Changed;
  2276. Exit;
  2277. end;
  2278. end;
  2279. Result := TExifTag.Create(Self, ID, DataType, ElementCount);
  2280. FTagList.Add(Result);
  2281. Changed;
  2282. end;
  2283. procedure TExifSection.Changed;
  2284. begin
  2285. FModified := True;
  2286. if (FOwner <> nil) and (FKind <> esUserDefined) then FOwner.Changed(Self);
  2287. end;
  2288. procedure TExifSection.Clear;
  2289. var
  2290. I: Integer;
  2291. begin
  2292. FLoadErrors := [];
  2293. if FOwner <> nil then FOwner.BeginUpdate;
  2294. try
  2295. for I := FTagList.Count - 1 downto 0 do
  2296. DoDelete(I, True);
  2297. finally
  2298. if FOwner <> nil then FOwner.EndUpdate;
  2299. end;
  2300. end;
  2301. procedure TExifSection.DoDelete(TagIndex: Integer; FreeTag: Boolean);
  2302. var
  2303. Tag: TExifTag;
  2304. begin
  2305. Tag := TExifTag(FTagList[TagIndex]);
  2306. FTagList.Delete(TagIndex);
  2307. Tag.FSection := nil;
  2308. if (Tag.ID = ttMakerNote) and (FKind = esDetails) and (FOwner <> nil) then
  2309. FOwner.ResetMakerNoteType;
  2310. if FreeTag then Tag.Free; //!!!don't call Destroy directly under ARC
  2311. Changed;
  2312. end;
  2313. function TExifSection.EnforceASCII: Boolean;
  2314. begin
  2315. Result := (FOwner <> nil) and FOwner.EnforceASCII;
  2316. end;
  2317. function TExifSection.Find(ID: TExifTagID; out Tag: TExifTag): Boolean;
  2318. var
  2319. Index: Integer;
  2320. begin
  2321. Result := FindIndex(ID, Index);
  2322. if Result then
  2323. Tag := TExifTag(FTagList[Index])
  2324. else
  2325. Tag := nil;
  2326. end;
  2327. function TExifSection.FindIndex(ID: TExifTagID; var TagIndex: Integer): Boolean;
  2328. var
  2329. I: Integer;
  2330. begin
  2331. Result := False;
  2332. for I := 0 to FTagList.Count - 1 do
  2333. if TExifTag(FTagList[I]).ID >= ID then
  2334. begin
  2335. if TExifTag(FTagList[I]).ID = ID then
  2336. begin
  2337. TagIndex := I;
  2338. Result := True;
  2339. end;
  2340. Exit;
  2341. end;
  2342. end;
  2343. function TExifSection.FindTag(TagID: TTiffTagID; out ParsedTag: ITiffTag): Boolean;
  2344. var
  2345. Obj: TExifTag;
  2346. begin
  2347. Result := Find(TagID, Obj);
  2348. if Result then ParsedTag := Obj;
  2349. end;
  2350. function TExifSection.ForceSetElement(ID: TExifTagID; DataType: TExifDataType;
  2351. Index: Integer; const Value): TExifTag;
  2352. var
  2353. Dest: PByte;
  2354. NewValueIsDifferent: Boolean;
  2355. begin
  2356. Assert(Index >= 0);
  2357. if Find(ID, Result) then
  2358. Result.UpdateData(DataType, Max(Result.ElementCount, Succ(Index)), PByte(nil)^)
  2359. else
  2360. Result := Add(ID, DataType, Succ(Index));
  2361. Dest := @PByteArray(Result.Data)[Index * TiffElementSizes[DataType]];
  2362. NewValueIsDifferent := not CompareMem(Dest, @Value, TiffElementSizes[DataType]);
  2363. if NewValueIsDifferent then
  2364. begin
  2365. Move(Value, Dest^, TiffElementSizes[DataType]);
  2366. Result.Changed;
  2367. end;
  2368. end;
  2369. function TExifSection.LoadSubDirectory(OffsetTagID: TTiffTagID): ITiffDirectory;
  2370. begin
  2371. Result := nil;
  2372. if Owner <> nil then
  2373. case Kind of
  2374. esGeneral:
  2375. case OffsetTagID of
  2376. ttExifOffset: Result := Owner[esDetails];
  2377. ttGPSOffset: Result := Owner[esGPS];
  2378. end;
  2379. esDetails: if OffsetTagID = ttInteropOffset then Result := Owner[esInterop];
  2380. end;
  2381. if Result = nil then
  2382. raise EInvalidTiffData.CreateRes(@SInvalidOffsetTag);
  2383. end;
  2384. function TExifSection.GetTagCount: Integer;
  2385. begin
  2386. Result := FTagList.Count;
  2387. end;
  2388. function TExifSection.GetEnumerator: TEnumerator;
  2389. begin
  2390. Result := TEnumerator.Create(FTagList);
  2391. end;
  2392. function TExifSection.GetEnumeratorIntf: ITiffDirectoryEnumerator;
  2393. begin
  2394. Result := GetEnumerator;
  2395. end;
  2396. function TExifSection.GetIndex: Integer;
  2397. begin
  2398. case FKind of
  2399. esGeneral: Result := 0;
  2400. esDetails: Result := ttExifOffset;
  2401. esInterop: Result := ttInteropOffset;
  2402. esGPS: Result := ttGPSOffset;
  2403. esThumbnail: Result := 1;
  2404. else Result := -1;
  2405. end;
  2406. end;
  2407. function TExifSection.GetParent: ITiffDirectory;
  2408. begin
  2409. if Owner = nil then
  2410. Result := nil
  2411. else
  2412. case FKind of
  2413. esDetails, esGPS: Result := Owner[esGeneral];
  2414. esInterop, esMakerNote: Result := Owner[esDetails];
  2415. else Result := nil;
  2416. end;
  2417. end;
  2418. function TExifSection.GetByteValue(TagID: TExifTagID; Index: Integer; Default: Byte;
  2419. MinValue: Byte = 0; MaxValue: Byte = High(Byte)): Byte;
  2420. begin
  2421. if not TryGetByteValue(TagID, Index, Result) or (Result < MinValue) or (Result > MaxValue) then
  2422. Result := Default;
  2423. end;
  2424. function TExifSection.GetDateTimeValue(MainID, SubSecsID: TExifTagID): TDateTimeTagValue;
  2425. var
  2426. DateTime: TDateTime;
  2427. SubSecsTag: TExifTag;
  2428. SubSecs: Integer;
  2429. S: TiffString;
  2430. begin
  2431. if not TryExifStringToDateTime(GetStringValue(MainID), DateTime) then
  2432. begin
  2433. Result := TDateTimeTagValue.CreateMissingOrInvalid;
  2434. Exit;
  2435. end;
  2436. if (Owner <> nil) and (SubSecsID <> 0) and Owner[esDetails].Find(SubSecsID, SubSecsTag) and
  2437. (SubSecsTag.ElementCount > 1) and (SubSecsTag.DataType = tdAscii) then
  2438. begin
  2439. SetLength(S, 3);
  2440. FillChar(Pointer(S)^, 3, '0');
  2441. Move(SubSecsTag.Data^, S[1], Max(3, SubSecsTag.ElementCount - 1));
  2442. if TryStrToInt(string(S), SubSecs) then
  2443. IncMilliSecond(DateTime, SubSecs);
  2444. end;
  2445. Result := DateTime;
  2446. end;
  2447. function TExifSection.GetFractionValue(TagID: TExifTagID; Index: Integer): TExifFraction;
  2448. begin
  2449. Result := GetFractionValue(TagID, Index, NullFraction)
  2450. end;
  2451. function TExifSection.GetFractionValue(TagID: TExifTagID; Index: Integer;
  2452. const Default: TExifFraction): TExifFraction;
  2453. var
  2454. Tag: TExifTag;
  2455. begin
  2456. if Find(TagID, Tag) and (Tag.DataType in [tdLongWordFraction,
  2457. tdLongIntFraction]) and (Tag.ElementCount > Index) and (Index >= 0) then
  2458. Result := PExifFractionArray(Tag.Data)[Index]
  2459. else
  2460. Result := Default;
  2461. end;
  2462. function TExifSection.GetLongIntValue(TagID: TExifTagID; Index: Integer): TLongIntTagValue;
  2463. begin
  2464. Result := 0; //needs initialising first
  2465. if not TryGetLongWordValue(TagID, Index, Result) then
  2466. Result := TLongIntTagValue.CreateMissingOrInvalid;
  2467. end;
  2468. function TExifSection.GetLongIntValue(TagID: TExifTagID; Index: Integer;
  2469. Default: LongInt): LongInt;
  2470. begin
  2471. if not TryGetLongWordValue(TagID, Index, Result) then
  2472. Result := Default;
  2473. end;
  2474. function TExifSection.GetLongWordValue(TagID: TExifTagID; Index: Integer): TLongWordTagValue;
  2475. begin
  2476. Result := 0; //needs initialising first
  2477. if not TryGetLongWordValue(TagID, Index, Result) then
  2478. Result := TLongWordTagValue.CreateMissingOrInvalid;
  2479. end;
  2480. function TExifSection.GetLongWordValue(TagID: TExifTagID; Index: Integer;
  2481. Default: LongWord): LongWord;
  2482. begin
  2483. if not TryGetLongWordValue(TagID, Index, Result) then
  2484. Result := Default;
  2485. end;
  2486. function TExifSection.GetSmallIntValue(TagID: TExifTagID; Index: Integer; Default: SmallInt;
  2487. MinValue: SmallInt = Low(SmallInt); MaxValue: SmallInt = High(SmallInt)): SmallInt;
  2488. begin
  2489. if not TryGetWordValue(TagID, Index, Result) or (Result < MinValue) or (Result > MaxValue) then
  2490. Result := Default;
  2491. end;
  2492. function TExifSection.GetStringValue(TagID: TExifTagID;
  2493. const Default: string): string;
  2494. begin
  2495. if not TryGetStringValue(TagID, Result) then
  2496. Result := Default;
  2497. end;
  2498. function TExifSection.GetWindowsStringValue(TagID: TExifTagID;
  2499. const Default: UnicodeString): UnicodeString;
  2500. begin
  2501. if not TryGetWindowsStringValue(TagID, Result) then
  2502. Result := Default;
  2503. end;
  2504. function TExifSection.GetWordValue(TagID: TExifTagID; Index: Integer): TWordTagValue;
  2505. begin
  2506. Result := 0; //ensure default as NOT missing or invalid
  2507. if not TryGetWordValue(TagID, Index, Result) then
  2508. Result := TWordTagValue.CreateMissingOrInvalid;
  2509. end;
  2510. function TExifSection.GetWordValue(TagID: TExifTagID; Index: Integer; Default: Word;
  2511. MinValue: Word = 0; MaxValue: Word = High(Word)): Word;
  2512. begin
  2513. if not TryGetWordValue(TagID, Index, Result) or (Result < MinValue) or (Result > MaxValue) then
  2514. Result := Default;
  2515. end;
  2516. function TExifSection.IsExtendable: Boolean;
  2517. begin
  2518. Result := InheritsFrom(TExtendableExifSection);
  2519. end;
  2520. procedure TExifSection.Load(const Directory: IFoundTiffDirectory;
  2521. TiffImageSource: Boolean);
  2522. var
  2523. I: Integer;
  2524. NewTag: TExifTag;
  2525. begin
  2526. Clear;
  2527. FLoadErrors := Directory.LoadErrors;
  2528. if Directory.TagInfo = nil then
  2529. FFirstTagHeaderOffset := 0
  2530. else
  2531. begin
  2532. FFirstTagHeaderOffset := Directory.TagInfo[0].HeaderOffset;
  2533. for I := 0 to High(Directory.TagInfo) do
  2534. if not TiffImageSource or (Kind <> esGeneral) or IsKnownExifTagInMainIFD(Directory.TagInfo[I]) then
  2535. begin
  2536. NewTag := TExifTag.Create(Self, Directory, I);
  2537. FTagList.Add(NewTag);
  2538. end;
  2539. FTagList.Sort;
  2540. end;
  2541. FModified := False;
  2542. end;
  2543. function TExifSection.Remove(ID: TExifTagID): Boolean;
  2544. var
  2545. Index: Integer;
  2546. begin
  2547. Result := FindIndex(ID, Index);
  2548. if Result then DoDelete(Index, True);
  2549. end;
  2550. procedure TExifSection.Remove(const IDs: array of TExifTagID);
  2551. var
  2552. I: Integer;
  2553. ID: TExifTagID;
  2554. Tag: TExifTag;
  2555. begin
  2556. if Owner <> nil then Owner.BeginUpdate;
  2557. try
  2558. for I := FTagList.Count - 1 downto 0 do
  2559. begin
  2560. Tag := TExifTag(FTagList[I]);
  2561. for ID in IDs do
  2562. if ID = Tag.ID then
  2563. begin
  2564. DoDelete(I, True);
  2565. Break;
  2566. end;
  2567. end;
  2568. finally
  2569. if Owner <> nil then Owner.EndUpdate;
  2570. end;
  2571. end;
  2572. function TExifSection.RemovePaddingTag: Boolean;
  2573. var
  2574. Tag: TExifTag;
  2575. begin
  2576. Result := False;
  2577. for Tag in Self do
  2578. if Tag.IsPadding then
  2579. begin
  2580. Tag.Delete;
  2581. Result := True;
  2582. Exit;
  2583. end;
  2584. end;
  2585. function TExifSection.SetByteValue(TagID: TExifTagID; Index: Integer; Value: Byte): TExifTag;
  2586. begin
  2587. Result := ForceSetElement(TagID, tdByte, Index, Value);
  2588. end;
  2589. procedure TExifSection.SetDateTimeValue(MainID, SubSecsID: TExifTagID;
  2590. const DateTime: TDateTimeTagValue);
  2591. var
  2592. SubSecsTag: TExifTag;
  2593. begin
  2594. if (Owner = nil) or (SubSecsID = 0) then
  2595. SubSecsTag := nil
  2596. else
  2597. if not Owner[esDetails].Find(SubSecsID, SubSecsTag) then
  2598. if not DateTime.MissingOrInvalid and Owner.AlwaysWritePreciseTimes then
  2599. SubSecsTag := Owner[esDetails].Add(SubSecsID, tdAscii, 4)
  2600. else
  2601. SubSecsTag := nil;
  2602. if DateTime.MissingOrInvalid then
  2603. begin
  2604. Remove(MainID);
  2605. FreeAndNil(SubSecsTag);
  2606. end
  2607. else
  2608. begin
  2609. if DateTime <> LastSetDateTimeValue then
  2610. begin
  2611. LastSetDateTimeValue := DateTime.Value;
  2612. LastSetDateTimeMainStr := DateTimeToExifString(DateTime.Value);
  2613. LastSetDateTimeSubSecStr := GetExifSubSecsString(DateTime.Value);
  2614. end;
  2615. SetStringValue(MainID, LastSetDateTimeMainStr);
  2616. if SubSecsTag <> nil then
  2617. begin
  2618. SubSecsTag.DataType := tdAscii;
  2619. SubSecsTag.AsString := LastSetDateTimeSubSecStr;
  2620. end;
  2621. end;
  2622. end;
  2623. procedure TExifSection.DoSetFractionValue(TagID: TExifTagID; Index: Integer;
  2624. DataType: TExifDataType; const Value);
  2625. var
  2626. Tag: TExifTag;
  2627. begin
  2628. if Int64(Value) = 0 then //prefer deleting over setting null fractions
  2629. if not Find(TagID, Tag) or (Tag.ElementCount <= Index) then
  2630. Exit
  2631. else if Tag.ElementCount = Succ(Index) then
  2632. begin
  2633. if Index = 0 then
  2634. Tag.Delete
  2635. else
  2636. Tag.ElementCount := Index;
  2637. Exit;
  2638. end;
  2639. ForceSetElement(TagID, DataType, Index, Value);
  2640. end;
  2641. procedure TExifSection.SetFractionValue(TagID: TExifTagID; Index: Integer;
  2642. const Value: TExifFraction);
  2643. begin
  2644. DoSetFractionValue(TagID, Index, tdExifFraction, Value);
  2645. end;
  2646. function TExifSection.SetLongWordValue(TagID: TExifTagID; Index: Integer; Value: LongWord): TExifTag;
  2647. begin
  2648. Result := ForceSetElement(TagID, tdLongWord, Index, Value);
  2649. end;
  2650. procedure TExifSection.SetSignedFractionValue(TagID: TExifTagID; Index: Integer;
  2651. const Value: TExifSignedFraction);
  2652. begin
  2653. DoSetFractionValue(TagID, Index, tdExifFraction, Value);
  2654. end;
  2655. procedure TExifSection.SetStringValue(TagID: TExifTagID; const Value: string);
  2656. var
  2657. ElemCount: Integer;
  2658. Tag: TExifTag;
  2659. begin
  2660. if Value = '' then
  2661. begin
  2662. Remove(TagID);
  2663. Exit;
  2664. end;
  2665. if EnforceASCII and not ContainsOnlyASCII(Value) then
  2666. raise ENotOnlyASCIIError.CreateRes(@STagCanContainOnlyASCII);
  2667. ElemCount := Length(Value) + 1; //ascii tiff tag data includes null terminator
  2668. if not Find(TagID, Tag) then
  2669. Tag := Add(TagID, tdAscii, ElemCount);
  2670. Tag.UpdateData(tdAscii, ElemCount, PAnsiChar(TiffString(Value))^)
  2671. end;
  2672. procedure TExifSection.SetWindowsStringValue(TagID: TExifTagID; const Value: UnicodeString);
  2673. var
  2674. ElemCount: Integer;
  2675. Tag: TExifTag;
  2676. begin
  2677. if Value = '' then
  2678. begin
  2679. Remove(TagID);
  2680. Exit;
  2681. end;
  2682. ElemCount := (Length(Value) + 1) * 2; //data includes null terminator
  2683. if not Find(TagID, Tag) then
  2684. Tag := Add(TagID, tdByte, ElemCount);
  2685. Tag.UpdateData(tdByte, ElemCount, PWideChar(Value)^);
  2686. end;
  2687. function TExifSection.SetWordValue(TagID: TExifTagID; Index: Integer; Value: Word): TExifTag;
  2688. begin
  2689. Result := ForceSetElement(TagID, tdWord, Index, Value);
  2690. end;
  2691. procedure TExifSection.TagChanging(Tag: TExifTag; NewID: TExifTagID;
  2692. NewDataType: TExifDataType; NewElementCount: LongInt; NewData: Boolean);
  2693. var
  2694. NewDataSize: Integer;
  2695. OtherTag: TExifTag;
  2696. begin
  2697. if (NewID <> Tag.ID) and CheckExtendable.Find(NewID, OtherTag) then //Changing of tag IDs is disallowed in patch
  2698. raise ETagAlreadyExists.CreateFmt(STagAlreadyExists, [NewID]); //mode to ensure sorting of IFD is preserved.
  2699. NewDataSize := TiffElementSizes[NewDataType] * NewElementCount;
  2700. if (NewDataSize > 4) and (NewDataSize > Tag.OriginalDataSize) then
  2701. CheckExtendable;
  2702. if (FKind = esDetails) and (Tag.ID = ttMakerNote) and (FOwner <> nil) then
  2703. FOwner.ResetMakerNoteType
  2704. end;
  2705. procedure TExifSection.TagChanged(Tag: TExifTag; ChangeType: TExifTagChangeType);
  2706. var
  2707. I: Integer;
  2708. begin
  2709. if ChangeType = tcID then
  2710. for I := FTagList.Count - 1 downto 0 do
  2711. if Tag.ID > TExifTag(FTagList[I]).ID then
  2712. begin
  2713. FTagList.Move(FTagList.IndexOf(Tag), I + 1);
  2714. Break;
  2715. end;
  2716. Changed;
  2717. end;
  2718. procedure TExifSection.TagDeleting(Tag: TExifTag);
  2719. begin
  2720. DoDelete(FTagList.IndexOf(Tag), False);
  2721. end;
  2722. function TExifSection.TagExists(ID: TExifTagID; ValidDataTypes: TExifDataTypes;
  2723. MinElementCount, MaxElementCount: LongInt): Boolean;
  2724. var
  2725. Tag: TExifTag;
  2726. begin
  2727. Result := Find(ID, Tag) and (Tag.DataType in ValidDataTypes) and
  2728. (Tag.ElementCount >= MinElementCount) and (Tag.ElementCount <= MaxElementCount);
  2729. end;
  2730. function TExifSection.TryGetByteValue(TagID: TExifTagID; Index: Integer; var Value): Boolean;
  2731. var
  2732. Tag: TExifTag;
  2733. begin
  2734. Result := Find(TagID, Tag) and (Tag.DataType in [tdByte, tdShortInt, tdUndefined]) and
  2735. (Tag.ElementCount > Index) and (Index >= 0);
  2736. if Result then
  2737. Byte(Value) := PByteArray(Tag.Data)[Index];
  2738. end;
  2739. function TExifSection.TryGetLongWordValue(TagID: TExifTagID; Index: Integer;
  2740. var Value): Boolean;
  2741. var
  2742. Tag: TExifTag;
  2743. begin
  2744. Result := Find(TagID, Tag) and (Index < Tag.ElementCount) and (Index >= 0);
  2745. if Result then
  2746. case Tag.DataType of
  2747. tdByte, tdShortInt: LongWord(Value) := PByteArray(Tag.Data)[Index];
  2748. tdWord, tdSmallInt: LongWord(Value) := PWordArray(Tag.Data)[Index];
  2749. tdLongWord, tdLongInt: LongWord(Value) := PLongWordArray(Tag.Data)[Index];
  2750. else Result := False;
  2751. end;
  2752. end;
  2753. function TExifSection.TryGetStringValue(TagID: TExifTagID; var Value: string): Boolean;
  2754. var
  2755. Len: Integer;
  2756. Tag: TExifTag;
  2757. S: AnsiString;
  2758. begin
  2759. Result := Find(TagID, Tag) and (Tag.DataType = tdAscii) and (Tag.ElementCount > 0);
  2760. if Result then
  2761. begin
  2762. Len := Tag.ElementCount - 1;
  2763. if PAnsiChar(Tag.Data)[Len] > ' ' then
  2764. Inc(Len)
  2765. else
  2766. while (Len > 0) and (PAnsiChar(Tag.Data)[Len - 1] = #0) do
  2767. Dec(Len);
  2768. SetString(S, PAnsiChar(Tag.Data), Len);
  2769. Value := string(S); //for D2009+ compatibility
  2770. end
  2771. end;
  2772. function TExifSection.TryGetWindowsStringValue(TagID: TExifTagID; var Value: UnicodeString): Boolean;
  2773. var
  2774. Tag: TExifTag;
  2775. begin
  2776. Result := Find(TagID, Tag) and (Tag.DataType = tdByte) and (Tag.ElementCount > 1); //should have at least 2 bytes since null terminated
  2777. if Result then
  2778. SetString(Value, PWideChar(Tag.Data), Tag.ElementCount div 2 - 1)
  2779. end;
  2780. function TExifSection.TryGetWordValue(TagID: TExifTagID; Index: Integer;
  2781. var Value): Boolean;
  2782. var
  2783. Tag: TExifTag;
  2784. begin
  2785. Result := Find(TagID, Tag) and (Index < Tag.ElementCount) and (Index >= 0);
  2786. if Result then
  2787. case Tag.DataType of
  2788. tdByte, tdShortInt: Word(Value) := PByteArray(Tag.Data)[Index];
  2789. tdWord, tdSmallInt: Word(Value) := PWordArray(Tag.Data)[Index];
  2790. else Result := False;
  2791. end;
  2792. end;
  2793. { TExtendableExifSection }
  2794. function TExtendableExifSection.Add(ID: TExifTagID; DataType: TExifDataType;
  2795. ElementCount: LongInt): TExifTag;
  2796. begin
  2797. Result := inherited Add(ID, DataType, ElementCount);
  2798. end;
  2799. function TExtendableExifSection.AddOrUpdate(ID: TExifTagID; DataType: TExifDataType;
  2800. ElementCount: Integer): TExifTag;
  2801. begin
  2802. if not Find(ID, Result) then
  2803. Result := Add(ID, DataType, ElementCount);
  2804. Result.UpdateData(DataType, ElementCount, Pointer(nil)^);
  2805. end;
  2806. function TExtendableExifSection.AddOrUpdate(ID: TExifTagID; DataType: TExifDataType;
  2807. ElementCount: Integer; const Data): TExifTag;
  2808. begin
  2809. if not Find(ID, Result) then
  2810. Result := Add(ID, DataType, ElementCount);
  2811. Result.UpdateData(DataType, ElementCount, Data);
  2812. end;
  2813. function TExtendableExifSection.AddOrUpdate(ID: TExifTagID; DataType: TExifDataType;
  2814. const Source: IStreamPersist): TExifTag;
  2815. var
  2816. Stream: TMemoryStream;
  2817. begin
  2818. Stream := TMemoryStream.Create;
  2819. try
  2820. if Source <> nil then Source.SaveToStream(Stream);
  2821. Result := AddOrUpdate(ID, DataType, Ceil(Stream.Size / TiffElementSizes[DataType]),
  2822. Stream.Memory^);
  2823. finally
  2824. Stream.Free;
  2825. end;
  2826. end;
  2827. procedure TExtendableExifSection.Assign(Source: TExifSection);
  2828. begin
  2829. if Owner <> nil then Owner.BeginUpdate;
  2830. try
  2831. if Source = nil then
  2832. Clear
  2833. else
  2834. begin
  2835. Clear;
  2836. CopyTags(Source)
  2837. end
  2838. finally
  2839. if Owner <> nil then Owner.EndUpdate;
  2840. end;
  2841. end;
  2842. procedure TExtendableExifSection.CopyTags(Section: TExifSection);
  2843. var
  2844. Tag: TExifTag;
  2845. begin
  2846. if Section <> nil then
  2847. for Tag in Section do
  2848. AddOrUpdate(Tag.ID, Tag.DataType, Tag.ElementCount, Tag.Data^)
  2849. end;
  2850. { TObjectTagValue }
  2851. constructor TObjectTagValue.Create(const AOwner: TCustomExifData);
  2852. begin
  2853. inherited Create;
  2854. FOwner := AOwner;
  2855. end;
  2856. function TObjectTagValue.GetOwner: TPersistent;
  2857. begin
  2858. Result := FOwner;
  2859. end;
  2860. {$IFNDEF HasToString}
  2861. function TObjectTagValue.ToString: string;
  2862. begin
  2863. Result := ClassName;
  2864. end;
  2865. {$ENDIF}
  2866. { TEnumObjectTagValue }
  2867. function TEnumObjectTagValue.GetValidCharsToAssign: TSysCharSet;
  2868. var
  2869. I: Integer;
  2870. Map: IEnumToCharMapper;
  2871. MapEx: IEnumToCharMapperEx;
  2872. begin
  2873. if FValidCharsToAssign = [] then
  2874. begin
  2875. if not Supports(Self, IEnumToCharMapper, Map) then
  2876. FValidCharsToAssign := [Low(AnsiChar)..High(AnsiChar)]
  2877. else
  2878. begin
  2879. MapEx := GetEnumToCharMapperEx(Map);
  2880. for I := MapEx.MinEnumValue to MapEx.MaxEnumValue do
  2881. Include(FValidCharsToAssign, Map.EnumValueToChar(I))
  2882. end;
  2883. end;
  2884. Result := FValidCharsToAssign;
  2885. end;
  2886. { TExifFlashInfo }
  2887. function DoGetMode(const BitSet: TWordBitSet): TExifFlashMode;
  2888. begin
  2889. if 4 in BitSet then
  2890. if 3 in BitSet then
  2891. Result := efAuto
  2892. else
  2893. Result := efCompulsorySuppression
  2894. else
  2895. if 3 in BitSet then
  2896. Result := efCompulsoryFire
  2897. else
  2898. Result := efUnknown;
  2899. end;
  2900. function DoGetStrobeLight(const BitSet: TWordBitSet): TExifStrobeLight;
  2901. begin
  2902. if 2 in BitSet then
  2903. if 1 in BitSet then
  2904. Result := esDetected
  2905. else
  2906. Result := esUndetected
  2907. else
  2908. Result := esNoDetectionFunction;
  2909. end;
  2910. procedure TExifFlashInfo.Assign(Source: TPersistent);
  2911. begin
  2912. if not (Source is TExifFlashInfo) and (Source <> nil) then
  2913. begin
  2914. inherited;
  2915. Exit;
  2916. end;
  2917. Owner.BeginUpdate;
  2918. try
  2919. if Source = nil then
  2920. begin
  2921. BitSet := [];
  2922. StrobeEnergy := NullFraction;
  2923. end
  2924. else
  2925. begin
  2926. BitSet := TExifFlashInfo(Source).BitSet;
  2927. StrobeEnergy := TExifFlashInfo(Source).StrobeEnergy;
  2928. end;
  2929. finally
  2930. Owner.EndUpdate;
  2931. end;
  2932. end;
  2933. function TExifFlashInfo.MissingOrInvalid: Boolean;
  2934. begin
  2935. with Owner[esDetails] do
  2936. Result := not TagExists(ttFlash, [tdWord, tdSmallInt]) and
  2937. not TagExists(ttFlashEnergy, [tdLongWordFraction, tdLongIntFraction]);
  2938. end;
  2939. function TExifFlashInfo.GetBitSet: TWordBitSet;
  2940. begin
  2941. if not Owner[esDetails].TryGetWordValue(ttFlash, 0, Result) then
  2942. Result := [];
  2943. end;
  2944. procedure TExifFlashInfo.SetBitSet(const Value: TWordBitSet);
  2945. const
  2946. XMPRoot = UnicodeString('Flash');
  2947. StrobeLightValues: array[TExifStrobeLight] of Integer = (0, 2, 3);
  2948. var
  2949. Root: TXMPProperty;
  2950. ValueAsSource: Integer absolute Value;
  2951. begin
  2952. if Value = [] then
  2953. begin
  2954. Owner[esDetails].Remove(ttFlash);
  2955. Owner.XMPPacket.RemoveProperty(xsExif, XMPRoot);
  2956. Exit;
  2957. end;
  2958. Owner[esDetails].ForceSetElement(ttFlash, tdWord, 0, Value);
  2959. if Owner.XMPWritePolicy = xwRemove then
  2960. begin
  2961. Owner.XMPPacket.RemoveProperty(xsExif, XMPRoot);
  2962. Exit;
  2963. end;
  2964. if not Owner.XMPPacket[xsExif].FindProperty(XMPRoot, Root) then
  2965. if Owner.XMPWritePolicy = xwUpdateIfExists then
  2966. Exit
  2967. else
  2968. Root := Owner.XMPPacket[xsExif].AddProperty(XMPRoot);
  2969. Root.Kind := xpStructure;
  2970. Root.UpdateSubProperty('Fired', FiredBit in Value);
  2971. Root.UpdateSubProperty('Function', NotPresentBit in Value);
  2972. Root.UpdateSubProperty('Mode', Ord(DoGetMode(Value)));
  2973. Root.UpdateSubProperty('RedEyeMode', RedEyeReductionBit in Value);
  2974. Root.UpdateSubProperty('Return', StrobeLightValues[DoGetStrobeLight(Value)]);
  2975. end;
  2976. function TExifFlashInfo.GetFired: Boolean;
  2977. begin
  2978. Result := FiredBit in BitSet;
  2979. end;
  2980. procedure TExifFlashInfo.SetFired(Value: Boolean);
  2981. begin
  2982. if Value then
  2983. BitSet := BitSet + [FiredBit]
  2984. else
  2985. BitSet := BitSet - [FiredBit]
  2986. end;
  2987. function TExifFlashInfo.GetMode: TExifFlashMode;
  2988. begin
  2989. Result := DoGetMode(BitSet);
  2990. end;
  2991. procedure TExifFlashInfo.SetMode(const Value: TExifFlashMode);
  2992. var
  2993. Values: TWordBitSet;
  2994. begin
  2995. Values := BitSet;
  2996. if Value in [efCompulsorySuppression, efAuto] then
  2997. Include(Values, 4)
  2998. else
  2999. Exclude(Values, 4);
  3000. if Value in [efCompulsoryFire, efAuto] then
  3001. Include(Values, 3)
  3002. else
  3003. Exclude(Values, 3);
  3004. BitSet := Values;
  3005. end;
  3006. function TExifFlashInfo.GetPresent: Boolean;
  3007. begin
  3008. Result := not (NotPresentBit in BitSet);
  3009. end;
  3010. procedure TExifFlashInfo.SetPresent(Value: Boolean);
  3011. begin
  3012. if Value then
  3013. BitSet := BitSet - [NotPresentBit]
  3014. else
  3015. BitSet := BitSet + [NotPresentBit]
  3016. end;
  3017. function TExifFlashInfo.GetRedEyeReduction: Boolean;
  3018. begin
  3019. Result := RedEyeReductionBit in BitSet
  3020. end;
  3021. procedure TExifFlashInfo.SetRedEyeReduction(Value: Boolean);
  3022. begin
  3023. if Value then
  3024. BitSet := BitSet + [RedEyeReductionBit]
  3025. else
  3026. BitSet := BitSet - [RedEyeReductionBit]
  3027. end;
  3028. function TExifFlashInfo.GetStrobeLight: TExifStrobeLight;
  3029. begin
  3030. Result := DoGetStrobeLight(BitSet);
  3031. end;
  3032. procedure TExifFlashInfo.SetStrobeLight(const Value: TExifStrobeLight);
  3033. var
  3034. Values: TWordBitSet;
  3035. begin
  3036. Values := BitSet;
  3037. Include(Values, 2);
  3038. case Value of
  3039. esUndetected: Exclude(Values, 1);
  3040. esDetected: Include(Values, 1);
  3041. else
  3042. Exclude(Values, 1);
  3043. Exclude(Values, 2);
  3044. end;
  3045. BitSet := Values;
  3046. end;
  3047. function TExifFlashInfo.GetStrobeEnergy: TExifFraction;
  3048. begin
  3049. Result := Owner[esDetails].GetFractionValue(ttFlashEnergy, 0);
  3050. end;
  3051. procedure TExifFlashInfo.SetStrobeEnergy(const Value: TExifFraction);
  3052. begin
  3053. Owner[esDetails].SetFractionValue(ttFlashEnergy, 0, Value);
  3054. Owner.XMPPacket.UpdateProperty(xsExif, 'FlashEnergy', Value.ToString);
  3055. end;
  3056. { TCustomExifVersion }
  3057. constructor TCustomExifVersion.Create(const AOwner: TCustomExifData);
  3058. begin
  3059. inherited Create(AOwner);
  3060. FMajorIndex := 1;
  3061. FStoreAsChar := True;
  3062. FTiffDataType := tdUndefined;
  3063. Initialize;
  3064. end;
  3065. procedure TCustomExifVersion.Assign(Source: TPersistent);
  3066. begin
  3067. if Source = nil then
  3068. Clear
  3069. else if not (Source is TCustomExifVersion) then
  3070. inherited
  3071. else if TCustomExifVersion(Source).MissingOrInvalid then
  3072. Clear
  3073. else
  3074. begin
  3075. Major := TCustomExifVersion(Source).Major;
  3076. Minor := TCustomExifVersion(Source).Minor;
  3077. Release := TCustomExifVersion(Source).Release;
  3078. end;
  3079. end;
  3080. procedure TCustomExifVersion.Clear;
  3081. begin
  3082. Owner[FSectionKind].Remove(FTagID);
  3083. end;
  3084. function TCustomExifVersion.MissingOrInvalid: Boolean;
  3085. begin
  3086. Result := (Major = 0) and (Minor = 0) and (Release = 0);
  3087. end;
  3088. function TCustomExifVersion.GetAsString: string;
  3089. begin
  3090. if MissingOrInvalid then
  3091. Result := ''
  3092. else
  3093. FmtStr(Result, '%d%s%d%s%d', [Major, DecimalSeparator, Minor, DecimalSeparator, Release]);
  3094. end;
  3095. procedure TCustomExifVersion.SetAsString(const Value: string);
  3096. var
  3097. SeekPtr: PChar;
  3098. function GetElement: TExifVersionElement;
  3099. begin
  3100. if SeekPtr^ = #0 then
  3101. Result := 0
  3102. else
  3103. begin
  3104. {$RANGECHECKS ON}
  3105. Result := Ord(SeekPtr^) - Ord('0');
  3106. {$IFDEF RangeCheckingOff}{$RANGECHECKS OFF}{$ENDIF}
  3107. Inc(SeekPtr);
  3108. end;
  3109. end;
  3110. begin
  3111. SeekPtr := Pointer(Value); //we *could* cast to a PChar and so be able to remove the
  3112. if SeekPtr = nil then //next five lines, but doing it this way gets the source
  3113. begin //tag removed if the string is empty
  3114. Assign(nil);
  3115. Exit;
  3116. end;
  3117. Major := GetElement;
  3118. if SeekPtr^ <> #0 then
  3119. begin
  3120. Inc(SeekPtr); //skip past separator, whatever that may precisely be
  3121. Minor := GetElement;
  3122. if not IsCharIn(SeekPtr^, [#0, '0'..'9']) then Inc(SeekPtr); //ditto, though allow no separator too
  3123. Release := GetElement;
  3124. end
  3125. else
  3126. begin
  3127. Minor := 0;
  3128. Release := 0;
  3129. end;
  3130. end;
  3131. function TCustomExifVersion.ToString: string;
  3132. begin
  3133. Result := AsString;
  3134. end;
  3135. function TCustomExifVersion.GetValue(Index: Integer): TExifVersionElement;
  3136. var
  3137. RawValue: Byte;
  3138. begin
  3139. if not Owner[FSectionKind].TryGetByteValue(FTagID, Index, RawValue) then
  3140. Result := 0
  3141. else if RawValue >= Ord('0') then
  3142. Result := RawValue - Ord('0')
  3143. else
  3144. Result := RawValue;
  3145. end;
  3146. procedure TCustomExifVersion.SetValue(Index: Integer; Value: TExifVersionElement);
  3147. var
  3148. RawValue: Byte;
  3149. begin
  3150. RawValue := Value;
  3151. if FStoreAsChar then Inc(RawValue, Ord('0'));
  3152. Owner[FSectionKind].ForceSetElement(FTagID, FTiffDataType, Index, RawValue);
  3153. end;
  3154. function TCustomExifVersion.GetMajor: TExifVersionElement;
  3155. begin
  3156. Result := GetValue(FMajorIndex);
  3157. end;
  3158. function TCustomExifVersion.GetMinor: TExifVersionElement;
  3159. begin
  3160. Result := GetValue(FMajorIndex + 1);
  3161. end;
  3162. function TCustomExifVersion.GetRelease: TExifVersionElement;
  3163. begin
  3164. Result := GetValue(FMajorIndex + 2);
  3165. end;
  3166. procedure TCustomExifVersion.SetMajor(Value: TExifVersionElement);
  3167. begin
  3168. SetValue(FMajorIndex, Value);
  3169. end;
  3170. procedure TCustomExifVersion.SetMinor(Value: TExifVersionElement);
  3171. begin
  3172. SetValue(FMajorIndex + 1, Value);
  3173. end;
  3174. procedure TCustomExifVersion.SetRelease(Value: TExifVersionElement);
  3175. begin
  3176. SetValue(FMajorIndex + 2, Value);
  3177. end;
  3178. { TExifVersion }
  3179. constructor TExifVersion.Create(const AOwner: TCustomExifData);
  3180. begin
  3181. inherited Create(AOwner);
  3182. end;
  3183. constructor TExifVersion.Create;
  3184. begin
  3185. Create(nil);
  3186. end;
  3187. procedure TExifVersion.Initialize;
  3188. begin
  3189. FSectionKind := esDetails;
  3190. FTagID := ttExifVersion;
  3191. end;
  3192. procedure TExifVersion.Clear;
  3193. begin
  3194. if Owner = nil then
  3195. FillChar(FValues, SizeOf(FValues), 0)
  3196. else
  3197. inherited;
  3198. end;
  3199. function TExifVersion.GetValue(Index: Integer): TExifVersionElement;
  3200. begin
  3201. if Owner = nil then
  3202. Result := FValues[Index]
  3203. else
  3204. Result := inherited GetValue(Index);
  3205. end;
  3206. procedure TExifVersion.SetValue(Index: Integer; Value: TExifVersionElement);
  3207. begin
  3208. if Owner = nil then
  3209. FValues[Index] := Value
  3210. else
  3211. inherited SetValue(Index, Value);
  3212. end;
  3213. { TFlashPixVersion }
  3214. procedure TFlashPixVersion.Initialize;
  3215. begin
  3216. FSectionKind := esDetails;
  3217. FTagID := ttFlashPixVersion;
  3218. end;
  3219. { TGPSVersion }
  3220. procedure TGPSVersion.Initialize;
  3221. begin
  3222. FMajorIndex := 0;
  3223. FSectionKind := esGPS;
  3224. FStoreAsChar := False;
  3225. FTagID := ttGPSVersionID;
  3226. FTiffDataType := tdByte;
  3227. end;
  3228. { TInteropVersion }
  3229. procedure TInteropVersion.Initialize;
  3230. begin
  3231. FSectionKind := esInterop;
  3232. FTagID := ttInteropVersion;
  3233. end;
  3234. { TCustomExifResolution }
  3235. constructor TCustomExifResolution.Create(const AOwner: TCustomExifData);
  3236. var
  3237. SectionKind: TExifSectionKind;
  3238. begin
  3239. inherited Create(AOwner);
  3240. FXTagID := ttXResolution;
  3241. FYTagID := ttYResolution;
  3242. FUnitTagID := ttResolutionUnit;
  3243. GetTagInfo(SectionKind, FXTagID, FYTagID, FUnitTagID, FSchema, FXName, FYName, FUnitName);
  3244. if AOwner <> nil then FSection := AOwner[SectionKind];
  3245. end;
  3246. procedure TCustomExifResolution.Assign(Source: TPersistent);
  3247. begin
  3248. if not (Source is TCustomExifResolution) and (Source <> nil) then
  3249. begin
  3250. inherited;
  3251. Exit;
  3252. end;
  3253. Owner.BeginUpdate;
  3254. try
  3255. if (Source = nil) or TCustomExifResolution(Source).MissingOrInvalid then
  3256. begin
  3257. Section.Remove(FXTagID);
  3258. Section.Remove(FYTagID);
  3259. Section.Remove(FUnitTagID);
  3260. if FSchema <> xsUnknown then
  3261. Owner.XMPPacket.RemoveProperties(FSchema, [FXName, FYName, FUnitName]);
  3262. end
  3263. else
  3264. begin
  3265. X := TCustomExifResolution(Source).X;
  3266. Y := TCustomExifResolution(Source).Y;
  3267. Units := TCustomExifResolution(Source).Units;
  3268. end;
  3269. finally
  3270. Owner.EndUpdate;
  3271. end;
  3272. end;
  3273. function TCustomExifResolution.GetUnit: TExifResolutionUnit;
  3274. begin
  3275. if not Section.TryGetWordValue(FUnitTagID, 0, Result) then
  3276. Result := trNone;
  3277. end;
  3278. function TCustomExifResolution.GetX: TExifFraction;
  3279. begin
  3280. Result := Section.GetFractionValue(FXTagID, 0);
  3281. end;
  3282. function TCustomExifResolution.GetY: TExifFraction;
  3283. begin
  3284. Result := Section.GetFractionValue(FYTagID, 0);
  3285. end;
  3286. function TCustomExifResolution.MissingOrInvalid: Boolean;
  3287. begin
  3288. Result := not Section.TagExists(FXTagID, [tdLongWordFraction, tdLongWordFraction]) or
  3289. not Section.TagExists(FYTagID, [tdLongWordFraction, tdLongWordFraction]);
  3290. end;
  3291. procedure TCustomExifResolution.SetUnit(const Value: TExifResolutionUnit);
  3292. begin
  3293. Section.SetWordValue(FUnitTagID, 0, Ord(Value));
  3294. if FSchema <> xsUnknown then
  3295. if Value = trNone then
  3296. Owner.XMPPacket.RemoveProperty(FSchema, FUnitName)
  3297. else
  3298. Owner.XMPPacket.UpdateProperty(FSchema, FUnitName, Integer(Value));
  3299. end;
  3300. procedure TCustomExifResolution.SetX(const Value: TExifFraction);
  3301. begin
  3302. Section.SetFractionValue(FXTagID, 0, Value);
  3303. if FSchema <> xsUnknown then
  3304. Owner.XMPPacket.UpdateProperty(FSchema, FXName, Value.ToString);
  3305. end;
  3306. procedure TCustomExifResolution.SetY(const Value: TExifFraction);
  3307. begin
  3308. Section.SetFractionValue(FYTagID, 0, Value);
  3309. if FSchema <> xsUnknown then
  3310. Owner.XMPPacket.UpdateProperty(FSchema, FYName, Value.ToString);
  3311. end;
  3312. function TCustomExifResolution.ToString: string;
  3313. begin
  3314. if MissingOrInvalid then
  3315. begin
  3316. Result := '';
  3317. Exit;
  3318. end;
  3319. case Units of
  3320. trInch: Result := '"';
  3321. trCentimetre: Result := 'cm';
  3322. end;
  3323. FmtStr(Result, '%g%s x %g%1:s', [X.Quotient, Result, Y.Quotient]);
  3324. end;
  3325. { TExifResolution }
  3326. constructor TExifResolution.Create;
  3327. begin
  3328. inherited Create(nil);
  3329. Assign(nil);
  3330. end;
  3331. procedure TExifResolution.Assign(Source: TPersistent);
  3332. begin
  3333. if Source = nil then
  3334. begin
  3335. FX := NullFraction;
  3336. FY := NullFraction;
  3337. FUnit := trNone;
  3338. Exit;
  3339. end;
  3340. if Source is TCustomExifResolution then
  3341. begin
  3342. FX := TCustomExifResolution(Source).X;
  3343. FY := TCustomExifResolution(Source).Y;
  3344. FUnit := TCustomExifResolution(Source).Units;
  3345. Exit;
  3346. end;
  3347. inherited;
  3348. end;
  3349. procedure TExifResolution.GetTagInfo(var Section: TExifSectionKind; var XTag, YTag,
  3350. UnitTag: TExifTagID; var Schema: TXMPNamespace; var XName, YName, UnitName: UnicodeString);
  3351. begin
  3352. end;
  3353. function TExifResolution.GetUnit: TExifResolutionUnit;
  3354. begin
  3355. Result := FUnit;
  3356. end;
  3357. function TExifResolution.GetX: TExifFraction;
  3358. begin
  3359. Result := FX;
  3360. end;
  3361. function TExifResolution.GetY: TExifFraction;
  3362. begin
  3363. Result := FY;
  3364. end;
  3365. function TExifResolution.MissingOrInvalid: Boolean;
  3366. begin
  3367. Result := X.MissingOrInvalid or Y.MissingOrInvalid;
  3368. end;
  3369. procedure TExifResolution.SetUnit(const Value: TExifResolutionUnit);
  3370. begin
  3371. FUnit := Value;
  3372. end;
  3373. procedure TExifResolution.SetX(const Value: TExifFraction);
  3374. begin
  3375. FX := Value;
  3376. end;
  3377. procedure TExifResolution.SetY(const Value: TExifFraction);
  3378. begin
  3379. FY := Value;
  3380. end;
  3381. { TImageResolution }
  3382. procedure TImageResolution.GetTagInfo(var Section: TExifSectionKind; var XTag, YTag,
  3383. UnitTag: TExifTagID; var Schema: TXMPNamespace; var XName, YName, UnitName: UnicodeString);
  3384. begin
  3385. Section := esGeneral;
  3386. Schema := xsTIFF;
  3387. XName := 'XResolution';
  3388. YName := 'YResolution';
  3389. UnitName := 'ResolutionUnit';
  3390. end;
  3391. { TFocalPlaneResolution }
  3392. procedure TFocalPlaneResolution.GetTagInfo(var Section: TExifSectionKind;
  3393. var XTag, YTag, UnitTag: TExifTagID; var Schema: TXMPNamespace;
  3394. var XName, YName, UnitName: UnicodeString);
  3395. begin
  3396. Section := esDetails;
  3397. XTag := ttFocalPlaneXResolution;
  3398. YTag := ttFocalPlaneYResolution;
  3399. UnitTag := ttFocalPlaneResolutionUnit;
  3400. Schema := xsExif;
  3401. XName := 'FocalPlaneXResolution';
  3402. YName := 'FocalPlaneYResolution';
  3403. UnitName := 'FocalPlaneResolutionUnit';
  3404. end;
  3405. { TThumbnailResolution }
  3406. procedure TThumbnailResolution.GetTagInfo(var Section: TExifSectionKind;
  3407. var XTag, YTag, UnitTag: TExifTagID; var Schema: TXMPNamespace;
  3408. var XName, YName, UnitName: UnicodeString);
  3409. begin
  3410. Section := esThumbnail;
  3411. end;
  3412. { TISOSpeedRatings }
  3413. procedure TISOSpeedRatings.Assign(Source: TPersistent);
  3414. var
  3415. SourceTag, DestTag: TExifTag;
  3416. begin
  3417. if Source = nil then
  3418. Clear
  3419. else if Source is TISOSpeedRatings then
  3420. begin
  3421. if not TISOSpeedRatings(Source).FindTag(True, SourceTag) then
  3422. Clear
  3423. else
  3424. begin
  3425. if FindTag(False, DestTag) then
  3426. DestTag.UpdateData(tdWord, SourceTag.ElementCount, PWord(SourceTag.Data)^)
  3427. else
  3428. begin
  3429. DestTag := Owner[esDetails].Add(ttISOSpeedRatings, tdWord, SourceTag.ElementCount);
  3430. Move(PWord(SourceTag.Data)^, DestTag.Data^, SourceTag.DataSize);
  3431. end;
  3432. Owner.XMPPacket.UpdateProperty(XMPSchema, XMPName, XMPKind, DestTag.AsString);
  3433. end;
  3434. end
  3435. else
  3436. inherited;
  3437. end;
  3438. procedure TISOSpeedRatings.Clear;
  3439. begin
  3440. Owner[esDetails].Remove(ttISOSpeedRatings);
  3441. Owner.XMPPacket.RemoveProperty(XMPSchema, XMPName);
  3442. end;
  3443. function TISOSpeedRatings.FindTag(VerifyDataType: Boolean; out Tag: TExifTag): Boolean;
  3444. begin
  3445. Result := Owner[esDetails].Find(ttISOSpeedRatings, Tag);
  3446. if Result and VerifyDataType and not (Tag.DataType in [tdWord, tdShortInt]) then
  3447. begin
  3448. Tag := nil;
  3449. Result := False;
  3450. end;
  3451. end;
  3452. function TISOSpeedRatings.GetAsString: string;
  3453. var
  3454. Tag: TExifTag;
  3455. begin
  3456. if FindTag(True, Tag) then
  3457. Result := Tag.AsString
  3458. else
  3459. Result := '';
  3460. end;
  3461. function TISOSpeedRatings.GetCount: Integer;
  3462. var
  3463. Tag: TExifTag;
  3464. begin
  3465. if FindTag(True, Tag) then
  3466. Result := Tag.ElementCount
  3467. else
  3468. Result := 0;
  3469. end;
  3470. function TISOSpeedRatings.GetItem(Index: Integer): Word;
  3471. var
  3472. Tag: TExifTag;
  3473. begin
  3474. if FindTag(True, Tag) and (Index < Tag.ElementCount) and (Index >= 0) then
  3475. Result := PWordArray(Tag.Data)[Index]
  3476. else
  3477. Result := 0;
  3478. end;
  3479. function TISOSpeedRatings.MissingOrInvalid: Boolean;
  3480. var
  3481. Tag: TExifTag;
  3482. begin
  3483. Result := not FindTag(True, Tag);
  3484. end;
  3485. procedure TISOSpeedRatings.SetAsString(const Value: string);
  3486. var
  3487. Tag: TExifTag;
  3488. begin
  3489. if Value = '' then
  3490. begin
  3491. Assign(nil);
  3492. Exit;
  3493. end;
  3494. if not FindTag(False, Tag) then
  3495. Tag := Owner[esDetails].Add(ttISOSpeedRatings, tdWord, 0);
  3496. Tag.AsString := Value;
  3497. Owner.XMPPacket.UpdateProperty(XMPSchema, XMPName, XMPKind, Value);
  3498. end;
  3499. function TISOSpeedRatings.ToString: string;
  3500. begin
  3501. Result := AsString;
  3502. end;
  3503. procedure TISOSpeedRatings.SetCount(const Value: Integer);
  3504. var
  3505. Tag: TExifTag;
  3506. begin
  3507. if Value <= 0 then
  3508. Clear
  3509. else if FindTag(False, Tag) then
  3510. Tag.ElementCount := Value
  3511. else
  3512. Owner[esDetails].Add(ttISOSpeedRatings, tdWord, Value);
  3513. end;
  3514. procedure TISOSpeedRatings.SetItem(Index: Integer; const Value: Word);
  3515. procedure WriteXMP;
  3516. begin
  3517. with Owner.XMPPacket[XMPSchema][XMPName] do
  3518. begin
  3519. Kind := XMPKind;
  3520. Count := Max(Count, Succ(Index));
  3521. SubProperties[Index].WriteValue(Value);
  3522. end;
  3523. end;
  3524. var
  3525. Tag: TExifTag;
  3526. Schema: TXMPSchema;
  3527. Prop: TXMPProperty;
  3528. begin
  3529. if not FindTag(True, Tag) or (Index >= Tag.ElementCount) then
  3530. raise EListError.CreateFmt(SListIndexError, [Index]);
  3531. Owner[esDetails].ForceSetElement(ttISOSpeedRatings, tdWord, Index, Value);
  3532. case Owner.XMPWritePolicy of
  3533. xwRemove: Owner.XMPPacket.RemoveProperty(XMPSchema, XMPName);
  3534. xwAlwaysUpdate: if Owner.XMPPacket.FindSchema(XMPSchema, Schema) and
  3535. Schema.FindProperty(XMPName, Prop) then WriteXMP;
  3536. else WriteXMP;
  3537. end
  3538. end;
  3539. { TCustomGPSFraction }
  3540. constructor TCustomGPSFraction.Create(const AOwner: TCustomExifData;
  3541. AMainTagID, ARefTagID: TExifTagID);
  3542. begin
  3543. inherited Create(AOwner);
  3544. FMainTagID := AMainTagID;
  3545. FRefTagID := ARefTagID;
  3546. end;
  3547. procedure TCustomGPSFraction.Assign(Source: TPersistent);
  3548. var
  3549. SourceAsFrac: TCustomGPSFraction;
  3550. begin
  3551. if Source = nil then
  3552. begin
  3553. SetValue(NullFraction);
  3554. SetRefChar(#0);
  3555. Exit;
  3556. end;
  3557. if Source is TCustomGPSFraction then
  3558. begin
  3559. SourceAsFrac := TCustomGPSFraction(Source);
  3560. if SourceAsFrac.InheritsFrom(ClassType) or (SourceAsFrac.Ref in ValidCharsToAssign) then
  3561. begin
  3562. SetValue(SourceAsFrac.Value);
  3563. SetRefChar(SourceAsFrac.Ref);
  3564. Exit;
  3565. end;
  3566. end;
  3567. inherited;
  3568. end;
  3569. function TCustomGPSFraction.GetDenominator: LongWord;
  3570. begin
  3571. Result := Value.Denominator;
  3572. end;
  3573. function TCustomGPSFraction.GetNumerator: LongWord;
  3574. begin
  3575. Result := Value.Numerator;
  3576. end;
  3577. function TCustomGPSFraction.GetQuotient: Extended;
  3578. begin
  3579. Result := Value.Quotient;
  3580. end;
  3581. function TCustomGPSFraction.GetRefChar: AnsiChar;
  3582. var
  3583. Tag: TExifTag;
  3584. begin
  3585. if Owner[esGPS].Find(FRefTagID, Tag) and (Tag.DataType = tdAscii) and (Tag.ElementCount >= 2) then
  3586. Result := UpCase(PAnsiChar(Tag.Data)^)
  3587. else
  3588. Result := #0;
  3589. end;
  3590. function TCustomGPSFraction.GetValue: TExifFraction;
  3591. begin
  3592. Result := Owner.GetGPSFraction(FMainTagID);
  3593. end;
  3594. function TCustomGPSFraction.MissingOrInvalid: Boolean;
  3595. begin
  3596. Result := Value.MissingOrInvalid;
  3597. end;
  3598. procedure TCustomGPSFraction.SetValue(const Value: TExifFraction);
  3599. begin
  3600. Owner.SetGPSFraction(FMainTagID, Value);
  3601. end;
  3602. procedure TCustomGPSFraction.SetRefChar(const Value: AnsiChar);
  3603. var
  3604. S: string;
  3605. begin
  3606. if Value = #0 then
  3607. S := ''
  3608. else
  3609. S := string(Value);
  3610. Owner[esGPS].SetStringValue(FRefTagID, S);
  3611. Owner.XMPPacket.UpdateProperty(xsExif, GetGPSTagXMPName(FRefTagID), S);
  3612. end;
  3613. function TCustomGPSFraction.ToString: string;
  3614. begin
  3615. if MissingOrInvalid then
  3616. Result := ''
  3617. else
  3618. Result := Value.ToString;
  3619. end;
  3620. { TGPSFraction }
  3621. constructor TGPSFraction.Create;
  3622. begin
  3623. inherited Create(nil);
  3624. end;
  3625. function TGPSFraction.GetRefChar: AnsiChar;
  3626. begin
  3627. Result := FRefChar;
  3628. end;
  3629. function TGPSFraction.GetValue: TExifFraction;
  3630. begin
  3631. Result := FValue;
  3632. end;
  3633. procedure TGPSFraction.SetRefChar(const Value: AnsiChar);
  3634. begin
  3635. FRefChar := Value;
  3636. end;
  3637. procedure TGPSFraction.SetValue(const NewValue: TExifFraction);
  3638. begin
  3639. FValue := NewValue;
  3640. end;
  3641. { TGPSAltitude }
  3642. function TGPSAltitude.CharToEnumValue(Ch: AnsiChar): Integer;
  3643. begin
  3644. case Ch of
  3645. 'A': Result := Ord(alAboveSeaLevel);
  3646. 'B': Result := Ord(alBelowSeaLevel);
  3647. else Result := Ord(alTagMissing);
  3648. end;
  3649. end;
  3650. function TGPSAltitude.EnumValueToChar(OrdValue: Integer): AnsiChar;
  3651. begin
  3652. case TGPSAltitudeRef(OrdValue) of
  3653. alAboveSeaLevel: Result := 'A';
  3654. alBelowSeaLevel: Result := 'B';
  3655. else Result := #0;
  3656. end;
  3657. end;
  3658. function TGPSAltitude.GetEnumName(OrdValue: Integer): string;
  3659. begin
  3660. case TGPSAltitudeRef(OrdValue) of
  3661. alAboveSeaLevel: Result := 'alAboveSeaLevel';
  3662. alBelowSeaLevel: Result := 'alBelowSeaLevel';
  3663. else Result := 'alTagMissing';
  3664. end;
  3665. end;
  3666. function TGPSAltitude.GetEnumTypeInfo: PTypeInfo;
  3667. begin
  3668. Result := nil;
  3669. end;
  3670. function TGPSAltitude.GetMaxEnumValue: Integer;
  3671. begin
  3672. Result := Ord(High(TGPSAltitudeRef));
  3673. end;
  3674. function TGPSAltitude.GetMinEnumValue: Integer;
  3675. begin
  3676. Result := Ord(Low(TGPSAltitudeRef));
  3677. end;
  3678. function TGPSAltitude.GetRef: TGPSAltitudeRef;
  3679. begin
  3680. if Owner[esGPS].TryGetByteValue(RefTagID, 0, Result) then
  3681. if not Owner.EnsureEnumsInRange then
  3682. Exit
  3683. else
  3684. case Ord(Result) of
  3685. Ord(Low(TGPSAltitudeRef))..Ord(High(TGPSAltitudeRef)): Exit;
  3686. end;
  3687. Result := alTagMissing;
  3688. end;
  3689. function TGPSAltitude.GetRefChar: AnsiChar;
  3690. begin
  3691. Result := EnumValueToChar(Ord(GetRef));
  3692. end;
  3693. procedure TGPSAltitude.SetRef(const Value: TGPSAltitudeRef);
  3694. begin
  3695. if Value = alTagMissing then
  3696. begin
  3697. Owner[esGPS].Remove(RefTagID);
  3698. Owner.XMPPacket.RemoveProperty(xsExif, GetGPSTagXMPName(RefTagID));
  3699. Exit;
  3700. end;
  3701. Owner[esGPS].SetByteValue(RefTagID, 0, Ord(Value));
  3702. Owner.XMPPacket.UpdateProperty(xsExif, GetGPSTagXMPName(RefTagID), Ord(Value));
  3703. end;
  3704. procedure TGPSAltitude.SetRefChar(const Value: AnsiChar);
  3705. begin
  3706. SetRef(TGPSAltitudeRef(CharToEnumValue(Value)));
  3707. end;
  3708. function TGPSAltitude.ToString: string;
  3709. begin
  3710. case Ref of
  3711. alAboveSeaLevel: FmtStr(Result, SAboveSeaLevelValue, [Value.ToString]);
  3712. alBelowSeaLevel: FmtStr(Result, SBelowSeaLevelValue, [Value.ToString]);
  3713. else Result := inherited ToString;
  3714. end;
  3715. end;
  3716. { TGPSSpeed }
  3717. function TGPSSpeed.CharToEnumValue(Ch: AnsiChar): Integer;
  3718. begin
  3719. case Ch of
  3720. 'K': Result := Ord(srKilometresPerHour);
  3721. 'M': Result := Ord(srMilesPerHour);
  3722. 'N': Result := Ord(srKnots);
  3723. else Result := Ord(srMissingOrInvalid);
  3724. end;
  3725. end;
  3726. function TGPSSpeed.EnumValueToChar(OrdValue: Integer): AnsiChar;
  3727. begin
  3728. case TGPSSpeedRef(OrdValue) of
  3729. srKilometresPerHour: Result := 'K';
  3730. srMilesPerHour: Result := 'M';
  3731. srKnots: Result := 'N';
  3732. else Result := #0;
  3733. end;
  3734. end;
  3735. function TGPSSpeed.GetEnumTypeInfo: PTypeInfo;
  3736. begin
  3737. Result := TypeInfo(TGPSSpeedRef)
  3738. end;
  3739. function TGPSSpeed.GetRef: TGPSSpeedRef;
  3740. begin
  3741. Result := TGPSSpeedRef(CharToEnumValue(GetRefChar));
  3742. end;
  3743. function TGPSSpeed.ToString: string;
  3744. begin
  3745. case Ref of
  3746. srKilometresPerHour: Result := Value.ToString + ' km/h';
  3747. srMilesPerHour: Result := Value.ToString + ' mph';
  3748. srKnots: Result := Value.ToString + ' kt';
  3749. else Result := inherited ToString;
  3750. end;
  3751. end;
  3752. procedure TGPSSpeed.SetRef(const Value: TGPSSpeedRef);
  3753. begin
  3754. SetRefChar(EnumValueToChar(Ord(Value)));
  3755. end;
  3756. { TCustomGPSFractionWithDirection }
  3757. function TCustomGPSFractionWithDirection.CharToEnumValue(Ch: AnsiChar): Integer;
  3758. begin
  3759. case Ch of
  3760. 'T': Result := Ord(drTrueNorth);
  3761. 'M': Result := Ord(drMagneticNorth);
  3762. else Result := Ord(drMissingOrInvalid);
  3763. end;
  3764. end;
  3765. function TCustomGPSFractionWithDirection.EnumValueToChar(OrdValue: Integer): AnsiChar;
  3766. begin
  3767. case TGPSDirectionRef(OrdValue) of
  3768. drTrueNorth: Result := 'T';
  3769. drMagneticNorth: Result := 'M';
  3770. else Result := #0;
  3771. end;
  3772. end;
  3773. function TCustomGPSFractionWithDirection.GetEnumTypeInfo: PTypeInfo;
  3774. begin
  3775. Result := TypeInfo(TGPSDirectionRef)
  3776. end;
  3777. function TCustomGPSFractionWithDirection.GetRef: TGPSDirectionRef;
  3778. begin
  3779. Result := TGPSDirectionRef(CharToEnumValue(GetRefChar));
  3780. end;
  3781. procedure TCustomGPSFractionWithDirection.SetRef(const Value: TGPSDirectionRef);
  3782. begin
  3783. SetRefChar(EnumValueToChar(Ord(Value)));
  3784. end;
  3785. function TCustomGPSFractionWithDirection.ToString: string;
  3786. begin
  3787. case Ref of
  3788. drTrueNorth: FmtStr(Result, STrueNorthValue, [Value.ToString]);
  3789. drMagneticNorth: FmtStr(Result, SMagneticNorthValue, [Value.ToString]);
  3790. else Result := inherited ToString;
  3791. end;
  3792. end;
  3793. { TGPSDestDistance }
  3794. function TGPSDestDistance.CharToEnumValue(Ch: AnsiChar): Integer;
  3795. begin
  3796. case Ch of
  3797. 'K': Result := Ord(dsKilometres);
  3798. 'M': Result := Ord(dsMiles);
  3799. 'N': Result := Ord(dsKnots);
  3800. else Result := Ord(dsMissingOrInvalid);
  3801. end;
  3802. end;
  3803. function TGPSDestDistance.EnumValueToChar(OrdValue: Integer): AnsiChar;
  3804. begin
  3805. case TGPSDistanceRef(OrdValue) of
  3806. dsKilometres: Result := 'K';
  3807. dsMiles: Result := 'M';
  3808. dsKnots: Result := 'N';
  3809. else Result := #0;
  3810. end;
  3811. end;
  3812. function TGPSDestDistance.GetEnumTypeInfo: PTypeInfo;
  3813. begin
  3814. Result := TypeInfo(TGPSDistanceRef)
  3815. end;
  3816. function TGPSDestDistance.GetRef: TGPSDistanceRef;
  3817. begin
  3818. Result := TGPSDistanceRef(CharToEnumValue(GetRefChar));
  3819. end;
  3820. procedure TGPSDestDistance.SetRef(const Value: TGPSDistanceRef);
  3821. begin
  3822. SetRefChar(EnumValueToChar(Ord(Value)));
  3823. end;
  3824. function TGPSDestDistance.ToString: string;
  3825. begin
  3826. case Ref of
  3827. dsKilometres: Result := Value.ToString + ' km';
  3828. dsMiles: Result := Value.ToString + ' mi';
  3829. dsKnots: Result := Value.ToString + ' kt';
  3830. else Result := '';
  3831. end;
  3832. end;
  3833. { TCustomGPSCoordinate }
  3834. constructor TCustomGPSCoordinate.Create(const AOwner: TCustomExifData; const ATagID: TExifTagID);
  3835. begin
  3836. inherited Create(AOwner);
  3837. FRefTagID := Pred(ATagID);
  3838. FTagID := ATagID;
  3839. FXMPName := GetGPSTagXMPName(ATagID)
  3840. end;
  3841. function TCustomGPSCoordinate.TryGetTag(out Tag: TExifTag): Boolean;
  3842. begin
  3843. Result := (Owner <> nil) and Owner[esGPS].Find(FTagID, Tag);
  3844. end;
  3845. procedure TCustomGPSCoordinate.AssignCoordinate(Source: TCustomGPSCoordinate);
  3846. var
  3847. SourceTag, DestTag: TExifTag;
  3848. begin
  3849. if Source = nil then
  3850. Assign(nil)
  3851. else if (Owner <> nil) and Source.InheritsFrom(Self.ClassType) then
  3852. begin
  3853. if not Source.TryGetTag(SourceTag) then
  3854. Assign(nil)
  3855. else
  3856. begin
  3857. Owner.BeginUpdate;
  3858. try
  3859. if not Owner[esGPS].Find(FTagID, DestTag) then
  3860. DestTag := Owner[esGPS].Add(FTagID, SourceTag.DataType, SourceTag.ElementCount);
  3861. DestTag.Assign(SourceTag);
  3862. Direction := Source.Direction;
  3863. finally
  3864. Owner.EndUpdate;
  3865. end;
  3866. end;
  3867. end
  3868. else if Source.Direction in ValidCharsToAssign then
  3869. Assign(Source.Degrees, Source.Minutes, Source.Seconds, Source.Direction)
  3870. else
  3871. Source.AssignTo(Self);
  3872. end;
  3873. procedure TCustomGPSCoordinate.Assign(Source: TPersistent);
  3874. begin
  3875. if Source is TCustomGPSCoordinate then
  3876. AssignCoordinate(TCustomGPSCoordinate(Source))
  3877. else if Source = nil then
  3878. if Owner = nil then
  3879. Assign(NullFraction, NullFraction, NullFraction, Direction)
  3880. else
  3881. begin
  3882. Owner[esGPS].Remove([FTagID, Pred(FTagID)]);
  3883. Owner.XMPPacket.RemoveProperty(xsExif, XMPName);
  3884. end
  3885. else
  3886. inherited;
  3887. end;
  3888. procedure TCustomGPSCoordinate.Assign(const ADegrees, AMinutes, ASeconds: TExifFraction;
  3889. ADirectionChar: AnsiChar);
  3890. var
  3891. NewElemCount: Integer;
  3892. Tag: TExifTag;
  3893. begin
  3894. if ASeconds.MissingOrInvalid then NewElemCount := 2 else NewElemCount := 3;
  3895. if Owner[esGPS].Find(FTagID, Tag) then
  3896. Tag.UpdateData(tdLongWordFraction, NewElemCount, PByte(nil)^)
  3897. else
  3898. Tag := Owner[esGPS].Add(FTagID, tdLongWordFraction, NewElemCount);
  3899. PExifFractionArray(Tag.Data)[0] := ADegrees;
  3900. PExifFractionArray(Tag.Data)[1] := AMinutes;
  3901. if NewElemCount > 2 then PExifFractionArray(Tag.Data)[2] := ASeconds;
  3902. Tag.Changed;
  3903. Direction := ADirectionChar;
  3904. end;
  3905. function TCustomGPSCoordinate.MissingOrInvalid: Boolean;
  3906. var
  3907. Mins, Degs: TExifFraction; //needed for D2006 compatibility - the D2006 compiler is buggy as hell with record methods
  3908. begin
  3909. Mins := Minutes; Degs := Degrees;
  3910. Result := Mins.MissingOrInvalid or Degs.MissingOrInvalid or (Direction = #0);
  3911. end;
  3912. function TCustomGPSCoordinate.ToString: string;
  3913. var
  3914. Ch: AnsiChar;
  3915. Direction: string;
  3916. Degrees, Minutes, Seconds: TExifFraction;
  3917. begin
  3918. Degrees := Self.Degrees;
  3919. Minutes := Self.Minutes;
  3920. Seconds := Self.Seconds;
  3921. if Degrees.MissingOrInvalid or Minutes.MissingOrInvalid then
  3922. begin
  3923. Result := '';
  3924. Exit;
  3925. end;
  3926. //Direction := FOwner[esGPS].GetStringValue(RefTagID);
  3927. Ch := GetDirectionChar;
  3928. if Ch = #0 then
  3929. Direction := ''
  3930. else
  3931. Direction := string(Ch);
  3932. if Seconds.MissingOrInvalid then
  3933. FmtStr(Result, '%s,%g%s', [Degrees.ToString, Minutes.Quotient, Direction])
  3934. else //if we do *exactly* what the XMP spec says, the value won't be round-trippable...
  3935. FmtStr(Result, '%s,%s,%s%s', [Degrees.ToString, Minutes.ToString, Seconds.ToString, Direction]);
  3936. end;
  3937. function TCustomGPSCoordinate.GetDirectionChar: AnsiChar;
  3938. var
  3939. Tag: TExifTag;
  3940. begin
  3941. if Owner[esGPS].Find(RefTagID, Tag) and (Tag.DataType = tdAscii) and (Tag.ElementCount >= 2) then
  3942. Result := UpCase(PAnsiChar(Tag.Data)^)
  3943. else
  3944. Result := #0;
  3945. end;
  3946. procedure TCustomGPSCoordinate.SetDirectionChar(NewChar: AnsiChar);
  3947. var
  3948. ValueAsString, XMPValue: string;
  3949. I: Integer;
  3950. begin
  3951. if NewChar = #0 then
  3952. begin
  3953. Owner[esGPS].Remove(RefTagID);
  3954. Owner.XMPPacket.RemoveProperty(xsExif, XMPName);
  3955. Exit;
  3956. end;
  3957. ValueAsString := string(UpCase(NewChar));
  3958. XMPValue := AsString;
  3959. Owner[esGPS].SetStringValue(RefTagID, ValueAsString);
  3960. for I := Length(XMPValue) downto 1 do
  3961. if not IsCharIn(XMPValue[I], ['A'..'Z', 'a'..'z']) then
  3962. begin
  3963. XMPValue := Copy(XMPValue, 1, I) + ValueAsString;
  3964. Owner.XMPPacket.UpdateProperty(xsExif, XMPName, XMPValue);
  3965. Break;
  3966. end;
  3967. end;
  3968. function TCustomGPSCoordinate.GetValue(Index: Integer): TExifFraction;
  3969. begin
  3970. Result := Owner[esGPS].GetFractionValue(TagID, Index);
  3971. end;
  3972. { TGPSCoordinate }
  3973. constructor TGPSCoordinate.Create;
  3974. begin
  3975. inherited Create(nil);
  3976. end;
  3977. procedure TGPSCoordinate.Assign(const ADegrees, AMinutes, ASeconds: TExifFraction;
  3978. ADirectionChar: AnsiChar);
  3979. begin
  3980. Degrees := ADegrees;
  3981. Minutes := AMinutes;
  3982. Seconds := ASeconds;
  3983. Direction := ADirectionChar;
  3984. end;
  3985. procedure TGPSCoordinate.AssignTo(Dest: TPersistent);
  3986. begin
  3987. if (Dest is TCustomGPSCoordinate) and (Direction in TCustomGPSCoordinate(Dest).ValidCharsToAssign) then
  3988. TCustomGPSCoordinate(Dest).Assign(Degrees, Minutes, Seconds, Direction)
  3989. else
  3990. inherited;
  3991. end;
  3992. function TGPSCoordinate.GetDirectionChar: AnsiChar;
  3993. begin
  3994. Result := FDirectionChar;
  3995. end;
  3996. function TGPSCoordinate.GetValue(Index: Integer): TExifFraction;
  3997. begin
  3998. Result := FValues[Index]
  3999. end;
  4000. procedure TGPSCoordinate.SetDirectionChar(NewChar: AnsiChar);
  4001. begin
  4002. FDirectionChar := NewChar;
  4003. end;
  4004. { TGPSLatitude }
  4005. procedure TGPSLatitude.Assign(const ADegrees, AMinutes, ASeconds: TExifFraction;
  4006. ADirection: TGPSLatitudeRef);
  4007. const
  4008. DirectionChars: array[TGPSLatitudeRef] of AnsiChar = (#0, 'N', 'S');
  4009. begin
  4010. Assign(ADegrees, AMinutes, ASeconds, DirectionChars[ADirection]);
  4011. end;
  4012. procedure TGPSLatitude.Assign(ADegrees, AMinutes: LongWord;
  4013. const ASeconds: TExifFraction; ADirection: TGPSLatitudeRef);
  4014. begin
  4015. Assign(TExifFraction.Create(ADegrees), TExifFraction.Create(AMinutes),
  4016. ASeconds, ADirection);
  4017. end;
  4018. procedure TGPSLatitude.Assign(ADegrees, AMinutes: LongWord; const ASeconds: Currency;
  4019. ADirection: TGPSLatitudeRef);
  4020. begin
  4021. Assign(TExifFraction.Create(ADegrees), TExifFraction.Create(AMinutes),
  4022. TExifFraction.Create(ASeconds), ADirection);
  4023. end;
  4024. procedure TGPSLatitude.Assign(ADegrees, AMinutes, ASeconds: LongWord;
  4025. ADirection: TGPSLatitudeRef);
  4026. begin
  4027. Assign(TExifFraction.Create(ADegrees), TExifFraction.Create(AMinutes),
  4028. TExifFraction.Create(ASeconds), ADirection);
  4029. end;
  4030. function TGPSLatitude.CharToEnumValue(Ch: AnsiChar): Integer;
  4031. begin
  4032. case Ch of
  4033. 'N': Result := Ord(ltNorth);
  4034. 'S': Result := Ord(ltSouth);
  4035. else Result := Ord(ltMissingOrInvalid);
  4036. end;
  4037. end;
  4038. function TGPSLatitude.EnumValueToChar(OrdValue: Integer): AnsiChar;
  4039. begin
  4040. case TGPSLatitudeRef(OrdValue) of
  4041. ltNorth: Result := 'N';
  4042. ltSouth: Result := 'S';
  4043. else Result := #0;
  4044. end;
  4045. end;
  4046. function TGPSLatitude.GetDirection: TGPSLatitudeRef;
  4047. begin
  4048. Result := TGPSLatitudeRef(CharToEnumValue(GetDirectionChar));
  4049. end;
  4050. function TGPSLatitude.GetEnumTypeInfo: PTypeInfo;
  4051. begin
  4052. Result := TypeInfo(TGPSLatitudeRef)
  4053. end;
  4054. { TGPSLongitude }
  4055. procedure TGPSLongitude.Assign(const ADegrees, AMinutes, ASeconds: TExifFraction;
  4056. ADirection: TGPSLongitudeRef);
  4057. const
  4058. DirectionChars: array[TGPSLongitudeRef] of AnsiChar = (#0, 'W', 'E');
  4059. begin
  4060. Assign(ADegrees, AMinutes, ASeconds, DirectionChars[ADirection]);
  4061. end;
  4062. procedure TGPSLongitude.Assign(ADegrees, AMinutes: LongWord;
  4063. const ASeconds: TExifFraction; ADirection: TGPSLongitudeRef);
  4064. begin
  4065. Assign(TExifFraction.Create(ADegrees), TExifFraction.Create(AMinutes),
  4066. ASeconds, ADirection);
  4067. end;
  4068. procedure TGPSLongitude.Assign(ADegrees, AMinutes: LongWord; const ASeconds: Currency;
  4069. ADirection: TGPSLongitudeRef);
  4070. begin
  4071. Assign(TExifFraction.Create(ADegrees), TExifFraction.Create(AMinutes),
  4072. TExifFraction.Create(ASeconds), ADirection);
  4073. end;
  4074. procedure TGPSLongitude.Assign(ADegrees, AMinutes, ASeconds: LongWord;
  4075. ADirection: TGPSLongitudeRef);
  4076. begin
  4077. Assign(TExifFraction.Create(ADegrees), TExifFraction.Create(AMinutes),
  4078. TExifFraction.Create(ASeconds), ADirection);
  4079. end;
  4080. function TGPSLongitude.CharToEnumValue(Ch: AnsiChar): Integer;
  4081. begin
  4082. case Ch of
  4083. 'W': Result := Ord(lnWest);
  4084. 'E': Result := Ord(lnEast);
  4085. else Result := Ord(lnMissingOrInvalid);
  4086. end;
  4087. end;
  4088. function TGPSLongitude.EnumValueToChar(OrdValue: Integer): AnsiChar;
  4089. begin
  4090. case TGPSLongitudeRef(OrdValue) of
  4091. lnWest: Result := 'W';
  4092. lnEast: Result := 'E';
  4093. else Result := #0;
  4094. end;
  4095. end;
  4096. function TGPSLongitude.GetDirection: TGPSLongitudeRef;
  4097. begin
  4098. Result := TGPSLongitudeRef(CharToEnumValue(GetDirectionChar));
  4099. end;
  4100. function TGPSLongitude.GetEnumTypeInfo: PTypeInfo;
  4101. begin
  4102. Result := TypeInfo(TGPSLongitudeRef)
  4103. end;
  4104. { TCustomExifData.TEnumerator }
  4105. constructor TCustomExifData.TEnumerator.Create(AClient: TCustomExifData);
  4106. begin
  4107. FClient := AClient;
  4108. FDoneFirst := False;
  4109. FSection := Low(TExifSectionKind);
  4110. end;
  4111. function TCustomExifData.TEnumerator.GetCurrent: TExifSection;
  4112. begin
  4113. Result := FClient[FSection];
  4114. end;
  4115. function TCustomExifData.TEnumerator.MoveNext: Boolean;
  4116. begin
  4117. Result := False;
  4118. if not FDoneFirst then
  4119. FDoneFirst := True
  4120. else
  4121. begin
  4122. if FSection = High(TExifSectionKind) then Exit;
  4123. Inc(FSection);
  4124. end;
  4125. Result := True;
  4126. end;
  4127. { TCustomExifData }
  4128. type
  4129. TContainedXMPPacket = class(TXMPPacket);
  4130. constructor TCustomExifData.Create(AOwner: TComponent = nil);
  4131. var
  4132. Kind: TExifSectionKind;
  4133. begin
  4134. inherited;
  4135. FEmbeddedIPTC := TIPTCData.CreateAsSubComponent(Self);
  4136. FEnforceASCII := True;
  4137. FEnsureEnumsInRange := True;
  4138. FExifVersion := TExifVersion.Create(Self);
  4139. FFlashPixVersion := TFlashPixVersion.Create(Self);
  4140. FGPSVersion := TGPSVersion.Create(Self);
  4141. FGPSAltitude := TGPSAltitude.Create(Self, ttGPSAltitude, ttGPSAltitudeRef);
  4142. FGPSLatitude := TGPSLatitude.Create(Self, ttGPSLatitude);
  4143. FGPSLongitude := TGPSLongitude.Create(Self, ttGPSLongitude);
  4144. FGPSDestBearing := TGPSDestBearing.Create(Self, ttGPSDestBearing, ttGPSDestBearingRef);
  4145. FGPSDestDistance := TGPSDestDistance.Create(Self, ttGPSDestDistance, ttGPSDestDistanceRef);
  4146. FGPSDestLatitude := TGPSLatitude.Create(Self, ttGPSDestLatitude);
  4147. FGPSDestLongitude := TGPSLongitude.Create(Self, ttGPSDestLongitude);
  4148. FGPSImgDirection := TGPSImgDirection.Create(Self, ttGPSImgDirection, ttGPSImgDirectionRef);
  4149. FGPSSpeed := TGPSSpeed.Create(Self, ttGPSSpeed, ttGPSSpeedRef);
  4150. FGPSTrack := TGPSTrack.Create(Self, ttGPSTrack, ttGPSTrackRef);
  4151. for Kind := Low(TExifSectionKind) to High(TExifSectionKind) do
  4152. FSections[Kind] := SectionClass.Create(Self, Kind);
  4153. FFlash := TExifFlashInfo.Create(Self);
  4154. FFocalPlaneResolution := TFocalPlaneResolution.Create(Self);
  4155. FInteropVersion := TInteropVersion.Create(Self);
  4156. FISOSpeedRatings := TISOSpeedRatings.Create(Self);
  4157. FResolution := TImageResolution.Create(Self);
  4158. FThumbnailResolution := TThumbnailResolution.Create(Self);
  4159. FXMPPacket := TContainedXMPPacket.CreateAsSubComponent(Self);
  4160. ResetMakerNoteType;
  4161. SetXMPWritePolicy(xwUpdateIfExists);
  4162. end;
  4163. destructor TCustomExifData.Destroy;
  4164. var
  4165. Section: TExifSectionKind;
  4166. begin
  4167. FUpdateCount := 1000;
  4168. FreeAndNil(FMakerNoteValue);
  4169. FThumbnailResolution.Free;
  4170. FResolution.Free;
  4171. FISOSpeedRatings.Free;
  4172. FInteropVersion.Free;
  4173. FGPSTrack.Free;
  4174. FGPSSpeed.Free;
  4175. FGPSImgDirection.Free;
  4176. FGPSDestLongitude.Free;
  4177. FGPSDestLatitude.Free;
  4178. FGPSDestDistance.Free;
  4179. FGPSDestBearing.Free;
  4180. FGPSLongitude.Free;
  4181. FGPSLatitude.Free;
  4182. FGPSAltitude.Free;
  4183. FGPSVersion.Free;
  4184. FFocalPlaneResolution.Free;
  4185. FFlash.Free;
  4186. FFlashPixVersion.Free;
  4187. FExifVersion.Free;
  4188. for Section := Low(TExifSectionKind) to High(TExifSectionKind) do
  4189. FSections[Section].Free;
  4190. inherited;
  4191. FThumbnailOrNil.Free;
  4192. end;
  4193. class function TCustomExifData.SectionClass: TExifSectionClass;
  4194. begin
  4195. Result := TExifSection;
  4196. end;
  4197. class procedure TCustomExifData.InitializeClass(const MakerNoteClasses: array of TExifMakerNoteClass);
  4198. var
  4199. I: Integer;
  4200. begin
  4201. FMakerNoteClasses := TClassList.Create;
  4202. for I := Low(MakerNoteClasses) to High(MakerNoteClasses) do
  4203. FMakerNoteClasses.Add(MakerNoteClasses[I]);
  4204. end;
  4205. class procedure TCustomExifData.FinalizeClass;
  4206. begin
  4207. FMakerNoteClasses.Free;
  4208. end;
  4209. class procedure TCustomExifData.RegisterMakerNoteType(AClass: TExifMakerNoteClass;
  4210. Priority: TMakerNoteTypePriority);
  4211. begin
  4212. if AClass = nil then Exit;
  4213. FMakerNoteClasses.Remove(AClass);
  4214. case Priority of
  4215. mtTestForLast: FMakerNoteClasses.Insert(0, AClass);
  4216. mtTestForFirst: FMakerNoteClasses.Add(AClass);
  4217. end;
  4218. end;
  4219. class procedure TCustomExifData.RegisterMakerNoteTypes(
  4220. const AClasses: array of TExifMakerNoteClass; Priority: TMakerNoteTypePriority);
  4221. var
  4222. LClass: TExifMakerNoteClass;
  4223. begin
  4224. for LClass in AClasses do
  4225. RegisterMakerNoteType(LClass, Priority);
  4226. end;
  4227. class procedure TCustomExifData.UnregisterMakerNoteType(AClass: TExifMakerNoteClass);
  4228. begin
  4229. FMakerNoteClasses.Remove(AClass)
  4230. end;
  4231. procedure TCustomExifData.BeginUpdate;
  4232. begin
  4233. Inc(FUpdateCount);
  4234. end;
  4235. procedure TCustomExifData.EndUpdate;
  4236. begin
  4237. Dec(FUpdateCount);
  4238. if (FUpdateCount = 0) and FChangedWhileUpdating then
  4239. begin
  4240. FChangedWhileUpdating := False;
  4241. Changed(nil);
  4242. end;
  4243. end;
  4244. function TCustomExifData.Updating: Boolean;
  4245. begin
  4246. Result := (FUpdateCount > 0);
  4247. end;
  4248. procedure TCustomExifData.Changed(Section: TExifSection);
  4249. begin
  4250. if FUpdateCount > 0 then
  4251. FChangedWhileUpdating := True
  4252. else
  4253. begin
  4254. FModified := True;
  4255. if Assigned(FOnChange) then FOnChange(Self);
  4256. end;
  4257. end;
  4258. procedure TCustomExifData.Clear(XMPPacketToo: Boolean = True);
  4259. var
  4260. Section: TExifSection;
  4261. begin
  4262. BeginUpdate;
  4263. try
  4264. FreeAndNil(FThumbnailOrNil);
  4265. ResetMakerNoteType;
  4266. for Section in FSections do
  4267. Section.Clear;
  4268. if XMPPacketToo then
  4269. FXMPPacket.Clear;
  4270. finally
  4271. EndUpdate;
  4272. end;
  4273. end;
  4274. function TCustomExifData.GetEmpty: Boolean;
  4275. var
  4276. Section: TExifSection;
  4277. begin
  4278. Result := False;
  4279. for Section in FSections do
  4280. if Section.Count > 0 then Exit;
  4281. if HasThumbnail then Exit;
  4282. Result := True;
  4283. end;
  4284. function TCustomExifData.GetEnumerator: TEnumerator;
  4285. begin
  4286. Result := TEnumerator.Create(Self);
  4287. end;
  4288. procedure TCustomExifData.GetKeywords(Dest: TStrings);
  4289. var
  4290. I, StartPos: Integer;
  4291. S: UnicodeString;
  4292. begin
  4293. Dest.BeginUpdate;
  4294. try
  4295. Dest.Clear;
  4296. S := Keywords;
  4297. StartPos := 1;
  4298. for I := 1 to Length(S) do
  4299. if S[I] = ';' then
  4300. begin
  4301. Dest.Add(Copy(S, StartPos, I - StartPos));
  4302. StartPos := I + 1;
  4303. end;
  4304. Dest.Add(Copy(S, StartPos, MaxInt));
  4305. finally
  4306. Dest.EndUpdate;
  4307. end;
  4308. end;
  4309. procedure TCustomExifData.SetKeywords(const NewWords: array of UnicodeString);
  4310. var
  4311. I: Integer;
  4312. MemStream: TMemoryStream;
  4313. S: UnicodeString;
  4314. begin
  4315. MemStream := TMemoryStream.Create;
  4316. try
  4317. for I := 0 to High(NewWords) do
  4318. begin
  4319. S := NewWords[I];
  4320. MemStream.WriteBuffer(PWideChar(S)^, Length(S));
  4321. if I < High(NewWords) then MemStream.WriteByte(';');
  4322. end;
  4323. SetString(S, PWideChar(MemStream.Memory), MemStream.Size);
  4324. Keywords := S;
  4325. finally
  4326. MemStream.Free;
  4327. end;
  4328. end;
  4329. procedure TCustomExifData.SetKeywords(NewWords: TStrings);
  4330. var
  4331. DynArray: array of UnicodeString;
  4332. I: Integer;
  4333. begin
  4334. SetLength(DynArray, NewWords.Count);
  4335. for I := 0 to High(DynArray) do
  4336. DynArray[I] := NewWords[I];
  4337. SetKeywords(DynArray);
  4338. end;
  4339. function TCustomExifData.GetMakerNote: TExifMakerNote;
  4340. begin
  4341. if FMakerNoteValue = nil then
  4342. begin
  4343. BeginUpdate;
  4344. try
  4345. FMakerNoteValue := FMakerNoteType.Create(FSections[esMakerNote]);
  4346. if (FMakerNoteType = THeaderlessMakerNote) and (FMakerNoteValue.Tags.Count = 0) then
  4347. begin
  4348. FMakerNoteType := TUnrecognizedMakerNote;
  4349. FreeAndNil(FMakerNoteValue);
  4350. FMakerNoteValue := TUnrecognizedMakerNote.Create(FSections[esMakerNote]);
  4351. end;
  4352. finally
  4353. EndUpdate;
  4354. end;
  4355. end;
  4356. Result := FMakerNoteValue;
  4357. end;
  4358. function TCustomExifData.GetSection(Section: TExifSectionKind): TExifSection;
  4359. begin
  4360. Result := FSections[Section];
  4361. if (Section = esMakerNote) and (FMakerNoteType <> TUnrecognizedMakerNote) then
  4362. GetMakerNote; //MakerNote tags are lazy-loaded
  4363. end;
  4364. function TCustomExifData.GetThumbnail: TJPEGImage;
  4365. begin
  4366. if FThumbnailOrNil = nil then
  4367. begin
  4368. FThumbnailOrNil := TJPEGImage.Create;
  4369. FThumbnailOrNil.OnChange := ThumbnailChanged;
  4370. end;
  4371. Result := FThumbnailOrNil;
  4372. end;
  4373. function TCustomExifData.HasMakerNote: Boolean;
  4374. begin
  4375. Result := FSections[esDetails].TagExists(ttMakerNote, [tdUndefined], 5)
  4376. end;
  4377. function TCustomExifData.HasThumbnail: Boolean;
  4378. begin
  4379. Result := not IsGraphicEmpty(FThumbnailOrNil);
  4380. end;
  4381. function TCustomExifData.LoadFromGraphic(Stream: TStream): Boolean;
  4382. var
  4383. Segment: IFoundJPEGSegment;
  4384. PSDInfo: TPSDInfo;
  4385. ResBlock: IAdobeResBlock;
  4386. begin
  4387. FMetadataInSource := [];
  4388. FXMPSegmentPosition := 0;
  4389. FXMPPacketSizeInSource := 0;
  4390. Result := False;
  4391. BeginUpdate;
  4392. try
  4393. Clear;
  4394. if HasJPEGHeader(Stream) then
  4395. begin
  4396. Result := True;
  4397. for Segment in JPEGHeader(Stream, [jmApp1]) do
  4398. if not (mkExif in MetadataInSource) and Segment.IsExifBlock then
  4399. begin
  4400. Include(FMetadataInSource, mkExif);
  4401. AddFromStream(Segment.Data);
  4402. Inc(FOffsetBase, Segment.OffsetOfData);
  4403. end
  4404. else if not (mkXMP in MetadataInSource) and Segment.IsXMPBlock then
  4405. begin
  4406. Include(FMetadataInSource, mkXMP);
  4407. FXMPSegmentPosition := Segment.Offset;
  4408. FXMPPacketSizeInSource := Segment.Data.Size;
  4409. XMPPacket.DataToLazyLoad := Segment;
  4410. end;
  4411. end
  4412. else if HasPSDHeader(Stream) then
  4413. begin
  4414. Result := True;
  4415. for ResBlock in ParsePSDHeader(Stream, PSDInfo) do
  4416. if not (mkExif in MetadataInSource) and ResBlock.IsExifBlock then
  4417. begin
  4418. Include(FMetadataInSource, mkExif);
  4419. AddFromStream(ResBlock.Data);
  4420. end
  4421. else if not (mkXMP in MetadataInSource) and ResBlock.IsXMPBlock then
  4422. begin
  4423. Include(FMetadataInSource, mkXMP);
  4424. FXMPPacketSizeInSource := ResBlock.Data.Size;
  4425. XMPPacket.DataToLazyLoad := ResBlock;
  4426. end;
  4427. end
  4428. else if HasTiffHeader(Stream) then
  4429. begin
  4430. Result := True;
  4431. AddFromStream(Stream, True);
  4432. if not Empty then Include(FMetadataInSource, mkExif);
  4433. if not XMPPacket.Empty then Include(FMetadataInSource, mkXMP);
  4434. end;
  4435. finally
  4436. FChangedWhileUpdating := False;
  4437. EndUpdate;
  4438. Modified := False;
  4439. end;
  4440. end;
  4441. procedure TCustomExifData.AddFromStream(Stream: TStream; TiffImageSource: Boolean);
  4442. var
  4443. Parser: ITiffParser;
  4444. procedure LoadSubDir(Source: TExifSectionKind; OffsetID: TExifTagID; Dest: TExifSectionKind);
  4445. var
  4446. SubDir: IFoundTiffDirectory;
  4447. Tag: TExifTag;
  4448. begin
  4449. if FSections[Source].Find(OffsetID, Tag) and Parser.ParseSubDirectory(Tag, SubDir) then
  4450. FSections[Dest].Load(SubDir, TiffImageSource);
  4451. end;
  4452. var
  4453. Directory: IFoundTiffDirectory;
  4454. ExifTag: TExifTag;
  4455. I: Integer;
  4456. PossibleType: TExifMakerNoteClass;
  4457. TiffTag: ITiffTag;
  4458. begin
  4459. if Stream.TryReadHeader(TJPEGSegment.ExifHeader, SizeOf(TJPEGSegment.ExifHeader)) then
  4460. TiffImageSource := False;
  4461. BeginUpdate;
  4462. try
  4463. Parser := ParseTiff(Stream);
  4464. FOffsetBase := Parser.BasePosition;
  4465. FEndianness := Parser.Endianness;
  4466. for Directory in Parser do
  4467. case Directory.Index of
  4468. 0:
  4469. begin
  4470. FSections[esGeneral].Load(Directory, TiffImageSource);
  4471. if Directory.FindTag(ttIPTC, TiffTag) and TiffTag.IsIPTCBlock then
  4472. EmbeddedIPTC.DataToLazyLoad := TiffTag;
  4473. if (TiffImageSource or XMPPacket.Empty) and Directory.FindTag(ttXMP,
  4474. TiffTag) and TiffTag.IsXMPBlock then
  4475. XMPPacket.DataToLazyLoad := TiffTag;
  4476. end;
  4477. 1:
  4478. begin
  4479. if not TiffImageSource or Directory.IsExifThumbailDirectory then
  4480. begin
  4481. FSections[esThumbnail].Load(Directory, TiffImageSource);
  4482. GetThumbnail.OnChange := nil;
  4483. if Directory.TryLoadExifThumbnail(FThumbnailOrNil) then
  4484. FThumbnailOrNil.OnChange := ThumbnailChanged
  4485. else
  4486. SetThumbnail(nil);
  4487. end;
  4488. Break;
  4489. end;
  4490. end;
  4491. LoadSubDir(esGeneral, ttExifOffset, esDetails);
  4492. LoadSubDir(esGeneral, ttGPSOffset, esGPS);
  4493. LoadSubDir(esDetails, ttInteropOffset, esInterop);
  4494. if FSections[esDetails].Find(ttMakerNote, ExifTag) then
  4495. begin
  4496. FMakerNoteType := THeaderlessMakerNote;
  4497. for I := FMakerNoteClasses.Count - 1 downto 0 do
  4498. begin
  4499. PossibleType := TExifMakerNoteClass(FMakerNoteClasses[I]);
  4500. if PossibleType.FormatIsOK(ExifTag) then
  4501. begin
  4502. FMakerNoteType := PossibleType;
  4503. Break;
  4504. end;
  4505. end;
  4506. end;
  4507. finally
  4508. FChangedWhileUpdating := False;
  4509. EndUpdate;
  4510. Modified := False;
  4511. end;
  4512. end;
  4513. procedure TCustomExifData.Rewrite;
  4514. begin
  4515. BeginUpdate;
  4516. try
  4517. CameraMake := CameraMake;
  4518. CameraModel := CameraModel;
  4519. Copyright := Copyright;
  4520. DateTime := DateTime;
  4521. ImageDescription := ImageDescription;
  4522. Orientation := Orientation;
  4523. Resolution := Resolution;
  4524. Software := Software;
  4525. Author := Author;
  4526. Comments := Comments;
  4527. Keywords := Keywords;
  4528. Subject := Subject;
  4529. Title := Title;
  4530. UserRating := UserRating;
  4531. ApertureValue := ApertureValue;
  4532. BrightnessValue := BrightnessValue;
  4533. ColorSpace := ColorSpace;
  4534. Contrast := Contrast;
  4535. CompressedBitsPerPixel := CompressedBitsPerPixel;
  4536. DateTimeOriginal := DateTimeOriginal;
  4537. DateTimeDigitized := DateTimeDigitized;
  4538. DigitalZoomRatio := DigitalZoomRatio;
  4539. ExifVersion := ExifVersion;
  4540. ExifImageWidth := ExifImageWidth;
  4541. ExifImageHeight := ExifImageHeight;
  4542. ExposureBiasValue := ExposureBiasValue;
  4543. ExposureIndex := ExposureIndex;
  4544. ExposureMode := ExposureMode;
  4545. ExposureProgram := ExposureProgram;
  4546. ExposureTime := ExposureTime;
  4547. FileSource := FileSource;
  4548. Flash := Flash;
  4549. FlashPixVersion := FlashPixVersion;
  4550. FNumber := FNumber;
  4551. FocalLength := FocalLength;
  4552. FocalLengthIn35mmFilm := FocalLengthIn35mmFilm;
  4553. FocalPlaneResolution := FocalPlaneResolution;
  4554. GainControl := GainControl;
  4555. ImageUniqueID := ImageUniqueID;
  4556. ISOSpeedRatings := ISOSpeedRatings;
  4557. LightSource := LightSource;
  4558. MaxApertureValue := MaxApertureValue;
  4559. MeteringMode := MeteringMode;
  4560. RelatedSoundFile := RelatedSoundFile;
  4561. Rendering := Rendering;
  4562. Saturation := Saturation;
  4563. SceneCaptureType := SceneCaptureType;
  4564. SceneType := SceneType;
  4565. SensingMethod := SensingMethod;
  4566. Sharpness := Sharpness;
  4567. ShutterSpeedValue := ShutterSpeedValue;
  4568. SpectralSensitivity := SpectralSensitivity;
  4569. SubjectDistance := SubjectDistance;
  4570. SubjectDistanceRange := SubjectDistanceRange;
  4571. SubjectLocation := SubjectLocation;
  4572. GPSVersion := GPSVersion;
  4573. GPSLatitude := GPSLatitude;
  4574. GPSLongitude := GPSLongitude;
  4575. GPSAltitude := GPSAltitude;
  4576. GPSSatellites := GPSSatellites;
  4577. GPSStatus := GPSStatus;
  4578. GPSMeasureMode := GPSMeasureMode;
  4579. GPSDOP := GPSDOP;
  4580. GPSSpeed := GPSSpeed;
  4581. GPSTrack := GPSTrack;
  4582. GPSImgDirection := GPSImgDirection;
  4583. GPSMapDatum := GPSMapDatum;
  4584. GPSDestLatitude := GPSDestLatitude;
  4585. GPSDestLongitude := GPSDestLongitude;
  4586. GPSDestBearing := GPSDestBearing;
  4587. GPSDestDistance := GPSDestDistance;
  4588. GPSDifferential := GPSDifferential;
  4589. GPSDateTimeUTC := GPSDateTimeUTC;
  4590. ThumbnailOrientation := ThumbnailOrientation;
  4591. ThumbnailResolution := ThumbnailResolution;
  4592. finally
  4593. EndUpdate;
  4594. end;
  4595. end;
  4596. procedure TCustomExifData.SetAllDateTimeValues(const NewValue: TDateTimeTagValue);
  4597. begin
  4598. BeginUpdate;
  4599. try
  4600. DateTime := NewValue;
  4601. DateTimeOriginal := NewValue;
  4602. DateTimeDigitized := NewValue;
  4603. finally
  4604. EndUpdate;
  4605. end;
  4606. end;
  4607. procedure TCustomExifData.SetEndianness(Value: TEndianness);
  4608. begin
  4609. if Value = FEndianness then Exit;
  4610. FEndianness := Value;
  4611. Changed(nil);
  4612. end;
  4613. procedure TCustomExifData.ResetMakerNoteType;
  4614. begin
  4615. FMakerNoteType := TUnrecognizedMakerNote;
  4616. FreeAndNil(FMakerNoteValue);
  4617. end;
  4618. procedure TCustomExifData.SetModified(Value: Boolean);
  4619. begin
  4620. if Value then
  4621. Changed(nil)
  4622. else
  4623. FModified := Value;
  4624. end;
  4625. procedure TCustomExifData.SetThumbnail(Value: TJPEGImage);
  4626. begin
  4627. if IsGraphicEmpty(Value) then
  4628. FreeAndNil(FThumbnailOrNil)
  4629. else
  4630. GetThumbnail.Assign(Value);
  4631. end;
  4632. function TCustomExifData.ShutterSpeedInMSecs: Extended;
  4633. var
  4634. Apex: TExifSignedFraction;
  4635. begin
  4636. Apex := ShutterSpeedValue;
  4637. if Apex.MissingOrInvalid then
  4638. Result := 0
  4639. else
  4640. Result := (1 / Power(2, Apex.Quotient)) * 1000;
  4641. end;
  4642. procedure TCustomExifData.ThumbnailChanged(Sender: TObject);
  4643. var
  4644. Tag: TExifTag;
  4645. begin
  4646. Modified := True;
  4647. if Sender = FThumbnailOrNil then
  4648. with Sections[esThumbnail] do
  4649. if Find(ttImageWidth, Tag) or Find(ttImageHeight, Tag) then
  4650. begin
  4651. SetWordValue(ttImageWidth, 0, FThumbnailOrNil.Width);
  4652. SetWordValue(ttImageHeight, 0, FThumbnailOrNil.Height);
  4653. end;
  4654. end;
  4655. function TCustomExifData.GetXMPWritePolicy: TXMPWritePolicy;
  4656. begin
  4657. Result := TContainedXMPPacket(XMPPacket).UpdatePolicy;
  4658. end;
  4659. procedure TCustomExifData.SetXMPWritePolicy(Value: TXMPWritePolicy);
  4660. begin
  4661. TContainedXMPPacket(XMPPacket).UpdatePolicy := Value;
  4662. end;
  4663. { TCustomExifData - tag getters }
  4664. function TCustomExifData.GetAuthor: UnicodeString;
  4665. begin
  4666. Result := GetGeneralWinString(ttWindowsAuthor);
  4667. if Result = '' then
  4668. Result := GetGeneralString(ttArtist);
  4669. end;
  4670. function TCustomExifData.GetColorSpace: TExifColorSpace;
  4671. begin
  4672. if FSections[esDetails].TryGetWordValue(ttColorSpace, 0, Result) then
  4673. if not EnsureEnumsInRange then
  4674. Exit
  4675. else
  4676. case Result of
  4677. csRGB, csAdobeRGB, csWideGamutRGB, csICCProfile, csUncalibrated: Exit;
  4678. end;
  4679. Result := csTagMissing;
  4680. end;
  4681. type
  4682. TExifUserCommentID = array[1..8] of AnsiChar;
  4683. const
  4684. UCID_ASCI: TExifUserCommentID = 'ASCII'#0#0#0;
  4685. UCID_Kanji: TExifUserCommentID = 'JIS'#0#0#0#0#0;
  4686. UCID_Unicode: TExifUserCommentID = 'UNICODE'#0;
  4687. function TCustomExifData.GetComments: UnicodeString;
  4688. const
  4689. IDSize = SizeOf(TExifUserCommentID);
  4690. var
  4691. Tag: TExifTag;
  4692. StrStart: PAnsiChar;
  4693. StrByteLen: Integer;
  4694. TiffStr: TiffString;
  4695. begin
  4696. Result := GetGeneralWinString(ttWindowsComments);
  4697. if (Result = '') and FSections[esDetails].Find(ttUserComment, Tag) and
  4698. (Tag.DataType in [tdByte, tdUndefined]) and (Tag.ElementCount > 9) then
  4699. begin
  4700. StrStart := @PAnsiChar(Tag.Data)[IDSize];
  4701. StrByteLen := Tag.ElementCount - IDSize;
  4702. while (StrByteLen > 0) and (StrStart[StrByteLen - 1] in [#0..' ']) do
  4703. Dec(StrByteLen);
  4704. if CompareMem(Tag.Data, @UCID_Unicode, IDSize) then
  4705. SetString(Result, PWideChar(StrStart), StrByteLen div 2)
  4706. else if CompareMem(Tag.Data, @UCID_ASCI, IDSize) then
  4707. begin
  4708. SetString(TiffStr, StrStart, StrByteLen);
  4709. Result := UnicodeString(TiffStr);
  4710. end;
  4711. end;
  4712. end;
  4713. function TCustomExifData.GetContrast: TExifContrast;
  4714. begin
  4715. if FSections[esDetails].TryGetWordValue(ttContrast, 0, Result) then
  4716. if not EnsureEnumsInRange then
  4717. Exit
  4718. else
  4719. case Ord(Result) of
  4720. Ord(Low(TExifContrast))..Ord(High(TExifContrast)): Exit;
  4721. end;
  4722. Result := cnTagMissing;
  4723. end;
  4724. procedure TCustomExifData.SetContrast(Value: TExifContrast);
  4725. begin
  4726. SetDetailsWordEnum(ttContrast, 'Contrast', Value);
  4727. end;
  4728. function TCustomExifData.GetDetailsDateTime(TagID: Integer): TDateTimeTagValue;
  4729. var
  4730. SubSecsID: TExifTagID;
  4731. begin
  4732. case TagID of
  4733. ttDateTimeOriginal: SubSecsID := ttSubsecTimeOriginal;
  4734. ttDateTimeDigitized: SubSecsID := ttSubsecTimeDigitized;
  4735. else SubSecsID := 0;
  4736. end;
  4737. Result := FSections[esDetails].GetDateTimeValue(TagID, SubSecsID);
  4738. end;
  4739. function TCustomExifData.GetDetailsFraction(TagID: Integer): TExifFraction;
  4740. begin
  4741. Result := FSections[esDetails].GetFractionValue(TagID, 0)
  4742. end;
  4743. function TCustomExifData.GetDetailsSFraction(TagID: Integer): TExifSignedFraction;
  4744. begin
  4745. Result := TExifSignedFraction(FSections[esDetails].GetFractionValue(TagID, 0))
  4746. end;
  4747. function TCustomExifData.GetOffsetSchema: TLongIntTagValue;
  4748. begin
  4749. Result := FSections[esDetails].GetLongIntValue(ttOffsetSchema, 0)
  4750. end;
  4751. function TCustomExifData.GetDetailsLongWord(TagID: Integer): TLongWordTagValue;
  4752. begin
  4753. Result := FSections[esDetails].GetLongWordValue(TagID, 0);
  4754. end;
  4755. function TCustomExifData.GetDetailsString(TagID: Integer): string;
  4756. begin
  4757. Result := FSections[esDetails].GetStringValue(TagID)
  4758. end;
  4759. {$IFNDEF CANINLINE}
  4760. function GetQuotient(const Fraction: TExifFraction): Extended;
  4761. begin
  4762. Result := Fraction.Quotient;
  4763. end;
  4764. {$ENDIF}
  4765. function TCustomExifData.GetFocalLengthIn35mmFilm: TWordTagValue;
  4766. var
  4767. CCDWidth, CCDHeight, ResUnit, FinalValue: Extended;
  4768. ExifWidth, ExifHeight: Integer;
  4769. FocalLengthFrac: TExifFraction;
  4770. begin
  4771. Result := FSections[esDetails].GetWordValue(ttFocalLengthIn35mmFilm, 0);
  4772. if not Result.MissingOrInvalid then Exit;
  4773. FocalLengthFrac := FocalLength;
  4774. if FocalLengthFrac.MissingOrInvalid then Exit;
  4775. ExifWidth := ExifImageWidth;
  4776. ExifHeight := ExifImageHeight;
  4777. if (ExifWidth = 0) or (ExifHeight = 0) then Exit;
  4778. case FocalPlaneResolution.Units of
  4779. trInch: ResUnit := 25.4;
  4780. trCentimetre: ResUnit := 10.0;
  4781. else Exit;
  4782. end;
  4783. {$IFDEF CANINLINE}
  4784. CCDWidth := FocalPlaneResolution.X.Quotient;
  4785. CCDHeight := FocalPlaneResolution.Y.Quotient;
  4786. {$ELSE} //crappy D2006 compiler!
  4787. CCDWidth := GetQuotient(FocalPlaneResolution.X);
  4788. CCDHeight := GetQuotient(FocalPlaneResolution.Y);
  4789. {$ENDIF}
  4790. if (CCDWidth = 0) or (CCDHeight = 0) then Exit;
  4791. CCDWidth := ExifWidth * ResUnit / CCDWidth;
  4792. CCDHeight := ExifHeight * ResUnit / CCDHeight;
  4793. if Diag35mm = 0 then
  4794. Diag35mm := Sqrt(Sqr(24) + Sqr(36));
  4795. {$IFDEF CANINLINE}
  4796. FinalValue := FocalLengthFrac.Quotient * Diag35mm / Sqrt(Sqr(CCDWidth) + Sqr(CCDHeight));
  4797. {$ELSE} //crappy D2006 compiler!
  4798. FinalValue := GetQuotient(FocalLengthFrac) * Diag35mm / Sqrt(Sqr(CCDWidth) + Sqr(CCDHeight));
  4799. {$ENDIF}
  4800. if InRange(FinalValue, 0, High(Word)) then
  4801. Result := Trunc(FinalValue);
  4802. end;
  4803. function TCustomExifData.GetExposureMode: TExifExposureMode;
  4804. begin
  4805. if FSections[esDetails].TryGetWordValue(ttExposureMode, 0, Result) then
  4806. if not EnsureEnumsInRange then
  4807. Exit
  4808. else
  4809. case Ord(Result) of
  4810. Ord(Low(TExifExposureMode))..Ord(High(TExifExposureMode)): Exit;
  4811. end;
  4812. Result := exTagMissing;
  4813. end;
  4814. function TCustomExifData.GetExposureProgram: TExifExposureProgram;
  4815. begin
  4816. if FSections[esDetails].TryGetWordValue(ttExposureProgram, 0, Result) then
  4817. if not EnsureEnumsInRange then
  4818. Exit
  4819. else
  4820. case Ord(Result) of
  4821. Ord(Low(TExifExposureProgram))..Ord(High(TExifExposureProgram)): Exit;
  4822. end;
  4823. Result := eeTagMissing;
  4824. end;
  4825. function TCustomExifData.GetFileSource: TExifFileSource;
  4826. begin
  4827. if FSections[esDetails].TryGetByteValue(ttFileSource, 0, Result) then
  4828. if not EnsureEnumsInRange then
  4829. Exit
  4830. else
  4831. case Ord(Result) of
  4832. Ord(Low(TExifFileSource))..Ord(High(TExifFileSource)): Exit;
  4833. end;
  4834. Result := fsUnknown;
  4835. end;
  4836. function TCustomExifData.GetGainControl: TExifGainControl;
  4837. begin
  4838. if FSections[esDetails].TryGetWordValue(ttGainControl, 0, Result) then
  4839. if not EnsureEnumsInRange then
  4840. Exit
  4841. else
  4842. case Ord(Result) of
  4843. Ord(Low(TExifGainControl))..Ord(High(TExifGainControl)): Exit;
  4844. end;
  4845. Result := egTagMissing;
  4846. end;
  4847. function TCustomExifData.GetDateTime: TDateTimeTagValue;
  4848. begin
  4849. Result := FSections[esGeneral].GetDateTimeValue(ttDateTime, ttSubsecTime);
  4850. end;
  4851. function TCustomExifData.GetGeneralString(TagID: Integer): string;
  4852. begin
  4853. Result := FSections[esGeneral].GetStringValue(TagID)
  4854. end;
  4855. function TCustomExifData.GetGeneralWinString(TagID: Integer): UnicodeString;
  4856. begin
  4857. Result := FSections[esGeneral].GetWindowsStringValue(TagID)
  4858. end;
  4859. function TCustomExifData.GetGPSDateTimeUTC: TDateTimeTagValue;
  4860. var
  4861. DatePart, TimePart: TDateTime;
  4862. Hour, Minute, Second: TExifFraction;
  4863. S: string;
  4864. Year, Month, Day: Integer;
  4865. begin
  4866. S := GPSDateStamp;
  4867. if (Length(S) <> 10) or not TryStrToInt(Copy(S, 1, 4), Year) or
  4868. not TryStrToInt(Copy(S, 6, 2), Month) or not TryStrToInt(Copy(S, 9, 2), Day) or
  4869. not TryEncodeDate(Year, Month, Day, DatePart) then
  4870. DatePart := 0;
  4871. Hour := GPSTimeStampHour;
  4872. Minute := GPSTimeStampMinute;
  4873. Second := GPSTimeStampSecond;
  4874. if Hour.MissingOrInvalid or Minute.MissingOrInvalid or Second.MissingOrInvalid or
  4875. not TryEncodeTime(Trunc(Hour.Quotient), Trunc(Minute.Quotient),
  4876. Trunc(Second.Quotient), 0, TimePart) then
  4877. begin
  4878. if DatePart = 0 then
  4879. Result := TDateTimeTagValue.CreateMissingOrInvalid
  4880. else
  4881. Result := DatePart;
  4882. Exit;
  4883. end;
  4884. if DatePart = 0 then
  4885. begin
  4886. DatePart := DateTime.Value;
  4887. if DatePart = 0 then DatePart := DateTimeOriginal.Value;
  4888. DatePart := DateOf(DatePart);
  4889. end;
  4890. if DatePart >= 0 then
  4891. Result := DatePart + TimePart
  4892. else
  4893. Result := DatePart - TimePart
  4894. end;
  4895. function TCustomExifData.GetGPSFraction(TagID: Integer): TExifFraction;
  4896. begin
  4897. Result := FSections[esGPS].GetFractionValue(TagID, 0);
  4898. end;
  4899. function TCustomExifData.GetGPSDifferential: TGPSDifferential;
  4900. begin
  4901. if FSections[esGPS].TryGetWordValue(ttGPSDifferential, 0, Result) then
  4902. if not EnsureEnumsInRange then
  4903. Exit
  4904. else
  4905. case Ord(Result) of
  4906. Ord(Low(TGPSDifferential))..Ord(High(TGPSDifferential)): Exit;
  4907. end;
  4908. Result := dfTagMissing;
  4909. end;
  4910. function TCustomExifData.GetGPSMeasureMode: TGPSMeasureMode;
  4911. var
  4912. S: string;
  4913. begin
  4914. Result := mmUnknown;
  4915. if FSections[esGPS].TryGetStringValue(ttGPSMeasureMode, S) and (S <> '') then
  4916. case UpCase(S[1]) of
  4917. '2': Result := mm2D;
  4918. '3': Result := mm3D;
  4919. end;
  4920. end;
  4921. function TCustomExifData.GetGPSStatus: TGPSStatus;
  4922. var
  4923. S: string;
  4924. begin
  4925. Result := stMissingOrInvalid;
  4926. if FSections[esGPS].TryGetStringValue(ttGPSStatus, S) and (S <> '') then
  4927. case UpCase(S[1]) of
  4928. 'A': Result := stMeasurementActive;
  4929. 'V': Result := stMeasurementVoid;
  4930. end;
  4931. end;
  4932. function TCustomExifData.GetGPSString(TagID: Integer): string;
  4933. begin
  4934. Result := FSections[esGPS].GetStringValue(TagID)
  4935. end;
  4936. function TCustomExifData.GetGPSTimeStamp(const Index: Integer): TExifFraction;
  4937. begin
  4938. Result := FSections[esGPS].GetFractionValue(ttGPSTimeStamp, Index)
  4939. end;
  4940. function TCustomExifData.GetInteropTypeName: string;
  4941. begin
  4942. Result := FSections[esInterop].GetStringValue(ttInteropIndex);
  4943. end;
  4944. procedure TCustomExifData.SetInteropTypeName(const Value: string);
  4945. begin
  4946. FSections[esInterop].SetStringValue(ttInteropIndex, Value);
  4947. end;
  4948. function TCustomExifData.GetLightSource: TExifLightSource;
  4949. begin
  4950. if FSections[esDetails].TryGetWordValue(ttLightSource, 0, Result) then
  4951. if not EnsureEnumsInRange then
  4952. Exit
  4953. else
  4954. case Ord(Result) of
  4955. Ord(Low(TExifLightSource))..Ord(High(TExifLightSource)): Exit;
  4956. end;
  4957. Result := elTagMissing;
  4958. end;
  4959. function TCustomExifData.GetMeteringMode: TExifMeteringMode;
  4960. begin
  4961. if FSections[esDetails].TryGetWordValue(ttMeteringMode, 0, Result) then
  4962. if not EnsureEnumsInRange then
  4963. Exit
  4964. else
  4965. case Ord(Result) of
  4966. Ord(Low(TExifMeteringMode))..Ord(High(TExifMeteringMode)): Exit;
  4967. end;
  4968. Result := emTagMissing;
  4969. end;
  4970. function TCustomExifData.GetOrientation(SectionKind: Integer): TExifOrientation;
  4971. var
  4972. Section: TExifSectionKind absolute SectionKind;
  4973. begin
  4974. if FSections[Section].TryGetWordValue(ttOrientation, 0, Result) then
  4975. if not EnsureEnumsInRange then
  4976. Exit
  4977. else
  4978. case Ord(Result) of
  4979. Ord(Low(TExifOrientation))..Ord(High(TExifOrientation)): Exit;
  4980. end;
  4981. Result := toUndefined;
  4982. end;
  4983. function TCustomExifData.GetRendering: TExifRendering;
  4984. begin
  4985. if FSections[esDetails].TryGetWordValue(ttCustomRendered, 0, Result) then
  4986. if not EnsureEnumsInRange then
  4987. Exit
  4988. else
  4989. case Ord(Result) of
  4990. Ord(Low(TExifRendering))..Ord(High(TExifRendering)): Exit;
  4991. end;
  4992. Result := erTagMissing;
  4993. end;
  4994. function TCustomExifData.GetSaturation: TExifSaturation;
  4995. begin
  4996. if FSections[esDetails].TryGetWordValue(ttSaturation, 0, Result) then
  4997. if not EnsureEnumsInRange then
  4998. Exit
  4999. else
  5000. case Ord(Result) of
  5001. Ord(Low(TExifSaturation))..Ord(High(TExifSaturation)): Exit;
  5002. end;
  5003. Result := euTagMissing;
  5004. end;
  5005. function TCustomExifData.GetSceneCaptureType: TExifSceneCaptureType;
  5006. begin
  5007. if FSections[esDetails].TryGetWordValue(ttSceneCaptureType, 0, Result) then
  5008. if not EnsureEnumsInRange then
  5009. Exit
  5010. else
  5011. case Ord(Result) of
  5012. Ord(Low(TExifSceneCaptureType))..Ord(High(TExifSceneCaptureType)): Exit;
  5013. end;
  5014. Result := ecTagMissing;
  5015. end;
  5016. function TCustomExifData.GetSceneType: TExifSceneType;
  5017. begin
  5018. if FSections[esDetails].TryGetByteValue(ttSceneType, 0, Result) then
  5019. if not EnsureEnumsInRange then
  5020. Exit
  5021. else
  5022. case Ord(Result) of
  5023. Ord(Low(TExifSceneType))..Ord(High(TExifSceneType)): Exit;
  5024. end;
  5025. Result := esUnknown;
  5026. end;
  5027. function TCustomExifData.GetSensingMethod: TExifSensingMethod;
  5028. begin
  5029. if FSections[esDetails].TryGetWordValue(ttSensingMethod, 0, Result) then
  5030. if not EnsureEnumsInRange then
  5031. Exit
  5032. else
  5033. case Ord(Result) of
  5034. Ord(Low(TExifSensingMethod))..Ord(High(TExifSensingMethod)): Exit;
  5035. end;
  5036. Result := esTagMissing;
  5037. end;
  5038. function TCustomExifData.GetSharpness: TExifSharpness;
  5039. begin
  5040. if FSections[esDetails].TryGetWordValue(ttSharpness, 0, Result) then
  5041. if not EnsureEnumsInRange then
  5042. Exit
  5043. else
  5044. case Ord(Result) of
  5045. Ord(Low(TExifSharpness))..Ord(High(TExifSharpness)): Exit;
  5046. end;
  5047. Result := ehTagMissing;
  5048. end;
  5049. function TCustomExifData.GetSubjectDistanceRange: TExifSubjectDistanceRange;
  5050. begin
  5051. if FSections[esDetails].TryGetWordValue(ttSubjectDistanceRange, 0, Result) then
  5052. if not EnsureEnumsInRange then
  5053. Exit
  5054. else
  5055. case Ord(Result) of
  5056. Ord(Low(TExifSubjectDistanceRange))..Ord(High(TExifSubjectDistanceRange)): Exit;
  5057. end;
  5058. Result := edTagMissing;
  5059. end;
  5060. function TCustomExifData.GetSubjectLocation: TSmallPoint;
  5061. var
  5062. Tag: TExifTag;
  5063. begin
  5064. if FSections[esDetails].Find(ttSubjectLocation, Tag) and
  5065. (Tag.DataType in [tdWord, tdSmallInt]) and (Tag.ElementCount >= 2) then
  5066. Result := PSmallPoint(Tag.Data)^
  5067. else
  5068. begin
  5069. Result.x := -1;
  5070. Result.y := -1;
  5071. end;
  5072. end;
  5073. function TCustomExifData.GetUserRating: TWindowsStarRating;
  5074. const
  5075. MinRating = Ord(Low(TWindowsStarRating));
  5076. MaxRating = Ord(High(TWindowsStarRating));
  5077. var
  5078. I: Integer;
  5079. begin
  5080. if FSections[esGeneral].TryGetWordValue(ttWindowsRating, 0, Result) then
  5081. if not EnsureEnumsInRange then
  5082. Exit
  5083. else
  5084. case Ord(Result) of
  5085. MinRating..MaxRating: Exit;
  5086. end
  5087. else
  5088. if TryStrToInt(XMPPacket[xsXMPBasic].Properties['Rating'].ReadValue, I) then
  5089. if not EnsureEnumsInRange or InRange(I, MinRating, MaxRating) then
  5090. begin
  5091. Result := TWindowsStarRating(I);
  5092. Exit;
  5093. end;
  5094. Result := urUndefined;
  5095. end;
  5096. function TCustomExifData.GetWhiteBalance: TExifWhiteBalanceMode;
  5097. begin
  5098. if FSections[esDetails].TryGetWordValue(ttWhiteBalance, 0, Result) then
  5099. if not EnsureEnumsInRange then
  5100. Exit
  5101. else
  5102. case Ord(Result) of
  5103. Ord(Low(TExifWhiteBalanceMode))..Ord(High(TExifWhiteBalanceMode)): Exit;
  5104. end;
  5105. Result := ewTagMissing;
  5106. end;
  5107. { TCustomExifData - tag setters }
  5108. procedure TCustomExifData.SetAuthor(const Value: UnicodeString);
  5109. { While Windows Explorer always writes both XMP properties, it always sets its own Unicode
  5110. Exif tag and clears the 'standard' ASCII one; we'll be a bit more intelligent though. }
  5111. var
  5112. Tag: TExifTag;
  5113. begin
  5114. XMPPacket.UpdateSeqProperty(xsDublinCore, 'creator', Value);
  5115. XMPPacket.UpdateProperty(xsTIFF, 'Artist', Value);
  5116. if Length(Value) = 0 then
  5117. begin
  5118. FSections[esGeneral].Remove(ttArtist);
  5119. FSections[esGeneral].Remove(ttWindowsAuthor);
  5120. Exit;
  5121. end;
  5122. if not ContainsOnlyASCII(Value) then
  5123. if EnforceASCII then
  5124. FSections[esGeneral].Remove(ttArtist)
  5125. else //This 'else' clause suggested by
  5126. FSections[esGeneral].SetStringValue(ttArtist, Value) //Thomas Mueller (*), and implemented in v1.5.2.
  5127. else
  5128. if FSections[esGeneral].Find(ttArtist, Tag) then
  5129. begin
  5130. Tag.UpdateData(tdAscii, Length(Value), TiffString(Value)[1]);
  5131. if not FSections[esGeneral].Find(ttWindowsAuthor, Tag) then
  5132. Exit;
  5133. end
  5134. else //Ditto for this 'else' clause.
  5135. FSections[esGeneral].SetStringValue(ttArtist, Value);
  5136. FSections[esGeneral].SetWindowsStringValue(ttWindowsAuthor, Value);
  5137. end;
  5138. //(*) http://delphihaven.wordpress.com/2012/01/16/xe2-update-for-my-image-metadata-readingwriting-library-ccr-exif/comment-page-1/#comment-4998
  5139. procedure TCustomExifData.SetColorSpace(Value: TExifColorSpace);
  5140. begin
  5141. if Value = csTagMissing then
  5142. begin
  5143. FSections[esDetails].Remove(ttColorSpace);
  5144. XMPPacket.RemoveProperty(xsExif, 'ColorSpace');
  5145. end
  5146. else
  5147. begin
  5148. FSections[esDetails].ForceSetElement(ttColorSpace, tdWord, 0, Value);
  5149. XMPPacket.UpdateProperty(xsExif, 'ColorSpace', Ord(Value));
  5150. end;
  5151. end;
  5152. procedure TCustomExifData.SetComments(const Value: UnicodeString);
  5153. var
  5154. Tag: TExifTag;
  5155. NewSize: Integer;
  5156. begin
  5157. XMPPacket.UpdateProperty(xsExif, 'UserComment', xpAltArray, Value);
  5158. if Length(Value) = 0 then
  5159. begin
  5160. FSections[esGeneral].Remove(ttWindowsComments);
  5161. FSections[esDetails].Remove(ttUserComment);
  5162. end
  5163. else
  5164. begin
  5165. FSections[esGeneral].SetWindowsStringValue(ttWindowsComments, Value);
  5166. if FSections[esGeneral].Find(ttUserComment, Tag) then
  5167. begin
  5168. NewSize := SizeOf(UCID_Unicode) + Length(Value) * 2;
  5169. if (NewSize > Tag.OriginalDataSize) and not Tag.Section.IsExtendable then
  5170. Tag.ElementCount := 0
  5171. else
  5172. begin
  5173. Tag.UpdateData(tdByte, NewSize, PByte(nil)^);
  5174. Move(UCID_Unicode, Tag.Data^, SizeOf(UCID_Unicode));
  5175. Move(PWideChar(Value)^, PByteArray(Tag.Data)[SizeOf(UCID_Unicode)],
  5176. NewSize - SizeOf(UCID_Unicode));
  5177. end;
  5178. end;
  5179. end;
  5180. end;
  5181. procedure TCustomExifData.SetDetailsDateTime(TagID: Integer; const Value: TDateTimeTagValue);
  5182. var
  5183. SubSecsID: TExifTagID;
  5184. XMPName: string;
  5185. begin
  5186. case TagID of
  5187. ttDateTimeOriginal: SubSecsID := ttSubsecTimeOriginal;
  5188. ttDateTimeDigitized: SubSecsID := ttSubsecTimeDigitized;
  5189. else SubSecsID := 0;
  5190. end;
  5191. FSections[esDetails].SetDateTimeValue(TagID, SubSecsID, Value);
  5192. case TagID of
  5193. ttDateTimeOriginal: XMPName := 'DateTimeOriginal';
  5194. ttDateTimeDigitized: XMPName := 'DateTimeDigitized';
  5195. else Exit;
  5196. end;
  5197. XMPPacket.UpdateDateTimeProperty(xsExif, XMPName, Value, False);
  5198. end;
  5199. procedure TCustomExifData.SetDetailsFraction(TagID: Integer;
  5200. const Value: TExifFraction);
  5201. var
  5202. XMPName: string;
  5203. begin
  5204. FSections[esDetails].SetFractionValue(TagID, 0, Value);
  5205. case TagID of
  5206. ttApertureValue: XMPName := 'ApertureValue';
  5207. ttCompressedBitsPerPixel: XMPName := 'CompressedBitsPerPixel';
  5208. ttDigitalZoomRatio: XMPName := 'DigitalZoomRatio';
  5209. ttExposureBiasValue: XMPName := 'ExposureBiasValue';
  5210. ttExposureTime: XMPName := 'ExposureTime';
  5211. ttFNumber: XMPName := 'FNumber';
  5212. ttMaxApertureValue: XMPName := 'MaxApertureValue';
  5213. ttSubjectDistance: XMPName := 'SubjectDistance';
  5214. else Exit;
  5215. end;
  5216. XMPPacket.UpdateProperty(xsExif, XMPName, Value.ToString);
  5217. end;
  5218. procedure TCustomExifData.SetDetailsSFraction(TagID: Integer;
  5219. const Value: TExifSignedFraction);
  5220. var
  5221. PropName: string;
  5222. begin
  5223. FSections[esDetails].SetSignedFractionValue(TagID, 0, Value);
  5224. case TagID of
  5225. ttBrightnessValue: PropName := 'BrightnessValue';
  5226. ttExposureBiasValue: PropName := 'ExposureBiasValue';
  5227. ttShutterSpeedValue: PropName := 'ShutterSpeedValue';
  5228. else Exit;
  5229. end;
  5230. XMPPacket.UpdateProperty(xsExif, PropName, Value.ToString);
  5231. end;
  5232. procedure TCustomExifData.SetOffsetSchema(const Value: TLongIntTagValue);
  5233. begin
  5234. if Value.MissingOrInvalid then
  5235. FSections[esDetails].Remove(ttOffsetSchema)
  5236. else
  5237. FSections[esDetails].ForceSetElement(ttOffsetSchema, tdLongInt, 0, Value)
  5238. end;
  5239. //procedure TCustomExifData.SetDetailsLongWord(TagID: Integer; const Value: LongWord);
  5240. //begin
  5241. // FSections[esDetails].SetLongWordValue(TagID, 0, Value)
  5242. //end;
  5243. procedure TCustomExifData.SetDetailsString(TagID: Integer; const Value: string);
  5244. var
  5245. XMPName: string;
  5246. begin
  5247. FSections[esDetails].SetStringValue(TagID, Value);
  5248. case TagID of
  5249. ttImageUniqueID: XMPName := 'ImageUniqueID';
  5250. ttRelatedSoundFile: XMPName := 'RelatedSoundFile';
  5251. ttSpectralSensitivity: XMPName := 'SpectralSensitivity';
  5252. else Exit;
  5253. end;
  5254. XMPPacket.UpdateProperty(xsExif, XMPName, Value);
  5255. end;
  5256. procedure TCustomExifData.SetFocalLengthIn35mmFilm(const Value: TWordTagValue);
  5257. const
  5258. XMPName = UnicodeString('FocalLengthIn35mmFilm');
  5259. begin
  5260. if Value.MissingOrInvalid then
  5261. begin
  5262. FSections[esDetails].Remove(ttFocalLengthIn35mmFilm);
  5263. XMPPacket.RemoveProperty(xsExif, XMPName);
  5264. end
  5265. else
  5266. begin
  5267. FSections[esDetails].SetWordValue(ttFocalLengthIn35mmFilm, 0, Value);
  5268. XMPPacket.UpdateProperty(xsExif, XMPName, Value);
  5269. end;
  5270. end;
  5271. procedure TCustomExifData.SetDetailsWordEnum(ID: TExifTagID;
  5272. const XMPName: UnicodeString; const Value);
  5273. begin
  5274. if SmallInt(Value) = -1 then
  5275. begin
  5276. FSections[esDetails].Remove(ID);
  5277. XMPPacket.RemoveProperty(xsExif, XMPName);
  5278. end
  5279. else
  5280. begin
  5281. FSections[esDetails].ForceSetElement(ID, tdWord, 0, Value);
  5282. XMPPacket.UpdateProperty(xsExif, XMPName, Word(Value));
  5283. end;
  5284. end;
  5285. procedure TCustomExifData.SetDetailsByteEnum(ID: TExifTagID; const XMPName: UnicodeString; const Value);
  5286. begin
  5287. if Byte(Value) = 0 then
  5288. begin
  5289. FSections[esDetails].Remove(ID);
  5290. XMPPacket.RemoveProperty(xsExif, XMPName);
  5291. end
  5292. else
  5293. begin
  5294. FSections[esDetails].ForceSetElement(ID, tdUndefined, 0, Value);
  5295. XMPPacket.UpdateProperty(xsExif, XMPName, Byte(Value));
  5296. end;
  5297. end;
  5298. procedure TCustomExifData.SetExifImageSize(ID: Integer; const NewValue: TLongWordTagValue);
  5299. const
  5300. PropNames: array[ttExifImageWidth..ttExifImageHeight] of UnicodeString =
  5301. ('PixelXDimension', 'PixelYDimension');
  5302. var
  5303. Tag: TExifTag;
  5304. begin
  5305. if NewValue.MissingOrInvalid then
  5306. begin
  5307. FSections[esDetails].Remove(ID);
  5308. XMPPacket.RemoveProperty(xsExif, PropNames[ID]);
  5309. Exit;
  5310. end;
  5311. Tag := nil;
  5312. if (NewValue <= High(Word)) and FSections[esDetails].Find(ID, Tag) and
  5313. (Tag.DataType = tdWord) and (Tag.ElementCount = 1) then
  5314. Tag.UpdateData(NewValue)
  5315. else if Tag <> nil then
  5316. Tag.UpdateData(tdLongWord, 1, NewValue)
  5317. else
  5318. PLongWord(FSections[esDetails].Add(ID, tdLongWord, 1).Data)^ := NewValue;
  5319. XMPPacket.UpdateProperty(xsExif, PropNames[ID], Integer(Int64(NewValue)));
  5320. end;
  5321. procedure TCustomExifData.SetExifVersion(Value: TCustomExifVersion);
  5322. begin
  5323. FExifVersion.Assign(Value);
  5324. end;
  5325. procedure TCustomExifData.SetExposureMode(const Value: TExifExposureMode);
  5326. begin
  5327. SetDetailsWordEnum(ttExposureMode, 'ExposureMode', Value);
  5328. end;
  5329. procedure TCustomExifData.SetExposureProgram(const Value: TExifExposureProgram);
  5330. begin
  5331. SetDetailsWordEnum(ttExposureProgram, 'ExposureProgram', Value);
  5332. end;
  5333. procedure TCustomExifData.SetFlashPixVersion(Value: TCustomExifVersion);
  5334. begin
  5335. FFlashPixVersion.Assign(Value);
  5336. end;
  5337. procedure TCustomExifData.SetFileSource(const Value: TExifFileSource);
  5338. begin
  5339. SetDetailsByteEnum(ttFileSource, 'FileSource', Value);
  5340. end;
  5341. procedure TCustomExifData.SetFlash(Value: TExifFlashInfo);
  5342. begin
  5343. FFlash.Assign(Value);
  5344. end;
  5345. procedure TCustomExifData.SetFocalPlaneResolution(Value: TCustomExifResolution);
  5346. begin
  5347. FFocalPlaneResolution.Assign(Value);
  5348. end;
  5349. procedure TCustomExifData.SetGainControl(const Value: TExifGainControl);
  5350. begin
  5351. SetDetailsWordEnum(ttGainControl, 'GainControl', Value);
  5352. end;
  5353. procedure TCustomExifData.SetDateTime(const Value: TDateTimeTagValue);
  5354. begin
  5355. FSections[esGeneral].SetDateTimeValue(ttDateTime, ttSubsecTime, Value);
  5356. XMPPacket.UpdateDateTimeProperty(xsTIFF, 'DateTime', Value, False);
  5357. end;
  5358. procedure TCustomExifData.SetGeneralString(TagID: Integer; const Value: string);
  5359. begin
  5360. FSections[esGeneral].SetStringValue(TagID, Value);
  5361. case TagID of
  5362. ttCopyright: XMPPacket.UpdateProperty(xsDublinCore, 'rights', xpAltArray,
  5363. Value);
  5364. ttImageDescription:
  5365. if (Value <> '') or (XMPWritePolicy = xwRemove) or
  5366. not FSections[esDetails].TagExists(ttWindowsSubject) then
  5367. XMPPacket.UpdateProperty(xsDublinCore, 'description', xpAltArray, Value);
  5368. ttMake: XMPPacket.UpdateProperty(xsTIFF, 'Make', Value);
  5369. ttModel: XMPPacket.UpdateProperty(xsTIFF, 'Model', Value);
  5370. ttSoftware:
  5371. begin
  5372. XMPPacket.UpdateProperty(xsXMPBasic, 'creatortool', Value);
  5373. XMPPacket.UpdateProperty(xsTIFF, 'Software', Value);
  5374. end;
  5375. end;
  5376. end;
  5377. procedure TCustomExifData.SetGeneralWinString(TagID: Integer;
  5378. const Value: UnicodeString);
  5379. begin
  5380. FSections[esGeneral].SetWindowsStringValue(TagID, Value);
  5381. case TagID of
  5382. ttWindowsKeywords:
  5383. begin
  5384. XMPPacket.UpdateBagProperty(xsDublinCore, 'subject', Value);
  5385. XMPPacket.UpdateBagProperty(xsMicrosoftPhoto, 'LastKeywordXMP', Value);
  5386. end;
  5387. ttWindowsSubject: XMPPacket.UpdateProperty(xsDublinCore, 'description', xpAltArray, Value);
  5388. ttWindowsTitle: XMPPacket.UpdateProperty(xsDublinCore, 'title', xpAltArray, Value);
  5389. end;
  5390. end;
  5391. procedure TCustomExifData.SetGPSAltitude(const Value: TGPSAltitude);
  5392. begin
  5393. FGPSAltitude.Assign(Value);
  5394. end;
  5395. procedure TCustomExifData.SetGPSDateTimeUTC(const Value: TDateTimeTagValue);
  5396. const
  5397. XMPName = UnicodeString('GPSTimeStamp');
  5398. var
  5399. Year, Month, Day, Hour, Minute, Second, MSecond: Word;
  5400. begin
  5401. BeginUpdate;
  5402. try
  5403. if Value.MissingOrInvalid then
  5404. begin
  5405. FSections[esGPS].Remove(ttGPSDateStamp);
  5406. FSections[esGPS].Remove(ttGPSTimeStamp);
  5407. XMPPacket.RemoveProperty(xsExif, XMPName);
  5408. Exit;
  5409. end;
  5410. DecodeDateTime(Value, Year, Month, Day, Hour, Minute, Second, MSecond);
  5411. GPSDateStamp := Format('%.4d:%.2d:%.2d', [Year, Month, Day]);
  5412. GPSTimeStampHour := TExifFraction.Create(Hour, 1);
  5413. GPSTimeStampMinute := TExifFraction.Create(Minute, 1);
  5414. GPSTimeStampSecond := TExifFraction.Create(Second, 1);
  5415. XMPPacket.UpdateDateTimeProperty(xsExif, XMPName, Value, False);
  5416. finally
  5417. EndUpdate;
  5418. end;
  5419. end;
  5420. procedure TCustomExifData.SetGPSDestBearing(const Value: TGPSDestBearing);
  5421. begin
  5422. FGPSDestBearing.Assign(Value);
  5423. end;
  5424. procedure TCustomExifData.SetGPSDestDistance(const Value: TGPSDestDistance);
  5425. begin
  5426. FGPSDestDistance.Assign(Value);
  5427. end;
  5428. procedure TCustomExifData.SetGPSDestLatitude(Value: TGPSLatitude);
  5429. begin
  5430. FGPSDestLatitude.Assign(Value);
  5431. end;
  5432. procedure TCustomExifData.SetGPSDestLongitude(Value: TGPSLongitude);
  5433. begin
  5434. FGPSDestLongitude.Assign(Value);
  5435. end;
  5436. procedure TCustomExifData.SetGPSDifferential(Value: TGPSDifferential);
  5437. begin
  5438. if Value = dfTagMissing then
  5439. begin
  5440. FSections[esGPS].Remove(ttGPSDifferential);
  5441. XMPPacket.RemoveProperty(xsExif, GetGPSTagXMPName(ttGPSDifferential));
  5442. Exit;
  5443. end;
  5444. FSections[esGPS].ForceSetElement(ttGPSDifferential, tdWord, 0, Value);
  5445. XMPPacket.UpdateProperty(xsExif, GetGPSTagXMPName(ttGPSDifferential), Ord(Value));
  5446. end;
  5447. procedure TCustomExifData.SetGPSFraction(TagID: Integer; const Value: TExifFraction);
  5448. var
  5449. XMPName: string;
  5450. begin
  5451. FSections[esGPS].SetFractionValue(TagID, 0, Value);
  5452. if FindGPSTagXMPName(TagID, XMPName) then
  5453. XMPPacket.UpdateProperty(xsExif, XMPName, Value.ToString);
  5454. end;
  5455. procedure TCustomExifData.SetGPSImgDirection(const Value: TGPSImgDirection);
  5456. begin
  5457. FGPSImgDirection.Assign(Value)
  5458. end;
  5459. procedure TCustomExifData.SetGPSLatitude(Value: TGPSLatitude);
  5460. begin
  5461. FGPSLatitude.Assign(Value);
  5462. end;
  5463. procedure TCustomExifData.SetGPSLongitude(Value: TGPSLongitude);
  5464. begin
  5465. FGPSLongitude.Assign(Value);
  5466. end;
  5467. procedure TCustomExifData.SetGPSMeasureMode(const Value: TGPSMeasureMode);
  5468. const
  5469. Strings: array[TGPSMeasureMode] of string = ('', '2', '3');
  5470. begin
  5471. FSections[esGPS].SetStringValue(ttGPSMeasureMode, Strings[Value]);
  5472. XMPPacket.UpdateProperty(xsExif, GetGPSTagXMPName(ttGPSMeasureMode), Strings[Value]);
  5473. end;
  5474. procedure TCustomExifData.SetGPSSpeed(const Value: TGPSSpeed);
  5475. begin
  5476. FGPSSpeed.Assign(Value);
  5477. end;
  5478. procedure TCustomExifData.SetGPSStatus(const Value: TGPSStatus);
  5479. const
  5480. Strings: array[TGPSStatus] of string = ('', 'A', 'V');
  5481. begin
  5482. FSections[esGPS].SetStringValue(ttGPSStatus, Strings[Value]);
  5483. XMPPacket.UpdateProperty(xsExif, 'GPSStatus', Strings[Value]);
  5484. end;
  5485. procedure TCustomExifData.SetGPSString(TagID: Integer; const Value: string);
  5486. var
  5487. XMPName: string;
  5488. begin
  5489. FSections[esGPS].SetStringValue(TagID, Value);
  5490. if FindGPSTagXMPName(TagID, XMPName) then
  5491. XMPPacket.UpdateProperty(xsExif, XMPName, Value);
  5492. end;
  5493. procedure TCustomExifData.SetGPSTimeStamp(const Index: Integer;
  5494. const Value: TExifFraction);
  5495. begin
  5496. FSections[esGPS].SetFractionValue(ttGPSTimeStamp, Index, Value);
  5497. if FUpdateCount = 0 then
  5498. XMPPacket.RemoveProperty(xsExif, GetGPSTagXMPName(ttGPSTimeStamp));
  5499. end;
  5500. procedure TCustomExifData.SetGPSTrack(const Value: TGPSTrack);
  5501. begin
  5502. FGPSTrack.Assign(Value)
  5503. end;
  5504. procedure TCustomExifData.SetGPSVersion(Value: TCustomExifVersion);
  5505. begin
  5506. FGPSVersion.Assign(Value);
  5507. end;
  5508. procedure TCustomExifData.SetInteropVersion(Value: TCustomExifVersion);
  5509. begin
  5510. FInteropVersion.Assign(Value);
  5511. end;
  5512. procedure TCustomExifData.SetISOSpeedRatings(Value: TISOSpeedRatings);
  5513. begin
  5514. if Value <> FISOSpeedRatings then FISOSpeedRatings.Assign(Value);
  5515. end;
  5516. procedure TCustomExifData.SetLightSource(const Value: TExifLightSource);
  5517. begin
  5518. SetDetailsWordEnum(ttLightSource, 'LightSource', Value);
  5519. end;
  5520. procedure TCustomExifData.SetMeteringMode(const Value: TExifMeteringMode);
  5521. begin
  5522. SetDetailsWordEnum(ttMeteringMode, 'MeteringMode', Value);
  5523. end;
  5524. procedure TCustomExifData.SetOrientation(SectionKind: Integer; Value: TExifOrientation);
  5525. var
  5526. XMPValue: UnicodeString;
  5527. begin
  5528. with FSections[TExifSectionKind(SectionKind)] do
  5529. if Value = toUndefined then
  5530. Remove(ttOrientation)
  5531. else
  5532. SetWordValue(ttOrientation, 0, Ord(Value));
  5533. if TExifSectionKind(SectionKind) <> esGeneral then Exit;
  5534. if Value = toUndefined then
  5535. XMPValue := ''
  5536. else
  5537. XMPValue := IntToStr(Ord(Value));
  5538. XMPPacket.UpdateProperty(xsTIFF, 'Orientation', XMPValue);
  5539. end;
  5540. procedure TCustomExifData.SetResolution(Value: TCustomExifResolution);
  5541. begin
  5542. FResolution.Assign(Value);
  5543. end;
  5544. procedure TCustomExifData.SetRendering(const Value: TExifRendering);
  5545. begin
  5546. SetDetailsWordEnum(ttCustomRendered, 'CustomRendered', Value);
  5547. end;
  5548. procedure TCustomExifData.SetSaturation(Value: TExifSaturation);
  5549. begin
  5550. SetDetailsWordEnum(ttSaturation, 'Saturation', Value);
  5551. end;
  5552. procedure TCustomExifData.SetSceneCaptureType(const Value: TExifSceneCaptureType);
  5553. begin
  5554. SetDetailsWordEnum(ttSceneCaptureType, 'SceneCaptureType', Value);
  5555. end;
  5556. procedure TCustomExifData.SetSceneType(Value: TExifSceneType);
  5557. begin
  5558. SetDetailsByteEnum(ttSceneType, 'SceneType', Value);
  5559. end;
  5560. procedure TCustomExifData.SetSensingMethod(const Value: TExifSensingMethod);
  5561. begin
  5562. SetDetailsWordEnum(ttSensingMethod, 'SensingMethod', Value);
  5563. end;
  5564. procedure TCustomExifData.SetSharpness(Value: TExifSharpness);
  5565. begin
  5566. SetDetailsWordEnum(ttSharpness, 'Sharpness', Value);
  5567. end;
  5568. procedure TCustomExifData.SetSubjectDistanceRange(Value: TExifSubjectDistanceRange);
  5569. begin
  5570. SetDetailsWordEnum(ttSubjectDistanceRange, 'SubjectDistanceRange', Value);
  5571. end;
  5572. procedure TCustomExifData.SetSubjectLocation(const Value: TSmallPoint);
  5573. const
  5574. XMPName = UnicodeString('SubjectLocation');
  5575. var
  5576. Tag: TExifTag;
  5577. begin
  5578. if Value.MissingOrInvalid then
  5579. begin
  5580. FSections[esDetails].Remove(ttSubjectDistance);
  5581. XMPPacket.RemoveProperty(xsExif, XMPName);
  5582. end
  5583. else
  5584. begin
  5585. if not FSections[esDetails].Find(ttSubjectDistance, Tag) then
  5586. Tag := FSections[esDetails].Add(ttSubjectDistance, tdWord, 2);
  5587. Tag.UpdateData(tdWord, 2, Value);
  5588. XMPPacket.UpdateSeqProperty(xsExif, XMPName, [IntToStr(Value.x), IntToStr(Value.y)]);
  5589. end;
  5590. end;
  5591. procedure TCustomExifData.SetThumbnailResolution(Value: TCustomExifResolution);
  5592. begin
  5593. FThumbnailResolution.Assign(Value);
  5594. end;
  5595. procedure TCustomExifData.SetUserRating(const Value: TWindowsStarRating);
  5596. const
  5597. MSPhotoValues: array[TWindowsStarRating] of UnicodeString = ('', '1', '25', '50', '75', '99');
  5598. XMPBasicValues: array[TWindowsStarRating] of UnicodeString = ('', '1', '2', '3', '4', '5');
  5599. begin
  5600. if Value = urUndefined then
  5601. FSections[esGeneral].Remove(ttWindowsRating)
  5602. else
  5603. FSections[esGeneral].SetWordValue(ttWindowsRating, 0, Ord(Value));
  5604. XMPPacket.UpdateProperty(xsMicrosoftPhoto, 'Rating', MSPhotoValues[Value]);
  5605. XMPPacket.UpdateProperty(xsXMPBasic, 'Rating', XMPBasicValues[Value]);
  5606. end;
  5607. procedure TCustomExifData.SetWhiteBalance(const Value: TExifWhiteBalanceMode);
  5608. begin
  5609. SetDetailsWordEnum(ttWhiteBalance, 'WhiteBalance', Value);
  5610. end;
  5611. { TExifDataPatcher }
  5612. constructor TExifDataPatcher.Create(const AFileName: string);
  5613. begin
  5614. inherited Create;
  5615. OpenFile(AFileName);
  5616. end;
  5617. destructor TExifDataPatcher.Destroy;
  5618. begin
  5619. CloseFile;
  5620. inherited;
  5621. end;
  5622. procedure TExifDataPatcher.CheckFileIsOpen;
  5623. begin
  5624. if FStream = nil then
  5625. raise ENoExifFileOpenError.CreateRes(@SNoFileOpenError);
  5626. end;
  5627. function TExifDataPatcher.GetFileDateTime: TDateTime;
  5628. begin
  5629. CheckFileIsOpen;
  5630. Result := FileDateToDateTime(FileGetDate(FStream.Handle));
  5631. end;
  5632. function TExifDataPatcher.GetFileName: string;
  5633. begin
  5634. if FStream <> nil then
  5635. Result := FStream.FileName
  5636. else
  5637. Result := '';
  5638. end;
  5639. {$IF CompilerVersion >= 22}
  5640. procedure TExifDataPatcher.GetImage<T>(const Dest: T);
  5641. {$ELSE}
  5642. procedure TExifDataPatcher.GetImage(const Dest: IStreamPersist);
  5643. {$IFEND}
  5644. begin
  5645. CheckFileIsOpen;
  5646. FStream.Position := 0;
  5647. Dest.LoadFromStream(FStream);
  5648. end;
  5649. {$IF CompilerVersion >= 22}
  5650. procedure TExifDataPatcher.GetThumbnail<T>(const Dest: T);
  5651. {$ELSE}
  5652. procedure TExifDataPatcher.GetThumbnail(Dest: TPersistent);
  5653. {$IFEND}
  5654. begin
  5655. CheckFileIsOpen;
  5656. Dest.Assign(Thumbnail);
  5657. end;
  5658. procedure TExifDataPatcher.SetFileDateTime(const Value: TDateTime);
  5659. begin
  5660. CheckFileIsOpen;
  5661. {$IFDEF MSWINDOWS} {$WARN SYMBOL_PLATFORM OFF}
  5662. FileSetDate(FStream.Handle, DateTimeToFileDate(Value)); {$WARN SYMBOL_PLATFORM ON}
  5663. {$ELSE}
  5664. FileSetDate(FStream.FileName, DateTimeToFileDate(Value)); //does actually work on OS X at least
  5665. {$ENDIF}
  5666. end;
  5667. procedure TExifDataPatcher.OpenFile(const JPEGFileName: string);
  5668. begin
  5669. CloseFile;
  5670. if JPEGFileName = '' then Exit;
  5671. FStream := TFileStream.Create(JPEGFileName, fmOpenReadWrite or fmShareDenyWrite);
  5672. if not HasJPEGHeader(FStream) then
  5673. begin
  5674. FreeAndNil(FStream);
  5675. raise EInvalidJPEGHeader.CreateResFmt(@SFileIsNotAValidJPEG, [JPEGFileName]);
  5676. end;
  5677. LoadFromGraphic(FStream);
  5678. FOriginalEndianness := Endianness;
  5679. end;
  5680. procedure TExifDataPatcher.CloseFile(SaveChanges: Boolean);
  5681. begin
  5682. if FStream = nil then Exit;
  5683. if SaveChanges then UpdateFile;
  5684. FreeAndNil(FStream);
  5685. Clear;
  5686. Modified := False;
  5687. end;
  5688. procedure TExifDataPatcher.UpdateFile;
  5689. var
  5690. DataOffsetFix: Int64;
  5691. OldDate, WrittenLen: Integer;
  5692. Section: TExifSection;
  5693. SectionEndianness: TEndianness;
  5694. Tag: TExifTag;
  5695. XMPStream: TMemoryStream;
  5696. Segment: IFoundJPEGSegment;
  5697. BytesToRewrite: TBytes;
  5698. begin
  5699. if (FStream = nil) or not Modified then Exit;
  5700. OldDate := FileGetDate(FStream.Handle);
  5701. for Section in Self do
  5702. if Section.Modified or ((Endianness <> FOriginalEndianness) and (Section.Kind <> esMakerNote)) then
  5703. begin
  5704. if Section.Kind = esMakerNote then
  5705. begin
  5706. DataOffsetFix := -OffsetSchema;
  5707. SectionEndianness := MakerNote.Endianness;
  5708. end
  5709. else
  5710. begin
  5711. DataOffsetFix := 0;
  5712. SectionEndianness := Endianness;
  5713. end;
  5714. Stream.Position := OffsetBase + Section.FirstTagHeaderOffset;
  5715. for Tag in Section do
  5716. Tag.WriteHeader(Stream, SectionEndianness, Tag.OriginalDataOffset + DataOffsetFix);
  5717. for Tag in Section do
  5718. begin
  5719. Stream.Position := OffsetBase + Tag.OriginalDataOffset;
  5720. Tag.WriteOffsettedData(Stream, SectionEndianness);
  5721. end;
  5722. Section.Modified := False;
  5723. end;
  5724. if (mkXMP in MetadataInSource) or not XMPPacket.Empty then
  5725. begin
  5726. BytesToRewrite := nil;
  5727. XMPStream := TMemoryStream.Create;
  5728. try
  5729. XMPStream.WriteBuffer(TJPEGSegment.XMPHeader, SizeOf(TJPEGSegment.XMPHeader));
  5730. XMPPacket.SaveToStream(XMPStream);
  5731. WrittenLen := XMPStream.Size;
  5732. if WrittenLen <= FXMPPacketSizeInSource then
  5733. begin
  5734. Assert(mkXMP in MetadataInSource);
  5735. XMPStream.Size := FXMPPacketSizeInSource;
  5736. FillChar(PAnsiChar(XMPStream.Memory)[WrittenLen], //!!!was a no-op until fixed v1.5.2
  5737. FXMPPacketSizeInSource - WrittenLen, $20);
  5738. end
  5739. else
  5740. begin
  5741. if mkXMP in MetadataInSource then
  5742. Stream.Position := FXMPSegmentPosition + SizeOf(TJPEGSegmentHeader) + FXMPPacketSizeInSource
  5743. else
  5744. begin
  5745. Stream.Position := 0;
  5746. for Segment in JPEGHeader(Stream) do
  5747. if Segment.MarkerNum <> jmApp1 then Break;
  5748. FXMPSegmentPosition := Stream.Position;
  5749. Include(FMetadataInSource, mkXMP);
  5750. end;
  5751. FXMPPacketSizeInSource := WrittenLen;
  5752. SetLength(BytesToRewrite, Stream.Size - Stream.Position);
  5753. Stream.ReadBuffer(BytesToRewrite[0], Length(BytesToRewrite));
  5754. end;
  5755. Stream.Position := FXMPSegmentPosition;
  5756. WriteJPEGSegment(Stream, jmApp1, XMPStream);
  5757. if BytesToRewrite <> nil then
  5758. Stream.WriteBuffer(BytesToRewrite[0], Length(BytesToRewrite));
  5759. finally
  5760. XMPStream.Free;
  5761. end;
  5762. end;
  5763. FOriginalEndianness := Endianness;
  5764. if PreserveFileDate then
  5765. SetFileDateTime(OldDate);
  5766. Modified := False;
  5767. end;
  5768. { TExifData }
  5769. constructor TExifData.Create(AOwner: TComponent = nil);
  5770. begin
  5771. inherited;
  5772. FRemovePaddingTagsOnSave := True;
  5773. end;
  5774. procedure TExifData.Assign(Source: TPersistent);
  5775. var
  5776. SourceData: TCustomExifData;
  5777. Section: TExifSectionKind;
  5778. begin
  5779. if Source = nil then
  5780. Clear
  5781. else if Source is TCustomExifData then
  5782. begin
  5783. BeginUpdate;
  5784. try
  5785. SourceData := TCustomExifData(Source);
  5786. for Section := Low(TExifSectionKind) to High(TExifSectionKind) do
  5787. Sections[Section].Assign(SourceData[Section]);
  5788. // if SourceData is TExifData then
  5789. // Thumbnail := TExifData(SourceData).FThumbnailOrNil
  5790. // else if Sections[esThumbnail].Count = 0 then
  5791. // SetThumbnail(nil);
  5792. finally
  5793. EndUpdate;
  5794. end;
  5795. end
  5796. else
  5797. inherited;
  5798. end;
  5799. {$IF Declared(TGraphic)}
  5800. procedure TExifData.CreateThumbnail(Source: TGraphic;
  5801. ThumbnailWidth, ThumbnailHeight: Integer);
  5802. begin
  5803. if IsGraphicEmpty(Source) then
  5804. Thumbnail := nil
  5805. else
  5806. CreateExifThumbnail(Source, Thumbnail, ThumbnailWidth, ThumbnailHeight);
  5807. end;
  5808. procedure TExifData.StandardizeThumbnail;
  5809. var
  5810. Image: TJPEGImage;
  5811. begin
  5812. if not HasThumbnail then Exit;
  5813. Image := Thumbnail;
  5814. if (Image.Width > StandardExifThumbnailWidth) or
  5815. (Image.Height > StandardExifThumbnailHeight) then
  5816. CreateExifThumbnail(Image, Image);
  5817. end;
  5818. {$IFEND}
  5819. procedure TExifData.DefineProperties(Filer: TFiler);
  5820. begin
  5821. inherited;
  5822. Filer.DefineBinaryProperty('Data', LoadFromStream, SaveToStream, not Empty);
  5823. end;
  5824. function TExifData.GetSection(Section: TExifSectionKind): TExtendableExifSection;
  5825. begin
  5826. Result := TExtendableExifSection(inherited Sections[Section]);
  5827. end;
  5828. class function TExifData.IsSupportedGraphic(Stream: TStream): Boolean;
  5829. begin
  5830. Result := HasJPEGHeader(Stream) or HasPSDHeader(Stream) or HasTiffHeader(Stream);
  5831. end;
  5832. class function TExifData.IsSupportedGraphic(const FileName: string): Boolean;
  5833. var
  5834. Stream: TFileStream;
  5835. begin
  5836. Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  5837. try
  5838. Result := IsSupportedGraphic(Stream);
  5839. finally
  5840. Stream.Free;
  5841. end;
  5842. end;
  5843. {$IFDEF FMX}
  5844. function TExifData.LoadFromBitmap(const Bitmap: TBitmap): Boolean;
  5845. begin
  5846. Result := LoadFromGraphic(Bitmap);
  5847. end;
  5848. function TExifData.LoadFromBitmap(const FileName: string): Boolean;
  5849. begin
  5850. Result := LoadFromGraphic(FileName);
  5851. end;
  5852. procedure TExifData.SaveToBitmap(const Bitmap: TBitmap);
  5853. begin
  5854. SaveToGraphic(Bitmap);
  5855. end;
  5856. procedure TExifData.SaveToBitmap(const FileName: string);
  5857. begin
  5858. SaveToGraphic(FileName);
  5859. end;
  5860. {$ENDIF FMX}
  5861. function TExifData.LoadFromGraphic(Stream: TStream): Boolean;
  5862. begin
  5863. Result := inherited LoadFromGraphic(Stream);
  5864. end;
  5865. function TExifData.LoadFromGraphic(const Graphic: IStreamPersist): Boolean;
  5866. var
  5867. Stream: TMemoryStream;
  5868. begin
  5869. Stream := TMemoryStream.Create;
  5870. try
  5871. Graphic.SaveToStream(Stream);
  5872. Stream.Position := 0;
  5873. Result := LoadFromGraphic(Stream)
  5874. finally
  5875. Stream.Free;
  5876. end;
  5877. end;
  5878. function TExifData.LoadFromGraphic(const FileName: string): Boolean;
  5879. var
  5880. Stream: TFileStream;
  5881. begin
  5882. Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  5883. try
  5884. Result := LoadFromGraphic(Stream);
  5885. finally
  5886. Stream.Free;
  5887. end;
  5888. end;
  5889. procedure TExifData.LoadFromStream(Stream: TStream);
  5890. begin
  5891. Clear(False);
  5892. AddFromStream(Stream);
  5893. end;
  5894. procedure TExifData.RemoveMakerNote;
  5895. begin
  5896. Sections[esDetails].Remove(ttMakerNote);
  5897. end;
  5898. procedure TExifData.RemovePaddingTags;
  5899. var
  5900. Section: TExifSection;
  5901. begin
  5902. for Section in Self do
  5903. Section.RemovePaddingTag;
  5904. end;
  5905. procedure TExifData.DoSaveToJPEG(InStream, OutStream: TStream);
  5906. var
  5907. SavedPos: Int64;
  5908. Segment: IFoundJPEGSegment;
  5909. SOFData: PJPEGStartOfFrameData;
  5910. Tag: TExifTag;
  5911. begin
  5912. for Tag in Sections[esDetails] do
  5913. case Tag.ID of
  5914. ttExifImageWidth, ttExifImageHeight:
  5915. begin
  5916. SavedPos := InStream.Position;
  5917. for Segment in JPEGHeader(InStream, TJPEGSegment.StartOfFrameMarkers) do
  5918. if Segment.Data.Size >= SizeOf(TJPEGStartOfFrameData) then
  5919. begin
  5920. SOFData := Segment.Data.Memory;
  5921. ExifImageWidth := SOFData.ImageWidth;
  5922. ExifImageHeight := SOFData.ImageHeight;
  5923. Break;
  5924. end;
  5925. InStream.Position := SavedPos;
  5926. Break;
  5927. end;
  5928. end;
  5929. UpdateApp1JPEGSegments(InStream, OutStream, Self, XMPPacket); //!!!IPTC (also TJPEGImageEx)
  5930. end;
  5931. procedure TExifData.DoSaveToPSD(InStream, OutStream: TStream);
  5932. var
  5933. Block: IAdobeResBlock;
  5934. Info: TPSDInfo;
  5935. NewBlocks: IInterfaceList;
  5936. StartPos: Int64;
  5937. begin
  5938. StartPos := InStream.Position;
  5939. NewBlocks := TInterfaceList.Create;
  5940. if not EmbeddedIPTC.Empty then
  5941. NewBlocks.Add(CreateAdobeBlock(TAdobeResBlock.IPTCTypeID, EmbeddedIPTC));
  5942. if not XMPPacket.Empty then
  5943. NewBlocks.Add(CreateAdobeBlock(TAdobeResBlock.XMPTypeID, XMPPacket));
  5944. for Block in ParsePSDHeader(InStream, Info) do
  5945. if not Block.IsExifBlock and not Block.IsXMPBlock then NewBlocks.Add(Block);
  5946. Sections[esGeneral].Remove([ttXMP, ttIPTC]); //!!!
  5947. if not Empty then
  5948. NewBlocks.Insert(0, CreateAdobeBlock(TAdobeResBlock.ExifTypeID, Self));
  5949. WritePSDHeader(OutStream, Info.Header);
  5950. WritePSDResourceSection(OutStream, NewBlocks);
  5951. InStream.Position := StartPos + Info.LayersSectionOffset;
  5952. OutStream.CopyFrom(InStream, InStream.Size - InStream.Position);
  5953. end;
  5954. procedure TExifData.AddNewTags(Rewriter: TTiffDirectoryRewriter);
  5955. begin
  5956. if Sections[esDetails].Count <> 0 then
  5957. Rewriter.AddSubDirectory(ttExifOffset, Sections[esDetails]); //!!! TExifSectionEx to implement the callback intf
  5958. if Sections[esGPS].Count <> 0 then
  5959. Rewriter.AddSubDirectory(ttGPSOffset, Sections[esGPS]);
  5960. end;
  5961. procedure TExifData.RewritingOldTag(const Source: ITiffDirectory; TagID: TTiffTagID;
  5962. DataType: TTiffDataType; var Rewrite: Boolean);
  5963. begin
  5964. if IsKnownExifTagInMainIFD(TagID, DataType) then Rewrite := False;
  5965. end;
  5966. procedure TExifData.DoSaveToTIFF(InStream, OutStream: TStream);
  5967. begin
  5968. RewriteTiff(InStream, OutStream, Self);
  5969. end;
  5970. procedure TExifData.GetGraphicSaveMethod(Stream: TStream; var Method: TGraphicSaveMethod);
  5971. begin
  5972. if HasJPEGHeader(Stream) then
  5973. Method := DoSaveToJPEG
  5974. else if HasPSDHeader(Stream) then
  5975. Method := DoSaveToPSD
  5976. else if HasTiffHeader(Stream) then
  5977. Method := DoSaveToTIFF
  5978. end;
  5979. procedure TExifData.SaveToGraphic(const FileName: string);
  5980. begin
  5981. DoSaveToGraphic(FileName, GetGraphicSaveMethod);
  5982. end;
  5983. procedure TExifData.SaveToGraphic(const Graphic: IStreamPersist);
  5984. begin
  5985. DoSaveToGraphic(Graphic, GetGraphicSaveMethod);
  5986. end;
  5987. procedure TExifData.SaveToGraphic(const InMemoryGraphic: TCustomMemoryStream);
  5988. begin
  5989. InMemoryGraphic.Position := 0;
  5990. DoSaveToGraphic(InMemoryGraphic, GetGraphicSaveMethod);
  5991. end;
  5992. type
  5993. TSectionSavingInfo = record
  5994. StartOffset, DirectorySize, OffsettedDataSize: Int64;
  5995. function EndOffsetPlus1: LongWord; {$IFDEF CanInline}inline;{$ENDIF}
  5996. end;
  5997. function TSectionSavingInfo.EndOffsetPlus1: LongWord;
  5998. begin
  5999. Result := StartOffset + DirectorySize + OffsettedDataSize;
  6000. end;
  6001. { TExifData.SaveToStream: sections are written out in TExifSection order, with a
  6002. section's offsetted data immediately following its tag directory. If a MakerNote tag
  6003. exists, then sections are saved around that tag's data.}
  6004. procedure TExifData.SaveToStream(Stream: TStream);
  6005. type
  6006. TOffsetSectionKind = esDetails..esThumbnail;
  6007. const
  6008. OffsetSectionKinds = [Low(TOffsetSectionKind)..High(TOffsetSectionKind)];
  6009. var
  6010. BaseStreamPos: Int64;
  6011. MakerNoteTag, MakerNoteOffsetTag: TExifTag;
  6012. MakerNoteDataOffset: Int64;
  6013. PreserveMakerNotePos: Boolean;
  6014. OffsetTags: array[TOffsetSectionKind] of TExifTag;
  6015. SavingInfo: array[TExifSectionKind] of TSectionSavingInfo;
  6016. procedure InitSavingInfo(Kind: TExifSectionKind;
  6017. const OffsettedSections: array of TOffsetSectionKind);
  6018. const
  6019. OffsetTagIDs: array[TOffsetSectionKind] of TExifTagID = (ttExifOffset,
  6020. ttInteropOffset, ttGPSOffset, ttThumbnailOffset);
  6021. var
  6022. Client: TOffsetSectionKind;
  6023. Tag: TExifTag;
  6024. begin
  6025. for Client in OffsettedSections do
  6026. if (SavingInfo[Client].DirectorySize > 0) or (Client = Kind){as for thumbnail} then
  6027. OffsetTags[Client] := Sections[Kind].AddOrUpdate(OffsetTagIDs[Client],
  6028. tdLongWord, 1, PLongWord(nil)^)
  6029. else
  6030. Sections[Kind].Remove(OffsetTagIDs[Client]);
  6031. if (Kind <> esGeneral) and (Sections[Kind].Count = 0) then Exit; //don't write out empty sections
  6032. for Tag in Sections[Kind] do
  6033. if Tag.DataSize > 4 then Inc(SavingInfo[Kind].OffsettedDataSize, Tag.DataSize);
  6034. SavingInfo[Kind].DirectorySize := 2 + (TTiffTag.HeaderSize * Sections[Kind].Count) + 4; //length + tag recs + pos of next IFD
  6035. end;
  6036. procedure WriteDirectory(Kind: TExifSectionKind);
  6037. var
  6038. NextDataOffset: LongWord;
  6039. Tag: TExifTag;
  6040. begin
  6041. if SavingInfo[Kind].DirectorySize = 0 then Exit;
  6042. Stream.Position := BaseStreamPos + SavingInfo[Kind].StartOffset;
  6043. Stream.WriteWord(Sections[Kind].Count, Endianness);
  6044. NextDataOffset := SavingInfo[Kind].StartOffset + SavingInfo[Kind].DirectorySize;
  6045. for Tag in Sections[Kind] do
  6046. if (Tag = MakerNoteTag) and PreserveMakerNotePos then
  6047. Tag.WriteHeader(Stream, Endianness, MakerNoteDataOffset)
  6048. else
  6049. begin
  6050. Tag.WriteHeader(Stream, Endianness, NextDataOffset);
  6051. if Tag.DataSize > 4 then
  6052. begin
  6053. if (Tag = MakerNoteTag) and (MakerNoteOffsetTag <> nil) then
  6054. Inc(PLongInt(MakerNoteOffsetTag.Data)^, NextDataOffset - Tag.OriginalDataOffset);
  6055. Inc(NextDataOffset, Tag.DataSize);
  6056. end;
  6057. end;
  6058. if (Kind = esGeneral) and (SavingInfo[esThumbnail].DirectorySize <> 0) then
  6059. Stream.WriteLongWord(SavingInfo[esThumbnail].StartOffset, Endianness)
  6060. else
  6061. Stream.WriteLongWord(0, Endianness);
  6062. for Tag in Sections[Kind] do
  6063. if (Tag <> MakerNoteTag) or not PreserveMakerNotePos then
  6064. Tag.WriteOffsettedData(Stream, Endianness);
  6065. end;
  6066. var
  6067. Kind: TExifSectionKind;
  6068. ThumbnailImageStream: TMemoryStream;
  6069. begin
  6070. if RemovePaddingTagsOnSave then RemovePaddingTags;
  6071. BaseStreamPos := Stream.Position;
  6072. FillChar(OffsetTags, SizeOf(OffsetTags), 0);
  6073. FillChar(SavingInfo, SizeOf(SavingInfo), 0);
  6074. { initialise saving of the easy sections first }
  6075. for Kind in [esInterop, esGPS] do
  6076. InitSavingInfo(Kind, []);
  6077. { initialise saving the details section, including maker note positioning }
  6078. with Sections[esDetails] do
  6079. begin
  6080. MakerNoteOffsetTag := nil;
  6081. PreserveMakerNotePos := Find(ttMakerNote, MakerNoteTag) and
  6082. (MakerNoteTag.OriginalDataOffset <> 0) and (MakerNoteTag.DataSize > 4); //if size is 4 or less, then data isn't offsetted
  6083. if PreserveMakerNotePos then //if Windows has moved it, put the maker note back, else keep it where it is
  6084. begin
  6085. MakerNoteDataOffset := MakerNoteTag.OriginalDataOffset;
  6086. if Find(ttOffsetSchema, MakerNoteOffsetTag) and
  6087. (MakerNoteOffsetTag.DataType = tdLongInt) and (MakerNoteOffsetTag.ElementCount = 1) and
  6088. (MakerNoteDataOffset - PLongInt(MakerNoteOffsetTag.Data)^ > 0) then
  6089. begin
  6090. Dec(MakerNoteDataOffset, PLongInt(MakerNoteOffsetTag.Data)^);
  6091. PLongInt(MakerNoteOffsetTag.Data)^ := 0;
  6092. end
  6093. else
  6094. MakerNoteOffsetTag := nil;
  6095. end;
  6096. end;
  6097. InitSavingInfo(esDetails, [esInterop]);
  6098. if PreserveMakerNotePos then
  6099. Dec(SavingInfo[esDetails].OffsettedDataSize, MakerNoteTag.DataSize);
  6100. { initialise saving the thumbnail section }
  6101. ThumbnailImageStream := nil;
  6102. try
  6103. if HasThumbnail then
  6104. begin
  6105. ThumbnailImageStream := TMemoryStream.Create;
  6106. Thumbnail.SaveToStream(ThumbnailImageStream);
  6107. ThumbnailImageStream.Position := 0;
  6108. ThumbnailImageStream.Size := GetJPEGDataSize(ThumbnailImageStream);
  6109. if ThumbnailImageStream.Size > MaxThumbnailSize then
  6110. begin
  6111. ThumbnailImageStream.Clear;
  6112. {$IF DECLARED(StandardizeThumbnail)}
  6113. StandardizeThumbnail;
  6114. {$IFEND}
  6115. {$IFDEF VCL}
  6116. if Thumbnail.CompressionQuality > 90 then
  6117. Thumbnail.CompressionQuality := 90;
  6118. {$ENDIF}
  6119. Thumbnail.SaveToStream(ThumbnailImageStream);
  6120. Assert(ThumbnailImageStream.Size <= MaxThumbnailSize);
  6121. end;
  6122. with Sections[esThumbnail] do
  6123. begin
  6124. SetWordValue(ttCompression, 0, 6);
  6125. SetLongWordValue(ttThumbnailSize, 0, ThumbnailImageStream.Size);
  6126. end;
  6127. InitSavingInfo(esThumbnail, [esThumbnail]);
  6128. Inc(SavingInfo[esThumbnail].OffsettedDataSize, ThumbnailImageStream.Size);
  6129. end;
  6130. { initialise saving of the general section }
  6131. InitSavingInfo(esGeneral, [esDetails, esGPS]);
  6132. { calculate section positions }
  6133. for Kind := Low(TExifSectionKind) to High(TExifSectionKind) do
  6134. begin
  6135. if Kind = esGeneral then
  6136. SavingInfo[esGeneral].StartOffset := 8
  6137. else
  6138. SavingInfo[Kind].StartOffset := SavingInfo[Pred(Kind)].EndOffsetPlus1;
  6139. if PreserveMakerNotePos and (SavingInfo[Kind].EndOffsetPlus1 > MakerNoteDataOffset) and
  6140. (SavingInfo[Kind].StartOffset < MakerNoteDataOffset + MakerNoteTag.OriginalDataSize) then
  6141. SavingInfo[Kind].StartOffset := MakerNoteDataOffset + MakerNoteTag.OriginalDataSize;
  6142. if (Kind in OffsetSectionKinds) and (OffsetTags[Kind] <> nil) then
  6143. if Kind = esThumbnail then
  6144. PLongWord(OffsetTags[Kind].Data)^ := SavingInfo[Kind].EndOffsetPlus1 - ThumbnailImageStream.Size
  6145. else
  6146. PLongWord(OffsetTags[Kind].Data)^ := SavingInfo[Kind].StartOffset;
  6147. end;
  6148. { let's do the actual writing }
  6149. WriteTiffHeader(Stream, Endianness);
  6150. for Kind := Low(TExifSectionKind) to High(TExifSectionKind) do
  6151. WriteDirectory(Kind);
  6152. if ThumbnailImageStream <> nil then
  6153. Stream.WriteBuffer(ThumbnailImageStream.Memory^, ThumbnailImageStream.Size);
  6154. if PreserveMakerNotePos then
  6155. begin
  6156. Stream.Position := BaseStreamPos + MakerNoteDataOffset;
  6157. Stream.WriteBuffer(MakerNoteTag.Data^, MakerNoteTag.DataSize);
  6158. end;
  6159. finally
  6160. ThumbnailImageStream.Free;
  6161. end;
  6162. end;
  6163. class function TExifData.SectionClass: TExifSectionClass;
  6164. begin
  6165. Result := TExtendableExifSection;
  6166. end;
  6167. {$IFDEF VCL}
  6168. { TJPEGImageEx }
  6169. type
  6170. TIPTCDataAccess = class(TIPTCData);
  6171. constructor TJPEGImageEx.Create;
  6172. begin
  6173. inherited;
  6174. FExifData := TExifData.Create;
  6175. FExifData.OnChange := Changed;
  6176. FIPTCData := TIPTCData.Create;
  6177. end;
  6178. destructor TJPEGImageEx.Destroy;
  6179. begin
  6180. FIPTCData.Free;
  6181. FExifData.Free;
  6182. inherited;
  6183. end;
  6184. procedure TJPEGImageEx.Assign(Source: TPersistent);
  6185. begin
  6186. inherited;
  6187. if not (Source is TBitmap) then
  6188. ReloadTags
  6189. else
  6190. begin //don't cause a compress operation
  6191. FExifData.Clear;
  6192. FIPTCData.Clear;
  6193. end;
  6194. FChangedSinceLastLoad := False;
  6195. end;
  6196. procedure TJPEGImageEx.Assign(Source: TBitmap; Options: TAssignOptions);
  6197. var
  6198. I: Integer;
  6199. SavedSegments: TInterfaceList;
  6200. Segment: IFoundJPEGSegment;
  6201. InStream, OutStream: TMemoryStream;
  6202. begin
  6203. if not (jaPreserveMetadata in Options) then
  6204. begin
  6205. Assign(Source);
  6206. Exit;
  6207. end;
  6208. SavedSegments := nil;
  6209. OutStream := nil;
  6210. InStream := TMemoryStream.Create;
  6211. try
  6212. SaveToStream(InStream);
  6213. InStream.Position := 0;
  6214. SavedSegments := TInterfaceList.Create;
  6215. for Segment in JPEGHeader(InStream, [jmApp1..jmAppSpecificLast]) do
  6216. SavedSegments.Add(Segment);
  6217. InStream.Clear;
  6218. inherited Assign(Source);
  6219. inherited SaveToStream(InStream);
  6220. InStream.Position := 0;
  6221. OutStream := TMemoryStream.Create;
  6222. for Segment in JPEGHeader(InStream, [jmJFIF]) do
  6223. begin
  6224. OutStream.WriteBuffer(InStream.Memory^, Segment.Offset + Segment.TotalSize);
  6225. for I := 0 to SavedSegments.Count - 1 do
  6226. WriteJPEGSegment(OutStream, SavedSegments[I] as IJPEGSegment);
  6227. OutStream.CopyFrom(InStream, InStream.Size - InStream.Position);
  6228. OutStream.Position := 0;
  6229. LoadFromStream(OutStream);
  6230. Exit;
  6231. end;
  6232. Assert(False, 'No JFIF segment!'); //needs to be handled properly if change Source to TPersistent
  6233. //InStream.Position := SizeOf(JPEGFileHeader);
  6234. finally
  6235. SavedSegments.Free;
  6236. InStream.Free;
  6237. OutStream.Free;
  6238. end;
  6239. end;
  6240. procedure TJPEGImageEx.Changed(Sender: TObject);
  6241. begin
  6242. FChangedSinceLastLoad := True;
  6243. inherited;
  6244. end;
  6245. procedure TJPEGImageEx.CreateThumbnail(ThumbnailWidth, ThumbnailHeight: Integer);
  6246. begin
  6247. if Empty then
  6248. FExifData.Thumbnail := nil
  6249. else
  6250. CreateExifThumbnail(Self, FExifData.Thumbnail, ThumbnailWidth, ThumbnailHeight);
  6251. end;
  6252. procedure TJPEGImageEx.CreateThumbnail;
  6253. begin
  6254. CreateThumbnail(StandardExifThumbnailWidth, StandardExifThumbnailHeight);
  6255. end;
  6256. function TJPEGImageEx.Segments(MarkersToLookFor: TJPEGMarkers): IJPEGHeaderParser;
  6257. begin
  6258. Result := JPEGHeader(Self, MarkersToLookFor);
  6259. end;
  6260. function TJPEGImageEx.GetXMPPacket: TXMPPacket;
  6261. begin
  6262. Result := FExifData.XMPPacket;
  6263. end;
  6264. procedure TJPEGImageEx.LoadFromStream(Stream: TStream);
  6265. begin
  6266. inherited;
  6267. ReloadTags;
  6268. end;
  6269. procedure TJPEGImageEx.ReadData(Stream: TStream);
  6270. begin
  6271. inherited;
  6272. ReloadTags;
  6273. end;
  6274. procedure TJPEGImageEx.ReloadTags;
  6275. var
  6276. MemStream: TMemoryStream;
  6277. begin
  6278. if Empty then
  6279. begin
  6280. FExifData.Clear;
  6281. FIPTCData.Clear;
  6282. end
  6283. else
  6284. begin
  6285. MemStream := TMemoryStream.Create;
  6286. try
  6287. inherited SaveToStream(MemStream);
  6288. MemStream.Position := 0;
  6289. FExifData.LoadFromGraphic(MemStream);
  6290. MemStream.Position := 0;
  6291. FIPTCData.LoadFromGraphic(MemStream);
  6292. finally
  6293. MemStream.Free;
  6294. end;
  6295. end;
  6296. FChangedSinceLastLoad := False;
  6297. end;
  6298. function TJPEGImageEx.RemoveMetadata(Kinds: TJPEGMetadataKinds): TJPEGMetadataKinds;
  6299. begin
  6300. Result := RemoveMetadataFromJPEG(Self, Kinds);
  6301. end;
  6302. function TJPEGImageEx.RemoveSegments(Markers: TJPEGMarkers): TJPEGMarkers;
  6303. begin
  6304. Result := RemoveJPEGSegments(Self, Markers);
  6305. end;
  6306. procedure TJPEGImageEx.SaveToStream(Stream: TStream);
  6307. var
  6308. MemStream1, MemStream2: TMemoryStream;
  6309. begin
  6310. if not FChangedSinceLastLoad or Empty then
  6311. begin
  6312. inherited;
  6313. Exit;
  6314. end;
  6315. MemStream1 := TMemoryStream.Create;
  6316. MemStream2 := TMemoryStream.Create;
  6317. try
  6318. inherited SaveToStream(MemStream1);
  6319. MemStream1.Position := 0;
  6320. FExifData.OnChange := nil; //the ExifImageWidth/Height properties may be updated when saving
  6321. FExifData.DoSaveToJPEG(MemStream1, MemStream2);
  6322. MemStream2.Position := 0;
  6323. TIPTCDataAccess(FIPTCData).DoSaveToJPEG(MemStream2, Stream);
  6324. finally
  6325. FExifData.OnChange := Changed;
  6326. MemStream1.Free;
  6327. MemStream2.Free;
  6328. end;
  6329. end;
  6330. {$ENDIF}
  6331. { TExifMakerNote }
  6332. constructor TExifMakerNote.Create(ASection: TExifSection);
  6333. var
  6334. BasePosition: Int64;
  6335. HeaderSize: Integer;
  6336. InternalOffset: Int64;
  6337. SourceTag: TExifTag;
  6338. Stream: TUserMemoryStream;
  6339. begin
  6340. inherited Create;
  6341. FTags := ASection;
  6342. if ClassType = TUnrecognizedMakerNote then Exit;
  6343. if not ASection.Owner[esDetails].Find(ttMakerNote, SourceTag) or not FormatIsOK(SourceTag,
  6344. HeaderSize) then raise EInvalidMakerNoteFormat.CreateRes(@SInvalidMakerNoteFormat);
  6345. FDataOffsetsType := doFromExifStart;
  6346. FEndianness := Tags.Owner.Endianness;
  6347. GetIFDInfo(SourceTag, FEndianness, FDataOffsetsType);
  6348. case FDataOffsetsType of
  6349. doCustomFormat: Exit;
  6350. doFromExifStart: BasePosition := -SourceTag.OriginalDataOffset;
  6351. doFromIFDStart: BasePosition := HeaderSize;
  6352. doFromMakerNoteStart: BasePosition := 0;
  6353. else
  6354. raise EProgrammerNotFound.CreateRes(@SRangeError);
  6355. end;
  6356. if FDataOffsetsType = doFromIFDStart then
  6357. InternalOffset := -8
  6358. else
  6359. InternalOffset := Tags.Owner.OffsetSchema;
  6360. Stream := TUserMemoryStream.Create(SourceTag.Data, SourceTag.DataSize);
  6361. try
  6362. Tags.Load(ParseTiffDirectory(Stream, FEndianness, BasePosition,
  6363. HeaderSize - BasePosition, InternalOffset), False);
  6364. { When edited in Vista's Explorer, Exif data are *always* re-written in big endian
  6365. format. Since MakerNotes are left 'as is', however, this means a parser can't rely
  6366. on the container's endianness to determine the endianness of the MakerNote. So, if
  6367. we get tag header load errors with the endianness suggested by GetIFDInfo, we'll
  6368. try the other one too. }
  6369. if (Tags.Count = 0) or ((Tags.LoadErrors <> []) and
  6370. not (leBadOffset in Tags.LoadErrors) and (Tags.Count < 3)) then
  6371. begin
  6372. if FEndianness = SmallEndian then
  6373. FEndianness := BigEndian
  6374. else
  6375. FEndianness := SmallEndian;
  6376. Tags.Load(ParseTiffDirectory(Stream, FEndianness, BasePosition,
  6377. HeaderSize - BasePosition, InternalOffset), False);
  6378. if Tags.LoadErrors <> [] then Tags.Clear;
  6379. if Tags.Count = 0 then Tags.LoadErrors := [leBadOffset];
  6380. end;
  6381. finally
  6382. Stream.Free;
  6383. end;
  6384. end;
  6385. class function TExifMakerNote.FormatIsOK(SourceTag: TExifTag): Boolean;
  6386. var
  6387. HeaderSize: Integer;
  6388. begin
  6389. Result := (SourceTag.DataType = tdUndefined) and (SourceTag.ElementCount >= 2) and
  6390. FormatIsOK(SourceTag, HeaderSize);
  6391. end;
  6392. procedure TExifMakerNote.GetIFDInfo(SourceTag: TExifTag;
  6393. var ProbableEndianness: TEndianness; var DataOffsetsType: TExifDataOffsetsType);
  6394. begin
  6395. end;
  6396. function TExifMakerNote.GetFractionValue(TagID: Integer): TExifFraction;
  6397. begin
  6398. if (TagID >= Low(TTiffTagID)) and (TagID <= Low(TTiffTagID)) then
  6399. Result := Tags.GetFractionValue(TTiffTagID(TagID), 0, NullFraction)
  6400. else
  6401. Result := NullFraction;
  6402. end;
  6403. function TExifMakerNote.GetTagAsString(TagID: Integer): string;
  6404. var
  6405. Tag: TExifTag;
  6406. begin
  6407. if (TagID >= Low(TTiffTagID)) and (TagID <= High(TTiffTagID)) and Tags.Find(TagID, Tag) then
  6408. Result := Tag.AsString
  6409. else
  6410. Result := '';
  6411. end;
  6412. { TUnrecognizedMakerNote }
  6413. class function TUnrecognizedMakerNote.FormatIsOK(SourceTag: TExifTag; out HeaderSize: Integer): Boolean;
  6414. begin
  6415. Result := False;
  6416. end;
  6417. { THeaderlessMakerNote }
  6418. class function THeaderlessMakerNote.FormatIsOK(SourceTag: TExifTag; out HeaderSize: Integer): Boolean;
  6419. begin
  6420. HeaderSize := 0;
  6421. Result := True;
  6422. end;
  6423. { TAppleMakerNote }
  6424. class function TAppleMakerNote.FormatIsOK(SourceTag: TExifTag; out HeaderSize: Integer): Boolean;
  6425. begin
  6426. HeaderSize := SizeOf(Header);
  6427. Result := (SourceTag.ElementCount > HeaderSize) and
  6428. CompareMem(SourceTag.Data, @Header, HeaderSize);
  6429. end;
  6430. procedure TAppleMakerNote.GetIFDInfo(SourceTag: TExifTag; var ProbableEndianness: TEndianness;
  6431. var DataOffsetsType: TExifDataOffsetsType);
  6432. begin
  6433. DataOffsetsType := doFromExifStart;
  6434. end;
  6435. { TCanonMakerNote }
  6436. class function TCanonMakerNote.FormatIsOK(SourceTag: TExifTag; out HeaderSize: Integer): Boolean;
  6437. begin
  6438. HeaderSize := 0;
  6439. Result := (SourceTag.Section.Owner.CameraMake = 'Canon'); //as no header, we'll look for something else
  6440. end;
  6441. procedure TCanonMakerNote.GetIFDInfo(SourceTag: TExifTag;
  6442. var ProbableEndianness: TEndianness; var DataOffsetsType: TExifDataOffsetsType);
  6443. begin
  6444. ProbableEndianness := SmallEndian;
  6445. end;
  6446. { TCasioMakerNote }
  6447. class function TCasioMakerNote.FormatIsOK(SourceTag: TExifTag; out HeaderSize: Integer): Boolean;
  6448. begin
  6449. HeaderSize := 0;
  6450. Result := (SourceTag.Section.Owner.CameraMake = 'CASIO');
  6451. end;
  6452. { TCasio2MakerNote }
  6453. class function TCasio2MakerNote.FormatIsOK(SourceTag: TExifTag; out HeaderSize: Integer): Boolean;
  6454. begin
  6455. HeaderSize := SizeOf(Header);
  6456. Result := (SourceTag.ElementCount > HeaderSize) and
  6457. CompareMem(SourceTag.Data, @Header, HeaderSize);
  6458. end;
  6459. { TKodakMakerNote }
  6460. class function TKodakMakerNote.FormatIsOK(SourceTag: TExifTag;
  6461. out HeaderSize: Integer): Boolean;
  6462. const
  6463. MinHeader: array[0..2] of AnsiChar = 'KDK';
  6464. begin
  6465. HeaderSize := TKodakMakerNote.HeaderSize;
  6466. Result := (SourceTag.ElementCount > HeaderSize) and
  6467. CompareMem(SourceTag.Data, @MinHeader, SizeOf(MinHeader));
  6468. end;
  6469. procedure TKodakMakerNote.GetIFDInfo(SourceTag: TExifTag;
  6470. var Endianness: TEndianness; var DataOffsetsType: TExifDataOffsetsType);
  6471. var
  6472. Buffer: array[Byte] of Byte;
  6473. I: TTiffTagID;
  6474. begin
  6475. DataOffsetsType := doCustomFormat;
  6476. if CompareMem(SourceTag.Data, @BigEndianHeader, HeaderSize) then
  6477. Endianness := BigEndian
  6478. else
  6479. Endianness := SmallEndian;
  6480. Tags.Clear;
  6481. SourceTag.DataStream.Position := HeaderSize;
  6482. InitializeTagSpecs;
  6483. for I := Low(TagSpecs) to High(TagSpecs) do
  6484. if SourceTag.DataStream.TryReadBuffer(Buffer,
  6485. TiffElementSizes[TagSpecs[I].DataType] * TagSpecs[I].ElementCount) then
  6486. begin
  6487. Tags.Add(I, TagSpecs[I].DataType, TagSpecs[I].ElementCount).UpdateData(Buffer);
  6488. //SourceTag.DataStream.Seek(TiffElementSizes[DataType] mod 4, soCurrent); //fields aligned on 4 byte boundaries
  6489. end
  6490. else
  6491. begin
  6492. Tags.LoadErrors := [leBadOffset];
  6493. Exit;
  6494. end;
  6495. end;
  6496. constructor TKodakMakerNote.TTagSpec.Create(ADataType: TTiffDataType; AElementCount: Byte);
  6497. begin
  6498. DataType := ADataType;
  6499. ElementCount := AElementCount;
  6500. end;
  6501. class procedure TKodakMakerNote.InitializeTagSpecs;
  6502. begin
  6503. if TagSpecs <> nil then Exit;
  6504. SetLength(TagSpecs, 6);
  6505. TagSpecs[0] := TTagSpec.Create(tdAscii, 8);//model
  6506. TagSpecs[1] := TTagSpec.Create(tdByte); //quality
  6507. TagSpecs[2] := TTagSpec.Create(tdByte, 2); //burst mode + 1 byte padding
  6508. TagSpecs[3] := TTagSpec.Create(tdWord); //width
  6509. TagSpecs[4] := TTagSpec.Create(tdWord); //height
  6510. TagSpecs[5] := TTagSpec.Create(tdWord); //year
  6511. end;
  6512. { TKonicaMinoltaMakerNote }
  6513. class function TKonicaMinoltaMakerNote.FormatIsOK(SourceTag: TExifTag;
  6514. out HeaderSize: Integer): Boolean;
  6515. begin
  6516. HeaderSize := 0;
  6517. Result := (SourceTag.Section.Owner.CameraMake = 'KONICA MINOLTA');
  6518. end;
  6519. { TPanasonicMakerNote }
  6520. class function TPanasonicMakerNote.FormatIsOK(SourceTag: TExifTag;
  6521. out HeaderSize: Integer): Boolean;
  6522. begin
  6523. HeaderSize := SizeOf(Header);
  6524. Result := (SourceTag.ElementCount > HeaderSize) and
  6525. CompareMem(SourceTag.Data, @Header, HeaderSize);
  6526. end;
  6527. procedure TPanasonicMakerNote.GetIFDInfo(SourceTag: TExifTag;
  6528. var ProbableEndianness: TEndianness; var DataOffsetsType: TExifDataOffsetsType);
  6529. begin
  6530. ProbableEndianness := SmallEndian;
  6531. end;
  6532. { TPentaxMakerNote }
  6533. class function TPentaxMakerNote.FormatIsOK(SourceTag: TExifTag;
  6534. out HeaderSize: Integer): Boolean;
  6535. begin
  6536. HeaderSize := SizeOf(Header);
  6537. Result := (SourceTag.ElementCount > HeaderSize) and
  6538. CompareMem(SourceTag.Data, @Header, HeaderSize);
  6539. end;
  6540. { TNikonType1MakerNote }
  6541. class function TNikonType1MakerNote.FormatIsOK(SourceTag: TExifTag;
  6542. out HeaderSize: Integer): Boolean;
  6543. begin
  6544. HeaderSize := SizeOf(Header);
  6545. Result := (SourceTag.ElementCount > HeaderSize) and
  6546. CompareMem(SourceTag.Data, @Header, HeaderSize);
  6547. end;
  6548. { TNikonType2MakerNote }
  6549. class function TNikonType2MakerNote.FormatIsOK(SourceTag: TExifTag;
  6550. out HeaderSize: Integer): Boolean;
  6551. begin
  6552. HeaderSize := 0;
  6553. Result := StartsStr('NIKON', SourceTag.Section.Owner.CameraMake); //can be NIKON or NIKON CORPORATION
  6554. end;
  6555. { TNikonType3MakerNote }
  6556. class function TNikonType3MakerNote.FormatIsOK(SourceTag: TExifTag;
  6557. out HeaderSize: Integer): Boolean;
  6558. begin
  6559. HeaderSize := 18;
  6560. Result := (SourceTag.ElementCount > HeaderSize) and
  6561. CompareMem(SourceTag.Data, @HeaderStart, SizeOf(HeaderStart));
  6562. end;
  6563. procedure TNikonType3MakerNote.GetIFDInfo(SourceTag: TExifTag;
  6564. var ProbableEndianness: TEndianness; var DataOffsetsType: TExifDataOffsetsType);
  6565. var
  6566. SeekPtr: PAnsiChar;
  6567. begin
  6568. SeekPtr := SourceTag.Data;
  6569. if (SeekPtr[10] = 'M') and (SeekPtr[11] = 'M') then
  6570. ProbableEndianness := BigEndian
  6571. else
  6572. ProbableEndianness := SmallEndian;
  6573. DataOffsetsType := doFromIFDStart;
  6574. end;
  6575. { TSonyMakerNote }
  6576. class function TSonyMakerNote.FormatIsOK(SourceTag: TExifTag;
  6577. out HeaderSize: Integer): Boolean;
  6578. begin
  6579. HeaderSize := 12;
  6580. Result := (SourceTag.ElementCount > HeaderSize) and
  6581. CompareMem(SourceTag.Data, @Header, SizeOf(Header));
  6582. end;
  6583. procedure TSonyMakerNote.GetIFDInfo(SourceTag: TExifTag;
  6584. var ProbableEndianness: TEndianness; var DataOffsetsType: TExifDataOffsetsType);
  6585. begin
  6586. if SourceTag.Section.Owner.CameraModel = 'DSLR-A100' then
  6587. ProbableEndianness := BigEndian
  6588. else
  6589. ProbableEndianness := SmallEndian;
  6590. end;
  6591. {$IFDEF DummyTJpegImage}
  6592. constructor TJPEGImage.Create;
  6593. begin
  6594. inherited Create;
  6595. FData := TMemoryStream.Create;
  6596. end;
  6597. destructor TJPEGImage.Destroy;
  6598. begin
  6599. FData.Free;
  6600. inherited Destroy;
  6601. end;
  6602. procedure TJPEGImage.Assign(Source: TPersistent);
  6603. var
  6604. SourceIntf: IStreamPersist;
  6605. begin
  6606. if Source = nil then
  6607. begin
  6608. if FData.Size = 0 then Exit;
  6609. FData.Clear;
  6610. Changed;
  6611. Exit;
  6612. end;
  6613. if Supports(Source, IStreamPersist, SourceIntf) then
  6614. begin
  6615. FData.Clear;
  6616. SourceIntf.SaveToStream(FData);
  6617. Changed;
  6618. Exit;
  6619. end;
  6620. inherited;
  6621. end;
  6622. procedure TJPEGImage.AssignTo(Dest: TPersistent);
  6623. var
  6624. DestIntf: IStreamPersist;
  6625. TempStream: TMemoryStream;
  6626. begin
  6627. if Supports(Dest, IStreamPersist, DestIntf) then
  6628. begin
  6629. TempStream := TMemoryStream.Create;
  6630. try
  6631. SaveToStream(TempStream);
  6632. TempStream.Position := 0;
  6633. DestIntf.LoadFromStream(TempStream);
  6634. finally
  6635. TempStream.Free;
  6636. end;
  6637. Exit;
  6638. end;
  6639. inherited;
  6640. end;
  6641. procedure TJPEGImage.Changed;
  6642. begin
  6643. if Assigned(FOnChange) then FOnChange(Self);
  6644. end;
  6645. function TJPEGImage.GetEmpty: Boolean;
  6646. begin
  6647. Result := (FData.Size = 0);
  6648. end;
  6649. function TJPEGImage.GetWidth: Integer;
  6650. begin
  6651. SizeFieldsNeeded;
  6652. Result := FWidth;
  6653. end;
  6654. function TJPEGImage.GetHeight: Integer;
  6655. begin
  6656. SizeFieldsNeeded;
  6657. Result := FHeight;
  6658. end;
  6659. procedure TJPEGImage.LoadFromStream(Stream: TStream);
  6660. var
  6661. JpegSize: Int64;
  6662. begin
  6663. JpegSize := GetJPEGDataSize(Stream);
  6664. if (JpegSize = 0) and (FData.Size = 0) then Exit;
  6665. FWidth := 0;
  6666. FData.Size := JpegSize;
  6667. Stream.ReadBuffer(FData.Memory^, JpegSize);
  6668. Changed;
  6669. end;
  6670. procedure TJPEGImage.SaveToStream(Stream: TStream);
  6671. begin
  6672. if FData.Size <> 0 then
  6673. begin
  6674. Stream.WriteBuffer(FData.Memory^, FData.Size);
  6675. Exit;
  6676. end;
  6677. WriteJPEGFileHeader(Stream);
  6678. Stream.WriteByte(jmEndOfImage);
  6679. end;
  6680. procedure TJPEGImage.SizeFieldsNeeded;
  6681. var
  6682. Header: PJPEGStartOfFrameData;
  6683. Segment: IFoundJPEGSegment;
  6684. begin
  6685. if (FWidth <> 0) or (FData.Size = 0) then Exit;
  6686. FData.Position := 0;
  6687. for Segment in JPEGHeader(FData, TJPEGSegment.StartOfFrameMarkers) do
  6688. if Segment.Data.Size > SizeOf(TJPEGStartOfFrameData) then
  6689. begin
  6690. Header := Segment.Data.Memory;
  6691. FWidth := Header.ImageWidth;
  6692. FHeight := Header.ImageWidth;
  6693. Exit;
  6694. end;
  6695. end;
  6696. {$ENDIF}
  6697. initialization
  6698. TCustomExifData.InitializeClass([
  6699. TCasioMakerNote,
  6700. TKonicaMinoltaMakerNote,
  6701. TNikonType2MakerNote,
  6702. TCanonMakerNote,
  6703. TPentaxMakerNote,
  6704. //TKodakMakerNote,
  6705. TSonyMakerNote,
  6706. TNikonType1MakerNote,
  6707. TNikonType3MakerNote,
  6708. TPanasonicMakerNote,
  6709. TCasio2MakerNote,
  6710. TAppleMakerNote
  6711. ]);
  6712. finalization
  6713. TCustomExifData.FinalizeClass;
  6714. end.