<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alan Vargas</title>
    <description>The latest articles on DEV Community by Alan Vargas (@a-vargas).</description>
    <link>https://dev.to/a-vargas</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4229191%2F7956437d-7ac5-4e0e-b96c-3ab547c31a13.png</url>
      <title>DEV Community: Alan Vargas</title>
      <link>https://dev.to/a-vargas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/a-vargas"/>
    <language>en</language>
    <item>
      <title>I Decoded 208 Round, Triangle and Hexagon QR Codes. Here's What Actually Breaks Them</title>
      <dc:creator>Alan Vargas</dc:creator>
      <pubDate>Thu, 05 Nov 2026 19:55:43 +0000</pubDate>
      <link>https://dev.to/a-vargas/i-decoded-208-round-triangle-and-hexagon-qr-codes-heres-what-actually-breaks-them-3fji</link>
      <guid>https://dev.to/a-vargas/i-decoded-208-round-triangle-and-hexagon-qr-codes-heres-what-actually-breaks-them-3fji</guid>
      <description>&lt;p&gt;Every article about round QR codes answers "do they scan?" the same way: &lt;em&gt;yes, as long as you keep good contrast and test it.&lt;/em&gt; That's true, and it doesn't help you much.&lt;/p&gt;

&lt;p&gt;I maintain &lt;a href="https://github.com/koloqr/qr-code-shapes" rel="noopener noreferrer"&gt;qr-code-shapes&lt;/a&gt;, a small dependency-free JavaScript library that renders QR codes as SVG inside a circle, a triangle or a hexagon. It's the renderer behind a free generator I run. I wanted a real answer, so I rendered every combination the library supports and fed each one to two open-source decoders at seven sizes.&lt;/p&gt;

&lt;p&gt;Short version: &lt;strong&gt;all 208 combinations decoded&lt;/strong&gt;. Two things got in the way, and neither one was the shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, What a "Round" QR Code Actually Is
&lt;/h2&gt;

&lt;p&gt;There's no such thing as a circular QR code. The standard (ISO/IEC 18004) defines a QR symbol as a square grid of modules, and no phone camera has a code path for anything else.&lt;/p&gt;

&lt;p&gt;A "round QR code" is therefore a normal square matrix with artwork around it. Here's the relevant bit of the circle renderer, slightly trimmed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;qz&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// quiet zone, in modules&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;contPad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// decorative band beyond the quiet zone&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;innerR&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;qz&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;contPad&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// The QR is a square - its corners reach (n/2)·√2 modules from the&lt;/span&gt;
&lt;span class="c1"&gt;// centre. For long payloads that pokes past the circle, so grow it.&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;halfDiag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SQRT2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;innerR&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;halfDiag&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;innerR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;halfDiag&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then it scans a grid around the matrix and drops decorative dots into the empty space. Each dot is skipped if it lands on the matrix or outside the circle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Skip QR matrix area (+ tiny margin)&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pcx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;safeX0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;pcx&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;safeX1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;pcy&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;safeY0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;pcy&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;safeY1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Skip outside content circle&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;distC&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;innerR&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real matrix is drawn on top, untouched. The decoration is in the same colour, so to your eye it's one round code. To a decoder it's noise around a square. The triangle and hexagon renderers work the same way, just with different geometry.&lt;/p&gt;

&lt;p&gt;One thing I didn't expect when I went back through the code: the decorative dots start about &lt;strong&gt;one module&lt;/strong&gt; from the matrix edge, not the four modules of quiet zone the standard asks for. I'd have bet that this would hurt. It didn't, at least not with the decoders below.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Test
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Inputs:&lt;/strong&gt; 4 shapes × 4 frames (&lt;code&gt;none&lt;/code&gt;, &lt;code&gt;thin&lt;/code&gt;, &lt;code&gt;double&lt;/code&gt;, &lt;code&gt;organic&lt;/code&gt;) × 13 module patterns (dots, hearts, stars, pills, bars…) = 208 SVGs. Payload &lt;code&gt;https://koloqr.com&lt;/code&gt;, error correction fixed at level H.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Raster:&lt;/strong&gt; each SVG rendered with &lt;code&gt;sharp&lt;/code&gt; to 1000, 600, 400, 250, 180, 140 and 110 px square, both sharp and with a small Gaussian blur to stand in for a phone lens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decoders:&lt;/strong&gt; &lt;a href="https://github.com/zxing-cpp/zxing-cpp" rel="noopener noreferrer"&gt;ZXing-C++&lt;/a&gt; through &lt;code&gt;zxing-wasm&lt;/code&gt; (the C++ port of ZXing, the open-source decoder behind many scanner apps), and &lt;a href="https://github.com/cozmo/jsQR" rel="noopener noreferrer"&gt;jsQR&lt;/a&gt;, the most popular pure-JS decoder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core loop is short:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;sharp&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sharp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readBarcodes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zxing-wasm/reader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;renderQR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SHAPES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FRAMES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PATTERNS&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/index.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;SHAPES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frame&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;FRAMES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;PATTERNS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;svg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;renderQR&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://koloqr.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;140&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sharp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;svg&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;density&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#fff&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#fff&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ensureAlpha&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBuffer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;resolveWithObject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ImageData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8ClampedArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;readBarcodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;formats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;QRCode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="c1"&gt;// record hit?.text === 'https://koloqr.com'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(In Node you need a three-line &lt;code&gt;ImageData&lt;/code&gt; shim. Everything else runs as written.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Result 1: The Shape Doesn't Break the Code
&lt;/h2&gt;

&lt;p&gt;With ZXing-C++, &lt;strong&gt;every one of the 208 combinations decoded at 250 px and above&lt;/strong&gt;, sharp or blurred. Hearts inside a triangle with a wavy frame decoded. Pills inside a hexagon decoded. The shape doesn't matter, because the decoder never looks at it. It finds the three finder patterns, works out the grid and samples the modules. The artwork is outside the area it reads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Result 2: The Shape Costs You Width
&lt;/h2&gt;

&lt;p&gt;Things got interesting when the images got small. Same artwork width for every shape, ZXing-C++, sharp images, out of 52 combinations per shape:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Shape&lt;/th&gt;
&lt;th&gt;250 px&lt;/th&gt;
&lt;th&gt;180 px&lt;/th&gt;
&lt;th&gt;140 px&lt;/th&gt;
&lt;th&gt;110 px&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Square&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Circle&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hexagon&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Triangle&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The order matches how much of the artwork the data matrix actually fills. In this renderer that's roughly 70% of a plain square export, 55–60% of a circle's diameter, 53% of a hexagon's width and only about 38% of a triangle's base. A triangle has to be wide enough to fit a square inside it, so most of its width is decoration.&lt;/p&gt;

&lt;p&gt;At 110 px that works out to roughly 2.7 pixels per module for the square, 2.2 for the circle, 2.0 for the hexagon and 1.4 for the triangle. Decoders give up somewhere around 2 px per module, and the table shows exactly that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The practical rule:&lt;/strong&gt; if a square code works at a given size, the same code in a circle needs to print about 20% wider, a hexagon about a third wider and a triangle nearly twice as wide to give the decoder the same module size. Shape is a layout cost, not a reliability cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Result 3: jsQR Will Tell You Your Code Is Broken When It Isn't
&lt;/h2&gt;

&lt;p&gt;This one could have sent me down a rabbit hole. With jsQR, on a sharp 1000 px render, only &lt;strong&gt;20 of 52 square codes&lt;/strong&gt; decoded, and the other shapes landed in the same range (19 to 28 of 52). Smaller images did &lt;em&gt;better&lt;/em&gt;: at 140 px, 51 of 52 square codes decoded.&lt;/p&gt;

&lt;p&gt;The failures weren't random. Every pattern made of separate shapes failed: dots, hearts, stars, diamonds, little triangles. Patterns that merge neighbouring modules into solid blocks (&lt;code&gt;square&lt;/code&gt;, &lt;code&gt;rounded&lt;/code&gt;, the bars) passed. My guess is jsQR's binarizer: at high resolution it sees the white gaps between dots as real light area. Downscaling blurs the dots back into something module-shaped.&lt;/p&gt;

&lt;p&gt;ZXing-C++ read all of those at every size from 250 px up. So if you build a QR feature and test it with jsQR on a crisp screenshot, you can "prove" a perfectly good code is broken. Test with more than one decoder, test with a real phone camera, and don't test only at one resolution.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Tell Anyone Drawing Custom QR Codes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Never modify the matrix to fit the shape.&lt;/strong&gt; Draw the square matrix whole and put the shape around it. Error correction (level H recovers roughly 30% of codewords) is a safety margin for logos and damage. Don't spend it on geometry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leave the finder patterns alone.&lt;/strong&gt; Decoders find them by a 1:1:3:1:1 dark/light ratio through the centre. Rounding their corners is fine. Filling in the light ring, or putting a logo over one, is not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Size by modules, not by artwork.&lt;/strong&gt; Work out the physical size of one module at print size and keep it the same as you'd give a square code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use two decoders and a camera.&lt;/strong&gt; One open-source decoder is a sample size of one.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;The library is MIT licensed, has no dependencies and gives you an SVG string, so it runs the same in Node, a worker or the browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/koloqr/qr-code-shapes.git
&lt;span class="nb"&gt;cd &lt;/span&gt;qr-code-shapes
npm &lt;span class="nb"&gt;test
&lt;/span&gt;npm run demo   &lt;span class="c"&gt;# → http://localhost:8000/demo/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;renderQR&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/index.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;svg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;renderQR&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;circle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;organic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dots&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#1d4ed8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you'd rather just make a round code and scan it with your own phone, the generator is preset to the circular shape here: &lt;a href="https://koloqr.com/inspiration/round-qr-codes" rel="noopener noreferrer"&gt;koloqr.com/inspiration/round-qr-codes&lt;/a&gt;. It's free, with no signup.&lt;/p&gt;

&lt;p&gt;I'd love to see the same test on Apple's Vision and Google's ML Kit. Both are closed, and probably better than either decoder here. If you run it, post your numbers in the comments.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>opensource</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
    </item>
  </channel>
</rss>
