Link to home
Start Free TrialLog in
Avatar of borgo
borgo

asked on

How to covert a Bitmap to an array of...

How I can covert a Bitmap to an array of integer or to an array of string.
And How I can convert this array to a Bitmap ?

Thanks.
Avatar of Madshi
Madshi

borgo,

with TBitmap.ScanLine property you get access to the rare pixel data. Of course that is not so easy, because you have to look at the color format yourself.
E.g., for 32 bit bitmaps you'll have to read/write width*4 bytes from/to the scanLine property. Then you'll have to write a little loop that read/writes all (height) lines of the bitmap.

Look at this example:

procedure TForm1.Button1Click(Sender: TObject);
var
  x,y : integer;
  BitMap : TBitMap;
  P : PByteArray;
begin
  BitMap := TBitMap.create;
  try
    BitMap.LoadFromFile('..\Images\Splash\256color\factory.bmp');
    for y := 0 to BitMap.height -1 do
    begin
      P := BitMap.ScanLine[y];
      for x := 0 to BitMap.width -1 do
        P[x] := y;
    end;
  canvas.draw(0,0,BitMap);
  finally
    BitMap.free;
  end;
end;

Regards, Madshi.
P.S: If you like pure winAPI more, look at CreateDIBSection, SetDIBits, ...
Avatar of borgo

ASKER

The example is from the Delphi Help. I have just read it.
The problem is: I have a bitmap. I want to convert it to an array of intger (or string). Then I have to reverse the process.
ScanLine proprerty is a read only property.
Please con you write me a loop example (Delphi4) to do it ?

borgo,

yes, the ScanLine property is a readonly property. But that does not mean, that you can't use it to change the bitmap pixels!
It is a readonly property because it gives you a pointer to the bitmap buffer. It makes no sense to change the pointer to the bitmap buffer, but nevertheless you can use the readonly pointer to write to the read&write bitmap pixel buffer.
You're right, the example is from the Delphi help. It's just because it fits your needs. Please look at it a little bit close again! It loads a 256 color bitmap, then changes each and every byte of it, then displays the result of this operation. Please compile it and look at the results!

Regards, Madshi.
ASKER CERTIFIED SOLUTION
Avatar of williams2
williams2

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
williams2,

of course you know, that this stream method is very very very slow!!! AND you get the bitmap file header and all this stuff. So you don't know which bits/bytes you have to read/write in order to read/write specific pixels.
I don't think that is what borgo needs, right borgo?
I really prefer the ScanLine method. It is much faster and gives you only the pixels. Of course you have to look at the palette yourself (that means if you're using 16 or 256 color bitmaps).

I think it depends on for what borgo needs this all.

Regards, Madshi.
Madshi

Yes, that is correct, I am saving the headers within the bitmap and I'm fully aware of that. Now he also asked how to convert it to a string . You might be aware of the fact that the pallette is always saved within the header, or you might go read the SaveToStream method in Graphics.pas.

You say it is slow, maybe it is, but it is the answer to the question. But as you say, it depends on the purpose.

At least you should be sure about the width, as I mentioned before. If you hsve a 24 bit image you'll only have copied a third of the original bitmap.

Hmm.. I'm working on tomething, that might be able to solve this problem in generel.

Regards
/Williams
Avatar of borgo

ASKER

Williams2: Your code do what I have tryed to do some days ago.
Thank you very much.
I'm coding an application which sends a bmp between an UDP connection. I think to convert the bmp in a array of integer (or string) and then to send it. Do you think I'm wrong ? What do you think is the best way to do it ?
(You will get your points.)
borgo,

if you had written in the beginning for what purpose you need it. I thought you wanted to manipulate some pixels in a fast way...

Ok, now it's clear:
You should save it to a memoryStream. The memoryStream object has a memory property that is a pointer to the data buffer.

function BitmapToStr(bmp: TBitmap) : string;
var ms : TMemoryStream;
begin
  ms:=TMemoryStream;
  try
    bmp.WriteToStream(ms);
    // now you can access all the bitmap data through memoryStream's memory property. E.g. converting to a string:
    setLength(result,ms.size);
    Move(ms.Memory^,pointer(result)^,ms.size);
    // now you have all the bitmap data in a string.
  finally ms.Free end;
end;

BTW, this code is much much much faster than Williams' code.  :-))    (Sorry, Williams...)

If you really want to convert it to a string, you can use the following code to convert it back to an image:

function StrToBitmap(str: string) : TBitmap;
var ms : TMemoryStream;
begin
  ms:=TMemoryStream.Create;
  try
    ms.size:=length(str);
    Move(pointer(str)^,ms.Memory^,ms.size);
    result:=TBitmap.Create;
    result.loadFromStream(ms);
  finally ms.Free end;
end;

If you want an array of byte, do something like this:
type TAByte  : array [0..maxInt] of byte;
     TPAByte : ^TAByte;
var  abyte   : TPAByte;
abyte:=ms.Memory^;  // Now you can use e.g. abyte^[0] to access the first byte

If you want an array of integer, do something like this:
type TAInteger  : array[0..maxInt shr 2] of integer;
     TPAInteger : ^TAInteger;
var  aInt       : TPAInteger;
aint:=ms.Memory^;  // Now you can use e.g. aint^[0] to access the first byte
// Note, that you now have to transfer (ms.size div 4) integers... So I wouldn't choose this method...

Regards, Madshi.
P.S: borgo, give the points to Williams, if you like his answer more than mine. (Otherwise of course to me...)    :-)))
To Madshi:
I will just say, that the purpose of your example was right, but the questions was in some way easy to misunderstand.

To Madshi:
I have allready done this once, where I'll send the whole screen from a server to a client on request.
I think you will appreciate the following example, since byte or char stuffing is both slow and oldfashioned.
I din't use UDP, and I will not recommend using it, as it is unreliable and mostly used for messageservices and server notifications.

In the following example, you should do the following things:
Server:
create New application
Add a OnCreate event procedure from the form object, then replace unit 1 with Section A.
Client:
You do the same here as with the server, just replace unit1 with Section B.

Section A:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ScktComp, ExtCtrls, StdCtrls, ExtDlgs;

Const
  //You may set this to False if you would like the whole image!
  STRETCH = True;
  STRETCH_WIDTH = 200;
  STRETCH_HEIGHT = 150;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    ServerSocket1: TServerSocket;
    procedure ServerSocket1ClientRead(Sender: TObject;
      Socket: TCustomWinSocket);
  private
    { Private declarations }
    Stream: TMemoryStream;
    procedure SendNextPart(Socket: TCustomWinSocket);
  public
    { Public declarations }
    Timer: TTimer;
    procedure OnTime(Sender: TObject);
    procedure SendScreen(Socket: TCustomWinSocket);
  protected
    Bitmap: TBitmap;
  end;

var
  Form1: TForm1;


implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
begin
  Bitmap:= TBitmap.Create;
  if STRETCH then
  begin
    Bitmap.Width:= STRETCH_WIDTH;
    Bitmap.height:= STRETCH_HEIGHT;
  End else
  begin
    Bitmap.Width:= Screen.Width;
    Bitmap.Height:= Screen.Height;
  End;
  Timer:= TTimer.Create(Self);
  Timer.Interval:= 1000;
  Timer.OnTimer:= OnTime;
  Timer.Enabled:= True;
  ServerSocket1:= TServerSocket.Create(Self);
  With ServerSocket1 do
  begin
    Port:= 2500;
    Active:= True;
    ServerType:= stNonBlocking;
    OnClientRead:= ServerSocket1ClientRead;
  End;
  Stream:= TMemoryStream.Create;
end;

procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
  Socket: TCustomWinSocket);
var
  S: String;
Begin
  S:= Socket.ReceiveText;
  If S='Get me the screen' then SendScreen(Socket) else
  If S='Send next' then SendNextPart(Socket) else
  If S='Done' then Socket.Close;
End;

procedure TForm1.SendScreen(Socket: TCustomWinSocket);
var
  Size: Integer;
Begin
  Bitmap.SaveToStream(Stream);
  Size:= Stream.Size;
  Stream.Position:= 0;
  Socket.SendBuf(Size,SizeOf(Size));
End;

procedure TForm1.SendNextPart(Socket: TCustomWinSocket);
const
  MaxChunkSize = 8192; { copy in 8K chunks }
var
  ChunkSize: Integer;
  CopyBuffer: Array[0..MaxChunkSize] of Byte;
Begin
  Chunksize:= Stream.Size-Stream.Position;
  If ChunkSize>0 then
  begin
    If ChunkSize > MaxChunkSize then ChunkSize:= MaxChunkSize;
    Stream.Read(CopyBuffer,ChunkSize);
    Socket.SendBuf(CopyBuffer,ChunkSize);
  End else
    Stream.Clear; //The final
End;

procedure TForm1.OnTime(Sender: TObject);
var
  dwRop: DWord;
  DC: hDC;
begin
  dwRop:= SRCCOPY;
  DC:= GetDC(0);
  If STRETCH then
    StretchBlt(
      Bitmap.Canvas.Handle, // handle to destination device context
      0, // x-coordinate of destination rectangle's upper-left corner
      0, // y-coordinate of destination rectangle's upper-left corner
      Bitmap.Width, // width of destination rectangle
      Bitmap.Height, // height of destination rectangle
      DC, // handle to source device context
      0, // x-coordinate of source rectangle's upper-left corner
      0, // y-coordinate of source rectangle's upper-left corner
      Screen.Width,      // width of source rectangle
      Screen.Height,      // height of source rectangle
      dwRop        // raster operation code (See below)
    )
  else
    BitBlt(
      Bitmap.Canvas.Handle, // handle to destination device context
      0, // x-coordinate of destination rectangle's upper-left corner
      0, // y-coordinate of destination rectangle's upper-left corner
      Bitmap.Width, // width of destination rectangle
      Bitmap.Height, // height of destination rectangle
      DC, // handle to source device context
      0, // x-coordinate of source rectangle's upper-left corner
      0, // y-coordinate of source rectangle's upper-left corner
      dwRop        // raster operation code (See below)
    );
  ReleaseDC(0,DC)
{
BLACKNESS Fills the destination rectangle using the color associated with
index 0 in the physical palette. (This color is black for the
                default physical palette.)
DSTINVERT Inverts the destination rectangle.
MERGECOPY Merges the colors of the source rectangle with the specified
pattern by using the Boolean AND operator.
MERGEPAINT Merges the colors of the inverted source rectangle with the
colors of the destination rectangle by using the Boolean OR
                operator.
NOTSRCCOPY Copies the inverted source rectangle to the destination.
NOTSRCERASE Combines the colors of the source and destination rectangles
by using the Boolean OR operator and then inverts the resultant
                color.
PATCOPY Copies the specified pattern into the destination bitmap.
PATINVERT Combines the colors of the specified pattern with the colors of
the destination rectangle by using the Boolean XOR operator.
PATPAINT Combines the colors of the pattern with the colors of the
inverted source rectangle by using the Boolean OR operator. The
                result of this operation is combined with the colors of the
                destination rectangle by using the Boolean OR operator.
SRCAND Combines the colors of the source and destination rectangles
by using the Boolean AND operator.
SRCCOPY Copies the source rectangle directly to the destination
rectangle.
SRCERASE Combines the inverted colors of the destination rectangle with
the colors of the source rectangle by using the Boolean AND operator.
SRCINVERT Combines the colors of the source and destination rectangles
by using the Boolean XOR operator.
SRCPAINT Combines the colors of the source and destination rectangles by
using the Boolean OR operator.
WHITENESS Fills the destination rectangle using the color associated with
index 1 in the physical palette. (This color is white for the
                default physical palette.)
}
End;


end.


Section B:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ScktComp, StdCtrls, ExtCtrls, ComCtrls;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    ProgressBar: TProgressBar;
    Button1: TButton;
    Button2: TButton;
    ClientSocket1: TClientSocket;
    Image1: TImage;
    Label1: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure ClientSocket1Connect(Sender: TObject;
      Socket: TCustomWinSocket);
    procedure ClientSocket1Error(Sender: TObject; Socket: TCustomWinSocket;
      ErrorEvent: TErrorEvent; var ErrorCode: Integer);
    procedure Button2Click(Sender: TObject);
    procedure ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
    procedure ClientSocket1Disconnect(Sender: TObject;
      Socket: TCustomWinSocket);

  private
    Stream: TMemoryStream;
  public
    Procedure UpdateProgressBar;
  protected
    Bitmap: TBitmap;
    Receiving: Boolean;
    FSize: Integer;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
begin
  Label1.Caption:= 'Connecting..';
  ClientSocket1.Open;
end;

procedure TForm1.ClientSocket1Connect(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  Label1.Caption:= 'Connected';
  Receiving:= False;
  FSize:= 0;
end;

procedure TForm1.ClientSocket1Error(Sender: TObject;
  Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
  var ErrorCode: Integer);
begin
  ShowMessage('Did you startup the server? I cannot find it!');
end;


procedure TForm1.Button2Click(Sender: TObject);
begin
  if ClientSocket1.Active then
    ClientSocket1.Socket.SendText('Get me the screen');
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Label1:= TLabel.Create(Self);
  Label1.SetBounds(32,72,32,13);
  Label1.Parent:= Self;
  Label1.Caption:= 'Idle..';
  Button1:= TButton.Create(Self);
  Button1.SetBounds(8,8,75,25);
  Button1.Parent:= Self;
  Button1.Caption:= 'Connect';
  Button1.OnClick:= Button1Click;
  Button2:= TButton.Create(Self);
  Button2.SetBounds(8,40,75,25);
  Button2.Parent:= Self;
  Button2.Caption:= 'Get screen';
  Button2.OnClick:= Button2Click;
  ClientSocket1:= TClientSocket.Create(Self);
  With ClientSocket1 do
  begin
    Active := False;
    ClientType := ctNonBlocking;
    Port := 2500;
    Address := '127.0.0.1';
    OnConnect := ClientSocket1Connect;
    OnDisconnect := ClientSocket1Disconnect;
    OnRead := ClientSocket1Read;
    OnError := ClientSocket1Error;
  end;
  Image1:= TImage.Create(Self);
  Image1.SetBounds(100,0,0,0);
  Image1.AutoSize:= true;
  Image1.Parent:= Self;

  Stream:= TMemoryStream.Create;
  ProgressBar:= TProgressBar.Create(Self);
  ProgressBar.Min:= 0;
  ProgressBar.Align:= alBottom;
  ProgressBar.Parent:= Self;
end;

Procedure TForm1.UpdateprogressBar;
Begin
  ProgressBar.Position:= ProgressBar.Max-FSize;
  progressBar.Update;
End;

procedure TForm1.ClientSocket1Read(Sender: TObject;
  Socket: TCustomWinSocket);
const
  MaxChunkSize = 8192; { copy in 8K chunks }
var
  BytesReceived: Longint;
  CopyBuffer: Array[0..MaxChunkSize] of Byte; { buffer for copying }
  ChunkSize: Integer;
  TempSize: Integer;
begin
  If FSize=0 then
  begin
    If Socket.ReceiveLength>=SizeOf(TempSize) then
    begin
      Socket.ReceiveBuf(TempSize,SizeOf(TempSize)); //get the size
      Stream.SetSize(TempSize);
      Stream.Position:= 0;
      ProgressBar.Max:= TempSize;
      FSize:= TempSize; //Threadsafe code!
    End;
  End;
  If (FSize>0) and not(Receiving) then
  begin
    Receiving:= True;
    While Socket.ReceiveLength>0 do
    Begin
      ChunkSize:= Socket.ReceiveLength;
      If ChunkSize > MaxChunkSize then ChunkSize:= MaxChunkSize;
      BytesReceived:= Socket.ReceiveBuf(CopyBuffer,ChunkSize);
      Stream.Write(CopyBuffer, BytesReceived); { ...write chunk }
      Dec(FSize,BytesReceived);
      UpdateProgressBar;
    End;
    //This is called "to Syncronize trasnsmissions"
    Socket.SendText('Send next');
    If FSize=0 then
    begin
      Socket.SendText('Done');
      Stream.Position:= 0;
      Image1.Picture.Bitmap.LoadFromStream(Stream);
      FSize:= 0;
      ProgressBar.Position:= 0;
//      Stream.SetSize(0);
      Stream.Clear;
    End;
    Receiving:= False;
  End;
end;


procedure TForm1.ClientSocket1Disconnect(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  Label1.Caption:= 'Disconnected';
end;

end.

Cheers
Williams
Correction:
Sorry! Last message ("To Madshi: ") was ment for borgo. :-)

Regards
Williams
Williams,

your sources look quite nice. Only one suggestion: Why are you not using the memory property of the TMemoryStream? You could save the CopyBuffer...

Regards, Madshi.
Very simple Madshi:

I'm doing this because I take care of the Delphi structure. When implementing the memory property directly, you are in a potential risc of getting pointer-errors in your application. That is why exceptions were created in the first place.
Maybe you didn't know it, but 72% of most errors using C/C++ are pointer-errors due to the pointerstructure in C, where most things are implemented by reading from or writing to the memory directly. These errors are sometimes VERY difficult to find.

If you'd really like to, I can easily give you an example without using any stream objects at all, but I think that is not why Streams were invented in the first place.

So when I'm posing an example, you will not see me adding things directly to the memory unless it's an important factor to the algorithm or program. So you won't see that either in Delphi's help support or developer support at www.inprise.com .

Regards,
Williams
If you would be right: Why should Inprise add a memory property?

No no, it is there to be used. And if you use it, your programs will be faster, because you save some memory copy actions. Of course you have to look that you handle it right, otherwise you'll get exceptions, but you can't simply say: "I use no pointers because they're dangerous". No, you can't get around pointers. And if you look into the Delphi original sources, you'll find that the Delphi programmers use pointers all the time.
And you're definetely NOT in a potential risk of getting pointer-errors, if you're using the memory property of TMemoryStream! Look at my last That's nonsense (sorry...). I'm using it in all my programs, and I can assure you, I've never had any problems with that!
You're right, that most errors in C++ are pointer-errors. But it's the wrong position to don't use pointers at all or to make long roundabout ways just because you're afraid of pointers. Ok, where you don't need them, very nice.

Regards, Madshi.

P.S: My first answer with the ScanLine stuff IS AN EXAMPLE from the Delphi help. And they're using a pointer to the pixel buffer and read and write directly from/to the pixel buffer memory. So you should think about what you said about the Delphi help and the developer support...
P.P.S: Did't mean to insult or annoy you in any way. I'm just a little bit confused, because you're saying somehow, that it's a bad style of programming to use directly memory access - and so you're implying that my programming style is bad, too. And - you will understand that - I DON'T LIKE THAT.
Madshi:

Please READ the context of my posing, you do not seem to read it carefully before applying the message above.

1. The Delphi programmers can NOT avoid using direct memory access, and they sure know what they do, because they built it. But when I pose an example of using the memory property, I automatically RECOMMEND to use it without a question. You do not know how experienced borgo is about this; If he gets a "Out of memory" exception, I'm responsible for it!

2. You do not seem to know anything about bytestuffing. To your knowledge you cannot typecast binary data to a string. When doing so, the strings looses everything next to the first #0 character. If strings might be WideSting specific ( using Strict Var's or the {$V+} compiler directive ). In that case the string truncates at the first two #0 characters.

3. I said specifically:
"..you will not see me adding things directly to the memory unless it's an important factor to the algorithm or program.."
If the user is experienced enough, he will see that it can be done faster, and then it's HIS OWN RESPONABILITY how to optimze it.
If you would like to read about the OSI-model, you might find, that the data is beeing copied from 5 to 7 times before the actually transmission takes place.

4. The scanline property is ok to use when implementing direct bitmap manipulation, but it is also a way MUCH faster than the canvas.pixel property. When implementing it, you are MAYBE accessing memory that is prior to the screen, which makes accessing it very very slow compared to other parts of the memory.
When copying memoryblocks in memory (as I do with copybuffer), that is not accessed by any hardware or software, the cpu is actually doing it very fast. If you do not believe me, you should go try looking it up.

Note: I think this discussion is brought too far away from it's origin. And I don't think it will be worth it discussing how fast these things can be done prior on examples of how to do it. So please, if you would have me excused!

Regards
Williams
(1) and (3): Ok, my point of view is a little bit different, but that's ok...  :-)
(2) You CAN typecast binary data to a Delphi (!) string, as long as you don't convert it to a pchar string, cause the length of Delphi strings is not determined by #0 character but by a length integer in front of the string data. I guess, you know that? I'm doing such stuff every day...
(4) The scanline property is implemented by using DIB functions. That means DeviceIndependentBitmap (I'm sure you know that). So if these bitmaps are device independent, they're surely not in the video memory.

Ok, I agree. We should not spend our time on discussing. It seems that there was a little misunderstanding. I thought you would condemn direct memory access as a bad programming style and YOU just want to protect unexperienced programmers from playing too much with that. Of course I can agree with that...  :-)
Avatar of borgo

ASKER

Thank you William2 for your answer.
Can you give me one easy and fast advice please ?
This is my code:

procedure TForm1.ClientSocket1Read(Sender: TObject;
  Socket: TCustomWinSocket);
var
  TempSize: Integer;
begin
  TempStr:=Socket.ReceiveText;
  // I need to use TempStr to read some text
  // Then I use your code.But Socket.ReceiveLength is allways = 0
  // If I delete the line "TempStr:=Socket.ReceiveText;"
  //   all is OK. Why ? Any advice ?
  If (Socket.ReceiveLength)>=(SizeOf(TempSize)) then
    begin
      Socket.ReceiveBuf(TempSize,SizeOf(TempSize));
      Stream.SetSize(TempSize);
      Stream.Position:= 0;
      ..........................................
...........
     
Yes! When sending text, you should receive the text with writing f.ex.:

S:= Socket.ReceiveText;

This way, you also should expect the incoming string from the opposite part. Unfortunately you cannot test the receivelength property, but the event is the same.

If you like to know another example, you should try read the example I wrote for "ItsMe" at:

             https://www.experts-exchange.com/Q.10104621

it explains how to capture the screen and send it to a client on request. At the bottom I also wrote an example using Francois Piettes freeware components.


Happy C/S developing!

Cheers,
Williams
Avatar of borgo

ASKER

Many many thanks to Williams :-)

Cheers,

Borgo.
That's allright dear borgo.

Happy C/S-haunting

Cheers,
Williams