Continuing on my blog posts about drawing in Xojo, a question came up on the forums about creating and scaling pictures correctly for screens with increased pixel densities.

Any time you call New Picture you need to consider what you're going to do with the picture next. If you know the graphics object you're going to be drawing the picture to, you can save a lot of performance.

We're going to need two methods with similar signatures. First:

Public Function IconAtSize(Icon As Picture, Width As Integer, Height As Integer, Factor As Double) As Picture
  Var ScaledIcon As Picture = Icon.BestRepresentation(Width, Height, Factor)
  
  Var Pic As New Picture(Width * Factor, Height * Factor)
  Pic.VerticalResolution = 72 * Factor
  Pic.HorizontalResolution = 72 * Factor
  Pic.Graphics.DrawPicture(ScaledIcon, 0, 0, Pic.Width, Pic.Height, 0, 0, ScaledIcon.Width, ScaledIcon.Height)
  
  Return Pic
End Function

This is the workhorse method. It creates a scaled picture that is configured correctly for a specific scaling factor.

The second method:

Public Function IconAtSize(Icon As Picture, Width As Integer, Height As Integer) As Picture
  Var Bitmaps() As Picture
  For Factor As Integer = 1 To 3
    Bitmaps.AddRow(IconAtSize(Icon, Width, Height, Factor))
  Next
  Return New Picture(Width, Height, Bitmaps)
End Function

This method creates a multi-resolution picture for use when the target scaling factor is not known. Since it calls IconAtSize three times this could be considered wasteful, but when you don't know what will be done with the returned picture, you don't have another choice.

To put this code to use, I'm going to use a picture of the LLVM logo called DragonFull. It is a 1024x1024 PNG file. In the Paint event of Window1, I'm going to draw a 256x256 red box at 20,20, then draw the picture inside of it. This will be done using the first implementation of IconAtSize where I specify the scaling factor I need. However, since Xojo actually gives us two factors - one for each dimension - I'm using the average of the two. In my experience this is fine, but if you wanted to expand IconAtSize to use two factors instead of one, it wouldn't be difficult.

Sub Paint(g As Graphics, areas() As REALbasic.Rect) Handles Paint
  #Pragma Unused Areas
  
  G.DrawingColor = &cFF0000
  G.DrawRectangle(20, 20, 256, 256)
  G.DrawPicture(IconAtSize(DragonFull, 256, 256, (G.ScaleX + G.ScaleY) / 2), 20, 20)
End Sub

See the pictures at the bottom of the blog post for the results: a pixel-perfect drawing at any scale up to 400%, because 1024 รท 256 = 4. If I needed even greater scaling factors, I could draw the icon smaller, or increase the resolution of the original.

This technique could also be used to draw completely new images at any scale factor, instead of scaling an already-generated bitmap. That's another exercise for the reader though.

Gallery