Skip to content

In Pixels we trust Scalable UIs In QML part 2

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 out it in QML? And more importantly where does it fail? Were it’s 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 16×16, 24×24, 32×32, 48×48, 64×64, 96×96, 128×128,172×172, 256×256, 512×152… 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 16×16 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…

rect4400

As you can see in the image, scaling an icon by just 1 pixel can result in very blurry results. My advice is that if you need icons in different sizes that look the same in different pixels per inch, you should provide multiple images using 2x and one in between scale factors with the icon redone to take advantage of the extra pixels.

 

What Android expects for icons

 

Again using android as our example platform

“to create an icon for different densities, you should follow the 2:3:4:6:8 scaling ratio between the five primary densities (medium, high, x-high, xx-high, and xxx-high respectively). For example, consider that the size for a launcher icon is specified to be 48×48 dp. This means the baseline (MDPI) asset is 48×48 px, and the high density (HDPI) asset should be 1.5x the baseline at 72×72 px, and the x-high density (XHDPI) asset should be 2x the baseline at 96×96 px, and so on.”

 

rect4587

Notice that each icon was slightly altered to take advantage of the extra pixels while maintaining the same look overall.

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..

BorderImage {
   id: scalableElement
   source: "./image/Imagebase.png"
   width: 100; height: 100
   border.left: 25; border.top: 25
   border.right: 25; border.bottom: 25
  }

The code above creates an element with 100 by 100 pixels using unscaled corners with 25×25 pixels from Imagebase.png wile stretching the areas in between those 25 pixel segments.rect4609

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.rect4867

To create the button above this code should in theory be enough:

....
property int dpi: Screen.pixelDensity*25.4 
....
BorderImage {
   id: scalableElement2
   source: "./image/Imagebase.png"
   width: 1.5*dpi; height: 0.36*dpi
   border.left: 25; border.top: 25
   border.right: 25; border.bottom: 25
  }

This code exposes the usual method we will use the most, ppi or dpi as the main metric.  it creates a button 1.5 inches long by 0.36 inches tall no mater what screen you use. However it still is not looking the same across multiple screens the curvatures on the corners will decrease as the PPI grows and the drop-shadow will become increasingly invisible. So one needs to introduce a similar solution that we created for the icons, were we use multiple baseImages for multiple PPI ranges. In my next blog post I will try to show how it can be done in an hopefully not too “ugly” way. We will also discuss the limitations of this method…

FacebookTwitterLinkedInEmail

Categories: Design / KDAB Blogs / KDAB on Qt / Scalable UIs

Tags: /
Senior Graphic Designer and Illustrator. Specialized in iconography, themes user interface design and Qml wire-framing/prototyping/implementation. Nuno's works include general illustrations, UI design, web design, corporate design as well as other works in creative areas. He is known for the Oxygen Project.

10 thoughts on “In Pixels we trust”

  1. Thanks for the infos about Qt on http://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

    1. 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.

  2. 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:

    function dp2px(dps)
    {
    return dps*(dpi/160);
    }

    You can use rounding floor or ceil to avoid visual artifacts.

  3. 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.

    1. 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.

      1. 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?

        1. 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

Leave a Reply

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