3

I am trying to convert an image (lets say black and white) to a Matrix (where 0 = black and 1 = white)

i tried with this code :

procedure TForm1.Button1Click(Sender: TObject);
type
  tab = array[1..1000,1..1000] of byte;
var i,j: integer;
    s : string;
    image : TBitmap;
    t : tab;
begin
  image := TBitmap.Create;
  image.LoadFromFile('c:\image.bmp');

  s := '';
  for i := 0 to image.Height do
  begin
     for j := 0 to image.Width do
     begin
      if image.Canvas.Pixels[i,j] = clWhite then
        t[i,j] := 0
      else
        t[i,j] := 1;

     end;
  end;
  for i := 0 to image.Height do
  begin
    for j := 0 to image.Width do
     begin
      s:=s + IntToStr(t[i,j]);
     end;
     Memo1.Lines.Add(s);
     s:='';
  end;
end;

But it gave me wrong results.

Any Idea?

Ouerghi Yassine
  • 1,835
  • 7
  • 43
  • 72
  • 3
    Nooo, `Pixels` are back again. – TLama Mar 09 '13 at 16:17
  • what ?? what am i doing wrong here??! – Ouerghi Yassine Mar 09 '13 at 16:21
  • 1
    It will be terribly slow... You should use `TBitmap.ScanLine` property when you're accessing large area of pixels. – TLama Mar 09 '13 at 16:22
  • well i dont care about it being slow or fast, and all the images im using are > 100 width and heiht – Ouerghi Yassine Mar 09 '13 at 16:23
  • 1
    @Roro: Except for the three obvious bugs, as pointed out in my answer. – Andreas Rejbrand Mar 09 '13 at 16:37
  • @yassine_hell : "what ?? what am i doing wrong here??!" you must looping width first. Pixels[i,j] = Pixels[x,y] – AsepRoro Mar 09 '13 at 17:10
  • 3
    "It's difficult to tell what is being asked here." Well, I'm pretty sure it's "What is the problem with the code?". "...cannot be reasonably answered in its current form" So neither my nor TLama's answer is good? "Ambigious, vague, incomplete, overly broad, or thetorical"? Really? I don't think so. – Andreas Rejbrand Mar 11 '13 at 13:49
  • 3
    Why was this question closed? Seems like a valid one. – Leonardo Herrera Mar 11 '13 at 18:25
  • 1
    Stack Overflow is not a personal debugging service, @Leonardo. The question essentially says "Please debug my code for me." The close reason I'd have chosen, had I seen this before, would have been *too localized*. – Rob Kennedy Mar 11 '13 at 21:46
  • @RobKennedy: I agree on that one. (So casperOne sure chose the wrong reason.) But still, if someone actually is nice enough to do the debugging (hm... like myself, in this case), I think its OK. In addition, discussion about good and bad coding techniques might be useful even to third parties. – Andreas Rejbrand Mar 12 '13 at 18:47

3 Answers3

12

There are five bugs and two other issues in your code!

First,

for i := 0 to image.Height do

must be replaced by

for i := 0 to image.Height - 1 do

(why?) and similarly,

for j := 0 to image.Width do

must be replaced by

for j := 0 to image.Width - 1 do

Second, the Pixels array takes arguments [x, y], not [y, x]. Hence, you need to replace

image.Canvas.Pixels[i,j]

by

image.Canvas.Pixels[j,i]

Third, you wrote "0 = black and 1 = white" but obviously you do the opposite!

Fourth, you try to access t[0, 0], even though your matrix starts indexing at 1. Use array[0..1000,0..1000] of byte; to fix that.

Fifth, you have a memory leak (image isn't freed -- use try..finally).

Also, it is better to use dynamic arrays:

type
  TByteMatrix = array of array of byte;

var
  mat: TByteMatrix;

and you begin with

SetLength(mat, image.Height - 1, image.Width - 1);

if you want it to index [y, x], and opposite otherwise.

Finally, you should not use the Pixels property at all in this case, since it is terribly slow. Instead, use the Scanline property. See this or that or something else for more information.

Also, you will gain a lot of speed simply by adding Memo1.Lines.BeginUpdate before and Memo1.Lines.EndUpdate after the update of the memo control.

Community
  • 1
  • 1
Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
5

The following procedure converts the input ABitmap bitmap to a multidimensional AMatrix array of bytes, which represents pixels and where 0 value means white pixel and 1 means any other color:

type
  TPixelMatrix = array of array of Byte;

procedure BitmapToMatrix(ABitmap: TBitmap; var AMatrix: TPixelMatrix);
type
  TRGBBytes = array[0..2] of Byte;
var
  I: Integer;
  X: Integer;
  Y: Integer;
  Size: Integer;
  Pixels: PByteArray;
  SourceColor: TRGBBytes;
const
  TripleSize = SizeOf(TRGBBytes);
begin
  case ABitmap.PixelFormat of
    pf24bit: Size := SizeOf(TRGBTriple);
    pf32bit: Size := SizeOf(TRGBQuad);
  else
    raise Exception.Create('ABitmap must be 24-bit or 32-bit format!');
  end;

  SetLength(AMatrix, ABitmap.Height, ABitmap.Width);
  for I := 0 to TripleSize - 1 do
    SourceColor[I] := Byte(clWhite shr (16 - (I * 8)));

  for Y := 0 to ABitmap.Height - 1 do
  begin
    Pixels := ABitmap.ScanLine[Y];
    for X := 0 to ABitmap.Width - 1 do
    begin
      if CompareMem(@Pixels[(X * Size)], @SourceColor, TripleSize) then
        AMatrix[Y, X] := 0
      else
        AMatrix[Y, X] := 1;
    end;
  end;
end;

This procedure prints out the multidimensional AMatrix array of bytes to the AMemo memo box:

procedure ShowPixelMatrix(AMemo: TMemo; const AMatrix: TPixelMatrix);
var
  S: string;
  X: Integer;
  Y: Integer;
begin
  AMemo.Clear;
  AMemo.Lines.BeginUpdate;
  try
    AMemo.Lines.Add('Matrix size: ' + IntToStr(Length(AMatrix[0])) + 'x' +
      IntToStr(Length(AMatrix)));
    AMemo.Lines.Add('');

    for Y := 0 to High(AMatrix) do
    begin
      S := '';
      for X := 0 to High(AMatrix[Y]) - 1 do
      begin
        S := S + IntToStr(AMatrix[Y, X]);
      end;
      AMemo.Lines.Add(S);
    end;
  finally
    AMemo.Lines.EndUpdate;
  end;
end;

And the usage of the above procedures:

procedure TForm1.Button1Click(Sender: TObject);
var
  Bitmap: TBitmap;
  PixelMatrix: TPixelMatrix;
begin
  Bitmap := TBitmap.Create;
  try
    Bitmap.LoadFromFile('d:\Image.bmp');
    BitmapToMatrix(Bitmap, PixelMatrix);
  finally
    Bitmap.Free;
  end;
  ShowPixelMatrix(Memo1, PixelMatrix);
end;

This extension of the above BitmapToMatrix procedure allows you to specify at which luminance level given by the AMinIntensity parameter will be pixels taken as non-white.

The more the AMinIntensity value is closer to 0, the more lighter pixels are treated as non-white. This allows you to work with a color intensity tolerance (e.g. to better recognize antialiased text):

procedure BitmapToMatrixEx(ABitmap: TBitmap; var AMatrix: TPixelMatrix;
  AMinIntensity: Byte);
type
  TRGBBytes = array[0..2] of Byte;
var
  X: Integer;
  Y: Integer;
  Gray: Byte;
  Size: Integer;
  Pixels: PByteArray;
begin
  case ABitmap.PixelFormat of
    pf24bit: Size := SizeOf(TRGBTriple);
    pf32bit: Size := SizeOf(TRGBQuad);
  else
    raise Exception.Create('ABitmap must be 24-bit or 32-bit format!');
  end;

  SetLength(AMatrix, ABitmap.Height, ABitmap.Width);

  for Y := 0 to ABitmap.Height - 1 do
  begin
    Pixels := ABitmap.ScanLine[Y];
    for X := 0 to ABitmap.Width - 1 do
    begin
      Gray := 255 - Round((0.299 * Pixels[(X * Size) + 2]) +
        (0.587 * Pixels[(X * Size) + 1]) + (0.114 * Pixels[(X * Size)]));

      if Gray < AMinIntensity then
        AMatrix[Y, X] := 0
      else
        AMatrix[Y, X] := 1;
    end;
  end;
end;
TLama
  • 75,147
  • 17
  • 214
  • 392
  • Note that the above code is untested written just in browser... I'll review it later (I hope). – TLama Mar 09 '13 at 16:43
  • If the image is black/white, it might actually be 1-bit! And if it is, it is even easier to convert it to a byte/textual matrix. – Andreas Rejbrand Mar 09 '13 at 16:59
  • @yassine_hell, what pixel formats so you need to support for this ? Are your bitmaps 1-bit as Andreas think ? If so, what other pixel formats do you need to work with ? – TLama Mar 09 '13 at 18:06
  • no i guess its 32bits, because i'm actually dealing with white background and light blue text. i just mentioned black and white to make it easier to understand – Ouerghi Yassine Mar 09 '13 at 19:58
  • (Most BMPs are 24-bit.) – Andreas Rejbrand Mar 09 '13 at 20:33
  • Thank you, your code worked like a charm :) and actually i was doing a small captcha solver, it consist at converting an image to 0's and 1's then extract each letter :) thank you anyways ^^ here s the result http://i48.tinypic.com/xmibr5.png – Ouerghi Yassine Mar 09 '13 at 20:35
  • @yassine_hell, you're welcome! Although, for your intention I would probably count with some gray tolerance as all captchas (at least which I've seen) have antialised text and using this way can make you to read the text more difficult. I would personally mark in that matrix only pixels having some minimal color intensity. – TLama Mar 09 '13 at 20:45
  • well thank god the captcha i'm dealing with is not that hard, only white background and blue text. – Ouerghi Yassine Mar 09 '13 at 20:48
  • It's quite easy to do so. You can calculate e.g. luminance for each pixel and if it's higher than a specified value, add it to the matrix as 1, otherwise as 0. I'll post an update in few minutes just out of curiosity. – TLama Mar 09 '13 at 20:51
  • lol ok have fun, and if you're interested here is the captcha i'm dealing with http://www.ocard.com.tn/jeu_ocard/captcha/captcha.php (if i can post external links) – Ouerghi Yassine Mar 09 '13 at 20:54
  • I don't like captcha solvers. @yassine_hell: Are you sure your application is legitimate? Are you sure the owners of the web site (http://www.ocard.com.tn/) want their system to be hacked? – Andreas Rejbrand Mar 09 '13 at 20:56
  • no i'm not hacking, actually i'm helping then for free lol, and we are contacting by phone, they just added the captcha (after i found out a way to spam their DB) now i wana prove that their captcha is week. – Ouerghi Yassine Mar 09 '13 at 21:00
-1

Memo lines position is decline, but your looping image.height first its will be result reverse in memo, to that try this code

procedure TForm1.Button1Click(Sender: TObject);
var i,j: integer;
    s : string;
    image : TBitmap;
begin
  image := TBitmap.Create;
  image.LoadFromFile('c:\image.bmp');

  s := '';
  for i := 0 to image.width-1 do
  begin
     for j := 0 to image.Height-1 do
     begin
      if image.Canvas.Pixels[i,j] = clWhite then
        s := s+'0'
      else
        s := s+'1';
     end;
     memo1.Lines.Add(s);
     s:='';
  end;
end;
AsepRoro
  • 353
  • 2
  • 6
  • 19
  • (1) out of bounds. (2) white = 0? (3) memory leak [Also: no begin/end update?] – Andreas Rejbrand Mar 09 '13 at 16:46
  • my answer have tested in my pc. and the memo view same with image.bitmap. if that code to slow please change to scanline – AsepRoro Mar 09 '13 at 16:52
  • 1
    Well, the out of bounds error is obvious! Personally, I prefer code that is guaranteed to work, rather than code that might almost work with some luck. *Update:* that's better. – Andreas Rejbrand Mar 09 '13 at 16:53
  • oh sory about that i forget to add -1 – AsepRoro Mar 09 '13 at 16:55
  • @Andreas : for (2) white = 0?, its simple way only change for integer to string, please look the final result that he want – AsepRoro Mar 09 '13 at 17:01
  • Yeah, buy the first line of the OP's question is "I am trying to convert an image (lets say black and white) to a Matrix (**where 0 = black and 1 = white**)" (my emphasis). Anyhow, I'm more concerned about the memory leak. – Andreas Rejbrand Mar 09 '13 at 17:03
  • Thanks for your submission and now I'm getting more knowledge about delphi – AsepRoro Mar 11 '13 at 13:35