Use Supersampling for offscreen bitmaps on Delphi Mobile

A common method for painting drawings is to draw to an offscreen bitmap and then draw the bitmap to your canvas (say a TPaintBox) as needed.  This is generally used when you create a drawing that does not change often; drawing once to an offscreen bitmap and then as needed on repaints to the real canvas can be very efficient.  However, if you have ever used offscreen bitmaps with Delphi on iOS or Android, you have quickly realized that the quality of the DrawBitmap method is awful.  There seems to be no anti-aliasing performed at all with DrawBitmap and the typical output looks terrible:

The quality of DrawBitmap from an offscreen bitmap to the screen is terrible on mobile platforms with no anti-aliasing

The quality of DrawBitmap from an offscreen bitmap to the screen is terrible on mobile platforms with no anti-aliasing

As you can see, the curve of the ellipse and the diagonal lines look jaggy, with no smooth transition or blur between the lines and the colors around them.

The image above comes from a sample project where I create an offscreen bitmap the same size as a TPaintBox.  Then, I draw the bitmap to the paintbox on its OnPaint event.

It is easy to not realize how bad the DrawBitmap is on mobile until late in your development as the output looks great on the desktop (Windows and OSX) platforms.  Unfortunately, there seem to be no way to improve the quality of the TCanvas.DrawBitmap function directly.  The best you can do is improve the quality of the entire form by changing its Quality property to HighQuality.  However, even this is often not enough:

High Quality forms do not fix the problem

High Quality forms do not fix the problem

Note that not using an offscreen bitmap drastically improves the quality over drawing to a bitmap and then drawing the bitmap to the final canvas.  If you just draw directly to the TPaintBox in its OnPaint event, the output looks pretty good:

Drawing directly to the final canvas has good quality on Mobile

Drawing directly to the final canvas has good quality on Mobile

However, presumably if you are reading this post, you need to use the Offscreen bitmap for various reasons such as its speed efficiency, so what else can we do?

An old technique, supersampling, can drastically improve the quality of the output and be more targeted than the blanket TForm.Quality property.  Supersampling is a brute force anti-aliasing technique where you draw your image on your offscreen bitmap at a much higher resolution (2x, 4x, 8x) than the one being displayed and then it is shrunk back down when it is drawn to your final canvas. The result is a downsampled image with smooth lines and no jaggies:

Drawing to an offscreen bitmap that is twice the width and height dramatically improves quality

Drawing to an offscreen bitmap that is twice the width and height dramatically improves quality which is arguably even better quality than drawing directly to the final canvas.

To perform supersampling, you create a bitmap that is twice as big in width and height (or 4x, 8x etc):

 OffscreenBitmap := TBitmap.Create;
 OffscreenBitmap.SetSize(trunc(PaintBox1.Width*ScaleFactor),
 trunc(PaintBox1.Height*ScaleFactor));
 OffscreenBitmap.Clear(TAlphaColorRec.Null);

Then you need to scale all your draw operations to the bigger bitmap.  You might think that this technique requires a more complex drawing routine, as you need to scale every draw and fill operation by the scale factor.  However, that is not the case.  By scaling the matrix of the TCanvas, you do not need to change your drawing routines at all:

procedure TForm1.Draw(aRect: TRectF; aCanvas: TCanvas; ScaleFactor: Integer);
var
 aMatrix: TMatrix;
begin
 aMatrix := aCanvas.Matrix;
 aCanvas.BeginScene;
 try
  <strong>aCanvas.SetMatrix(aCanvas.Matrix*TMatrix.CreateScaling(ScaleFactor, ScaleFactor));</strong>
  aCanvas.Fill.Kind := TBrushKind.Solid;
  aCanvas.Fill.Color := TAlphaColorRec.Blue;
  aCanvas.Stroke.Kind := TBrushKind.Solid;
  aCanvas.Stroke.Thickness := 3;
  aCanvas.Stroke.Color := TAlphaColorRec.Green;
  aCanvas.DrawRect(aRect, 10, 10, AllCorners, 1);
  aCanvas.FillEllipse(aRect, 1);
  aCanvas.DrawEllipse(aRect, 1);
  aCanvas.Stroke.Color := TAlphaColorRec.Red;
  aCanvas.Stroke.Thickness := 5;
  aCanvas.DrawLine(aRect.TopLeft, aRect.BottomRight, 0.7);
  aCanvas.DrawLine(PointF(aRect.Right, 0), PointF(0, aRect.Bottom), 0.7);
  aCanvas.Stroke.Color := TAlphaColorRec.Red;
  aCanvas.Stroke.Thickness := 1;
  aCanvas.DrawLine(PointF(aRect.Width / 2, 0), PointF(aRect.Right, aRect.Bottom / 2), 1);
  aCanvas.DrawLine(PointF(aRect.Right, aRect.Bottom / 2), PointF(aRect.Width / 2, aRect.Bottom), 1);
  aCanvas.DrawLine(PointF(aRect.Width / 2, aRect.Bottom), PointF(0, aRect.Bottom / 2), 1);
  aCanvas.DrawLine(PointF(0, aRect.Bottom / 2), PointF(aRect.Width / 2, 0), 1);
 finally
  aCanvas.EndScene;
  aCanvas.SetMatrix(aMatrix);
 end;
end;

The aRect parameter is the size of the final TPaintBox.ClipRect.  The aCanvas parameter is the offscreen bitmaps canvas.  Here is how the routine is called:

 Draw(RectF(0, 0, PaintBox1.Width, PaintBox1.Height),
 OffscreenBitmap.Canvas,
 (Sender as TRadioButton).Tag);
 PaintBox1.Repaint; // changed the offscreen bitmap, trigger the paint

Finally, in the TPaintBox.OnPaint event, draw the offscreen bitmap to the canvas:

procedure TForm1.PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
var
 aRect: TRectF;
begin
 aRect := RectF(0,0,OffscreenBitmap.Width,OffscreenBitmap.Height);
 Canvas.DrawBitmap(OffscreenBitmap, aRect, PaintBox1.ClipRect, 1, False);
end;

The scale factor can be as large as you want but in practice going above 4x or at the most 8x is unneccessary.  Here is the 4x output.

Drawing to a 4x bitmap looks almost perfect

Drawing to a 4x bitmap looks almost perfect

The output looks fantastic, but there are downsides.  The biggest is that this is a memory intensive technique.  Doubling the width and height of your bitmap quadruples the amount of memory you use.   Quadrupling the width and height uses 16x the amount of memory over your original offscreen bitmap!  However, since this can be a targeted technique, you only have to increase the size of the offscreen bitmaps for the important painting.

Another major issue with this technique can be speed.  Since you are drawing 4x or 16x more pixels, the offscreen drawing is naturally going to take longer.  And then, of course, drawing back to the main canvas adds time as well.  However, this is offset by the fact that you generally use offscreen bitmaps for things that don’t change as often and the draw bitmap function can still be much faster than drawing directly to the canvas depending on the complexity of your drawing.

The code for the example is here: Super Sampling Example Project.  Feel free to use as you wish.

Note that the RiverSoftAVG SVG Component Library and RiverSoftAVG IMPACT multimedia instrument package add-on use this technique.  There is a Quality property that provides the scale factor for most controls.  By default in FMX, when drawing the TRSSVGImage control to a buffer (Buffered=True), rendering the SVG to an image list, or drawing an instrument, the Quality property is 2 which provides a good balance between memory, speed, and quality.  Change the Quality property from 1 (no supersampling) to 8 (8x supersampling consuming 64x the memory).  The Quality property can also improve the look for extremely small final bitmaps (16×16, 32×32).  Since the memory usage is minimal at these small sizes, this property can really help with small bitmaps.

That is all for today.  Hopefully this tip is useful to some of you.  Happy CodeSmithing!

Leave a Reply

Your email address will not be published. Required fields are marked *