Drawing text with outline on a canvas

Top  Previous  Next

Doom9's Forum > Programming and Hacking > Development > [delphi] drawing text with outline on a canvas

PDA

 

View Full Version : [delphi] drawing text with outline on a canvas

Kaiousama

10th September 2004, 18:13

Hi,

i'd like to draw text on a canvas from my delphi7 application but i'd like to add outline to the drawed text.

Something like the rasterize.cpp of Subtitler/VSFilter does.

 

Do you think it is possible? do you know any resource i can look into in order to find information about the ropic? are there any delphi graphical library with this feature?

 

Thanks.

pieter1976

11th September 2004, 18:01

I think you should have a look at the font properties

 

Canvas.Font.Style

Pyscrow

11th September 2004, 22:54

THis was recently discussed in the Delphi newsgroups, did you ask the question there as well?

 

If not - I think I copied the answer from Peter Below as it sounded like it might be useful at some stage, I can post it if you want.

unmei

12th September 2004, 00:54

Psycrow, yes please. I would be interested as well.

 

At the moment i do it "the hardcore way", with scanning and changing pixel on scanlines.

It works well for me, but i think if you can do it with path it would be incredibly faster. This scanline operations are OK for me as i have to many other things to do on scanline basis as well. But if outline is the only "special" thing you want to apply, the way over bitmaps seems rather inconvienent.

 

Anyway, if you want to have a look at (or use) my solution, it is in the u96 subversion tree (http://subversion.corecodec.org/index.cgi/u96), in the file pixfxU.pas (you need spuUtilU.pas and eventually arrayutilU.pas as well), the function for RGBA bitmaps is addOutlineRGBQ and for RGB bitmaps it is addOutline. The RGB-only version is probably MUCH slower since i no longer use it and therefore did not optimise it. You can however look at what i changed in the RGBA version and apply that to the RBG-only version. The source is like 150Kb, but you only need few functions, so you can probably put all you need in one unit with only 5-10 or so Kb.

[Toff]

12th September 2004, 04:24

Theorically you would do something like :

 

procedure TForm1.Button1Click(Sender: TObject);

begin

with Form1.Canvas do

begin

Font.Name := 'Arial';

Font.Size := 64;

Font.Style := Font.Style + [fsBold];

Pen.Width := 3;

Brush.Style := bsClear;

BeginPath(Handle);

TextOut(50, 50, 'OutLined');

EndPath(Handle);

Brush.Style := bsSolid;

Brush.Color := clWhite;

StrokeAndFillPath(Handle)

end;

end;

 

Unfortunately it's not antialiased :-(

To have antialiasing you would have to use the GetPath function which return a table with all coordinates of lines and curves to trace, and then use another rasterizer to do this job :eek:

I don't know any for Delphi though. Something like libart, Anti-Grain Geometry or freetype.

unmei

12th September 2004, 14:27

If it's really that simply i'll have to try it - it looks like it also will work with TNT's wideTextOut. Just need to mod the function for setting the alpha bit then.. :)

The antialiasing ..well, doesnt really matter to me, it wasn't antialiased before neither ..just have to keep my own smoothing.

 

Thanks Toff

pieter1976

12th September 2004, 18:55

I have a delphi question to.

 

How can I make graphics go 30 frames / sec doublebufferd and keeping the image perfect.

 

I know that something call scanline is very efficient for drawing the pixels.

 

How do I synchronize?

Kaiousama

14th September 2004, 07:47

Thanks everybody for your replies.

 

Like Toff said the first problem is to get antialiased text, take a look to this piece of code (start a new delphi project than place on the main form 1 TImage and 2 TButton and use this unit):

 

 

unit Unit1;

 

interface

 

uses

Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

Dialogs, ExtCtrls, TntExtCtrls, StdCtrls, math;

 

type

TForm1 = class(TForm)

Image1: TImage;

Button1: TButton;

Button2: TButton;

procedure Button1Click(Sender: TObject);

procedure Button2Click(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end;

 

var

Form1: TForm1;

 

implementation

 

{$R *.dfm}

procedure AntiAliasRect(clip: tbitmap; Rect: TRect);

var

x,y: Integer;

p0,p1,p2: pbytearray;

begin

Rect.Left:=max(1,Rect.Left);

Rect.Top:=max(1,Rect.Top);

Rect.Right:=min(clip.width-2,Rect.Right);

Rect.Bottom:=min(clip.height-2,Rect.Bottom);

 

clip.PixelFormat :=pf24bit; // 24bit needed to work with scanlines

 

for y:=Rect.Top to Rect.Bottom do

begin

p0:=clip.ScanLine [y-1];

p1:=clip.scanline [y];

p2:=clip.ScanLine [y+1];

for x:=Rect.Left to Rect.Right do

begin

p1[x*3]:=(p0[x*3]+p2[x*3]+p1[(x-1)*3]+p1[(x+1)*3])div 4;

p1[x*3+1]:=(p0[x*3+1]+p2[x*3+1]+p1[(x-1)*3+1]+p1[(x+1)*3+1])div 4;

p1[x*3+2]:=(p0[x*3+2]+p2[x*3+2]+p1[(x-1)*3+2]+p1[(x+1)*3+2])div 4;

end;

end;

end;

 

procedure TForm1.Button1Click(Sender: TObject);

const

TestText = 'This is a test';

var

CV: TCanvas;

R: TRect;

begin

CV := image1.Canvas;

CV.Font.Size := 48;

CV.Font.Color := clSkyBlue;

CV.Font.Name := 'Arial'; {Needs a true-type font!}

CV.Brush.Color := clWhite;

CV.Brush.Style := bsSolid;

CV.Pen.Color := clBlue;

CV.Pen.Style := psSolid;

CV.Pen.Width := 1;

CV.FillRect(TPaintbox(Sender).Clientrect);

{Uncomment the following line to see the text output rect in yellow}

// CV.Brush.Color := clYellow;

 

{This draws the interior of the letters}

CV.TextOut(0,0,TestText);

 

{Uncomment the following block to remove the frame drawn around

the text by StrokePath }

{

R.TopLeft := Point(0,0);

R.BottomRight := TPoint(CV.TextExtent(TestText));

InflateRect(R, -1, -1);

IntersectClipRect(CV.Handle, R.Left, R.Top, R.Right, R.Bottom);

}

 

{This draws an outline around the letters}

BeginPath(CV.Handle);

CV.Pen.Width := 1;

CV.TextOut(0,0,TestText);

EndPath(CV.Handle);

StrokePath(CV.Handle);

end;

 

procedure TForm1.Button2Click(Sender: TObject);

var

clip : TBitmap;

begin

clip := image1.Picture.Bitmap;

AntiAliasRect(clip,Rect(0,0,Clip.Width,Clip.Height));

end;

 

end.

 

Button1 draws text with outline, Button2 performs a very simple antialiasing. You'll notice that resulting image is blurred, that's because good antialiasing creates a Super-sampled image (4x, 8x, 16x) then apply antialiasing, then resize back to original size.

Do you have any idea of how to efficiently perform supersampling and scaling back?

 

There are even other matters to solve:

 

1) Outline is drawed over the text in the example, while it needs to be drawed below the text.

 

2) Pen.Width property is Integer, a floating point increment would be better (maybe it can be reached drawing text directly in a 16x superpersampled image).

 

3) Include Alpha level (transparency) for the drawed text, possibly with distinct values for main text and outline.

 

Any idea?

 

@Unmei : I'll take a look to pixifier in the next days, thank you very much.

unmei

14th September 2004, 15:33

i recommend you don't try to understand all of pixifier, you find these graphic things in the units i mentioned. Unless you want to see more than the graphic part of course..

 

Just a few thoughts,

 

You can use pf32bit with scanlines as well. This is what i do everywhere, to work with alpha.

You can cast the scanlines to PRGBQuadArray. This is defined somewhere in windows or graphics ..else you define it yourself:

 

TRGBQuadArray = array[0..65535] of TRGBQuad;

PRGBQuadArray = ^TRGBQuadArray;

 

I think it will not work with open array, so just use a large enough fixed one. You never use T acually only the pointer so it doesnt matter defining such large arrays.

 

TRGBQuad is defined in Windows. Its acually a 32-bit record with which you han acess red/green/blue/alpha induvidually. You can also cast it to Cardinal. alpha is called "reserved" and the help says it must be set to zero, but ignore this, you can use it like the other channels.

 

p0,p1,p2: pbytearray; -> p0,p1,p2: PRGBQuadArray;

.

.

p0:=clip.ScanLine [y-1]; -> p0 := clip.ScanLine [y-1];

.

.

then you can play with the components like this

p1^[x*3].rgbReserved := p0^[x*3].rgbBlue;

 

 

At first i though i get a heart attack when i saw p0 := clip.ScanLine [y-1], because all my lights flashed "scanline out of range" or "access violation", but your rectangle prevents this :)

 

Is there a reason you don't use bsClear during textOut, but then remove the bouding box later?

 

If you want to use anti-alias and alpha i think you have to use the antialias after clearing the background. This way you can decide about transparency based on (color of pixel = text or outline or bg color). Otherwise you have a smooth thansition between the colors and you have to figure out how much background/how much foreground that color corresponds to to assign a intermediate alpha level. IMO it is much easier to to a sharp alpha clearing and only after that a anti-alias. You can anti-alias the alpha channel then as well of course.

Pyscrow

16th September 2004, 21:39

Originally posted by unmei

Psycrow, yes please. I would be interested as well.

 

At the moment i do it "the hardcore way", with scanning and changing pixel on scanlines.

It works well for me, but i think if you can do it with path it would be incredibly faster. This scanline operations are OK for me as i have to many other things to do on scanline basis as well. But if outline is the only "special" thing you want to apply, the way over bitmaps seems rather inconvienent.

 

Anyway, if you want to have a look at (or use) my solution, it is in the u96 subversion tree (http://subversion.corecodec.org/index.cgi/u96), in the file pixfxU.pas (you need spuUtilU.pas and eventually arrayutilU.pas as well), the function for RGBA bitmaps is addOutlineRGBQ and for RGB bitmaps it is addOutline. The RGB-only version is probably MUCH slower since i no longer use it and therefore did not optimise it. You can however look at what i changed in the RGBA version and apply that to the RBG-only version. The source is like 150Kb, but you only need few functions, so you can probably put all you need in one unit with only 5-10 or so Kb.

 

 

As I said - this is an original response to a question in the Delphi newsgroups by Peter Below, I have not tried it, but Peter's stuff is usually impecable.

 

The standard Windows GDI API used by the VCL does not support "ornamental"

text styles. But you can usually fake them. To draw a text with a shadow,

for instance, you draw the text with a dark color first, 1 pixel to the left

and down from the text start position, then draw it again with a lighter color

on top. For an outline you can use the Path API. The following example is

the OnPaint handler for a TPaintbox:

 

procedure TForm1.PaintBox2Paint(Sender: TObject);

const

TestText = 'This is a test';

var

CV: TCanvas;

R: TRect;

begin

CV := (Sender as TPaintbox).Canvas;

CV.Font.Size := 48;

CV.Font.Color := clSkyBlue;

CV.Font.Name := 'Arial'; {Needs a true-type font!}

CV.Brush.Color := clWhite;

CV.Brush.Style := bsSolid;

CV.Pen.Color := clBlue;

CV.Pen.Style := psSolid;

CV.Pen.Width := 1;

CV.FillRect(TPaintbox(Sender).Clientrect);

{Uncomment the following line to see the text output rect in yellow}

// CV.Brush.Color := clYellow;

 

{This draws the interior of the letters}

CV.TextOut(0,0,TestText);

 

{Uncomment the following block to remove the frame drawn around

the text by StrokePath }

{

R.TopLeft := Point(0,0);

R.BottomRight := TPoint(CV.TextExtent(TestText));

InflateRect(R, -1, -1);

IntersectClipRect(CV.Handle, R.Left, R.Top, R.Right, R.Bottom);

}

 

{This draws an outline around the letters}

BeginPath(CV.Handle);

CV.TextOut(0,0,TestText);

EndPath(CV.Handle);

StrokePath(CV.Handle);

end;

 

 

THAiSi

29th September 2006, 15:20

I searched for anti alias in google and got to this page. I made an anti alias function for Delphi, and I'll put it here so that other people who seek an anti alias function can get it here!

 

 

 

procedure WriteAntiAlias(Canvas: TCanvas; X, Y: Integer; Text: String);

const

Sampling = 2;

var

Width, Height: Integer;

VirtualCanvas: TBitmap;

RenderCanvas: TBitmap;

DrawCanvas: TCanvas;

 

x2,y2: Integer;

i0, i1, o0: pbytearray;

begin

Width := Canvas.TextWidth(Text) + 10;

Height := Canvas.TextHeight(Text);

 

VirtualCanvas := TBitmap.Create;

RenderCanvas := TBitmap.Create;

try

VirtualCanvas.Width := Width * Sampling;

VirtualCanvas.Height := Height * Sampling;

VirtualCanvas.PixelFormat := pf24bit;

 

RenderCanvas.Width := Width;

RenderCanvas.Height := Height;

RenderCanvas.PixelFormat := pf24bit;

 

RenderCanvas.Canvas.CopyRect(Rect(0,0, Width, Height), Canvas, Rect(X, Y, X + Width, Y + Height));

 

DrawCanvas := VirtualCanvas.Canvas;

DrawCanvas.StretchDraw(Rect(0,0, Width *2, Height *2), RenderCanvas);

 

DrawCanvas.Brush.Style := bsClear;

DrawCanvas.Font.Color := Canvas.Font.Color;

DrawCanvas.Font.Name := Canvas.Font.Name;

DrawCanvas.Font.Size := Canvas.Font.Size * Sampling;

DrawCanvas.Font.Style := Canvas.Font.Style;

DrawCanvas.TextOut(-Sampling, 0, Text);

 

for y2 := 0 to Height-1 do

begin

i0 := VirtualCanvas.ScanLine[(y2 * 2)];

i1 := VirtualCanvas.Scanline[(y2 * 2) + 1];

o0 := RenderCanvas.Scanline[y2];

 

for x2 := 0 to Width-1 do

begin

o0[x2 * 3] := (i0[x2 * 2 * 3] + i1[x2 * 2 * 3] + i0[(x2 * 2 * 3) + 3] + i1[(x2 * 2 * 3) + 3]) div 4;

o0[(x2 * 3) + 1] := (i0[(x2 * 2 * 3) + 1] + i1[(x2 * 2 * 3) + 1] + i0[(x2 * 2 * 3) + 4] + i1[(x2 * 2 * 3) + 4]) div 4;

o0[(x2 * 3) + 2] := (i0[(x2 * 2 * 3) + 2] + i1[(x2 * 2 * 3) + 2] + i0[(x2 * 2 * 3) + 5] + i1[(x2 * 2 * 3) + 5]) div 4;

end;

end;

Canvas.CopyRect(Rect(X, Y, X + Width, Y + Height), RenderCanvas.Canvas, Rect(0,0, Width, Height));

 

finally

VirtualCanvas.Free;

RenderCanvas.Free;

end;

end;

 

 

here an example of how to use it:

 

// old way

//DrawCanvas.TextOut(LeftPos, LineTop, 'Hello anti-aliased world!');

 

// Anti-alias

WriteAntiAlias(DrawCanvas, LeftPos, LineTop, 'Hello anti-aliased world!');

 

 

The anti alias code gets the font properties from the DrawCanvas, so you can set your font properties like you used to and instead of TextOut you can use WriteAntiAlias.

 

Thanks for the pointers in this thread to create this function!

 

Greets, Matthijs Groen

 

ps. Annoying that I had to be member for 5 days just to put a solution up for others....

pps. A todo for this function could be a global virtual screen for rendering so that not every anti-alias text out is creating and freeing a buffer.

tekNerd

9th June 2007, 10:32

Hi. I'm new here and I don't speak English so well - I appologize in advance if I'm saying something wrong.

Using Scanline I've made an outline + shadow on a text. You cand find the demo at http://www.clicknet.ro/baciuionut/Demo.zip .

My questions are:

Is there any way

- to improve speed without decreasing visual quality (at least not with much);

- to increase visual quality without a significally decrease in speed?

And how to draw semitransparent pixels? I've tried rgbReserved from TRGBQuad (using a 32bit TBitmap) but without success. If it's set to 0 or 255 - same thing, 100% opaque.

 

I'm using this code in a video player to display subtitle - so both visual quality and speed are required.

 

Thank you in advance.

 

Cosmin3

Cosmin3

17th June 2007, 13:46

Nobody...?

vBulletin® v3.7.3, Copyright ©2000-2008, Jelsoft Enterprises Ltd.