In the past couple of days I tried to finally address an issue in KDE Itinerary where UIC 918.3 train tickets could be rendered in a way that they weren’t accepted by the scanner. That turned into a journey into the depths of high DPI rendering inside KDE Frameworks’ barcode rendering library Prison.

The Problem

First of all, when talking about barcodes I refer to both one-dimensional barcodes as well as two-dimensional matrix codes like QR or Aztec. Both have one thing in common, in order to be readable, they need to be presented in a sufficiently high resolution for the scanner to properly detect them. And ultimately this is only about physical resolution of the output medium, not about pixels. However, the software producing the barcode of course works in pixels, so how this is mapped to the output is where things get interesting.

There’s a number of constraints to consider:

  • The smallest feature of the barcode (line width for 1D, dot size for 2D) must be large enough so it’s represented with enough physical resolution to be clearly detectable by a scanner. One pixel on a conventional standard DPI screen isn’t enough for that.
  • A 1D code must be high enough to be easily covered entirely by the scanline (the smaller it is, the harder it is to aim the scanner correctly). A few pixels are usually not good enough for this either.
  • You do not want fractional scaling of the barcode image, at least not at smaller scale factors when you are still too close to the minimal physical resolution for the scanner.

KF5::Prison addressed this as follows so far:

  • 2D matrix codes had a hardcoded dot size of 4 pixels, as that’s where barcodes became reliably readable on a standard DPI screen. This makes them somewhat larger than an equivalent code on a printed paper would need to be. 1D barcodes had no such limit on the minimum line width though, making them usually not readable at their minimum size.
  • 1D barcodes had a hardcoded minimum height of 10px, which seems somewhat arbitrary, as it’s too small to not require a very precisely aimed scanner.

As long as you only dealt with 2D matrix codes this worked quite fine. Until we ended up with an unfortunate set of factors:

  • A very large Aztec code as found in e.g. German railway tickets.
  • A small phone display, possibly with a higher than usual high DPI scale factor for its physical resolution.

As a result the code that on paper fits on a 50x50mm square doesn’t fit on the phone screen anymore, truncating it and thus making it impossible to read. So we end up in the bizarre situation that something is rendered too large in a high DPI scenario for once ;-)

Improving KF5::Prison

Obviously we want to improve as much as possible on the KF5 level, rather than working around this in applications. However, there’s a few challenges:

  • We cannot break API, ABI or behavior of the existing functions. In particular the behavior aspect is a problem, since that’s exactly what we want to change.
  • KF5::Prison uses Qt5::Gui for rendering the barcode images, but is conceptually rather a “core-only” library, ie. it works without a QGuiApplication instance in an off-screen scenario as well. This means that we have no information about the output screen and its physical resolution.

Nothing unsolvable though, just making things a little less straightforward. For KF5 5.69 we now have the following improvements:

  • Explicit API to query the barcode dimension (1D or 2D), to avoid various application-level solutions for doing appropriate layouting of 1D or 2D codes - D27730.
  • Refactored the specific barcode format implementations to no longer contain any of the hardcoded scaling logic mentioned above, but rather collect this in one central place - D27909. This will make it easier to drop this entirely for KF6, and it ensures all barcode types actually behave consistently. And more importantly, with all barcode types now reporting the absolute minimal size required, it paves the way for a more appropriate scaling.
  • The true minimum size can now be queried with a new method - D27989, in case applications want full control over the scaling.
  • The new preferredSize(qreal devicePixelRatio) method taking the screen resolution into account (passed in as an argument) is the primary replacement for the now deprecated old minimumSize() method. This also reports sensible sizes for 1D barcodes now - D27989.
  • While at it, the size-related methods now also all report correct results before the first requested rendering of a barcode, making the API easier to use and avoiding application-level workarounds.

Adapting Applications

Application code might need a few adjustments to fully use the new API:

  • If you use the QML Barcode item, you are probably fine as-is, that is already using the new preferredSize method internally, using the device pixel ratio of the primary screen.
  • If you are using the C++ API, to the very least you want to move from minimumSize to preferredSize now. While at it, you can drop any hack that might exist to get valid results before the first painting call.
  • If you are doing more advanced scaling/layouting yourself, you have a lot of new options now. At least, you can probably replace some adhoc solutions or workarounds by new API in KF5::Prison now.
  • If your application implements user-zoomable barcodes (e.g. via a pinch gesture), make sure to only scale by integer factors on the lower scale levels, to keep the barcode readable. The trueMinimumSize() method and the corresponding QML properties help with determining this.

Let’s see if this finally fixes all barcode scanning issues :)