In meinem vorigen Beitrag hatte ich ein geheimnisvolles und bisher noch nicht vollständig realisiertes privates Druckprojekt erwähnt. Dieses soll – neben dem DESMO Schriftzug – auch einen QR-Code enthalten.

Auch hier benötigt der Dienstleister eine vektorisierte Version einer Struktur, die auf den ersten Blick wie eine stark vergrösserte Bitmap aussieht.
Als Computergrafiker bin ich es gewohnt, mich mit Bitmaps und Pixeln in allen Variationen auseinanderzusetzen. Also warum sollte man Bitmaps zuerst vektorisieren, wenn sie am Ende doch wieder auf einem Rastergerät ausgegeben werden und wie eine Bitmap aussehen sollen?
Dann habe ich aber im Verlauf meines Druckprojekts die Vorteile vektorisierter Grafik zu schätzen gelernt: man kann vektorisierten Content aus unterschiedlichen Quellen mittels geeigneter Programme beliebig miteinander kombinieren (d.h.: «montieren»), ohne auf unterschiedliche Formate und Abmessungen Rücksicht nehmen zu müssen. Das Schöne an vektorisierten Grafiken ist, dass man sie beliebig (und beliebig oft!) skalieren oder umpositionieren kann ohne auch nur den geringsten Qualitätsverlust zu erleiden. Ich habe zu diesem Zweck Inkscape benutzt, eine freie Software für die Erstellung und Bearbeitung von Vektorgrafiken.
Anders verhält es sich bei Bitmap-Grafiken. Nachfolgend der Vergleich zwischen einer Original Bitmap, und einer zehnmal hintereinander im Rasterraum(!) um den Faktor drei vergrösserten, sowie anschliessend um den Faktor drei verkleinerten Bitmap (jeweils Ausschnittvergrösserung):


So komfortabel die Verwendung von vektorisierten Grafiken während einer Montage verschiedener Motiv-Bestandteile ist, so verschleiert doch deren ungehemmte Verwendung die Tatsache, dass am Ende einer derartigen Verarbeitungskette eben doch immer noch eine Rasterung steht. Zwar nur einmalig, aber doch genug, um einen Qualitätsverlust im Ergebnis herbeizuführen. Woher kommt dieser mögliche Qualitätsverlust, und wie äussert er sich?
Antialiasing
Im Vektorraum werden alle Koordinaten, Abmessungen etc. als Fliesskommazahlen gespeichert, wodurch nahezu beliebig feine Abstufungen möglich werden. Bei einem Rasterausgabegerät (z.B. LC Display, Laserdrucker, Tintenstrahdrucker etc.) ist man aber an feste, ganzzahlige Punkt-Positionen des jeweiligen Rasters gebunden. In der Anfangszeit der Computergrafik kam es daher zu dem Einigen noch bekannten «pixeligen» Aussehen. Mit dem Aufkommen von Geräten, die mehr als 1 Bit Farbtiefe unterstützten, wurde dann «Antialiasing» eingesetzt, um den unschönen Treppeneffekt visuell abzumildern und dem Auge die Interpretation als kontinuierliche Struktur zu erleichtern:

So gefällig eine Kantenglättung durch Antialiasing bei grösseren Strukturen wirkt die als Ganzes wahrgenommen werden, so störend und schädlich ist Antialiasing bei Strukturen, bei denen Details erhalten bleiben müssen, wie z.B. QR-Codes. Die Übergänge zwischen Vordergrundfarbe und Hintergrundfarbe zweier Module eines QR-Codes sollten idealerweise sprunghaft von einem Rasterpunkt zum nächsten erfolgen. Durch Antialiasing bei QR-Codes muss man allerdings damit rechnen, dass Modulgrenzen nicht exakt mit Rastergrenzen zusammenfallen und ein Modul im ungünstigsten Fall allseitig von einer Punktreihe in einer Zwischenfarbe zwischen Vordergrund- und Hintergrundfarbe umgeben ist.

Antialiasing an Modulgrenzen bedeutet somit einen Verlust an Kantenschärfe, und reduziert die Lesbarkeit eines QR-Codes im Grenzbereich der Erkennbarkeit, z.B. aufgrund von Verschmutzung, Beschädigung, Ausleuchtung, Entfernung.
Mir ist kein Verfahren bekannt, in dem man selektiv – für Motivabschnitte – Antialiasing deaktivieren kann. Was kann man also tun, wenn man in bestimmten Motivbereichen Antialiasing nutzen möchte, im Bereich eines QR-Codes jedoch maximale Kantenschärfe anstrebt?
Meine Lösung bestand darin, darauf zu achten, dass beim finalen Übergang vom Vektorraum zum Rasterraum ausschliesslich ganzzahlige Punkt-Adressen vorkommen.
Wie macht man das?
Da ich mein eigener Auftraggeber bin, geniesse ich eine gewisse Freiheit darin, meine Wünsche mit den realen Möglichkeiten abzugleichen und ggf. anzupassen.
So war es anfänglich mein Ziel im erwähnten Druckprojekt, dass QR-Code, DESMO-Logo und URL in einer Breite von 50 mm ausgedruckt werden sollten.
Drucker (im Sinne von: Dienstleister, die Druckdienstleistungen erbringen) verwenden für ihre Ausdrucke Rasterausgabegeräte, deren Auflösungsvermögen in [dpi] ausgedrückt wird – dots per inch. Wieviele diskret und ganzzahlig addressierbare Dots kann das Gerät in einer Strecke der Länge 1 inch ( = 25.4 mm ) unterbringen. Ich habe diese Frage mit meinem prospektiven Dienstleister geklärt: er wird einen Drucker mit 600 dpi einsetzen.
50 mm entsprechen überschlägig 2 inch. Also stünden auf der angepeilten Druckbreite rund 1200 dots zur Verfügung.
Die Information, welche ich in meinem QR-Code kodieren möchte, erfordert bei dem mir unbekannten Korrekturlevel eines kostenlosen QR-Code Generators einen QR-Code der Version 4, entsprechend 33 x 33 Modulen. Eine garantiert Antialiasing-freie Druckausgabe erhalte ich genau dann, wenn die Koordinaten der Startposition und quadratischen Modulabmessung exakt einem ganzzahligen Vielfachen der Rasterweite des Zielgeräts entspricht.
In meinem konkreten Fall: 1200 Dots (s.o.) verteilt auf 33 Module ergibt den krummen Wert von 36.3636… dots pro Modul. Bei einer (freiwilligen) Reduktion auf exakt 36.0 dots pro Modul käme ich auf eine Breite des QR-Codes von 33 Module x 36.0 dots pro Modul = 1188.0 dots. Diese würden bei 600 dpi genau 1.98 inch Druckbreite ergeben, entsprechend 50.292 mm. Das ist für meine Zwecke völlig ausreichend nah an meinem ursprünglich angepeilten Zielwert von 50.0 mm Druckbreite. 😎
Zur Vektorisierung von QR-Codes
An dieser Stelle vielleicht noch ein Einschub darüber, wie ich meinen QR-Code vektorisiert habe. Auch wenn es mir ein wenig peinlich ist: bei 33×33 Modulen ging das gerade noch mit der Methode des erweiterten «Scharfen Hinsehens». Ich habe den QR-Code bildschirmfüllend dargestellt und dann Modulzeilenweise von links nach rechts abgezählt, nach wievielen Modulen jeweils ein Farbumschlag zwischen Vordergrundfarbe und Hintergrundfarbe, bzw. umgekehrt erfolgt.

Im Ergebnis kam ich pro Modul-Zeile auf eine Komma-separierte Liste von Ganzzahlen. An der ersten Position jeder Zeile dieser Liste merke ich mir, ob die Modulreihe mit einem «gesetzten = 1» Modul beginnt welches in der Vordergrundfarbe zu zeichnen ist, oder mit einem «ungesetzten = 0» Modulbereich beginnt, welcher in der Hintergrundfarbe zu zeichnen ist, etwa so:
const QRData = [
// start_flag values ...
[1, 7,3,1,1,4,4,2,2,1,1,7],
[1, 1,5,1,1,1,1,1,2,6,3,1,3,1,5,1],
[1, 1,1,3,1,1,5,2,1,1,1,7,2,1,1,3,1,1],
[1, 1,1,3,1,1,1,3,1,1,1,2,1,1,2,1,3,1,1,1,1,3,1,1],
[1, 1,1,3,1,1,2,1,5,2,1,1,1,2,4,1,1,3,1,1],
[1, 1,5,1,1,2,1,2,4,1,4,1,1,1,1,1,5,1],
[1, 7,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,7],
[0, 9,3,2,1,1,6,1,1,9],
[1, 5,1,4,1,1,5,2,2,2,1,2,1,1,1,1,1,1,1],
[0, 2,2,6,1,1,4,2,2,1,5,3,2,2],
// ...
];
Schliesslich werte ich obiges Datenarray in einem kleinen von mir geschriebenen Progrämmchen Modul-zeilenweise in eine Serie svg rect Kommandos aus. Da ich auf eine transparente Klebefolie drucken lassen möchte zum späteren Aufkleben auf einen dunklen Hintergrund, habe ich die Vordergrundfarbe auf «weiss» gesetzt und die Hintergrund»farbe» auf transparent. Dabei wandle ich «gesetzte» Modulbereiche um in rahmenlose und weiss gefüllte Rechtecke der jeweiligen Breite, und überspringe ungesetzte Modulbereiche vor transparentem Hintergrund in der erforderlichen Breite. Hier der resultierende svg-Code für die obersten drei Modulzeilen und eine von mir gesetzte Modulweite von 10 «Einheiten»:
<svg id="svgOne" width="330" height="330">
<rect x="0" y="0" width="70" height="10" fill="white"></rect>
<rect x="100" y="0" width="10" height="10" fill="white"></rect>
<rect x="120" y="0" width="40" height="10" fill="white"></rect>
<rect x="200" y="0" width="20" height="10" fill="white"></rect>
<rect x="240" y="0" width="10" height="10" fill="white"></rect>
<rect x="260" y="0" width="70" height="10" fill="white"></rect>
<rect x="0" y="10" width="10" height="10" fill="white"></rect>
<rect x="60" y="10" width="10" height="10" fill="white"></rect>
<rect x="80" y="10" width="10" height="10" fill="white"></rect>
<rect x="100" y="10" width="10" height="10" fill="white"></rect>
<rect x="130" y="10" width="60" height="10" fill="white"></rect>
<rect x="220" y="10" width="10" height="10" fill="white"></rect>
<rect x="260" y="10" width="10" height="10" fill="white"></rect>
<rect x="320" y="10" width="10" height="10" fill="white"></rect>
<rect x="0" y="20" width="10" height="10" fill="white"></rect>
<rect x="20" y="20" width="30" height="10" fill="white"></rect>
<rect x="60" y="20" width="10" height="10" fill="white"></rect>
<rect x="120" y="20" width="20" height="10" fill="white"></rect>
<rect x="150" y="20" width="10" height="10" fill="white"></rect>
<rect x="170" y="20" width="70" height="10" fill="white"></rect>
<rect x="260" y="20" width="10" height="10" fill="white"></rect>
<rect x="280" y="20" width="30" height="10" fill="white"></rect>
<rect x="320" y="20" width="10" height="10" fill="white"></rect>
<rect x="0" y="30" width="10" height="10" fill="white"></rect>
<rect x="20" y="30" width="30" height="10" fill="white"></rect>
<!-- und so weiter ... -->
</svg>
Die vollständige svg-Datei enthält die vektorisierte Beschreibung meines QR-Codes die ich in Inkscape mit den anderen grafischen Elementen zu einem vereinheitlichten Layout montiere.
In Inkscape kann ich mir Objektpositionen (X, Y) und -abmessungen (Breite, Höhe) in unterschiedlichen Einheiten anzeigen lassen, darunter auch in Millimetern [mm] oder Pixeln [px]. Dies ist dann die Stelle, an der ich die Breite und Höhe des QR-Codes auf die oben erwähnten 1188 Pixel des Ausgabegeräts eingestellt habe. Zu beachten ist auch, dass ich sämtliche grün eingerahmte Angaben auf ganzzahlige Werte eingestellt habe, mit sämtlichen Nachkommastellen gleich Null:

Wie kommt man dazu, sich mit so etwas zu befassen?
Im vorliegenden Anwendungsfall war das nur Zufall. Mir war nämlich aufgefallen, dass erste Test-Exporte aus Inkscape mit anschliessendem Re-Import in Gimp eine deutliche Zeilenstruktur im Bereich des QR-Codes erkennen liessen, obwohl ich den «Zeilenvorschub» bei der zeilenweisen Vektorisierung des QR-Codes exakt auf die Modulweite eingestellt hatte und folglich eine nahtlose Folge von Modul-Zeilen erwartet hatte.
Nach vielen Versuchen kam ich schliesslich dahinter, dass die horizontalen «Linien» Antialiasing-Artefakte sind welche daher rühren, dass ich die den QR-Code beschreibende svg-Datei in einer Breite und Höhe hatte rendern lassen, welche nicht exakt gleich einem ganzzahligen Vielfachen der Anzahl Module meines QR-Codes war, nämlich 33. Die genannten «Linien» entstehen durch das sequentielle Rendern eines jeden Rechtecks, wobei es bei Höhe/Breite ungleich ganzzahligen Vielfachen von 33 an Ober- bzw. Unterkante des Rechtecks zu Antialiasing kommen kann. Die betroffenen Linien werden dabei als Mischfarbe zwischen der von mir gewählten Vordergrundfarbe weiss und transparentem Hintergrund, also teil-transparent gezeichnet. Beim anschliessenden Hinterlegen mit einer schwarzen Hintergrundebene schimmert dieser dann als mehr oder weniger dunkle Linie durch die teiltransparenten Pixelreihen hindurch:


Die obigen beiden Grafiken stellen einen QR-Code der Abmessungen 33×33 Module dar. Links auf eine Bitmap der Grösse 199×199 Pixel gerendert, einem nicht exakt ganzzahligen Vielfachen von 33×33. Die rechte Grafik wurde auf eine Bitmap der Abmessungen 198×198 Pixel gerendert, was dem exakt 6-fachen der QR-Code Abmessungen von 33×33 entspricht. Der Effekt von nur einem einzigen Pixel Abweichung vom ganzzahligen Vielfachen ist echt verblüffend. Er wird stärker, je kleiner das Vielfache der Anzahl Module beim Rendern gewählt wird. Nachfolgend für diverse kleine Vielfache von 33:






Fazit
- Es lohnt sich immer, Druckabmessungen in [Anzahl Dots des Ausgabegeräts] eines QR-Codes auf ganzzahlige Vielfache der jeweiligen QR-Code Abmessungen in [Anzahl Module] auszurichten. Auch die Positionen von Rechtecken (linke obere Ecke) müssen auf glatten, ganzzahligen Rasteraddressen liegen.
- Falls dies nicht möglich ist, den QR-Code mit möglichst vielen Dots pro Modul drucken lassen. Dadurch wird der Einfluss einer umlaufenden 1-Dot breiten Unschärfezone verringert. Der Erfinder der QR-Code Technologie, die japanische Firma Denso Wave empfiehlt, zur Vermeidung von Unschärfe (sic!) und zur Optimierung des Anteils erfolgreicher QR-Code Scans mindestens(!) 4 Drucker-Dots pro Modul zu verwenden.
- Als Verallgemeinerung von Tip 2: sofern eine Wahlmöglichkeit besteht, einem höher auflösenden Ausgabegerät bei vorgegebener Breite des Ausdrucks immer den Vorzug geben.