Qt Reference Documentation

Rounding of Measurements

Files:

Using Rounding to Ensure Integer Pixel Measurements.

Introduction

This example demonstrates how rounding in Javascript expressions can be used to ensure that all layout measurements are ultimately rendered using integer values. The example demonstrates the same layout scenarios with and without the application of rounding. This gives a direct comparison between the outcomes, in order to demonstrate the importance of using rounding correctly.

In most cases, layouts are defined using the built in layout managers available in QML. These managers are responsible for ensuring that layouts look good, but this is based on the assumption that all measurements and component sizes will be defined in integer pixel measurements. Therefore, in order to get the best visual performance, it is important to enforce the rule that all measurements must eventually be calculated as an integer value.

This includes:

  • Declared Item height and width measurements
  • Declared margin and spacing sizes
  • Declared Image sizes
  • Item x and y coordinate positions relative to a container
  • Conversion of device independent measurement units into pixels
  • Sharing available space between a number of items
  • Distributing items within a parent, with equally sized gaps

Note that any non-integer item positions will result in automatic rounding of the final drawing of both Text and Image elements. However, when resizing or scrolling items, the effect of rounding can be different, and hence inconsistent. If there are any non-integer measurements or positions, in any container all the way down the containment hierarchy from the screen, this could effect the visual quality of any other items at that level or below in the hierarchy.

Also note that clipping can occur in a way that means the edge of a non-integer sized item might not be visible, This could potentially create visual drawing artefacts.

Therefore, it is highly recommended that every calculation that results in a measurement value should be rounded, either rounded up or down according to the exact needs of the layout. This means every occasion that division occurs; in particular when converting device independent measurements into pixels, and also when sharing some available space between a variable number of items.

Walkthrough of Even Distribution Example

This example page demonstrates how to distribute a number of items within a container. The items might be of equal size, or they might be of variable size. In this example, the items are all different sizes to demonstrate the point.

The page definition includes two copies of the "rounder" component in a column. The first instance has rounding disabled, while the second instance has rounding enabled. The slider can be used to resize the column.

Each rounder generates a number of children, where the width depends on the index in the repeater. This is just an example, but it could be that an arbitrary set of child items are declared with different widths, or the child items could all be identical with the same width; the gap rounding issue would apply in each case.

 Page {
     id: gaps
     ...
     Column {
         id: col2
         width: slider.value
         ...
         Loader {
             width: parent.width
             sourceComponent: rounder
             Component.onCompleted: item.evenlyDistributed = false
         }
         Loader {
             width: parent.width
             sourceComponent: rounder
             Component.onCompleted: item.evenlyDistributed = true
         }
     ...
     }
 }

The rounder contains a number of Rectangle children. It performs the role of a layout manager, and therefore must update its layout when its parent or children configuration changes.

 Component {
     id: rounder

     Item {
         id: container
         property bool evenlyDistributed: true
         Repeater {
             Rectangle {
                 anchors.verticalCenter: parent.verticalCenter
                 radius: height / 9
                 width:  10 * (2 + index)
                 height: parent.height
                 color: "white"
             }
             model: 6
         }
         Component.onCompleted: container.layoutChildren()
         onParentChanged: container.layoutChildren()
         onChildrenChanged: container.layoutChildren()
         onWidthChanged: container.layoutChildren()
         onHeightChanged: container.layoutChildren()
     }

Finally the layoutChildren method calculates the x positions of each child. Notice that the evenlyDistributed property is used to determine whether to apply the rounding.

 function layoutChildren() {
     if (parent == null || parent.width == 0 || parent.height == 0 || children.length == 0)
         return;

     var remainingSpace = width
     var spacingNotRounded = remainingSpace

     // in this example, we have to subtract one for the Repeater, which inserts the remaining
     // children before its position
     var childrenRemaining = children.length - 1
     for (var p = 0; p < childrenRemaining; p++) {
         spacingNotRounded -= children[p].width
     }
     spacingNotRounded /= (childrenRemaining + 1)
     var spacing = evenlyDistributed ? Math.floor(spacingNotRounded) : spacingNotRounded
     var totalRoundingError = (spacingNotRounded - spacing) * (childrenRemaining + 1)
     var curPos = Math.floor(totalRoundingError / 2.0)

     for (var q = 0; q < childrenRemaining; q++) {
         var nextChild = children[q]
         curPos += spacing
         nextChild.x = curPos
         curPos += nextChild.width
     }
 }

In the image, you can see that the gaps on the first row are not consistent. However, note that if the gaps are larger, it becomes increasingly difficult to notice the visual artefacts.

Walkthrough of ListView Example

This example shows the effect of using rounding when using a ListView. We declare two ListView instances; each ListView instance uses the same delegate, but the rounding property is set to false for the first list, and true for the second list.

Note that the width of each list is calculated by dividing the container size. In the first case this is not rounded, and in the second case it is rounded.

 Page {
     id: lists
     ...
     Row {
         id: row2
         property real itemWidth: (row2.width - 50) / 2
         width: slider.value
         ...
         ListView {
             property bool rounding: false
             clip: true
             maximumFlickVelocity: 100
             flickDeceleration: 0.01
             width: row2.itemWidth
             height: parent.height
             delegate: listItem
             model: 20
         }
         ListView {
             property bool rounding: true
             clip: true
             maximumFlickVelocity: 100
             flickDeceleration: 0.01
             width: Math.floor(row2.itemWidth)
             height: parent.height
             delegate: listItem
             model: 20
         }
     }
     ...
 }

This is the list delegate, which checks the rounding property of the list. In this case, the item height is a fixed pixel value.

 Component {
     id: listItem

     Rectangle {
         color: "black"
         border.color: "white"
         border.width: 2

         width: parent.width
         height: ListView.view.rounding ? 32 : 32.5
     }
 }

As the slider is moved, you can see that the width of the boundary of the list items is shown as a different width depending on the exact ratio of the container size. However, in the right hand example, the rounding means that the appearance is consistent whatever the container size.

In practice, with more subtle list item separators, these artefacts would not be as visible to the user.

Conclusion

All measurements, sizes, gaps, margins, spacings, items, must all be either defined as, or eventually calcuated as integer pixel values. This applies to the entire layout containment hierarchy. All division operators should be followed by either rounding, floor, or ceiling operations.

In practice, if the platform style uses subtle highlights and list item separators, then many of these symptoms will remain un-noticed. However, it is recommended that the rounding rules are still applied to provide future-proof layout definitions, and to ensure that the user interface is consistent throughout.