Recent

Author Topic: How to make a TBitMap in 2-color-Mode (PixelFormat=pf1bit) to work?  (Read 3521 times)

Hartmut

  • Hero Member
  • *****
  • Posts: 761
I want to draw font characters of a selectable Font on a TCanvas of a TBitMap and then analyze their shape. For 2 reasons I want to use this TBitMap in 2-color-Mode (PixelFormat=pf1bit):
1) then I have only 2 colors to analyze (not black and white and *many* gray values)
2) it might speed things up to carry only 1 bit per pixel around instead of 24.

My code works with 'PixelFormat=pf24bit', but not with 'PixelFormat=pf1bit'. Here is a short demo:

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. {$IFNDEF LINUX}
  6.    {$apptype console} {for writeln on Windows}
  7. {$ENDIF}
  8.  
  9. interface
  10.  
  11. uses
  12.  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, Types;
  13.  
  14. type
  15.  
  16.  { TForm1 }
  17.  
  18.  TForm1 = class(TForm)
  19.   Button1: TButton;
  20.   procedure Button1Click(Sender: TObject);
  21.  private
  22.  public
  23.  end;
  24.  
  25. var
  26.  Form1: TForm1;
  27.  
  28. implementation
  29.  
  30. {$R *.lfm}
  31.  
  32. { TForm1 }
  33.  
  34. const BGC = clWhite; {Background Color}
  35.       FGC = clBlack; {Foreground Color}
  36.  
  37. procedure drawBitMap(BM: TBitMap);
  38.    {"draws" the content of a BitMap as Hex-values}
  39.    var col: TColor;
  40.        xx,yy: longint;
  41.        x,y: integer;
  42.    begin
  43.    BM.GetSize(xx,yy); {get size of the Bitmap}
  44.  
  45.    for y:=0 to yy-1 do
  46.       begin
  47.       for x:=0 to xx-1 do
  48.          begin
  49.          col:=BM.Canvas.Pixels[x,y];
  50.          write(IntToHex(col,6), ' ');
  51.          end;
  52.       writeln;
  53.       end;
  54.    end;
  55.  
  56. procedure TForm1.Button1Click(Sender: TObject);
  57.    var BM: TBitMap;
  58.        CV: TCanvas;
  59.        SR: TSize;
  60.        s: string;
  61.    begin
  62.    BM:=TBitMap.Create;
  63.    BM.PixelFormat:=pf24bit; // works
  64. // BM.PixelFormat:=pf1bit;  // works not
  65.    writeln('PixelFormat=', BM.PixelFormat);
  66.  
  67.    CV:=BM.Canvas; {abbreviation}
  68.    CV.Font.Name:='Courier New';
  69.    CV.Font.Height:=12;
  70.  
  71.    s:='X';
  72.    SR:=CV.TextExtent(s); {get size of string 's'}
  73.    BM.SetSize(SR.cx,SR.cy);
  74.  
  75.    CV.Brush.Color:=BGC;
  76.    CV.Clear;
  77.    CV.Clear;
  78.    CV.Pen.Color:=FGC;
  79.  
  80.    CV.TextOut(0,0,s); {draw 's' on Canvas}
  81.    drawBitMap(BM);    {shows the content of a BitMap as Hex-values}
  82.  
  83.    BM.Free;
  84.    end;
  85.  
  86. end.

The demo draws letter 'X' on a Canvas and then shows the color-values of every pixel. On the screenshot you see my output:
a) with 'PixelFormat=pf24bit' you can recognize the letter 'X' with values of white ('FFFFFF') and several gray values.
b) with 'PixelFormat=pf1bit' you see, that *all* pixels have the same value ('010000'). I played with const 'BGC' and 'FGC' but this did not help. And there is a message "Gdk-WARNING **: 14:34:05.858: No colormap in gc_get_foreground". What does it mean?

What do I do wrong? I use Lazarus 2.0.10 on Linux Ubuntu 22.04. I attached a compilable project. Thanks in advance.

wp

  • Hero Member
  • *****
  • Posts: 12004
Re: How to make a TBitMap in 2-color-Mode (PixelFormat=pf1bit) to work?
« Reply #1 on: December 19, 2023, 03:40:53 pm »
The trick is to set the bitmap's Monochrome property to true, and to do this immediately after creating the bitmap, before setting its size. See attached demo
Code: Pascal  [Select][+][-]
  1.   FBitmapMono := TBitmap.Create;
  2.   FBitmapMono.Monochrome := true;  // before SetSize
  3.   FBitmapMono.SetSize(W, H);
  4.   FBitmapMono.Canvas.Brush.Color := clWhite;
  5.   FBitmapMono.Canvas.FillRect(0, 0, W, H);
  6.   FBitmapMono.Canvas.Brush.Color := clBlack;
  7.   FBitmapMono.Canvas.Ellipse(5, 5, W-10, H-10);

[P.S.]
Tried this on Windows, where it works. But double-checking on a Linux-VM I see that I get a crash when the monochrome bitmap is painted. And with your code, I get the same result as you.

[P.P.S.]
Now I see the 'x'! I must open the debug console before the bitmap is written to the console...
« Last Edit: December 19, 2023, 04:51:43 pm by wp »

domasz

  • Sr. Member
  • ****
  • Posts: 443
Re: How to make a TBitMap in 2-color-Mode (PixelFormat=pf1bit) to work?
« Reply #2 on: December 19, 2023, 03:55:50 pm »
PixelFormats different than 24 and 32 bit are broken in Lazarus.

Hartmut

  • Hero Member
  • *****
  • Posts: 761
Re: How to make a TBitMap in 2-color-Mode (PixelFormat=pf1bit) to work?
« Reply #3 on: December 19, 2023, 05:15:43 pm »
Hello wp, thank you for this idea. Unfortunately this trick does not help on my Linux system (as you said). Meanwhile I tested my demo on Windows 7, 32 bit, where it works without problems (no need for the monochrome-trick), see new screenshot. I'm frustrated, that again something, what works on Windows, does not work on Linux. Unfortunately I need this only for Linux...



PixelFormats different than 24 and 32 bit are broken in Lazarus.

Hmmm. But on Windows it works perfectly... Is this an official statement from a FPC/Lazarus developer? Is this documented somewhere?

wp

  • Hero Member
  • *****
  • Posts: 12004
Re: How to make a TBitMap in 2-color-Mode (PixelFormat=pf1bit) to work?
« Reply #4 on: December 19, 2023, 05:31:04 pm »
Hello wp, thank you for this idea. Unfortunately this trick does not help on my Linux system (as you said). Meanwhile I tested my demo on Windows 7, 32 bit, where it works without problems (no need for the monochrome-trick), see new screenshot. I'm frustrated, that again something, what works on Windows, does not work on Linux. Unfortunately I need this only for Linux...
I did some further investigation why it first did not work in Linux and then it did: I also had switched from gtk2 to qt5 widgetset - on gtk2 it is not working (screenshot like in your initial post), on qt5 it works (screenshot like in my previous post). I also checked gtk3 - here the monochrome bitmap is converted to 24bit.

PixelFormats different than 24 and 32 bit are broken in Lazarus.
Hmmm. But on Windows it works perfectly... Is this an official statement from a FPC/Lazarus developer? Is this documented somewhere?
In my experience, it depends... It is possible to create 1-bit, or paletted 8-bit bitmaps and to store them. But when they are loaded into a TImage they are always converted to 24 or 32-bit.

Hartmut

  • Hero Member
  • *****
  • Posts: 761
Re: How to make a TBitMap in 2-color-Mode (PixelFormat=pf1bit) to work?
« Reply #5 on: December 19, 2023, 06:41:36 pm »
I did some further investigation why it first did not work in Linux and then it did: I also had switched from gtk2 to qt5 widgetset - on gtk2 it is not working (screenshot like in your initial post), on qt5 it works (screenshot like in my previous post). I also checked gtk3 - here the monochrome bitmap is converted to 24bit.

Thank you wp for investigating this. I'm on gtk2. Switching to qt5 now is not an option. But good to know for the future. Maybe I must stay with my 24-bit TBitMap...

Or is there any other class, where I can draw a font character on it and which can be set to monochrome and where I can read it's pixels?

wp

  • Hero Member
  • *****
  • Posts: 12004
Re: How to make a TBitMap in 2-color-Mode (PixelFormat=pf1bit) to work?
« Reply #6 on: December 19, 2023, 06:57:09 pm »
I want to draw font characters of a selectable Font on a TCanvas of a TBitMap and then analyze their shape. For 2 reasons I want to use this TBitMap in 2-color-Mode (PixelFormat=pf1bit):
1) then I have only 2 colors to analyze (not black and white and *many* gray values)
Hmmm.... But the gray colors are what makes true-type fonts look nice. In pure black and white (no grays), the characters will look very ugly. Do you want this?

Hartmut

  • Hero Member
  • *****
  • Posts: 761
Re: How to make a TBitMap in 2-color-Mode (PixelFormat=pf1bit) to work?
« Reply #7 on: December 19, 2023, 07:06:09 pm »
I want to draw font characters of a selectable Font on a TCanvas of a TBitMap and then analyze their shape. For 2 reasons I want to use this TBitMap in 2-color-Mode (PixelFormat=pf1bit):
1) then I have only 2 colors to analyze (not black and white and *many* gray values)
Hmmm.... But the gray colors are what makes true-type fonts look nice. In pure black and white (no grays), the characters will look very ugly. Do you want this?

Yes. Because I do not display those characters, I only "draw" them somewhere not visual with the only purpose to analyze their shape. Therefore monochrome is much easier as to have many grays.

wp

  • Hero Member
  • *****
  • Posts: 12004
Re: How to make a TBitMap in 2-color-Mode (PixelFormat=pf1bit) to work?
« Reply #8 on: December 20, 2023, 12:47:38 am »
Here is a mini project based on FCLImage which works in Windows and Linux/gtk2 and displays the (anti-aliased) pixels of a text in black-and-white:
Code: Pascal  [Select][+][-]
  1. program project1;
  2.  
  3. uses
  4.   Types, FPImage, FPCanvas, FPImgCanv, FTFont;
  5.  
  6. const
  7.   TXT = 'A';
  8.  
  9. var
  10.   img: TFPMemoryImage;
  11.   cnv: TFPCustomCanvas;
  12.   fnt: TFreeTypeFont;
  13.   sz: TSize;
  14.   x, y: Integer;
  15.   c: TFPColor;
  16.   fontName: String = 'DejaVuSans';
  17. begin
  18.   {$IFDEF Linux}
  19.   FTFont.InitEngine('/usr/share/fonts/truetype/ttf-dejavu/');
  20.   fontName := '/usr/share/fonts/truetype/dejavu/' + fontName;
  21.   {$ENDIF}
  22.   fnt := TFreeTypeFont.Create;
  23.   fnt.AntiAliased := false;
  24.  
  25.   img := TFPMemoryImage.Create(1, 1);
  26.  
  27.   // measure text size
  28.   cnv := TFPImageCanvas.Create(img);
  29.   cnv.Font := fnt;
  30.   cnv.Font.Name := fontName;
  31.   cnv.Font.Size := 20;
  32.   cnv.Font.FPColor := colBlack;
  33.   sz := cnv.TextExtent(TXT);
  34.   img.SetSize(sz.CX, sz.CY);
  35.  
  36.   // Draw background
  37.   cnv.Brush.FPColor := colWhite;
  38.   cnv.FillRect(0, 0, img.Width, img.Height);
  39.  
  40.   // Draw text
  41.   cnv.TextOut(0, sz.Cy, TXT);
  42.  
  43.   // Output to screen
  44.   for y := 0 to img.Height-1 do
  45.   begin
  46.     for x := 0 to img.Width-1 do
  47.     begin
  48.       c := img.Colors[x, y];
  49.       if c = colBlack then
  50.         Write('XX')
  51.       else
  52.       if c = colWhite then
  53.         Write('..')
  54.       else
  55.         Write('??');
  56.     end;
  57.     WriteLn;
  58.   end;
  59.  
  60.   cnv.Free;
  61.   img.Free;
  62.   fnt.Free;
  63. end.        

Hartmut

  • Hero Member
  • *****
  • Posts: 761
Re: How to make a TBitMap in 2-color-Mode (PixelFormat=pf1bit) to work?
« Reply #9 on: December 20, 2023, 05:03:49 pm »
Thank you very much wp for that demo. I studied it and again learned from you. Unfortunately it does not return the results which I am looking for:

In 1st line I want to detect if an UTF8-character is contained in a font or not. If not, those characters are normally displayed as a rectangle with 4 or 6 hex digits in it (see $EFBFBE and $EFBFBF in screenshot1).

With my program from my 1st post (with a small modification by using your displaying routine and setting Font.Height:=16) you can see this shape in screenshot2 (lower part), while your program shows something completely other (see screenshot3, lower part).

But even for valid UTF8-characters I found discrepancies: e.g. UTF-char $EFBFA5 is shown normally as something like letter "y" with 2 horizontal lines (see also screenshot1). My program from my 1st post shows the same (see screenshot2, upper part). But your program shows some additional artefact (see screenshot3, upper part). Another example would be $C2A9, which should display a "(c)" Copyright sign, but there is an additional "Ä" before it with your code (screenshot 4).

I don't want you to spend more time into this project because it is not important enough for me. I have a (not so ideal) 24-bit solution which "does the job". It would be fine to have a monochrome solution (which might be something faster), but it's not worth that you spend more time on it (I hope I found the right words - my English is not so good). Again thanks that you tried to help me.

wp

  • Hero Member
  • *****
  • Posts: 12004
Re: How to make a TBitMap in 2-color-Mode (PixelFormat=pf1bit) to work?
« Reply #10 on: December 20, 2023, 07:00:47 pm »
One idea: My demo was an FPC commandline program, and this does not know about UTF8. Either transfer the code to a Lazarus GUI application, or add package LazUtils to the project requirements and unit LazUTF8 to the uses clause. This way I was able to display a greek alpha character (which is not in my ANSI code page). Did not test on Linux, though.

Hartmut

  • Hero Member
  • *****
  • Posts: 761
Re: How to make a TBitMap in 2-color-Mode (PixelFormat=pf1bit) to work?
« Reply #11 on: December 21, 2023, 12:43:06 pm »
You are right, wp, with package LazUtils and unit LazUTF8 the results are better - but not correct. In screenshot 5 you see UTF8-char $C2A9 in the upper part from your code without LazUtils & LazUTF8 and in the lower part with both. The right part of the "(c)" is missing.

And in screenshot 6 (which was made with LazUtils & LazUTF8) you see in the upper part UTF8-char $EFBFA5 (which should be a "y" with 2 horizontal lines as in screenshot 2 from my reply #9) and in the lower part of screenshot 6 you see UTF8-char $EFBFBF (which should be a complete rectangle with "FFFF" inside as in screenshot 2 from my reply #9).

wp

  • Hero Member
  • *****
  • Posts: 12004
Re: How to make a TBitMap in 2-color-Mode (PixelFormat=pf1bit) to work?
« Reply #12 on: December 21, 2023, 05:08:55 pm »
I don't know. Basically the approach is correct, but there seems to be some issue in extraction of the text size. In case of the '(c)', you can double the width of the image (img.SetSize(sz.CX*2, sz.CY) ), and now the character is complete (with some extra blank pixels at the right). From this I get the impression that the glyph has some empty pixels at the left which are not considered in width calculation, but the glyph nevertheless is drawn at its origin. Unfortunately the relevant information is contained only in classes which are declared as private and thus are not accessible. There are some other truetype libraries in the Lazarus/FPC source tree, maybe they would be more helpful, but as you said that you are happy with your gray-scale workaround, I do not intend to further investigate in this direction.

Hartmut

  • Hero Member
  • *****
  • Posts: 761
Re: How to make a TBitMap in 2-color-Mode (PixelFormat=pf1bit) to work?
« Reply #13 on: December 21, 2023, 05:58:15 pm »
Yes, with double-width the Copyright sign is complete, but for UTF8-characters $EFBFA5 and $EFBFBF you must double the height too and still don't get the correct shape as I get with my code from my 1st post.

We should stop here, because other truetype libraries in the Lazarus/FPC source tree could be interesting, but will probably cause other problems and this is not worth your valuable time, because I have a working workaround. Thanks again for your help. Again I learned from you.

domasz

  • Sr. Member
  • ****
  • Posts: 443
Re: How to make a TBitMap in 2-color-Mode (PixelFormat=pf1bit) to work?
« Reply #14 on: December 21, 2023, 06:03:57 pm »
If speed is not very important you can set background to something like 10xwidth, 10xheight, draw the character somewhere in the middle and then crop borders.

 

TinyPortal © 2005-2018