In my project I receive data from a tcp connection with a custom protocol in packets of 1095 bytes, then I must look for a sync word and try to show gray scale image.
At first step I read data and save them in a TStringList fifo
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
rowFrame : string;
data: TIdBytes;
begin
offReCStatus := false;
repeat
AContext.Connection.IOHandler.ReadBytes(data, 1099, False);
rowFrame :='';
for I := 0 to length(data)-1 do
begin
rowFrame := rowFrame + (data[i].ToHexString);
end;
tcpFrameList.Append( rowFrame );
until offReCStatus = true;
end;
Then in a separated thread, I try the data from the list.
{I added some comments in code}
Get first string from string list
Convert it to binary and append to previous data
Find sync word and copy data after sync word
Split image data to 1024 * 10 bits to load image
Draw image from data
Find new sync word(number 3)
Note: one very important thing is the sync-word is not byte,its bits and can start from middle of a byte for example 10 101011-00010101-00001100-10011001-01111111-00 111111 in this case 10 at first and 111111 at the end merged to sync word and its not AC543265FC any more.in the past in fpga I wrote code that shift the bits until find the 40 bits sync word but i don't know how this can be done in Delphi!
procedure TMyThread.Execute;
var
str3,str4,frameStr,frameId,strData, str6 : string;
iPos,y ,imageBit , frameIdNum :integer;
imageRol : TStringList;
begin
while not Terminated do
begin
FTermEvent.WaitFor( 500 );
if not Terminated then
begin
while tcpFrameList.Count >0 do //process que
begin
try
dta := dta + HexStrToBinStr(tcpFrameList[0]);//convert hex data to binary string and append to olddata
tcpFrameList.Delete(0);//delete converted thread
str3 := '1010110001010100001100100110010111111100';//sync word "AC543265FC"
iPos := pos( str3 , dta );//find 1st sync word in binary data
while dta.Length>20000 do //process data to find sync words
begin
Delete(dta,1, iPos-1 );//delete data until first sync word
str4 := copy( dta , 1, 12240);//copy image frame data after sync word
Delete(dta,1, 12240 );//delete image frame data that copied
strData := copy(BinToHex(str4),11); //hex image data
frameId := copy( strData , 1, 6 ); //get image column id from data
frameStr := copy( strData , 107, 330 );//get image color data as protocol
frameStr := frameStr + copy( strData , 501, 446 );//get image data as in protocol
frameStr := frameStr + copy( strData , 1011, 446 );//get image data as in protocol
frameStr := frameStr + copy( strData , 1521, 446 );//get image data as in protocol
frameStr := frameStr + copy( strData , 2031, 446 );//get image data as in protocol
frameStr := frameStr + copy( strData , 2541, 446 );//get image data as in protocol
imageBin := HexStrToBinStr( frameStr );
//now we have 10240 bit that for one frame column .10240 is 1024 of 10 bits for each pixel
imageRol := TstringList.Create;
imageRol := spliToLength( imageBin,10);//split 10240 to 1024 *10
frameIdNum := HexToDec(frameId);//frame id to show image
//application.ProcessMessages;
TThread.Synchronize (TThread.CurrentThread,
procedure () var y,n:integer;
begin
form1.Image1.Width := frameIdNum+1;//set TImage width
for y := 0 to imageRol.Count-1 do //process imageRol to grab 1024 pixel color of new column
begin
str6 := imageRol[y];
imageBit := trunc( BinToDec( str6 ) /4 );//div 10bit(1024) to 4 to get a number 0-255 for color
form1.Image1.Canvas.Pixels[frameIdNum ,y)] := RGB( imageBit , imageBit , imageBit );//gray scale image
end;
end);
iPos := pos( str3 , dta );
end;
except
on E : Exception do
TThread.Synchronize (TThread.CurrentThread,
procedure ()
begin
form1.Memo1.Lines.Add(E.ClassName+' , message: '+E.Message);
end);
end;
end;
end;
end;
end;
The code above is working good but its slow..
I don't know how can process data as bits so try to convert data between hex and string to complete the process. Is there a way to do this job without any hex converting from tcp layer!?
I commented the code to explain what happening.but tell me to add some more data where necessary.
Here is an example how you could process the Binary data.
DISCLAMER
This code sample is far from optimized as I tried to keep it simple so one can grasp the concept how to process binary data.
The main concept here is that we have a 40 bit sync word (marker) but since we are dealing with individual bits, it can be on a non byte boundary. So all we need to do is read at least 48 bits (6 bytes) into a 64 bit integer and shift the bits to the right until we find our marker. I did not include the RGB pixel extraction logic, I leave that as an exercise for you :), I think you can decode it with WIC as GUID_WICPixelFormat32bppBGR101010
program SO59584303;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Classes,
System.SysUtils;
type ImageArray = TArray<Byte>;
const FrameSync : UInt64 = $AC543265FC; // we need Int64 as our marker is > 32 bits
function GetByte(const Value : UInt64; const ByteNum : Byte) : Byte; inline;
begin
Result := (Value shr ((ByteNum-1)*8)) and $FF ;
end;
procedure WriteInt64BigEndian(const Value: UInt64; NumberOfBytes : Integer; var Stream : TBytes; var Ps : Integer);
var
I : Integer;
begin
for I := NumberOfBytes downto 1 do
begin
Stream[Ps] := GetByte(Value, I);
Inc(Ps);
end;
end;
function ReadInt64BigEndian(const NumberOfBytes : Integer; const Stream : TBytes; var Ps : Integer) : UInt64;
var
I : Integer;
B : Byte;
begin
Result := 0;
for I := NumberOfBytes downto 1 do
begin
B := Stream[Ps];
Result := Result or (UInt64(B) shl ((I-1)* 8));
Inc(Ps);
// sanity check
if Ps >= Length(Stream) then
Exit;
end;
end;
procedure ReadPixelData(const Stream : TBytes; Var Ps : Integer; const Shift : Byte; var Buffer : ImageArray);
// our buffer
var
I : UInt64;
BPos : Integer;
begin
BPos := 0;
// 1024 * 10 bit pixel = 10240 bits = 1280 bytes // initialize buffer
SetLength(Buffer, 1280);
// fill with 0's
FillChar(Buffer[0], Length(Buffer), 0);
if Shift = 0 then
begin
// if we are byte boundary, we can just copy our data
Move(Stream[Ps], Buffer[0], Length(Buffer));
Inc(Ps, Length(Buffer));
end
else
while Bpos < Length(Buffer) do
begin
// Read 8 bytes at a time and shift x bits to the right, mask off highest byte
// this means we can get max 7 bytes at a time
I := (ReadInt64BigEndian(8, Stream, Ps) shr Shift) and $00FFFFFFFFFFFFFF;
// Write 7 bytes to our image data buffer
WriteInt64BigEndian(I, 7, Buffer, BPos);
// go one position back for the next msb bits
Dec(Ps);
end;
end;
procedure WritePixelData(var Stream : TBytes; Var Ps : Integer; var Shift : Byte);
var
Count : Integer;
ByteNum : Byte;
Data : UInt64;
begin
for Count := 1 to 160 do
begin
// write four bytes at a time, due to the shifting we get 5 bytes in total
Data := $F1F2F3F4;
if (Shift > 0) then
begin
// special case, we need to fillup shift bits on last written byte in the buffer with highest byte from our UInt64
Data := Data shl Shift;
Stream[Ps-1] := Stream[Ps-1] or GetByte(Data, 5);
end;
WriteInt64BigEndian(Data, 4, Stream, Ps);
Data := $F5F6F7F8;
if (Shift > 0) then
begin
// special case, we need to fillup shift bits on last written byte in the buffer with highest byte from our UInt64
Data := Data shl Shift;
Stream[Ps-1] := Stream[Ps-1] or GetByte(Data, 5);
end;
WriteInt64BigEndian(Data, 4, Stream, Ps);
end;
end;
procedure GenerateData(var Stream : TBytes);
var
Count : Integer;
I : UInt64;
Ps : Integer;
Shift : Byte;
begin
Count := 1285*4+10;
SetLength(Stream, Count); // make room for 4 Imageframes (1280 bytes or 10240 bits) and 5 byte marker (40 bits) + 10 bytes extra room
FillChar(Stream[0], Count, 0);
Ps := 1;
// first write some garbage
Stream[0] := $AF;
// our first marker will be shifted 3 bits to the left
Shift := 3;
I := FrameSync shl Shift;
// write our Framesync (40+ bits = 6 bytes)
WriteInt64BigEndian(I, 6, Stream, Ps);
// add our data, 1280 bytes or 160 times 8 bytes, we use $F1 F2 F3 F4 F5 F6 F7 F8 as sequence
// (fits in Int 64) so that we can verify our decoding stage later on
WritePixelData(Stream, Ps, Shift);
// write some garbage
Stream[Ps] := $AE;
Inc(Ps);
// our second marker will be shifted 2 bits to the left
Shift := 2;
I := FrameSync shl Shift;
WriteInt64BigEndian(I, 6, Stream, Ps);
WritePixelData(Stream, Ps, Shift);
// write some garbage
Stream[Ps] := $AD;
Inc(Ps);
// our third marker will be shifted 1 bit to the left
Shift := 1;
I := FrameSync shl Shift;
WriteInt64BigEndian(I, 6, Stream, Ps);
WritePixelData(Stream, Ps, Shift);
// write some garbage
Stream[Ps] := $AC;
Inc(Ps);
// our third marker will be shifted 5 bits to the left
Shift := 5;
I := FrameSync shl Shift;
WriteInt64BigEndian(I, 6, Stream, Ps);
WritePixelData(Stream, Ps, Shift);
SetLength(Stream, Ps-1)
end;
procedure DecodeData(const Stream : TBytes);
var
Ps : Integer;
OrgPs : Integer;
BPos : Integer;
I : UInt64;
Check : UInt64;
Shift : Byte;
ByteNum : Byte;
ImageData : ImageArray;
begin
Ps := 0;
Shift := 0;
while Ps < Length(Stream) do
begin
// try to find a marker
// determine the number of bytes we need to read, 40bits = 5 bytes,
// when we have shifted bits this will require 6 bytes
if Shift = 0 then
ByteNum := 5
else
ByteNum := 6;
// save initial position in the stream
OrgPs := Ps;
// read our marker
I := ReadInt64BigEndian(ByteNum, Stream, Ps);
// if we have shifted bits, shift them on byte boundary and make sure we only have the 40 lower bits
if Shift > 0 then
I := (I shr Shift) and $FFFFFFFFFF;
if I = FrameSync then
begin
// we found our marker, process pixel data (ie read next 10240 bits, taking shift into account)
// If we have shift, our first bits will be found in the last marker byte, so go back one position in the stream
if Shift > 0 then
Dec(Ps);
ReadPixelData(Stream, Ps, Shift, ImageData);
// process Image array accordingly, here we will just check that we have our written data back
BPos := 0;
Check := $F1F2F3F4F5F6F7F8;
for ByteNum := 1 to 160 do
begin
I := ReadInt64BigEndian(8, ImageData, BPos);
// if our data is not correct, raise error
Assert(I = Check, 'error decoding image data');
end;
end
else
begin
Ps := OrgPs;
// we did not find our marker, advance 1 bit
Inc(Shift);
if Shift > 7 then
begin
// reset shift value
Shift := 0;
// advance to next byte boundary
Inc(Ps);
end;
end;
end;
end;
Var
AStream : TBytes;
begin
try
GenerateData(AStream);
DecodeData(AStream);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Related
I can build a raw IP packet that contains a UDP packet that contains useful data (DNS request).
I can send it and see that it's sent in Wireshark. Wireshark parses it as a legal DNS request, so everything looks smoothly except the DNS answer - I get no answer, nothing.
My code (sorry, it's far from prod-level code):
var
D:WSAData;
SendSocket, ReceiveSocket: TSocket;
bytes: Integer;
bOpt : Integer;
Buf : TPacketBuffer;
SendAddrIn : TSockAddrIn;
RecvAddIn: TSockAddrIn;
sockAddrSize: Integer;
iTotalSize : Word;
begin
try
if WSAStartup($202, D)<>0 then
begin
writeln('error..');
exit;
end;
SendSocket:=socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if SendSocket=INVALID_SOCKET then
writeln(WSAGetLastError);
// Option: Header Include
bOpt := 1;
bytes := SetSockOpt(SendSocket, IPPROTO_IP, IP_HDRINCL, #bOpt, SizeOf(bOpt));
if bytes = SOCKET_ERROR then
begin
Writeln('setsockopt(IP_HDRINCL) failed: '+IntToStr(WSAGetLastError));
exit;
end;
BuildHeaders(SrcIP, SrcPort,
DestIP, DestPort,
dns,
Buf, SendAddrIn, iTotalSize);
Writeln(inttostr(iTotalSize) + ' bytes to send');
bytes := SendTo(SendSocket, buf, iTotalSize, 0, #SendAddrIn, SizeOf(SendAddrIn));
if bytes = SOCKET_ERROR then
writeln('sendto() failed: '+IntToStr(WSAGetLastError))
else
writeln('send '+IntToStr(bytes)+' bytes.');
ReceiveSocket:=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
RecvAddIn.sin_addr.s_addr := htonl(0);
RecvAddIn.sin_family := AF_INET;
RecvAddIn.sin_port := htons(SrcPort);
if bind(ReceiveSocket, TSockAddr(RecvAddIn), sizeof(RecvAddIn)) = SOCKET_ERROR then
begin
writeln('bind() failed: '+IntToStr(WSAGetLastError));
exit;
end;
FillChar(buf, SizeOf(buf), 0);
sockAddrSize := sizeof(RecvAddIn);
bytes := RecvFrom(ReceiveSocket, buf, SizeOf(buf), 0, TSockAddr(RecvAddIn), sockAddrSize);
if bytes = SOCKET_ERROR then
writeln('RecvFrom() failed: '+IntToStr(WSAGetLastError))
else
writeln('RecvFrom '+IntToStr(bytes)+' bytes.');
CloseSocket(SendSocket);
CloseSocket(ReceiveSocket);
WSACleanup;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Wireshark shows this packet as:
I tried to create two sockets with the same local port to send and to receive data, each of its own type. What is wrong?..
UPDATE:
Thank you guys for the ideas.
Indeed, the receiving socket has to be fully initialized before any sending.
But as I've found - the main issue is with UDP packet checksum calculation. I've found out that a simple "ping" tool generates a checksum that doesn't equal the one generated by my code (of course, for the same input values). And when I just used their value (again, all the input values were preserved) - the DNS server returned the response!
To generate the checksum I use the next code:
function CheckSum(var Buffer; Size : integer) : Word;
type
TWordArray = array[0..1] of Word;
var
ChkSum : LongWord;
i : Integer;
begin
ChkSum := 0;
i := 0;
while Size > 1 do
begin
ChkSum := ChkSum + TWordArray(Buffer)[i];
inc(i);
Size := Size - SizeOf(Word);
end;
if Size=1 then
ChkSum := ChkSum + Byte(TWordArray(Buffer)[i]);
ChkSum := (ChkSum shr 16) + (ChkSum and $FFFF);
ChkSum := ChkSum + (Chksum shr 16);
Result := Word(ChkSum);
end;
procedure BuildHeaders(FromIP : string; iFromPort : Word;
ToIP : string; iToPort : Word;
StrMessage : TBytes; var Buf: TPacketBuffer;
var remote : TSockAddrIn; var iTotalSize: Word);
var
dwFromIP : LongWord;
dwToIP : LongWord;
iIPVersion : Word;
iIPSize : Word;
ipHdr : T_IP_Header;
udpHdr : T_UDP_Header;
iUdpSize : Word;
iUdpChecksumSize : Word;
cksum : Word;
Ptr : ^Byte;
procedure IncPtr(Value : Integer);
begin
ptr := pointer(integer(ptr) + Value);
end;
begin
dwFromIP := inet_Addr(PAnsiChar(AnsiString(FromIP)));
dwToIP := inet_Addr(PAnsiChar(AnsiString(ToIP)));
iTotalSize := sizeof(ipHdr) + sizeof(udpHdr) + length(strMessage);
iIPVersion := 4;
iIPSize := sizeof(ipHdr) div sizeof(LongWord);
//
// IP version goes in the high order 4 bits of ip_verlen. The
// IP header length (in 32-bit words) goes in the lower 4 bits.
//
ipHdr.ip_verlen := (iIPVersion shl 4) or iIPSize;
ipHdr.ip_tos := 0; // IP type of service
ipHdr.ip_totallength := htons(iTotalSize); // Total packet len
ipHdr.ip_id := $1545; // Unique identifier: set to 0
ipHdr.ip_offset := 0; // Fragment offset field
ipHdr.ip_ttl := 128;
ipHdr.ip_protocol := $11; // Protocol(UDP)
ipHdr.ip_checksum := 0 ; // IP checksum
ipHdr.ip_srcaddr := dwFromIP; // Source address
ipHdr.ip_destaddr := dwToIP; // Destination address
iUdpSize := sizeof(udpHdr) + length(strMessage);
udpHdr.src_portno := htons(iFromPort) ;
udpHdr.dst_portno := htons(iToPort) ;
udpHdr.udp_length := htons(iUdpSize) ;
udpHdr.udp_checksum := 0 ;
//
// Build the UDP pseudo-header for calculating the UDP checksum.
// The pseudo-header consists of the 32-bit source IP address,
// the 32-bit destination IP address, a zero byte, the 8-bit
// IP protocol field, the 16-bit UDP length, and the UDP
// header itself along with its data (padded with a 0 if
// the data is odd length).
//
iUdpChecksumSize := 0;
ptr := #buf[0];
FillChar(Buf, SizeOf(Buf), 0);
Move(ipHdr.ip_srcaddr, ptr^, SizeOf(ipHdr.ip_srcaddr));
IncPtr(SizeOf(ipHdr.ip_srcaddr));
iUdpChecksumSize := iUdpChecksumSize + sizeof(ipHdr.ip_srcaddr);
Move(ipHdr.ip_destaddr, ptr^, SizeOf(ipHdr.ip_destaddr));
IncPtr(SizeOf(ipHdr.ip_destaddr));
iUdpChecksumSize := iUdpChecksumSize + sizeof(ipHdr.ip_destaddr);
IncPtr(1);
Inc(iUdpChecksumSize);
Move(ipHdr.ip_protocol, ptr^, sizeof(ipHdr.ip_protocol));
IncPtr(sizeof(ipHdr.ip_protocol));
iUdpChecksumSize := iUdpChecksumSize + sizeof(ipHdr.ip_protocol);
Move(udpHdr.udp_length, ptr^, sizeof(udpHdr.udp_length));
IncPtr(sizeof(udpHdr.udp_length));
iUdpChecksumSize := iUdpChecksumSize + sizeof(udpHdr.udp_length);
move(udpHdr, ptr^, sizeof(udpHdr));
IncPtr(sizeof(udpHdr));
iUdpChecksumSize := iUdpCheckSumSize + sizeof(udpHdr);
Move(StrMessage[1], ptr^, Length(strMessage));
IncPtr(Length(StrMessage));
iUdpChecksumSize := iUdpChecksumSize + length(strMessage);
cksum := checksum(buf, iUdpChecksumSize);
udpHdr.udp_checksum := $FA8B;//cksum;
//
// Now assemble the IP and UDP headers along with the data
// so we can send it
//
FillChar(Buf, SizeOf(Buf), 0);
Ptr := #Buf[0];
Move(ipHdr, ptr^, SizeOf(ipHdr)); IncPtr(SizeOf(ipHdr));
Move(udpHdr, ptr^, SizeOf(udpHdr)); IncPtr(SizeOf(udpHdr));
Move(StrMessage[0], ptr^, length(StrMessage));
remote.sin_family := AF_INET;
remote.sin_port := htons(iToPort);
remote.sin_addr.s_addr := dwToIP;
end;
If anyone has another well-working implementation, please share...
You are creating separate sockets to send and receive the DNS packets, but you are creating the receiving socket after sending the request. It is possible/likely that the response arrives before the receiving socket is ready (use Wireshark to confirm that), in which case the response will simply be discarded by the OS.
You need to fully prepare the receiving socket before you send the request.
Ok, I've found the bug in the checksum calculation.
The next edition works fine and generates the correct checksum:
function CheckSum(var Buffer; Size : integer) : Word;
type
TWordArray = array[0..1] of Word;
var
ChkSum : LongWord;
i : Integer;
Item: Word;
begin
ChkSum := 0;
i := 0;
while Size > 1 do
begin
Item := TWordArray(Buffer)[i];
Item := Swap(Item);
ChkSum := ChkSum + Item;
inc(i);
Size := Size - SizeOf(Word);
end;
if Size=1 then
ChkSum := ChkSum + Byte(TWordArray(Buffer)[i]);
ChkSum := (ChkSum shr 16) + (ChkSum and $FFFF);
ChkSum := not ChkSum;
// ChkSum := ChkSum + (Chksum shr 16);
Result := Word(ChkSum);
end;
If you see any issues with it, please share your thoughts.
I am trying to print a code128C (numbers only) but I believe that the way of sending the data is incorrect ... at the time of reading the code the conversion does not result in the data initially informed.
In code128A I submit an ASCCI code, the printer converts to hex and print...the reader convert it back to ASCII.
In code128C if I submit an ASCCI, at the time of reading the reader converts to decimal, which does not result in the initial value.
EX:
128A Input: '1' Printer: 31 Reading: 1
128C Input: '1' Printer: 31 Reading: 49
I imagine that I should submit the input code already in integer .... but as the command is composed of other information I do not know how to send it in integer.
This is the code of code128A:
ComandoAnsiString := tp.cod128A('12'); //Data entry
function TTP650.cod128A(cod: AnsiString): AnsiString;
begin
// Fill out the CODE 128 printing protocol
Result := #29+#107+#73 + chr(length(cod)+2) + #123+#65 + cod;
end;
WritePrinter( HandleImp, PAnsiChar(ComandoAnsiString), Length(ComandoAnsiString),
CaracteresImpressos); //send to printer
This is the code I've been trying with code128C:
ComandoAnsiString := tp.cod128C('12');
function TTP650.cod128C(cod: AnsiString): AnsiString;
begin
Result := #29+#107+#73 + chr(length(cod)+2) + #123+#67 + cod;
end;
WritePrinter( HandleImp, PAnsiChar(ComandoAnsiString), Length(ComandoAnsiString),
CaracteresImpressos);
I'm dealing with a thermal printer and one codebar reader simple, default.
The sending codes(WritePrinter) are from the library WinSpool ... the rest are codes written by me.
Important code information is on pages 47 to 50 of the guide.
Guide
Assuming users will enter the wanted barcodes as a string of digits which may be stored somewhere as string and at the time of printing, passed to the printing function as human readable string.
The printing function will then convert to an array of bytes, packing the digits according to CODE C (each pair of two decimal digits, forming a value 00..99, stored in a byte). Iow, if the entry string of digits is e.g. '123456', then this is represented by three bytes with values 12, 34, 56.
function cod128C(const cod: string): TBytes;
const
GS = 29; // GS - Print bar code
k = 107; // k - -"-
m = 73; // m - CODE128
CS = 123; // { - select code set //}
CC = 67; // C - CODE C
var
i, len, n, x: integer;
s: string;
begin
len := Length(cod);
if len = 0 then exit;
// raise for odd number of digits in cod, ...
// if Odd(len) then
// raise Exception.Create('cod must have even number of digits');
s := cod;
// ... alternatively assume a preceeding zero digit before the first digit
// in cod
if Odd(len) then
begin
s := '0'+s;
inc(len);
end;
len := len div 2; // we pack 2 digits into one byte
SetLength(result, 6 + len);
result[0] := GS;
result[1] := k;
result[2] := m;
result[3] := 2 + len; // length of cod, + 2 for following code set selector
result[4] := CS;
result[5] := CC;
n := length(s);
i := 1; // index to S
x := 6; // index to result
while i < n do
begin
result[x] := StrToInt(MidStr(s, i, 2));
inc(i, 2);
inc(x, 1);
end;
end;
And with a form with a button, edit and memo you can test the function and send it to your printer with the following.
procedure TForm1.Button1Click(Sender: TObject);
var
cmnd: TBytes;
i: integer;
s: string;
begin
cmnd := cod128C(Edit1.Text);
for i := 0 to Length(cmnd)-1 do
s := s+IntToStr(cmnd[i])+', ';
Memo1.Lines.Add(s);
WritePrinter( HandleImp, #cmnd[0], Length(cmnd), CaracteresImpressos);
end;
You may want to add a check for only decimal digits in the input string, but I leave that to you.
This question already has answers here:
Converting decimal/integer to binary - how and why it works the way it does?
(6 answers)
Closed 4 years ago.
I have done some Example to convert a string to binary but i couldn't find a way to walk on each character in the string and complete the whole calculations process and then step to the next character in the string, Here is my code:
var i,j, rest, results :integer;
restResult : string;
begin
results := 1;
for i := 1 to length(stringValue) do
begin
while (results > 0) do
begin
results := ord(stringValue[i]) div 2;
rest := ord(stringValue[i]) mod 2;
restResult := restResult + inttostr(rest);
end;
end;
// Get The Rests Backwards
for i := length(restResult) downto 1 do
begin
result := result + restResult[i];
end;
The application always get into infinite loop, any suggestions?
Your results := ord(stringValue[i]) div 2; remains the same, because stringValue[i] does not change, so while loop is infinite.
To solve this mistake:
for i := 1 to length(stringValue) do
begin
t := ord(stringValue[i]);
repeat
restResult := restResult + inttostr(t mod 2);
t := t div 2;
until t = 0;
end;
But note that you cannot divide resulting string into pieces for distinct chars, because length of binary representation will vary depending on char itself.
This is example of code with fixed length for representation of char (here AnsiChar):
function AnsiStringToBinaryString(const s: AnsiString): String;
const
SBits: array[0..1] of string = ('0', '1');
var
i, k, t: Integer;
schar: string;
begin
Result := '';
for i := 1 to Length(s) do begin
t := Ord(s[i]);
schar := '';
for k := 1 to 8 * SizeOf(AnsiChar) do begin
schar := SBits[t mod 2] + schar;
t := t div 2
end;
Result := Result + schar;
end;
end;
'#A z': (division bars are mine)
01000000|01000001|00100000|01111010
# A space z
source array(4 bytes)
[$80,$80,$80,$80] =integer 0
[$80,$80,$80,$81] = 1
[$80,$80,$80,$FF] = 127
[$80,$80,$81,$01] = 128
need to convert this to integer.
below is my code and its working at the moment.
function convert(b: array of Byte): Integer;
var
i, st, p: Integer;
Negative: Boolean;
begin
result := 0;
st := -1;
for i := 0 to High(b) do
begin
if b[i] = $80 then Continue // skip leading 80
else
begin
st := i;
Negative := b[i] < $80;
b[i] := abs(b[i] - $80);
Break;
end;
end;
if st = -1 then exit;
for i := st to High(b) do
begin
p := round(Power(254, High(b) - i));
result := result + b[i] * p;
result := result - (p div 2);
end;
if Negative then result := -1 * result
end;
i'm looking for a better function?
Update:
file link
https://drive.google.com/file/d/0ByBA4QF-YOggZUdzcXpmOS1aam8/view?usp=sharing
in uploaded file ID field offset is from 5 to 9
NEW:
Now i got into new problem which is decoding date field
Date field hex [$80,$8F,$21,$C1] -> possible date 1995-12-15
* in uploaded file date field offset is from 199 to 203
Just an example of some improvements as outlined by David.
The array is passed by reference as a const.
The array is fixed in size.
The use of floating point calculations are converted directly into a constant array.
Const
MaxRange = 3;
Type
TMySpecial = array[0..MaxRange] of Byte;
function Convert(const b: TMySpecial): Integer;
var
i, j: Integer;
Negative: Boolean;
Const
// Pwr[i] = Round(Power(254,MaxRange-i));
Pwr: array[0..MaxRange] of Cardinal = (16387064,64516,254,1);
begin
for i := 0 to MaxRange do begin
if (b[i] <> $80) then begin
Negative := b[i] < $80;
Result := Abs(b[i] - $80)*Pwr[i] - (Pwr[i] shr 1);
for j := i+1 to MaxRange do
Result := Result + b[j]*Pwr[j] - (Pwr[j] shr 1);
if Negative then
Result := -Result;
Exit;
end;
end;
Result := 0;
end;
Note that less code lines is not always a sign of good performance.
Always measure performance before optimizing the code in order to find real bottlenecks.
Often code readability is better than optimizing over the top.
And for future references, please tell us what the algorithm is supposed to do.
Code for testing:
const
X : array[0..3] of TMySpecial =
(($80,$80,$80,$80), // =integer 0
($80,$80,$80,$81), // = 1
($80,$80,$80,$FF), // = 127
($80,$80,$81,$01)); // = 128
var
i,j: Integer;
sw: TStopWatch;
begin
sw := TStopWatch.StartNew;
for i := 1 to 100000000 do
for j := 0 to 3 do
Convert(X[j]);
WriteLn(sw.ElapsedMilliseconds);
ReadLn;
end.
When I search the words "JPEG" and "metadata", I have many answers to manipulate the metadata... and this is the opposite I want... ;o)
I have written a function which exactly works like I want... (if images are similar, and only the metadata change or not, the function returns True ; if at least one pixel changes, it returns False) but, I'd like to improve the performance...
The bottleneck is the bmp.Assign(jpg);
function CompareImages(fnFrom, fnTo: TFileName): Boolean;
var
j1, j2: TJpegImage;
b1, b2: TBitmap;
s1, s2: TMemoryStream;
begin
Result := False;
sw1.Start;
j1 := TJpegImage.Create;
j2 := TJpegImage.Create;
sw1.Stop;
sw2.Start;
s1 := TMemoryStream.Create;
s2 := TMemoryStream.Create;
sw2.Stop;
//sw3.Start;
b1 := TBitmap.Create;
b2 := TBitmap.Create;
//sw3.Stop;
try
sw1.Start;
j1.LoadFromFile(fnFrom);
j2.LoadFromFile(fnTo);
sw1.Stop;
// the very long part...
sw3.Start;
b1.Assign(j1);
b2.Assign(j2);
sw3.Stop;
sw4.Start;
b1.SaveToStream(s1);
b2.SaveToStream(s2);
sw4.Stop;
sw2.Start;
s1.Position := 0;
s2.Position := 0;
sw2.Stop;
sw5.Start;
Result := IsIdenticalStreams(s1, s2);
sw5.Stop;
finally
// sw3.Start;
b1.Free;
b2.Free;
// sw3.Stop;
sw2.Start;
s1.Free;
s2.Free;
sw2.Stop;
sw1.Start;
j1.Free;
j2.Free;
sw1.Stop;
end;
end;
sw1, ..., sw5 are TStopWatch, I used to identify the time spent.
IsIdenticalStreams comes from here.
If I directly compare the TJpegImage, the streams are different...
Any better way to code that?
Regards,
W.
Update:
Testing some solutions extract from the comments, I have the same performance with this code:
type
TMyJpeg = class(TJPEGImage)
public
function Equals(Graphic: TGraphic): Boolean; override;
end;
...
function CompareImages(fnFrom, fnTo: TFileName): Boolean;
var
j1, j2: TMyJpeg;
begin
sw1.Start;
Result := False;
j1 := TMyJpeg.Create;
j2 := TMyJpeg.Create;
try
j1.LoadFromFile(fnFrom);
j2.LoadFromFile(fnTo);
Result := j1.Bitmap.Equals(j2.Bitmap);
finally
j1.Free;
j2.Free;
end;
sw1.Stop;
end;
Any way to directly access the pixel data bytes from the file (skipping the metadata bytes) without bitmap conversion?
JPEG file consists of chunks, which types are identified by markers. The structure of chunks (except for stand-alone SOI, EOI, RSTn):
chunk type marker (big-endian FFxx)
chunk length (big-endian word)
data (length-2 bytes)
Edit: SOS chunk is limited by another marker, not by length.
Metadata chunks start with APPn marker (FFEn), except for APP0 (FFE0) marker with JFIF title.
So we can read and compare only significant chunks and ignore APPn chunks and COM chunk (as TLama noticed).
Example: hex view of some jpeg file:
It starts with SOI (Start Of Image) marker FFD8 (stand-alone, without length),
then APP0 chunk (FFE0) with length = 16 bytes,
then APP1 chunk (FFE1), which contains metadata (EXIF data, NIKON COOLPIX name etc), so we can ignore 9053 bytes (23 5D) and check next chunk marker at address 2373, and so on...
Edit: Simple parsing example:
var
jp: TMemoryStream;
Marker, Len: Word;
Position: Integer;
PBA: PByteArray;
procedure ReadLenAndMovePosition;
begin
Inc(Position, 2);
Len := Swap(PWord(#PBA[Position])^);
Inc(Position, Len);
end;
begin
jp := TMemoryStream.Create;
jp.LoadFromFile('D:\3.jpg');
Position := 0;
PBA := jp.Memory;
while (Position < jp.Size - 1) do begin
Marker := Swap(PWord(#PBA[Position])^);
case Marker of
$FFD8: begin
Memo1.Lines.Add('Start Of Image');
Inc(Position, 2);
end;
$FFD9: begin
Memo1.Lines.Add('End Of Image');
Inc(Position, 2);
end;
$FFE0: begin
ReadLenAndMovePosition;
Memo1.Lines.Add(Format('JFIF Header Len: %d', [Len]));
end;
$FFE1..$FFEF, $FFFE: begin
ReadLenAndMovePosition;
Memo1.Lines.Add(Format('APPn or COM Len: %d Ignored', [Len]));
end;
$FFDA: begin
//SOS marker, data stream, ended by another marker except for RSTn
Memo1.Lines.Add(Format('SOS data stream started at %d', [Position]));
Inc(Position, 2);
while Position < jp.Size - 1 do begin
if PBA[Position] = $FF then
if not (PBA[Position + 1] in [0, $D0..$D7]) then begin
Inc(Position, 2);
Memo1.Lines.Add(Format('SOS data stream ended at %d',
[Position]));
Break;
end;
Inc(Position);
end;
end;
else begin
ReadLenAndMovePosition;
Memo1.Lines.Add(Format('Marker %x Len: %d Significant', [Marker, Len]));
end;
end;
end;
jp.Free;
end;