Menu

#21 Top spacing seems too big

1.0
closed
None
2022-03-18
2022-01-05
Pierre Y.
No

Hi Angus,

With the same code as #20, except Text Vertical Alignment is set to tvaTop, the spacing between bounding rect "top" and the text seems very large, and the space between lines seems very large, too :

program TestDrawText;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.Types,
  Img32,
  Img32.Vector,
  Img32.Draw,
  Img32.Text,
  Img32.Fmt.PNG;

var
  FontReader: TFontReader;
  FontCache: TFontCache;
  I: TImage32;
  TextRect: TRect;

begin
  try
    FontReader := TFontReader.Create('Segoe UI');
    FontCache := TFontCache.Create(FontReader, 18);

    I := TImage32.Create(200, 200);
    I.Clear(clWhite32);

    TextRect := Rect(0, 0, 150, 200);
    DrawText(I, TextRect, 'This is a test, my friend', taCenter, tvaTop, FontCache, clBlack32);

    DrawDashedLine(I, Rectangle(TextRect), [20,10,15,10], nil, 1, clRed32, esPolygon);

    I.SaveToFile('Text.png');

    I.Free;
    FontCache.Free;
    FontReader.Free;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

You will get the image attached. Is it possible to reduce this spacing ?

Thank you very much,

--
Pierre Y.

1 Attachments

Discussion

  • Pierre Y.

    Pierre Y. - 2022-01-05

    As an example, in the attached image, the "button" on the left is rendered using TImage32, the one on the right is rendered using Windows GDI.

    What's... fun is that "Top" alignment places the text lower than "Bottom" alignment.

     
  • Angus Johnson

    Angus Johnson - 2022-01-06

    Firstly, part of the problem (if it is a problem) is the Segoe UI font seems to have a relatively large ascent (the space between the test baseline and the very top of the tallest text including diacritics - eg Ñ) compared to other fonts.

    Having said that, text in Image32 is generally positioned relative to the text's baseline. So, when positioning text relative to its top, it's up to you as the user to decide between using the TFontCache's Ascent property or using Img32.Vector.GetBounds to get the height of specific text. With regard to Img32.Text.DrawText and vtaTop alignment, the text is aligned according to the font's ascent. Again this would look less problematic with another font. Evidently the GDI uses some other metric to position text relative to the top. I'm open to suggestions 😜.

      fr: TFontReader;
      fc: TFontCache;
      I: TImage32;
      rec: TRect;
      recD: TRectD;
      B: TBitmap;
      fontName: string;
      fontSize: integer;
    begin
      fontSize := 18;
      fontName := 'Arial';
      //fontName := 'Segoe UI';
    
      font.Name := fontName;
      font.Height := -fontSize;
      rec := Types.Rect(20, 20, 180, 130);
      recD := RectD(rec);
      OffsetRect(recD, -0.5,-0.5);
    
      fr := TFontReader.Create(fontName);
      fc := TFontCache.Create(fr, fontSize);
      I := TImage32.Create(200, 150);
      B := TBitmap.Create(200, 150);
      try
        B.PixelFormat := pf32bit;
        with B.Canvas do
        begin
          Font.Assign(self.font);
          //Brush.Color := clWhite;
          Pen.Color := clRed;
          Windows.DrawText(Handle, 'This is a test', 14, rec, DT_CENTER or DT_TOP);
          with rec do
          begin
            MoveTo(Left, Top);
            LineTo(Right, Top);
            LineTo(Right, Bottom);
            LineTo(Left, Bottom);
            LineTo(Left, Top);
          end;
    
        end;
        //copy the TBitmap to I and save
        GetBitmapBits(B.Handle, B.Width * B.Height * 4, I.PixelBase);
        I.SetAlpha(255);
        I.SaveToFile(Format('c:\temp\%s_GDI.png', [fontName]));
    
        I.Clear(clWhite32);
        DrawText(I, rec, 'This is a test', taCenter, tvaTop, fc, clBlack32, true);
        DrawLine(I, Rectangle(recD), 1, clRed32, esClosed);
        I.SaveToFile(Format('c:\temp\%s_Img32.png', [fontName]));
      finally
        B.Free;
        I.Free;
        fc.Free;
        fr.Free;
      end;
    
     
  • Pierre Y.

    Pierre Y. - 2022-01-06

    Hi Angus,

    Thank you for taking time to help me.

    As I only target Windows devices... And I want to keep Segoe UI... Could it be possible to use the Windows Text rendering functions to render the text on a 32 bits bitmap (for antialiasing and alpha transparency) and then copy this image at the right place on the final TImage32 ?

    -- Pierre

     
  • Angus Johnson

    Angus Johnson - 2022-01-06

    You can draw text onto an opaque bitmap and fairly easily copy that text with the opaque background into a TImage32 object. When doing so however (eg using Windows.GetBitmapBits) you'll also need to manually set the alpha channel to 255 (because the GDI leaves the alpha channel completely untouched). That's pretty easy to do using TImage32.SetAlpha. Alternatively, you could use TImage32.CopyFromDC passing the bitmaps' canvas handle but that just hides quite a bit of unnecessary work.

    If you really want GDI text with a transparent background, that can be done but it's a fiddle - see below,

    procedure ConvertGrayscaleToAlpha(img: TImage32; rect: TRect);
    var
      p: PARGB;
      y,x, w,w2: integer;
    begin
      Types.IntersectRect(rect, rect, img.Bounds);
      w := RectWidth(rect);
      w2 := img.Width - w;
      p := PARGB(img.PixelRow[rect.Top]);
      inc(p, rect.Left);
    
      //nb: use the green color channel to set the alpha byte
      //as that bypasses issues with 'ClearType' text
      for y := rect.Top to rect.Bottom -1 do
      begin
        for x := rect.Left to rect.Right -1 do
        begin
          p.A := 255-p.G;
          p.R := 0; p.G := 0; p.B := 0;
          inc(p);
        end;
        inc(p, w2);
      end;
    end;
    
    var
      I, I2: TImage32;
      rec: TRect;
      recD: TRectD;
      B: TBitmap;
      fontName: string;
      fontSize: integer;
      txt: string;
    begin
    
      txt := 'This text is drawn using Windows'' GDI.';
      font.Size := -9;
      font.Name := 'Segoe UI';
    
      I := TImage32.Create(200, 150);
      I2 := TImage32.Create(I.Width, I.Height);
      B := TBitmap.Create(I.Width, I.Height);
      try
    
        //draw a fancy ellipse on I
        recD := RectD(I.Bounds);
        InflateRect(recD, -2, -2); //to accommodate line drawing
        DrawPolygon(I, Ellipse(recD), frNonZero, clBtnFace32);
        DrawEdge(I, Ellipse(recD), clWhite32, clSilver32, 3);
        InflateRect(recD, -20, -20);
        DrawEdge(I, Ellipse(recD), clSilver32, clWhite32, 3);
    
        //draw on the bitmap using GDI and then copy its pixels to I2 ...
        B.PixelFormat := pf32bit;
        with B.Canvas do
        begin
          Font.Assign(self.font);
          rec := Types.Rect(40, 38, I.Width - 40, I.Height);
          DrawText(Handle, PChar(txt), Length(txt), rec, DT_CENTER or DT_WORDBREAK);
          GetBitmapBits(B.Handle, B.Width * B.Height * 4, I2.PixelBase);
        end;
    
        //And since GDI drawing does not touch the alpha channel (ie left == 0)
        //I2.SetAlpha(255); //not needed because of ConvertGrayscaleToAlpha below
    
        //convert everything inside rec to
        ConvertGrayscaleToAlpha(I2, rec);
    
        //copy I2 onto I ...
        I.CopyBlend(I2, rec, rec, BlendToAlpha);
        I.SaveToFile('c:\temp\test.png');
    
      finally
        B.Free;
        I.Free;
        I2.Free;
      end;
    
     
  • Angus Johnson

    Angus Johnson - 2022-03-18
    • status: open --> closed