You've already forked lazarus-ccr
728 lines
29 KiB
ObjectPascal
728 lines
29 KiB
ObjectPascal
![]() |
unit fetExifLE;
|
||
|
|
||
|
{$IFDEF FPC}
|
||
|
{$mode objfpc}{$H+}
|
||
|
{$ENDIF}
|
||
|
|
||
|
interface
|
||
|
|
||
|
uses
|
||
|
Classes, SysUtils,
|
||
|
{$ifdef FPC}
|
||
|
fpcunit, testutils, testregistry;
|
||
|
{$else}
|
||
|
fetTestUtils, TestFrameWork;
|
||
|
{$endif}
|
||
|
|
||
|
const
|
||
|
// JPEG picture with Exif data
|
||
|
ExifJpegPic = '..\pictures\originals\with_exif.jpg';
|
||
|
WorkFile_JpegWithExif = '.\pictures\with_exif.jpg';
|
||
|
|
||
|
// TIFF pPicture with Exif data
|
||
|
ExifTiffPic = '..\pictures\originals\with_exif.tif';
|
||
|
WorkFile_TiffWithExif = '.\pictures\with_exif.tif';
|
||
|
|
||
|
// Picture without Exif data
|
||
|
NoExifPic = '..\pictures\originals\no_metadata.jpg';
|
||
|
WorkFile_NoExif = '.\pictures\no_exif.jpg';
|
||
|
|
||
|
type
|
||
|
TstExifLE = class(TTestCase)
|
||
|
protected
|
||
|
procedure SetUp; override;
|
||
|
procedure TearDown; override;
|
||
|
procedure Internal_CheckHasExif(AFileName: String; ExpectExif: Boolean);
|
||
|
published
|
||
|
procedure CheckForPictures;
|
||
|
procedure CheckCreateImgInfo;
|
||
|
procedure CheckHasExif;
|
||
|
procedure ReadExifTest_Jpeg;
|
||
|
procedure ReadExifTest_Tiff;
|
||
|
procedure ReadGPSTest;
|
||
|
procedure CreateExifTest;
|
||
|
procedure WriteExifTest_Jpeg;
|
||
|
procedure WriteGPSTest_Jpeg;
|
||
|
procedure ValidFileTest_Jpeg;
|
||
|
procedure CreateThumbnail_Jpeg;
|
||
|
end;
|
||
|
|
||
|
implementation
|
||
|
|
||
|
uses
|
||
|
{$IFDEF FPC}
|
||
|
Graphics, FileUtil,
|
||
|
{$ELSE}
|
||
|
Graphics, Jpeg,
|
||
|
{$ENDIF}
|
||
|
Math,
|
||
|
fpeGlobal, fpeTags, fpeUtils, fpeExifData, fpeMetadata;
|
||
|
|
||
|
procedure TstExifLE.SetUp;
|
||
|
var
|
||
|
dir: String;
|
||
|
begin
|
||
|
if FileExists(Workfile_JpegWithExif) then
|
||
|
DeleteFile(WorkFile_JpegWithExif);
|
||
|
if FileExists(Workfile_TiffWithExif) then
|
||
|
DeleteFile(WorkFile_TiffWithExif);
|
||
|
if FileExists(Workfile_NoExif) then
|
||
|
DeleteFile(Workfile_NoExif);
|
||
|
|
||
|
dir := ExtractFileDir(WorkFile_JpegWithExif);
|
||
|
if not DirectoryExists(dir) then
|
||
|
ForceDirectories(dir);
|
||
|
|
||
|
if not FileExists(WorkFile_JpegWithExif) then
|
||
|
if FileExists(ExifJpegPic) then
|
||
|
CopyFile(ExifJpegPic, WorkFile_JpegWithExif);
|
||
|
if not FileExists(WorkFile_TiffWithExif) then
|
||
|
if FileExists(ExifTiffPic) then
|
||
|
CopyFile(ExifTiffPic, WorkFile_TiffWithExif);
|
||
|
if not FileExists(WorkFile_NoExif) then
|
||
|
if FileExists(NoExifPic) then
|
||
|
CopyFile(NoExifPic, WorkFile_NoExif);
|
||
|
end;
|
||
|
|
||
|
procedure TstExifLE.TearDown;
|
||
|
begin
|
||
|
if FileExists(WorkFile_NoExif) then
|
||
|
DeleteFile(WorkFile_NoExif);
|
||
|
if FileExists(WorkFile_JpegWithExif) then
|
||
|
DeleteFile(WorkFile_JpegWithExif);
|
||
|
if FileExists(WorkFile_TiffWithExif) then
|
||
|
DeleteFile(WorkFile_TiffWithExif);
|
||
|
end;
|
||
|
|
||
|
procedure TstExifLE.CheckForPictures;
|
||
|
begin
|
||
|
CheckTrue(FileExists(ExifJpegPic), 'Original test picture file "' + ExifJpegPic + '" does not exist');
|
||
|
CheckTrue(FileExists(ExifTiffPic), 'Original test picture file "' + ExifTiffPic + '" does not exist');
|
||
|
CheckTrue(FileExists(NoExifPic), 'Original test picture file "' + NoExifPic + '" does not exist');
|
||
|
|
||
|
CheckTrue(FileExists(WorkFile_JpegWithExif), 'Test picture file "' + WorkFile_JpegWithExif + '" does not exist');
|
||
|
CheckTrue(FileExists(WorkFile_TiffWithExif), 'Test picture file "' + WorkFile_TiffWithExif + '" does not exist');
|
||
|
CheckTrue(FileExists(WorkFile_NoExif), 'Test picture file "' + WorkFile_NoExif + '" does not exist');
|
||
|
end;
|
||
|
|
||
|
procedure TstExifLE.CheckCreateImgInfo;
|
||
|
var
|
||
|
imgInfo: TImgInfo;
|
||
|
begin
|
||
|
imgInfo := TImgInfo.Create();
|
||
|
try
|
||
|
CheckIs(imgInfo, TImgInfo,'Is not TImgInfo');
|
||
|
finally
|
||
|
imgInfo.Free;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure TstExifLE.Internal_CheckHasExif(AFileName: String; ExpectExif: Boolean);
|
||
|
var
|
||
|
imgInfo: TImgInfo;
|
||
|
begin
|
||
|
imgInfo := TImgInfo.Create;
|
||
|
try
|
||
|
imgInfo.LoadFromFile(AFileName);
|
||
|
if ExpectExif then
|
||
|
CheckTrue(imgInfo.HasExif, 'Failure to detect EXIF in test picture file "' + AFileName + '"')
|
||
|
else
|
||
|
CheckFalse(imgInfo.HasExif, 'Unexpected EXIF in test picture file "' + AFileName + '" detected');
|
||
|
finally
|
||
|
imgInfo.Free;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure TstExifLE.CheckHasExif;
|
||
|
begin
|
||
|
Internal_CheckHasExif(WorkFile_JpegWithExif, true);
|
||
|
Internal_CheckHasExif(WorkFile_TiffWithExif, true);
|
||
|
Internal_CheckHasExif(WorkFile_NoExif, false);
|
||
|
end;
|
||
|
|
||
|
procedure TstExifLE.ReadExifTest_Jpeg;
|
||
|
{ Output of ExifTool for the jpeg test image with exif using this commandline:
|
||
|
exiftool -G -H -s with_exif.jpg > with_exif.txt
|
||
|
|
||
|
These values are checked
|
||
|
|
|
||
|
[ExifTool] - ExifToolVersion : 10.60
|
||
|
[File] - FileName : with_exif.jpg <--
|
||
|
[File] - Directory : .
|
||
|
[File] - FileSize : 5.0 kB <--
|
||
|
[File] - FileModifyDate : 2017:10:16 19:35:01+02:00
|
||
|
[File] - FileAccessDate : 2017:10:16 19:35:01+02:00
|
||
|
[File] - FileCreateDate : 2017:10:16 19:34:46+02:00
|
||
|
[File] - FilePermissions : rw-rw-rw-
|
||
|
[File] - FileType : JPEG
|
||
|
[File] - FileTypeExtension : jpg
|
||
|
[File] - MIMEType : image/jpeg
|
||
|
[File] - ExifByteOrder : Little-endian (Intel, II) <--
|
||
|
[File] - ImageWidth : 200 <--
|
||
|
[File] - ImageHeight : 150 <--
|
||
|
[File] - EncodingProcess : Baseline DCT, Huffman coding
|
||
|
[File] - BitsPerSample : 8
|
||
|
[File] - ColorComponents : 3
|
||
|
[File] - YCbCrSubSampling : YCbCr4:2:0 (2 2)
|
||
|
[EXIF] 0x010d DocumentName : Test image <--
|
||
|
[EXIF] 0x010e ImageDescription : This is just a test image <--
|
||
|
[EXIF] 0x0112 Orientation : Horizontal (normal) <--
|
||
|
[EXIF] 0x011a XResolution : 72 <--
|
||
|
[EXIF] 0x011b YResolution : 72 <--
|
||
|
[EXIF] 0x0128 ResolutionUnit : inches <--
|
||
|
[EXIF] 0x0131 Software : PhotoFiltre 7 <--
|
||
|
[EXIF] 0x0132 ModifyDate : 2017:10:14 23:35:07 <--
|
||
|
[EXIF] 0x9000 ExifVersion : 0210 <--
|
||
|
[EXIF] 0xa002 ExifImageWidth : 200 <--
|
||
|
[EXIF] 0xa003 ExifImageHeight : 150 <--
|
||
|
[EXIF] 0x0000 GPSVersionID : 2.3.0.0 <--
|
||
|
[EXIF] 0x0001 GPSLatitudeRef : South <--
|
||
|
[EXIF] 0x0003 GPSLongitudeRef : West <--
|
||
|
[Composite] - GPSLatitude : 51 deg 33' 48.28" S <-- fpExif coordinates without the S
|
||
|
[Composite] - GPSLongitude : 59 deg 49' 53.55" W <-- fpExif coordinates without the W
|
||
|
[Composite] - GPSPosition : 51 deg 33' 48.28" S, 59 deg 49' 53.55" W
|
||
|
[Composite] - ImageSize : 200x150
|
||
|
[Composite] - Megapixels : 0.030
|
||
|
}
|
||
|
var
|
||
|
imgInfo: TImgInfo;
|
||
|
lTag: TTag;
|
||
|
begin
|
||
|
imgInfo := TImgInfo.Create;
|
||
|
try
|
||
|
imgInfo.LoadFromFile(WorkFile_JpegWithExif);
|
||
|
|
||
|
// This is general information stored within imgImfInfo
|
||
|
CheckEquals('with_exif.jpg', ExtractFileName(imgInfo.FileName), 'Filename mismatch');
|
||
|
CheckEquals('5.0', Format('%.1f', [imgInfo.FileSize/1024], fpExifFmtSettings), 'File size mismatch');
|
||
|
// CheckEquals('2017:10:14 23:57:49', FormatDateTime(EXIF_DATETIME_FORMAT, imgInfo.FileDate), 'File access date mismatch');
|
||
|
CheckEquals(200, imgInfo.ImgWidth, 'jpeg image width mismatch');
|
||
|
CheckEquals(150, imgInfo.ImgHeight, 'jpeg image height mismatch');
|
||
|
|
||
|
// The following pieces of information are obtained from the EXIF segment
|
||
|
CheckFalse(imgInfo.ExifData.BigEndian, 'Exif byte order detection error');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['DocumentName'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "DocumentName" not found');
|
||
|
CheckEquals('Test image', lTag.AsString, 'Value mismatch of tag "DocumentName"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['ImageDescription'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "ImageDescription" not found');
|
||
|
CheckEquals('This is just a test image', lTag.AsString, 'Value mismatch of tag "ImageDescription"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['Orientation'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "Orientation" not found');
|
||
|
CheckEquals('Horizontal (normal)', lTag.AsString, 'Value mismatch of tag "Orientation"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['XResolution'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "XResolution" not found');
|
||
|
CheckEquals('72', lTag.AsString, 'Value mismatch of tag "XResolution"');
|
||
|
CheckTrue(lTag is TNumericTag, 'Tag "XResolution" is no TNumericTag');
|
||
|
CheckEquals(72, lTag.AsInteger, 'Integer value mismatch of tag "XResolution"');
|
||
|
CheckTrue(lTag is TFloatTag, 'Tag "XResolution" is no TNumericTag');
|
||
|
CheckEquals(72.0, lTag.AsFloat, 'Float value mismatch of tag "XResolution"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['YResolution'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "YResolution" not found');
|
||
|
CheckEquals('72', lTag.AsString, 'Value mismatch of tag "YResolution"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['ResolutionUnit'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "ResolutionUnit" not found');
|
||
|
CheckEquals('inches', lTag.AsString, 'Value mismatch of tag "ResolutionUnit"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['Software'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "Software" not found');
|
||
|
CheckEquals('PhotoFiltre 7', lTag.AsString, 'Value mismatch of tag "Software"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['DateTime'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "DateTime" not found');
|
||
|
CheckTrue(lTag is TDateTimeTag, 'Tag "DateTime" is no TDateTimeTag');
|
||
|
TDateTimeTag(lTag).FormatStr := EXIF_DATETIME_FORMAT;
|
||
|
CheckEquals('2017:10:14 23:35:07', lTag.AsString, 'Value mismatch of tag "DateTime"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['ExifVersion'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "ExifVersion" not found');
|
||
|
CheckTrue(lTag is TVersionTag, 'Tag "ExifVersion" is not TVersionTag');
|
||
|
CheckEquals('0210', lTag.AsString, 'Value mismatch of tag "ExifVersion"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['ExifImageWidth'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "ExifImageWidth" not found');
|
||
|
CheckEquals('200', lTag.AsString, 'Value mismatch of tag "ExifImageWidth"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['ExifImageHeight'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "ExifImageHeight" not found');
|
||
|
CheckEquals('150', lTag.AsString, 'Value mismatch of tag "ExifImageHeight"');
|
||
|
|
||
|
finally
|
||
|
imgInfo.Free;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure TstExifLE.ReadExifTest_Tiff;
|
||
|
{ Output of ExifTool for the tiff test image with exif using this commandline:
|
||
|
exiftool -G -H -s with_exif.tif > with_exif_tif.txt
|
||
|
|
||
|
These values are checked
|
||
|
|
|
||
|
[ExifTool] - ExifToolVersion : 10.60
|
||
|
[File] - FileName : with_exif.tif
|
||
|
[File] - Directory : .
|
||
|
[File] - FileSize : 88 kB
|
||
|
[File] - FileModifyDate : 2017:10:16 10:07:38+02:00
|
||
|
[File] - FileAccessDate : 2017:10:16 10:07:38+02:00
|
||
|
[File] - FileCreateDate : 2017:10:16 10:07:38+02:00
|
||
|
[File] - FilePermissions : rw-rw-rw-
|
||
|
[File] - FileType : TIFF
|
||
|
[File] - FileTypeExtension : tif
|
||
|
[File] - MIMEType : image/tiff
|
||
|
[File] - ExifByteOrder : Little-endian (Intel, II)
|
||
|
[EXIF] 0x0100 ImageWidth : 200
|
||
|
[EXIF] 0x0101 ImageHeight : 150
|
||
|
[EXIF] 0x0102 BitsPerSample : 8 8 8
|
||
|
[EXIF] 0x0103 Compression : Uncompressed
|
||
|
[EXIF] 0x0106 PhotometricInterpretation : RGB
|
||
|
[EXIF] 0x0111 StripOffsets : (Binary data 68 bytes, use -b option to extract)
|
||
|
[EXIF] 0x0115 SamplesPerPixel : 3
|
||
|
[EXIF] 0x0116 RowsPerStrip : 13
|
||
|
[EXIF] 0x0117 StripByteCounts : (Binary data 59 bytes, use -b option to extract)
|
||
|
[EXIF] 0x011a XResolution : 72
|
||
|
[EXIF] 0x011b YResolution : 72
|
||
|
[EXIF] 0x011c PlanarConfiguration : Chunky
|
||
|
[EXIF] 0x0128 ResolutionUnit : inches
|
||
|
[EXIF] 0x0131 Software : LIBFORMAT (c) Pierre-e Gougelet
|
||
|
[EXIF] 0x0132 ModifyDate : 2017:10:14 23:35:07
|
||
|
[EXIF] 0x9000 ExifVersion : 0210
|
||
|
[EXIF] 0xa002 ExifImageWidth : 200
|
||
|
[Composite] - ImageSize : 200x150
|
||
|
[Composite] - Megapixels : 0.030 }
|
||
|
var
|
||
|
imgInfo: TImgInfo;
|
||
|
lTag: TTag;
|
||
|
begin
|
||
|
imgInfo := TImgInfo.Create;
|
||
|
try
|
||
|
imgInfo.LoadFromFile(WorkFile_TiffWithExif);
|
||
|
|
||
|
// This is general information stored within imgImfInfo
|
||
|
CheckEquals('with_exif.tif', ExtractFileName(imgInfo.FileName), 'Filename mismatch');
|
||
|
CheckEquals('88', Format('%.0f', [imgInfo.FileSize/1024], fpExifFmtSettings), 'File size mismatch');
|
||
|
|
||
|
// The following pieces of information are obtained from the EXIF segment
|
||
|
CheckFalse(imgInfo.ExifData.BigEndian, 'Exif byte order detection error');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['ImageWidth'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "ImageWidth" not found');
|
||
|
CheckEquals(200, lTag.AsInteger, 'Value mismatch of tag "ImageWidth"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['ImageHeight'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "ImageHeight" not found');
|
||
|
CheckEquals(150, lTag.AsInteger, 'Value mismatch of tag "ImageHeight"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['BitsPerSample'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "BitsPerSample" not found');
|
||
|
CheckEquals('8,8,8', lTag.AsString, 'Value mismatch of tag "BitsPerSample"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['Compression'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "Compression" not found');
|
||
|
CheckEquals('Uncompressed', lTag.AsString, 'Value mismatch of tag "Compression"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['PhotometricInterpretation'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "PhotometricInterpretation" not found');
|
||
|
CheckEquals('RGB', lTag.AsString, 'Value mismatch of tag "PhotometricInterpretation"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['SamplesPerPixel'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "SamplesPerPixel" not found');
|
||
|
CheckEquals(3, lTag.AsInteger, 'Value mismatch of tag "SamplesPerPixel"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['RowsPerStrip'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "RowsPerStrip" not found');
|
||
|
CheckEquals(13, lTag.AsInteger, 'Value mismatch of tag "RowsPerStrip"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['XResolution'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "XResolution" not found');
|
||
|
CheckEquals(72, lTag.AsInteger, 'Integer value mismatch of tag "XResolution"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['YResolution'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "YResolution" not found');
|
||
|
CheckEquals(72, lTag.AsInteger, 'Value mismatch of tag "YResolution"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['PlanarConfiguration'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "PlanarConfiguration" not found');
|
||
|
CheckEquals('Chunky', lTag.AsString, 'Value mismatch of tag "PlanarConfiguration"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['ResolutionUnit'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "ResolutionUnit" not found');
|
||
|
CheckEquals('inches', lTag.AsString, 'Value mismatch of tag "ResolutionUnit"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['Software'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "Software" not found');
|
||
|
CheckEquals('LIBFORMAT (c) Pierre-e Gougelet', lTag.AsString, 'Value mismatch of tag "Software"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['DateTime'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "DateTime" not found');
|
||
|
CheckTrue(lTag is TDateTimeTag, 'Tag "DateTime" is no TDateTimeTag');
|
||
|
TDateTimeTag(lTag).FormatStr := EXIF_DATETIME_FORMAT;
|
||
|
CheckEquals('2017:10:14 23:35:07', lTag.AsString, 'Value mismatch of tag "DateTime"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['ExifVersion'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "ExifVersion" not found');
|
||
|
CheckTrue(lTag is TVersionTag, 'Tag "ExifVersion" is not TVersionTag');
|
||
|
CheckEquals('0210', lTag.AsString, 'Value mismatch of tag "ExifVersion"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['ExifImageWidth'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "ExifImageWidth" not found');
|
||
|
CheckEquals(200, lTag.AsInteger, 'Value mismatch of tag "ExifImageWidth"');
|
||
|
|
||
|
finally
|
||
|
imgInfo.Free;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
|
||
|
{ This test read the GPS data contained in file "with_exif.jpg". The GPS
|
||
|
data were written there using the service https://www.geoimgr.com/
|
||
|
See expected values in comment of "ReadExifTest". }
|
||
|
procedure TstExifLE.ReadGPSTest;
|
||
|
var
|
||
|
imgInfo: TImgInfo;
|
||
|
lTag: TTag;
|
||
|
begin
|
||
|
imgInfo := TImgInfo.Create;
|
||
|
try
|
||
|
imgInfo.LoadFromFile(WorkFile_JpegWithExif);
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['GPSVersionID'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "GPSVersionID" not found');
|
||
|
CheckTrue(lTag is TVersionTag, 'Tag "GPSVersionID" is not TVersionTag');
|
||
|
TVersionTag(lTag).Separator := '.';
|
||
|
CheckEquals('2.3.0.0', lTag.AsString, 'Value mismatch of tag "GPSVersionID"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['GPSLatitude'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "GPSLatitude" not found');
|
||
|
CheckTrue(lTag is TGPSPositionTag, 'Tag "GPSLatitude" is not a TGpsPositionTag');
|
||
|
TGpsPositionTag(lTag).FormatStr := '%0:.0f deg %1:.0f'' %2:.2f"';
|
||
|
CheckEquals('51 deg 33'' 48.28"', lTag.AsString, 'Value mismatch of tag "GPSLatitude"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['GPSLatitudeRef'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "GPSLatitudeRef" not found');
|
||
|
CheckEquals('South', lTag.AsString, 'Value mismatch of tag "GPSLatitudeRef"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['GPSLongitude'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "GPSLongitude" not found');
|
||
|
CheckTrue(lTag is TGPSPositionTag, 'Tag "GPSLongitude" is not a TGpsPositionTag');
|
||
|
TGpsPositionTag(lTag).FormatStr := '%0:.0f deg %1:.0f'' %2:.2f"';
|
||
|
CheckEquals('59 deg 49'' 53.55"', lTag.AsString, 'Value mismatch of tag "GPSLongitude"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['GPSLongitudeRef'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "GPSLongitudeRef" not found');
|
||
|
CheckEquals('West', lTag.AsString, 'Value mismatch of tag "GPSLongitudeRef"');
|
||
|
|
||
|
finally
|
||
|
imgInfo.Free;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
{ This test creates a new empty exif structure, but does not write anything to
|
||
|
file. }
|
||
|
procedure TstExifLE.CreateExifTest;
|
||
|
var
|
||
|
imgInfo: TImgInfo;
|
||
|
begin
|
||
|
imgInfo := TImgInfo.Create;
|
||
|
try
|
||
|
CheckTrue(imgInfo.ExifData = nil, 'EXIF found, but not expected.');
|
||
|
imgInfo.CreateExifData;
|
||
|
CheckTrue(imgInfo.ExifData <> nil, 'EXIF not found.');
|
||
|
finally
|
||
|
imgInfo.Free;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
{ This test creates an empty EXIF structure, fills it with some data and saves
|
||
|
it to the No_exif file. After writing the file is read back and compared
|
||
|
with the written data. }
|
||
|
procedure TstExifLE.WriteExifTest_Jpeg;
|
||
|
var
|
||
|
imgInfo: TImgInfo;
|
||
|
lTag: TTag;
|
||
|
begin
|
||
|
imgInfo := TImgInfo.Create;
|
||
|
try
|
||
|
// Create empty EXIF
|
||
|
imgInfo.CreateExifData;
|
||
|
|
||
|
// Add tags
|
||
|
lTag := imgInfo.ExifData.AddTagByName('Primary.DocumentName');
|
||
|
lTag.AsString := 'Test image';
|
||
|
|
||
|
lTag := imgInfo.ExifData.AddTagByName('ImageDescription');
|
||
|
lTag.AsString := 'This is just a test image';
|
||
|
|
||
|
lTag := imgInfo.ExifData.AddTagByName('Orientation');
|
||
|
CheckTrue(lTag <> nil, 'Tag "Orientation" not found for writing');
|
||
|
lTag.AsString := 'Horizontal (normal)';
|
||
|
|
||
|
lTag := imgInfo.ExifData.AddTagByName('XResolution');
|
||
|
CheckTrue(lTag <> nil, 'Tag "XResolution" not found for writing');
|
||
|
lTag.AsInteger := 72;
|
||
|
|
||
|
lTag := imgInfo.ExifData.AddTagByName('YResolution');
|
||
|
CheckTrue(lTag <> nil, 'Tag "YResolution" not found for writing');
|
||
|
lTag.AsInteger := 72;
|
||
|
|
||
|
lTag := imgInfo.ExifData.AddTagByName('ResolutionUnit');
|
||
|
CheckTrue(lTag <> nil, 'Tag "ResolutionUnit" not found for writing');
|
||
|
ltag.AsString := 'inches';
|
||
|
|
||
|
lTag := imgInfo.ExifData.AddTagByName('Software');
|
||
|
CheckTrue(lTag <> nil, 'Tag "Software" not found for writing');
|
||
|
lTag.AsString := 'FPC/fpExif';
|
||
|
|
||
|
lTag := imgInfo.ExifData.AddTagByName('DateTime');
|
||
|
CheckTrue(lTag <> nil, 'Tag "DateTime" not found for writing');
|
||
|
CheckTrue(lTag is TDateTimeTag, 'Tag "DateTime" is no TDateTimeTag');
|
||
|
TDateTimeTag(lTag).AsDateTime := EncodeDate(2017,10,14) + EncodeTime(23,35,07,0);
|
||
|
|
||
|
lTag := imgInfo.ExifData.AddTagByName('ExifVersion');
|
||
|
CheckTrue(lTag <> nil, 'Tag "ExifVersion" not found for writing');
|
||
|
CheckTrue(lTag is TVersionTag, 'Tag "ExifVersion" is not TVersionTag');
|
||
|
TVersionTag(lTag).AsString := '0210';
|
||
|
|
||
|
lTag := imgInfo.ExifData.AddTagByName('ExifImageWidth');
|
||
|
CheckTrue(lTag <> nil, 'Tag "ExifImageWidth" not found for writing');
|
||
|
lTag.AsInteger := 200;
|
||
|
|
||
|
lTag := imgInfo.ExifData.AddTagByName('ExifImageHeight');
|
||
|
CheckTrue(lTag <> nil, 'Tag "ExifImageHeight" not found for writing');
|
||
|
lTag.AsInteger := 150;
|
||
|
|
||
|
// Save to file;
|
||
|
// Takes the image data from WorkFile_WithExif, replaces its EXIF with the
|
||
|
// current EXIF structure and writes to WorkFile_NoExif.
|
||
|
imgInfo.SaveToFile(WorkFile_NoExif, Workfile_JpegWithExif);
|
||
|
finally
|
||
|
imgInfo.Free;
|
||
|
end;
|
||
|
|
||
|
// Read written file and check EXIF
|
||
|
imgInfo := TImgInfo.Create;
|
||
|
try
|
||
|
imgInfo.LoadFromFile(Workfile_NoExif);
|
||
|
// Now there should be EXIF
|
||
|
CheckTrue(imgInfo.ExifData <> nil, 'EXIF not found.');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['DocumentName'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "DocumentName" not found for reading');
|
||
|
CheckEquals('Test image', lTag.AsString, 'Value mismatch of tag "DocumentName"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['ImageDescription'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "ImageDescription" not found for reading');
|
||
|
CheckEquals('This is just a test image', lTag.AsString, 'Value mismatch of tag "ImageDescription"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['Orientation'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "Orientation" not found for reading');
|
||
|
CheckEquals('Horizontal (normal)', lTag.AsString, 'Value mismatch of tag "Orientation"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['XResolution'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "XResolution" not found for reading');
|
||
|
CheckEquals(72, lTag.AsInteger, 'Integer value mismatch of tag "XResolution"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['YResolution'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "YResolution" not found for reading');
|
||
|
CheckEquals('72', lTag.AsString, 'Value mismatch of tag "YResolution"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['ResolutionUnit'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "ResolutionUnit" not found for reading');
|
||
|
CheckEquals('inches', lTag.AsString, 'Value mismatch of tag "ResolutionUnit"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['Software'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "Software" not found for reading');
|
||
|
CheckEquals('FPC/fpExif', lTag.AsString, 'Value mismatch of tag "Software"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['DateTime'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "DateTime" not found for reading');
|
||
|
CheckTrue(lTag is TDateTimeTag, 'Tag "DateTime" is no TDateTimeTag');
|
||
|
TDateTimeTag(lTag).FormatStr := ISO_DATETIME_FORMAT;
|
||
|
CheckEquals('2017-10-14 23:35:07', lTag.AsString, 'Value mismatch of tag "DateTime"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['ExifVersion'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "ExifVersion" not found for reading');
|
||
|
CheckTrue(lTag is TVersionTag, 'Tag "ExifVersion" is not TVersionTag');
|
||
|
CheckEquals('0210', lTag.AsString, 'Value mismatch of tag "ExifVersion"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['ExifImageWidth'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "ExifImageWidth" not found for reading');
|
||
|
CheckEquals('200', lTag.AsString, 'Value mismatch of tag "ExifImageWidth"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['ExifImageHeight'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "ExifImageHeight" not found for reading');
|
||
|
CheckEquals('150', lTag.AsString, 'Value mismatch of tag "ExifImageHeight"');
|
||
|
|
||
|
finally
|
||
|
imgInfo.Free;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
{ This test loads the With_Exif and changes the GPS data, saves to the same file,
|
||
|
reads back and checks validity of the GPS data. }
|
||
|
procedure TstExifLE.WriteGpsTest_Jpeg;
|
||
|
var
|
||
|
imgInfo: TImgInfo;
|
||
|
lTag: TTag;
|
||
|
begin
|
||
|
imgInfo := TImgInfo.Create;
|
||
|
try
|
||
|
imgInfo.LoadfromFile(Workfile_JpegWithExif);
|
||
|
// In spite of its name, the file must contain EXIF now, written in prev test.
|
||
|
CheckTrue(imgInfo.ExifData <> nil, 'EXIF not found.');
|
||
|
|
||
|
// Add tags
|
||
|
lTag := imgInfo.ExifData.AddTagByName('GPSVersionID');
|
||
|
CheckTrue(lTag <> nil, 'Tag "GPSVersionID" not found');
|
||
|
CheckTrue(lTag is TVersionTag, 'Tag "GPSVersionID" is not TVersionTag');
|
||
|
TVersionTag(lTag).Separator := '.';
|
||
|
lTag.AsString := '2.3.1.1';
|
||
|
|
||
|
lTag := imgInfo.ExifData.AddTagByName('GPSLatitude');
|
||
|
CheckTrue(lTag <> nil, 'Tag "GPSLatitude" not found');
|
||
|
CheckTrue(lTag is TGPSPositionTag, 'Tag "GPSLatitude" is not a TGpsPositionTag');
|
||
|
TGpsPositionTag(lTag).FormatStr := '%0:.0f deg %1:.0f'' %2:.2f"';
|
||
|
lTag.AsString := '45 deg 30'' 15.32"';
|
||
|
|
||
|
lTag := imgInfo.ExifData.AddTagByName('GPSLatitudeRef');
|
||
|
CheckTrue(lTag <> nil, 'Tag "GPSLatitudeRef" not found');
|
||
|
lTag.AsString := 'North';
|
||
|
|
||
|
lTag := imgInfo.ExifData.AddTagByName('GPSLongitude');
|
||
|
CheckTrue(lTag <> nil, 'Tag "GPSLongitude" not found');
|
||
|
CheckTrue(lTag is TGPSPositionTag, 'Tag "GPSLongitude" is not a TGpsPositionTag');
|
||
|
TGpsPositionTag(lTag).FormatStr := '%0:.0f deg %1:.0f'' %2:.2f"';
|
||
|
lTag.AsString := '15 deg 16'' 17.18"';
|
||
|
|
||
|
lTag := imgInfo.ExifData.AddTagByName('GPSLongitudeRef');
|
||
|
CheckTrue(lTag <> nil, 'Tag "GPSLongitudeRef" not found');
|
||
|
lTag.AsString := 'East';
|
||
|
|
||
|
// Save to file
|
||
|
imgInfo.SaveToFile(WorkFile_JpegWithExif);
|
||
|
finally
|
||
|
imgInfo.Free;
|
||
|
end;
|
||
|
|
||
|
imgInfo := TImgInfo.Create;
|
||
|
try
|
||
|
imgInfo.LoadFromFile(Workfile_JpegWithExif);
|
||
|
CheckTrue(imgInfo.ExifData <> nil, 'EXIF not found after writing.');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['GPSVersionID'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "GPSVersionID" not found');
|
||
|
CheckTrue(lTag is TVersionTag, 'Tag "GPSVersionID" is not TVersionTag');
|
||
|
TVersionTag(lTag).Separator := '.';
|
||
|
CheckEquals('2.3.1.1', lTag.AsString, 'Value mismatch of tag "GPSVersionID"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['GPSLatitude'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "GPSLatitude" not found');
|
||
|
CheckTrue(lTag is TGPSPositionTag, 'Tag "GPSLatitude" is not a TGpsPositionTag');
|
||
|
TGpsPositionTag(lTag).FormatStr := '%0:.0f deg %1:.0f'' %2:.2f"';
|
||
|
CheckEquals('45 deg 30'' 15.32"', lTag.AsString, 'Value mismatch of tag "GPSLatitude"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['GPSLatitudeRef'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "GPSLatitudeRef" not found');
|
||
|
CheckEquals('North', lTag.AsString, 'Value mismatch of tag "GPSLatitudeRef"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['GPSLongitude'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "GPSLongitude" not found');
|
||
|
CheckTrue(lTag is TGPSPositionTag, 'Tag "GPSLongitude" is not a TGpsPositionTag');
|
||
|
TGpsPositionTag(lTag).FormatStr := '%0:.0f deg %1:.0f'' %2:.2f"';
|
||
|
CheckEquals('15 deg 16'' 17.18"', lTag.AsString, 'Value mismatch of tag "GPSLongitude"');
|
||
|
|
||
|
lTag := imgInfo.ExifData.TagByName['GPSLongitudeRef'];
|
||
|
CheckTrue(lTag <> nil, 'Tag "GPSLongitudeRef" not found');
|
||
|
CheckEquals('East', lTag.AsString, 'Value mismatch of tag "GPSLongitudeRef"');
|
||
|
|
||
|
finally
|
||
|
imgInfo.Free;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure TstExifLE.ValidFileTest_Jpeg;
|
||
|
var
|
||
|
jpg: TJpegImage;
|
||
|
fn: string;
|
||
|
bmp: TBitmap;
|
||
|
success: Boolean;
|
||
|
begin
|
||
|
// Modify the EXIF structure of WorkFile_WithExif;
|
||
|
fn := Workfile_JpegWithExif;
|
||
|
WriteExifTest_Jpeg;
|
||
|
success := false;
|
||
|
jpg := TJpegImage.Create;
|
||
|
try
|
||
|
jpg.LoadFromFile(fn);
|
||
|
bmp := TBitmap.Create;
|
||
|
try
|
||
|
bmp.Width := jpg.Width;
|
||
|
bmp.Height := jpg.Height;
|
||
|
bmp.Canvas.Draw(0, 0, jpg);
|
||
|
success := true;
|
||
|
finally
|
||
|
bmp.Free;
|
||
|
CheckTrue(success, 'Non-readable file');
|
||
|
end;
|
||
|
finally
|
||
|
jpg.Free;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure TstExifLE.CreateThumbnail_Jpeg;
|
||
|
const
|
||
|
THUMBSIZE = 120;
|
||
|
var
|
||
|
imgInfo: TImgInfo;
|
||
|
srcStream, destStream: TMemoryStream;
|
||
|
destStreamSize, currentThumbSize: Int64;
|
||
|
w, h: Integer;
|
||
|
begin
|
||
|
imgInfo := TImgInfo.Create;
|
||
|
try
|
||
|
imgInfo.LoadFromFile(Workfile_JpegWithExif);
|
||
|
CheckTrue(imgInfo.ExifData <> nil, 'EXIF not found.');
|
||
|
CheckFalse(imgInfo.ExifData.HasThumbnail, 'Presence of thumbnail not expected');
|
||
|
|
||
|
srcStream := TMemoryStream.Create;
|
||
|
destStream := TMemoryStream.Create;
|
||
|
try
|
||
|
srcStream.LoadFromFile(Workfile_JpegWithExif);
|
||
|
JPEGScaleImage(srcStream, destStream, THUMBSIZE);
|
||
|
destStreamSize := destStream.Size;
|
||
|
destStream.Position := 0;
|
||
|
imgInfo.ExifData.LoadThumbnailFromStream(deststream);
|
||
|
finally
|
||
|
destStream.Free;
|
||
|
srcStream.Free;
|
||
|
end;
|
||
|
|
||
|
CheckTrue(imgInfo.ExifData.HasThumbnail, 'Thumbnail not found.');
|
||
|
w := imgInfo.ExifData.TagByName['ThumbnailWidth'].AsInteger;
|
||
|
h := imgInfo.ExifData.TagByName['ThumbnailHeight'].AsInteger;
|
||
|
currentThumbSize := imgInfo.ExifData.TagByName['ThumbnailSize'].AsInteger;
|
||
|
CheckEquals(THUMBSIZE, Max(w, h), 'Thumbnailsize mismatch');
|
||
|
CheckEquals(destStreamSize, currentThumbSize, 'Thumbnail size mismatch');
|
||
|
|
||
|
finally
|
||
|
imgInfo.Free;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
|
||
|
|
||
|
initialization
|
||
|
{$IFDEF FPC}
|
||
|
RegisterTest(TstExifLE);
|
||
|
{$ELSE}
|
||
|
TestFramework.RegisterTest(TstExifLE.Suite);
|
||
|
{$ENDIF}
|
||
|
|
||
|
end.
|
||
|
|