In the earlier post, we talked about the difficulty of scaling UIs across the current PPI range we have on the mobile and embedded space. We also hinted that the most common way to avoid this problem was done via providing several image assets that help to mitigate this issues.
How does it work? How can one carry it out in QML? And more importantly where does it fail? What is the best solution and what trade-offs does it involve?
Icons
Icons are probably the easiest place to start here, long before the mobile became what it is, we were scaling icons, very much in the same way we are doing those very same icons for mobile today.
Icons depending on the platform were done in 2x multiples of 16 and a size in between. So 16x16, 24x24, 32x32, 48x48, 64x64, 96x96, 128x128,172x172, 256x256, 512x152... The reasons for all these sizes are multiple and range from marketing, specific use, type, clickable area and visibility. But you might ask how come so many and how come we don't just use one size and scale it?
To answer that question we first need to understand the Pixel and how scaling works.
What is a Pixel?
A pixel is a color composed of smaller colors, it's the almost indivisible element, its shape depends on the technology used by the screen, but for simplicity's sake we use the “mostly” square element made by the RGB strips on an LCD screen. We use this pixel because 1) it maps really well to image pixels, and 2) it's the most common technology used in displays.
How does scale work?
Scaling can be done via multiple smart or not so smart algorithms but usually is the result of some interpolation of pixels in a new rendering grid. The result is that when a pixel is stretched it is distributed across the new pixel grid.
Something about icons
Icons are special images that use the pixel grid to their full potential. In icons we still paint in pixels even if they are done in a vector drawing application. We still align our vectors to a virtual rendering grid, we do this to maximize contrasts and sharpness, and as a result icons tend to make a lot of use of perpendicular and horizontal contrasts.
Scaling an icon results in blurry images, as those pixel contrasts get blurred on the interpolating process and only work on multiple values if the contrast lines were done on the major 16x16 grid lines. Even there we lose meaningful data as a contrast that was 1 pixel wide becomes sub-pixel 0.5 or 0.25.
For this reason, when we do iconography, each icon is redone for every size, and we NEVER scale icons from one size to another...
Buttons and other X/Y independent scalable elements
What about UI elements that are supposed to scale in an X/Y independent fashion, like Buttons, GroupBoxes and other custom image-based elements. How do they work?
In QML we have a Component that was made with these sort of elements in mind, the BorderIMage
, it expect an image that it can slice in 9 bits so it can scale/tile each one in an appropriate way. example code..
Now for the main question: How big should it be? And in what metric?
If it's used on a touch screen then it should be big enough for you to touch it without triggering false hits or miss hits, and big enough is something between 7 and 9 mm depending also on how tolerable a missed hit can be for the user. This number is roughly speaking the size of your thumb contact surface with a touch screen.
This means that we have two conflicting metrics here, on one end we have the pixel, on the other physical size, we still express values to the QML code in pixels and yet we need to make this elements as big as my thumb.
PPI or DPI
PPI or DPI are the amount of pixels in an inch of your screen, you can expose the PPI to QML via several methods, but we want the real PPI not the logical one. If you are "pixel crazy" like me, you will want the PPIx and PPIy - yes the pixels are not always really "square" and you might want them both.
10 Comments
3 - Jun - 2014
Jens H. Göbbert
Thanks for the infos about Qt on www.kdab.com. I always enjoy reading them.
Thinking about scaling of GUI elements and icons I wonder what the drawback are of using SVG as a starting point and cache the scaled image on disk for reuse.
This way you will not have to worry about any future display size or possible zoom feature of a new operating system. Your application will always look perfekt, no matter where.
The generation of the bitmaps would only happen once at the first start of your application and will be saved on disk then. And you could even add some default once, which fit most of the screens already to avoid this initial generation step for most users.
The only drawback I can think of is adding QtSvg to your project ... but isn't that negligible?
Greetings Jens
3 - Jun - 2014
Nuno Pinheiro
Hello Jens. there are several drawback's in using SVG as a source image, and it has to do with our old friend the Pixel, Scaling a vector suffers from much of the problems scaling an pixmap suffers from a pixel POV. BTW you can already use svg as the source image for your Image elements QtQuick will do just that, cache a bitmap and be done with it. I plan to explain all about SVG in a post in this series.
4 - Jun - 2014
Jens H. Göbbert
Thanks for the answer. I am looking forward to read your SVG post.
10 - Mar - 2017
Miguel
I would suggest using real instead of int for the dpi calculation, since in my test int won't let the decimal part of the calculation and you would end up with less precises calculations. I've used some functions to calculate px from pd (Android Independent Pixel) like this:
You can use rounding floor or ceil to avoid visual artifacts.
28 - Apr - 2017
Tony
Is this article specifically talking about scaling icons up? What's wrong with having one high res icon targeted at your highest resolution device and scale that image down for your lower res devices (e.g. using QML Image sourceSize property) ? It's the way I've been doing it and I haven't experienced any blur. Yet, I feel like this is wrong since I haven't come across anyone else doing it this way.
7 - May - 2017
Nuno Pinheiro
Hey, Tony, interesting question. Ok this depends, an image that is not relying on pixel perfect tricks can be scaled down and in today's extremely hey dpi screens/minimalist icons, the user will have a hard time telling any difference, now that does not mean that such difference is none existent. Example you have a highly detail grid like icon that relies on 2 pixel lines and 2 pixel empty space the moment you scale those in a non integer factor down you will have moiré a like effect. and the only way to avoid it is to redo the icon for the new resolution. So even if for today's minimalist icons and high dpi screens this is not a huge issue, you should not ignore it, and best practice is to look at each icon and see if you need to redo it or if one is good enough, truth be told icons that scale well are also pretty trivial to redo fora new def so that is not a lot of extra work. the complicated ones tend to be the ones that don't. Also its not an absolute truth that all screens are very high dpi, and for the lower res some tweaking is hallways good IMO. And finally think of the memory footprint a device with a lower res screens tends to have less memory, loading high res images has a non insignificant impact.
28 - Dec - 2017
joni
hello, I see this line dpi: Screen.pixelDensity*25.4 . From where this 25.4 value came from?
29 - Dec - 2017
Nuno Pinheiro
hey Joni, milliliters to inches conversion. Qt gives pixels densities in pixels per mm
in http://doc.qt.io/qt-5/qml-qtquick-window-screen.html
30 - Dec - 2017
joni
Yes I know, but how you can make an assumption 25.4 value is the right value used here for unit convertion (pixel to mm)? Why not direct calculation by using Screen.pixelDensity *3.5 for my assumption that unit calculation is 3.5mm unit in qml?
31 - Dec - 2017
Nuno Pinheiro
dpi = dots per inch
ok Screen.pixelDensity = number of pixels/dots in one mm
then we have that in one inch there are 25.4 mm
So in order to know how many dots/pixels there are in an inch we multiply the 2 values.. and get a dpi (dots per inch).
Lately many devices have been reporting the Screen.pixelDensity in virtual pixels and not real ones to get the true screen dpi (approximation) you will have to multiply Screen.pixelDensity by Screen.devicePixelRatio.
PS happy new year