unit fpeExifData; {$IFDEF FPC} {$mode objfpc}{$H+} {$ENDIF} {$I fpexif.inc} interface uses Classes, SysUtils, fpeGlobal, fpeTags; const // Constants for tag IDs used explicitly somewhere TAG_IMAGEWIDTH = $0100; TAG_IMAGELENGTH = $0101; TAG_IMAGEHEIGHT = $0101; TAG_COMPRESSION = $0103; TAG_MAKE = $010F; TAG_MODEL = $0110; TAG_THUMBSTARTOFFSET = $0201; TAG_THUMBSIZE = $0202; TAG_EXIFVERSION = $9000; TAG_FOCALLENGTH35MM = $A405; // Parent's ID tag's ID FULLTAG_IMAGEWIDTH = TAGPARENT_PRIMARY or TAG_IMAGEWIDTH; FULLTAG_IMAGELENGTH = TAGPARENT_PRIMARY or TAG_IMAGELENGTH; FULLTAG_COMPRESSION = TAGPARENT_PRIMARY or TAG_COMPRESSION; FULLTAG_MAKE = TAGPARENT_PRIMARY or TAG_MAKE; FULLTAG_MODEL = TAGPARENT_PRIMARY or TAG_MODEL; FULLTAG_THUMBSTARTOFFSET = TAGPARENT_THUMBNAIL or TAG_THUMBSTARTOFFSET; FULLTAG_THUMBSIZE = TAGPARENT_THUMBNAIL or TAG_THUMBSIZE; FULLTAG_THUMBCOMPRESSION = TAGPARENT_THUMBNAIL or TAG_COMPRESSION; FULLTAG_THUMBWIDTH = TAGPARENT_THUMBNAIL or TAG_IMAGEWIDTH; FULLTAG_THUMBHEIGHT = TAGPARENT_THUMBNAIL or TAG_IMAGEHEIGHT; FULLTAG_THUMBLENGTH = TAGPARENT_THUMBNAIL or TAG_IMAGELENGTH; FULLTAG_EXIFVERSION = TAGPARENT_EXIF or TAG_EXIFVERSION; FULLTAG_FOCALLENGTH35mm = TAGPARENT_EXIF or TAG_FOCALLENGTH35mm; type TExifBeginReadingEvent = procedure of object; TExifEndReadingEvent = procedure of object; { TExifData } TExifData = class private FTagList: TTagList; FBigEndian: Boolean; FThumbnailBuffer: TBytes; FReadFlag: Integer; FExportOptions: TExportOptions; FOnBeginReading: TExifBeginReadingEvent; FOnEndReading: TExifEndReadingEvent; function GetImgHeight: Integer; function GetImgWidth: Integer; function GetOrientation: TExifOrientation; function GetTagByID(ATagID: TTagID): TTag; function GetTagByIndex(AIndex: Integer): TTag; function GetTagByName(AFullTagName: String): TTag; function GetTagCount: Integer; procedure SetExportOptions(const AValue: TExportOptions); procedure SetTagByID(ATagID: TTagID; ATag: TTag); procedure SetTagByIndex(AIndex: Integer; ATag: TTag); procedure SetTagByName(AFullTagName: String; ATag: TTag); protected FTiffHeaderOffset: Int64; procedure CheckFocalLengthIn35mm; procedure DoBeginReading; procedure DoEndReading; function InternalAddTag(ATagDef: TTagDef): TTag; public constructor Create(ABigEndian: Boolean); destructor Destroy; override; function AddMakerNoteTag(AIndex: Integer; ATagID: TTagID; ATagName: String; ADataValue: Integer; ALkupTbl: String = ''; AFormatStr: String = ''; ATagType: TTagType = ttUInt16): Integer; overload; function AddMakerNoteTag(AIndex: Integer; ATagID: TTagID; ATagName: String; ADataValue: Double; AFormatStr: String = ''; ATagType: TTagType = ttURational): Integer; overload; function AddMakerNoteStringTag(AIndex: Integer; ATagID: TTagID; ATagName: String; AData: TBytes; ACount: Integer; ALkupTbl: String = ''): Integer; function AddOrReplaceTag(ATag: TTag): Integer; function AddTag(ATag: TTag): Integer; function AddTagByID(ATagID: TTagID): TTag; function AddTagByName(AFullTagName: String): TTag; procedure Clear; function ExportOptionsToTagOptions: TTagOptions; procedure ExportToStrings(AList: TStrings; ASeparator: String = '='; AGroup: TTagGroup = tgUnknown); function FindTagByID(ATagID: TTagID): TTag; function FindTagByName(AFullTagName: String): TTag; function GetParentTag(ATag: TTag): TTag; function HasTagsOfGroup(AGroup: TTagGroup): Boolean; function IndexOfTagID(ATagID: TTagID): Integer; function IndexOfTagName(AFullTagName: String): Integer; // Reading procedure BeginReading; procedure EndReading; function IsReading: Boolean; property TiffHeaderOffset: Int64 read FTiffHeaderOffset; // Thumbnail procedure LoadThumbnailFromStream(AStream: TStream; ASize: Integer = -1; AUpdateThumbnailTags: Boolean = true); function HasThumbnail: Boolean; procedure RemoveThumbnail; procedure SaveThumbnailToStream(AStream: TStream); function ThumbnailSize: Integer; // Properties property BigEndian: Boolean read FBigEndian; property ExportOptions: TExportOptions read FExportOptions write SetExportOptions; property TagByID[ATagID: TTagID]: TTag read GetTagByID write SetTagByID; property TagByIndex[AIndex: Integer]: TTag read GetTagByIndex write SetTagByIndex; property TagByName[ATagName: String]: TTag read GetTagByName write SetTagByName; property TagCount: Integer read GetTagCount; property ImgHeight: Integer read GetImgHeight; property ImgWidth: Integer read GetImgWidth; property ImgOrientation: TExifOrientation read GetOrientation; property OnBeginReading: TExifBeginReadingEvent read FOnBeginReading write FOnBeginReading; property OnEndReading: TExifEndReadingEvent read FOnEndReading write FOnEndReading; end; TVersionTag = class(TBinaryTag) private FSeparator: String; protected function GetAsString: String; override; procedure SetAsString(const AValue: String); override; public property Separator: String read FSeparator write FSeparator; end; TComponentsConfigTag = class(TBinaryTag) protected function GetAsString: String; override; procedure SetAsString(const AValue: String); override; end; TDateTimeTag = class(TStringTag) private function GetDateTime: TDateTime; function GetFormat: String; procedure SetDateTime(const AValue: TDateTime); protected function ExifDateToDateTime(AStr: string): TDateTime; function GetAsString: String; override; procedure SetAsString(const AValue: String); override; public procedure AdjustBy(ADays, AHours, AMinutes, ASeconds: Integer); property AsDateTime: TDateTime read GetDateTime write SetDateTime; property FormatStr; // e.g.: 'yyyy-mm-dd hh:nn:ss' end; TGPSPositionTag = class(TFloatTag) protected function GetAsFloat: Double; override; function GetAsString: String; override; procedure SetAsFloat(const AValue: Double); override; procedure SetAsString(const AValue: String); override; end; TMakerNoteStringTag = class(TStringTag) private FIndex: Integer; public constructor Create(ATagID, AIndex: Integer; AName: String; AData: TBytes; ACount: Integer; ALkupTbl: String; AOptions: TTagOptions); reintroduce; property Index: Integer read FIndex; end; TMakerNoteIntegerTag = class(TIntegerTag) private FIndex: Integer; public constructor Create(ATagID, AIndex: Integer; AName: String; AValue: Integer; ALkupTbl, AFormatStr: String; ATagType: TTagType; AOptions: TTagOptions); reintroduce; property Index: Integer read FIndex; end; TMakerNoteFloatTag = class(TFloatTag) private FIndex: Integer; public constructor Create(ATagID, AIndex: Integer; AName: String; AValue: Double; AFormatStr: String; ATagType: TTagType; AOptions: TTagOptions); reintroduce; property Index: Integer read FIndex; end; TExposureTimeTag = class(TFloatTag) protected function GetAsString: String; override; procedure SetAsString(const AValue: String); override; public property FormatStr; end; TShutterSpeedTag = class(TExposureTimeTag) protected // function GetFloat(AIndex: Integer; out AValue: Double): Boolean; override; function GetRational(AIndex: Integer; out AValue: TExifRational): Boolean; override; procedure SetFloat(AIndex: Integer; const AValue: Double); override; procedure SetRational(AIndex: Integer; const AValue: TExifRational); override; (* function GetAsFloat: Double; override; function GetAsRational: TExifRational; override; procedure SetAsFloat(const AValue: Double); override; procedure SetAsRational(const AValue: TExifRational); override; *) end; TApertureTag = class(TFloatTag) protected function GetFloat(AIndex: Integer; out AValue: Double): Boolean; override; function GetRational(AIndex: Integer; out AValue: TExifRational): Boolean; override; procedure SetFloat(AIndex: Integer; const AValue: Double); override; procedure SetRational(AIndex: Integer; const AValue: TExifRational); override; end; TUserCommentTag = class(TBinaryTag) protected function GetAsString: String; override; procedure SetAsString(const AValue: String); override; end; TXPTag = class(TBinaryTag) protected function GetAsString: String; override; end; (* TSingleTag = class(TBinaryTag) protected function GetAsString: String; override; function GetAsFloat: Double; override; procedure SetAsFloat(const AValue: Double); override; end; *) procedure BuildExifTagDefs; procedure FreeExifTagDefs; function FindExifTagDef(ATagID: TTagID): TTagDef; overload; function FindExifTagDef(AFullTagName: String): TTagDef; overload; function FindExifTagDefWithoutParent(ATagID: word): TTagDef; implementation uses {$IFDEF FPC} LazUTF8, {$ENDIF} Math, DateUtils, StrUtils, fpeStrConsts, fpeUtils; //============================================================================== // Tag definitions (TagDef) //============================================================================== var ExifTagDefs: TTagDefList = nil; procedure BuildExifTagDefs; const I = TAGPARENT_INTEROP; // for shorter lines... P = TAGPARENT_PRIMARY; T = TAGPARENT_THUMBNAIL; E = TAGPARENT_EXIF; G = TAGPARENT_GPS; begin if ExifTagDefs = nil then ExifTagDefs := TTagDefList.Create; with ExifTagDefs do begin Clear; AddStringTag (I+$0001, 'InterOpIndex', 1, rsInterOpIndex); AddBinaryTag (I+$0002, 'InterOpVersion', 1, rsInterOpVersion, '', '', TVersionTag); AddULongTag (P+$00FE, 'SubfileType', 1, '', rsSubfileTypeLkup, '', nil, true); AddULongTag (P+$0100, 'ImageWidth', 1, rsImageWidth); AddULongTag (T+$0100, 'ThumbnailWidth', 1, rsThumbnailWidth); AddULongTag (P+$0101, 'ImageHeight', 1, rsImageHeight); // official: "Image length" AddULongTag (T+$0101, 'ThumbnailHeight', 1, rsThumbnailHeight); // official: "Image length" AddULongTag (P+$0101, 'ImageLength', 1, rsImageHeight); AddULongTag (T+$0101, 'ThumbnailLength', 1, rsThumbnailHeight); AddUShortTag (P+$0102, 'BitsPerSample', 1, rsBitsPerSample); AddUShortTag (P+$0103, 'Compression', 1, rsCompression, rsCompressionLkup); AddUShortTag (T+$0103, 'ThumbnailCompression', 1, rsCompression, rsCompressionLkup); AddUShortTag (P+$0106, 'PhotometricInterpretation', 1, rsPhotometricInt, rsPhotometricIntLkup); AddUShortTag (P+$0107, 'Thresholding', 1, rsThresholding, rsThresholdingLkup); AddUShortTag (P+$0108, 'CellWidth', 1, rsCellWidth); AddUShortTag (P+$0109, 'CellHeight', 1, rsCellHeight); AddUShortTag (P+$010A, 'FillOrder', 1, rsFillOrder, rsFillOrderLkup); AddStringTag (P+$010D, 'DocumentName', 1, rsDocumentName); AddStringTag (P+$010E, 'ImageDescription', 1, rsImageDescr); AddStringTag (P+$010F, 'Make', 1, rsMake); AddStringTag (P+$0110, 'Model', 1, rsModel); AddULongTag (P+$0111, 'StripOffsets', 1, rsStripOffsets); AddUShortTag (P+$0112, 'Orientation', 1, rsOrientation, rsOrientationLkup); AddUShortTag (T+$0112, 'Orientation', 1, rsOrientation, rsOrientationLkup); AddUShortTag (P+$0115, 'SamplesPerPixel', 1, rsSamplesPerPixel); AddULongTag (P+$0116, 'RowsPerStrip', 1, rsRowsPerStrip); AddULongTag (P+$0117, 'StripByteCounts', 1, rsStripByteCounts); AddUShortTag (P+$0118, 'MinSampleValue', 1, rsMinSampleValue); AddUShortTag (P+$0119, 'MaxSampleValue', 1, rsMaxSampleValue); AddURationalTag(P+$011A, 'XResolution', 1, rsXResolution); AddURationalTag(T+$011A, 'ThumbnailXResolution', 1, rsXResolution); AddURationalTag(P+$011B, 'YResolution', 1, rsYResolution); AddURationalTag(T+$011B, 'ThumbnailYResolution', 1, rsYResolution); AddUShortTag (P+$011C, 'PlanarConfiguration', 1, rsPlanarConfiguration, rsPlanarConfigurationLkup); AddStringTag (P+$011D, 'PageName', 1, rsPageName); AddURationalTag(P+$011E, 'XPosition', 1, rsXPosition); AddURationalTag(P+$011F, 'YPosition', 1, rsYPosition); AddUShortTag (P+$0128, 'ResolutionUnit', 1, rsResolutionUnit, rsResolutionUnitLkup); AddUShortTag (T+$0128, 'ThumbnailResolutionUnit', 1, rsResolutionUnit, rsResolutionUnitLkup); AddUShortTag (P+$0129, 'PageNumber', 2, rsPageNumber); AddUShortTag (P+$012D, 'TransferFunction', 768, rsTransferFunction); AddStringTag (P+$0131, 'Software', 1, rsSoftware); AddStringTag (P+$0132, 'DateTime', 1, rsDateTime, '', TDateTimeTag); AddStringTag (T+$0132, 'DateTime', 1, rsDateTime, '', TDateTimeTag); AddStringTag (P+$013B, 'Artist', 1, rsArtist); AddStringTag (P+$013C, 'HostComputer', 1, rsHostComputer); AddUShortTag (P+$013D, 'Predictor', 1, rsPredictor, rsPredictorLkup); AddURationalTag(P+$013E, 'WhitePoint', 2, rsWhitePoint); AddURationaltag(P+$013F, 'PrimaryChromaticities', 6, rsPrimaryChromaticities); AddUShortTag (P+$0141, 'HalftoneHints', 2, rsHalftoneHints); AddULongTag (P+$0142, 'TileWidth', 1, rsTileWidth); AddULongTag (P+$0143, 'TileLength', 1, rsTileLength); AddULongTag (P+$014C, 'InkSet', 1, rsInkSet, rsInkSetLkup); AddUShortTag (P+$0151, 'TargetPrinter', 1, rsTargetPrinter); AddULongTag (T+$0201, 'ThumbnailOffset', 1, rsThumbnailOffset, '', '', TOffsetTag); AddULongTag (T+$0202, 'ThumbnailSize', 1, rsThumbnailSize); AddURationaltag(P+$0211, 'YCbCrCoefficients', 3, rsYCbCrCoefficients); AddUShortTag (P+$0212, 'YCbCrSubsamping', 2, rsYCbCrSubsampling); AddUShortTag (P+$0213, 'YCbCrPositioning', 1, rsYCbCrPositioning, rsYCbCrPosLkup); AddUShortTag (T+$0213, 'YCbCrPositioning', 1, rsYCbCrPositioning, rsYCbCrPosLkup); AddURationalTag(P+$0214, 'ReferenceBlackWhite', 6, rsRefBlackWhite); // AddByteTag(P+$02BC, 'ExtensibleMetadataPlatform', 1, rsExtensibleMetadataPlatform); AddStringTag (P+$02BC, 'ExtensibleMetadataPlatform',1, rsExtensibleMetadataPlatform); AddStringTag (I+$1000, 'RelatedImageFileFormat', 1, rsRelatedImageFileFormat); AddUShortTag (I+$1001, 'RelatedImageWidth', 1, rsRelatedImageWidth); AddUShortTag (I+$1002, 'RelatedImageHeight', 1, rsRelatedImageHeight); AddStringTag (P+$8298, 'Copyright', 1, rsCopyright); AddURationalTag(E+$829A, 'ExposureTime', 1, rsExposureTime, '', '', TExposureTimeTag); //, nil, '%0:.0f/%1:.0f s'); AddURationalTag(E+$829D, 'FNumber', 1, rsFNumber); //, nil, 'F/%2:.1f'); AddULongTag (P+$83BB, 'IPTC/NAA', 1, rsIPTCNAA); AddStringTag (P+$8546, 'SEMInfo', 1, rsSEMInfo); AddBinaryTag (P+$8649, 'PhotoShopSettings', 1, ''); AddULongTag (P+$8769, 'ExifOffset', 1, rsExifOffset, '', '', TSubIFDTag, true); AddBinaryTag (P+$83BB, 'IPTC', 1, rsIPTCNAA); AddUShortTag (E+$8822, 'ExposureProgram', 1, rsExposureProgram, rsExposureProgramLkup); AddStringTag (E+$8824, 'SpectralSensitivity', 1, rsSpectralSensitivity); AddULongTag (P+$8825, 'GPSInfo', 1, rsGPSInfo, '', '', TSubIFDTag); AddULongTag (E+$8827, 'ISO', 1, rsISO); AddUShortTag (E+$882A, 'TimeZoneOffset', 2, rsTimeZoneOffset); AddUShortTag (E+$882B, 'SelfTimerMode', 1, rsSelfTimerMode); AddUShortTag (E+$8830, 'SensitivityType', 1, rsSensitivityType, rsSensitivityTypeLkup); AddULongTag (E+$8831, 'StandardOutputSensitivity', 1, rsStdOutputSens); AddULongTag (E+$8832, 'RecommendedExposureIndex', 1, rsRecExpIndex); AddULongTag (E+$8833, 'ISOSpeed', 1, rsIsoSpeed); AddULongTag (E+$8834, 'ISOSpeedLatitudeYYY', 1, rsIsoSpeedLatitudeYYY); AddULongTag (E+$8835, 'ISOSpeedLatitudeZZZ', 1, rsIsoSpeedLatitudeZZZ); AddBinaryTag (E+$9000, 'ExifVersion', 4, rsExifVersion, '', '', TVersionTag); AddStringTag (E+$9003, 'DateTimeOriginal', 1, rsDateTimeOriginal, '', TDateTimeTag); AddStringTag (E+$9004, 'DateTimeDigitized', 1, rsDateTimeDigitized, '', TDateTimeTag); AddStringTag (E+$9010, 'OffsetTime', 1, rsOffsetTime); AddStringTag (E+$9011, 'OffsetTimeOriginal', 1, rsOffsetTimeOriginal); AddStringTag (E+$9012, 'OffsetTimeDigitized', 1, rsOffsetTimeDigitized); AddBinaryTag (E+$9101, 'ComponentsConfiguration', 1, rsComponentsConfig, '', '', TComponentsConfigTag, true); AddURationalTag(E+$9102, 'CompressedBitsPerPixel', 1, rsCompressedBitsPerPixel); AddSRationalTag(E+$9201, 'ShutterSpeedValue', 1, rsShutterSpeedValue, '', '', TShutterSpeedTag); AddURationalTag(E+$9202, 'ApertureValue', 1, rsApertureValue, '', 'F/%2:.1f', TApertureTag); AddSRationalTag(E+$9203, 'BrightnessValue', 1, rsBrightnessValue); AddSRationalTag(E+$9204, 'ExposureBiasValue', 1, rsExposureBiasValue); AddURationalTag(E+$9205, 'MaxApertureValue', 1, rsMaxApertureValue, '', 'F/%2:.1f', TApertureTag); AddURationalTag(E+$9206, 'SubjectDistance', 1, rsSubjectDistance); AddUShortTag (E+$9207, 'MeteringMode', 1, rsMeteringMode, rsMeteringModeLkup); AddUShortTag (E+$9208, 'LightSource', 1, rsLightSource, rsLightSourceLkup); AddUShortTag (E+$9209, 'Flash', 1, rsFlash, rsFlashLkup); AddURationalTag(E+$920A, 'FocalLength', 1, rsFocalLength, '', '%2:.1f mm'); AddULongTag (E+$9211, 'ImageNumber', 1, rsImageNumber); AddStringTag (E+$9212, 'SecurityClassification', 1, rsSecurityClassification); AddStringTag (E+$9213, 'ImageHistory', 1, rsImageHistory); AddUShortTag (E+$9214, 'SubjectArea', 4, rsSubjectArea); AddBinaryTag (E+$927C, 'MakerNote', 1, rsMakerNote, '', '', TMakerNoteTag, true); AddBinaryTag (E+$9286, 'UserComment', 1, rsUserComment, '', '', TUserCommentTag); AddStringTag (E+$9286, 'SubSecTime', 1, rsSubSecTime); AddStringTag (E+$9291, 'SubSecTimeOriginal', 1, rsSubSecTimeOriginal); AddStringTag (E+$9292, 'SubSecTimeDigitized', 1, rsSubSecTimeDigitized); AddURationalTag(E+$9400, 'Temperature', 1, rsTemperature); AddURationalTag(E+$9401, 'Humidity', 1, rsHumidity); AddURationalTag(E+$9402, 'Pressure', 1, rsPressure); AddSRationalTag(E+$9403, 'WaterDepth', 1, rsWaterDepth); AddURationalTag(E+$9404, 'Acceleration', 1, rsAcceleration); AddURationalTag(E+$9405, 'CameraElevationAngle', 1, rsCameraElevationAngle); AddBinaryTag (P+$9C9B, 'XPTitle', 1, '', '', '', TXPTag); AddBinaryTag (P+$9C9C, 'XPComment', 1, '', '', '', TXPTag); AddBinaryTag (P+$9C9D, 'XPAuthor', 1, '', '', '', TXPTag); AddBinaryTag (P+$9C9E, 'XPKeywords', 1, '', '', '', TXPTag); AddBinaryTag (P+$9C9F, 'XPSubject', 1, '', '', '', TXPTag); AddBinaryTag (E+$A000, 'FlashPixVersion', 1, rsFlashPixVersion, '', '', TVersionTag); AddUShortTag (E+$A001, 'ColorSpace', 1, rsColorSpace, rsColorSpaceLkup); AddUShortTag (E+$A002, 'ExifImageWidth', 1, rsExifImageWidth); AddUShortTag (E+$A003, 'ExifImageHeight', 1, rsExifImageHeight); // is called "ExifImageLength" in Specs AddStringTag (E+$A004, 'RelatedSoundFile', 1, rsRelatedSoundFile); AddULongTag (E+$A005, 'InterOperabilityOffset', 1, rsInterOpOffset, '', '', TSubIFDTag, true); AddURationalTag(E+$A20B, 'FlashEnergy', 1, rsFlashEnergy); AddBinaryTag (E+$A20C, 'SpatialFrequencyResponse', 1, rsSpatialFrequResponse); AddURationalTag(E+$A20E, 'FocalPlaneXResolution', 1, rsFocalPlaneXRes, '', '%2:f'); AddURationalTag(E+$A20F, 'FocalPlaneYResolution', 1, rsFocalPlaneYRes, '', '%2:f'); AddUShortTag (E+$A210, 'FocalPlaneResolutionUnit', 1, rsFocalPlaneResUnit, rsFocalPlaneResUnitLkup); AddBinaryTag (E+$A211, 'ImageNumber', 1, rsImageNumber); AddStringTag (E+$A212, 'SecurityClassification', 1, rsSecurityClassification); AddBinaryTag (E+$A213, 'ImageHistory', 1, rsImageHistory); AddUShortTag (E+$A214, 'SubjectLocation', 2, rsSubjectLocation); AddURationalTag(E+$A215, 'ExposureIndex', 1, rsExposureIndex); AddUShortTag (E+$A217, 'SensingMethod', 1, rsSensingMethod, rsSensingMethodLkup); AddBinaryTag (E+$A300, 'FileSource', 1, rsFileSource, rsFileSourceLkup); AddBinaryTag (E+$A301, 'SceneType', 1, rsSceneType, rsSceneTypeLkup); AddBinaryTag (E+$A302, 'CFAPattern', 1, rsCFAPattern); AddUShortTag (E+$A401, 'CustomRendered', 1, rsCustomRendered, rsCustomRenderedLkup); AddUShortTag (E+$A402, 'ExposureMode', 1, rsExposureMode, rsExposureModeLkup); AddUShortTag (E+$A403, 'WhiteBalance', 1, rsWhiteBalance, rsAutoManual); AddURationalTag(E+$A404, 'DigitalZoomRatio', 1, rsDigitalZoomRatio); AddUShortTag (E+$A405, 'FocalLengthIn35mmFilm', 1, rsFocalLengthIn35mm, '', '%d mm'); AddUShortTag (E+$A406, 'SceneCaptureType', 1, rsSceneCaptureType, rsSceneCaptureTypeLkup); AddUShortTag (E+$A407, 'GainControl', 1, rsGainControl, rsGainControlLkup); AddUShortTag (E+$A408, 'Contrast', 1, rsContrast, rsNormalLowHigh); AddUShortTag (E+$A409, 'Saturation', 1, rsSaturation, rsNormalLowHigh); AddUShortTag (E+$A40A, 'Sharpness', 1, rsSharpness, rsNormalSoftHard); AddBinaryTag (E+$A40B, 'DeviceSettingDescription', 1, rsDeviceSettingDescription); AddUShortTag (E+$A40C, 'SubjectDistanceRange', 1, rsSubjectDistancerange, rsSubjectDistanceRangeLkup); AddStringTag (E+$A420, 'ImgeUniqueID', 1, rsImageUniqueID); AddStringTag (E+$A430, 'OwnerName', 1, rsOwnerName); AddStringTag (E+$A431, 'SerialNumber', 1, rsSerialNumber); AddURationalTag(E+$A432, 'LensInfo', 4, rsLensInfo); AddStringTag (E+$A433, 'LensMake', 1, rsLensMake); AddStringTag (E+$A434, 'LensModel', 1, rsLensModel); AddStringTag (E+$A435, 'LensSerialNumber', 1, rsLensSerialNumber); AddURationalTag(E+$A500, 'Gamma', 1, rsGamma); AddBinaryTag (P+$C4A5, 'PrintIM', $FFFF, '', '', '', nil, true); AddBinaryTag (P+$C6D2, 'PanasonicTitle', $FFFF, '', '', '', nil, true); AddBinaryTag (P+$C6D3, 'PanasonicTitle2', $FFFF, '', '', '', nil, true); AddBinaryTag (E+$EA1C, 'Padding', $FFFF, '', '', '', nil, true); AddSLongTag (E+$EA1D, 'OffsetSchema', 1, '', '', '', nil, true); AddByteTag (G+$0000, 'GPSVersionID', 4, rsGpsVersionID, '', '', TVersionTag); AddStringTag (G+$0001, 'GPSLatitudeRef', 2, rsGPSLatitudeRef, rsGPSLatitudeRefLkup); AddURationalTag(G+$0002, 'GPSLatitude', 3, rsGPSLatitude, '', '%0:.0f° %1:.0f'' %2:.3f"', TGPSPositionTag); AddStringTag (G+$0003, 'GPSLongitudeRef', 2, rsGPSLongitudeRef, rsGPSLongitudeRefLkup); AddURationalTag(G+$0004, 'GPSLongitude', 3, rsGPSLongitude, '', '%0:.0f° %1:.0f'' %2:.3f"', TGPSPositionTag); AddByteTag (G+$0005, 'GPSAltitudeRef', 1, rsGPSAltitudeRef, rsGPSAltitudeRefLkup); AddURationalTag(G+$0006, 'GPSAltitude', 1, rsGPSAltitude); AddURationalTag(G+$0007, 'GPSTimeStamp', 3, rsGPSTimeStamp); // !!!!!!!!!!!, nil, '', @CvtTime); AddStringTag (G+$0008, 'GPSSatellites', 1, rsGPSSatellites); AddStringTag (G+$0009, 'GPSStatus', 2, rsGPSStatus); AddStringTag (G+$000A, 'GPSMeasureMode', 2, rsGPSMeasureMode, rsGPSMeasureModeLkup); AddURationalTag(G+$000B, 'GPSDOP', 1, rsGPSDOP); AddStringTag (G+$000C, 'GPSSpeedRef', 2, rsGPSSpeedRef, rsGPSSpeedRefLkup); AddURationalTag(G+$000D, 'GPSSpeed', 1, rsGPSSpeed); AddStringTag (G+$000E, 'GPSTrackRef', 2, rsGPSTrackRef, rsGPSTrackRefLkup); AddURationalTag(G+$000F, 'GPSTrack', 1, rsGPSTrack); AddStringTag (G+$0010, 'GPSImageDirectionRef', 2, rsGPSImageDirectionRef, rsGPSTrackRefLkup); // same option texts AddURationalTag(G+$0011, 'GPSImageDirection', 1, rsGPSImageDirection); AddStringTag (G+$0012, 'GPSMapDatum', 1, rsGPSMapDatum); AddStringTag (G+$0013, 'GPSDestLatitudeRef', 2, rsGPSDestLatitudeRef, rsGPSLatitudeRefLkup); AddURationalTag(G+$0014, 'GPSDestLatitude', 3, rsGPSDestLatitude, '', '%0:.0f° %1:.0f'' %2:.3f"', TGPSPositionTag); AddStringTag (G+$0015, 'GPSDestLongitudeRef', 2, rsGPSDestLongitudeRef, rsGPSLongitudeRefLkup); AddURationalTag(G+$0016, 'GPSDestLongitude', 3, rsGPSDestLongitude, '', '%0:.0f° %1:.0f'' %2:.3f"', TGPSPositionTag); AddStringTag (G+$0017, 'GPSDestBearingRef', 2, rsGPSDestBearingRef, rsGPSTrackRefLkup); AddURationalTag(G+$0018, 'GPSDestBearing', 1, rsGPSDestBearing); AddStringTag (G+$0019, 'GPSDestDistanceRef', 2, rsGPSDestDistanceRef, rsGPSDistanceRefLkup); AddURationalTag(G+$001A, 'GPSDestDistance', 1, rsGPSDestDistance); AddBinaryTag (G+$001B, 'GPSProcessingMode', 1, rsGPSProcessingMode); AddBinaryTag (G+$001C, 'GPSAreaInformation', 1, rsGPSAreaInformation); AddStringTag (G+$001D, 'GPSDateStamp', 11, rsGPSDateStamp); AddUShortTag (G+$001E, 'GPSDifferential', 1, rsGPSDateDifferential, rsGPSDateDifferentialLkup); AddURationalTag(G+$001F, 'GPSHPositioningError', 1, rsGPSHPositioningError); end; end; function FindExifTagDef(ATagID: TTagID): TTagDef; begin if ExifTagDefs = nil then BuildExifTagDefs; Result := ExifTagDefs.FindByID(ATagID); end; function FindExifTagDef(AFullTagName: String): TTagDef; begin if ExifTagDefs = nil then BuildExifTagDefs; Result := ExifTagDefs.FindByName(AFullTagName); end; { seeks for the definition of the tag specified by the given id of the tag part only, the parent ID is ignored. } function FindExifTagDefWithoutParent(ATagID: Word): TTagDef; begin if ExifTagDefs = nil then BuildExifTagDefs; Result := ExifTagDefs.FindByIDWithoutParent(ATagID); end; procedure FreeExifTagDefs; begin FreeAndNil(ExifTagDefs); end; //============================================================================== // TExifData //============================================================================== constructor TExifData.Create(ABigEndian: Boolean); begin BuildExifTagDefs; FTagList := TTagList.Create; FBigEndian := ABigEndian; FExportOptions := [eoShowTagName, eoDecodeValue, eoTruncateBinary]; end; destructor TExifData.Destroy; begin FTagList.Free; inherited; end; { function TExifData.AddMakerNoteTag(AIndex: Integer; ATagID: TTagID; ATagName: String; AData: TBytes; ACount: Integer; ALkupTbl: String = ''; AFormatStr: String = ''; ATagType: TTagType = ttUInt8): Integer; var tag: TTag; begin tag := TMakerNoteByteTag.Create(ATagID, AIndex, ATagName, AData, ACount, ALkupTbl, AFormatStr, ATagType, ExportOptionsToTagOptions); Result := FTagList.Add(tag); end; } function TExifData.AddMakerNoteStringTag(AIndex: Integer; ATagID: TTagID; ATagName: String; AData: TBytes; ACount: Integer; ALkupTbl: String = ''): Integer; var tag: TTag; begin tag := TMakerNoteStringTag.Create(ATagID, AIndex, ATagName, AData, ACount, ALkupTbl, ExportOptionsToTagOptions); Result := FTagList.Add(tag); end; function TExifData.AddMakerNoteTag(AIndex: Integer; ATagID: TTagID; ATagName: String; ADataValue: Integer; ALkupTbl: String = ''; AFormatStr: String = ''; ATagType: TTagType = ttUInt16): Integer; var tag: TTag; begin tag := TMakerNoteIntegerTag.Create(ATagID, AIndex, ATagName, ADataValue, ALkupTbl, AFormatStr, ATagType, ExportOptionsToTagOptions); Result := FTagList.Add(tag); end; function TExifData.AddMakerNoteTag(AIndex: Integer; ATagID: TTagID; ATagName: String; ADataValue: Double; AFormatStr: String = ''; ATagType: TTagType = ttURational): Integer; var tag: TTag; begin tag := TMakerNoteFloatTag.Create(ATagID, AIndex, ATagName, ADataValue, AFormatStr, ATagType, ExportOptionsToTagOptions); Result := FTagList.Add(tag); end; function TExifData.AddOrReplaceTag(ATag: TTag): Integer; begin Result := IndexOfTagID(ATag.TagID); if Result <> -1 then begin FTagList.Delete(Result); FTagList.Insert(Result, ATag); end else Result := AddTag(ATag); end; function TExifData.AddTag(ATag: TTag): Integer; var parentID: TTagID; parentTag: TTag; parentTagDef: TTagDef; begin parentID := ATag.TagID and $FFFF0000; if not ((parentID = TAGPARENT_PRIMARY) or (parentID = TAGPARENT_THUMBNAIL)) then begin // Make sure that the parent directories of the new tag already exist. // If not, create them. repeat // Look if the parent tag already exists. parentTag := GetParentTag(ATag); if parentTag <> nil then break; // No - not found... // The tagID of the tag which defines the subIFD is encoded in the high-word // of the tagID parentID := TTagIDRec(ATag.TagID).Parent; // Just to make sure: the primary and thumbnail IFDs are always existing... if (parentID = TAG_PRIMARY) or (parentID = TAG_THUMBNAIL) then break; // Find definition of the sub-ifd tag parentTagDef := FindExifTagDefWithoutParent(parentID); // ... Could not be found, tag not defined. We cannot handle this case. if parentTagDef = nil then begin ATag.Free; Result := -1; // This will signal the calling procedure to destroy ATag. exit; end; // ... create tag for it and add it to the list. parentTag := TSubIFDTag.Create(parentTagDef, FBigEndian); AddOrReplaceTag(parentTag); until false; end; // Add the new tag Result := FTagList.Add(ATag); end; function TExifData.AddTagByID(ATagID: TTagID): TTag; var idx: Integer; tagDef: TTagDef; begin idx := IndexOfTagID(ATagID); if idx > -1 then Result := FTagList[idx] else begin tagDef := FindExifTagDef(ATagID); Result := InternalAddTag(tagDef); end; end; function TExifData.AddTagByName(AFullTagName: String): TTag; var idx: Integer; tagdef: TTagDef; begin idx := IndexOfTagName(AFullTagName); if idx > -1 then Result := FTagList[idx] else begin tagDef := FindExifTagDef(AFullTagName); Result := InternalAddTag(tagDef); end; end; procedure TExifData.BeginReading; begin inc(FReadFlag); if FReadFlag = 1 then DoBeginReading; end; { Checks whether the tag "FocalLengthIn35mm" is available. Otherwise it is created as a volatile, readonly tag. } procedure TExifData.CheckFocalLengthIn35mm; var tag: TTag; fpu, flen, resol: Double; ccdwidth, ccdheight, ratio: Double; tagdef: TTagDef; optns: TTagOptions; begin tag := TagByID[FULLTAG_FOCALLENGTH35mm]; if tag <> nil then exit; tag := TagByName['Exif.FocalLength']; if tag = nil then exit; flen := tag.AsFloat; if IsNaN(flen) or (flen <= 0.0) then exit; tag := TagByName['Exif.FocalPlaneResolutionUnit']; if tag = nil then tag := TagByName['ResolutionUnit']; if tag = nil then exit; fpu := tag.AsFloat; if IsNaN(fpu) or (fpu <= 0) then exit; tag := TagByName['Exif.FocalPlaneResolutionX']; if tag = nil then exit; resol := tag.AsFloat; if IsNaN(resol) or (resol <= 0.0) then exit; ccdwidth := GetImgWidth() * fpu/resol; tag := TagByName['Exif.FocalPlaneResolutionY']; if tag = nil then exit; resol := tag.AsFloat; if IsNaN(resol) or (resol <= 0.0) then exit; ccdheight := GetImgHeight() * fpu/resol; ratio := sqrt(sqr(24) + sqr(36)) / sqrt(sqr(CCDWidth) + sqr(CCDHeight)); optns := [toReadOnly, toVolatile]; if BigEndian then optns := optns + [toBigEndian]; tagDef := FindExifTagDef(FULLTAG_FOCALLENGTH35mm); tag := TFloatTag.Create(tagDef, optns); tag.AsFloat := flen * ratio; AddOrReplaceTag(tag); end; procedure TExifData.Clear; begin FTagList.Clear; end; procedure TExifData.DoBeginReading; begin if Assigned(FOnBeginReading) then FOnBeginReading(); end; procedure TExifData.DoEndReading; begin if Assigned(FOnEndReading) then FOnEndReading(); end; procedure TExifData.EndReading; begin dec(FReadFlag); if FReadFlag = 0 then begin CheckFocalLengthIn35mm; DoEndReading; end; end; function TExifData.ExportOptionsToTagOptions: TTagOptions; begin Result := []; if eoDecodeValue in FExportOptions then Include(Result, toDecodeValue); if eoTruncateBinary in FExportOptions then Include(Result, toTruncateBinary); if eoBinaryAsASCII in FExportOptions then Include(Result, toBinaryAsASCII); end; procedure TExifData.ExportToStrings(AList: TStrings; ASeparator: String = '='; AGroup: TTagGroup = tgUnknown); var i: Integer; tag: TTag; nam: String; tagval: String; usedExportOptions: TExportOptions; begin Assert(AList <> nil); if AGroup = tgUnknown then begin ExportToStrings(AList, ASeparator, tgExifPrimary); ExportToStrings(AList, ASeparator, tgExifThumbnail); ExportToStrings(AList, ASeparator, tgExifSub); ExportToStrings(AList, ASeparator, tgExifGps); ExportToStrings(AList, ASeparator, tgExifInterop); ExportToStrings(AList, ASeparator, tgExifMakerNote); exit; end; if not HasTagsOfGroup(AGroup) then exit; if AList.Count > 0 then AList.Add(''); AList.Add('*** ' + NiceGroupNames[AGroup] + ' ***'); for i := 0 to TagCount-1 do begin tag := TagByIndex[i]; if tag.Group = AGroup then begin usedExportOptions := FExportOptions * [eoShowDecimalTagID, eoShowHexTagID]; if usedExportOptions = [eoShowDecimalTagID] then nam := Format('[%d %d] %s', [ tag.TagIDRec.Parent, tag.TagIDRec.Tag, tag.Description ]) else if usedExportOptions = [eoShowHexTagID] then nam := Format('[$%.4x %.4x] %s', [ tag.TagIDRec.Parent, tag.TagIDRec.Tag, tag.Description ]) else nam := tag.Description; tagval := tag.AsString; if tagval <> '' then AList.Add(nam + ASeparator + tagval); end; end; end; { Seeks the tag list for the tag with the specified (full) TagID. The function returns nil if the tag is not found. } function TExifData.FindTagByID(ATagID: TTagID): TTag; var i: Integer; begin for i:=0 to FTagList.Count-1 do begin Result := FTagList[i]; if (Result.TagID = ATagID) then exit; end; Result := nil; end; { Seeks the tag list for the tag with the specified name. The name must be composed of the name of the tag group and the name of the tag, i.e. 'EXIF.FNumber'. If the group is not specified (i.e. 'FNumber' only) the first matching tag is returned (in spite of other tags possibly having the same name in other groups). The function returns nil if the tag is not found. } function TExifData.FindTagByName(AFullTagName: String): TTag; var idx: Integer; begin idx := IndexOfTagName(AFullTagName); if idx = -1 then Result := nil else Result := FTagList[idx]; end; function TExifData.GetImgHeight: Integer; var tag: TTag; begin tag := TagByName['ImageHeight']; if tag = nil then tag := TagByName['Exif.ExifImageHeight']; if tag = nil then result := 0 else Result := tag.AsInteger; end; function TExifData.GetImgWidth: Integer; var tag: TTag; begin tag := TagByName['ImageWidth']; if tag = nil then tag := TagByName['Exif.ExifImageWidth']; if tag = nil then Result := 0 else Result := tag.AsInteger; end; function TExifData.GetOrientation: TExifOrientation; var tag: TTag; begin tag := TagByName['Orientation']; if tag = nil then Result := eoUnknown else Result := TExifOrientation(tag.AsInteger); end; { Finds the tag which defines the sub-IFD to which the specified tag belongs } function TExifData.GetParentTag(ATag: TTag): TTag; var idx: Integer; begin Result := nil; if ATag <> nil then begin idx := FTagList.IndexOfParentByID(ATag.TagID); if idx <> -1 then Result := FTagList[idx]; end; end; { Seeks the tag list for the tag with the specified TagID and the specified tag group } function TExifData.GetTagByID(ATagID: TTagID): TTag; var idx: Integer; begin idx := IndexOfTagID(ATagID); if idx = -1 then Result := nil else Result := FTagList.Items[idx]; end; function TExifData.GetTagByIndex(AIndex: Integer): TTag; begin Result := FTagList[AIndex]; end; { Seeks the tag list for the tag with the specified name. The name must be composed of the name of the tag group and the name of the tag, i.e. 'EXIF.FNumber'. If the group is not specified (i.e. 'FNumber' only) the first matching tag is returned (in spite of other tags possibly having the same name in other groups). } function TExifData.GetTagByName(AFullTagName: String): TTag; var idx: Integer; begin idx := IndexOfTagName(AFullTagName); if idx > -1 then Result := FTagList[idx] else Result := nil end; function TExifData.GetTagCount: Integer; begin Result := FTagList.Count; end; function TExifData.HasTagsOfGroup(AGroup: TTagGroup): Boolean; var i: Integer; tag: TTag; begin Result := true; for i:=0 to FTagList.Count-1 do begin tag := FTagList[i]; if (tag.Group = AGroup) then exit; end; Result := false; end; function TExifData.HasThumbnail: Boolean; begin Result := Length(FThumbnailBuffer) > 0; end; function TExifData.IndexOfTagID(ATagID: TTagID): Integer; begin Result := FTagList.IndexOfTagByID(ATagID); end; function TExifData.IndexOfTagName(AFullTagName: String): Integer; var gname: String; tname: String; p: Integer; g: TTagGroup; i: Integer; tag: TTag; begin p := pos('.', AFullTagName); if p <> 0 then begin gname := copy(AFullTagName, 1, p-1); tname := copy(AFullTagName, p+1, MaxInt); for g := Low(TTagGroup) to High(TTagGroup) do if SameText(gname, GroupNames[g]) or SameText(gname, NiceGroupNames[g]) then begin for i:=0 to FTagList.Count-1 do begin tag := FTagList[i]; if SameText(tag.Name, tname) and (tag.Group = g) then begin Result := i; exit; end; end; end; end else begin for i:=0 to FTagList.Count-1 do begin tag := FTagList[i]; if SameText(tag.Name, AFullTagName) then begin Result := i; exit; end; end; end; Result := -1; end; function TExifData.InternalAddTag(ATagDef: TTagDef): TTag; var optns: TTagOptions; begin if ATagDef <> nil then begin optns := ExportOptionsToTagOptions; if FBigEndian then Include(optns, toBigEndian); Result := ATagDef.TagClass.Create(ATagDef, optns); AddTag(Result); end else Result := nil end; function TExifData.IsReading: Boolean; begin Result := FReadFlag > 0; end; procedure TExifData.LoadThumbnailFromStream(AStream: TStream; ASize: Integer = -1; AUpdateThumbnailTags: Boolean = true); var n: Integer; w, h: Integer; begin SetLength(FThumbnailBuffer, 0); if AUpdateThumbnailTags then RemoveThumbnail; // Check whether the image is a jpeg, and extract size of the thrumbnail image if not JPEGImageSize(AStream, w, h) then raise EFpExif.Create('Only jpeg images accepted for thumbnail.'); // Write the image from the stream into the thumbnail buffer if ASize < 0 then n := AStream.Size else n := ASize; if n > 65000 then // limit probably still too high, thumbnail must fit into a 64k segment along with all other tags... raise EFpExif.Create('Thumbnail too large.'); SetLength(FThumbnailBuffer, n); if AStream.Read(FThumbnailBuffer[0], n) < n then raise EFpExif.Create('Could not read thumbnail image.'); if AUpdateThumbnailTags then begin // Make sure that the IFD1 tags for the thumbnail are correct AddTagByID(FULLTAG_THUMBCOMPRESSION).AsInteger := 6; // 6 = JPEG - this was checked above. AddTagByID(FULLTAG_THUMBWIDTH).AsInteger := w; AddTagByID(FULLTAG_THUMBLENGTH).AsInteger := h; AddTagByID(FULLTAG_THUMBSTARTOFFSET).AsInteger := 0; // to be replaced by the offset to the thumbnail when writing AddTagByID(FULLTag_THUMBSIZE).AsInteger := n; end; end; procedure TExifData.RemoveThumbnail; var tag: TTag; i: Integer; begin SetLength(FThumbnailBuffer, 0); for i:=FTagList.Count-1 downto 0 do begin tag := FTagList[i]; if tag.Group = tgExifThumbnail then FTagList.Delete(i) end; end; procedure TExifData.SaveThumbnailToStream(AStream: TStream); var n: Int64; begin if HasThumbnail then begin n := Length(FThumbnailBuffer); if AStream.Write(FThumbnailBuffer[0], n) <> n then raise EFpExif.Create('Error writing thumbnail image to stream.'); end; end; procedure TExifData.SetExportOptions(const AValue: TExportOptions); var i: Integer; tag: TTag; decodeVal, truncBin, binASCII: Boolean; needUpdate: Boolean; optns: set of TExportOption; begin optns := [eoDecodeValue, eoTruncateBinary, eoBinaryAsASCII]; needUpdate := (optns * FExportOptions <> optns * AValue); FExportOptions := AValue; if not needUpdate then exit; decodeVal := eoDecodeValue in FExportOptions; truncBin := eoTruncateBinary in FExportOptions; binASCII := eoBinaryAsASCII in FExportOptions; for i:=0 to TagCount-1 do begin tag := TagByIndex[i]; tag.DecodeValue := decodeVal; tag.TruncateBinary := truncBin; tag.BinaryAsASCII := binASCII; end; end; procedure TExifData.SetTagByID(ATagID: TTagID; ATag: TTag); var idx: Integer; begin if (ATag <> nil) and ATag.ReadOnly then exit; idx := IndexOfTagID(ATagID); SetTagByIndex(idx, ATag); end; procedure TExifData.SetTagByIndex(AIndex: Integer; ATag: TTag); var tag: TTag; begin if (ATag <> nil) and ATag.ReadOnly then exit; if AIndex > -1 then begin tag := FTagList[AIndex]; if tag.ReadOnly then exit; FTagList.Delete(AIndex); if ATag <> nil then FTagList.Insert(AIndex, ATag); end else AddOrReplaceTag(ATag); end; procedure TExifData.SetTagByName(AFullTagName: String; ATag: TTag); var idx: Integer; begin if (ATag <> nil) and ATag.ReadOnly then exit; idx := IndexOfTagName(AFullTagName); SetTagByIndex(idx, ATag); end; function TExifData.ThumbnailSize: Integer; begin Result := Length(FThumbnailBuffer); end; //============================================================================== // TVersionTag //============================================================================== function TVersionTag.GetAsString: String; var i: Integer; ch: Char; begin for i:=0 to High(FRawData) do begin if (FType = ttUInt8) then ch := char(ord('0') + FRawData[i]) else ch := char(FRawData[i]); if i = 0 then Result := ch else if FSeparator = #0 then Result := Result + ch else Result := Result + FSeparator + ch; end; end; procedure TVersionTag.SetAsString(const AValue: String); var i, n: Integer; sa: ansistring; b: Byte; begin sa := ansistring(AValue); SetLength(FRawData, Length(sa)); i := 1; n := 0; while i <= Length(sa) do begin if sa[i] <> FSeparator then begin if (FType = ttUInt8) then b := ord(sa[i]) - ord('0') else b := ord(sa[i]); FRawData[n] := b; inc(n); end; inc(i); end; SetLength(FRawData, n); FCount := n; end; //============================================================================== // TComponentsConfigTag //============================================================================== function TComponentsConfigTag.GetAsString: String; var i: Integer; begin Result := ''; for i:=0 to 3 do case FRawData[i] of 1: Result := Result + 'Y'; 2: Result := Result + 'Cb'; 3: Result := Result + 'Cr'; 4: Result := Result + 'R'; 5: Result := Result + 'G'; 6: Result := Result + 'B'; end; end; procedure TComponentsConfigTag.SetAsString(const AValue: String); var i, j: Integer; s: String; elem: String; begin SetLength(FRawData, 4); FCount := 4; s := InsertSpaces(AValue) + ' '; elem := ''; j := 0; for i:=1 to Length(s) do begin if (s[i] >= 'A') and (s[i] <= 'Z') then elem := s[i] else if (s[i] = ' ') then begin if elem = 'Y' then FRawData[j] := 1 else if elem = 'Cb' then FRawData[j] := 2 else if elem = 'Cr' then FRawData[j] := 3 else if elem = 'R' then FRawdata[j] := 4 else if elem = 'G' then FRawData[j] := 5 else if elem = 'B' then FRawData[j] := 6 else continue; inc(j); if j = 4 then exit; end else elem := elem + s[i]; end; end; //============================================================================== // TDateTimeTag //============================================================================== procedure TDateTimeTag.AdjustBy(ADays, AHours, AMinutes, ASeconds: Integer); var dt: TDateTime; begin dt := GetDateTime; dt := dt + ADays + AHours/24 + AMinutes/(24*60) + ASeconds/(24*60*60); SetDateTime(dt); end; function TDateTimeTag.ExifDateToDateTime(AStr: string): TDateTime; type TConvert= packed record year: Array [1..4] of char; f1:char; mon: Array [1..2] of char; f2:char; day: Array [1..2] of char; f3:char; hr: Array [1..2] of char; f4:char; min: Array [1..2] of char; f5:char; sec: Array [1..2] of char; end; PConvert= ^TConvert; var yr, mn, dy, h, m, s: Integer; d: TDateTime; t: TDateTime; begin Result := 0; if Length(AStr) = 10 then AStr := AStr + ' 00:00:00'; if Length(AStr) * SizeOf(Char) >= SizeOf(TConvert) then // take care of Delphi's WideChars with PConvert(@AStr[1])^ do if TryStrToInt(year, yr) and TryStrToInt(mon, mn) and TryStrToInt(day, dy) and TryEncodeDate(yr, mn, dy, d) and TryStrToInt(hr, h) and TryStrToInt(min, m) and TryStrToInt(sec, s) and TryEncodeTime(h, m, s, 0, t) then Result := d + t; end; function TDateTimeTag.GetAsString: String; var dt: TDateTime; i: Integer; begin dt := GetDateTime; Result := FormatDateTime(GetFormat, dt); if dt = 0 then for i:= 1 to Length(Result) do if Result[i] in ['1'..'9'] then Result[i] := '0'; end; function TDateTimeTag.GetDateTime: TDateTime; var s: String; begin s := inherited GetAsString; Result := ExifDateToDateTime(s); end; function TDateTimeTag.GetFormat: String; begin Result := IfThen(FFormatStr = '', fpExifFmtSettings.ShortDateFormat + ' ' + fpExifFmtSettings.LongTimeFormat, FFormatStr ); end; procedure TDateTimeTag.SetAsString(const AValue: String); var d: TDateTime; {$IFNDEF FPC} fs: TFormatSettings; p: Integer; fmt: String; {$ENDIF} begin {$IFDEF FPC} d := ScanDateTime(GetFormat, AValue); {$ELSE} fmt := GetFormat; fs := fpExifFmtSettings; p := pos(' ', fmt); if p <> 0 then begin fs.ShortDateFormat := Copy(fmt, 1, p-1); fs.LongTimeFormat := Copy(fmt, p+1, MaxInt); d := StrToDateTime(AValue, fs); end else begin fs.ShortDateFormat := fmt; d := StrToDate(AValue, fs); end; {$ENDIF} SetDateTime(d); end; procedure TDateTimeTag.SetDateTime(const AValue: TDateTime); var s: string; begin s := FormatDateTime(EXIF_DATETIME_FORMAT, AValue); inherited SetAsString(s); end; //============================================================================== // TGPSPositionTag //============================================================================== function TGPSPositionTag.GetAsFloat: Double; var arr: TExifDoubleArray; begin arr := GetAsFloatArray; Result := arr[0] + arr[1]/60 + arr[2]/3600; end; { Parmeters in the FormatString are expected to be in this order #0 degrees as integer #1 minutes as integer #2 seconds as float #3 minutes + seconds as float (mins) #4 degrees + minutes + seconds as float (degs) Example: '%0:d° %3:.6'' --> 45° 12.123456' } function TGPSPositionTag.GetAsString: String; var arr: TExifDoubleArray; degs: Double; mins: Double; begin arr := GetAsFloatArray; if Length(arr) = 0 then begin Result := ''; exit; end; degs := arr[0] + arr[1]/60 + arr[2]/3600; // Fix me: consider the case that all may be floats mins := arr[1] + arr[2]/60; Result := Format(FFormatStr, [arr[0], arr[1], arr[2], mins, degs], FpExifFmtSettings); end; procedure TGPSPositionTag.SetAsFloat(const AValue: Double); var arr: TExifDoubleArray; begin SetLength(arr, 3); SplitGps(AValue, arr[0], arr[1], arr[2]); SetAsFloatArray(arr); end; procedure TGPSPositionTag.SetAsString(const AValue: String); var deg: Double; begin if AValue = '' then exit; if TryStrToGps(AValue, deg) then SetAsFloat(deg) else raise EFpExif.CreateFmt('"%s" is not a valid GPS position string.', [AValue]); end; //============================================================================== // TMakerNoteTag //============================================================================== constructor TMakerNoteIntegerTag.Create(ATagID, AIndex: Integer; AName: String; AValue: Integer; ALkupTbl, AFormatStr: String; ATagType: TTagType; AOptions: TTagOptions); begin if not (ATagType in [ttUInt8, ttUInt16, ttUInt32, ttSInt8, ttSInt16, ttSInt32]) then raise EFpExif.Create('Tag type not allowed for TMakerNoteIntegerTag'); FIndex := AIndex; FTagID := ATagID; FGroup := tgExifMakerNote; FName := AName; FDesc := ''; FType := ATagType; FLkupTbl := ALkupTbl; FFormatStr := AFormatStr; FOptions := [toReadOnly, toVolatile] + AOptions; FCount := 1; SetLength(FRawData, TagElementSize[ord(FType)]); SetInteger(0, AValue, false); // false: MakeNote tags are poorly defined -> don't crash end; constructor TMakerNoteFloatTag.Create(ATagID, AIndex: Integer; AName: String; AValue: Double; AFormatStr: String; ATagType: TTagType; AOptions: TTagOptions); begin if not (ATagType in [ttURational, ttSRational]) then raise EFpExif.Create('Tag type not allowed for TMakerNoteFloatTag'); FIndex := AIndex; FTagID := ATagID; FGroup := tgExifMakerNote; FName := AName; FDesc := ''; FType := ATagType; FFormatStr := AFormatStr; FOptions := [toReadOnly, toVolatile] + AOptions; AsFloat := AValue; end; constructor TMakerNoteStringTag.Create(ATagID, AIndex: Integer; AName: String; AData: TBytes; ACount: Integer; ALkupTbl: String; AOptions: TTagOptions); begin FIndex := AIndex; FTagID := ATagID; FGroup := tgExifMakerNote; FName := AName; FDesc := ''; FType := ttString; FLkupTbl := ALkUpTbl; FOptions := [toReadOnly, toVolatile] + AOptions; FCount := ACount; SetLength(FRawData, FCount * TagElementSize[ord(FType)]); Move(AData[0], FRawData[0], Length(FRawData)); end; //============================================================================== // TExposureTimeTag //============================================================================== { The FormatStr of the ExposureTag consists of 2 sections separated by a colon: - 1st part for times < 1s, using reciprocal exposure time - 2nd part for times >= 1s, using (non-reciprocal) exposure time If only a single section is used then it is applied to all (non-reciprocal) exposure times. Example: '1/%.0f;%.0f' } function TExposureTimeTag.GetAsString: String; var floatVal: Double; fmt1, fmt2: String; p: Integer; begin floatVal := GetAsFloat; if FFormatStr = '' then begin if IsNaN(floatVal) then Result := '' else if floatVal >= 10 then Result := Format('%.0fs', [floatVal]) else if floatVal >= 1 then Result := Format('%.1fs', [floatVal]) else Result := Format('1/%.0fs', [1.0/floatVal]); end else begin p := pos(';', FFormatStr); if p > 0 then begin fmt1 := copy(FFormatStr, 1, p-1); fmt2 := copy(FFormatStr, p+1, MaxInt); if floatVal < 1.0 then Result := Format(fmt1, [1.0/floatVal]) else Result := Format(fmt2, [floatVal]); end else Result := Format(FFormatStr, [floatVal]); end; end; procedure TExposureTimeTag.SetAsString(const AValue: String); var i: Integer; s, sNum, sDenom: String; r: TExifRational; floatVal: Double; code: Integer; begin s := ''; snum := ''; sdenom := ''; for i:=1 to Length(AValue) do if AValue[i] in ['0'..'9','.'] then s := s + AValue[i] else if AValue[i] = ',' then s := s + '.' else if AValue[i] = '/' then begin snum := s; s := ''; end; if snum <> '' then begin sdenom := s; r.Numerator := StrToInt(snum); r.Denominator := StrToInt(sdenom); SetAsRational(r); end else begin val(s, floatVal, code); SetAsFloat(floatVal); end; end; //============================================================================== // TShutterSpeedTag // // Sputter speed value (Tv) is stored in APEX units: // Tv := -log2(t), t = exposure time in seconds // http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf //============================================================================== function TShutterSpeedTag.GetRational(AIndex: Integer; out AValue: TExifRational): Boolean; var r: TExifRational; dbl: double; begin Result := inherited GetRational(AIndex, r); if Result then begin dbl := r.Numerator / r.Denominator; AValue := FloatToRational(Power(2.0, -dbl), 1E-9); end; end; procedure TShutterSpeedTag.SetFloat(AIndex: integer; const AValue: Double); begin inherited SetFloat(AIndex, -log2(AValue)); end; procedure TShutterSpeedTag.SetRational(AIndex: Integer; const AValue: TExifRational); begin SetFloat(AIndex, AValue.Numerator / AValue.Denominator); end; //============================================================================== // TApertureTag // // Aperture value (AV) is stored in APEX units: // AV = 2 log2(FNumber) // see http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf //============================================================================== function TApertureTag.GetFloat(AIndex: Integer; out AValue: Double): Boolean; var dbl: Double; begin Result := inherited GetFloat(AIndex, dbl); AValue := Power(2.0, dbl * 0.5); end; function TApertureTag.GetRational(AIndex: Integer; out AValue: TExifRational): Boolean; var r: TExifRational; dbl: Double; begin Result := inherited GetRational(AIndex, r); dbl := r.Numerator / r.Denominator; AValue := FloatToRational(Power(2.0, dbl/2), 1E-9); end; procedure TApertureTag.SetFloat(AIndex: integer; const AValue: Double); begin inherited SetFloat(AIndex, 2.0 * log2(AValue)); end; procedure TApertureTag.SetRational(AIndex: Integer; const AValue: TExifRational); begin SetFloat(AIndex, AValue.Numerator / AValue.Denominator); end; //============================================================================== // TUserCommentTag //============================================================================== function TUserCommentTag.GetAsString: String; var sw: WideString; sa: AnsiString; begin Result := ''; if PosInBytes('UNICODE', FRawData) = 0 then begin SetLength(sw, (Length(FRawData) - 8) div SizeOf(WideChar)); Move(FRawData[8], sw[1], Length(sw) * SizeOf(WideChar)); if BigEndian then sw := BEtoN(sw) else sw := LEtoN(sw); {$IFDEF FPC} Result := UTF8Encode(sw); {$ELSE} Result := sw; {$ENDIF} end else if PosInBytes('ASCII', FRawData) = 0 then begin SetLength(sa, Length(FRawData) - 8); Move(FRawData[8], sa[1], Length(sa)); Result := sa; end else if PosInBytes(#0#0#0#0#0#0#0#0, FRawData) = 0 then begin SetLength(sa, Length(FRawData) - 8); Move(FRawData[8], sa[1], Length(sa)); {$IFDEF FPC} {$IFDEF FPC3+} Result := WinCPToUTF8(sa); {$ELSE} Result := SysToUTF8(sa); {$ENDIF} {$ELSE} Result := sa; {$ENDIF} end else if PosInBytes('JIS', FRawData) = 0 then raise EFpExif.Create('JIS-encoded user comment is not supported.'); while (Result <> '') and (Result[Length(Result)] = #0) do Delete(Result, Length(Result), 1); end; // Note: No trailing zero needed here. procedure TUserCommentTag.SetAsString(const AValue: String); var i: integer; sw: WideString; sa: AnsiString; isASCII: Boolean; begin if AValue = '' then SetLength(FRawData, 0) else begin isASCII := true; for i:=1 to Length(AValue) do if AValue[i] > #127 then begin isASCII := false; break; end; if isASCII then begin SetLength(FRawData, 8 + Length(AValue)); sa := 'ASCII'#0#0#0; Move(sa[1], FRawData[0], 8); sa := ansistring(AValue); Move(sa[1], FRawData[8], Length(sa)); end else begin {$IFDEF FPC} sw := UTF8Decode(AValue); {$ELSE} sw := AValue; {$ENDIF} if BigEndian then sw := NtoBE(sw) else sw := NtoLE(sw); SetLength(FRawData, 8 + Length(sw) * SizeOf(WideChar)); // +8 for header sa := 'UNICODE'#0; Move(sa[1], FRawData[0], 8); Move(sw[1], FRawData[8], Length(sw) * SizeOf(WideChar)); end; end; FCount := Length(FRawData); end; //============================================================================== // TXPTag // // tag used by Windows, encoded in UCS2 // See http://www.exiv2.org/tags.html //============================================================================== function TXPTag.GetAsString: String; var ws: WideString; begin SetLength(ws, Length(FRawData) div SizeOf(WideChar)); Move(FRawData[0], ws[1], Length(FRawData)); Result := UTF8Encode(ws); end; (* //============================================================================== // TSingleTag // // Binary tag of size 4 which is interpreted as a single value //============================================================================== function TSingleTag.GetAsString: String; var sng: Single; dw: DWord absolute sng; begin Move(FRawData[0], dw, 4); if BigEndian then dw := BEToN(dw) else dw := LEToN(dw); Result := FloatToStr(sng); end; function TSingleTag.GetAsFloat: Double; var sng: Single; dw: DWord absolute sng; begin Move(FRawData[0], dw, 4); if BigEndian then dw := BEToN(dw) else dw := LEToN(dw); Result := sng; end; procedure TSingleTag.SetAsFloat(const AValue: Double); var sng: Single; dw: DWord absolute sng; begin sng := AValue; if BigEndian then dw := NToBE(dw) else dw := NToLE(dw); SetLength(FRawData, 4); Move(dw, FRawData[0], 4); end; *) initialization finalization FreeExifTagDefs; end.