Save array of packed record to disk - arrays

I am getting crazy.
I am trying to save an array of packed record to disk to be read later on.
The following unit contain mainly two procedures.
The first one InitSaveAndReLoad(), init a packed array of record, save it to the disk and reload from the disk in a new array of packed record and go through the loaded array and print the 20 first value. Works perfectly.
The second one LoadFromFile(), just reload the array from disk. It is even call by InitSaveAndReload() and works perfectly as soon as the file has been created previously by the same instance of the application. I mean if I quit the application and relaunch, the LoadFromFile() procedure which just reload the file in an array of records does not works anymore. I don't understand why.
Any clue?
Thanks for your help. Already spend a full day on this issue and turning crazy!
unit Unit4;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.DateUtils,
System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Menus, Vcl.StdCtrls;
type
TRate = packed record
time : int64;
open : double;
low : double;
high : double;
close : double;
tick_volume : int64;
spread : integer;
real_volume : int64;
end;
PRate = ^TRate;
TForm4 = class(TForm)
MemoLogs: TMemo;
SaveDialog1: TSaveDialog;
edFile: TEdit;
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
procedure InitSaveAndReload(Sender: TObject);
procedure Reload(Sender: TObject);
procedure SelectFile(Sender: TObject);
procedure LoadFromFile();
procedure Button4Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form4: TForm4;
implementation
{$R *.dfm}
function TimeElapsedToString(time : int64; show_ms : boolean = false) : string;
var
TmpVal:real;
TmpStr:string;
begin
TmpVal := time;
TmpStr := '';
TmpVal := TmpVal / 3600000;
TmpStr := inttostr(trunc(TmpVal));
if Length(TmpStr) = 1 then TmpStr := '0' + TmpStr;
TmpVal := (TmpVal-trunc(TmpVal))* 3600000;
TmpVal := TmpVal / 60000;
if TmpVal<10 then
TmpStr := TmpStr + ':0' + inttostr(trunc(TmpVal))
else
TmpStr := TmpStr + ':' + inttostr(trunc(TmpVal));
TmpVal := (TmpVal-trunc(TmpVal))*60000;
TmpVal := TmpVal / 1000;
if TmpVal<10 then
TmpStr := TmpStr + ':0' + inttostr(trunc(TmpVal))
else
TmpStr := TmpStr + ':' + inttostr(trunc(TmpVal));
if show_ms then
begin
TmpVal := (TmpVal-trunc(TmpVal))*1000;
TmpVal := TmpVal;
if TmpVal<10 then
TmpStr := TmpStr + ':00' + inttostr(trunc(TmpVal))
else if TmpVal<100 then
TmpStr := TmpStr + ':0' + inttostr(trunc(TmpVal))
else
TmpStr := TmpStr + ':' + inttostr(trunc(TmpVal));
end;
Result := TmpStr;
end;
procedure TForm4.SelectFile(Sender: TObject);
begin
if SaveDialog1.Execute then
edFile.Text := SaveDialog1.FileName;
end;
procedure TForm4.Button4Click(Sender: TObject);
begin
MemoLogs.Lines.Clear;
end;
procedure TForm4.InitSaveAndReload(Sender: TObject);
var
_start : TDatetime;
ARate : PRate;
filename : string;
Stream : TFileStream;
i,L : integer;
rates_M1 : array of PRate;
//rates : array of PRate;
begin
filename := edFile.Text;
MemoLogs.Lines.Add('Initialization of 7 million array of records... Please wait.');
Refresh;
// init array
_start := Now;
SetLength(rates_M1, 7000000);
for i:= 0 to 6999999 do
begin
New(ARate);
ARate.time := DateTimeToUnix(IncMinute(Now, i));
ARate.open := 1.25698;
ARate.low := 1.2574;
ARate.high := 1.2547;
ARate.close := 1.65874;
ARate.tick_volume := 154;
ARate.spread := 5;
ARate.real_volume := 15741;
rates_M1[i] := ARate;
end;
MemoLogs.Lines.Add(IntToStr(Length(rates_M1)) + ' array of records initialized ' + TimeElapsedToString(MilliSecondsBetween(Now, _start), true));
// save array
_start:= Now;
Stream:= TFileStream.Create(filename , fmCreate);
try
L:= Length(rates_M1);
Stream.WriteBuffer(L, SizeOf(L));
Stream.WriteBuffer(Pointer(rates_M1)^, L * SizeOf(ARate));
finally
Stream.Free;
MemoLogs.Lines.Add(IntToStr(Length(rates_M1)) + ' records saved to disk in ' + TimeElapsedToString(MilliSecondsBetween(Now, _start), true));
end;
LoadFromFile();
end;
procedure TForm4.LoadFromFile;
var
_start : TDatetime;
ARate : PRate;
filename : string;
Stream : TFileStream;
i,L : integer;
rates : array of PRate;
begin
// reload array
_start := Now;
filename := edFile.Text;
Stream:= TFileStream.Create(filename , fmOpenRead);
try
Stream.Read(L, SizeOf(L));
//SetLength(rates_M1, L);
// even use another empty array of ARate to be sure I am not using the same filled array!
SetLength(rates, L);
// I don't want to parse all records...
// for i := 0 to L-1 do
// begin
// Stream.Read(rates_M1[i].AID, SizeOf(ARecord.AID));
// Stream.Read(rates_M1[i].time, SizeOf(ARecord.time));
// end;
Stream.Read(Pointer(rates)^, L * SizeOf(ARate));
finally
Stream.Free;
MemoLogs.Lines.Add(IntToStr(Length(rates)) + ' records loaded from disk in ' + TimeElapsedToString(MilliSecondsBetween(Now, _start), true));
end;
// Print 20 first records just reloaded!
MemoLogs.Lines.Add('Print 20 first records just reloaded in another array of records!' + TimeElapsedToString(MilliSecondsBetween(Now, _start), true));
for i := 0 to 20 do
MemoLogs.Lines.Add('i=' + IntToStr(i) + #9
+ IntToStr(rates[i].time) + #9
+ FloatToStr(rates[i].open) + #9
);
end;
procedure TForm4.Reload(Sender: TObject);
begin
LoadFromFile();
end;
end.
Result
When I say 'Does not works anymore', I mean once you called InitSaveAndReload() procedure, you can call LoadFromFile() as many time as you want, but if you to call this procedure just after launching the app, trying to use an old file created by the InitSaveAndReload procedure, then it does not work the same!
The Unit provide is as simple as possible. Just create a new projet add 3 buttons a TMemo and one TEdit. If I could join a .rar I would have enclosed the project...

You are saving the address of each record rather than saving contents of the record. You have an array of pointers, and you save that array to file. With your current data structure you would need to save each record individually, because the actual data does not lie contiguously in memory.

Related

Extract correct creation date from avi file

I am using Delphi 10 and Windows 10 Home Edition 64-bit. I have a video file called MVI_0640.AVI. The date shown in the File Explorer is 15/04/04 which corresponds to the Media Created Date in the Properties window.
I am using the following code to extract dates.
procedure TForm1.Button1Click(Sender: TObject);
var
ADate: TDateTime;
FlHandle: integer;
MyData: TWin32FindData;
FlTime: TFileTime;
MySysTime: TSystemTime;
begin
{get date using GetCreationTime}
ADate := TFile.GetCreationTime(FlName);
Memo1.Lines.Add('GetCreationTime ' + DateToStr(ADate));
{get date using FileGetDate}
FlHandle := FileOpen(FlName,fmOpenRead);
ADate := FileDateToDateTime(FileGetDate(FlHandle));
FileClose(FlHandle);
Memo1.Lines.Add('FileGetDate ' + DateToStr(ADate));
{get date using FindFirstFile}
FindFirstFile(PChar(FlName), MyData);
FlTime := MyData.ftCreationTime;
FileTimeToSystemTime(FlTime, MySysTime);
ADate := SystemTimeToDateTime(MySysTime);
Memo1.Lines.Add('ftCreationTime ' + DateToStr(ADate));
FlTime := MyData.ftLastAccessTime;
FileTimeToSystemTime(FlTime, MySysTime);
ADate := SystemTimeToDateTime(MySysTime);
Memo1.Lines.Add('ftLastAccessTime ' + DateToStr(ADate));
FlTime := MyData.ftLastWriteTime;
FileTimeToSystemTime(FlTime, MySysTime);
ADate := SystemTimeToDateTime(MySysTime);
Memo1.Lines.Add('ftLastWriteTime ' + DateToStr(ADate));
end;
The result looks like this:
None of the dates reflect the Media Created Date. How can I extract it?
In answer to Tom Brunberg’s comment I attach an extract of the file taken with a hex editor.
The date you are looking for is in a chunk called IDIT. It is referred to e.g. in this document
Structure is simple, (sample data from a file of mine):
chunk id: IDIT // 4 ASCII chars
chunk length: 0000001A // 26 bytes
chunk data: Sun Aug 31 12:15:22 2008/n/0 // date as ascii string
The structure of an AVI file is outlined by Microsoft, as follows, with addition of the location of IDIT chunk if present
RIFF ('AVI '
LIST ('hdrl'
'avih'(<Main AVI Header>)
LIST ('strl'
'strh'(<Stream header>)
'strf'(<Stream format>)
[ 'strd'(<Additional header data>) ]
[ 'strn'(<Stream name>) ]
...
)
... (note, if present, the IDIT chunk appears here)
)
LIST ('movi'
{SubChunk | LIST ('rec '
SubChunk1
SubChunk2
...
)
...
}
...
)
['idx1' (<AVI Index>) ]
)
The above mentioned document also outlines the various structures.
Sample data:
A function to get the date (if present in the file) could be as follows:
// Note! finetuned to search only the main TLIST 'hdrl'
function GetOriginalDate(AviFileName: TFileName; out s: string): boolean;
type
TChunkId = array[0..3] of AnsiChar;
TChunk = record
chid: TChunkId;
size: cardinal;
form: TChunkId;
end;
var
fs: TFileStream;
Root: TChunk;
Chnk: TChunk;
Done: boolean;
Date: ansistring;
endpos: integer;
begin
s := 'not found';
Done := False;
result := False;
fs:= TFileStream.Create(AviFileName, fmOpenRead or fmShareDenyWrite);
try
fs.Read(Root, SizeOf(Root));
if Root.chid <> 'RIFF' then exit;
if Root.form <> 'AVI ' then exit;
fs.Read(Chnk, SizeOf(TChunk)); // main LIST
if Chnk.chid <> 'LIST' then exit;
if Chnk.form <> 'hdrl' then exit;
endpos := fs.Position + Chnk.size;
repeat
fs.Read(Chnk, SizeOf(TChunk));
if Chnk.chid = 'IDIT' then
begin
fs.Seek(-4, TSeekOrigin.soCurrent);
SetLength(Date, Chnk.size);
fs.Read(Date[1], Length(Date));
s := Date;
Done := True;
end
else
fs.Seek(Chnk.size-4, TSeekOrigin.soCurrent);
until Done or (fs.Position > endpos);
finally
fs.Free;
end;
end;
To call it, for example:
procedure TForm1.Button2Click(Sender: TObject);
var
s: string;
begin
GetOriginalDate('F:\My Video\2008-08-31\MVI_1279.AVI', s);
Memo1.Lines.Add(s);
end;
And result in Memo1
Sun Aug 31 12:15:22 2008
I found an answer to my question. Probably not very elegant or save but it does it's job. I tested it on more than 100 files and it worked without a problem. Here is my answer:
function TForm1.GetAviMediaCreationDate(AFile: string): TDateTime;
var
FS: TFileStream;
NumOfChar: integer;
i,d: integer;
ABuffer: array of byte;
AStr: string;
DateStr: string;
sdp: integer; //start date position
dn,mn,yn: integer; //used to encode date
begin
sdp := 0;
FS := TFileStream.Create(AFile,fmOpenRead);
NumOfChar := 400;
SetLength(ABuffer,NumOfChar);
FS.Read(Pointer(ABuffer)^, NumOfChar);
{find IDIT}
for i := 0 to NumOfChar-1 do
begin
AStr := Char(ABuffer[i]) +
Char(ABuffer[i+1]) +
Char(ABuffer[i+2]) +
Char(ABuffer[i+3]);
if AStr = 'IDIT' then sdp := i+7;
end;
{extract date}
for d := 1 to 24 do
DateStr := DateStr + Char(ABuffer[sdp+d]);
{assemble TDateTime}
//123456789 123456789 123456789
//Sun Jun 28 10:13:39 2015
dn := StrToInt(Copy(DateStr,9,2));
mn := IndexText(Copy(DateStr,5,3),ShortMonthNames)+1;
yn := StrToInt(Copy(DateStr,21,4));
Result := EncodeDate(yn, mn, dn);
FS.Free;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
ADate: TDateTime;
begin
ADate := GetAviMediaCreationDate(FlName);
Memo1.Lines.Add(DateToStr(ADate));
end;

How to assign values to an array of objects in Delphi

I have an array of TBoek and and a loop that is supposed to assign values to each of the elements of the array. What happens instead is that the array ends up with the exact same values in each index. Perhaps my order of processing is incorrect or I'm incorrectly assigning values to the array, but either way I cannot for the life of me figure it out.
procedure TBoek.MaakArray;
var
i: integer;
sTitel, sOuteur, sISBN, sUitgewer, sPrys, sI: string;
boek: TBoek;
begin
boek := TBoek.Create;
for i := 0 to 9 do
begin
{$REGION 'Parse JSON om eienskappe van boek te kry'}
sI := IntToStr(i);
sTitel := JSONFile.GetValue<string>('items[' + sI + '].volumeInfo.title');
try
sOuteur := JSONFile.GetValue<string>
('items[' + sI + '].volumeInfo.authors[0]');
except
sOuteur := '<none>'
end;
try
sISBN := JSONFile.GetValue<string>
('items[' + sI + '].volumeInfo.industryIdentifiers[1].identifier');
except
sISBN := '<none>'
end;
try
sUitgewer := JSONFile.GetValue<string>
('items[' + sI + '].volumeInfo.publisher');
except
sUitgewer := '<none>'
end;
try
sPrys := JSONFile.GetValue<string>
('items[' + sI + '].saleInfo.listPrice.amount');
except
sPrys := '0';
end;
{$ENDREGION}
arrBoeke[i] := boek;
with arrBoeke[i] do
begin
SetTitel(sTitel);
SetOuteur(sOuteur);
SetISBN(sISBN);
SetUitgewer(sUitgewer);
SetPrys(sPrys);
end;
end;///end of for loop
end;
The Set functions all follow this format:
procedure TBoek.SetTitel(BoekTitel: string);
begin
fTitel := BoekTitel;
end;
This is the GetString function:
function TBoek.GetString: string;
begin
Result := GetTitel + #13#10 + GetOuteur + #13#10 + GetISBN + #13#10 +
GetUitgewer + #13#10 + GetPrys + #13#10 + #13#10;
end;
And the GetTitel,GetOuteur etc. functions all follow the same format:
function TBoek.GetTitel: string;
begin
Result := fTitel;
end;
What I want is to call:
for I := 0 to 9 do
begin
ShowMessage(arrBoeke[i].GetString);
end;
and access the values in the array one at a time, instead each value is the same.

TurboPowern OnGuard create license file with machine modifier

I use TurboPower onGuard for licensing and there is an option to specify and generate the machine specific keys for license validation. With following code I create machine modifier based on machineID
const
Ckey :TKey = ($56,$1B,$B0,$48,$2D,$AF,$F4,$E5,$30,$6C,$E5,$3B,$A7,$8E,$1A,$14);
procedure TForm1.FormCreate(Sender: TObject);
var
InfoSet : TEsMachineInfoSet;
MachineID : Longint;
begin
{ initialize the machine information set }
InfoSet := [midUser, midSystem, midNetwork, midDrives];
{ create the machine ID and display in hex }
try
MachineID := CreateMachineID(InfoSet);
Edit1.Text := '$' + BufferToHex(MachineID, SizeOf(MachineID));
except on E:Exception do
ShowMessage(E.Message);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
modifier :LongInt;
key :TKey;
releasecode,unlockcode :TCode;
inexpirydate: TdateTime;
begin
modifier := GenerateMachineModifierPrim ;
ApplyModifierToKeyPrim(Modifier, CKey, SizeOf(CKey));
//HexToBuffer(Ckey, key, SizeOf(TKey));
InitRegCode(Ckey, edit1.Text, inExpiryDate, releaseCode);
Edit2.text := BufferToHex(unlockCode, SizeOf(releaseCode));
end;
On the other hand to validate serial number and relase code I use the following code on client side to validate application, I place SerialNumberCode component to implement that.
Ckey :TKey = ($56,$1B,$B0,$48,$2D,$AF,$F4,$E5,$30,$6C,$E5,$3B,$A7,$8E,$1A,$14);
procedure TForm1.OgSerialNumberCode1GetKey(Sender: TObject; var Key: TKey);
begin
Key := Ckey;
end;
procedure TForm1.OgSerialNumberCode1GetModifier(Sender: TObject;
var Value: LongInt);
begin
Value := GenerateMachineModifierPrim;
end;
function TForm1.GetSNData(var S: string): integer;
var
TC : TCode;
SNC : string;
modifier :LOngInt;
begin
try
modifier := GenerateMachineModifierPrim;
ApplyModifierToKeyPrim(Modifier, CKey, SizeOf(CKey));
IniSNVal := StrToInt(InputBox('Serial number', 'Enter serial Value ', '12345678'));
SNC := (InputBox('release code', 'Enter code ', ''));
{Check that Release Code was entered correctly}
HexToBuffer(SNC, TC, SizeOf(TCode));
if not (IsSerialNumberCodeValid(CKey, TC)) then begin
S := 'Release code not entered correctly';
Result := mrCancel;
end else begin
IniFile := TIniFile.Create(TheDir + 'test.INI');
try
IniFile.WriteInteger('Codes', 'SN', IniSNVal);
IniFile.WriteString('Codes', 'SNCode', SNC);
finally
IniFile.Free;
end;
end;
finally
end;
end;
procedure TForm1.OgSerialNumberCode1GetCode(Sender: TObject; var Code: TCode);
var
S1 : string;
L : integer;
begin
if not (FileExists(TheDir + 'test.INI')) then
Exit;
{open Ini File}
IniFile := TIniFile.Create(TheDir + 'test.INI');
try
{try to read release code}
S1 := IniFile.ReadString('Codes', 'SNCode', '');
IniSNVal := IniFile.ReadInteger('Codes', 'SN', 0);
{convert retrieved string to a code}
HexToBuffer(S1, Code, SizeOf(Code));
finally
IniFile.Free;
end;
end;
procedure TForm1.OgSerialNumberCode1Checked(Sender: TObject; Status: TCodeStatus);
var
S,
C1,
C2 : string;
TC : TCode;
LI : longint;
begin
case Status of
ogValidCode : begin
{check if retrieved Serial Number matches Code}
LI := OgSerialNumberCode1.GetValue;
if (LI <> IniSNVal) then begin
Status := ogInvalidCode;
S := 'The serial number has been changed';
end else begin
ShowMessage('Serial #: ' + IntToStr(IniSNVal));
Exit;
end;
end;
ogInvalidCode : begin
{if INI file doesn't exist, presume this is first run}
if not (FileExists(TheDir + 'test.INI')) then
begin
if not (GetSNData(S) = mrCancel) then begin
{Check the SN/ReleaseCode}
OgSerialNumberCode1.CheckCode(True);
{must Exit since line above began a recursive call}
Exit;
end;
end else
S := 'Invalid Code';
end;
ogCodeExpired : S := 'Evaluation period expired';
end;
ShowMessage(S);
Application.Terminate;
end;
But at the client side machine never accepts the validation and throws Release code not entered correctly. Probably I don't understand usage of machineID exmaple in onguard folder. Please also note that the key for client side and generation for relaease code is same so there shouldn't be any mismatch.

Saving and loading jpeg images to database not working DELPHI

I'm trying to make a program that saves images in a database. I've pulled together some info on the net and wrote up code that I think is supposed to work, but fails in a completely unique way on either side (the saving and the loading).
Here's my code, could you tell me what I'm doing wrong?
Load from Database, gives me a memory address access violation error:
procedure TfrmMain.btnAddPickT4Click(Sender: TObject);
var
S : TMemoryStream;
ikode : integer;
begin
if cbxDeelnemerT4.ItemIndex < 0 then
begin
MessageDlg('Kies asseblief ''n deelnemer!',mtInformation,[mbOK],1);
Exit;
end;
if OpenPictureDialog1.Execute then
if FileExists(OpenPictureDialog1.FileName) then
Image1.Picture.LoadFromFile(OpenPictureDialog1.FileName)
else
MessageDlg('Die lêer bestaan nie!',mtError,[mbOK],1);
ikode := cbxDeelnemerT4.ItemIndex + 1;
S := TMemoryStream.Create;
ADOQuery1.Close;
ADOQuery1.SQL.Text := 'SELECT * FROM DEELNEMERS WHERE Kode = '+inttostr(ikode);
ADOQuery1.Open;
try
Image1.Picture.Graphic.SaveToStream(S);
S.Position := 1;
ADOQuery1.Insert;
TBLobfield(ADOQuery1.FieldByName('Foto')).LoadFromStream(S);
finally
S.Free;
end;
end;
Saving to the database. This code doesn't give an error as such, but it just doesn't save the image to the database:
procedure TfrmMain.btnOpenProfileT4Click(Sender: TObject);
var
S : TMemoryStream;
ikode : integer;
begin
ikode := cbxDeelnemerT4.ItemIndex + 1;
S := TMemoryStream.Create;
ADOQuery1.Close;
ADOQuery1.SQL.Text := 'SELECT * FROM DEELNEMERS WHERE Kode = '+inttostr(ikode);
ADOQuery1.Open;
try
TBlobField(ADOQuery1.FieldByName('Foto')).SaveToStream(S);
S.Position := 0;
Image1.Picture.Graphic.LoadFromStream(S);
finally
s.Free;
end;
end;
You are calling TGraphic.LoadFromStream() but you are not initializing the TPicture.Graphic property with a valid TGraphic-derived object beforehand, so the property is likely nil, hense the crash.
Also, when saving the TPicture.Graphic to the TMemoryStream, you are skipping the first byte of the graphic when saving it to the DB. All of the bytes are important, so do not skip any of them.
Assuming your images are specifically JPEGs and nothing else (your saving code is not restricting the file type), then try this instead:
procedure TfrmMain.btnAddPickT4Click(Sender: TObject);
var
S : TMemoryStream;
ikode : integer;
begin
if cbxDeelnemerT4.ItemIndex < 0 then
begin
MessageDlg('Kies asseblief ''n deelnemer!',mtInformation,[mbOK],1);
Exit;
end;
if not OpenPictureDialog1.Execute then
Exit;
Image1.Picture.LoadFromFile(OpenPictureDialog1.FileName);
if not (Image1.Picture.Graphic is TJpegImage) then
raise Exception.Create('Sorry, only JPG images can be saved in the DB');
ikode := cbxDeelnemerT4.ItemIndex + 1;
S := TMemoryStream.Create;
try
Image1.Picture.Graphic.SaveToStream(S);
S.Position := 0;
ADOQuery1.Close;
ADOQuery1.SQL.Text := 'SELECT * FROM DEELNEMERS WHERE Kode = '+IntToStr(ikode);
ADOQuery1.Open;
ADOQuery1.Insert;
try
TBlobField(ADOQuery1.FieldByName('Foto')).LoadFromStream(S);
ADOQuery1.Post;
except
ADOQuery1.Cancel;
raise;
end;
finally
S.Free;
end;
end;
uses
..., Jpeg;
procedure TfrmMain.btnOpenProfileT4Click(Sender: TObject);
var
S : TMemoryStream;
ikode : integer;
Jpg: TJPEGImage;
begin
ikode := cbxDeelnemerT4.ItemIndex + 1;
ADOQuery1.Close;
ADOQuery1.SQL.Text := 'SELECT * FROM DEELNEMERS WHERE Kode = ' + IntToStr(ikode);
ADOQuery1.Open;
S := TMemoryStream.Create;
try
TBlobField(ADOQuery1.FieldByName('Foto')).SaveToStream(S);
S.Position := 0;
Jpg := TJPEGImage.Create;
try
Jpg.LoadFromStream(S);
Image1.Picture.Assign(Jpg);
finally
Jpg.Free;
end;
finally
S.Free;
end;
end;
But if your images can be other formats besides JPEG, then you need to store the image type in the DB as well so that you can read it back and instantiate the correct TGraphic class type (TBitmap, TJpegImage, TGifImage, TPNGImage, etc) to handle it as needed, eg:
procedure TfrmMain.btnAddPickT4Click(Sender: TObject);
var
S : TMemoryStream;
ikode : integer;
begin
if cbxDeelnemerT4.ItemIndex < 0 then
begin
MessageDlg('Kies asseblief ''n deelnemer!',mtInformation,[mbOK],1);
Exit;
end;
if not OpenPictureDialog1.Execute then
Exit;
Image1.Picture.LoadFromFile(OpenPictureDialog1.FileName);
ikode := cbxDeelnemerT4.ItemIndex + 1;
S := TMemoryStream.Create;
try
Image1.Picture.Graphic.SaveToStream(S);
S.Position := 0;
ADOQuery1.Close;
ADOQuery1.SQL.Text := 'SELECT * FROM DEELNEMERS WHERE Kode = '+IntToStr(ikode);
ADOQuery1.Open;
ADOQuery1.Insert;
try
// this is just an example, there are other ways to do this
if Image1.Picture.Graphic is TJPEGImage then
ADOQuery1.FieldByName('FotoType').AsString := 'jpg'
else if Image1.Picture.Graphic is TPNGImage then
ADOQuery1.FieldByName('FotoType').AsString := 'png'
else if Image1.Picture.Graphic is TGIFImage then
ADOQuery1.FieldByName('FotoType').AsString := 'gif'
else if Image1.Picture.Graphic is TBitmap then
ADOQuery1.FieldByName('FotoType').AsString := 'bmp'
else
raise Exception.Create('Cannot save unsupported image type to DB');
TBlobField(ADOQuery1.FieldByName('Foto')).LoadFromStream(S);
ADOQuery1.Post;
except
ADOQuery1.Cancel;
raise;
end;
finally
S.Free;
end;
end;
uses
..., Jpeg, GifImg, PngImg;
procedure TfrmMain.btnOpenProfileT4Click(Sender: TObject);
var
S : TMemoryStream;
ikode : integer;
Graphic: TGraphic;
GraphicType: String;
begin
ikode := cbxDeelnemerT4.ItemIndex + 1;
ADOQuery1.Close;
ADOQuery1.SQL.Text := 'SELECT * FROM DEELNEMERS WHERE Kode = ' + IntToStr(ikode);
ADOQuery1.Open;
S := TMemoryStream.Create;
try
TBlobField(ADOQuery1.FieldByName('Foto')).SaveToStream(S);
S.Position := 0;
GraphicType := ADOQuery1.FieldByName('FotoType').AsString;
if GraphicType = 'jpg' then
Graphic := TJPEGImage.Create
ADOQuery1.FieldByName('FotoType').AsString := 'jpg'
else if GraphicType = 'png' then
Graphic := TPNGImage.Create
else if GraphicType = 'gif' then
Graphic := TGifImage.Create
else if GraphicType = 'bmp' then
Graphic := TBitmap.Create
else
raise Exception.Create('Cannot load unsupported image type from DB');
try
Graphic.LoadFromStream(S);
Image1.Picture.Assign(Graphic);
finally
Graphic.Free;
end;
finally
S.Free;
end;
end;
With that said, you should not be accessing TBlobField directly. Use the TDataSet.CreateBlobStream() method instead, let ADO give you a TStream object that is optimized for accessing ADO blob data, eg:
procedure TfrmMain.btnAddPickT4Click(Sender: TObject);
var
S : TStream;
ikode : integer;
begin
if cbxDeelnemerT4.ItemIndex < 0 then
begin
MessageDlg('Kies asseblief ''n deelnemer!',mtInformation,[mbOK],1);
Exit;
end;
if not OpenPictureDialog1.Execute then
Exit;
Image1.Picture.LoadFromFile(OpenPictureDialog1.FileName);
ikode := cbxDeelnemerT4.ItemIndex + 1;
ADOQuery1.Close;
ADOQuery1.SQL.Text := 'SELECT * FROM DEELNEMERS WHERE Kode = '+IntToStr(ikode);
ADOQuery1.Open;
ADOQuery1.Insert;
try
...
S := ADOQuery1.CreateBlobStream(ADOQuery1.FieldByName('Foto'), bmWrite);
try
Image1.Picture.Graphic.SaveToStream(S);
finally
S.Free;
end;
ADOQuery1.Post;
except
ADOQuery1.Cancel;
raise;
end;
end;
procedure TfrmMain.btnOpenProfileT4Click(Sender: TObject);
var
S : TStream;
ikode : integer;
Graphic: TGraphic;
begin
ikode := cbxDeelnemerT4.ItemIndex + 1;
ADOQuery1.Close;
ADOQuery1.SQL.Text := 'SELECT * FROM DEELNEMERS WHERE Kode = ' + IntToStr(ikode);
ADOQuery1.Open;
...
Graphic := ...;
try
S := ADOQuery1.CreateBlobStream(ADOQuery1.FieldByName('Foto'), bmRead);
try
Graphic.LoadFromStream(S);
finally
S.Free;
end;
Image1.Picture.Assign(Graphic);
finally
Graphic.Free;
end;
end;

Delphi - BinToHex and HexToBin : Reversal

I have an array of byte that I wish to convert into a hex string, and then back into an array of byte.
I am using the following to convert into a hex string:
function bintoHex(const bin: array of byte): String;
const HexSymbols = '0123456789ABCDEF';
var i: integer;
begin
SetLength(Result, 2*Length(bin));
for i := 0 to Length(bin)-1 do begin
Result[1 + 2*i + 0] := HexSymbols[1 + bin[i] shr 4];
Result[1 + 2*i + 1] := HexSymbols[1 + bin[i] and $0F];
end;
end;
I am unsure how to convert it back into an array of byte properly. I am shooting for something like the following:
Type TDCArray = Array of Byte;
function xHexToBin(const HexStr: String): TDCArray;
const HexSymbols = '0123456789ABCDEF';
var i: integer;
begin
SetLength(Result, ???); //Need to now get half of the length, unsure how to go about that
for i := 0 to Length(HexStr)-1 do begin
//Here convert back into an array somehow...
//Result[i] := ???
end;
end;
How would I go about doing this?
Also, I am using Delphi XE2.
Why not just use the BinToHex and HexToBin RTL functions?
{$APPTYPE CONSOLE}
uses
System.Classes,
System.SysUtils;
var
LArray : array[1..6] of Byte = (10, 11, 12, 13, 14, 15);
LText: string;
I : integer;
begin
try
SetLength(LText, Length(LArray)*2);
BinToHex(#LArray, PChar(LText), SizeOf(LArray));
//show the hex string
Writeln(LText);
//fill the array with 0
FillChar(LArray, SizeOf(LArray), #0);
//get the values from the hex string
HexToBin(PChar(LText), #LArray, Length(LArray));
//show the array values
for i := 1 to Length(LArray) do
Write(LArray[i]);
Readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
If you want to do it yourself:
function xHexToBin(const HexStr: String): TBytes;
const HexSymbols = '0123456789ABCDEF';
var i, J: integer;
B: Byte;
begin
SetLength(Result, (Length(HexStr) + 1) shr 1);
B:= 0;
i := 0;
while I < Length(HexStr) do begin
J:= 0;
while J < Length(HexSymbols) do begin
if HexStr[I + 1] = HexSymbols[J + 1] then Break;
Inc(J);
end;
if J = Length(HexSymbols) then ; // error
if Odd(I) then
Result[I shr 1]:= B shl 4 + J
else
B:= J;
Inc(I);
end;
if Odd(I) then Result[I shr 1]:= B;
end;
2 hex chars represent 1 byte
The code i wrote earlier was pseudocode I wanted you to get the ideea. But if you need some code to paste here is the actual implementation:
program project1;
uses SysUtils,classes;
type
TByteArray = array of byte;
function StrToArray(const Hexstr: String): TByteArray ;
var
i: Integer;
begin
SetLength(Result, Length(Hexstr) div 2);
for i:=0 to (Length(Hexstr) div 2 - 1) do
Result[i]:= StrToInt('$' + Copy(Hexstr, (i * 2) + 1, 2));
end;
var
arr : TByteArray;
i : Integer;
begin
arr := StrToArray('0A0B0C0D');
for i:=0 to High(arr) do
WriteLn(arr[i]);
Readln;
end.
Btw coding is not about cuting and pasting ;)
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
SysUtils, Forms, StdCtrls, ExtCtrls, Classes;
type
{ TTextToHex_HexToBin }
TTextToHex_HexToBin = class(TForm)
Button1: TButton;
Edit1: TEdit;
Edit2: TEdit;
CheckTime: TImage;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
procedure CheckTimeClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
end;
var
TextToHex_HexToBin: TTextToHex_HexToBin;
implementation
{$R *.lfm}
{ TTextToHex_HexToBin }
//hex to binary
function HextoBinary(const AHexStr: string):string;
var
i, j: Integer;
const
HexParts: array[0..$F] of string =
(
{0} '0000',{1} '0001',{2} '0010',{3} '0011',{4} '0100',{5} '0101',{6} '0110',{7} '0111',
{8} '1000',{9} '1001',{A} '1010',{B} '1011',{C} '1100',{D} '1101',{E} '1110',{F} '1111'
);
begin
SetLength(Result, 4 * AHexStr.Length);
j := 1;
for i := 1 to AHexStr.Length do
begin
case AHexStr[i] of
'0'..'9':
Move(HexParts[Ord(AHexStr[i]) - Ord('0')][1], Result[j], sizeof(char) * 4);
'A'..'F':
Move(HexParts[$A + Ord(AHexStr[i]) - Ord('A')][1], Result[j], sizeof(char) * 4);
'a'..'f':
Move(HexParts[$A + Ord(AHexStr[i]) - Ord('a')][1], Result[j], sizeof(char) * 4);
else
raise EConvertError.CreateFmt('Invalid hexadecimal string "%s".', [AHexStr]);
end;
Inc(j, 4);
end;
end;
procedure TTextToHex_HexToBin.Button1Click(Sender: TObject);
var
TB: TBytes;
WS: WideString;
S: string;
i: Integer;
begin
WS := Trim(Edit1.Caption);
TB := WideBytesOf(WS);
// WideString to Hexadecimal
S := '';
for i := Low(TB) to High(TB) do
S := S + IntToHex(TB[i], -1);
Edit2.Caption :=Trim(S);
// Hex to Binary
Memo1.Caption:= HextoBinary(Trim(S));
end;
procedure TTextToHex_HexToBin.CheckTimeClick(Sender: TObject);
begin
end;
procedure TTextToHex_HexToBin.FormCreate(Sender: TObject);
begin
end;
end.
[Result Show as Below]

Resources