BGRABitmap tutorial 13
│ Deutsch (de) │ English (en) │
Home | Tutorial 1 | Tutorial 2 | Tutorial 3 | Tutorial 4 | Tutorial 5 | Tutorial 6 | Tutorial 7 | Tutorial 8 | Tutorial 9 | Tutorial 10 | Tutorial 11 | Tutorial 12 | Tutorial 13 | Tutorial 14 | Tutorial 15 | Tutorial 16 | Edit
This tutorial talks about the coordinate system of BGRABitmap.
Standard canvas routines use integer coordinates. This is also the case with the CanvasBGRA property, which emulates standard canvas functions, but with antialiasing (AntialiasingMode), alphablending (Opacity property of Pen, Brush and Font) and gamma correction.
When using pure integer coordinates without antialiasing, the coordinates specify a pixel position, i.e. a square. So when drawing a line from (0,0) to (5,5), the top-left pixel is the first painted pixel. In standard canvas, the last pixel is not painted, so the end of the line will be at (4,4).
When drawing an ellipse, the bounding rectangle specifies the pixels that will be used to render the ellipse. Once again, in standard canvas, the lower-right coordinates are excluded from the drawing, so filling the rectangle (0,0)-(5,5) will in fact fill pixels having coordinates from 0 to 4.
Now, when working with floating point values, the coordinates specify a position that can be anywhere on the pixels. The width value of the pen really means a distance, and not a number of pixels. So what does (0.0,0.0) mean ?
Top-left distance floating point coordinates
It can, for example, be the distance from the top-left corner (this is not the case with BGRABitmap). In this case, this coordinate would be the upper-left corner of the first pixel. But if we do this, the behavior is slightely different between integer coordinates and floating point coordinates. Indeed, imagine we draw an horizontal segment on pixels (0,1) to (4,1). In pixel coordinates it would be the line (0,1)-(5,1) and the width would be 1 pixel. Now if we want to define this segment with floating point coordinates: the left side would be at 0.0, the right at 5.0, that's ok. The top side would be at 1.0 and the bottom at 2.0. Here is the problem. The center of the line is at a vertical coordinate of 1.5. So to draw this line, we should supply coordinates (0.0,1.5)-(5.0,1.5).
In fact every 1-pixel wide line with integer coordinates would be drawn between pixels, and with antialiasing, this leads to blurred lines. What appeared to be ok with horizontal coordinates is in fact an illusion, because if the line has caps, the correct coordinates are (0.5,1.5)-(4.5,1.5). If we ignore the last pixel issue, we see that these coordinates are simply 0.5 greater.
Pixel-center floating point coordinates
The coordinates can be the distance from the center of the top-left pixel. In other words, integer values are at the center of the pixels. This is the case with BGRABitmap functions. Using these coordinates, the line that fills pixels (0,1) to (4,1) is simply (0.0,1.0)-(4.0,1.0).
This is strange from a mathematical point of view, but very convenient, because you can use integer coordinates to draw normal 1-pixel wide lines. This way, there is little difference between calls to CanvasBGRA functions and BGRABitmap normal floating point functions.
Create a new project
Canvas and BGRACanvas
Let's try to show what happens at the pixel level.
First, we'll use the standard Canvas:
procedure TForm1.FormPaint(Sender: TObject); var image: TBGRABitmap; begin image := TBGRABitmap.Create(10,10); with image.Canvas do begin //clear with white brush.color := clWhite; FillRect(0,0,image.width,image.height); //blue ellipse with black border brush.style := bsClear; pen.color := clBlack; Ellipse(0,0,9,9); end; /stretch the image so we can see the pixels BGRAReplace(image,image.Resample(image.Width*10,image.Height*10,rmSimpleStretch)); image.Draw(Canvas,0,0,True); image.free; end;
The resample option rmSimpleStretch prevents the use of interpolation filters.
You should get this:
The pixels of coordinate 9 are not filled as explained above about rectangle bounds. Now, let's draw this BGRACanvas. Just change the 'with' line:
with image.CanvasBGRA do
Now you should get this:
The result is very similar except with antialiasing.
Suppose we want to fill an antialiased ellipse that fit exactly in a 9x9 bitmap. We could try with this code:
procedure TForm1.FormPaint(Sender: TObject); var image: TBGRABitmap; begin image := TBGRABitmap.Create(9,9,BGRAWhite); image.FillEllipseAntialias(4,4, 4,4, BGRABlack); BGRAReplace(image,image.Resample(image.Width*10,image.Height*10,rmSimpleStretch)); image.Draw(Canvas,0,0,True); image.free; end;
As you can see, the border is not completely filled. The ellipse is smaller than expected. In fact, the center of the ellipse is (4,4) so the left border is at (0,4). But remember it is the center of the pixel. So if we want the ellipse to go to the border of the pixel, we need to add 0.5 to the radius:
image.FillEllipseAntialias(4,4, 4.5,4.5, BGRABlack);
Filled shapes with canvas
Note that with standard canvas, the result is very surprising. Suppose we want to do the same thing. We could try this:
brush.color := clWhite; FillRect(0,0,image.width,image.height); brush.color := clBlue; pen.style := psClear; Ellipse(0,0,9,9);
We get this:
So in this case, there is yet another pixel substracted. The ellipse is 8 pixels wide whereas the supplied rectangle is 9 pixels wide.