Creating Pictures in a HiDPI World
By Thom McGrath on
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.
( 260.6 KB )