You've already forked lazarus-ccr
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@6080 8e941d3f-bd1b-0410-a28a-d453659cc2b4
631 lines
15 KiB
ObjectPascal
631 lines
15 KiB
ObjectPascal
unit rwMain;
|
|
|
|
{$I ..\..\..\fpExif.inc}
|
|
|
|
{$IFDEF FPC}
|
|
{$MODE DELPHI}
|
|
{$ENDIF}
|
|
|
|
interface
|
|
|
|
uses
|
|
{$IFDEF FPC}
|
|
LazUtf8,
|
|
{$ELSE}
|
|
Windows, Messages, ImgList, jpeg,
|
|
{$IFDEF UNICODE}
|
|
System.ImageList,
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
Classes, SysUtils, Forms, Controls, Graphics, Dialogs,
|
|
StdCtrls, Buttons, ComCtrls, ExtCtrls, Variants,
|
|
fpeGlobal, fpeTags, fpeMetadata;
|
|
|
|
type
|
|
|
|
{ TMainForm }
|
|
|
|
TMainForm = class(TForm)
|
|
BtnTest1: TSpeedButton;
|
|
BtnTest2: TSpeedButton;
|
|
CbTestfile: TComboBox;
|
|
ImageList1: TImageList;
|
|
Label1: TLabel;
|
|
ListView: TListView;
|
|
OpenDialog: TOpenDialog;
|
|
Panel1: TPanel;
|
|
Panel2: TPanel;
|
|
ExifListView: TListView;
|
|
ExifTabControl: TTabControl;
|
|
BtnBrowse: TSpeedButton;
|
|
Splitter1: TSplitter;
|
|
procedure CbTestfileEditingDone(Sender: TObject);
|
|
procedure FormCreate(Sender: TObject);
|
|
procedure FormDestroy(Sender: TObject);
|
|
procedure BtnTest1Click(Sender: TObject);
|
|
procedure ExifTabControlChange(Sender: TObject);
|
|
procedure BtnBrowseClick(Sender: TObject);
|
|
private
|
|
ImgInfo: TImgInfo;
|
|
OutFile: String;
|
|
procedure ExecTest(const AParamsFile: String);
|
|
procedure ExifToListview(AImgInfo: TImgInfo; AListView: TListView);
|
|
function ReadTagValue(ATagName: String; out ATag: TTag): String; overload;
|
|
function ReadTagValue(ATagName: String): String; overload;
|
|
function Success(ATag: TTag; ACurrValue, AExpectedValue: String): Boolean;
|
|
procedure WriteTagValue(ATagName, ATagValue: String);
|
|
|
|
procedure AddToHistory(AFilename: String);
|
|
procedure ReadFromIni;
|
|
procedure WriteToIni;
|
|
|
|
public
|
|
procedure BeforeRun;
|
|
|
|
end;
|
|
|
|
var
|
|
MainForm: TMainForm;
|
|
|
|
implementation
|
|
|
|
{$IFDEF FPC}
|
|
{$R *.lfm}
|
|
{$ELSE}
|
|
{$R *.dfm}
|
|
{$ENDIF}
|
|
|
|
uses
|
|
StrUtils, Math, IniFiles,
|
|
fpeUtils, fpeExifData;
|
|
|
|
const
|
|
IMGINDEX_SUCCESS = 0;
|
|
IMGINDEX_FAIL = 1;
|
|
|
|
TESTCASES_DIR = 'common\';
|
|
|
|
type
|
|
TStringArray = array of string;
|
|
|
|
function Split(s: String; AMinCount: Integer; Separator: Char = #9): TStringArray;
|
|
const
|
|
BLOCK_SIZE = 20;
|
|
var
|
|
i, j, n, L: Integer;
|
|
begin
|
|
if s = '' then begin
|
|
SetLength(Result, 0);
|
|
exit;
|
|
end;
|
|
|
|
s := s + Separator;
|
|
L := Length(s);
|
|
SetLength(Result, BLOCK_SIZE);
|
|
i := 1;
|
|
j := 1;
|
|
n := 0;
|
|
while (i <= L) do begin
|
|
if (s[i] = Separator) or (i = L) then begin
|
|
Result[n] := Copy(s, j, i-j);
|
|
inc(n);
|
|
if n mod BLOCK_SIZE = 0 then
|
|
SetLength(Result, Length(Result) + BLOCK_SIZE);
|
|
j := i+1;
|
|
end;
|
|
inc(i);
|
|
end;
|
|
while n < AMinCount do begin
|
|
Result[n] := '';
|
|
inc(n);
|
|
if n mod BLOCK_SIZE = 0 then
|
|
SetLength(Result, Length(Result) + BLOCK_SIZE);
|
|
end;
|
|
SetLength(Result, n);
|
|
end;
|
|
|
|
{ The date/time string is expected in the ISO format "yyyy-mm-dd hh:nn:ss" }
|
|
function ExtractDateTime(AValue: String): TDateTime;
|
|
var
|
|
p: Integer;
|
|
yr, mn, dy, h, m, s: Integer;
|
|
begin
|
|
Result := 0;
|
|
p := pos('-', AValue);
|
|
if p = 0 then
|
|
raise Exception.Create('ISO date/time format expected: "yyyy-mm-dd hh:nn:ss"');
|
|
yr := StrToInt(copy(AValue, 1, p-1));
|
|
Delete(AValue, 1, p);
|
|
p := pos('-', AValue);
|
|
if p = 0 then
|
|
raise Exception.Create('ISO date/time format expected: "yyyy-mm-dd hh:nn:ss"');
|
|
mn := StrToInt(copy(AValue, 1, p-1));
|
|
Delete(AValue, 1, p);
|
|
p := pos(' ', AValue);
|
|
if p = 0 then begin
|
|
dy := StrToInt(AValue);
|
|
Result := EncodeDate(yr, mn, dy);
|
|
exit;
|
|
end;
|
|
dy := StrToInt(copy(AValue, 1, p-1));
|
|
Delete(AValue, 1, p);
|
|
p := pos(':', AValue);
|
|
if p = 0 then
|
|
raise Exception.Create('ISO date/time format expected: "yyyy-mm-dd hh:nn:ss"');
|
|
h := StrToInt(copy(AValue, 1, p-1));
|
|
Delete(AValue, 1, p);
|
|
p := pos(':', AValue);
|
|
if p = 0 then begin
|
|
m := StrToInt(AValue);
|
|
s := 0;
|
|
end else begin
|
|
m := StrToInt(copy(AValue, 1, p-1));
|
|
s := StrToInt(copy(AValue, p+1, MaxInt));
|
|
end;
|
|
Result := EncodeDate(yr, mn, dy) + EncodeTime(h, m, s, 0);
|
|
end;
|
|
|
|
function DecimalSep: Char;
|
|
begin
|
|
{$IFDEF FPC}
|
|
Result := FormatSettings.DecimalSeparator;
|
|
{$ELSE}
|
|
{$IFDEF VER150} // Delphi 7
|
|
Result := DecimalSeparator;
|
|
{$ELSE}
|
|
Result := FormatSettings.DecimalSeparator;
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function CleanFloatStr(AText: String): String;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result := '';
|
|
i := 1;
|
|
while i <= Length(AText) do begin
|
|
// case aperture value, e.g. "F/2.8"
|
|
if (i < Length(AText)) and (AText[i] in ['f', 'F']) and (AText[i+1] = '/') then
|
|
inc(i)
|
|
else
|
|
if AText[i] in ['0'..'9', '.', '/'] then
|
|
Result := Result + AText[i]
|
|
else if AText[i] = ',' then
|
|
Result := Result + '.';
|
|
inc(i);
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TMainForm }
|
|
|
|
procedure TMainForm.AddToHistory(AFileName: String);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if (AFileName = '') or (not FileExists(AFileName)) then
|
|
exit;
|
|
|
|
i := CbTestFile.Items.Indexof(AFileName);
|
|
if i > -1 then
|
|
CbTestfile.Items.Delete(i);
|
|
CbTestFile.Items.Insert(0, AFileName);
|
|
CbTestFile.ItemIndex := 0;
|
|
end;
|
|
|
|
procedure TMainForm.BeforeRun;
|
|
begin
|
|
ReadFromIni;
|
|
end;
|
|
|
|
procedure TMainForm.FormCreate(Sender: TObject);
|
|
begin
|
|
ImgInfo := TImgInfo.Create;
|
|
end;
|
|
|
|
procedure TMainForm.FormDestroy(Sender: TObject);
|
|
begin
|
|
WriteToIni;
|
|
ImgInfo.Free;
|
|
end;
|
|
|
|
procedure TMainForm.BtnTest1Click(Sender: TObject);
|
|
begin
|
|
AddToHistory(CbTestFile.Text);
|
|
if Sender = BtnTest1 then
|
|
ExecTest(TESTCASES_DIR + 'testcases1.txt')
|
|
else if Sender = BtnTest2 then
|
|
ExecTest(TESTCASES_DIR + 'testcases2.txt')
|
|
else
|
|
raise Exception.Create('BtnTextClick: Unexpected Sender');
|
|
end;
|
|
|
|
procedure TMainForm.CbTestfileEditingDone(Sender: TObject);
|
|
begin
|
|
AddToHistory(CbTestFile.Text);
|
|
end;
|
|
|
|
procedure TMainForm.ExecTest(const AParamsFile: String);
|
|
var
|
|
testCases: TStringList;
|
|
i, j, n, p: Integer;
|
|
s: String;
|
|
testdata: TStringArray;
|
|
listitem: TListItem;
|
|
lTag: TTag;
|
|
tagName: String;
|
|
currTagValue: String;
|
|
newTagValue: String;
|
|
newTagValues: TStringArray;
|
|
jpeg: TJpegImage;
|
|
{$IFDEF FPC}
|
|
stream: TMemorystream;
|
|
{$ELSE}
|
|
stream: TMemoryStream;
|
|
a: ansistring;
|
|
{$ENDIF}
|
|
begin
|
|
Listview.Items.Clear;
|
|
|
|
if not FileExists(AParamsFile) then begin
|
|
showMessage('Parameter file "' + AParamsFile + '" not found.');
|
|
exit;
|
|
end;
|
|
if not FileExists(CbTestfile.Text) then begin
|
|
ShowMessage('Test picture file "' + CbTestfile.Text + '" not found.');
|
|
exit;
|
|
end;
|
|
|
|
// Read test parameters
|
|
testCases := TStringList.Create;
|
|
try
|
|
|
|
{$IFDEF FPC}
|
|
// The testcases text files are encoded in ANSI for Delphi7 compatibility
|
|
// In Lazarus we must convert to UTF8 }
|
|
testCases.LoadFromFile(AParamsFile);
|
|
s := testCases.Text;
|
|
{$IFDEF FPC3+}
|
|
testCases.Text := WinCPToUTF8(s);
|
|
{$ELSE}
|
|
testCases.Text := AnsiToUTF8(s);
|
|
{$ENDIF}
|
|
{$ELSE}
|
|
stream := TMemoryStream.Create;
|
|
try
|
|
stream.LoadFromFile(AParamsFile);
|
|
SetLength(a, stream.Size);
|
|
stream.Read(a[1], Length(a));
|
|
testcases.Text := a;
|
|
finally
|
|
stream.Free;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
// Read EXIF tags from image file
|
|
ImgInfo.LoadFromFile(CbTestfile.Text);
|
|
if not ImgInfo.HasExif then
|
|
ImgInfo.CreateExifData(false);
|
|
|
|
OutFile := 'test-image.jpg'; // File name of the modified test image
|
|
|
|
ListView.Items.BeginUpdate;
|
|
try
|
|
j := 0;
|
|
n := testCases.Count;
|
|
for i:=0 to n-1 do begin
|
|
if (testCases[i] = ':quit') then
|
|
break;
|
|
|
|
if (testCases[i] = '') or (testCases[i][1] = ';') then
|
|
Continue;
|
|
|
|
// Extract test parameters
|
|
testdata := Split(testCases[i], 2);
|
|
tagName := testdata[0];
|
|
newTagValue := testdata[1];
|
|
newTagValues := Split(newTagValue, 2, '|');
|
|
if Length(newTagValues) =0 then begin
|
|
SetLength(newTagValues, 1);
|
|
newTagValues[0] := '';
|
|
end;
|
|
|
|
// Add test to listview
|
|
listitem := ListView.Items.Add;
|
|
listItem.Caption := tagname;
|
|
|
|
// Read current tag value
|
|
currTagValue := ReadTagValue(tagName, lTag);
|
|
listItem.SubItems.Add(currTagValue);
|
|
listItem.Data := lTag;
|
|
|
|
// Write new tag value into ExifObj
|
|
WriteTagValue(tagName, newTagValues[0]);
|
|
listItem.SubItems.Add(newTagValue);
|
|
end;
|
|
finally
|
|
ListView.Items.EndUpdate;
|
|
end;
|
|
|
|
// Write new tags to file
|
|
ImgInfo.SaveToFile(OutFile);
|
|
|
|
// read back
|
|
ImgInfo.LoadFromFile(OutFile);
|
|
if not ImgInfo.HasExif then
|
|
raise Exception.Create('No EXIF structure detected in "' + Outfile + '"');
|
|
|
|
j := 0;
|
|
for i:=0 to testCases.Count-1 do begin
|
|
if (testcases[i] = ':quit') then
|
|
break;
|
|
if (testcases[i] = '') or (testcases[i][1] = ';') then
|
|
Continue;
|
|
testdata := Split(testCases[i], 2);
|
|
tagname := testdata[0];
|
|
newTagValue := testdata[1];
|
|
currTagValue := ReadTagValue(tagname, lTag);
|
|
listItem := ListView.Items[j];
|
|
listItem.SubItems.Add(currTagValue);
|
|
if Success(lTag, currTagValue, newTagValue) then
|
|
listItem.ImageIndex := IMGINDEX_SUCCESS else
|
|
listItem.ImageIndex := IMGINDEX_FAIL;
|
|
inc(j);
|
|
end;
|
|
|
|
jpeg := TJpegImage.Create;
|
|
try
|
|
try
|
|
jpeg.LoadFromFile(OutFile);
|
|
listitem := ListView.Items.Add;
|
|
listItem.Caption := 'Successfully loaded';
|
|
listItem.ImageIndex := IMGINDEX_SUCCESS;
|
|
except
|
|
listitem := ListView.Items.Add;
|
|
listItem.Caption := 'Loading failed.';
|
|
listItem.ImageIndex := IMGINDEX_FAIL;
|
|
end;
|
|
finally
|
|
jpeg.Free;
|
|
end;
|
|
|
|
finally
|
|
testCases.Free;
|
|
end;
|
|
|
|
ExifTabControlChange(nil);
|
|
end;
|
|
|
|
procedure TMainForm.BtnBrowseClick(Sender: TObject);
|
|
var
|
|
olddir: String;
|
|
begin
|
|
olddir := GetCurrentDir;
|
|
OpenDialog.FileName := '';
|
|
if OpenDialog.Execute then
|
|
AddToHistory(OpenDialog.Filename);
|
|
SetCurrentDir(oldDir);
|
|
end;
|
|
|
|
function TMainForm.Success(ATag: TTag; ACurrValue, AExpectedValue: String): Boolean;
|
|
const
|
|
relEPS = 1E-3;
|
|
var
|
|
p: Integer;
|
|
snum, sdenom: String;
|
|
valexp, valcurr: Double;
|
|
decode: Boolean;
|
|
currVal, expVal: String;
|
|
begin
|
|
Result := ACurrValue = AExpectedValue;
|
|
if Result then
|
|
exit;
|
|
|
|
if (ACurrValue = '') or (AExpectedValue = '') then begin
|
|
Result := false;
|
|
exit;
|
|
end;
|
|
(*
|
|
{ Check for alternative expected value }
|
|
p := pos('|', AExpectedValue);
|
|
if p > 0 then begin
|
|
expected2 := Copy(AExpectedValue, p+1, MaxInt);;
|
|
expected1 := Copy(AExpectedValue, 1, p-1);
|
|
Result := (ACurrValue = expected1);
|
|
if Result then
|
|
exit;
|
|
Result := (ACurrValue = expected2);
|
|
if Result then
|
|
exit;
|
|
end; *)
|
|
|
|
{ Check for float values, e.g. 12.0 vs 12 }
|
|
if (ATag is TFloatTag) then begin
|
|
currVal := CleanFloatStr(ACurrValue);
|
|
expVal := CleanFloatStr(AExpectedValue);
|
|
|
|
Result := currVal = expval;
|
|
if Result then
|
|
exit;
|
|
|
|
{ Check for fractional result, e.g. exposure time }
|
|
p := pos('/', currVal);
|
|
if p > 0 then begin
|
|
snum := Copy(currVal, 1, p-1);
|
|
sdenom := Copy(currVal, p+1, MaxInt);
|
|
valcurr := StrToInt(snum) / StrToInt(sdenom);
|
|
end else
|
|
valcurr := StrToFloat(currVal, fpExifFmtSettings);
|
|
|
|
p := pos('/', expVal);
|
|
if p > 0 then begin
|
|
snum := Copy(expval, 1, p-1);
|
|
sdenom := Copy(currval, p+1, MaxInt);
|
|
valexp := StrToInt(snum) / StrToInt(sdenom);
|
|
end else
|
|
valexp := StrToFloat(expval, fpExifFmtSettings);
|
|
|
|
Result := SameValue(valcurr, valexp, relEPS * valexp);
|
|
if Result then
|
|
exit;
|
|
end;
|
|
|
|
if (ATag is TIntegerTag) then begin
|
|
decode := ATag.DecodeValue;
|
|
ATag.DecodeValue := not decode;
|
|
currVal := ATag.AsString;
|
|
ATag.DecodeValue := decode;
|
|
Result := (currVal = AExpectedValue);
|
|
if Result then
|
|
exit;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.ExifToListview(AImgInfo: TImgInfo; AListView: TListView);
|
|
var
|
|
i: Integer;
|
|
lTag: TTag;
|
|
item: TListItem;
|
|
begin
|
|
AListview.Items.BeginUpdate;
|
|
try
|
|
AListview.Items.Clear;
|
|
if not AImgInfo.HasExif then
|
|
exit;
|
|
for i:=0 to AImgInfo.ExifData.TagCount-1 do begin
|
|
lTag := AImgInfo.ExifData.TagByIndex[i];
|
|
if lTag = nil then
|
|
Continue;
|
|
item := AListView.Items.Add;
|
|
with item do begin
|
|
Caption := lTag.Description;
|
|
SubItems.Add(lTag.AsString);
|
|
end;
|
|
end;
|
|
AListView.AlphaSort;
|
|
finally
|
|
AListview.Items.EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
function TMainForm.ReadTagValue(ATagName: String): String;
|
|
var
|
|
lTag: TTag;
|
|
begin
|
|
Result := ReadTagValue(ATagName, lTag);
|
|
end;
|
|
|
|
function TMainForm.ReadTagValue(ATagName: String; out ATag: TTag): String;
|
|
begin
|
|
if ATagName = 'Comment' then begin
|
|
Result := ImgInfo.Comment;
|
|
ATag := nil;
|
|
end else
|
|
begin
|
|
ATag := ImgInfo.ExifData.FindTagByName(ATagName);
|
|
if ATag = nil then
|
|
Result := ''
|
|
else
|
|
Result := ATag.AsString;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.ExifTabControlChange(Sender: TObject);
|
|
var
|
|
data: TImgInfo;
|
|
begin
|
|
data := TImgInfo.Create;
|
|
try
|
|
case ExifTabControl.TabIndex of
|
|
0: data.LoadFromFile(CbTestfile.Text);
|
|
1: data.LoadFromFile(OutFile);
|
|
end;
|
|
ExifToListView(data, ExifListView);
|
|
finally
|
|
data.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.WriteTagValue(ATagName, ATagValue: String);
|
|
var
|
|
lTag: TTag;
|
|
begin
|
|
if ATagName = 'Comment' then
|
|
ImgInfo.Comment := ATagValue
|
|
else begin
|
|
lTag := ImgInfo.ExifData.TagByName[ATagName];
|
|
if lTag = nil then
|
|
lTag := ImgInfo.ExifData.AddTagByName(ATagName);
|
|
lTag.AsString := ATagvalue;
|
|
end;
|
|
end;
|
|
|
|
function CreateIni: TCustomIniFile;
|
|
begin
|
|
Result := TMemIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
|
|
end;
|
|
|
|
procedure TMainForm.ReadFromIni;
|
|
var
|
|
ini: TCustomIniFile;
|
|
list: TStrings;
|
|
i: Integer;
|
|
W, H, L, T: Integer;
|
|
R: TRect;
|
|
begin
|
|
ini := CreateIni;
|
|
try
|
|
list := TStringList.Create;
|
|
try
|
|
if WindowState = wsNormal then begin
|
|
W := ini.ReadInteger('MainForm', 'Width', Width);
|
|
H := ini.ReadInteger('MainForm', 'Height', Height);
|
|
L := ini.ReadInteger('MainForm', 'Left', Left);
|
|
T := ini.ReadInteger('MainForm', 'Top', Top);
|
|
R := Screen.DesktopRect;
|
|
if W > R.Right - R.Left then W := R.Right - R.Left;
|
|
if L+W > R.Right then L := R.Right - W;
|
|
if L < R.Left then L := R.Left;
|
|
if H > R.Bottom - R.Top then H := R.Bottom - R.Top;
|
|
if T+H > R.Bottom then T := R.Bottom - H;
|
|
if T < R.Top then T := R.Top;
|
|
SetBounds(L, T, W, H);
|
|
end;
|
|
|
|
ini.ReadSection('History', list);
|
|
for i:=list.Count-1 downto 0 do // count downward because AddToHistory adds to the beginning of the list
|
|
AddToHistory(ini.ReadString('History', list[i], ''));
|
|
CbTestFile.ItemIndex := 0;
|
|
finally
|
|
list.Free;
|
|
end;
|
|
finally
|
|
ini.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.WriteToIni;
|
|
var
|
|
ini: TCustomIniFile;
|
|
i: Integer;
|
|
begin
|
|
ini := CreateIni;
|
|
try
|
|
ini.WriteInteger('MainForm', 'Left', Left);
|
|
ini.WriteInteger('MainForm', 'Top', Top);
|
|
ini.WriteInteger('MainForm', 'Width', Width);
|
|
ini.WriteInteger('MainForm', 'Height', Height);
|
|
|
|
for i:=0 to CbTestFile.Items.Count-1 do
|
|
if (CbTestFile.Items[i] <> '') and FileExists(CbTestFile.Items[i]) then
|
|
ini.WriteString('History', 'Item'+IntToStr(i+1), CbTestFile.Items[i]);
|
|
ini.UpdateFile;
|
|
finally
|
|
ini.Free;
|
|
end;
|
|
end;
|
|
|
|
end.
|
|
|