Fractional Scale Factors

Published February 15, 2017 by
William Yu

With all the Retina/HiDPI work done in the past few years, we’ve had to add some new features along with it. One of these newer features is the Graphics ScaleX and ScaleY properties. For the purposes of Retina/HiDPI, the scale factor is used when converting user space coordinates to backing store coordinates. While mostly integral on MacOS (unless originating from some code that’s probably not ours), it can vary on Windows, and perhaps arguably mostly fractional. This is because Windows allows you to set DPI scales at 125%, 150%, etc. So when dealing with fractional scales there are a few things to watch out for:

Rounding issues

Anti-alias effect

While the framework takes care of rounding issues, for the most part, the secondary issue of anti-aliasing is up to you.

Here’s an example to demonstrate the issue:

// In this simple example the user wanted to tile their UI for some reasonSub Window1.Paint(g As Graphics, areas() As REALbasic.Rect)
// For simplicity the tile will just be black, 15px tallDim oneTile As New Picture(Self.Width, 15, 32)
oneTile.Graphics.FillRect(0, 0, oneTile.Width, oneTile.Height)
// For this demo we'll create a new picture
// whose Scale factor is set at 1.5xDim resultPic As New Picture(Self.Width, Self.Height, 32)
Dim resultGfx As Graphics = resultPic.Graphics
resultGfx.ScaleX = 1.5
resultGfx.ScaleY = 1.5
// Now tile the picture so the result should
// be completely blackFor y As Integer = 0 To resultPic.Height Step oneTile.Height
resultGfx.DrawPicture(oneTile, 0, y)
Next
g.DrawPicture(resultPic, 0, 0)
End Sub

Now here’s the result, from the above code, with Anti-aliasing enabled (that’s the default for our Graphics object):

Probably not what the user wanted. This is because with Anti-alias enabled the drawing functions smooth the edges and makes fractional scale that much more obvious. For example, drawing the tile at y = 15 means the pixel location (after adjusting for scale) at 15 * 1.5 = 22.5. While there doesn’t exist a half pixel, the drawing function instead aliases that portion. If this is not what you wanted, and in this case it’s not, you must manually disable Anti-aliasing.

Now we get output more in line with our expectations:
Alternatively, we could have sized our tile to be 10px high and thus avoid any rounding issues (in this particular case anyway). While there could be many different ways to solve a particular problem, recognizing the issue will help you find that answer even quicker. Now there’s no excuse for not making your app fractionallygreat!