<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Gianni Rosato</title>
    <subtitle>Hi, I&#x27;m Gianni. I build video &amp; image compression technology for a more beautiful Internet.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://giannirosato.com/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://giannirosato.com"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2025-10-12T00:00:00+00:00</updated>
    <id>https://giannirosato.com/atom.xml</id>
    <entry xml:lang="en">
        <title>Introducing oavif</title>
        <published>2025-10-12T00:00:00+00:00</published>
        <updated>2025-10-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Gianni Rosato
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://giannirosato.com/blog/post/oavif/"/>
        <id>https://giannirosato.com/blog/post/oavif/</id>
        <content type="html" xml:base="https://giannirosato.com/blog/post/oavif/">&lt;p&gt;oavif is a new approach to target quality encoding in image compression, designed around smarter convergence strategies and quicker scoring to be as fast as possible.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;div class=&quot;image-container&quot;&gt;
  &lt;picture&gt;
    &lt;img
      src=&quot;https:&#x2F;&#x2F;giannirosato.com&#x2F;static&#x2F;img&#x2F;waves.avif&quot;
      width=&quot;1536&quot;
      height=&quot;864&quot;
      alt=&quot;Waves Crashing&quot;
    &#x2F;&gt;
  &lt;&#x2F;picture&gt;
&lt;&#x2F;div&gt;
&lt;h2 id=&quot;why&quot;&gt;Why?&lt;&#x2F;h2&gt;
&lt;p&gt;Target quality encoding is one of the highest impact use cases for image compression. A target quality encoder framework aims to produce an image encoded at a particular quality set by the user according to some metric or visual quality index. This kind of encoder framework is useful for a variety of users, ranging from small website owners to content delivery networks pushing vast quantities of image data through the Web.&lt;&#x2F;p&gt;
&lt;p&gt;The value of target quality encoding is perceptual consistency. If I rely entirely on my encoder&#x27;s internal quality index (often set with a &quot;q&quot; parameter), I may not get outputs of perfectly consistent quality when using the same &quot;q&quot; across different images. Relying on a metric that represents the viewer&#x27;s experience is the solution to this; targeting a representative score within this metric will ensure you always receive an optimally encoded file that never undershoots and ruins image quality, and never overshoots and wastes data.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gianni-rosato&#x2F;oavif&quot;&gt;&lt;strong&gt;oavif&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt; is a tool to do target quality encoding extremely quickly. There are three core components to a target quality encoding framework: the metric, the encoder, and the convergence algorithm. oavif aims to leverage or improve the state of the art in all three categories.&lt;&#x2F;p&gt;
&lt;div class=&quot;inline-button&quot;&gt;
  
  &lt;a href=&quot;https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;gianni-rosato&amp;#x2F;oavif&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; class=&quot;pill-btn&quot; aria-label=&quot;Source Code&quot;&gt;Source Code&lt;&#x2F;a&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;I think this use case has been neglected because it sits in an awkward spot, stuck between encoder development and content deployment. Considering image encoders and powerful metrics are fast, it is easy to take them for granted and build inefficient frameworks around them. Slow frameworks waste valuable resources; processing images is expensive. I built oavif with the same approach I&#x27;ve adopted when building encoders, where every CPU cycle counts.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;metric&quot;&gt;Metric&lt;&#x2F;h2&gt;
&lt;p&gt;There are good metrics and bad metrics in the context of what humans care about in images. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;codecs.wiki&#x2F;docs&#x2F;metrics&#x2F;PSNR&quot;&gt;PSNR&lt;&#x2F;a&gt; is a bad metric; targeting a PSNR score doesn&#x27;t mean anything to users, because images at the same PSNR could look completely different. However, PSNR is very fast, and faster metrics lend themselves more favorably to target quality encoding.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;cloudinary&#x2F;ssimulacra2&quot;&gt;SSIMULACRA2&lt;&#x2F;a&gt; correlates highly with subjective human quality ratings, but it is comparatively slow compared to simpler metrics. I set out to remedy this with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gianni-rosato&#x2F;fssimu2&quot;&gt;fssimu2&lt;&#x2F;a&gt;, a faster implementation that uses almost 40% less memory. This is what oavif uses, and it makes computing the in-loop metric much faster compared to the reference library.&lt;&#x2F;p&gt;
&lt;p&gt;Testing on a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;files.catbox.moe&#x2F;ott1x0.png&quot;&gt;4k test image&lt;&#x2F;a&gt; against a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;files.catbox.moe&#x2F;xpab87.jpg&quot;&gt;distorted sample&lt;&#x2F;a&gt; for an average time to score across 8 runs, Butteraugli (a perceptual metric from the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;libjxl&#x2F;libjxl&quot;&gt;libjxl&lt;&#x2F;a&gt; project) took 2455ms, while the reference SSIMULACRA2 implementation took 1162ms. fssimu2 takes 631.9ms. Testing was done on my M2 MacBook Air using &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;sharkdp&#x2F;hyperfine&quot;&gt;&lt;code&gt;hyperfine&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;img&#x2F;fssimu2.svg&quot; alt=&quot;fssimu2 performance graph&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;encoder&quot;&gt;Encoder&lt;&#x2F;h2&gt;
&lt;p&gt;AVIF is a capable Web image format. oavif uses &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;aomedia.googlesource.com&#x2F;aom&#x2F;&quot;&gt;libaom&lt;&#x2F;a&gt; (via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;AOMediaCodec&#x2F;libavif&quot;&gt;libavif&lt;&#x2F;a&gt;) because it is the best open-source image encoder available relative to its speed. I worked on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;halide.cx&#x2F;blog&#x2F;improving-avif-in-open-source&#x2F;&quot;&gt;improving AVIF encoding&lt;&#x2F;a&gt; in 2024 during my work on &lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;svt-av1-psy.com&quot;&gt;SVT-AV1-PSY&lt;&#x2F;a&gt;. Google (with help from &lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;juliobbv.com&quot;&gt;Julio Barba&lt;&#x2F;a&gt;) later adopted this work and advanced it further in libaom. It is now used by some websites you may know, such as &lt;em&gt;&lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;theguardian.com&quot;&gt;The Guardian&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Aside from speed, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;halide.cx&#x2F;blog&#x2F;consistency&#x2F;&quot;&gt;encoder consistency&lt;&#x2F;a&gt; is valuable in the context of target quality encoding (I&#x27;ll explain more about why later). In fact, a perfectly consistent encoder would be able to eliminate the need for targeting entirely since the encoder&#x27;s user-configurable Q would map perfectly to some perceptual index. libaom has had engineering effort go into encoder consistency, which is a valuable thing.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;convergence&quot;&gt;Convergence&lt;&#x2F;h2&gt;
&lt;p&gt;A simple convergence loop looks like this:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Decode input, pass to encoder&lt;&#x2F;li&gt;
&lt;li&gt;Decode encoder output &amp;amp; compare to input with metric&lt;&#x2F;li&gt;
&lt;li&gt;If we hit the target metric score, finish; otherwise, repeat prev. step with modified settings&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The most important part here is how we decide to modify our settings. This is the convergence algorithm that allows us to search for the best encoder Q. The easiest way to do this is with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Binary_search&quot;&gt;binary search&lt;&#x2F;a&gt;, and some more recent implementations have utilized &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-av&#x2F;Av1an&#x2F;pull&#x2F;1014&quot;&gt;clever interpolation&lt;&#x2F;a&gt; using past data to inform the next guess based on the fact that we know encoder Q and target score are likely correlated.&lt;&#x2F;p&gt;
&lt;p&gt;oavif takes inspiration from both of these, adding predictive modeling alongside error-informed search space correction to improve search times significantly. To understand why, we&#x27;ll walk through each stage of the implementation.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;binary-search&quot;&gt;Binary Search&lt;&#x2F;h3&gt;
&lt;p&gt;This testing was done using the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;WyohKnott&#x2F;image-formats-comparison&#x2F;tree&#x2F;gh-pages&#x2F;comparisonfiles&#x2F;subset2&#x2F;Original&quot;&gt;Daala subset2&lt;&#x2F;a&gt; image dataset. Importantly, I only used this dataset for validation; oavif was not designed around this specific dataset in any way. When testing, the oavif configuration was left at defaults; only the convergence implementation was modified. The threshold for meeting the target score is ±2.0 in oavif by default, and the default target score is 80.0 as measured by fssimu2 because it is a reasonable &quot;high fidelity&quot; target.&lt;&#x2F;p&gt;
&lt;p&gt;Everyone with some algorithms background will start with binary search. Set your bounds for encoder Q to 0..100, and divide the range in half each time you test. In oavif, a pure binary search implementation at default settings yields the following results:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Average encoding time: 467.95 ms ± 94.64&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Average passes: 3.20 ± 0.45 (max: 4 min: 2)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;interpolation&quot;&gt;Interpolation&lt;&#x2F;h3&gt;
&lt;p&gt;Interpolation-based target quality searches by iteratively probing, measuring, and narrowing the search interval just like binary search. The difference is that it tries to model the score-vs-quantizer curve with interpolation (linear, quadratic, etc) as more data is accumulated. This should theoretically reduce the number of necessary encodes, and can start with standard binary search when there is not enough data to interpolate with.&lt;&#x2F;p&gt;
&lt;p&gt;Metric score vs encoder Q is generally (though not perfectly) a mostly monotonic curve. Interpolation-based inverse estimation uses the measured points to approximate that curve and solve for the quantizer that would produce the target score. Higher-order methods use more shape information and are thus theoretically more accurate. Adding linear and quadratic interpolation support to oavif, we see a small reduction in the average number of passes on subset2:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Average encoding time: 468.98 ms ± 97.28&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Average passes: 3.12 ± 0.39 (max: 4 min: 2)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is a 2.5% improvement. We still need a minimum of two passes to accurately target.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;predictive-modeling&quot;&gt;Predictive Modeling&lt;&#x2F;h3&gt;
&lt;p&gt;This feature uses an exponential curve trained on the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gianni-rosato&#x2F;gb82-image-set&quot;&gt;gb82 image dataset&lt;&#x2F;a&gt; with libaom (at speed 9, 10-bit, 4:4:4 chroma). The curve looks like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;img&#x2F;libaom-pred.svg&quot; alt=&quot;libaom predicted Q from SSIMULACRA2 curve&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Based on this, we can write some very simple code to predict a Q value from the target score:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;zig&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt; predictQFromScore&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;tgt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; f64&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; q&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 6.83&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @exp&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;0.0282&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; tgt&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @intFromFloat&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt;@min&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;100.0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @round&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;q&lt;&#x2F;span&gt;&lt;span&gt;)))&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This yields the biggest average improvement in this testing so far, decreasing average pass count by 56.4% versus interpolation search and 57.5% versus binary search.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Average encoding time: 218.33 ms ± 114.16&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Average passes: 1.36 ± 0.78 (max: 3 min: 1)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The gb82 image set is fairly low-resolution mixed photographic content, while Daala subset2 is medium-resolution photographic content with less variation. The fact that the model generalizes so well is exciting.&lt;&#x2F;p&gt;
&lt;p&gt;It is at this stage that encoder consistency becomes important. A more consistent encoder will diverge from our model&#x27;s predictions less frequently, and theoretically result in a faster target quality loop.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;error-bounds&quot;&gt;Error Bounds&lt;&#x2F;h3&gt;
&lt;p&gt;Because our initial predictions tend to be so accurate, we can use them to aggressively narrow our search space without incurring too much risk of a search space collapse.&lt;&#x2F;p&gt;
&lt;p&gt;The basis of this is that utilizing plain binary search with prediction is often unreliable. Let&#x27;s say we would like to target score=80, and our model predicts we need Q=65. We score 82.38. Now we are forced to search (0..65), which is worse than if we had just avoided prediction in the first place (our search space would be 50..100 in that case). This is in spite of the fact that our prediction was very close to the target.&lt;&#x2F;p&gt;
&lt;p&gt;oavif uses the distance from the target to its advantage:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;zig&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; abs_err&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @abs&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;e&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;score&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; o&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;score_tgt&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;pass&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; err_bound&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @intFromFloat&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt;@ceil&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;abs_err&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 4.0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;e&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;score&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; o&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;score_tgt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;        hi_bound&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; e&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;q&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;        lo_bound&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;e&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;q&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; err_bound&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;e&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;q&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; err_bound&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; else&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    } &lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;else&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;        lo_bound&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; e&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;q&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;        hi_bound&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @min&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;100&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; e&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;q&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; err_bound&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In this case, the error was 2.38; &lt;code&gt;@ceil()&lt;&#x2F;code&gt; brings this to 3, and we multiply by 4 because the midpoint of the new range tends to be very close to the target value based on my testing. The performance improves in kind:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Average encoding time: 194.50 ms ± 69.89&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Average passes: 1.18 ± 0.39 (max: 2 min: 1)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This costs 13.2% fewer passes than interpolation + prediction, and 63.1% fewer passes than binary search.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;img&#x2F;oavif-passes.svg&quot; alt=&quot;oavif Convergence Passes&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The minimum number of passes necessary in a naive binary search or interpolation-informed search is now the maximum number of passes we need to converge on the target on Daala subset2. You&#x27;ll also notice the standard deviation went down due to the fact that the ceiling has been lowered.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;architecture&quot;&gt;Architecture&lt;&#x2F;h2&gt;
&lt;p&gt;We&#x27;ve made it work and we&#x27;ve made it good, so now we can make it fast. oavif is written in Zig, and uses available high-performance C decoder libraries for handling inputs and decoding AVIF in the convergence loop. All image I&#x2F;O during convergence is done in memory, and a buffer is kept of our latest encode to write to a file if we meet the target in the search space.&lt;&#x2F;p&gt;
&lt;p&gt;Efforts have gone into making oavif comparable to libavif&#x27;s &lt;code&gt;avifenc&lt;&#x2F;code&gt; in terms of features as well. It supports high bit depth I&#x2F;O, ICC profile handling for most formats, user-configurable encoder settings, and better defaults (until tune=iq becomes the libaom default in libavif).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;future-directions&quot;&gt;Future Directions&lt;&#x2F;h2&gt;
&lt;p&gt;Architecturally, it would be trivial to keep a history of buffers active and always pick from the history, even if our loop doesn&#x27;t converge on the target. I opted to avoid this for now because it dramatically increases memory usage, but if I receive widespread feedback that memory is unimportant I&#x27;ll consider an implementation. In its current state, we hit the in-loop buffer the vast majority of the time anyway.&lt;&#x2F;p&gt;
&lt;p&gt;I think the future of this kind of workflow is far more accurate predictive modeling. I believe it is possible to improve what I&#x27;ve done if we provide details about the source image as another term in the equation (like variance or entropy) and train our prediction mechanism on this additional data. I&#x27;m optimistically convinced this could result in a very high success rate for one-shot targeting.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m looking forward to seeing more target quality workflows taking advantage of smarter targeting. If you&#x27;ve made it this far, thanks for reading, and enjoy oavif!&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>The Multimedia Renaissance</title>
        <published>2025-08-18T00:00:00+00:00</published>
        <updated>2025-08-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Gianni Rosato
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://giannirosato.com/blog/post/the-multimedia-renaissance/"/>
        <id>https://giannirosato.com/blog/post/the-multimedia-renaissance/</id>
        <content type="html" xml:base="https://giannirosato.com/blog/post/the-multimedia-renaissance/">&lt;p&gt;The best open source video and image compression algorithms have improved significantly over the past year, and developer tools have miraculously kept up with this rapid pace. There has never been a better time for video.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;div class=&quot;image-container&quot;&gt;
  &lt;picture&gt;
    &lt;img
      src=&quot;https:&#x2F;&#x2F;giannirosato.com&#x2F;static&#x2F;img&#x2F;leaves.avif&quot;
      width=&quot;1536&quot;
      height=&quot;864&quot;
      alt=&quot;Green
Leaves&quot;
    &#x2F;&gt;
  &lt;&#x2F;picture&gt;
&lt;&#x2F;div&gt;
&lt;h2 id=&quot;purpose&quot;&gt;Purpose&lt;&#x2F;h2&gt;
&lt;p&gt;When you work on anything, I think it is worth thinking about why you are
working on it aside from whether or not it provides some instant gratification.
Thinking about what the core purpose of video and image compression might be, it
is easy to land on a simple conclusion: smaller images and (especially) videos
make the internet cheaper. Video makes up the vast majority of internet
bandwidth, so small gains add up. However, this prompts further questioning;
what might the end game be for compression? For the average person, what is the
deliverable derived from compression technology indistinguishable from magic?&lt;&#x2F;p&gt;
&lt;p&gt;I believe this final goal is beautiful multimedia accessible anywhere. In other
words, an artist&#x27;s vision is not served half-baked to any sub-section of their
audience. Compression doesn&#x27;t just make the internet cheaper, it also makes it
more beautiful.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;&#x2F;h2&gt;
&lt;p&gt;The most revolutionary compression technology has been able to unlock new
experiences for people creating and experiencing art. If you look at the history
of video, this is illustrated by certain major leaps made over time. Every
breakthrough represents a widening of the pipeline between artist and audience.
Let&#x27;s look at the timeline.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Video begins as something you experience at a theater; people travel to see
certain videos at specific times, and then maybe never again. Video doesn&#x27;t
look that great, and for a while, it doesn&#x27;t have sound or color.&lt;&#x2F;li&gt;
&lt;li&gt;Analog broadcast television lets people see certain videos at specific times
anywhere that has the right equipment. At some point, video gets color.&lt;&#x2F;li&gt;
&lt;li&gt;VCRs enable time-shifted viewing; you can see certain videos at almost any
time.&lt;&#x2F;li&gt;
&lt;li&gt;Digital optical discs improve upon the last step by providing better looking
videos.&lt;&#x2F;li&gt;
&lt;li&gt;The world of digital internet video emerges through peer-to-peer downloads.
Now you can see pretty much any video anywhere after waiting for a download.
Regardless of your internet connection, a smaller file means a faster
download.&lt;&#x2F;li&gt;
&lt;li&gt;Streaming platforms make on-demand web video real; now you can watch pretty
much any video anywhere with a good internet connection almost instantly.&lt;&#x2F;li&gt;
&lt;li&gt;Smartphones proliferate; smarter compression and streaming technology means
we can do what we did in the last step on most internet connections, allowing
people to access virtually any video almost anywhere on Earth.&lt;&#x2F;li&gt;
&lt;li&gt;Today, beautiful video is no longer exclusive to Blu-ray; high-end streaming
services provide access to high-fidelity video anywhere with decent internet.
Additionally, live video platforms let anyone show lots of people a window
into their life from almost anywhere.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Through the combined efforts of better internet and better compression, we could
see more breakthroughs in the future. Emerging immersive formats (360° video,
VR&#x2F;AR) and cloud gaming are opportunities for more innovation, and could unlock
compelling art with enough accessibility.&lt;&#x2F;p&gt;
&lt;p&gt;For now, it is worth acknowledging that we&#x27;ve come a really long way.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;present-day&quot;&gt;Present Day&lt;&#x2F;h2&gt;
&lt;p&gt;The past two years have been incredibly consequential for video. I would argue
this is the strongest the ecosystem has been in over a decade from a
technological standpoint. The best open-source video encoder in the world is
beginning to show signs of maturity, web-first image compression had its first
major breakthrough since JPEG, and life has become a lot easier for developers.
Let&#x27;s walk through each of these stories.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;video&quot;&gt;Video&lt;&#x2F;h2&gt;
&lt;p&gt;SVT-AV1 is the best open-source video encoder in the world, according to a lot
of metrics. Despite this, it is still not the most mature (that title goes to
x264). For SVT-AV1 to fully supersede x264, it needs to always be better; it
cannot just be mostly better. Metrics say we are already here, but subjective
testing tells a different story.&lt;&#x2F;p&gt;
&lt;p&gt;One year ago (August 2024), the predominant narrative around AV1 was that it was
&quot;blurry&quot;. Time and energy were invested in swaying this perception, but it
ultimately still held. To address this, I had started the SVT-AV1-PSY project
earlier that year with the stated goal of building and testing research-grade
features for subjective quality based on user feedback.&lt;&#x2F;p&gt;
&lt;p&gt;In August 2025, this perception has mostly been shattered. A number of the
aforementioned research-grade features have been upgraded to production-grade,
with more progress happening every day. Additionally, the number of features has
ballooned; SVT-AV1-PSY saw a number of additional releases, and as the project
was put to rest, a few new forks emerged to perpetuate the effort.&lt;&#x2F;p&gt;
&lt;p&gt;Utilities for using video encoders have improved dramatically as well; the
popular chunked encoding script called Av1an saw a development resurgence to
reinvigorate some older features, and other tools with similar functionality
arrived on the scene to provide more options to users.&lt;&#x2F;p&gt;
&lt;p&gt;The implications of these dramatic improvements are numbered. As an enthusiast,
it is no longer as easy to avoid producing good-looking AV1 video. SVT-AV1 is
arguably more mature than x265, and x264 is the final available contender.
Considering it takes the better part of a decade to develop a robust video
encoder, the fact that so much is in motion right now is exciting news.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;images&quot;&gt;Images&lt;&#x2F;h2&gt;
&lt;p&gt;In August 2024, the image compression story was looking unfortunate compared to
video. WebP failed to deliver exceptional compression improvements over modern
JPEG encoders, and AVIF was looking promising but underwhelming. It suffered
from the same &quot;blurriness&quot; as AV1, but it was far more severe in a world where
modern image encoders tend to be well-optimized for human perception. The best
standard by far was JPEG XL, which had been removed from Chrome, effectively
killing any chance of ubiquitous use of the format on the web.&lt;&#x2F;p&gt;
&lt;p&gt;While all of this held true, in August Julio Barba and I were working on
image-focused enhancements for SVT-AV1-PSY. We announced our results publicly,
and while they were promising, the implementation was limited to our
community-supported video encoder.&lt;&#x2F;p&gt;
&lt;p&gt;In 2025, Julio has worked diligently with Google to bring our work to libaom,
the reference implementation of AV1. Due to its more complete feature set, it
already has a number of image-specific performance considerations, so it was a
perfect fit. The new improvements are wrapped into Tune IQ in libaom, and
websites like The Guardian are already benefiting from its vastly improved
consistency and compression gains. Before Tune IQ, AVIF would occasionally lose
to JPEG; this is no longer true.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;developers&quot;&gt;Developers&lt;&#x2F;h2&gt;
&lt;p&gt;In August 2024, everyone&#x27;s favorite metric is SSIMULACRA2. It is incredibly
accurate to human visual perception, and helped guide certain decisions made
during SVT-AV1-PSY development. It had a couple of issues, though.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The reference implementation in C doesn&#x27;t support videos&lt;&#x2F;li&gt;
&lt;li&gt;The Rust implementation ssimulacra2_rs is not fast&lt;&#x2F;li&gt;
&lt;li&gt;The Zig implementation is decently fast, but still not as fast as VMAF, SSIM
or PSNR&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Enter Vship, an SSIMULACRA2 and Butteraugli implementation that uses the GPU.
Better developer tools enable new development paradigms, and Vship&#x27;s 10–100x
speed improvement opened the doors for encoder testing frameworks like PSY-EX
&lt;code&gt;metrics&lt;&#x2F;code&gt; that allow for streamlined encoder benchmarking automation. My blog
post on comparing video encoders would have been a lot longer and more complex
in 2024, but now testing an encoder&#x27;s convex hull is as simple as running a
single Bash script to call PSY-EX metrics tooling powered by Vship.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-s-next&quot;&gt;What&#x27;s Next&lt;&#x2F;h2&gt;
&lt;p&gt;I have a lot of hope that AV2 will rally intense community efforts in open
source like AV1 did. There is a lot of performance left on the table with a
standard as complex as AV2, and I hope in the next decade it will be properly
realized.&lt;&#x2F;p&gt;
&lt;p&gt;For now, I think we should celebrate what has happened so far.&lt;&#x2F;p&gt;
&lt;p&gt;AV1 currently features:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;An innovative open-source decoder (dav1d) that has outperformed everyone&#x27;s
initial expectations for AV1&lt;&#x2F;li&gt;
&lt;li&gt;A performant, competitive reference encoder that is still seeing developments
right now, for both images and videos&lt;&#x2F;li&gt;
&lt;li&gt;A competitive, performant open-source production encoder that has driven
dedicated psychovisual feature implementations&lt;&#x2F;li&gt;
&lt;li&gt;An independent encoder in Rust&lt;&#x2F;li&gt;
&lt;li&gt;Some proprietary solutions that offer extra value (features, performance,
quality, etc) to select clients&lt;&#x2F;li&gt;
&lt;li&gt;Vibrant online compression communities that are drawn to the field out of pure
passion&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In the past year, we&#x27;ve seen:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Major advances in maturity in the world&#x27;s best open-source video encoder&lt;&#x2F;li&gt;
&lt;li&gt;The first image compression implementation available on any browser that
meaningfully surpasses JPEG&lt;&#x2F;li&gt;
&lt;li&gt;Faster, easier-to-use developer tools than ever before&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I&#x27;m excited for what the next year of development has to offer.&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Comparing Video Encoders</title>
        <published>2025-03-15T00:00:00+00:00</published>
        <updated>2025-03-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Gianni Rosato
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://giannirosato.com/blog/post/comparing-encoders/"/>
        <id>https://giannirosato.com/blog/post/comparing-encoders/</id>
        <content type="html" xml:base="https://giannirosato.com/blog/post/comparing-encoders/">&lt;p&gt;Comparing video encoders involves using synthetic metrics to assess visual quality, helping generate data that can be visualized. Plotting encoder performance in different ways helps determine the best encoder for specific implementation needs.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;div class=&quot;image-container&quot;&gt;
  &lt;picture&gt;
    &lt;img
      src=&quot;https:&#x2F;&#x2F;giannirosato.com&#x2F;static&#x2F;img&#x2F;skygb.avif&quot;
      width=&quot;1536&quot;
      height=&quot;864&quot;
      alt=&quot;Sky&quot;
    &#x2F;&gt;
  &lt;&#x2F;picture&gt;
&lt;&#x2F;div&gt;
&lt;h2 id=&quot;why&quot;&gt;Why?&lt;&#x2F;h2&gt;
&lt;p&gt;Comparing video encoders isn&#x27;t hard; in fact, it is usually quite easy. However,
it is very often done incorrectly.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s a bit of an oversimplification. Comparing video encoders extremely well
&lt;em&gt;is&lt;&#x2F;em&gt; rather difficult, and it is the focus of a lot of impactful research that
aims to produce metrics that can properly assess how good a video looks to our
eyes. The human eye is very complex, and guiding compression algorithms to care
about the human visual system can get very interesting (I wrote a bit about
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;giannirosato.com&#x2F;blog&#x2F;post&#x2F;jpegli-xyb&#x2F;&quot;&gt;perceptual color encoding in JPEG&lt;&#x2F;a&gt;
with the XYB colorspace used in JPEG XL, it can be very cool stuff).&lt;&#x2F;p&gt;
&lt;p&gt;This article is more about what we can do now with the tools that we have,
regardless of the metric we&#x27;re interested in. Many people, including the SVT-AV1
team, make use of &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;codecs.wiki&#x2F;docs&#x2F;metrics&#x2F;PSNR&quot;&gt;PSNR&lt;&#x2F;a&gt;,
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;codecs.wiki&#x2F;docs&#x2F;metrics&#x2F;SSIM&quot;&gt;SSIM&lt;&#x2F;a&gt;, and
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;codecs.wiki&#x2F;docs&#x2F;metrics&#x2F;VMAF&quot;&gt;VMAF&lt;&#x2F;a&gt;, but today we&#x27;re going to be
(mainly) focusing on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;codecs.wiki&#x2F;docs&#x2F;metrics&#x2F;XPSNR&quot;&gt;XPSNR&lt;&#x2F;a&gt;, a
perceptual metric by Fraunhofer HHI that is readily available in FFmpeg 7.1.&lt;&#x2F;p&gt;
&lt;p&gt;Now that we have established the problem space, we can talk about:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The tools we have available to compute metrics in a useful way&lt;&#x2F;li&gt;
&lt;li&gt;How we can use them to compare video encoders&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;tools&quot;&gt;Tools&lt;&#x2F;h2&gt;
&lt;p&gt;A helpful toolbox of various scripts is provided by the &lt;code&gt;metrics&lt;&#x2F;code&gt; utility by the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;psy-ex&quot;&gt;Psychovisual Experts Group&lt;&#x2F;a&gt;. You can find the code
via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;psy-ex&#x2F;metrics&quot;&gt;this GitHub link&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This tool lets us compute some image-focused metrics that we will use for video,
and Weighted XPSNR, a video metric based on XPSNR that includes chroma
information (officially, XPSNR is recommended to be luma-only).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;comparisons&quot;&gt;Comparisons&lt;&#x2F;h2&gt;
&lt;p&gt;There are three &quot;tiers&quot; of comparisons, each involving a bit more data than the
last:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Comparing a single video to another single video&lt;&#x2F;li&gt;
&lt;li&gt;Comparing a series of encoders in terms of compression efficiency&lt;&#x2F;li&gt;
&lt;li&gt;Comparing a series of encoders in terms of overall efficiency&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;We&#x27;ll start with simple two-video comparisons.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;comparing-two-videos&quot;&gt;Comparing Two Videos&lt;&#x2F;h2&gt;
&lt;p&gt;XPSNR is what we call a &lt;em&gt;full-reference distortion metric&lt;&#x2F;em&gt;, which means we
compare a distorted video to its source to get a score. Since we&#x27;re encoding a
source video with a video encoder, we can compare the source and the encode with
&lt;code&gt;scores.py&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;.&#x2F;scores.py [source] [encode]&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;You can also use &lt;code&gt;encode.py&lt;&#x2F;code&gt; to encode the video for you. Either one will give
us various statistics for the metrics we have available to us. Given we used the
GPU for computation of SSIMULACRA2&#x2F;Butteraugli (more on that in a second),
you&#x27;ll get something like this output:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;SSIMULACRA2 scores for every 1 frame:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Average:       75.22395&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Std Deviation: 3.19206&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;10th Pctile:   70.52215&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Butteraugli scores for every 1 frame:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Distance:      0.80522&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Max Distance:  0.97927&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;XPSNR scores:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;XPSNR Y:       34.80490&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;XPSNR U:       38.48910&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;XPSNR V:       37.42110&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;W-XPSNR:       35.61793&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You&#x27;ll notice that this is a lot more than just a single data point. We&#x27;re just
supposed to compare two videos and get a number for how the encode looks, right?
Ideally, yes, but with the imperfect tools we have, we must do the best we can.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;codecs.wiki&#x2F;docs&#x2F;metrics&#x2F;SSIMULACRA2&quot;&gt;SSIMULACRA2&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The average, or the arithmetic mean, is simple; now we know how our frames
look, on average, according to an image metric.&lt;&#x2F;li&gt;
&lt;li&gt;The harmonic mean could pull our average down toward the lower scores present
in our per-frame score dataset that we&#x27;re interpreting. This is theoretically
a bit more informative than the average, as our eyes are going to be more
sensitive to variability in the video&#x27;s fidelity, so we make consistency
desirable by favoring the lower-scoring frames. Note that we haven&#x27;t reported
the harmonic mean with SSIMULACRA2 here, as SSIMULACRA2 is capable of
producing negative scores which are incompatible with the harmonic mean.&lt;&#x2F;li&gt;
&lt;li&gt;The standard deviation tells us more about the video&#x27;s consistency.&lt;&#x2F;li&gt;
&lt;li&gt;The 10th percentile lets us know how our least desirable frames are scoring.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;So, lots of ways to try to make an image fidelity metric useful for video.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;google&#x2F;butteraugli&quot;&gt;Butteraugli&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The way we use Butteraugli in &lt;code&gt;metrics&lt;&#x2F;code&gt;, we use 3pnorm, which weighs and
averages certain parts of the frame, leaning toward more noticeable differences.
So for our use case:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The &quot;Distance&quot; is the average of per-frame 3pnorm scores&lt;&#x2F;li&gt;
&lt;li&gt;The &quot;Max Distance&quot; is the maximum 3pnorm score we saw in a frame&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;And finally, Weighted XPSNR, or W-XPSNR. This is kind of simple:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Y XPSNR is &quot;real&quot; XPSNR, luma-only&lt;&#x2F;li&gt;
&lt;li&gt;U &amp;amp; V XPSNR are for chroma&lt;&#x2F;li&gt;
&lt;li&gt;W-XPSNR extrapolates mean square error from each of the three scores and
computes a weighted average favoring luma, then computes back to the dB units
that PSNR-derived metrics (and others) use for reporting fidelity.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;So, Weighted XPSNR is just a weighted average for luma and chroma scores that
aims to fairly favor luma since that is what our eyes care most about.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;comparing-compression-efficiency&quot;&gt;Comparing Compression Efficiency&lt;&#x2F;h2&gt;
&lt;p&gt;Now we have scores for one video. But, what size is it? How does it compare to
another video from another encoder that&#x27;s a slightly different size with
slightly different scores? You can interpret this subjectively, like saying your
1.74MB video at XPSNR 34.03 from Encoder A &lt;em&gt;feels&lt;&#x2F;em&gt; like a better option than a
1.81MB video that scores 34.21 from Encoder B, but how can we know for sure?&lt;&#x2F;p&gt;
&lt;p&gt;The best way we can do this is by looking at a curve that plots size-to-score
for a series of clips, which is meant to allow us to see which encoder (or
configuration) achieves the best compression efficiency.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s a plot comparing various SVT-AV1 speed settings:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;compression_efficiency.svg&quot; alt=&quot;SVT-AV1 Speed Plot&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;You can see that despite the fact that Preset 4 &amp;amp; Preset 2 produce smaller files
at each CRF level, they are not the most efficient presets, because Preset 0
displays the best compression efficiency according to the curve. Each one of
these curves came from an invocation of &lt;code&gt;stats.py&lt;&#x2F;code&gt; that provided us with the
data we wanted.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s an example of how to use &lt;code&gt;stats.py&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;.&#x2F;stats.py \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-i source.mkv \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-q &amp;quot;20 21 24 26 30&amp;quot; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-o .&#x2F;stats.csv \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-g 4 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;svtav1 -- --preset 8 --tune 2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This encodes &lt;code&gt;source.mkv&lt;&#x2F;code&gt; at 5 CRF values, then outputs the results to
&lt;code&gt;stats.csv&lt;&#x2F;code&gt; which include metrics and encode time. We use 4 GPU threads, and we
pass a couple of options to SVT-AV1.&lt;&#x2F;p&gt;
&lt;p&gt;We picked our 5 CRF values by choosing our bounds and the number of values we
want, according to a formula (in Python):&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;min_q + (max_q - min_q) * ((step &#x2F; (q_steps - 1)) ** 1.5) for step in range(q_steps)&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Rounding our results to integers (necessary with current SVT-AV1) gave us 5 CRF
values between 20 and 30, according to my input. We use this formula to focus
more of our data points on higher fidelity encodes, where the difference in
filesize may be larger for smaller differences in fidelity. This is more helpful
when working with much higher fidelity than we care about here, but it is a good
thing to remember, because we want a curve with less data points to look more
like one with a greater number of data points.&lt;&#x2F;p&gt;
&lt;p&gt;Now, we can compare encoders by generating multiple curves. We have the data,
and we can use &lt;code&gt;plot.py&lt;&#x2F;code&gt; with our data inputs for a simple plot.&lt;&#x2F;p&gt;
&lt;p&gt;For a hobbyist use case, this may be a fine place to stop. If it encodes in
reasonable time, and it is closer to the upper left on the curve, it may satisfy
you to use the more compression efficient encoder. But, what about at production
scale, where you care more about time?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;comparing-overall-efficiency&quot;&gt;Comparing Overall Efficiency&lt;&#x2F;h2&gt;
&lt;p&gt;Before moving on, consider what we need for this graph:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;A value representing the metric difference between two curves&lt;&#x2F;li&gt;
&lt;li&gt;A value representing the time difference between the encoder&#x2F;configuration
used for each curve&lt;&#x2F;li&gt;
&lt;li&gt;A graph that compares these two for various steps on a curve, which we can use
to compare other curves for other encoders&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;That final encoder curve describes an encoder&#x27;s overall efficiency according to
a given metric. Now, let&#x27;s explore how to gather each value.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;BD-Rate&lt;&#x2F;strong&gt; (Bjontegaard Delta Rate) is a way to compare the efficiency of two
curves. It answers the question: &quot;For the same quality level, how much more or
less data does method B need compared to method A?&quot;&lt;&#x2F;p&gt;
&lt;p&gt;If you stopped at the end of the previous section and ran &lt;code&gt;plot.py&lt;&#x2F;code&gt;, you&#x27;d
notice that it provides BD-Rate numbers for each stats file you provided it,
relative to the first stats file. So, if the BD-Rate is -20% between A &amp;amp; B, it
means the second method needs 20% less data to achieve the same quality as the
first method, which is a good improvement.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;plot.py&lt;&#x2F;code&gt; writes these BD-Rate values to a CSV, along with the average time
computed across the encodes in each stats file.&lt;&#x2F;p&gt;
&lt;p&gt;Now, your next &lt;code&gt;plot.py&lt;&#x2F;code&gt; invocation (for the next stats files belonging to the
next encoder) &lt;em&gt;needs&lt;&#x2F;em&gt; to use the &lt;em&gt;previous&lt;&#x2F;em&gt; worst stats file as the first
argument in order to compute BD-Rates relative to the encoder you&#x27;re now
comparing against. You&#x27;ll get another CSV output.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s an example result, comparing SVT-AV1 v3.0.0 to SVT-AV1-PSY v2.3.0-B:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;encoder_efficiency.svg&quot; alt=&quot;SVT-AV1 BD-Rate Plot&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;BD-Rates computed relative to SVT-AV1-PSY v2.3.0-B&#x27;s Preset 10, which is why
that data point has a BD-Rate of 0%.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;You can see that SVT-AV1 v3.0.0 is able to produce smaller BD-Rates relative to
v2.3.0-B in less time, so it would be considered the more efficient encoder,
according to W-XPSNR. Again, even though Preset 10 in SVT-AV1 v3.0.0 has a worse
BD-Rate than Preset 10 in SVT-AV1-PSY v2.3.0-B, the time difference means that
along the curve SVT-AV1 v3.0.0 is more efficient overall.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;That&#x27;s all for now. I hope you found this blog post helpful in understanding how
to compare encoders, because it is a crucial part of encoder development and can
help you make an informed decision about which encoder to use for your specific
needs. Happy encoding!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;sponsor-me-on-github-sponsors&quot;&gt;Sponsor Me on GitHub Sponsors&lt;&#x2F;h3&gt;
&lt;p&gt;Help support my open source efforts - a little goes a long way!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;sponsors&#x2F;gianni-rosato?o=esc&quot;&gt;Sponsor&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>The End of SVT-AV1-PSY</title>
        <published>2024-11-26T00:00:00+00:00</published>
        <updated>2024-11-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Gianni Rosato
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://giannirosato.com/blog/post/end-of-psy/"/>
        <id>https://giannirosato.com/blog/post/end-of-psy/</id>
        <content type="html" xml:base="https://giannirosato.com/blog/post/end-of-psy/">&lt;p&gt;SVT-AV1-PSY, a community fork of the SVT-AV1 video encoder, was created to enhance perceptual quality and ease of use for AV1 encoding. I co-created and currently maintain the project. Our new goal is to merge our features into SVT-AV1.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;h2 id=&quot;why&quot;&gt;Why?&lt;&#x2F;h2&gt;
&lt;p&gt;If you aren&#x27;t familiar with codecs or AV1, I&#x27;m not sure why you&#x27;re on my site.
Nevertheless, here&#x27;s some background:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;AV1 is a modern video codec, meant to make videos smaller and better looking&lt;&#x2F;li&gt;
&lt;li&gt;Unlike H.264 and H.265, AV1 is royalty-free, meaning you don&#x27;t have to pay to
use it&lt;&#x2F;li&gt;
&lt;li&gt;There are three very popular AV1 encoders that can be used to produce AV1
video: libaom, rav1e, and SVT-AV1&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;encoders&quot;&gt;Encoders&lt;&#x2F;h3&gt;
&lt;p&gt;There are three main AV1 encoders, as mentioned above: libaom from Google, rav1e
from xiph, and SVT-AV1 from Intel, Netflix, Meta, and others. Historically,
SVT-AV1 has been noteworthy for its impressive speed and parallelism, while
libaom was known for its great quality and class-leading still image performance
(AV1 has an image format associated with it called AVIF).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;forks&quot;&gt;Forks&lt;&#x2F;h3&gt;
&lt;p&gt;The libaom team at Google is largely insulated from community influence; this
means a random individual submitting some new code has a low chance of it being
accepted. This led to a community culture of forking, with the first noteworthy
fork being &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;BlueSwordM&#x2F;aom-av1-psy&quot;&gt;aom-av1-psy&lt;&#x2F;a&gt; by
BlueSwordM.&lt;&#x2F;p&gt;
&lt;p&gt;The goal of this fork was to enhance libaom (aomenc) with better perceptual
quality. This means that the encoder would be able to make better decisions
about what to keep and what to throw away according to our eyes, instead of
chasing metric scores that, while important, don&#x27;t always reflect the
characteristics of the human visual system. BlueSwordM retired aom-av1-psy in
September of 2022 to focus on other work.&lt;&#x2F;p&gt;
&lt;p&gt;Perceptual fidelity is not a new concept; encoders like x264 and x265 have a
number of perceptually focused features like &quot;psy-rd&quot; and &quot;psy-trellis&quot; (you can
read more about these elsewhere). x264 and x265 are very mature encoders for
older formats, so mainstream AV1 encoders haven&#x27;t had the same level of
development in this area. This helped aom-av1-psy become very popular among AV1
enthusiasts, and its legacy lives on in the form of two newer forks:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;codecs.wiki&#x2F;docs&#x2F;encoders&#x2F;aom-av1-lavish&quot;&gt;aom-av1-lavish&lt;&#x2F;a&gt; (retired as
of June 2024) and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;codecs.wiki&#x2F;docs&#x2F;encoders&#x2F;aom-psy101&quot;&gt;aom-psy101&lt;&#x2F;a&gt;
(still maintained at the time of writing).&lt;&#x2F;p&gt;
&lt;p&gt;Usually, forks were maintained by a single individual – all of the forks I&#x27;ve
mentioned so far have been or currently are. Coding isn&#x27;t the only thing that
goes into maintaining a project, so a solo dev is often responsible for
documentation, community management, releases, bug reports, feature requests,
testing, and more. This puts pressure on a singular developer to manage a rather
complex project with a lot of eyes on it, which is difficult and has often led
to project maintenance issues.&lt;&#x2F;p&gt;
&lt;p&gt;All of this had been swirling around in my head at the time, because compiling
aom-av1-lavish by collecting the latest experimental patches floating around
Discord was kind of difficult and I was keenly interested in what a new project
isolated from the aom-av1-psy legacy might look like. This brings us to
SVT-AV1-PSY.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-it-started&quot;&gt;How It Started&lt;&#x2F;h2&gt;
&lt;p&gt;I had entertained little discussions with friends about what kind of potential
SVT-AV1 might have in 2023. The community often doesn&#x27;t care much about speed
versus perceptual quality, so many weren&#x27;t that invested in seeing what could
come out of SVT-AV1 as the aomenc forks were (and have always been) quite good.
The rav1e encoder already had a perceptual lean, so many thought the path
forward was clearly to invest as much community time as possible into rav1e. I
thought this would be a herculean effort that would likely not pay off, and I
was convinced that SVT-AV1 had something to offer with the incredible year over
year speed improvements we kept on seeing. However, I never once thought of
forking.&lt;&#x2F;p&gt;
&lt;p&gt;Coincidentally, at this point
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;AOMediaCodec&#x2F;SVT-AV1&#x2F;-&#x2F;issues&#x2F;2105&quot;&gt;a perceptual quality-related issue&lt;&#x2F;a&gt;
appeared in the SVT-AV1 GitLab repository. This piqued my attention immediately,
and the initial results that Variance Boost was producing looked extremely
promising. While the author, Julio Barba, was sharing patch files to communicate
with the dev team, excited community members were manually patching the source
code and building the encoder to test it out. I was one of those people, and I
was very impressed with the results, but the patching workflow was cumbersome
and reminded me of my current woes with aom-av1-lavish. So, I hastily put
together a GitHub repository with the patch applied to the SVT-AV1 source code
alongside some minor tweaks from an SVT-AV1 fork maintained by BlueSwordM. I
called it SVT-AV1-PSY, named after aom-av1-psy.&lt;&#x2F;p&gt;
&lt;p&gt;Julio ended up appearing across the various AV1 Discord servers run by community
members, and BlueSwordM was able to get in touch with me about how I was
integrating and testing his work. I offered to maintain the project while Julio
and BlueSwordM would act as the primary contributors, so they could focus on
code while I dealt with everything else. We made our first release in February
of 2024, and the project began to gain traction from there.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-it-s-going&quot;&gt;How It&#x27;s Going&lt;&#x2F;h2&gt;
&lt;p&gt;Although I didn&#x27;t intend to do much programming to start, it was too
tantalizing, and I had just begun learning C in October of 2023 so I thought I
would try my hand. In the end, everyone on the dev team ended up contributing
code, and we also attracted the attention of Clybius – the maintainer of
aom-av1-lavish – who stopped working on his aom-av1-psy fork to join us. Trix, a
noteworthy encoder known for their work with animation, also joined the team and
provided us with a lot of valuable feedback through their extensive testing in
the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;codecs.wiki&#x2F;blog&#x2F;svt-av1-third-deep-dive&quot;&gt;SVT-AV1 Deep Dive&lt;&#x2F;a&gt; blog
series.&lt;&#x2F;p&gt;
&lt;p&gt;I could not have asked for a better group of people to work with. The amount I
have learned through them, especially Julio, has been immeasurable, and I
wouldn&#x27;t be nearly as competent as I am now without them. I am very grateful for
their time and effort, and I am proud of what we have accomplished together.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s a short list of what has been achieved with SVT-AV1-PSY:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;We&#x27;ve authored over 20 highly effective features, aimed at improving
perceptual quality and ease of use&lt;&#x2F;li&gt;
&lt;li&gt;We have produced a total of 13 releases at the time of writing, introducing
our own release cadence to help push finished features to users faster&lt;&#x2F;li&gt;
&lt;li&gt;We&#x27;ve built a community of over 500 members on the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;discord.gg&#x2F;bbQD5MjDr3&quot;&gt;AV1 for Dummies&lt;&#x2F;a&gt; Discord server, where
discussion mainly revolves around SVT-AV1-PSY and related projects&lt;&#x2F;li&gt;
&lt;li&gt;We&#x27;ve racked up over 250 stars on GitHub at the time of writing (more than
SVT-AV1! Though that&#x27;s likely due to GitHub vs GitLab)&lt;&#x2F;li&gt;
&lt;li&gt;We have a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;svt-av1-psy.com&#x2F;&quot;&gt;website&lt;&#x2F;a&gt; for showing off our results,
where we notably demonstrate our
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;svt-av1-psy.com&#x2F;avif&#x2F;&quot;&gt;AVIF performance&lt;&#x2F;a&gt;. At its inception, AVIF with
SVT-AV1-PSY was the best in the world by a wide margin; libaom is competitive
now due to the introduction of our breakthrough changes into libaom&lt;&#x2F;li&gt;
&lt;li&gt;We&#x27;ve recently forged a relationship with the incredibly responsive and
talented mainline SVT-AV1 team, and we are working to get our features merged
into the mainline encoder so everyone can enjoy them&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;SVT-AV1-PSY is the most popular community fork of a major AV1 encoder, and I am
very proud of what we have accomplished. I am excited to see where we go from
here, and I am grateful for the opportunity to have worked with such talented
and dedicated individuals. I am also grateful for the community that has
supported us, and I hope we can continue to provide you even more exciting
compression technology in the future.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-it-ended&quot;&gt;How It Ended (?)&lt;&#x2F;h2&gt;
&lt;p&gt;Full disclosure, SVT-AV1-PSY isn&#x27;t &quot;ending,&quot; per se – that was just some
clickbait. We are in the process of getting as many of our features as possible
merged into the mainline SVT-AV1 encoder, and we are working closely with the
mainline team to see that through. What&#x27;s happening next is that we are slowly
rolling back our involvement in SVT-AV1-PSY.&lt;&#x2F;p&gt;
&lt;p&gt;Due to life, work, and other extenuating circumstances, both myself and Julio
are going to be taking a step back from heavily contributing to the project for
the time being. I am still going to be around to help out as the maintainer, but
we are going to be less active in the day-to-day operations of the project. As
of January 2025, I&#x27;m going to be stepping back from authoring perceptual quality
and performance changes for reasons I will disclose soon; my involvement will
mostly consist of maintaining the project and helping out with the mainline
integration going forward through December.&lt;&#x2F;p&gt;
&lt;p&gt;Hopefully this isn&#x27;t a disappointing announcement; I think it is incredibly
exciting that the community is in a position to contribute to mainline SVT-AV1
directly, and a fork only serves to duplicate efforts on that front. SVT-AV1-PSY
was always meant to be a temporary project, and started as a playground for
testing new features that may not have been production-ready immediately. I am
very proud of what we have accomplished, and I am excited to see what the future
holds for SVT-AV1.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;&#x2F;h2&gt;
&lt;p&gt;I am a big believer in the idea that breakthrough compression performance
enables brand-new applications for multimedia tech, and compression doesn&#x27;t
solely exist to make video files smaller for the benefit of the corporate bottom
line. Saying good compression only exists to save X% bandwidth is akin to saying
post production only exists to drive X% more sales; post production and visual
art are creative and expressive, and compression is a tool to enable that
creativity in ways that we likely can&#x27;t imagine yet.&lt;&#x2F;p&gt;
&lt;p&gt;I am humbled to be a part of a community that is pushing the boundaries of what
is possible with video compression, and to have contributed my drop in the
bucket with SVT-AV1-PSY. I am excited to see where things go from here, and I&#x27;m
hopefully not leaving the multimedia compression world anytime soon.&lt;&#x2F;p&gt;
&lt;p&gt;Thank you to Julio, BlueSwordM, and Clybius for all of your help, and thank you
to the community for your support. I am excited to see what the future holds!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;sponsor-me-on-github-sponsors&quot;&gt;Sponsor Me on GitHub Sponsors&lt;&#x2F;h3&gt;
&lt;p&gt;Help support my open source efforts - a little goes a long way!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;sponsors&#x2F;gianni-rosato?o=esc&quot;&gt;Sponsor&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Stupidly Tiny Images</title>
        <published>2024-10-07T00:00:00+00:00</published>
        <updated>2024-10-07T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Gianni Rosato
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://giannirosato.com/blog/post/88x31/"/>
        <id>https://giannirosato.com/blog/post/88x31/</id>
        <content type="html" xml:base="https://giannirosato.com/blog/post/88x31/">&lt;p&gt;How tiny can very tiny banner images get?&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;h2 id=&quot;why&quot;&gt;Why?&lt;&#x2F;h2&gt;
&lt;p&gt;My friend &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;hayl.in&#x2F;&quot;&gt;Haylin&lt;&#x2F;a&gt; introduced me to
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;matdoes.dev&#x2F;buttons&quot;&gt;88x31 images&lt;&#x2F;a&gt; and I completely fell in love with
them. She told me that this weekend I should add some to my site, so I ended up
making a couple of custom ones and peering with friends on this site&#x27;s home
page. Fun times.&lt;&#x2F;p&gt;
&lt;p&gt;However, there was a catch. It bothered me that these images were all PNGs and
(&lt;em&gt;shudders&lt;&#x2F;em&gt;) GIFs straight out of the 1990s &amp;amp; early 2000s. I get that&#x27;s the
aesthetic, but I don&#x27;t think it is paramount that we stick with older formats.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve been involved in the multimedia compression scene donating my time as an
AV1 framework and encoder developer for a bit now, and I&#x27;d consider myself to be
somewhat well-versed in digital multimedia and data compression. Recently in the
image compression space, I&#x27;ve been focused on
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;svt-av1-psy.com&#x2F;avif&#x2F;&quot;&gt;making lossy AVIF performant on larger, mostly photographic images&lt;&#x2F;a&gt;
that you&#x27;d commonly find on the Web. The 88x31s are thus super far out of my
comfort zone, so I was excited to see what I could accomplish with
&quot;off-the-shelf&quot; tools in a day or two.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;level-1-png&quot;&gt;Level 1: PNG&lt;&#x2F;h2&gt;
&lt;p&gt;The easiest question to ask ourselves here is &lt;strong&gt;&quot;How can we make the existing
88x31s smaller without any compromises whatsoever?&quot;&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The answer is simple: PNG optimization. PNG is (in my opinion) an extremely
bloated spec compared to formats like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;qoiformat.org&#x2F;&quot;&gt;QOI&lt;&#x2F;a&gt; that achieve
the same goals at much lower complexity. The benefit of this is that there is
usually a lot of room for improvement in any standard PNG bitstream you
encounter online.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m going to ignore animated GIFs and APNG&#x27;s for now and just look as still
images - we can start with twelve 88x31s
(&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;files.catbox.moe&#x2F;mbzslh.tzst&quot;&gt;archive&lt;&#x2F;a&gt;) pulled straight from
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;matdoes.dev&#x2F;buttons&quot;&gt;matdoes.dev&#x2F;buttons&lt;&#x2F;a&gt;. We&#x27;ll cover animations
later.&lt;&#x2F;p&gt;
&lt;p&gt;We have ourselves a combined dataset of GIFs and PNGs here.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;originals&#x2F;9.png: 1339 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;originals&#x2F;4.gif: 3058 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;originals&#x2F;5.gif: 845 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;originals&#x2F;7.gif: 2206 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;originals&#x2F;12.png: 389 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;originals&#x2F;3.gif: 1099 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;originals&#x2F;linux_2.png: 1052 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;originals&#x2F;6.png: 825 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;originals&#x2F;firefox_now.gif: 2355 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;originals&#x2F;linux_1.gif: 583 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;originals&#x2F;11.gif: 667 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;originals&#x2F;10.gif: 2523 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Total size: 16941 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Number of items: 12&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Average size: 1411 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We&#x27;re weighing in at an average of 1411 bytes per image. That&#x27;s not too bad, but
we can do better even with the most basic, low-effort PNG encoding.&lt;&#x2F;p&gt;
&lt;p&gt;Using &lt;code&gt;magick -quality 0&lt;&#x2F;code&gt; we can get some PNGs out of the static GIFs. Here&#x27;s
the effect on the filesize:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_unoptim&#x2F;9.png: 1339 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_unoptim&#x2F;12.png: 389 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_unoptim&#x2F;linux_1.png: 721 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_unoptim&#x2F;firefox_now.png: 2352 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_unoptim&#x2F;11.png: 752 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_unoptim&#x2F;linux_2.png: 1052 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_unoptim&#x2F;10.png: 2458 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_unoptim&#x2F;4.png: 3137 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_unoptim&#x2F;5.png: 957 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_unoptim&#x2F;7.png: 1621 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_unoptim&#x2F;6.png: 825 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_unoptim&#x2F;3.png: 1213 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Total size: 16816 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Number of items: 12&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Average size: 1401 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Even low effort, completely unoptimized PNG beats GIF here on average. GIF
sucks, please don&#x27;t use GIF.&lt;&#x2F;p&gt;
&lt;p&gt;Now let&#x27;s see what happens if we use &lt;code&gt;ect -9 --mt-deflate -strip&lt;&#x2F;code&gt; on the PNGs:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;9.png: 1326 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;12.png: 369 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;linux_1.png: 458 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;firefox_now.png: 2050 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;11.png: 495 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;linux_2.png: 1029 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;10.png: 2182 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;4.png: 2765 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;5.png: 706 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;7.png: 1316 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;6.png: 808 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;3.png: 1006 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Total size: 14510 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Number of items: 12&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Average size: 1209 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It doesn&#x27;t seem like a crazy difference, but considering how little flexibility
we have with such tiny images, it is pretty impressive that we&#x27;re looking at the
same exact format and the same image data.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s take it a step further with &lt;code&gt;ect -9999 -strip&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;9.png: 1324 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;12.png: 368 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;linux_1.png: 454 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;firefox_now.png: 2048 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;11.png: 492 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;linux_2.png: 1027 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;10.png: 2179 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;4.png: 2761 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;5.png: 706 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;7.png: 1311 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;6.png: 806 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;png_ect9&#x2F;3.png: 1003 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Total size: 14479 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Number of items: 12&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Average size: 1206 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Look at that, a &lt;em&gt;teensy&lt;&#x2F;em&gt; bit more off. This is pretty much right up against the
ceiling of what PNG can do for us without lossy techniques like palette
compression.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;png-summary&quot;&gt;PNG Summary&lt;&#x2F;h3&gt;
&lt;p&gt;TL;DR, convert your GIFs to PNGs and run them through
&lt;code&gt;ect -9 --mt-deflate -strip&lt;&#x2F;code&gt;. With images this tiny, it is super fast, and
you&#x27;ll shave off some bits at zero cost to compatibility or fidelity. But what
if we want to go further?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;level-2-lossless-webp&quot;&gt;Level 2: Lossless WebP&lt;&#x2F;h2&gt;
&lt;p&gt;I know WebP has a bit of a reputation on the Web as having been the format that
was shoved down our throats by Google and didn&#x27;t really ever become a new
standard for anything. I&#x27;ve talked in the past about lossy WebP being barely
competitive with JPEG despite being far newer, but overall I can&#x27;t say I dislike
WebP as much as the next person. There are some important things to consider
with WebP:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;WebP is mostly talked about as a lossy format, but it can be lossless.&lt;&#x2F;li&gt;
&lt;li&gt;WebP&#x27;s lossless mode is really impressive, and consistently outperforms the
most optimized PNGs.&lt;&#x2F;li&gt;
&lt;li&gt;WebP&#x27;s animation support is pretty complete, especially compared to the mess
that is GIF and the relatively obscure APNG. Discord recently supports
animated WebP by default.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I&#x27;m going to go in guns blazing with max-effort bruteforce WebP lossless via
&lt;code&gt;cwebp&lt;&#x2F;code&gt;. I have &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gianni-rosato&#x2F;minify&quot;&gt;a script&lt;&#x2F;a&gt; that tries
every encoding effort level from 0 to 9 and picks the smallest one, as effort 9
isn&#x27;t always the smallest for some reason. Here&#x27;s the result:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;webp_l&#x2F;5.webp: 670 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;webp_l&#x2F;linux_2.webp: 700 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;webp_l&#x2F;9.webp: 1114 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;webp_l&#x2F;4.webp: 2456 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;webp_l&#x2F;3.webp: 954 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;webp_l&#x2F;firefox_now.webp: 1724 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;webp_l&#x2F;12.webp: 296 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;webp_l&#x2F;11.webp: 456 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;webp_l&#x2F;10.webp: 1866 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;webp_l&#x2F;7.webp: 1182 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;webp_l&#x2F;linux_1.webp: 370 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;webp_l&#x2F;6.webp: 744 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Total size: 12532 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Number of items: 12&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Average size: 1044 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;webp-summary&quot;&gt;WebP Summary&lt;&#x2F;h3&gt;
&lt;p&gt;An additional meaningful improvement with a bare compatibility cost and &lt;em&gt;still&lt;&#x2F;em&gt;
no loss of fidelity whatsoever. This is the power of WebP&#x27;s lossless mode. It&#x27;s
almost as significant as the gap between PNG and GIF. WebP is &lt;em&gt;worth using&lt;&#x2F;em&gt; for
something like this. It is fair to have hesitation about adopting a slightly
less common format, but WebP is supported across all major browsers. If your
favorite app doesn&#x27;t support WebP, it isn&#x27;t WebP&#x27;s fault at this point; it is a
royalty free format that has its merits, and shouldn&#x27;t be bastardized an account
of its shaky introduction.&lt;&#x2F;p&gt;
&lt;p&gt;But what if you don&#x27;t care &lt;em&gt;at all&lt;&#x2F;em&gt; about compatibility, and you just want the
smallest possible file size without fidelity loss?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;step-3-jpeg-xl&quot;&gt;Step 3: JPEG XL&lt;&#x2F;h2&gt;
&lt;p&gt;JPEG XL is an incredibly cool format. I have plenty of additional JPEG XL
coverage on my blog, and sites like the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;jpegxl.info&#x2F;&quot;&gt;community-run JPEG XL site&lt;&#x2F;a&gt; exist if you&#x27;d like more
information, but the TL;DR is that JPEG XL is pretty much great at everything.
It is a royalty-free, modern format that is designed to be the best of all
worlds, and it is a format that I am very excited about.&lt;&#x2F;p&gt;
&lt;p&gt;Unfortunately, Google&#x27;s Chromium team notoriously rejected JPEG XL from the
Chromium browser engine, so it is not supported in Chrome or any browser that
uses Chromium. This is a huge bummer. JPEG XL is supported in Safari and
throughout the Apple ecosystem, as well as in browsers like Waterfox and
Thorium, so it is fair game to use JPEG XL images on your site with an
appropriate fallback.&lt;&#x2F;p&gt;
&lt;p&gt;For our purposes, we&#x27;re just going to look at JPEG XL&#x27;s lossless compression
with &lt;code&gt;cjxl 0.11.0&lt;&#x2F;code&gt;. We&#x27;ll be using the hidden super high effort mode via
&lt;code&gt;cjxl -d 0 -e 11 --allow_expert_options&lt;&#x2F;code&gt; to see how low we can go:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_l&#x2F;11.jxl: 417 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_l&#x2F;10.jxl: 2394 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_l&#x2F;linux_2.jxl: 740 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_l&#x2F;12.jxl: 279 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_l&#x2F;linux_1.jxl: 369 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_l&#x2F;firefox_now.jxl: 2505 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_l&#x2F;9.jxl: 1216 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_l&#x2F;3.jxl: 846 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_l&#x2F;7.jxl: 1244 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_l&#x2F;6.jxl: 773 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_l&#x2F;4.jxl: 3403 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_l&#x2F;5.jxl: 590 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Total size: 14776 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Number of items: 12&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Average size: 1231 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;...Hm. Not exactly what was promised.&lt;&#x2F;p&gt;
&lt;p&gt;The reality is that JPEG XL&#x27;s lossless mode is best with larger images, and
images with &amp;gt;8 BPC. Especially for HDR, JPEG XL significantly outperforms high
bit depth PNG, and WebP doesn&#x27;t support &amp;gt;8 BPC. For tiny images like this, it is
clearly not the best choice given what we&#x27;re seeing here.&lt;&#x2F;p&gt;
&lt;p&gt;The one saving grace we have at our disposal with JPEG XL is the super useful
tools it makes available to us to do &lt;em&gt;slightly lossy&lt;&#x2F;em&gt; compression. We can do
this by lowering the bit depth the encoder uses, or allowing JPEG XL&#x27;s modular
lossless compression to utilize a lossy palette. PNG and WebP are capable of
similar tricks as well (like PNG&#x27;s palette compression mode and WebP&#x27;s &quot;near
lossless&quot; compression), but JPEG XL does it better via cjxl in my opinion.&lt;&#x2F;p&gt;
&lt;p&gt;Through adding &lt;code&gt;--modular_lossy_palette --modular_palette_colors=0&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_lossy&#x2F;11.jxl: 638 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_lossy&#x2F;10.jxl: 1677 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_lossy&#x2F;linux_2.jxl: 946 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_lossy&#x2F;12.jxl: 281 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_lossy&#x2F;linux_1.jxl: 608 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_lossy&#x2F;firefox_now.jxl: 1589 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_lossy&#x2F;9.jxl: 1248 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_lossy&#x2F;3.jxl: 996 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_lossy&#x2F;7.jxl: 1403 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_lossy&#x2F;6.jxl: 773 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_lossy&#x2F;4.jxl: 1583 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_lossy&#x2F;5.jxl: 746 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Total size: 12488 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Number of items: 12&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Average size: 1040 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is our best result yet, albeit a lossy one. Visual inpection will allow you
determine if this is too lossy for you, in which case it may be best to stick
with WebP.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;jpeg-xl-summary&quot;&gt;JPEG XL Summary&lt;&#x2F;h3&gt;
&lt;p&gt;Overall, JPEG XL results seem mixed at this scale, and we&#x27;ve been driven to
resorting to slightly lossy compression which isn&#x27;t a great showing for the
codec. However, if you look closely, there are some images that are the smallest
with JPEG XL...&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_l&#x2F;11.jxl: 417 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_l&#x2F;12.jxl: 279 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_l&#x2F;linux_1.jxl: 369 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_l&#x2F;3.jxl: 846 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_l&#x2F;6.jxl: 773 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jxl_l&#x2F;5.jxl: 590 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Exactly half the pictures were smallest with cjxl. The reference library for
JPEG XL (called libjxl) is still pre-1.0, so maybe the bitstream has latent
expressivity in its modular lossless mode that we&#x27;ve yet to see realized. The
creators of the format have mentioned that peak bitstream expressivity has yet
to be reached with libjxl especially as it pertains to lossy encoding, so only
time will tell.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;mixing-codecs&quot;&gt;Mixing Codecs&lt;&#x2F;h2&gt;
&lt;p&gt;By mixing our favorable results from WebP and JPEG XL, we minify our 88x31s to
the maximum degree:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mix&#x2F;11.jxl: 417 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mix&#x2F;12.jxl: 279 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mix&#x2F;linux_2.webp: 700 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mix&#x2F;9.webp: 1114 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mix&#x2F;linux_1.jxl: 369 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mix&#x2F;4.webp: 2456 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mix&#x2F;firefox_now.webp: 1724 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mix&#x2F;3.jxl: 846 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mix&#x2F;10.webp: 1866 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mix&#x2F;6.jxl: 773 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mix&#x2F;5.jxl: 590 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mix&#x2F;7.webp: 1182 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Total size: 12316 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Number of items: 12&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Average size: 1026 bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This gives us, with no fidelity loss, our smallest average size yet. This is a
great result, and it shows that inspecting your outputs with compression is
almost always worth doing.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;animation&quot;&gt;Animation&lt;&#x2F;h2&gt;
&lt;p&gt;For animated images, we have three major options:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;GIF&lt;&#x2F;li&gt;
&lt;li&gt;APNG&lt;&#x2F;li&gt;
&lt;li&gt;Animated WebP&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I&#x27;ll spare you the misery - Animated WebP is the best choice for animated
images. It&#x27;s the smallest, and it&#x27;s supported in all major browsers. GIF is
supported everywhere, but it&#x27;s the largest.&lt;&#x2F;p&gt;
&lt;p&gt;APNG encoding with FFmpeg: &lt;code&gt;ffmpeg -y -i {} -plays 0 {.}.apng&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Animated WebP encoding with FFmpeg:
&lt;code&gt;ffmpeg -y -i {} -pix_fmt bgra -c:v libwebp_anim -lossless 1 {.}.webp&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Using &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;upallnight.neocities.org&#x2F;anibanner.gif&quot;&gt;this image&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;du&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt; -h&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt; upallnight.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt;*&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;104K&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;	upallnight.apng&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;104K&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;	upallnight.gif&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;72K&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;	upallnight.webp&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In a bit more detail:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt;stat&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt; -f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D77;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;%z&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D77;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt; upallnight.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt;*&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;106127&lt;&#x2F;span&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;  # APNG&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;106384&lt;&#x2F;span&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;  # GIF&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;70712&lt;&#x2F;span&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;   # WebP&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I know single image comparisons are flawed, but I&#x27;ve done more than just this
one and the results are consistent. APNG can often make GIFs a bit smaller, but
WebP crushes it here. You can optimize APNG further, but there isn&#x27;t (usually) a
compatibility benefit - animated WebP is the way to go.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;my-approach&quot;&gt;My Approach&lt;&#x2F;h2&gt;
&lt;p&gt;The 88x31s on my homepage are a combination of a number of different coding
techniques. I prioritized getting the maximum fidelity out of JPEG XL, and used
lossless WebP as a fallback. My personal 88x31 is a lossy palette JPEG XL, while
the JPEG XL community button is a lossy VarDCT JPEG XL. I quantized the bit
depth more aggressively with some images at very little fidelity loss, and
others I was able to use full lossless. Doing each of your images individually
is a great way to get the best results, but can be time consuming.&lt;&#x2F;p&gt;
&lt;p&gt;Everything animated is animated WebP, as animated JPEG XL isn&#x27;t supported on
Safari so the majority of people seeing my JPEG XL animations wouldn&#x27;t see
anything but a still frame.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;My final thoughts:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;For lossless compression, &lt;strong&gt;WebP consistently outperformed PNG and GIF&lt;&#x2F;strong&gt;,
offering meaningful size reductions at a minimal compatibility tradeoff.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;JPEG XL showed mixed results at this small scale&lt;&#x2F;strong&gt; but was able to achieve
the smallest file sizes for about half the images using lossless mode. Its
slightly lossy palette mode produced the smallest average size overall, but it
is still lossy so you may want to use with caution.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Using WebP for some images and JPEG XL for others yielded the best overall
lossless compression results.&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;For animated images, WebP is clearly superior to both GIF and APNG&lt;&#x2F;strong&gt; in
terms of file size while still being lossless (lossy WebP is quite good, too;
it is a video codec under the hood, after all).&lt;&#x2F;li&gt;
&lt;li&gt;Carefully evaluating each image individually with different codecs and
settings is the key to achieving the best results.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Even for tiny, simple images, modern compression techniques can yield meaningful
improvements over legacy formats. It&#x27;s worth taking the time to experiment with
different codecs and settings to find the best balance of quality and file size
for your images. You can argue it isn&#x27;t practical because the images are already
so tiny, and you&#x27;d probably be right, but this is more about doing it for sport.&lt;&#x2F;p&gt;
&lt;p&gt;So, mess around a bit and see what you can do - it was a lot of fun for me, and
as the &quot;compression guy,&quot; I can&#x27;t be caught dead using unoptimized images
anywhere :)&lt;&#x2F;p&gt;
&lt;p&gt;Peer with me:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #444444;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; href&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D77;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;https:&#x2F;&#x2F;giannirosato.com&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D77;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; target&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D77;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;_blank&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D77;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #444444;&quot;&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;picture&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #444444;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;source&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;      srcset&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D77;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;https:&#x2F;&#x2F;giannirosato.com&#x2F;static&#x2F;images&#x2F;88x31&#x2F;gb82_88x31.jxl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D77;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;      type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D77;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;image&#x2F;jxl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D77;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #444444;&quot;&gt;    &amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #444444;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;img&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;      src&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D77;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;https:&#x2F;&#x2F;giannirosato.com&#x2F;static&#x2F;images&#x2F;88x31&#x2F;gb82_88x31.webp&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D77;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;      alt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D77;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;gianni&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D77;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;      width&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D77;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;88&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D77;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;      height&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D77;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;31&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D77;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #444444;&quot;&gt;    &amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #444444;&quot;&gt;  &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;picture&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #444444;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;sponsor-me-on-github-sponsors&quot;&gt;Sponsor Me on GitHub Sponsors&lt;&#x2F;h3&gt;
&lt;p&gt;Help support my open source efforts - a little goes a long way!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;sponsors&#x2F;gianni-rosato?o=esc&quot;&gt;Sponsor&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>QOI Encoding in Zig</title>
        <published>2024-03-13T00:00:00+00:00</published>
        <updated>2024-03-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Gianni Rosato
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://giannirosato.com/blog/post/qoi-zig/"/>
        <id>https://giannirosato.com/blog/post/qoi-zig/</id>
        <content type="html" xml:base="https://giannirosato.com/blog/post/qoi-zig/">&lt;p&gt;Writing a fast and simple QOI encoder; illustrated with code examples, a spec breakdown, and step by step implementation instructions.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;&lt;em&gt;Note: the code for this project on GitHub has changed, but all of my commentary
here remains valid.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why&quot;&gt;Why?&lt;&#x2F;h2&gt;
&lt;p&gt;I started writing C as my first programming language in October. While I have
other projects labelled under the programming tag on this site, a friend was the
lead developer on both, and I was just becoming familiar with Python; I never
truly learned the concepts until later, as I hadn&#x27;t needed to until I took over
development for those projects. I had dabbled with Bash scripts, but other than
that, I have had very limited programming exposure until recently, though I&#x27;ve
had extensive exposure to technology. Becoming familiar with C quickly led me to
discover Zig in November. Around the same time, I stumbled upon the QOI (Quite
OK Image Format) lossless image format built around speed and simplicity. The
simplicity part was what caught my eye; I&#x27;m a new programmer, and writing a QOI
encoder would simultaneously allow me to get down and dirty with manipulating
data on a low level without getting into the weeds of a more complicated spec
like that of PNG.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the project link, before we get too carried away:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gianni-rosato&#x2F;qoi-enc-zig&quot;&gt;GitHub Link&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;As soon as I got started with Zig, I already felt it was cool; Rust felt (and
continues to feel) intimidating as I was becoming acquainted with C.
&quot;Higher-level&quot; languages don&#x27;t interest me very much.&lt;&#x2F;p&gt;
&lt;p&gt;Full disclosure: I&#x27;m not going to go through &lt;em&gt;everything&lt;&#x2F;em&gt; here, and I may get
some terminology details wrong. Please reach out to correct me if you notice
anything.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;zig&quot;&gt;Zig&lt;&#x2F;h2&gt;
&lt;p&gt;Zig is a programming language designed to be the modern replacement for C.&lt;&#x2F;p&gt;
&lt;p&gt;I won&#x27;t go over every advantage Zig brings to the table over other languages,
but according to
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;ziglang.org&#x2F;learn&#x2F;why_zig_rust_d_cpp&#x2F;&quot;&gt;Zig&#x27;s official site&lt;&#x2F;a&gt;, Zig has
some unique advantages over languages like C++, D, and Rust:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;No hidden control flow or allocations&lt;&#x2F;em&gt;: Zig code is explicit about function
calls and memory allocation, so readability is improved and the programmer
has more control.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;First-class support for no standard library&lt;&#x2F;em&gt;: Zig can be used without any
standard library, making it friendly for bare-metal and high-performance
development.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;A portable language for libraries&lt;&#x2F;em&gt;: Zig aims to be a new portable language
for libraries, interacting well with C code while introducing safety and
better language design.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;A package manager and build system&lt;&#x2F;em&gt;: Zig comes with a capable build system
and package manager, useful even for existing C&#x2F;C++ projects.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;Simplicity&lt;&#x2F;em&gt;: Zig avoids unclear features like macros and operator
overloading found in other languages.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;Tooling&lt;&#x2F;em&gt;: Zig provides a comprehensive toolchain with cross-compilation
(which is very cool), C&#x2F;C++ support, resource compiler, and more, packaged in
a single archive.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Zig positions itself as a simpler, more explicit, more portable language than
C++, D, and Rust while offering more powerful tooling, which is all fine and
dandy, but I&#x27;m not super familiar with Rust, D, or C++. Compared to C, Zig&#x27;s
additional safety features like safety-checked undefined behavior and the
&lt;code&gt;defer&lt;&#x2F;code&gt; keyword to prevent issues one could normally have with &lt;code&gt;free()&lt;&#x2F;code&gt; in C are
what make Zig special and interesting to me. Its focus on simplicity and safety
makes it a compelling choice for systems programming tasks, including working
with multimedia codecs.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;qoi-overview&quot;&gt;QOI Overview&lt;&#x2F;h2&gt;
&lt;p&gt;QOI is very cool because it is extremely simple while still being relatively
efficient for a lossless image codec.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;A QOI file consists of a 14-byte header, followed by any number of data
“chunks” and an 8-byte end marker.
(&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;qoiformat.org&#x2F;qoi-specification.pdf&quot;&gt;spec&lt;&#x2F;a&gt;)&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;QOI supports two colorspaces: Linear RGB &amp;amp; sRGB with linear alpha. These do not
affect the way pixels are encoded. Pixels (well, &quot;chunks&quot; according to the spec,
but I find it more intuitive to call them pixels as that is how they are
interpreted and represented on a screen) have either three or four channels;
they are either RGB or RGBA. The QOI philosophy is that everything is
byte-aligned, making it trivial to stream bytes to a decoder one at a time and
have the decoder produce a usable output.&lt;&#x2F;p&gt;
&lt;p&gt;QOI&#x27;s lossless compression takes place via six main pixel types:&lt;&#x2F;p&gt;
&lt;ol start=&quot;0&quot;&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;SPEC_NAME&lt;&#x2F;code&gt; (&quot;Name I Made Up&quot;): Description from spec. I&#x27;m only making up a
name so I can reference it elsewhere more easily.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;QOI_OP_RGB&lt;&#x2F;code&gt; (&quot;RGB Pixels&quot;): Full RGB pixel value using 8 bits for each of
the red, green, and blue channels. The alpha channel is 255 in RGB images,
and remains unchanged from the previous pixel.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;QOI_OP_RGBA&lt;&#x2F;code&gt; (&quot;RGBA Pixels&quot;): Full RGBA pixel value using 8 bits for each of
the red, green, blue, &amp;amp; alpha channels.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;QOI_OP_DIFF&lt;&#x2F;code&gt; (&quot;Diff Pixels&quot;): The difference between the current pixel and
the previous pixel for the red, green, and blue channels are stored using 2
bits for each channel. The differences are stored with a bias of 2 and wrap
(so 1 minus 2 would be 255). The alpha channel remains unchanged.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;QOI_OP_LUMA&lt;&#x2F;code&gt; (&quot;Luma Pixels&quot;): These pixels encode the green channel
difference from the previous pixel using 6 bits, and then encode the red and
blue channel differences relative to the green channel difference using 4
bits each. This allows for more efficient encoding of small color changes.
The alpha channel remains unchanged.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;QOI_OP_RUN&lt;&#x2F;code&gt; (&quot;Run Pixels&quot;): These are the simplest, encoding a run-length of
pixels that are identical to the previous pixel. The run length is stored
using 6 bits with a bias of -1, allowing for runs of 1 to 62 pixels.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;QOI_OP_INDEX&lt;&#x2F;code&gt; (&quot;Hashed Pixels&quot;): These are stored by referencing a
previously seen pixel value from a rolling array of 64 recent pixel values by
using a simple hash on each pixel as it is identified. If another pixel
matches a previously seen hash value in the array, the index of the
referenced pixel is stored.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;That&#x27;s pretty much it! The entire spec is
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;qoiformat.org&#x2F;qoi-specification.pdf&quot;&gt;one page&lt;&#x2F;a&gt; if you&#x27;d like a bit more
detail. Amazingly, QOI
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;qoiformat.org&#x2F;benchmark&#x2F;&quot;&gt;trades blows with libpng&#x27;s PNG encoding while being much faster&lt;&#x2F;a&gt;
on average with the reference encoder given the test corpus the author used in
that benchmark.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;explaining-the-codebase&quot;&gt;Explaining the Codebase&lt;&#x2F;h2&gt;
&lt;p&gt;Now that I&#x27;ve done the requisite amount of shilling, it is time to get into the
real content. I want this to be a thorough walk-through of my decisions writing
this encoding implementation, so you can take inspiration from my work on your
own if you are also new to Zig. Again, the source code for my project can be
found at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gianni-rosato&#x2F;qoi-enc-zig&#x2F;&quot;&gt;this git repository&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The program starts by importing the necessary standard library modules and
defining the necessary data structures. The &lt;code&gt;QoiEnum&lt;&#x2F;code&gt; enum, for instance,
defines the different opcodes used in the QOI format, such as &lt;code&gt;QOI_OP_RGB&lt;&#x2F;code&gt; and
&lt;code&gt;QOI_OP_RGBA&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;zig&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; QoiEnum&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; enum&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    QOI_OP_RGB&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 0xFE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    QOI_OP_RGBA&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 0xFF&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    QOI_OP_INDEX&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 0x00&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    QOI_OP_DIFF&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 0x40&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    QOI_OP_LUMA&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 0x80&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    QOI_OP_RUN&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 0xC0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;These are fundamental to the QOI format&#x27;s compression techniques, which we&#x27;ll
explore in a bit.&lt;&#x2F;p&gt;
&lt;p&gt;Next, we have the &lt;code&gt;QoiPixel&lt;&#x2F;code&gt; union, which represents a single pixel&#x27;s color
values. By using a union, we can access the pixel&#x27;s channels (red, green, blue,
alpha) individually through the &lt;code&gt;channels&lt;&#x2F;code&gt; array or the &lt;code&gt;vals&lt;&#x2F;code&gt; struct. We can
also access them as a single unsigned 32-bit integer through
&lt;code&gt;concatenated_pixel_values&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;zig&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; QoiPixel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; extern union&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    vals&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; extern struct&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;        red&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;        green&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;        blue&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;        alpha&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    channels&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    concatenated_pixel_values&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Important to note here is that the &lt;code&gt;extern&lt;&#x2F;code&gt; keyword defines the union (and the
enclosed struct) as C ABI compatible data structures. In Zig&#x27;s standard unions
without the &lt;code&gt;extern&lt;&#x2F;code&gt; keyword, trying to access inactive fields of the union is
safety-checked undefined behavior. Your code will compile successfully, but if
you compiled in Safe mode (which you probably did by default with &lt;code&gt;zig build&lt;&#x2F;code&gt;)
you&#x27;ll get some runtime errors accompanied by a stack trace. I enjoy the fact
that Zig allows you to use &lt;code&gt;extern union&lt;&#x2F;code&gt;s to essentially get different
&quot;viewpoints&quot; on how to access the underlying data in memory while maintaining a
different native paradigm; it is a testament to the language&#x27;s ability to
espouse flexibility while still providing a feeling of familiarity.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;QoiDesc&lt;&#x2F;code&gt; struct holds essential information about the image being encoded,
including its dimensions, color channels, and colorspace, in order to write the
header for the encoded file. This struct provides methods for assigning our
instance of &lt;code&gt;QoiDesc&lt;&#x2F;code&gt; &amp;amp; writing the QOI header to the output file:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;zig&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; QoiDesc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; struct&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    width&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    height&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    channels&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    colorspace&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;    fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt; qoiSetEverything&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;w&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; h&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; ch&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QoiDesc&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; QoiDesc&lt;&#x2F;span&gt;&lt;span&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;width&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; w&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;, .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;height&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; h&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;, .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;channels&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; ch&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;, .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;colorspace&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; c&lt;&#x2F;span&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;    fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt; writeQoiHeader&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; QoiDesc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; dest&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;14&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;void&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;        &#x2F;&#x2F; ... (implementation omitted for brevity)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Struct methods in Zig are not super different from normal functions. According
to Zig&#x27;s website:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Struct methods are not special, they are only namespaced functions that you
can call with dot syntax.
(&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;ziglang.org&#x2F;documentation&#x2F;master&#x2F;#struct&quot;&gt;1&lt;&#x2F;a&gt;)&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Using these within the scope of struct definitions is cool because it makes the
codebase nice and neat. You can also call methods with dot syntax, which feels
tidy:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;zig&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;&#x2F;&#x2F; Declare variable `desc` providing width, height, channels, colorspace.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;&#x2F;&#x2F; We can call this method using dot syntax on the type:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    var&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; desc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; QoiDesc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;qoiSetEverything&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;width&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; height&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; channels&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; colorspace&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;&#x2F;&#x2F; Now, we use dot syntax on the `desc` variable to call the&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;&#x2F;&#x2F; `writeQoiHeader` method, where `desc` is passed to the method as `self`:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    desc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;writeQoiHeader&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;qoi_file&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;14&lt;&#x2F;span&gt;&lt;span&gt;])&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You don&#x27;t need to do &lt;code&gt;foo-&amp;gt;bar&lt;&#x2F;code&gt; like in C, either; Zig will do that
automatically if you just do &lt;code&gt;foo.bar&lt;&#x2F;code&gt;. I like this!&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;QoiEnc&lt;&#x2F;code&gt; struct encapsulates the encoding logic. It maintains a pixel
buffer, a run counter, and various other state variables necessary for the
encoding process. The &lt;code&gt;qoiEncInit&lt;&#x2F;code&gt; method initializes the encoder with the image
description &amp;amp; an output buffer:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;zig&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; QoiEnc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; struct&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    buffer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;64&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QoiPixel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    prev_pixel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; QoiPixel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    pixel_offset&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; usize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    len&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; usize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    data&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    offset&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    run&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    pad&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u24&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;    fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt; qoiEncInit&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QoiEnc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; desc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; QoiDesc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; data&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;void&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;        &#x2F;&#x2F; ... (implementation omitted for brevity)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;    &#x2F;&#x2F; ... (other methods omitted for brevity, but there are a bunch)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now, remember the types of pixels allowed in QOI that we enumerated earlier? The
&lt;code&gt;QoiEnc&lt;&#x2F;code&gt; struct provides separate methods for encoding each of these, including
&lt;code&gt;qoiEncRun&lt;&#x2F;code&gt;, &lt;code&gt;qoiEncLuma&lt;&#x2F;code&gt;, &lt;code&gt;qoiEncIndex&lt;&#x2F;code&gt;, &lt;code&gt;qoiEncFullColor&lt;&#x2F;code&gt;, and
&lt;code&gt;qoiEncDifference&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s take a closer look at the &lt;code&gt;qoiEncRun&lt;&#x2F;code&gt; method, which handles run-length
encoding:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;zig&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt; qoiEncRun&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QoiEnc&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;void&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; tag&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @intFromEnum&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QoiEnum&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QOI_OP_RUN&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;run&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;run&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;offset&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; tag&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;offset&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is a &lt;em&gt;&quot;Run pixel&quot;&lt;&#x2F;em&gt; which is the simplest kind of pixel conceptually within
QOI, in my opinion.&lt;&#x2F;p&gt;
&lt;p&gt;This method writes a tag byte to the output buffer, indicating a run of
identical pixels. The tag byte consists of the &lt;code&gt;QOI_OP_RUN&lt;&#x2F;code&gt; opcode combined with
the run length minus one via a logical or.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;qoiEncLuma&lt;&#x2F;code&gt; method, on the other hand, handles encoding &lt;em&gt;&quot;Luma Pixels&quot;&lt;&#x2F;em&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;zig&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt; qoiEncLuma&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QoiEnc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; green_diff&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; i8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; dr_dg&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; i8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; db_dg&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; i8&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;void&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; green_diff_biased&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @intCast&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;green_diff&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 32&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; dr_dg_biased&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @intCast&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;dr_dg&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; db_dg_biased&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @intCast&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;db_dg&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; tags&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt;@intFromEnum&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QoiEnum&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QOI_OP_LUMA&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; green_diff_biased&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; dr_dg_biased&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; &amp;lt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; db_dg_biased&lt;&#x2F;span&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;tags&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;tag&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;offset&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;i&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; tag&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;offset&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; tags&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;len&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This method calculates the differences between the red, green, and blue channels
of the current pixel and the previous pixel. It then encodes these differences
using a luma tag and bias values, as we already explained when we talked about
QOI.&lt;&#x2F;p&gt;
&lt;p&gt;Encoding &quot;Diff Pixels&quot; with &lt;code&gt;qoiEncDifference&lt;&#x2F;code&gt; is a similar story:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;zig&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;    fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt; qoiEncDifference&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QoiEnc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; red_diff&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; i32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; green_diff&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; i32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; blue_diff&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; i32&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;void&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;        const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; green_diff_biased&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @intCast&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;green_diff&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;        const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; red_diff_biased&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @intCast&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;red_diff&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;        const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; blue_diff_biased&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @intCast&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;blue_diff&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;        const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; tag&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt;            @intFromEnum&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QoiEnum&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QOI_OP_DIFF&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;            red_diff_biased&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; &amp;lt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;            green_diff_biased&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; &amp;lt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;            blue_diff_biased&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;        enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;offset&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; tag&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;        enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;offset&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The bit manipulations for properly writing to &lt;code&gt;enc.offset&lt;&#x2F;code&gt; felt a bit
intimidating, and to be honest, they still do &amp;amp; probably always will. Thinking
about the actual structure of the pixels being written to the file, it begins to
make a bit more sense thinking about the fact that the pixels are stored in
order of red&#x27;s difference, then green&#x27;s difference, then blue&#x27;s difference, with
a tag preceding them; the entire pixel is a single byte, the differences are
just being ordered &lt;em&gt;within&lt;&#x2F;em&gt; that byte.&lt;&#x2F;p&gt;
&lt;p&gt;Now, let&#x27;s look at encoding &quot;RGB Pixels&quot; &amp;amp; &quot;RGBA Pixels&quot; with &lt;code&gt;qoiEncFullColor&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;zig&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;    fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt; qoiEncFullColor&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QoiEnc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; px&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; QoiPixel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; channels&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;void&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;        var&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; s&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u3&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;        if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;channels&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;s&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 5&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; else&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; s&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;        const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; tags&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;channels&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt;            @intFromEnum&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QoiEnum&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QOI_OP_RGBA&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;            px&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;vals&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;red&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;            px&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;vals&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;green&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;            px&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;vals&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;blue&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;            px&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;vals&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;alpha&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        } &lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;else&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; .&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt;            @intFromEnum&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QoiEnum&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QOI_OP_RGB&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;            px&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;vals&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;red&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;            px&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;vals&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;green&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;            px&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;vals&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;blue&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;            undefined&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;        for&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;tags&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;s&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;s&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;tag&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;offset&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;i&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; tag&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;        enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;offset&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; s&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Here, we have some trademark &quot;Zigginess&quot;. Let&#x27;s walk through it:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The method takes a pointer to a QoiEnc, which is essentially &quot;self&quot;&lt;&#x2F;li&gt;
&lt;li&gt;A QoiPixel, and&lt;&#x2F;li&gt;
&lt;li&gt;the number of channels. Within the method:&lt;&#x2F;li&gt;
&lt;li&gt;We declare a variable &lt;code&gt;s&lt;&#x2F;code&gt; which is a mere 3 bits&lt;&#x2F;li&gt;
&lt;li&gt;We&#x27;re making &lt;code&gt;tags&lt;&#x2F;code&gt; an array of 5 unsigned 8-bit integers, and assigning
different values to the array depending on the number of channels.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Let&#x27;s stop here for a moment. As you can see, these &lt;code&gt;if&lt;&#x2F;code&gt; expressions are being
used more like ternary expressions would be in C. The assignment to &lt;code&gt;tags&lt;&#x2F;code&gt; is
being done via an &lt;em&gt;anonymous list literal&lt;&#x2F;em&gt;. These confused me greatly when first
learning Zig, but it makes a bit more sense looking at a more concise example:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;zig&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;&#x2F;&#x2F; Initialize an array with array literal syntax. Compiler infers size&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; arr_a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;_&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;i6&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;6&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 7&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;&#x2F;&#x2F; Initialize an array with an anonymous list literal. Compiler cannot&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;&#x2F;&#x2F; infer size, as it isn&amp;#39;t aware of the anonymous list literal&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; arr_b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;i6&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; = .&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;6&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 7&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now we can observe the &lt;code&gt;for&lt;&#x2F;code&gt; loop at the end of the function block. I like the
way Zig handles for loops, essentially leaving traditional C-style &lt;code&gt;i++&lt;&#x2F;code&gt;
iteration for &lt;code&gt;while&lt;&#x2F;code&gt; loops. What we&#x27;re essentially doing in this loop is
slicing &lt;code&gt;tags&lt;&#x2F;code&gt; from index 0 to the index before &lt;code&gt;s&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;A slice is a pointer and a length. The difference between an array and a slice
is that the array&#x27;s length is part of the type and known at compile-time,
whereas the slice&#x27;s length is known at runtime. Both can be accessed with the
&lt;code&gt;len&lt;&#x2F;code&gt; field. (&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;ziglang.org&#x2F;documentation&#x2F;master&#x2F;#Slices&quot;&gt;2&lt;&#x2F;a&gt;)&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;We&#x27;re also keeping track of 0 through the integer just preceding &lt;code&gt;s&lt;&#x2F;code&gt; with
&lt;code&gt;0..s&lt;&#x2F;code&gt;. The values are captured with &lt;code&gt;|tag, i|&lt;&#x2F;code&gt;, where we use &lt;code&gt;i&lt;&#x2F;code&gt; to assign
values with &lt;code&gt;tag&lt;&#x2F;code&gt; to elements in &lt;code&gt;enc.offset&lt;&#x2F;code&gt;. Finally, we add &lt;code&gt;s&lt;&#x2F;code&gt; to
&lt;code&gt;enc.offset&lt;&#x2F;code&gt;, as &lt;code&gt;s&lt;&#x2F;code&gt; represents the number of bytes we need, and &lt;code&gt;enc.offset&lt;&#x2F;code&gt; is
a u8 multi-pointer.&lt;&#x2F;p&gt;
&lt;p&gt;This felt pretty cool, to have &lt;code&gt;for&lt;&#x2F;code&gt; loops and &lt;code&gt;if&lt;&#x2F;code&gt; statements so gracefully
doing my bidding based on little &lt;code&gt;s&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;How about we take a look at encoding &quot;Hashed Pixels&quot; next, now that we&#x27;re
getting the hang of things?&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;zig&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt; qoiEncIndex&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QoiEnc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; index_pos&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;void&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; tag&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @intFromEnum&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QoiEnum&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QOI_OP_INDEX&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; index_pos&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;offset&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; tag&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;offset&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is pretty straightforward. We calculate the hash itself outside of the
function with
&lt;code&gt;const index_pos: u6 = @truncate(cur_pixel.vals.red *% 3 +% cur_pixel.vals.green *% 5 +% cur_pixel.vals.blue *% 7 +% cur_pixel.vals.alpha *% 11);&lt;&#x2F;code&gt;.
The &lt;code&gt;*%&lt;&#x2F;code&gt; &amp;amp; &lt;code&gt;+%&lt;&#x2F;code&gt; just mean &lt;em&gt;wrapping addition&#x2F;multiplication&lt;&#x2F;em&gt;, which we
understand from the QOI pixel descriptions. We need our numbers to wrap around.
The built-in &lt;code&gt;@truncate&lt;&#x2F;code&gt; helps too, as it ensures we don&#x27;t overflow a &lt;code&gt;u6&lt;&#x2F;code&gt;; it
truncates bits from an integer type resulting in a smaller integer type, so it
is taking the low 6 bits of our hash result because there are only 64 entries in
our index table. The &lt;code&gt;enc.offset&lt;&#x2F;code&gt; is incremented by 1, because the pixel is a
single byte.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s all the pixels!&lt;&#x2F;p&gt;
&lt;p&gt;Now, the &lt;code&gt;qoiEncodeChunk&lt;&#x2F;code&gt; function is where the magic happens. It iterates over
the input pixel data, compares each pixel with the previous one, and chooses the
optimal encoding operation based on the differences between the pixels. The
encoded data is then written to the output buffer using the respective encoding
methods:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;zig&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt; qoiEncodeChunk&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;desc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QoiDesc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;QoiEnc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; qoi_pixel_bytes&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;void&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    var&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; cur_pixel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; QoiPixel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; undefined&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;    &#x2F;&#x2F; ... (omitted for brevity. this is just deciding &amp;quot;alpha or no alpha?&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;    &#x2F;&#x2F; and writing relevant data to the current pixel.)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; index_pos&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u6&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @truncate&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;cur_pixel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;vals&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;red&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; *%&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +%&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; cur_pixel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;vals&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;green&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; *%&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 5&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +%&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; cur_pixel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;vals&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;blue&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; *%&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 7&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +%&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; cur_pixel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;vals&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;alpha&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; *%&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 11&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;qoiComparePixel&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;cur_pixel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;prev_pixel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; desc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;channels&lt;&#x2F;span&gt;&lt;span&gt;)) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;        &#x2F;&#x2F; Handle &amp;quot;run pixel&amp;quot; encoding&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;        &#x2F;&#x2F; ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    } &lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;else&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;        if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;run&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;            enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;qoiEncRun&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;        if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;qoiComparePixel&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;buffer&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;index_pos&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; cur_pixel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span&gt;)) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;            &#x2F;&#x2F; Handle &amp;quot;hash pixel&amp;quot; encoding&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;            &#x2F;&#x2F; ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        } &lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;else&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;            enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;buffer&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;index_pos&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; cur_pixel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;            &#x2F;&#x2F; Handle various encoding operations based on pixel differences&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;            &#x2F;&#x2F; ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;prev_pixel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; cur_pixel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;pixel_offset&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;    &#x2F;&#x2F; Handle padding at the end of the image once it is time to do so&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;    &#x2F;&#x2F; ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can see the &lt;code&gt;index_pos&lt;&#x2F;code&gt; hash calculation in there, where it happens, and
what triggers it. Hopefully, everything else is clear, too. Alongside the
others, this function demonstrates Zig&#x27;s ability to work with low-level data
structures and perform bit-level operations.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;main-function&quot;&gt;Main Function&lt;&#x2F;h2&gt;
&lt;p&gt;Now that we understand everything we can do to encode a QOI image, let&#x27;s look at
the &lt;code&gt;main&lt;&#x2F;code&gt; function.&lt;&#x2F;p&gt;
&lt;p&gt;This &lt;code&gt;main&lt;&#x2F;code&gt; function serves as a great example of Zig&#x27;s clean and readable
syntax as well as QOI&#x27;s simplicity. It parses command-line arguments, handles
file I&#x2F;O, and orchestrates the encoding process while staying concise. Error
handling is easy in Zig, with the error union &lt;code&gt;!void&lt;&#x2F;code&gt; ensuring any errors are
dealt with as they are returned. I&#x27;d like to expand on error unions more, but
I&#x27;ll save that for another article.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;zig&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt; main&lt;&#x2F;span&gt;&lt;span&gt;() &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;void&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;    &#x2F;&#x2F; Get allocator&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    var&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; gpa&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; std&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;heap&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #5DA994;&quot;&gt;GeneralPurposeAllocator&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;{}){}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; allocator&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; gpa&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;allocator&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    defer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; _&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; gpa&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;deinit&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #758575DD;&quot;&gt;    &#x2F;&#x2F; Parse args into string array (error union needs &amp;#39;try&amp;#39;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; args&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; try&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; std&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;process&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;argsAlloc&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;allocator&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    defer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; std&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;process&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;argsFree&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;allocator&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; args&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;std&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;mem&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;eql&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; args&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt; &amp;quot;-h&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;or&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;        std&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;mem&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;eql&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; args&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt; &amp;quot;--help&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;or&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;        args&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;len&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; or&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;        args&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;len&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; or&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;        args&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;len&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;        _&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; try&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt; printHelp&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    var&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; width&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; undefined&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    var&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; height&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; undefined&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    var&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; channels&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; undefined&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; colorspace&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; try&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt; parseInt&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; args&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;&amp;quot;Opening {s} ... &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;, .&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;args&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]})&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; file&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; try&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; std&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;fs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;cwd&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;openFile&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;args&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;, .&lt;&#x2F;span&gt;&lt;span&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; = .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;read_only&lt;&#x2F;span&gt;&lt;span&gt; })&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    defer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; file&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;close&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; bytes_read&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; try&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; file&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;readToEndAlloc&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;allocator&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; std&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;math&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;maxInt&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;))&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    defer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; allocator&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;free&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;bytes_read&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    var&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; offset&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; usize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;std&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;mem&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;eql&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; bytes_read&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt; &amp;quot;P7&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;        print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;&amp;quot;file is a PAM&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;, .&lt;&#x2F;span&gt;&lt;span&gt;{})&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;        offset&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; try&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt; parsePamHeader&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;bytes_read&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;72&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;width&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;height&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;channels&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    } &lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;else&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;        print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt;\n\x1b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;[31mInvalid Input: Input file does not appear to be a compatible PAM.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt;\x1b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;[0m&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;, .&lt;&#x2F;span&gt;&lt;span&gt;{})&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;        print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;&amp;quot;If your PAM input contains comments in the header, please strip them.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;, .&lt;&#x2F;span&gt;&lt;span&gt;{})&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;        return error&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;InvalidInput&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; image_size&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; usize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; width&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; height&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; channels&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;image_size&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; bytes_read&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;len&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; offset&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;        print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt;\x1b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;[31mInvalid Input: Image size is larger than the file size.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt;\x1b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;[0m&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;, .&lt;&#x2F;span&gt;&lt;span&gt;{})&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;        return error&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;InvalidInput&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    var&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; desc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; QoiDesc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;qoiSetEverything&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;width&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; height&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; channels&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; colorspace&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; qoi_file_size&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @as&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; desc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;width&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @as&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; desc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;height&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt;@as&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; desc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;channels&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 14&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @sizeOf&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    var&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; qoi_file&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; try&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; allocator&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;alloc&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; qoi_file_size&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    defer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; allocator&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;free&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;qoi_file&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;&amp;quot;Writing {s} ... &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;, .&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;args&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;]})&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    desc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;writeQoiHeader&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;qoi_file&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;14&lt;&#x2F;span&gt;&lt;span&gt;])&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    var&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; pixel_seek&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; bytes_read&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;offset&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;ptr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    var&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; QoiEnc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; undefined&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    try&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;qoiEncInit&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;desc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; qoi_file&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;ptr&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    while&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;pixel_offset&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; &amp;gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;len&lt;&#x2F;span&gt;&lt;span&gt;)) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;        qoiEncodeChunk&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;desc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; pixel_seek&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;        pixel_seek&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; desc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;channels&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; used_len&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @intFromPtr&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;offset&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @intFromPtr&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;data&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; outfile&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; try&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; std&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;fs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;cwd&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;createFile&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;args&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;, .&lt;&#x2F;span&gt;&lt;span&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;truncate&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; true&lt;&#x2F;span&gt;&lt;span&gt; })&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    defer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; outfile&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;close&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;    _&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; try&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; outfile&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;writeAll&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;enc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;data&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;used_len&lt;&#x2F;span&gt;&lt;span&gt;])&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt;\x1b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;[32mSuccess!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt;\x1b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;[0m&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt;\n\t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;Original:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt;\t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;{d} bytes&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt;\n\t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;Compressed:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt;\t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;{d} bytes &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;, .&lt;&#x2F;span&gt;&lt;span&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;image_size&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; offset&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; used_len&lt;&#x2F;span&gt;&lt;span&gt; })&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; ((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;image_size&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; offset&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; used_len&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;        const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; used_len_flt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; f64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @floatFromInt&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;used_len&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;        const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; image_size_flt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; f64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B8A965;&quot;&gt; @floatFromInt&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;image_size&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; offset&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;        const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; percent_dec&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt; f64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 100.0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; ((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;used_len_flt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt; image_size_flt&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CB7676;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span style=&quot;color: #4C9A91;&quot;&gt; 100.0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;        print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;&amp;quot;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt;\x1b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;[33m{d:.2}%&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt;\x1b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;[0m smaller)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;, .&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #BD976A;&quot;&gt;percent_dec&lt;&#x2F;span&gt;&lt;span&gt;})&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    } &lt;&#x2F;span&gt;&lt;span style=&quot;color: #4D9375;&quot;&gt;else&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #80A665;&quot;&gt;        print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C99076;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C98A7D;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;, .&lt;&#x2F;span&gt;&lt;span&gt;{})&lt;&#x2F;span&gt;&lt;span style=&quot;color: #444444;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That&#x27;s it, the entire &lt;code&gt;main&lt;&#x2F;code&gt; function. It could probably be made even more
concise with some code golfing, but for now, I&#x27;m happy with how readable it is
even without comments.&lt;&#x2F;p&gt;
&lt;p&gt;Coming from any other low-level language background, I think this function is
relatively easy to parse. This is a big argument for Zig, in my opinion -
transitioning C projects is not a massive burden if you have interoperability as
well as a codebase that feels familiar to long-time C developers. Even as a
relatively inexperienced C developer, Zig was trivial to pick up.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;Exploring QOI encoding in Zig has been an incredibly rewarding ordeal. Not only
did this experience allow me to dive into the intricacies of simple lossless
image compression, but it also showcased to me the power and versatility of Zig.&lt;&#x2F;p&gt;
&lt;p&gt;Full disclosure, I may have gotten some details incorrect here, so corrections
may be imminent. I&#x27;ll add a note to the very top of this page if I make any
larger corrections to the article.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re a programmer seeking a modern systems programming language, I highly
recommend giving Zig a try. If you have any questions, comments, or concerns,
feel free to reach out to me via my socials on this site&#x27;s homepage. Thanks for
reading!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Thank you to Cancername for their help with the QOI encoding implementation!&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Freestanding QOI Encoder in Zig:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gianni-rosato&#x2F;qoi-enc-zig&quot;&gt;GitHub Link&lt;&#x2F;a&gt; (no code comments
yet, coming soon).&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Lossless Data Compression</title>
        <published>2023-10-02T00:00:00+00:00</published>
        <updated>2023-10-02T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Gianni Rosato
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://giannirosato.com/blog/post/lossless-data-comp/"/>
        <id>https://giannirosato.com/blog/post/lossless-data-comp/</id>
        <content type="html" xml:base="https://giannirosato.com/blog/post/lossless-data-comp/">&lt;p&gt;Comparing the efficacy of various lossless data compression algorithms on text data, comparing time to compress vs output size. The algorithms tested include ZIP, 7-zip, XZ, Brotli, Zstandard, and zpaq.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;h1 id=&quot;lossless-data-compression&quot;&gt;Lossless Data Compression&lt;&#x2F;h1&gt;
&lt;p&gt;While I&#x27;ve covered image compression a decent amount in these sorts of blog
posts, I wanted to dive into generic lossless data compression a bit more to see
what this interesting realm of compression has to offer. Inspired by the many
other existing lossless data compression comparisons, I wanted to approach this
with visualizations similar to the ones I provide in my image codec comparisons.
Because lossless data compression doesn&#x27;t discard data, these graphs plot time
vs. compressed size, allowing you to compare the various compression steps of
lossless data compression algorithms relative to the time taken to
compress&#x2F;decompress.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;methodology-algorithms&quot;&gt;Methodology &amp;amp; Algorithms&lt;&#x2F;h1&gt;
&lt;p&gt;I want to make it clear upfront that I did &lt;em&gt;not&lt;&#x2F;em&gt; thoroughly test every single
available compression algorithm under the sun, but instead hand-picked a couple
that I found interesting. Notably not present are gzip &amp;amp; bzip2; gzip has an
interesting history, but its use of Deflate &amp;amp; LZ77 makes it pretty much
identical to ZIP for all intents &amp;amp; purposes. Bzip2 is interesting, but due to
its age &amp;amp; effectiveness compared to more modern compression algorithms, I found
it less interesting than more modern options. It also decompresses slower than
the more space-efficient XZ. Feel free to pique my interest in these two
algorithms if you&#x27;re interested in seeing them included.&lt;&#x2F;p&gt;
&lt;p&gt;I must provide the usual disclaimer as well that my testing is non-scientific &amp;amp;
shouldn&#x27;t inform your use of one technology over another. That being said, here
are the formats I tested:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;ZIP (via 7zip)&lt;&#x2F;li&gt;
&lt;li&gt;7zip&lt;&#x2F;li&gt;
&lt;li&gt;XZ&lt;&#x2F;li&gt;
&lt;li&gt;Brotli&lt;&#x2F;li&gt;
&lt;li&gt;Zstandard&lt;&#x2F;li&gt;
&lt;li&gt;zpaq&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&amp;amp; here are the parameters I used for compression, often after archiving to a
.tar:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;ZIP: &lt;code&gt;7zzs a -bso0 -tzip -mx[level] &quot;out&quot; &quot;input&quot;&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;7zip: &lt;code&gt;7zzs a -bso0 -mx[level] &quot;out&quot; &quot;input&quot;&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;XZ: &lt;code&gt;xz -q -q -T0 -k -[level] &quot;input&quot; &quot;out&quot;&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Brotli: &lt;code&gt;brotli -q [level] &quot;input&quot; -o &quot;out&quot;&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Zstandard: &lt;code&gt;zstd -q --ultra -[level] -T0 &quot;input&quot; -o &quot;out&quot;&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;zpaq: &lt;code&gt;zpaq a &quot;out&quot; &quot;input&quot; -m[level]&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Finally, here are my system specs:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #DBD7CACC; background-color: #000000;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;CPU: 16-core (8-mt&#x2F;8-st) 13th Gen Intel Core i7-13700K (-MST AMCP-)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     speed&#x2F;min&#x2F;max: 888&#x2F;800&#x2F;5300:5400:4200 MHz Kernel: 6.5.5-zen1-1-zen x86_64&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you&#x27;d like to do a deeper dive into my methodology, I have my script
&lt;code&gt;compression-plotter&lt;&#x2F;code&gt; available
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gianni-rosato&#x2F;compression-plotter&quot;&gt;on GitHub&lt;&#x2F;a&gt;. Given you
have all the dependencies, you are free to run it yourself to cross-reference
any results you come up with against mine.&lt;&#x2F;p&gt;
&lt;p&gt;I used a 1.0GB generic Wikipedia download for this test, which makes it
text-specific. Now, let&#x27;s dive into which algorithms I chose &amp;amp; what they do.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;zip-deflate&quot;&gt;ZIP (Deflate)&lt;&#x2F;h3&gt;
&lt;p&gt;Most modern variants of the tried &amp;amp; true ZIP use the Deflate compression
algorithm, which is also famously used in PNG. Deflate acts as a combination of
LZ77 lossless coding &amp;amp; Huffman coding, where it can first use LZ77 to find
patterns in the data &amp;amp; reduce redundancy. This is followed by using Huffman
coding to assign smaller bit values to patterns found more frequently by LZ77.&lt;&#x2F;p&gt;
&lt;p&gt;Considering ZIP came out in 1989, I don&#x27;t expect it to be the strongest
performer on this benchmark. I&#x27;m interested to see how 7zip&#x27;s supposedly
improved ZIP encoder fares against the other standards, though; as the 7zip
website&#x27;s homepage claims: &quot;For ZIP and GZIP formats, 7-Zip provides a
compression ratio that is 2-10 % better than the ratio provided by PKZip and
WinZip.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;ZIP is noteworthy for its nearly universal compatibility. &quot;Traditional ZIP&quot;
(compression method 8) is limited to 4 GB, though most ZIP compressors use
Deflate64(tm) (compression level 9 in the ZIP specification) to bypass this
limitation.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;7zip&quot;&gt;7zip&lt;&#x2F;h3&gt;
&lt;p&gt;Using the static 7zip binary for Linux, I benchmarked 7zip not initially very
interested in its compression capabilities. However, as I continued to use it, I
was impressed by its usability &amp;amp; sane defaults; 7zip compression &amp;amp; decompression
both thread rather effectively, which makes the format &lt;em&gt;feel&lt;&#x2F;em&gt; very fast to work
with even if system &amp;amp; user time tell a slightly different story. It is supported
just fine on my Arch Linux installation with GNOME, and it works on macOS by
default. To open a 7zip (.7z) archive on Windows 10, you need the well-known
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.7-zip.org&#x2F;&quot;&gt;7-Zip utility&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;7zip is &lt;em&gt;mostly&lt;&#x2F;em&gt; based on LZMA &amp;amp; LZMA2, though there is
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.7-zip.org&#x2F;7z.html&quot;&gt;a lot more going on&lt;&#x2F;a&gt; if you look deeper. LZMA2
is an improved version of LZMA, which itself is based on LZ77.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;xz&quot;&gt;XZ&lt;&#x2F;h3&gt;
&lt;p&gt;XZ can only compress one file at a time, so making a tar archive of the files
you&#x27;d like to compress (if there are multiple) is necessary when using XZ. The
XZ format itself is an improvement on LZMA, allowing for preprocessing filters
similar to 7zip to increase the resulting archive&#x27;s compression ratio. I&#x27;ve been
able to decompress .tar.xz archives on macOS &amp;amp; Linux just fine, but Windows 10
needs 7-Zip once again.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;brotli&quot;&gt;Brotli&lt;&#x2F;h3&gt;
&lt;p&gt;Brotli was designed by Google, &amp;amp; is commonly used as a compression format on the
Web. It was released in late 2013, &amp;amp; it is commonly used on the Web for content
delivery. It is a core part of the .woff2 Web Open Font Format, allowing web
fonts to be smaller when sent to users as part of a website. It is not very
common to pass around .tar.br files, so it is perfectly acceptable that such
files aren&#x27;t really compatible anywhere. Brotli is almost universally compatible
across the Web, being supported by as much as 96% of the world wide web&#x27;s users.&lt;&#x2F;p&gt;
&lt;p&gt;Brotli is based on LZ77 &amp;amp; Huffman coding, much like ZIP. It also uses context
modeling to allow the use of multiple Huffman trees for the same alphabet in the
same block; without getting into the weeds, this essentially means that based on
the &lt;em&gt;context&lt;&#x2F;em&gt; of the data being compressed, it can be compressed more
efficiently especially if it contains multiple different kinds of data.&lt;&#x2F;p&gt;
&lt;p&gt;Brotli was co-authored &amp;amp; partially developed by Jyrki Alakuijala, who also
worked on JPEG XL &amp;amp; jpegli. JPEG XL&#x27;s metadata information can be uncompressed
or Brotli-compressed.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;zstandard&quot;&gt;Zstandard&lt;&#x2F;h3&gt;
&lt;p&gt;Zstandard is a compression algorithm by Facebook known for its extremely fast
decompression speeds. It was released in early 2015 and is used in a variety of
different contexts. It was designed to perform similarly to older Deflate-based
compression algorithms like ZIP or gzip while being overall faster. In practice,
it is said to compress similarly to pure LZMA while being much faster.&lt;&#x2F;p&gt;
&lt;p&gt;While .tar.zst archives aren&#x27;t going to be very popular to find on the Internet
&amp;amp; elsewhere, it is already a very popular tool for compression in the world of
open-source software. It has been integrated into both the FreeBSD kernel &amp;amp; the
Linux kernel and is available as a filesystem compression method for the btrfs,
squashfs, bcachefs, &amp;amp; OpenZFS filesystems. All Arch Linux packages are
compressed at zstd level 20, allowing Arch packages to be decompressed &lt;em&gt;14
times&lt;&#x2F;em&gt; faster than when Arch used XZ at the cost of an average 0.8% filesize
increase. It is popular in the game emulation scene as well, as many game file
formats for emulating console games support zstd compression. The ZIP file
format actually supports Zstandard in compression level 93 since version 6.3.8
which was published in 2020. Content encoding using zstd is supported since
Chromium 118 behind an experimental flag, meaning it might compete with Brotli
on the web in the future. Apple&#x27;s LZFSE algorithm is purportedly similar to
Zstandard compression level 6.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;zpaq&quot;&gt;ZPAQ&lt;&#x2F;h3&gt;
&lt;p&gt;I know much less about ZPAQ, but from what I can glean it uses a multitude of
different compression algorithms to try to achieve the best
size-to-compression-time ratio possible while producing the smallest possible
archives without much concern given to decompression performance. On the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;mattmahoney.net&#x2F;dc&#x2F;zpaq.html&quot;&gt;official ZPAQ website&lt;&#x2F;a&gt;, it looks like it
is designed for &quot;realistic backups that have a lot of duplicate files and a lot
of already compressed files.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;What I find very cool about ZPAQ is that it is an &lt;em&gt;incremental&lt;&#x2F;em&gt; journaling
archiver, meaning you can add files to an existing archive based on if they were
changed or not which reduces the time needed to wait for a new backup to finish.
If other tools here are capable of this, I have not seen it advertised, but this
is particularly cool for ZPAQ since it is so focused on compression ratio &amp;amp; in
practice, this kind of feature may reduce the burden imposed by long compression
times. ZPAQ archives aren&#x27;t handled by default on my Linux installation in
Nautilus.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;results&quot;&gt;Results&lt;&#x2F;h1&gt;
&lt;h3 id=&quot;compression&quot;&gt;Compression&lt;&#x2F;h3&gt;
&lt;p&gt;Here are my compression performance results, measured in real time. This graph
uses a logarithmic horizontal scale factor:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;compression_realtime.svg&quot; alt=&quot;compression_realtime_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;My personal takeaways from this test:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;This is very much not fair to Brotli, as the reference encoder can only use 1
thread at a time, disadvantaging it in real clock-on-the-wall time comparisons
like this one. Wait for the next test&lt;&#x2F;li&gt;
&lt;li&gt;Zstandard is indeed competitive with xz &amp;amp; 7zip, although it isn&#x27;t quite as
good. Higher effort ZPAQ pulls far ahead of everything&lt;&#x2F;li&gt;
&lt;li&gt;ZIP is, predictably, behind the pack by a good amount. Can&#x27;t say I&#x27;m surprised
:P&lt;&#x2F;li&gt;
&lt;li&gt;XZ extreme vs. normal doesn&#x27;t seem to have a meaningful effect on&lt;&#x2F;li&gt;
&lt;li&gt;Low-effort Zstandard is &lt;em&gt;really&lt;&#x2F;em&gt; fast, and competes with ZIP despite being
much faster. Mission accomplished?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Here are my compression performance results, measured in &lt;em&gt;user + system&lt;&#x2F;em&gt; time
(the time the computer uses across its resources, added up). This graph uses a
logarithmic horizontal scale factor:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;compression_usrtime.svg&quot; alt=&quot;compression_usrtime_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;My personal takeaways from this test:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Not sure why I omitted ZPAQ from this test. Either way, we know from the
previous test it probably would have looked the same considering it is
multithreaded&lt;&#x2F;li&gt;
&lt;li&gt;Now, Brotli looks much more competitive. Not sure if allowing the other
encoders to use multiple threads hampers their performance at all, though I
don&#x27;t know why it would&lt;&#x2F;li&gt;
&lt;li&gt;This makes zstd look much more competitive with XZ &amp;amp; 7zip&lt;&#x2F;li&gt;
&lt;li&gt;ZIP still lags behind. Using this older Deflate algorithm isn&#x27;t going to save
you any compute resources compared to modern options like Brotli &amp;amp; zstd&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;decompression&quot;&gt;Decompression&lt;&#x2F;h3&gt;
&lt;p&gt;Evaluating any of these compression standards for practical use means you must
take decompression into account. Let&#x27;s look at the first batch of results for
real time, where I tested decompression performance for the &lt;strong&gt;lowest &amp;amp; highest
levels of compression effort&lt;&#x2F;strong&gt; for each standard. Once again, this graph uses a
logarithmic horizontal scale factor:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;decompression_realtime.svg&quot; alt=&quot;decompression_realtime_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;My personal takeaways from this test:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Yikes, not looking good for ZPAQ in this test. It definitely showed its
strengths before as an archival format, but as anything else, it doesn&#x27;t look
viable&lt;&#x2F;li&gt;
&lt;li&gt;The gap between XZ &amp;amp; 7zip is pretty astonishing. I&#x27;d like to see in the next
test if this has to do with 7zip taking advantage of multiple threads more
effectively, but either way, 7zip clearly has the better user experience&lt;&#x2F;li&gt;
&lt;li&gt;I&#x27;m once again left wondering how much better Brotli will perform when given
the opportunity to level the playing field with the other standards&lt;&#x2F;li&gt;
&lt;li&gt;ZIP still isn&#x27;t good&lt;&#x2F;li&gt;
&lt;li&gt;Considering decompression performance is one of Zstandard&#x27;s highlights, 7zip
makes a strong case for being a better choice if Zstandard can&#x27;t compete in
the next test&lt;&#x2F;li&gt;
&lt;li&gt;That third pink X next to the one closest to the bottom for XZ is
decompression performance for the &quot;extreme&quot; version of the highest compression
level. Not much difference, again&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Finally, here are the user + sys time results for decompression. This should
tell us a more complete story about why 7zip is so fast in real time. This graph
uses a logarithmic horizontal scale factor:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;decompression_usrtime.svg&quot; alt=&quot;decompression_usrtime_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;My personal takeaways from this test:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;ZPAQ is still not great&lt;&#x2F;li&gt;
&lt;li&gt;The gap has closed a &lt;em&gt;lot&lt;&#x2F;em&gt; between 7zip &amp;amp; XZ, showing that under the hood,
7zip is really just a more advanced XZ with more thoughtfully designed
utilities optimized for desktop use where a decoder can comfortably make use
of multi-threading on powerful systems&lt;&#x2F;li&gt;
&lt;li&gt;ZIP isn&#x27;t that bad compared to 7zip &amp;amp; XZ! This is the first &lt;em&gt;real&lt;&#x2F;em&gt; win ZIP
pulls out, in my opinion, considering weaker systems may see performance more
similar to this test for real time decompression than my previous test&lt;&#x2F;li&gt;
&lt;li&gt;Brotli is good, but zstd is clearly in a class by itself. Now it makes a lot
more sense why decompression performance was emphasized so much, and why zstd
is the optimal choice for filesystem compression&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h1&gt;
&lt;p&gt;My conclusion overall is that there really are different scenarios where each
format shines. I&#x27;ll explain for each format:&lt;&#x2F;p&gt;
&lt;p&gt;Firstly, &lt;strong&gt;ZPAQ&lt;&#x2F;strong&gt; is going to be my choice for computer backups if I ever do
them (I often don&#x27;t). The incremental option alongside the incredible
compression ratio makes it the best option for this use case, in my opinion,
given you&#x27;ll be willing to allow it to take its sweet time as it decompresses.
For a backup format, I don&#x27;t think that&#x27;s the biggest deal in the world anyway.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;7zip&lt;&#x2F;strong&gt; seems like the best option for general desktop use, and if you&#x27;re
distributing files, providing &lt;code&gt;.7z&lt;&#x2F;code&gt; archives might not be a terrible idea if
you&#x27;re looking to phase out ZIP to save server space &amp;amp; bandwidth while
maintaining compatibility. The last holdout for support is Android, as Windows
11 can now decompress 7zip archives. 7zip&#x27;s widespread support as well as its
good performance earn it this position. Did you know even iOS can preview the
contents of 7zip archives?&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;XZ&lt;&#x2F;strong&gt; is a bit of a black sheep here in a world where 7zip exists. 7zip &amp;amp; XZ
are both open source, too. Tell me if XZ has some hidden strengths I&#x27;m missing.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Brotli&lt;&#x2F;strong&gt; as a replacement for simpler algorithms on the Web for content
delivery makes complete sense. It decompresses fast &amp;amp; compresses well, and can
be parallelized using other implementations to optimize real time encode
performance. In the meantime, servers are more concerned with user &amp;amp; system
time, so those graphs are likely more salient when considering Brotli
performance where it is applicable.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Zstandard&lt;&#x2F;strong&gt; is what I&#x27;d consider to be the &lt;em&gt;future&lt;&#x2F;em&gt; of all of the above
categories. In certain scenarios, if it takes off as a content delivery format,
I could see it replacing Brotli if the benefits of super-fast &amp;amp; super-light
decode improve the responsiveness of web pages &amp;amp; are worth sacrificing a bit of
compression ratio. When using the much higher effort settings, it actually beats
Brotli on all accounts. I&#x27;d be happy to pass around &lt;code&gt;.tar.zst&lt;&#x2F;code&gt; archives in the
future to replace 7zip &amp;amp; ZIP, making speedy decode a reality on systems of all
different measures of compute performance. I&#x27;m glad Zstandard is making its way
as a great format to use in the FOSS community for innovative new
implementations of compression like filesystem compression &amp;amp; accessible package
compression that doesn&#x27;t tax your CPU when decoding, but I&#x27;d personally like to
see its use cases expand in the future.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;ZIP&lt;&#x2F;strong&gt;&#x27;s Deflate implementation is very clearly useful for one thing:
compatibility. I&#x27;m glad the Deflate patent has expired, otherwise I&#x27;d be much
more disappointed in ZIP&#x27;s standardization as the default compression archive
format in most situations. Personally, for the time being, I&#x27;m just glad we
didn&#x27;t unintentionally standardize around &lt;em&gt;uncompressed&lt;&#x2F;em&gt; archives, as I&#x27;d be
willing to bet many don&#x27;t know ZIPs are actually compressed.&lt;&#x2F;p&gt;
&lt;p&gt;What do you think of my results? Is there anything I missed or could have done
better? Should I include bzip2 or some other algorithm next time? Let me know.
Thank you for reading!&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>XYB JPEG</title>
        <published>2023-07-16T00:00:00+00:00</published>
        <updated>2023-07-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Gianni Rosato
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://giannirosato.com/blog/post/jpegli-xyb/"/>
        <id>https://giannirosato.com/blog/post/jpegli-xyb/</id>
        <content type="html" xml:base="https://giannirosato.com/blog/post/jpegli-xyb/">&lt;p&gt;A brand new JPEG implementation from Google – called jpegli – supports the use of a perceptual colorspace to improve compression efficiency. How does it fare against other codecs, like WebP and AVIF?&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;h1 id=&quot;xyb-jpeg&quot;&gt;XYB JPEG&lt;&#x2F;h1&gt;
&lt;p&gt;This is going to be a shorter post because I have done this kind of visual
quality evaluation three times now, so if you&#x27;re not sure what you&#x27;re reading,
please look at my
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;giannirosato.com&#x2F;blog&#x2F;post&#x2F;image-comparison&#x2F;&quot;&gt;Image Codec Comparison&lt;&#x2F;a&gt;
post from March &amp;amp; my
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;giannirosato.com&#x2F;blog&#x2F;post&#x2F;jpegli&#x2F;&quot;&gt;Mini Image Codec Comparison&lt;&#x2F;a&gt; from
just last month. This may be the last one of these kinds of posts for a while
unless something interesting can be wrung out of another SSIMULACRA2 plot. Maybe
with comparing speeds next time?&lt;&#x2F;p&gt;
&lt;p&gt;The reason I&#x27;m making this post is that I just discovered that the &lt;code&gt;ssimulacra2&lt;&#x2F;code&gt;
binary that comes with libjxl can properly decode &amp;amp; benchmark XYB JPEG encoded
with &lt;code&gt;cjpegli&lt;&#x2F;code&gt; &amp;amp; I wanted to test it for myself. The source images &amp;amp; results are
published here, but first I think it is worth explaining in greater detail what
the XYB color space is.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;perceptual-color&quot;&gt;Perceptual Color&lt;&#x2F;h2&gt;
&lt;p&gt;A perceptual color space is a color space designed to work around certain
assumptions about the human visual system. One of these assumptions is
trichromacy, which implies we can perceptually understand three independent
color channels; the response from each channel can be attenuated or bolstered by
the stimulus it receives, which allows us to see a myriad of different colors
due to different wavelengths of light in the visual spectrum interacting at
different intensities. We assume trichromacy in humans due to the presence of
three types of cones in our eyes: one that receives green light, another that
receives red, &amp;amp; a final cone that can perceive blue light. These are the three
additive primaries in our trichromatic visual system. Our cones &amp;amp; their
respective responses are illustrated in the graph below.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;cones_response.svg&quot; alt=&quot;cones_response&quot; &#x2F;&gt; &lt;em&gt;Wikimedia Commons. CC BY
3.0&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;If you were to place two coordinates in a perfect perceptual color space, the
distance between the coordinates should map perfectly to our perception of their
color difference. The problem is color perception is non-Euclidian, &amp;amp; this is an
impossible problem to completely solve. W. David Wright &amp;amp; John Guild were up to
the challenge, however, &amp;amp; built the CIE XYZ colorspace to map more perfectly to
our human visual system than prior attempts. Instead of a red, green, &amp;amp; blue
channel, XYZ specifies a luminance channel Y (like the commonly used YCbCr) &amp;amp;
chromaticity channels Z (&quot;quasi-equal&quot; to blue) &amp;amp; X (another chromaticity
coefficient based on psychovisual testing by Wright &amp;amp; Guild; &quot;a mix of the three
CIE RGB curves chosen to be nonnegative&quot;). If you want to try to visualize it,
here&#x27;s an example of our visual gamut under D65 illumination visualized within
the XYZ color space:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;upload.wikimedia.org&#x2F;wikipedia&#x2F;commons&#x2F;transcoded&#x2F;3&#x2F;34&#x2F;Visible_gamut_within_CIEXYZ_color_space_D65_whitepoint_mesh.webm&#x2F;Visible_gamut_within_CIEXYZ_color_space_D65_whitepoint_mesh.webm.480p.vp9.webm&quot;&gt;xyz_visual&lt;&#x2F;a&gt;
&lt;em&gt;Wikimedia Commons. CC BY-SA 4.0&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;xyb-color&quot;&gt;XYB Color&lt;&#x2F;h3&gt;
&lt;p&gt;Now, that&#x27;s the &lt;em&gt;XYZ&lt;&#x2F;em&gt; color space. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;youtu.be&#x2F;rvhf6feXw7w&quot;&gt;XYB&lt;&#x2F;a&gt; is an
LMS-based color space. LMS stands for long, medium, and short, representing the
response our cones have to different visible spectra, &amp;amp; is newer than XYZ. The
same general principle of modeling colors based on our cone response still
applies to both, though, &amp;amp; I think talking about XYZ is the best way to explain
perceptual color on a basic level. The labels on the cones_response SVG above
show our L, M, &amp;amp; S cones. A 3x3 matrix operation can be applied to LMS to
produce XYB, &amp;amp; you will find that XYB justifies allocating fewer bits to the S
channel (blue) due to the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;LMS_color_space#Image_processing&quot;&gt;lower density of blue cones in our eyes&lt;&#x2F;a&gt;.
This allows perceptual quantization of color values that map better to our
visual system, spending bits where we can see them instead of where they aren&#x27;t
salient to us. JPEG XL already uses XYB internally.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re interested in how jpegli works, see my
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;giannirosato.com&#x2F;blog&#x2F;post&#x2F;jpegli&#x2F;&quot;&gt;Mini Image Codec Comparison&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;methodology&quot;&gt;Methodology&lt;&#x2F;h2&gt;
&lt;p&gt;Here are the encoders I used for this test:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;cjpegli&lt;&#x2F;strong&gt; from the libjxl repos, in 4:4:4 RGB, 4:4:4 XYB, 4:2:2 XYB, &amp;amp; 4:2:0
XYB modes&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;mozjpeg&lt;&#x2F;strong&gt; &lt;code&gt;mozjpeg version 4.1.3 (build 20230612)&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;cwebp&lt;&#x2F;strong&gt; &lt;code&gt;cwebp 1.3.1 | libsharpyuv: 0.2.1&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;cjxl&lt;&#x2F;strong&gt; &lt;code&gt;cjxl v0.9.0 2a6f1f2c [AVX2]&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;avifenc&lt;&#x2F;strong&gt; via aom-av1-lavish, latest git (Endless_Merging branch)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;And here are their parameters:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cjpegli input -q [quality] N&#x2F;A&#x2F;--xyb --chroma_subsampling=[444&#x2F;422&#x2F;420] output.jpg&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;cjpeg -q [quality] input &amp;gt; [output.jpg]&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;cwebp -m 6 -q [quality] input -o output.webp&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;cjxl input output.jxl -q [quality] -e 6 -p&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;avifenc -c aom -s 8 -j 12 -d 10 -y 444 --min 1 --max 63 -a end-usage=q -a cq-level=[quality] -a tune=ssim [input] [output.avif]&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I&#x27;m also using the standard &lt;code&gt;ssimulacra2&lt;&#x2F;code&gt; binary instead of &lt;code&gt;ssimulacra2_rs&lt;&#x2F;code&gt;
because it seems to be much faster &amp;amp; can decode both JPEG &amp;amp; JXL natively.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;faster-avif-jxl-encoding&quot;&gt;Faster AVIF &amp;amp; JXL encoding&lt;&#x2F;h3&gt;
&lt;p&gt;Last blog post, Jon Sneyers suggested I use more realistic encoding speeds for
&lt;code&gt;cjxl&lt;&#x2F;code&gt; &amp;amp; &lt;code&gt;avifenc&lt;&#x2F;code&gt; as opposed to slower, more thorough speeds to test what
people might realistically end up using outside hobbyist work where codec nerds
are eager to wait an excruciatingly long time for encoding to finish. Besides, I
already have plenty of material published regarding high-effort &lt;code&gt;cjxl&lt;&#x2F;code&gt; &amp;amp;
&lt;code&gt;avifenc&lt;&#x2F;code&gt;, so let&#x27;s see what they can both do with a little speed this time
around.&lt;&#x2F;p&gt;
&lt;p&gt;One thing to note is that I&#x27;m using 12 threads to encode AVIF despite having a
24-thread system. It seems that allowing avifenc to use all available threads
harms encoding performance a bit on my machine, as was the case in my previous
blog post. At speed 8, 12 threads seemed to outperform 8, so I decided to
specify &lt;code&gt;-j 12&lt;&#x2F;code&gt; to give AVIF encoding its best shot for speed.&lt;&#x2F;p&gt;
&lt;p&gt;I decided on JXL effort 6 &amp;amp; AVIF speed 8 based on the following speed test
results on my laptop with a different image not present in this corpus. While
AVIF is still slower, they both seem to line up well enough here. &lt;code&gt;avifenc&lt;&#x2F;code&gt; is
better for overall user time &amp;amp; much better for system time in this
configuration, but &lt;code&gt;cjxl&lt;&#x2F;code&gt; wins when it comes down to how much real time it takes
to perform the benchmark which correlates to how fast it actually is between
hitting the figurative &quot;start&quot; &amp;amp; &quot;stop&quot; buttons. Because this is likely due to
&lt;code&gt;cjxl&lt;&#x2F;code&gt; utilizing system resources better, I won&#x27;t fault it for using more user &amp;amp;
system time versus avifenc.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;avifenc_cjxl_speed.svg&quot; alt=&quot;avifenc_v_jxl_speed&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Log scale:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;avifenc_cjxl_speed_log.svg&quot; alt=&quot;avifenc_v_jxl_speed&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Here are some speed benchmarks for each, using the parameters I specified
earlier. Parameter modifications are disclosed. Benchmarked with &lt;code&gt;hyperfine&lt;&#x2F;code&gt;,
tested on the first image in the corpus (test1.png).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;hyperfine --warmup 5 --runs 20 &quot;command&quot;&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;zsh inxi -v CPU: 16-core (8-mt&#x2F;8-st) 13th Gen Intel Core i7-13700K (-MST AMCP-) speed&#x2F;min&#x2F;max: 1512&#x2F;800&#x2F;5400 MHz Kernel: 6.4.3-zen1-1-zen x86_64 Up: 5h 5m Mem: 6.95&#x2F;31.12 GiB (22.3%) Storage: 6.83 TiB (2.6% used) Procs: 523 Shell: Zsh inxi: 3.3.28&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;avifenc&lt;&#x2F;strong&gt; speed 8 j12 &lt;em&gt;182.4 kB&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;% hyperfine --warmup 5 --runs 20 &quot;avifenc -c aom -s 8 -j 12 -d 10 -y 444 --min 1 --max 63 -a end-usage=q -a cq-level=21 -a tune=ssim test1.png out.avif&quot;&lt;&#x2F;code&gt;
Benchmark 1: avifenc -c aom -s 8 -j 12 -d 10 -y 444 --min 1 --max 63 -a
end-usage=q -a cq-level=21 -a tune=ssim test1.png out.avif&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Time (mean ± σ): 249.9 ms ± 6.2 ms [User: 397.2 ms, System: 19.5 ms] Range
(min … max): 243.4 ms … 271.7 ms 20 runs&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;&lt;strong&gt;cjxl&lt;&#x2F;strong&gt; e6 &lt;em&gt;180.9 kB&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;% hyperfine --warmup 5 --runs 20 &quot;cjxl test1.png out.jxl -d 1.0 -e 6 -p -v&quot;&lt;&#x2F;code&gt;
Benchmark 1: cjxl test1.png out.jxl -d 1.0 -e 6 -p -v&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Time (mean ± σ): 192.1 ms ± 2.3 ms [User: 641.1 ms, System: 666.6 ms] Range
(min … max): 188.7 ms … 199.7 ms 20 runs&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;&lt;strong&gt;cwebp&lt;&#x2F;strong&gt; m6 &lt;em&gt;177.2 kB&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;% hyperfine --warmup 5 --runs 20 &quot;cwebp -m 6 -q 90 test1.png -o out.webp&quot;&lt;&#x2F;code&gt;
Benchmark 1: cwebp -m 6 -q 90 test1.png -o out.webp&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Time (mean ± σ): 342.9 ms ± 1.0 ms [User: 337.4 ms, System: 4.5 ms] Range
(min … max): 341.0 ms … 344.9 ms 20 runs&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;cjpeg (&lt;strong&gt;mozjpeg&lt;&#x2F;strong&gt;) &lt;em&gt;184.1 kB&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;% hyperfine --warmup 5 --runs 20 &quot;cjpeg -q 80 test1.png &amp;gt; out.jpeg&quot;&lt;&#x2F;code&gt; Benchmark
1: cjpeg -q 80 test1.png &amp;gt; out.jpeg&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Time (mean ± σ): 196.7 ms ± 1.2 ms [User: 191.2 ms, System: 5.1 ms] Range
(min … max): 195.3 ms … 199.7 ms 20 runs&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;cjpegli &lt;strong&gt;4:4:4 rgb&lt;&#x2F;strong&gt; &lt;em&gt;184.8 kB&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;% hyperfine --warmup 5 --runs 20 &quot;cjpegli test1.png -d 1.4 --chroma_subsampling=444 out_444.jpeg&quot;&lt;&#x2F;code&gt;
Benchmark 1: cjpegli test1.png -d 1.4 --chroma_subsampling=444 out_444.jpeg&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Time (mean ± σ): 139.8 ms ± 0.8 ms [User: 132.0 ms, System: 7.5 ms] Range
(min … max): 138.3 ms … 141.5 ms 20 runs&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;cjpegli &lt;strong&gt;4:4:4 xyb&lt;&#x2F;strong&gt; &lt;em&gt;179.5 kB&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;% hyperfine --warmup 5 --runs 20 &quot;cjpegli test1.png --xyb -d 1.4 --chroma_subsampling=444 out_444-xyb.jpeg&quot;&lt;&#x2F;code&gt;
Benchmark 1: cjpegli test1.png --xyb -d 1.4 --chroma_subsampling=444
out_444-xyb.jpeg&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Time (mean ± σ): 156.7 ms ± 0.8 ms [User: 148.7 ms, System: 7.6 ms] Range
(min … max): 155.4 ms … 158.3 ms 20 runs&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;cjpegli &lt;strong&gt;4:2:2 xyb&lt;&#x2F;strong&gt; &lt;em&gt;178.9 kB&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;% hyperfine --warmup 5 --runs 20 &quot;cjpegli test1.png --xyb -d 0.9 --chroma_subsampling=422 out_422-xyb.jpeg&quot;&lt;&#x2F;code&gt;
Benchmark 1: cjpegli test1.png --xyb -d 0.9 --chroma_subsampling=422
out_422-xyb.jpeg&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Time (mean ± σ): 149.8 ms ± 0.8 ms [User: 141.9 ms, System: 7.5 ms] Range
(min … max): 148.4 ms … 151.8 ms 20 runs&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;cjpegli &lt;strong&gt;4:2:0 xyb&lt;&#x2F;strong&gt; &lt;em&gt;186.4 kB&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;% hyperfine --warmup 5 --runs 20 &quot;cjpegli test1.png --xyb -d 0.5 --chroma_subsampling=420 out_420-xyb.jpeg&quot;&lt;&#x2F;code&gt;
Benchmark 1: cjpegli test1.png --xyb -d 0.5 --chroma_subsampling=420
out_420-xyb.jpeg&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Time (mean ± σ): 147.8 ms ± 0.7 ms [User: 139.8 ms, System: 7.6 ms] Range
(min … max): 146.5 ms … 149.0 ms 20 runs&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;enc_speeds_faster.svg&quot; alt=&quot;speedtest_all&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;photographic-images&quot;&gt;Photographic Images&lt;&#x2F;h2&gt;
&lt;p&gt;Here are some lossy previews of the four images that were tested. &lt;em&gt;These will
load properly on a browser supporting JXL, and on other browsers will fall back
to XYB JPEG.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;test1-xyb.jpg&quot; alt=&quot;XYB Test 1 Image&quot; &#x2F;&gt;
&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;test2-xyb.jpg&quot; alt=&quot;XYB Test 2 Image&quot; &#x2F;&gt;
&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;test3-xyb.jpg&quot; alt=&quot;XYB Test 3 Image&quot; &#x2F;&gt;
&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;test4-xyb.jpg&quot; alt=&quot;XYB Test 4 Image&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Here are the test results for each image!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;xyb-test1.svg&quot; alt=&quot;test1_results_xyb&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This was an incredibly shocking first showing for XYB JPEG, outperforming other
JPEGs, WebP, &amp;amp; even AVIF at the critical high-fidelity range. There&#x27;s some
weirdness going on with mixing chroma subsampling &amp;amp; XYB color for some reason,
with an incredibly low quality ceiling that is hit very quickly. For 4:4:4, this
is impressive to see XYB JPEG come second only to JXL.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;xyb-test2.svg&quot; alt=&quot;test2_results_xyb&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Here, once again, XYB JPEG impresses by surpassing AVIF entirely at medium &amp;amp;
higher fidelity while putting the other JPEG encoders to shame. I am floored by
how well this performs, although JXL remains a better option overall.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;xyb-test3.svg&quot; alt=&quot;test3_results_xyb&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This is more what I&#x27;d expect out of AVIF, as it seems to be suffering in these
other tests due to the faster preset we&#x27;re using. I&#x27;m still impressed with XYB
JPEG&#x27;s ability to surpass WebP at 75+ SSIMU2.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;xyb-test4.svg&quot; alt=&quot;test4_results_xyb&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Pretty similar to before, XYB JPEG handily beats WebP while still being
outperformed by AVIF. I&#x27;m still wowed by the fact that the &lt;em&gt;ancient&lt;&#x2F;em&gt; JPEG is
still even reasonably competitive with AVIF, but seeing how much better JXL does
throughout these tests, it is clear the modern formats still have hope.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;extrapolations&quot;&gt;Extrapolations&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;xyb_results_avg.svg&quot; alt=&quot;avg_xyb_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;On average, seeing such a good showing from XYB JPEG has inspired me to start
using the format more often. Anywhere ICC profiles are supported along with
JPEG, XYB JPEG will be supported. I&#x27;m interested to see how it&#x27;d perform on a
bigger dataset against higher effort JXL &amp;amp; AVIF, but for now, I&#x27;m satisfied with
its performance enough to argue for its adoption.&lt;&#x2F;p&gt;
&lt;p&gt;The one snag is ICC color management support, although I&#x27;ve yet to run into a
modern browser that doesn&#x27;t pass
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;cameratico.com&#x2F;tools&#x2F;web-browser-color-management-test&#x2F;&quot;&gt;these tests&lt;&#x2F;a&gt;.
You can try it for yourself if you like, I&#x27;m not sure which browsers &lt;em&gt;don&#x27;t&lt;&#x2F;em&gt;
support ICCv2 color management, but those would not deal with XYB JPEG properly.&lt;&#x2F;p&gt;
&lt;p&gt;Inspiration &amp;amp; Sources:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;CIE_1931_color_space#Definition_of_the_CIE_XYZ_color_space&quot;&gt;en.wikipedia.org&#x2F;wiki&#x2F;CIE_1931_color_space#Definition_of_the_CIE_XYZ_color_space&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;raphlinus.github.io&#x2F;color&#x2F;2021&#x2F;01&#x2F;18&#x2F;oklab-critique.html&quot;&gt;raphlinus.github.io&#x2F;color&#x2F;2021&#x2F;01&#x2F;18&#x2F;oklab-critique.html&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;LMS_color_space#Image_processing&quot;&gt;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;LMS_color_space#Image_processing&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;observablehq.com&#x2F;@mattdesl&#x2F;perceptually-smooth-multi-color-linear-gradients&quot;&gt;observablehq.com&#x2F;@mattdesl&#x2F;perceptually-smooth-multi-color-linear-gradients&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Thanks for reading!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;sponsor-me-on-github-sponsors&quot;&gt;Sponsor Me on GitHub Sponsors&lt;&#x2F;h3&gt;
&lt;p&gt;Help support my open source efforts - a little goes a long way!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;sponsors&#x2F;gianni-rosato?o=esc&quot;&gt;Sponsor&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Better JPEG Encoding From Google</title>
        <published>2023-06-14T00:00:00+00:00</published>
        <updated>2023-06-14T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Gianni Rosato
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://giannirosato.com/blog/post/jpegli/"/>
        <id>https://giannirosato.com/blog/post/jpegli/</id>
        <content type="html" xml:base="https://giannirosato.com/blog/post/jpegli/">&lt;p&gt;JPEG is old, but a new perceptually optimized encoder just became available from Google called jpegli. Like the mozjpeg encoder aimed to outperform older JPEG implementations, jpegli aims to push the format to its limits in terms of compression efficiency.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;h2 id=&quot;why&quot;&gt;Why?&lt;&#x2F;h2&gt;
&lt;p&gt;JPEG is a really neat codec. It has served us super well for a number of years.
JPEG&#x27;s ingeniously simple design that divides an image into 8x8 pixel blocks,
separates chroma (color) from luma (luminance) into the YCbCr color space (you
can also use RGB), applies the DCT, quantizes the DCT coefficients, &amp;amp; passes the
data on to the decoder makes JPEG images very easy to read and write nowadays.
They look good, too; in fact, they are looking better and better as their
encoding implementations (somehow) continue to improve; MozJPEG improved quality
relative to libjpeg-turbo &amp;amp; older implementations, especially at low to medium
fidelity; jpegli promises to further improve over MozJPEG, potentially across
the board. JPEG also still has features that many modern codecs don&#x27;t have that
are very useful, like support for progressive decode which allows parts of the
image to be sent as the image data is being transferred.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;blurha.sh&#x2F;&quot;&gt;Blurhashes&lt;&#x2F;a&gt; are very popular for their ability to send some
data before the entire image, and progressive decode enhances this even further.
WebP, HEIC, &amp;amp; AVIF don&#x27;t support progressive decode*, although JPEG XL does.
Outside of features, JPEG supposedly became competitive with WebP in coding
efficiency when MozJPEG released. This is contrary to the narrative that WebP is
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;developers.google.com&#x2F;speed&#x2F;webp&#x2F;docs&#x2F;webp_study&quot;&gt;25%-34% smaller&lt;&#x2F;a&gt; than
JPEG (which, if you read the article, isn&#x27;t the conclusion here; Google
specifically says &quot;We observed that the average WebP file size is 25%-34%
smaller compared to JPEG file size at equivalent SSIM index&quot; which is important
because SSIM is a rather outdated metric at this point &amp;amp; largely isn&#x27;t
indicative of perceptual quality. They also tested libjpeg, not mozjpeg). Could
jpegli be so efficient that it results in WebP being &lt;em&gt;entirely&lt;&#x2F;em&gt; outcompeted by a
codec that&#x27;s nearly &lt;em&gt;twenty years&lt;&#x2F;em&gt; older?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-jpegli-works&quot;&gt;How Jpegli Works&lt;&#x2F;h2&gt;
&lt;p&gt;Jpegli&#x27;s gains are largely due to better adaptive quantization based on the
heuristics used by JPEG XL. Projects like
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;google&#x2F;guetzli&quot;&gt;Guetzli&lt;&#x2F;a&gt; have achieved better JPEG
compression through similar means, but have been &lt;em&gt;really&lt;&#x2F;em&gt; slow to work with. It
is outlined in the readme for Guetzli that it is not fast:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Guetzli uses a significant amount of CPU time. You should count on using about
1 minute of CPU per 1 MPix of input image.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Another smart jpegli trick is usage of the XYB colorspace, which is perceptually
more in line with the human visual system compared to RGB. We haven&#x27;t tested
that today because SSIMULACRA2.1 doesn&#x27;t understand what it is looking at when
given XYB JPEGs (even properly transcoded to PNG, there&#x27;s some sort of harmful
color shift), but JPEG XL uses the XYB color space by default for lossy image
coding and the results are clearly incredible for coding efficiency.&lt;&#x2F;p&gt;
&lt;p&gt;The way jpegli handles XYB color in a JPEG image is by applying an ICC color
profile that maps the existing JPEG color channels to XYB. This actually has the
potential to increase the bit depth of the image, which could allow 10 bit JPEGs
in the future. I&#x27;m excited to see XYB JPEG continue to improve via jpegli, but
for now we&#x27;re just going going to use libjxl&#x27;s included &lt;code&gt;cjpegli&lt;&#x2F;code&gt; binary to test
some photographic images.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;methodology-photographic&quot;&gt;Methodology (Photographic)&lt;&#x2F;h2&gt;
&lt;p&gt;Here are the encoders I used for this test:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;cjpegli&lt;&#x2F;strong&gt; from the libjxl repos, in 4:4:4, 4:2:2, &amp;amp; 4:2:0 chroma subsampling
modes&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;mozjpeg&lt;&#x2F;strong&gt; &lt;code&gt;mozjpeg version 4.1.1 (build 20230217)&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;cwebp&lt;&#x2F;strong&gt; &lt;code&gt;cwebp 1.3.0 | libsharpyuv: 0.2.0&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;cjxl&lt;&#x2F;strong&gt; via &lt;code&gt;cjxl v0.9.0 e2fe7bad [AVX2]&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;avifenc&lt;&#x2F;strong&gt; via aom-av1-lavish, latest git (Endless_Merging branch)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;And here are their parameters:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cjpegli input -q [quality] --chroma_subsampling=[444&#x2F;422&#x2F;420] output.jpg&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;cjpeg -q [quality] input &amp;gt; [output.jpg]&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;cwebp -m 6 -q [quality] input -o output.webp&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;cjxl input output.jxl -q [quality] -e 8&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;avifenc -c aom -s 4 -j 8 -d 10 -y 444 --min 1 --max 63 -a end-usage=q -a cq-level=[quality] -a tune=ssim [input] [output.avif]&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Here are some speed benchmarks for each, using the parameters above. Parameter
modifications are disclosed. Benchmarked with &lt;code&gt;hyperfine&lt;&#x2F;code&gt;, tested on the first
image in the corpus:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;hyperfine --warmup 2 --runs 20 &quot;command&quot;&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;inxi -v CPU: 16-core (8-mt&#x2F;8-st) 13th Gen Intel Core i7-13700K (-MST AMCP-) speed&#x2F;min&#x2F;max: 1386&#x2F;800&#x2F;5400 MHz Kernel: 6.3.7-zen1-1-zen x86_64 Up: 29m Mem: 5633.8&#x2F;31868.7 MiB (17.7%) Storage: 6.83 TiB (20.4% used) Procs: 520 Shell: Zsh inxi: 3.3.27&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;cjpegli&lt;&#x2F;strong&gt; -q 90, --chroma_subsampling=444: &lt;em&gt;156.1 kB&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Time (mean ± σ): 80.1 ms ± 0.7 ms [User: 75.7 ms, System: 4.2 ms] Range
(min … max): 79.1 ms … 81.4 ms 20 runs&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;cjpegli&lt;&#x2F;strong&gt; -q 90, --chroma_subsampling=422: &lt;em&gt;145.4 kB&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Time (mean ± σ): 74.3 ms ± 0.7 ms [User: 70.2 ms, System: 3.9 ms] Range
(min … max): 73.2 ms … 76.0 ms 20 runs&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;cjpegli&lt;&#x2F;strong&gt; -q 90, --chroma_subsampling=420: &lt;em&gt;132.2 kB&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Time (mean ± σ): 70.6 ms ± 0.5 ms [User: 66.3 ms, System: 4.1 ms] Range
(min … max): 69.8 ms … 71.5 ms 20 runs&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;mozjpeg&lt;&#x2F;strong&gt; -q 86: &lt;em&gt;154.8 kB&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Time (mean ± σ): 105.0 ms ± 0.9 ms [User: 101.6 ms, System: 3.0 ms] Range
(min … max): 103.5 ms … 107.6 ms 20 runs&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;cwebp&lt;&#x2F;strong&gt; -q 90: &lt;em&gt;130.7 kB&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Time (mean ± σ): 194.4 ms ± 1.4 ms [User: 190.5 ms, System: 3.6 ms] Range
(min … max): 191.8 ms … 197.1 ms 20 runs&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;cjxl&lt;&#x2F;strong&gt; -q 92: &lt;em&gt;134.0 kB&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Time (mean ± σ): 768.1 ms ± 7.0 ms [User: 1068.8 ms, System: 398.4 ms]
Range (min … max): 758.6 ms … 784.1 ms 20 runs&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;avifenc&lt;&#x2F;strong&gt; cq-level=11: &lt;em&gt;155.5 kB&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Time (mean ± σ): 1.500 s ± 0.008 s [User: 3.670 s, System: 0.016 s] Range
(min … max): 1.493 s … 1.529 s 20 runs&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;avifenc&lt;&#x2F;strong&gt; cq-level=11 -j all: &lt;em&gt;155.5 kB&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Time (mean ± σ): 1.571 s ± 0.132 s [User: 3.778 s, System: 0.019 s] Range
(min … max): 1.495 s … 1.975 s 20 runs&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;jpegli_speeds.svg&quot; alt=&quot;test1_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Using all available threads with avifenc would routinely produce slightly worse
results, and Hyperfine threw up an error every time suggesting some results were
appearing to be outliers. This is why I lowered the worker count to 8 for the
quality benchmark. I found this anomalous behavior interesting as it highlights
avifenc&#x27;s trouble with scaling that stems from issues with the AVIF image
format. I would wager a guess that this scaling issue is because of AVIF&#x27;s use
of certain intra-frame coding techniques like directional prediction that share
data between blocks, theoretically improving coding efficiency but reducing
parallelization &amp;amp; worsening generation loss. If I recall correctly, JPEG XL
specifically avoided intra coding techniques like these in order to parallelize
effectively (and improve resilience to generation loss).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;photographic-images&quot;&gt;Photographic Images&lt;&#x2F;h2&gt;
&lt;p&gt;Here are some lossy previews of the four images that were tested. &lt;em&gt;These will
load properly on a browser supporting JXL, and on other browsers will be
transcoded to PNG.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;jpegli_test1.jxl&quot; alt=&quot;test1&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;jpegli_test2.jxl&quot; alt=&quot;test2&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;jpegli_test3.jxl&quot; alt=&quot;test3&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;jpegli_test4.jxl&quot; alt=&quot;test4&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;And, here are the test results for each image! It is worth disclosing that in my
analyses, I prefer to look at the 60-70+ range that I consider most useful.
Others may disagree, so please take my rankings for what they are: my opinions.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;jpegli_test1.svg&quot; alt=&quot;test1_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The first image favors jpegli quite a bit here over mozjpeg. The medium-high
fidelity range from 60-80 is kind of a tie between all three chroma subsampling
modes &amp;amp; WebP, while mozjpeg lags behind. It is worth noting that WebP&#x27;s overall
quality ceiling is lower here, with a max score of around 90.&lt;&#x2F;p&gt;
&lt;p&gt;My picks:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;JXL&lt;&#x2F;li&gt;
&lt;li&gt;AVIF&lt;&#x2F;li&gt;
&lt;li&gt;jpegli 4:2:0&lt;&#x2F;li&gt;
&lt;li&gt;WebP&lt;&#x2F;li&gt;
&lt;li&gt;jpegli 4:4:4&lt;&#x2F;li&gt;
&lt;li&gt;jpegli 4:2:2&lt;&#x2F;li&gt;
&lt;li&gt;mozjpeg&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;jpegli_test2.svg&quot; alt=&quot;test2_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The simple lines and large sections of color in this image remind me of a
nonphotographic image, and therefore favor AVIF &amp;amp; WebP which are derived from
video codecs (AV1 &amp;amp; VP8 respectively). Although it looks like 4:4:4 jpegli does
well at scores &amp;gt;95, that medium-high fidelity range is dominated by mozjpeg.
WebP&#x27;s quality ceiling is really low here, and it appears incapable of hitting a
score of 90 which is where SSIMULACRA2.1 starts to consider an image
perceptually lossless.&lt;&#x2F;p&gt;
&lt;p&gt;My picks:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;AVIF&lt;&#x2F;li&gt;
&lt;li&gt;JXL&lt;&#x2F;li&gt;
&lt;li&gt;WebP&lt;&#x2F;li&gt;
&lt;li&gt;mozjpeg&lt;&#x2F;li&gt;
&lt;li&gt;jpegli 4:4:4&lt;&#x2F;li&gt;
&lt;li&gt;jpegli 4:2:0&lt;&#x2F;li&gt;
&lt;li&gt;jpegli 4:2:2&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;jpegli_test3.svg&quot; alt=&quot;test3_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This one looks like a toss-up the whole time between jpegli, mozjpeg, and webp,
with AVIF pulling ahead &amp;amp; JXL pulling even further ahead. Jpegli 4:4:4 is
consistent all the way across, while mozjpeg starts to drop off at higher
fidelity &amp;amp; WebP sadly hits its quality ceiling just before cresting the 90 mark
while still maintaining strong coding efficiency throughout the rest of the
quality scale where it is present.&lt;&#x2F;p&gt;
&lt;p&gt;My picks:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;JXL&lt;&#x2F;li&gt;
&lt;li&gt;AVIF&lt;&#x2F;li&gt;
&lt;li&gt;jpegli 4:4:4&lt;&#x2F;li&gt;
&lt;li&gt;WebP&lt;&#x2F;li&gt;
&lt;li&gt;mozjpeg&lt;&#x2F;li&gt;
&lt;li&gt;jpegli 4:2:0&lt;&#x2F;li&gt;
&lt;li&gt;jpegli 4:2:2&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;jpegli_test4.svg&quot; alt=&quot;test4_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This is the closest of all. I am once again disappointed that WebP is
outperformed by jpegli 4:4:4 at around 83+ &amp;amp; can&#x27;t seem to score higher than a
90, while jpegli 4:4:4 &amp;amp; mozjpeg cruise up to around 95. That extra headroom is
important for photography &amp;amp; lots of use cases outside of web delivery, which is
why it is important to me that a codec is able to perform throughout the quality
spectrum without bumping its head too early. Here we see AVIF &amp;amp; JXL behaving
similarly to my
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;giannirosato.com&#x2F;blog&#x2F;post&#x2F;image-comparison&#x2F;&quot;&gt;Image Codec Comparison&lt;&#x2F;a&gt;
blog post, with strong low fidelity performance from AVIF &amp;amp; better high fidelity
results coming out of JXL.&lt;&#x2F;p&gt;
&lt;p&gt;My picks:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;JXL&lt;&#x2F;li&gt;
&lt;li&gt;AVIF&lt;&#x2F;li&gt;
&lt;li&gt;jpegli 4:4:4&lt;&#x2F;li&gt;
&lt;li&gt;WebP&lt;&#x2F;li&gt;
&lt;li&gt;mozjpeg&lt;&#x2F;li&gt;
&lt;li&gt;jpegli 4:2:0&lt;&#x2F;li&gt;
&lt;li&gt;jpegli 4:2:2&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;extrapolations&quot;&gt;Extrapolations&lt;&#x2F;h2&gt;
&lt;p&gt;First, if you don&#x27;t know what chroma subsampling is, I highly recommend you
check out &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;master-of-zen.xyz&#x2F;yuv-vs-rgb&#x2F;&quot;&gt;Master of Zen&#x27;s explainer&lt;&#x2F;a&gt;. It
is a more detailed explanation, &amp;amp; it thoroughly covers the effects of chroma
subsampling.&lt;&#x2F;p&gt;
&lt;p&gt;It is worth noting that I didn&#x27;t try &lt;em&gt;every&lt;&#x2F;em&gt; mozjpeg tune here, and I have seen
the SSIM &amp;amp; MS-SSIM tunes work well compressing photographic images. I also
didn&#x27;t test every chroma subsampling mode that mozjpeg has to offer, and allowed
mozjpeg to subsample chroma automatically - this automatic selection gives it a
theoretical advantage over each &lt;em&gt;individual&lt;&#x2F;em&gt; jpegli chroma subsampling mode, but
wouldn&#x27;t allow the encoder to display its breadth of competence like jpegli
here. In the end, I wouldn&#x27;t call this a loss for mozjpeg anyhow - it is clearly
not obsolete in any case, and the only scenario in which it clearly lost was the
first image test. The overall average &amp;amp; three image average results make a
compelling case for mozjpeg overall.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;jpegli_avg.svg&quot; alt=&quot;avg_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;jpegli_someavg.svg&quot; alt=&quot;someavg_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;While four images isn&#x27;t anything significant to extrapolate any real conclusions
about these encoders, we can see some clear differences between the different
encoders. In particular, I think the notion that WebP is noticeably more
efficient than JPEG is almost entirely negated by modern JPEG encoders like
mozjpeg &amp;amp; now jpegli. WebP is especially weak outside of the Web, where higher
fidelity is more precious.&lt;&#x2F;p&gt;
&lt;p&gt;Stepping back from my personal preference for high fidelity, it is very
impressive how consistently well jpegli 4:2:0 is able to do when the target is
&amp;lt;50-60. This is usually WebP&#x27;s territory (ignoring AVIF &amp;amp; JXL for a moment)
relative to JPEG, but 4:2:0 jpegli is closer than mozjpeg to WebP on every image
except the second one. 4:2:2 jpegli seems like it doesn&#x27;t have much of a use at
all.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;should-i-use-it&quot;&gt;Should I Use It?&lt;&#x2F;h2&gt;
&lt;p&gt;I would say yes, given you:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Cannot use AVIF or JXL.&lt;&#x2F;li&gt;
&lt;li&gt;Can&#x27;t use WebP, or if you can, it compresses worse.&lt;&#x2F;li&gt;
&lt;li&gt;Prefer progressive decode &amp;amp; can&#x27;t use JXL&lt;&#x2F;li&gt;
&lt;li&gt;Have A&#x2F;B tested against mozjpeg at the very least, &amp;amp; I&#x27;d recommend testing
against other cjpegli chroma subsampling modes as well.&lt;&#x2F;li&gt;
&lt;li&gt;Like the results more with your eyes, which are more important than metrics.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I use metrics because it&#x27;d be really difficult to articulate my visual
preferences here and extrapolate objective data based on them. On your own, try
to use your eyes as much as possible, and train yourself to look for artifacts &amp;amp;
recognize what they are; mosquito noise, ringing, blocking, and banding are all
terms you should be familiar with.&lt;&#x2F;p&gt;
&lt;p&gt;Now that
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;developer.apple.com&#x2F;videos&#x2F;play&#x2F;wwdc2023&#x2F;10122&quot;&gt;Apple announced support for JXL&lt;&#x2F;a&gt;,
hopefully the industry tide on the Web will shift toward the new image codec &amp;amp;
we&#x27;ll see such incredible market penetration that we don&#x27;t need to use JPEG
anymore. For now, this isn&#x27;t a reality, and JPEG&#x27;s compatibility remains
unmatched; given the circumstances, I&#x27;d recommend that you of course use JXL
when possible. When you must use a JPEG, it wouldn&#x27;t be unwise to leverage the
plethora of existing JPEG encoding tools to your advantage when you can. Thanks
for reading!&lt;&#x2F;p&gt;
&lt;p&gt;*&lt;em&gt;AVIF kinda supports progressive decode if you ship an animated AVIF with a
single super lossy frame that loads before the actual image. There are other
tricks you can use as well, and more information about this is outlined in this
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;autocompressor.net&#x2F;blog&#x2F;progressive-avif&quot;&gt;Autocompressor blog post&lt;&#x2F;a&gt;.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;sponsor-me-on-github-sponsors&quot;&gt;Sponsor Me on GitHub Sponsors&lt;&#x2F;h3&gt;
&lt;p&gt;Help support my open source efforts - a little goes a long way!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;sponsors&#x2F;gianni-rosato?o=esc&quot;&gt;Sponsor&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Who Has the Best Hardware AV1 Encoder?</title>
        <published>2023-04-15T00:00:00+00:00</published>
        <updated>2023-04-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Gianni Rosato
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://giannirosato.com/blog/post/nvenc-v-qsv/"/>
        <id>https://giannirosato.com/blog/post/nvenc-v-qsv/</id>
        <content type="html" xml:base="https://giannirosato.com/blog/post/nvenc-v-qsv/">&lt;p&gt;This article compares Nvidia&#x27;s NVENC encoder to Intel&#x27;s QSV encoder for AV1, as well as for other codecs. Who will win?&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;&lt;em&gt;Please be aware that this page contains JPEG XL images. In unsupported
browsers, images may not load properly.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why&quot;&gt;Why?&lt;&#x2F;h2&gt;
&lt;p&gt;Nobody really knows which hardware accelerated AV1 encoder is better right now.&lt;&#x2F;p&gt;
&lt;p&gt;There are other resources available, but they don&#x27;t use good metrics. Everyone&#x27;s
favorite bad, obsolete, easily tricked metric is VMAF, which was good for a time
but simply isn&#x27;t good anymore. You can check out
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;nitter.poast.org&#x2F;jonsneyers&#x2F;status&#x2F;1573371624132419585&quot;&gt;this Twitter thread&lt;&#x2F;a&gt;
to see what I&#x27;m talking about; these aren&#x27;t edge cases either. You can produce a
&quot;better quality&quot; video according to VMAF using Contrast Adaptive Sharpening
filters which inherently do not improve video fidelity in any way.&lt;&#x2F;p&gt;
&lt;p&gt;Other metrics are PSNR, SSIM, MS-SSIM, SSIMULACRA, Butteraugli, &amp;amp; SSIMULACRA2.
PSNR doesn&#x27;t accurately represent our visual system as humans, SSIM was good for
its time but is now old and also provides inconsistent&#x2F;misleading results, &amp;amp;
MS-SSIM does better but also doesn&#x27;t correlate as well with our visual systems
as newer metrics. The distance-based Butteraugli metric isn&#x27;t bad, but
SSIMULACRA2 is currently considered the gold standard for a visual quality
metric.&lt;&#x2F;p&gt;
&lt;p&gt;You can read more about why this metric is great
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;cloudinary&#x2F;ssimulacra2&quot;&gt;here&lt;&#x2F;a&gt;, but SSIMULACRA2 (ssimu2) is,
in my opinion, the only way to really measure subjective visual quality right
now. The scores it produces correlate best with my eyes, and many others agree.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s how SSIMULACRA2 assesses quality:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Returns a score in range -inf..100, which correlates to subjective visual
quality scores as follows:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;30 = low quality. This corresponds to the p10 worst output of mozjpeg
-quality 30.&lt;&#x2F;li&gt;
&lt;li&gt;50 = medium quality. This corresponds to the average output of cjxl -q 40 or
mozjpeg -quality 40, or the p10 output of cjxl -q 50 or mozjpeg -quality 60.&lt;&#x2F;li&gt;
&lt;li&gt;70 = high quality. This corresponds to the average output of cjxl -q 65 or
mozjpeg -quality 70, p10 output of cjxl -q 75 or mozjpeg -quality 80.&lt;&#x2F;li&gt;
&lt;li&gt;90 = very high quality. Likely impossible to distinguish from the original
when viewed at 1:1 from a normal viewing distance. This corresponds to the
average output of mozjpeg -quality 95 or the p10 output of cjxl -q 95.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;In my
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;giannirosato.com&#x2F;blog&#x2F;post&#x2F;image-comparison&#x2F;&quot;&gt;Image Codec Comparison&lt;&#x2F;a&gt;
benchmark, I said &quot;Because of the irrelevance of negative SSIMULACRA2 scores, I
stopped the vertical axis on the graph(s) at 0.&quot; I still maintain this logic was
correct for the image benchmark, but with livestreams, it isn&#x27;t unusual to see a
low bitrate stream due to a poor network connection on either a streamer&#x27;s end
or your own. This test is also more about how the encoders perform at certain
target bitrates, as mentioned above, and since the goal isn&#x27;t necessarily always
to target a certain quality like it is with images, I think some of the less
relevant data (eg target 500kb&#x2F;s) is still important to include because of how
it frames the more useful data (eg the 2000-6000kb&#x2F;s range popular with
streamers).&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, I&#x27;ve seen testing from many using VMAF, and their conclusions are varied
relative to which hardware encoder is the best.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rigaya.github.io&#x2F;vq_results&#x2F;&quot;&gt;These tests&lt;&#x2F;a&gt; are more thorough than mine,
but aren&#x27;t accompanied by much explanation or any VAAPI results for Linux. They
also use VMAF, so everything must be taken with a grain of salt. I still think
they&#x27;re useful in judging the quality of many hardware encoders that I haven&#x27;t
tested here (like AMD&#x27;s stuff) so I&#x27;ll still recommend you go take a look if
you&#x27;re interested. So, what did we find in our own testing?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;methodology-gaming-corpus&quot;&gt;Methodology (Gaming Corpus)&lt;&#x2F;h2&gt;
&lt;p&gt;In order to understand who uses hardware encoders and where, look no further
than livestreaming. Livestreaming can be done via OBS, and you have a choice to
decide between CBR, VBR, &amp;amp; CQP encoding. Many choose to opt for CBR or VBR,
since your connection is most likely the bottleneck &amp;amp; you can target a certain
bitrate. I&#x27;d personally opt for VBR so that the encoder can intelligently
allocate more or less bits when necessary while still targeting a specific
bitrate on average. This also allows us to get better overall quality per bit,
so the viewer can see a higher quality stream while using less data on average.
It is more reliable for data usage &amp;amp; bandwidth than CQP which targets quality, &amp;amp;
more efficient than CBR which will give every scene the same amount of bits no
matter its content.&lt;&#x2F;p&gt;
&lt;p&gt;With help from a couple friends online (namely
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;Bl4cKn1gh7&quot;&gt;BlacKnight&lt;&#x2F;a&gt; &amp;amp;
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;Maikurosofuto&quot;&gt;Maik&lt;&#x2F;a&gt;), I tested hardware &amp;amp; software encoders
from target 500kb&#x2F;s to target 8500kb&#x2F;s in increments of 500. This gave us 16
data points per encoder per clip to work with. I used two clips from DERF&#x27;s Test
Media - namely the CSGO clip &amp;amp; the Minecraft clip under &quot;Gaming&quot; - and
transcoded them to h264 with &lt;code&gt;crf 1&lt;&#x2F;code&gt; to provide the encoders with near
mathematically lossless video that they could effectively decode. The loss
introduced by reencoding at such high quality is miniscule &amp;amp; should have a
nearly undetectable effect on the benchmark results.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s a screenshot from the CSGO clip:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;csgo.jxl&quot; alt=&quot;csgo&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&amp;amp; here&#x27;s one from the Minecraft clip:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;minecraft.jxl&quot; alt=&quot;minecraft&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;All hardware encoders were on their highest effort preset
(-compression_level 7) for optimal quality. It didn&#x27;t have a massive effect on
speed in my testing.&lt;&#x2F;p&gt;
&lt;p&gt;The encoders tested for the CSGO clip were the following:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;h264 (UHD770 VAAPI)&lt;&#x2F;li&gt;
&lt;li&gt;h264 (Arc A770 VAAPI)&lt;&#x2F;li&gt;
&lt;li&gt;HEVC (UHD770 VAAPI)&lt;&#x2F;li&gt;
&lt;li&gt;HEVC (Arc A770 VAAPI)&lt;&#x2F;li&gt;
&lt;li&gt;HEVC (Arc A770 VAAPI, 10 bit)&lt;&#x2F;li&gt;
&lt;li&gt;VP9 (UHD770 VAAPI)&lt;&#x2F;li&gt;
&lt;li&gt;x264 (slower preset)&lt;&#x2F;li&gt;
&lt;li&gt;SVT-AV1 (preset 6, 10 bit)&lt;&#x2F;li&gt;
&lt;li&gt;SVT-AV1 (preset 8)&lt;&#x2F;li&gt;
&lt;li&gt;SVT-AV1 (preset 8, 10 bit)&lt;&#x2F;li&gt;
&lt;li&gt;Nvenc AV1 (RTX 4090)&lt;&#x2F;li&gt;
&lt;li&gt;Nvenc AV1 (RTX 4090, 10 bit)&lt;&#x2F;li&gt;
&lt;li&gt;QSV AV1 (Arc A770)&lt;&#x2F;li&gt;
&lt;li&gt;QSV AV1 (Arc A770, 10 bit)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The encoders tested for the Minecraft clip were the following:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;x265 (medium preset, 10 bit)&lt;&#x2F;li&gt;
&lt;li&gt;SVT-AV1 (preset 4, 10 bit)&lt;&#x2F;li&gt;
&lt;li&gt;SVT-AV1 (preset 6, 10 bit)&lt;&#x2F;li&gt;
&lt;li&gt;SVT-AV1 (preset 8, 10 bit)&lt;&#x2F;li&gt;
&lt;li&gt;Nvenc AV1 (RTX 4090, 10 bit)&lt;&#x2F;li&gt;
&lt;li&gt;QSV AV1 (Arc A770, 10 bit)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;You may be wondering why there is such an emphasis on 10 bit encoding despite
the 8 bit source. In my testing, even just with my eyes, encoding to 10 bit
video with most lossy video codecs (HEVC &amp;amp; AV1 in particular) yields better
coding efficiency than encoding to 8 bit. I can&#x27;t tell you exactly why, but I&#x27;m
sure others on the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;discord.gg&#x2F;Ecu428C&quot;&gt;AV1 Community Discord&lt;&#x2F;a&gt; can.&lt;&#x2F;p&gt;
&lt;p&gt;You may also be wondering about the lack of AMD numbers. I have a Radeon Rx
6600xt &amp;amp; myself and others found it very difficult to properly test AMD&#x27;s
hardware encoders. You can look at the results I linked above that report VMAF
numbers - the massive discrepancy in AMD&#x27;s AV1 performance compared to other AV1
encoders should be enough to let you know how good AMD&#x27;s results are.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;results&quot;&gt;Results&lt;&#x2F;h2&gt;
&lt;p&gt;Finally, we come to the graphs. It is also worth noting that the default GOP
size for the hardware encoders is around 300 frames; leaving it on &quot;auto&quot; in
FFmpeg generally yielded &lt;em&gt;slightly&lt;&#x2F;em&gt; better results by a few fractions of a point
in ssimu2, so that is what we did.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;csgo&quot;&gt;CSGO&lt;&#x2F;h3&gt;
&lt;p&gt;Here is a graph for target bitrate (VBR) with the CSGO clip:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;csgo_results.svg&quot; alt=&quot;csgo_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;And a log scale version that we can use to see discrepancies better:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;csgo_results_log.svg&quot; alt=&quot;csgo_results_log&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This is kind of a lot to unpack, as there are a considerable number of
overlapping lines here. I&#x27;ve attached a CSV file
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gianni-rosato&#x2F;hwenc-ssimu2-plotter&#x2F;blob&#x2F;main&#x2F;csgo_results.csv&quot;&gt;here&lt;&#x2F;a&gt;
so that you can visualize this data on your own in any way you please, but I
think we can come to a couple interesting conclusions given this current
visualization. For this clip:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;There are clear, marked differences in coding efficiency depending on the
encoder even if the codec is the same. So, just because your video is h264
doesn&#x27;t mean that is was encoded as efficiently as every other h264 video. You
can see here that the Arc A770&#x27;s h264 encoder yields better quality per bit
than the h264 encoder that&#x27;s part of the iGPU on my 13700k. The fixed function
hardware Intel is deploying for these two implementations is distinct, and
Arc&#x27;s is clearly better. The software x264 encoder that runs directly on the
CPU is certainly slower, but you can see how that pays off in spades.&lt;&#x2F;li&gt;
&lt;li&gt;SVT-AV1 is in a class by itself. Preset 8 dominates the hardware encoders
across the board by a not insignificant margin, &amp;amp; Preset 6 pulls ahead even
further. While hardware encoders are very fast and a gem for streaming,
software encoders still take home the efficiency crown.&lt;&#x2F;li&gt;
&lt;li&gt;NVENC AV1 is more efficient than QSV AV1 on Arc, even if the advantage is
slight. Even when comparing 8 bit NVENC to 10 bit QSV, NVENC still manages to
pull ahead ever so slightly.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;If you came here to rub salt in Arc users&#x27; wounds, I wouldn&#x27;t say the advantage
is significant enough to warrant purchasing a 40-series card over an Arc card
&lt;em&gt;solely&lt;&#x2F;em&gt; for encoding especially considering the price discrepancy. However, the
advantage is still there, and shouldn&#x27;t be ignored. My takeaway is if you have
more money than you know what to do with, don&#x27;t buy an Arc GPU for encoding if
you have a 40-series card from Nvidia already. Spend it on a nice bowl of ramen
instead (or a couple nice bowls of ramen, realistically).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;minecraft&quot;&gt;Minecraft&lt;&#x2F;h3&gt;
&lt;p&gt;Let&#x27;s see what else we can find looking at the Minecraft results:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;minecraft_results.svg&quot; alt=&quot;minecraft_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s another log scale version:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;minecraft_results_log.svg&quot; alt=&quot;minecraft_results_log&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This one is more stripped down, &amp;amp; everything is easier to see. Here are my
takeaways:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Now that we have x265 in the mix, we can see NVENC beat it across the board &amp;amp;
QSV came close. This is pretty impressive, as x265 medium isn&#x27;t a fast setting
by any stretch of the imagination.&lt;&#x2F;li&gt;
&lt;li&gt;SVT-AV1 preset 4&#x27;s performance leaves us with some clues about our final
conclusion, seeing as software AV1 encoding (even with SVT-AV1, which doesn&#x27;t
produce as good quality per bit as AOM encoder forks like aom-av1-lavish)
continues to pull ahead significantly the more time &amp;amp; effort we allow it to
use. It may be possible to stream using SVT-AV1 preset 8 on higher end systems
right now, but more likely you&#x27;d be using preset 9 or 10, which is where I
think SVT-AV1 is probably on par with hardware encoders. As CPUs become more
powerful, this will become easier, &amp;amp; it is clear that dedicated streaming
hardware may continue to provide value for streamers who can afford it.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;who-won&quot;&gt;Who Won?&lt;&#x2F;h3&gt;
&lt;p&gt;Nvidia&#x27;s hardware acceleration for AV1 video encoding is superior in these two
clips. It is a tiny margin, and it is important to judge subjective visual
quality for yourself when evaluating tiny discrepancies like this, but
SSIMULACRA2 says that Nvidia has the better solution. I don&#x27;t think that&#x27;s the
most important takeaway here, though, considering our peek at the potential
software encoders have.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;software-vs-hardware&quot;&gt;Software vs hardware?&lt;&#x2F;h3&gt;
&lt;p&gt;Software encoders are run on the CPU without any fixed function hardware
designed around any specific codec. Hardware accelerated video encoding takes
place on specialized ASICs that are attached to most consumer GPUs, &amp;amp; encoding
with hardware acceleration means the encoder can also make use of a GPU&#x27;s highly
parallel compute to further accelerate certain video encoding functions.
Developing these ASICs is expensive, and dedicating valuable silicon real estate
on GPUs to such features must prove to be well worth a company&#x27;s time and money.
Nvidia &amp;amp; Intel both seem to agree that AV1 has enough of a future to make it
financially sane to put fast dedicated hardware in the hands of every consumer
who has one of their latest GPUs, which is pretty cool.&lt;&#x2F;p&gt;
&lt;p&gt;The issue regarding efficiency is that this fixed function hardware is designed
to be fast above all else, &amp;amp; implementing less important codec features into
this hardware can inflate costs without helping efficiency meaningfully. In
software, while slower, encoders can pick and choose to enable&#x2F;disable whatever
features they like based on user specified parameters or internal defaults for
each preset. This makes the use cases for both solutions very different.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;when-should-i-use-hardware-encoders&quot;&gt;When should I use hardware encoders?&lt;&#x2F;h3&gt;
&lt;p&gt;As I mentioned earlier, livestreaming is where hardware accelerated video
encoding is most popular. Nvidia has turned their NVENC hardware into a
differentiating feature because of the popularity of livestreaming, &amp;amp; as sites
like Twitch &amp;amp; YouTube roll out AV1 for the masses, livestreamers will appreciate
having a powerful hardware implementation for the more efficient codec to stream
with given their hardware readily supports it.&lt;&#x2F;p&gt;
&lt;p&gt;Hardware encoding can also be great for screen recording, capturing clips in
games, and trancoding media on the fly for a media server or something similar.
When speed is the priority over coding efficiency, hardware encoders are a
fantastic choice.&lt;&#x2F;p&gt;
&lt;p&gt;Here are some places where you &lt;em&gt;probably shouldn&#x27;t&lt;&#x2F;em&gt; use hardware encoders:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Transcoding media you legally own for later consumption (unless you are
burdened by a time limit or it is too painful to encode at less than real
time)&lt;&#x2F;li&gt;
&lt;li&gt;Re-encoding home video that needs to take up less space somewhere or needs to
be shared easily&lt;&#x2F;li&gt;
&lt;li&gt;Producing a final copy of a video project of some sort to be shared with
others&lt;&#x2F;li&gt;
&lt;li&gt;Illegally releasing pirated media that has been transcoded to achieve a
smaller filesize for viewer convenience (I cannot recommend this regardless)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;You should never transcode something that has already been transcoded unless you
have to - it is best to search for the source. If you have a BluRay collection
that you transcoded to h264 ten years ago, don&#x27;t go re-encoding it to AV1 now -
find the source &amp;amp; do it over, else you risk preserving artifacts from the
original encode. And do it with a software encoder. If you&#x27;re exporting a video
for a project or something similar, it may be worth encoding to a lossless or
near lossless file &amp;amp; reencoding as efficiently as possible later when you can
afford the time so you can optimize for quality per bit.&lt;&#x2F;p&gt;
&lt;p&gt;SVT-AV1 is great for that sweet spot balance of speed &amp;amp; efficiency for AV1
video. Rav1e, my personal favorite software AV1 encoder, isn&#x27;t the best, but it
is great for high fidelity &amp;amp; should never segfault.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;Clybius&#x2F;aom-av1-lavish&quot;&gt;aom-av1-lavish&lt;&#x2F;a&gt; is the reigning
champ for coding efficiency in a software encoder. Use at speed 4 or lower for
excellent results; they will be results you&#x27;ll have to wait very long for,
though. Tools like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;master-of-zen&#x2F;Av1an&quot;&gt;av1an&lt;&#x2F;a&gt; can help.&lt;&#x2F;p&gt;
&lt;p&gt;You can try my SVT-AV1 GUI, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gianni-rosato&#x2F;aviator&quot;&gt;Aviator&lt;&#x2F;a&gt;,
if you&#x27;d like to mess around with SVT-AV1 without using the command line.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gianni-rosato&#x2F;aviator&#x2F;tree&#x2F;rAV1ator&quot;&gt;rAV1ator&lt;&#x2F;a&gt; it its
cousin, powered by av1an &amp;amp; rav1e instead of SVT-AV1 &amp;amp; FFmpeg. Please give them a
look if you&#x27;re interested in getting started with AV1. Once you&#x27;d like to dive
down the rabbit hole, feel free to look into
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rentry.co&#x2F;av1&quot;&gt;this article&lt;&#x2F;a&gt; for a detailed guide written by a talented
encoder. Thanks for reading!&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Photographic Corpus (lossy)</title>
        <published>2023-03-16T00:00:00+00:00</published>
        <updated>2023-03-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Gianni Rosato
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://giannirosato.com/blog/post/corpus-lossy/"/>
        <id>https://giannirosato.com/blog/post/corpus-lossy/</id>
        <content type="html" xml:base="https://giannirosato.com/blog/post/corpus-lossy/">&lt;p&gt;A collection of the images I used for the blog post published on the same day as this one, lossily compressed with JPEG XL. All images are licensed under CC BY-SA 4.0.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest01.jxl&quot; alt=&quot;imagetest01&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest02.jxl&quot; alt=&quot;imagetest02&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest03.jxl&quot; alt=&quot;imagetest03&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest04.jxl&quot; alt=&quot;imagetest04&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest05.jxl&quot; alt=&quot;imagetest05&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest06.jxl&quot; alt=&quot;imagetest06&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest07.jxl&quot; alt=&quot;imagetest07&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest08.jxl&quot; alt=&quot;imagetest08&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest09.jxl&quot; alt=&quot;imagetest09&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest10.jxl&quot; alt=&quot;imagetest10&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest11.jxl&quot; alt=&quot;imagetest11&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest12.jxl&quot; alt=&quot;imagetest12&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest13.jxl&quot; alt=&quot;imagetest13&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest14.jxl&quot; alt=&quot;imagetest14&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest15.jxl&quot; alt=&quot;imagetest15&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest16.jxl&quot; alt=&quot;imagetest16&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest17.jxl&quot; alt=&quot;imagetest17&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest18.jxl&quot; alt=&quot;imagetest18&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest19.jxl&quot; alt=&quot;imagetest19&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest20.jxl&quot; alt=&quot;imagetest20&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest21.jxl&quot; alt=&quot;imagetest21&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest22.jxl&quot; alt=&quot;imagetest22&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest23.jxl&quot; alt=&quot;imagetest23&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest24.jxl&quot; alt=&quot;imagetest24&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest25.jxl&quot; alt=&quot;imagetest25&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest26.jxl&quot; alt=&quot;imagetest26&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;corpus_lossy&#x2F;imagetest27.jxl&quot; alt=&quot;imagetest27&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Image Codec Comparison</title>
        <published>2023-03-16T00:00:00+00:00</published>
        <updated>2023-03-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Gianni Rosato
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://giannirosato.com/blog/post/image-comparison/"/>
        <id>https://giannirosato.com/blog/post/image-comparison/</id>
        <content type="html" xml:base="https://giannirosato.com/blog/post/image-comparison/">&lt;p&gt;The current debate surrounding AVIF versus JPEG XL needs some third-party quantitative data to support arguments on either side. I&#x27;ve compiled a dataset of 27 high-resolution photographic images to compare performance across them all at a wide fidelity range.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;h2 id=&quot;why&quot;&gt;Why?&lt;&#x2F;h2&gt;
&lt;p&gt;To start things off, I have been a fan of the AVIF image format since I heard
about it, not long after I started learning about the AV1 video codec in 2020. I
figured the fact that it was backed by the Alliance for Open Media, was the
natural successor to WebP, and was based on a rather powerful video codec that
was rapidly growing in popularity was great for users &amp;amp; organizations alike to
easily understand what the technology stood for, where it came from, and what
benefits it provided over the existing lossy image king, JPEG. Good codecs are
open source codecs, so the fact that AVIF was open source was a big deal to me
as well. It is also already &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;caniuse.com&#x2F;avif&quot;&gt;widely supported&lt;&#x2F;a&gt; at the
time of writing. I thought unequivocally that AVIF would be the future of images
on the web when I first learned about it. I don&#x27;t think I was entirely wrong,
but I hadn&#x27;t yet heard of JPEG XL.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;jpegxl.info&#x2F;&quot;&gt;JPEG XL&lt;&#x2F;a&gt; (JXL for short) is an image codec by the JPEG
committee (yes, &lt;em&gt;the&lt;&#x2F;em&gt; JPEG committee). Initially I hadn&#x27;t thought much of the
codec because the compression efficiency was &quot;the same as AVIF,&quot; so I kind of
wrote it off, as I&#x27;m sure many uninformed people have &amp;amp; continue to do. Upon
doing more research, my position flipped and I found myself deeply invested in
the future of JXL.&lt;&#x2F;p&gt;
&lt;p&gt;JXL&#x27;s fascinating features include the highly efficient variable-blocksize
discrete cosine transform (which is superior to JPEG&#x27;s DCT), usage of the XYB
colorspace which more accurately represents the response our cones in our
eyeballs have to different wavelengths of light, &amp;amp; a &quot;modular&quot; mode for
efficient lossless or near-lossless coding. It is also used while encoding lossy
JPEG XL images, but I don&#x27;t know much about it &amp;amp; I won&#x27;t pretend to. Currently
the reference encoder also supports patch detection, noise synthesis, and will
(probably) eventually support spline detection as it is present in the codec
specification. JPEG XL also supports &lt;em&gt;lossless JPEG transcoding&lt;&#x2F;em&gt; that &lt;em&gt;reduces
the filesize of JPEGs&lt;&#x2F;em&gt; which in my opinion is a game-changing feature. That
alone could be a revolutionary codec by itself. Progressive decode is also a
standout feature of JPEG XL, and I found
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;opensource.googleblog.com&#x2F;2021&#x2F;09&#x2F;using-saliency-in-progressive-jpeg-xl-images.html&quot;&gt;this article&lt;&#x2F;a&gt;
very interesting regarding that.&lt;&#x2F;p&gt;
&lt;p&gt;Some more interesting JXL articles &amp;amp; resources:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.roboleary.net&#x2F;webdev&#x2F;2023&#x2F;03&#x2F;06&#x2F;next-web-image-format-not-jpegxl.html&quot;&gt;www.roboleary.net&#x2F;webdev&#x2F;2023&#x2F;03&#x2F;06&#x2F;next-web-image-format-not-jpegxl.html&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;tonisagrista.com&#x2F;blog&#x2F;2023&#x2F;jpegxl-vs-avif&#x2F;&quot;&gt;tonisagrista.com&#x2F;blog&#x2F;2023&#x2F;jpegxl-vs-avif&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;cloudfour.com&#x2F;thinks&#x2F;on-container-queries-responsive-images-and-jpeg-xl&#x2F;&quot;&gt;cloudfour.com&#x2F;thinks&#x2F;on-container-queries-responsive-images-and-jpeg-xl&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;motionmill.com&#x2F;2023&#x2F;02&#x2F;google-stopt-jpex-xl-gebruiken&#x2F;&quot;&gt;motionmill.com&#x2F;2023&#x2F;02&#x2F;google-stopt-jpex-xl-gebruiken&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;ds.jpeg.org&#x2F;whitepapers&#x2F;jpeg-xl-whitepaper.pdf&quot;&gt;ds.jpeg.org&#x2F;whitepapers&#x2F;jpeg-xl-whitepaper.pdf&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;cloudinary.com&#x2F;blog&#x2F;the-case-for-jpeg-xl&quot;&gt;cloudinary.com&#x2F;blog&#x2F;the-case-for-jpeg-xl&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Also worth noting is the format&#x27;s most outspoken pioneer
&lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;sneyers.info&#x2F;&quot;&gt;Jon Sneyers&lt;&#x2F;a&gt; (co-author of the JPEG XL spec) as well as
other noteworthy developers are extremely active within the community and value
the feedback of the people who are passionate about their work. This should not
be as unique as it is, but it is incredibly valuable to have one&#x27;s voice heard
so easily &amp;amp; frequently by developers who &lt;em&gt;actually care&lt;&#x2F;em&gt;. I commend Sneyers &amp;amp;
the other core JXL devs for this.&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, TL;DR: JXL has a lot more to offer than just good compression. But, I
still wish to know; how does it stack up against other image formats regarding
lossy compression &amp;amp; lossless compression?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;methodology-photographic-corpus&quot;&gt;Methodology (Photographic Corpus)&lt;&#x2F;h2&gt;
&lt;p&gt;In order to produce an unbiased test to determine how good JPEG XL is relative
to &lt;em&gt;my&lt;&#x2F;em&gt; needs, I took 27 photos (mostly consisting of landscape&#x2F;architecture
photography) around my seaside hometown using my Sony a5000 at 20.1mp, edited
them in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.darktable.org&#x2F;&quot;&gt;Darktable&lt;&#x2F;a&gt;, exported them as 16-bit sRGB
PNGs (AdobeRGB had
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ImageMagick&#x2F;ImageMagick&#x2F;issues&#x2F;6157&quot;&gt;some&lt;&#x2F;a&gt;
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;libjxl&#x2F;libjxl&#x2F;issues&#x2F;2289&quot;&gt;problems&lt;&#x2F;a&gt;), &amp;amp; finally I ran a
script written by my friend RootAtCali (check out their
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;autocompressor.net&#x2F;&quot;&gt;cool site&lt;&#x2F;a&gt;) to determine each image format&#x27;s
SSIMULACRA2 scores for each quality level available via the format&#x27;s lossy
coding option.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ll be certain to make my lossless sources available via an edit to this post,
&amp;amp; they&#x27;ll be linked at the top. In the meantime, here&#x27;s
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;giannirosato.com&#x2F;blog&#x2F;post&#x2F;corpus-lossy&#x2F;&quot;&gt;another page&lt;&#x2F;a&gt; with full
resolution lossy JXL photos encoded at &lt;code&gt;-d 1.0&lt;&#x2F;code&gt;. The images are under CC-BY-SA
4.0.&lt;&#x2F;p&gt;
&lt;p&gt;These tests were run using the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;cloudinary&#x2F;ssimulacra2&quot;&gt;SSIMULACRA2&lt;&#x2F;a&gt; visual quality metric
via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-av&#x2F;ssimulacra2&quot;&gt;ssimulacra2_rs&lt;&#x2F;a&gt;. This metric is
designed to model human vision far better than VMAF, SSIM, PSNR, &amp;amp; other less
effective alternatives. Via the readme on Github:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Returns a score in range -inf..100, which correlates to subjective visual
quality scores as follows:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;30 = low quality. This corresponds to the p10 worst output of mozjpeg
-quality 30.&lt;&#x2F;li&gt;
&lt;li&gt;50 = medium quality. This corresponds to the average output of cjxl -q 40 or
mozjpeg -quality 40, or the p10 output of cjxl -q 50 or mozjpeg -quality 60.&lt;&#x2F;li&gt;
&lt;li&gt;70 = high quality. This corresponds to the average output of cjxl -q 65 or
mozjpeg -quality 70, p10 output of cjxl -q 75 or mozjpeg -quality 80.&lt;&#x2F;li&gt;
&lt;li&gt;90 = very high quality. Likely impossible to distinguish from the original
when viewed at 1:1 from a normal viewing distance. This corresponds to the
average output of mozjpeg -quality 95 or the p10 output of cjxl -q 95.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Because of the irrelevance of negative SSIMULACRA2 scores, I stopped the
vertical axis on the graph(s) at 0.&lt;&#x2F;p&gt;
&lt;p&gt;For context, here&#x27;s an overview of the image formats tested:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JPEG&lt;&#x2F;strong&gt;, the reigning champ. has been used for decades. Supports progressive
decode&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;WebP&lt;&#x2F;strong&gt;, based on VP8, an open-source video codec.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;JPEG XL&lt;&#x2F;strong&gt;, the designated successor to JPEG. Designed by the JPEG committee.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;AVIF&lt;&#x2F;strong&gt;, based on AV1, a modern open-source video codec.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Here&#x27;s a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;jpegxl.info&#x2F;comparison.png&quot;&gt;helpful chart&lt;&#x2F;a&gt; provided by
Cloudinary.&lt;&#x2F;p&gt;
&lt;p&gt;Notably absent are JPEG 2000 (J2K) &amp;amp; HEIC. Considering JXL is J2K&#x27;s successor &amp;amp;
J2K never saw widespread adoption, I&#x27;m not sure including it helps anybody. HEIC
is being excluded because it sucks, &amp;amp; also is notably not royalty free.&lt;&#x2F;p&gt;
&lt;p&gt;Here are the encoders tested for each format:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JPEG&lt;&#x2F;strong&gt; via the mozjpeg encoder&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;JPEG&lt;&#x2F;strong&gt; via the mozjpeg encoder, tuned for MS-SSIM, using the &#x27;Tuned for
MS-SSIM on Kodak image set&#x27; quantization table &amp;amp; arithmetic coding&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;JPEG&lt;&#x2F;strong&gt; via the elusive &amp;amp; mysterious jpegli encoder (more on this later)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;WebP&lt;&#x2F;strong&gt; via cwebp&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;JPEG XL&lt;&#x2F;strong&gt; via the libjxl reference encoder implementation, cjxl&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;AVIF&lt;&#x2F;strong&gt; via aom-av1-lavish, tuned for SSIM&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Here&#x27;s the encoder parameters I used, along with the encoder version (in the
same order as above)&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;cjpeg -q [quality] &quot;input&quot; &amp;gt; &quot;output.jpg&quot;&lt;&#x2F;code&gt; | mozjpeg version 4.1.1
(build 20230217)&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;cjpeg -q [quality] -quant-table 2 -tune-ms-ssim -arithmetic &quot;input&quot; &amp;gt; &quot;output.jpg&quot;&lt;&#x2F;code&gt;
| mozjpeg version 4.1.1 (build 20230217)&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;benchmark_xl --input=[input] --codec=jpeg:enc-jpegli:rgb:q[quality] --save_compressed --output_dir=[outdir]&lt;&#x2F;code&gt;
| cjxl v0.9.0 c4927fbf&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;cwebp -m 6 -q [quality] &quot;input&quot; -o [output.webp]&lt;&#x2F;code&gt; | 1.3.0 libsharpyuv: 0.2.0&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;cjxl &quot;input&quot; &quot;output.jxl&quot; -q [quality] -e 7 --brotli_effort 11&lt;&#x2F;code&gt; | cjxl v0.9.0
c4927fbf&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;avifenc -c aom -s 6 -j 16 -d 10 -y 444 --min 1 --max 63 -a end-usage=q -a cq-level=[quality] -a tune=ssim &quot;input&quot; &quot;output.avif&quot;&lt;&#x2F;code&gt;
| AOMedia Project AV1 Encoder Psy v3.6.0 (default)&lt;&#x2F;p&gt;
&lt;p&gt;WebP is at maximum effort &amp;amp; the JPEG encoders were left stock in terms of
effort. cjxl is on effort 7 (the internal default, so I didn&#x27;t really need to
specify it, &amp;amp; &lt;code&gt;brotli_effort&lt;&#x2F;code&gt; is only for lossless iirc), &amp;amp; we encode avif at
speed 6 because it correlates best with JXL&#x27;s default speed (&amp;amp; is also rav1e&#x27;s
default speed internally although we&#x27;re not using rav1e).
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;beta.flathub.org&#x2F;apps&#x2F;net.natesales.Aviator&quot;&gt;Aviator&lt;&#x2F;a&gt;, based on
SVT-AV1, consciously defaults to speed 6 for AV1 video encoding, as does
rav1e-based &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;giannirosato.com&#x2F;blog&#x2F;post&#x2F;aviator-1&#x2F;&quot;&gt;rAV1ator&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Benchmarking with this image dataset gave us a total of just below 18,000 data
points before averaging. For the graph, I calculated the arithmetic mean of the
bpp &amp;amp; SSIMULACRA2 score for each quality step for each encoder.&lt;&#x2F;p&gt;
&lt;p&gt;Here are the results:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;photographic_corpus_results.svg&quot; alt=&quot;photographic_corpus_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Now, hold your breath, conclusions come at the end!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;methodology-non-photographic&quot;&gt;Methodology (Non-photographic)&lt;&#x2F;h2&gt;
&lt;p&gt;This is a shorter test with less going on, but still interesting &amp;amp; worth
bringing up. I&#x27;m using &lt;a href=&quot;&#x2F;static&#x2F;images&#x2F;the_apps.png&quot;&gt;this&lt;&#x2F;a&gt; image to test
non-photographic encoding using the same parameters as above. Here are the
results:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;nonphotographic_results.svg&quot; alt=&quot;nonphotographic_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;While this is only one image, I have universally seen AVIF take a rather large
lead with every non-photographic image I&#x27;ve tested.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;lossless-animation&quot;&gt;Lossless? Animation?&lt;&#x2F;h2&gt;
&lt;p&gt;Stay tuned! While both AVIF &amp;amp; JXL (as well as WebP) support animation, I&#x27;d like
to cover that in a different post. I&#x27;d like to learn more about how AVIF &amp;amp; JXL
work relative to lossless image coding as well before I tackle a test like that.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;where-does-jpeg-xl-win&quot;&gt;Where does JPEG XL win?&lt;&#x2F;h3&gt;
&lt;p&gt;It is clear to me that JPEG XL is going to be my choice for exporting my amateur
photographs. It performs exceptionally well at higher quality, outperforming
AVIF &amp;amp; the others by a significant margin. AVIF is stronger as quality
decreases, but AVIF is further weakened by the fact that encoding using more
threads generally hurts efficiency &amp;amp; encoding an AVIF image is generally slower
than encoding a JXL image. As for the other plethora of features JXL has to
offer, AVIF can barely compete. AVIF is also hampered by its lower maximum image
dimensions compared to competing codecs.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;where-does-avif-win&quot;&gt;Where does AVIF win?&lt;&#x2F;h3&gt;
&lt;p&gt;AVIF wins at low to medium (maybe low-medium) quality. This is great for content
devilery on the web, as images don&#x27;t always need to be high fidelity or retain a
ton of high-frequency detail to maintain their appeal; something that looks
pleasing and resembles the source can be more desirable. Despite the fact that
AVIF lacks progressive decode, its superior coding efficiency at lower quality
somewhat makes up for this in my opinion. AVIF is also the winner for our
non-photographic image, and if you trust that image isn&#x27;t an edge case, it opens
up some cool opportunities for sharing digital art as JXL doesn&#x27;t have an
encoder available with spline detection to compete right now. Being based on the
AV1 video codec, AVIF is allegedly easier to implement as well; the codec&#x27;s
widespread adoption sees it already achieving a degree of success on the Web.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;how-about-the-others&quot;&gt;How about the others?&lt;&#x2F;h3&gt;
&lt;p&gt;WebP isn&#x27;t very good in my opinion, plumetting at higher quality &amp;amp; being soundly
defeated by AVIF at lower quality. JPEG clearly &lt;em&gt;still&lt;&#x2F;em&gt; has potential even years
later, and MS-SSIM tuning seems to do the encoder some favors with the
photographic dataset. The jpegli encoder borrows some of JXL&#x27;s fancy coding
techniques to improve JPEG quality even further, especially at higher quality. I
didn&#x27;t even test jpegli with the superior XYB colorspace (as this proved hard to
do), which to my eyes nets jpegli an even better quality improvement over
standard jpeg encoders. There&#x27;s a cool
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;nitter.poast.org&#x2F;jyzg&#x2F;status&#x2F;1622900057816145922&quot;&gt;Twitter thread&lt;&#x2F;a&gt;
about this. According to
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;research.google&#x2F;people&#x2F;105344&#x2F;&quot;&gt;Jyrki Alakuijala&lt;&#x2F;a&gt;, &quot;jpegli supports
more than 8 bits per channel (around 10.5 bits) and can codify HDR dynamics
(HLG&#x2F;PQ&#x2F;XYB&#x2F;etc.) in the old &#x27;8-bit&#x27; format.&quot; This seems extremely promising.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;takeaway&quot;&gt;Takeaway&lt;&#x2F;h3&gt;
&lt;p&gt;I want a web where both AVIF &amp;amp; JPEG XL can exist, and developers decide which
format to use for its merits. Google&#x27;s Chromium team has rejected JXL support
for Chrome, and Mozilla remains &quot;neutral,&quot; on JXL, keeping a flag in Firefox
Nightly to enable partial support. The Chromium team shouldn&#x27;t have the absolute
authority to shoot down would-be standards like this, as it seems they are the
sole decision-maker in this space &amp;amp; all others must follow their lead. In my
opinion, JPEG XL &amp;amp; AVIF have &lt;em&gt;fundamentally different strengths&lt;&#x2F;em&gt; which lend them
to different use cases. Considering JXL has been endorsed by Facebook, Adobe,
Intel and the Video Electronics Standards Association, The Guardian, Flickr and
SmugMug, Shopify, the Krita Foundation, Serif Ltd, Gaia Sky, and many more, the
market is most certainly interested. My current optimistic hope is that JXL
takes off outside the web among professionals working with tools like the Adobe
suite or alternatives, and camera manufacturers, smartphone OEMS, and others
take notice &amp;amp; begin to think about JXL more seriously. The benefits cannot be
ignored, and it is (in my opinion) the only image format that is in every way
superior to JPEG &amp;amp; offers a concrete future for the many existing JPEGs on the
Web &amp;amp; beyond.&lt;&#x2F;p&gt;
&lt;p&gt;While this is a non-scientific test, please share this post if you&#x27;d like to see
JPEG XL get more attention for its merits. AVIF is already widely supported, I
don&#x27;t need to fight for it; JPEG XL still has a war to be won, and I&#x27;d like
anyone who wants better images &amp;amp; a better Web to be on JXL&#x27;s side as well as
AVIF&#x27;s, and to understand that JPEG XL needs our assistance as users to gain
widespread adoption. Here&#x27;s what I&#x27;d recommend doing right now:&lt;&#x2F;p&gt;
&lt;p&gt;Using Chrome or another Chromium-based browser? Consider switching to
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;thorium.rocks&#x2F;&quot;&gt;Thorium&lt;&#x2F;a&gt;. It is available for nearly every platform,
including Windows, Linux, macOS, and Android, and has complete JXL support. It
will sync with your Google account just like Chrome will. It is also
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;giannirosato.com&#x2F;blog&#x2F;post&#x2F;browser-benchmarks&#x2F;&quot;&gt;faster&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Using Firefox? Not a bad choice. If your workflow permits, switching to Firefox
Nightly for casual browsing &amp;amp; enabling the JXL flag for partial JXL support by
searching &#x27;jxl&#x27; in &lt;code&gt;about:config&lt;&#x2F;code&gt; is not a bad option. I&#x27;m attached to Firefox
myself, &amp;amp; using the flag on my phone works great for most things that don&#x27;t
employ an alpha channel or HDR.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;d like to take a look at some beautiful JXL images, you can head
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;people.csail.mit.edu&#x2F;ericchan&#x2F;hdr&#x2F;hdr-jxl.php&quot;&gt;here&lt;&#x2F;a&gt;. You can test your
browser&#x27;s JXL support &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;jpegxl.info&#x2F;test-page&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;. Remember, the Web
may be the last platform without a proprietor, and we need to keep it that way.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;sponsor-me-on-github-sponsors&quot;&gt;Sponsor Me on GitHub Sponsors&lt;&#x2F;h3&gt;
&lt;p&gt;Help support my open source efforts - a little goes a long way!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;sponsors&#x2F;gianni-rosato?o=esc&quot;&gt;Sponsor&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Browser Benchmarks</title>
        <published>2023-02-28T00:00:00+00:00</published>
        <updated>2023-02-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Gianni Rosato
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://giannirosato.com/blog/post/browser-benchmarks/"/>
        <id>https://giannirosato.com/blog/post/browser-benchmarks/</id>
        <content type="html" xml:base="https://giannirosato.com/blog/post/browser-benchmarks/">&lt;p&gt;Let&#x27;s compare browser performance across a variety of architectures, operating systems, and browsers. Which browser is the most performant?&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;h2 id=&quot;why&quot;&gt;Why?&lt;&#x2F;h2&gt;
&lt;p&gt;The web browser &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;thorium.rocks&#x2F;&quot;&gt;Thorium&lt;&#x2F;a&gt; claims to improve upon
Chromium&#x27;s (and by extension, Chrome&#x27;s) browser performance significantly. I
think Thorium&#x27;s a really neat browser, &amp;amp; I commend their continued support for
the fantastic JPEG XL codec when most seem to have abandoned it. This isn&#x27;t to
disprove or undermine their browser benchmark results in any way, I simply
wanted to do my own testing &amp;amp; report back my results as a means of letting
myself know how my different browsers perform. I am a Firefox user right now,
but that&#x27;s always subject to change, although right now I&#x27;m not particularly
partial to Chromium-based browsers.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;methodology&quot;&gt;Methodology&lt;&#x2F;h2&gt;
&lt;p&gt;I currently use the Firefox Flatpak on my desktop &amp;amp; laptop, Firefox on my iPad
(being WebKit based, I didn&#x27;t feel it was useful to test it), &amp;amp; I just recently
switched from Bromite to Firefox Nightly on my Pixel 7 for its JPEG XL support &amp;amp;
timely updates (where Bromite hasn&#x27;t been updated since Chromium 108). For this
test, I benchmarked:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Chromium 110.0.5481.177 on my 13700k&#x2F;6600xt&lt;&#x2F;li&gt;
&lt;li&gt;Thorium 109.0.5414.120 on my 13700k&#x2F;6600xt&lt;&#x2F;li&gt;
&lt;li&gt;Firefox 110 (Build ID 20230214051806) on my 13700k&#x2F;6600xt&lt;&#x2F;li&gt;
&lt;li&gt;Safari on my iPad Mini 6 (iOS 16.3)&lt;&#x2F;li&gt;
&lt;li&gt;Chromium 109.0.5414.117 on my Pixel 7 (CalyxOS)&lt;&#x2F;li&gt;
&lt;li&gt;Firefox Nightly 112.0a1 (Build ID 20230226214053) on my Pixel 7 (CalyxOS)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;And, here are my system specs:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;➜ ~ inxi -v CPU: 16-core (8-mt&#x2F;8-st) 13th Gen Intel Core i7-13700K (-MST AMCP-) speed&#x2F;min&#x2F;max: 1041&#x2F;800&#x2F;5400 MHz Kernel: 6.1.12-zen1-1-zen x86_64 Up: 4d 17h 40m Mem: 8923.9&#x2F;31871.1 MiB (28.0%) Storage: 6.83 TiB (19.2% used) Procs: 557 Shell: Zsh inxi: 3.3.25&lt;&#x2F;code&gt;
Although I was on the previous kernel release when these tests were completed, a
version of kernel 6.1. I don&#x27;t think that matters too much here, but I thought
it may be important to note. I didn&#x27;t enable any flags for any of the browsers
except one to enable JXL support on Firefox Nightly, so all were kept almost
completely stock. Anyhow, let&#x27;s get into the results:&lt;&#x2F;p&gt;
&lt;h3 id=&quot;speedometer-2-1&quot;&gt;Speedometer 2.1&lt;&#x2F;h3&gt;
&lt;p&gt;Here are my Speedometer 2.1 results:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;browser_benchmark_05.svg&quot; alt=&quot;speedometer_2.1_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;browserbench.org&quot;&gt;browserbench.org&lt;&#x2F;a&gt;: &lt;em&gt;&quot;Speedometer is a browser
benchmark that measures the responsiveness of Web applications. It uses demo web
applications to simulate user actions such as adding to-do items.&quot;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Here we see Thorium pull ahead quite a bit, &amp;amp; it&#x27;s a consistent theme that
Thorium takes the lead when it comes to JavaScript stuff. My iPad follows in a
closer second than anticipated considering its running a mobile chip, followed
by my desktop&#x27;s other browsers &amp;amp; finally the Pixel. It is important to note that
the WebKit team produces this benchmark along with MotionMark &amp;amp; JetStream.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;motionmark-1-2&quot;&gt;MotionMark 1.2&lt;&#x2F;h3&gt;
&lt;p&gt;Here are my MotionMark 1.2 results:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;browser_benchmark_03.svg&quot; alt=&quot;motionmark_1.2_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;browserbench.org&quot;&gt;browserbench.org&lt;&#x2F;a&gt;: &lt;em&gt;&quot;MotionMark is a graphics
benchmark that measures a browser’s capability to animate complex scenes at a
target frame rate.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;browserbench.org&#x2F;MotionMark1.2&#x2F;about.html&quot;&gt;More details&lt;&#x2F;a&gt; about the
benchmark are available. Bigger scores are better. For accurate results, please
take your browser window full screen, or rotate your device to landscape
orientation.&quot;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This is where we see Firefox take the lead for once on my desktop, but it is
negated by my iPad, which somehow obliterates my desktop-class Radeon GPU. I&#x27;m
not entirely sure how to interpret these results, but I guess Firefox&#x27;s
WebRender really uses the GPU well &amp;amp; Safari does even better.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;jetstream-2&quot;&gt;JetStream 2&lt;&#x2F;h3&gt;
&lt;p&gt;Here are my JetStream 2 results:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;browser_benchmark_02.svg&quot; alt=&quot;jetstream_2_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;browserbench.org&quot;&gt;browserbench.org&lt;&#x2F;a&gt;: &lt;em&gt;&quot;JetStream 2.1 is a
JavaScript and WebAssembly benchmark suite focused on the most advanced web
applications. It rewards browsers that start up quickly, execute code quickly,
and run smoothly. For more information, read the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;browserbench.org&#x2F;JetStream&#x2F;in-depth.html&quot;&gt;in-depth analysis&lt;&#x2F;a&gt;. Bigger
scores are better.&quot;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Once again, the iPad has a fantastic showing. The Pixel surprisingly gained a
lot of ground here on Chromium, &amp;amp; Thorium continues to show steady &amp;amp; consistent
gains over Chromium.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;basemark-web-3-0&quot;&gt;BaseMark Web 3.0&lt;&#x2F;h3&gt;
&lt;p&gt;Here are my BaseMark Web 3.0 results:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;browser_benchmark_01.svg&quot; alt=&quot;basemark_web_3.0_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.basemark.com&#x2F;benchmarks&#x2F;basemark-web&#x2F;&quot;&gt;basemark.com&lt;&#x2F;a&gt;:
&lt;em&gt;&quot;Basemark Web 3.0 is a comprehensive web browser performance benchmark that
tests how well your mobile or desktop system can use web based applications.
This benchmark includes various system and graphic tests that use the web
recommendations and features. After running the benchmark you will see how your
system performed compared to other systems and browsers in
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;powerboard.basemark.com&#x2F;&quot;&gt;Basemark Power Board&lt;&#x2F;a&gt;. Basemark Web 3.0
measures real-world client-side performance to detect browser bottlenecks.&quot;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This basically shows the same results as before, with Firefox falling further
behind that I&#x27;d expect. I don&#x27;t think that in everyday use it &lt;em&gt;feels&lt;&#x2F;em&gt; much
slower, and actually I think the &quot;feeling&quot; of speed is more pronounced going
from Chromium to Thorium versus Firefox to Chromium. The iPad once again kills
it, which is also not consistent with my &quot;feeling&quot; when it comes to browsing the
web on that device. The Pixel&#x27;s results are relatively in line with how I&#x27;ve
tangibly experienced the device&#x27;s web performance.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;webxprt-4&quot;&gt;WebXPRT 4&lt;&#x2F;h3&gt;
&lt;p&gt;Here are my WebXPRT 4 results:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;browser_benchmark_06.svg&quot; alt=&quot;webxprt_4_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Via
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.principledtechnologies.com&#x2F;benchmarkxprt&#x2F;webxprt&#x2F;&quot;&gt;principledtechnologies.com&lt;&#x2F;a&gt;:
&lt;em&gt;&quot;WebXPRT 4 is a browser benchmark that compares the performance of almost any
web-enabled device. It contains HTML5, JavaScript, and WebAssembly-based
scenarios created to mirror the tasks you do every day: Photo Enhancement,
Organize Album Using AI, Stock Option Pricing, Encrypt Notes and OCR Scan using
WASM, Sales Graphs, and Online Homework. Use WebXPRT to see exactly how well
different devices handle real-world tasks.&quot;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;My interpretation of WebXPRT is that it is a more applicable (albeit more
complex) indicator of web performance, and doesn&#x27;t necessarily mean anything
relative to the &quot;snappy&quot; feeling you get when loading simple web pages. I&#x27;m
surprised to see Firefox pull ahead here, even if it&#x27;s just by a tiny bit, while
the iPad&#x27;s results &lt;em&gt;finally&lt;&#x2F;em&gt; feel more realistic to me. The Pixel&#x27;s results are
basically the same as always, although Firefox Nightly actually ekes out a win
here.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;mozilla-kraken&quot;&gt;Mozilla Kraken&lt;&#x2F;h3&gt;
&lt;p&gt;Here are my Mozilla Kraken results (important that I be thorough in saying lower
is better here):&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;browser_benchmark_04.svg&quot; alt=&quot;mozilla_kraken_results&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;wiki.mozilla.org&#x2F;Kraken&quot;&gt;wiki.mozilla.org&lt;&#x2F;a&gt;: &lt;em&gt;&quot;Kraken is a
JavaScript performance benchmark
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;blog.mozilla.org&#x2F;en&#x2F;mozilla&#x2F;release-the-kraken-2&#x2F;&quot;&gt;created by Mozilla&lt;&#x2F;a&gt;
that measures the speed of several different test cases extracted from
real-world applications and libraries.&quot;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Ultimately, this feels very similar to Speedometer &amp;amp; I honestly kind of doubt
Firefox&#x27;s win is very noteworthy here. The iPad continues to perform really
well, &amp;amp; the Pixel&#x27;s Firefox &amp;amp; Chromium results feel closer to one another. Maybe
Mozilla is optimizing JS performance to score well on this benchmark, as it is
their benchmark, after all.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;Here&#x27;s the geometric mean of all the test results:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;geometric_mean.svg&quot; alt=&quot;geometric_mean&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I calculated the geometric mean using the reciprocal of each Kraken benchmark
result because for that test lower was better, and apparently the geometric mean
is sensitive to values close to zero, so keep that in mind. If there&#x27;s a better
way to do this (like normalizing the values with min-max or median
normalization) let me know.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the geometric mean of all the test results besides MotionMark:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;geometric_mean_2.svg&quot; alt=&quot;geometric_mean_no_motion_mark&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Just because I really doubt the iPad is &lt;em&gt;that&lt;&#x2F;em&gt; performant. I&#x27;m open to being
corrected, though.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the number of first place finishes each browser recieved:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;first_place_finishes.svg&quot; alt=&quot;first_place_finishes&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I will leave you, the audience, to come to your own conclusions here. Overall, I
really like Thorium. Let me know if you&#x27;d like another one of these with certain
special flags, different benchmarks, browser compatibility, or different
versions of the same browser. This took me around 3 days to put together. Thanks
for reading!&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Aviator Versus Handbrake; Visual Quality Assessment</title>
        <published>2023-02-12T00:00:00+00:00</published>
        <updated>2023-02-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Gianni Rosato
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://giannirosato.com/blog/post/aviator-v-handbrake/"/>
        <id>https://giannirosato.com/blog/post/aviator-v-handbrake/</id>
        <content type="html" xml:base="https://giannirosato.com/blog/post/aviator-v-handbrake/">&lt;p&gt;In this test, we will compare the latest Aviator release to the latest Handbrake release in terms of visual quality per bit.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;&lt;em&gt;Note: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;giannirosato.com&#x2F;blog&#x2F;post&#x2F;aviator-1&#x2F;&quot;&gt;rAV1ator&lt;&#x2F;a&gt; isn&#x27;t compared
in this test because it is based on different underlying technologies that would
be hard to compare directly to Aviator &amp;amp; Handbrake, especially for very short
clips.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h1&gt;
&lt;p&gt;This blog post illustrates a comparison between Aviator 0.2.0 &amp;amp; Handbrake 1.6.1
using two sources that illustrate different use cases; the first being the 2160p
&lt;code&gt;crowd_run.y4m&lt;&#x2F;code&gt; source from
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;media.xiph.org&#x2F;video&#x2F;derf&#x2F;&quot;&gt;Derf&#x27;s Test Media&lt;&#x2F;a&gt; &amp;amp; the other being a
short clip of an animated sequence from a popular animated show.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conditions&quot;&gt;Conditions&lt;&#x2F;h2&gt;
&lt;p&gt;This is a relevant comparison because both
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;beta.flathub.org&#x2F;apps&#x2F;net.natesales.Aviator&quot;&gt;Aviator&lt;&#x2F;a&gt; &amp;amp;
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;beta.flathub.org&#x2F;apps&#x2F;fr.handbrake.ghb&quot;&gt;Handbrake&lt;&#x2F;a&gt; use the SVT-AV1
encoder under the hood, and both can encode videos with 10 bit color (default
for Aviator) which should increase the visual quality of the output. Because of
the similarity of the two programs when outputting AV1 video, I was initially
under the impression that upon beginning the test I&#x27;d be incapable of discerning
a difference between the two based on how well they scored. I was mistaken.&lt;&#x2F;p&gt;
&lt;p&gt;Aviator settings: - Resolution: 3840x2160 for crowd_run, 1920x1080 for
animation - CRF: 25-50 (10-60 for the expanded set) - Speed: 6 for crowd_run, 4
for animation - Container: MKV&lt;&#x2F;p&gt;
&lt;p&gt;Handbrake settings: - Format: Matroska - Resolution Limit: None (defaults to
same as Aviator) - All Filters Off - Video Encoder: AV1 10-bit (SVT) -
Framerate: Same as Source - RF: 25-50 (10-60 for the expanded set) - Speed: 6
for crowd_run, 4 for animation&lt;&#x2F;p&gt;
&lt;h2 id=&quot;notes&quot;&gt;Notes&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;While encoding, both used a similar amount of RAM under load but Handbrake
used 10gb in the background even after the encode had completed for each trial
with crowd_run. This meant that I had to restart it every time I wanted to
encode another iteration of the test.&lt;&#x2F;li&gt;
&lt;li&gt;Handbrake failed to copy the subtitle track by default for the animated
source, which can be a problem when encoding any kind of animation where the
audio isn&#x27;t in your native language.&lt;&#x2F;li&gt;
&lt;li&gt;Handbrake&#x27;s &quot;Tune&quot; dropdown explains in the tooltip that it can &quot;improve
efficiency for particular source characteristics&quot; without specifying such
characteristics, so this was left alone. Its dropdown had one option, which
was to tune for PSNR. I know PSNR is not an accurate gauge of visual quality,
but the average user may not because there is no tooltip explaining what this
acronym means.&lt;&#x2F;li&gt;
&lt;li&gt;The results for the animation tests were calculated using the overall bitrate
of the file, not just the video portion; this means that for Aviator, the
subtitle track was factored into that overall bitrate. This shouldn&#x27;t have
hurt the results very much, but offers a minute disadvantage to Aviator.&lt;&#x2F;li&gt;
&lt;li&gt;Film grain synthesis was not used on the crowd_run source, despite the
presence of grain. While Aviator offers this functionality, Handbrake does
not, and most visual quality metrics don&#x27;t understand the benefits of grain
synthesis anyway despite the improvement to visual quality offered by the
feature for sources with grain present.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;results&quot;&gt;Results&lt;&#x2F;h1&gt;
&lt;p&gt;These tests were run using the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;cloudinary&#x2F;ssimulacra2&quot;&gt;SSIMULACRA2&lt;&#x2F;a&gt; visual quality metric
via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-av&#x2F;ssimulacra2&quot;&gt;ssimulacra2_rs&lt;&#x2F;a&gt;. This metric is
designed to model human vision far better than VMAF, SSIM, PSNR, &amp;amp; other less
effective alternatives.&lt;&#x2F;p&gt;
&lt;p&gt;First, the crowd_run results from CRF 25 through 50.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;aviator_handbrake_crowdrun.svg&quot; alt=&quot;crowd_run&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This is a clear and decisive victory for Aviator by a larger margin than I
initially thought possible between the two utilities, considering that they&#x27;re
both using 10 bit SVT-AV1. Aviator&#x27;s out-of-the box tuning for visual quality
has paid off.&lt;&#x2F;p&gt;
&lt;p&gt;It is important to note that these are incredibly high bitrates. The scene is
very complex, and the video being 2160p50 makes it require a lot of bits to
achieve a watchable level of visual quality.&lt;&#x2F;p&gt;
&lt;p&gt;SVT-AV1 defaluts to CRF 35 internally, while Aviator defaults to CRF 32 &amp;amp;
Handbrake defaults to RF 30. The results above were done in increments of 5 from
CRF&#x2F;RF 25 through 50 (25, 30, 35, etc). In order to get the bigger picture, I
tested a wider quality range from 10 through 60 that dips into the realm of
impracticality a bit given the obscene bitrate approached by lower CRF&#x2F;RF values
&amp;amp; the relatively low quality image produced with higher CRF.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the expanded results, with CRF 10 through 60.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;aviator_handbrake_expanded.svg&quot; alt=&quot;crowd_run_expanded&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Here, we see the two become relatively the same at lower &amp;amp; higher quality. While
Aviator has a tiny advantage at lower quality, Handbrake looks to take the lead
by an almost imperceptible margin at higher quality. For this source, because
the bitrate skyrockets beyond CRF&#x2F;RF 25 &amp;amp; the quality plummets beyond CRF&#x2F;RF 50,
I would consider this a win for Aviator in the range I&#x27;d consider usable that
encompasses the default quality levels for Aviator, Handbrake, &amp;amp; SVT-AV1&#x27;s stock
behavior.&lt;&#x2F;p&gt;
&lt;p&gt;For the animation test, CRF 25 through 50 were tested.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;aviator_handbrake_animation.svg&quot; alt=&quot;animation&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This sees less of a performance delta than the more lifelike crowd_run source,
and also sees bitrates reach an acceptable level that would be more common to
see from an animated 1080p24 source. Aviator sees an advantage here still, with
the gap widening at slightly higher bitrates.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h1&gt;
&lt;p&gt;It is clear that Aviator&#x27;s prioritization of visual quality performance has paid
off, even with SSIMULACRA2 being a synthetic benchmark. It appears that
Handbrake is only worth using when dipping below CRF&#x2F;RF 20, but when fine detail
preservation at very high bitrate is a priority it may be worth using another
codec (which Handbrake will offer you the option to use, seeing its diverse
selection of codecs besides AV1). Even then, the quality difference is minute &amp;amp;
may vary between sources. Aviator is the undisputed AV1 champ between the two,
and appears to win in situations where AV1 is most useful.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;grain-synth&quot;&gt;Grain Synth&lt;&#x2F;h2&gt;
&lt;p&gt;While it is hard to benchmark, it is worth mentioning that Aviator supports film
grain synthesis (&quot;Grain Synth&quot;) while Handbrake does not. This can improve the
visual quality of any source with grain present by removing it &amp;amp; reapplying a
synthesized version at decode time. This allows the encoder to spend less bits
compressing grain (which is notoriously difficult to compress) and instead apply
it artificially with little to no discernible difference to the viewer. The
crowd_run source (or any live action source that hasn&#x27;t been heavily denoised)
has grain present, and while SSIMULACRA2 doesn&#x27;t totally understand the benefits
of grain synthesis, it is clear to even the untrained eye the advantage it
offers.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;sources&quot;&gt;Sources&lt;&#x2F;h2&gt;
&lt;p&gt;If you&#x27;d like the encoded clips I used to run this test, I will give them to you
for crowd_run. Otherwise, feel free to replicate the crowd_run segment for
yourself or the animation segment using another animated source of your
choosing. Thanks for reading!&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Aviator 0.2.0</title>
        <published>2023-02-11T00:00:00+00:00</published>
        <updated>2023-02-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Gianni Rosato
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://giannirosato.com/blog/post/aviator-flathub/"/>
        <id>https://giannirosato.com/blog/post/aviator-flathub/</id>
        <content type="html" xml:base="https://giannirosato.com/blog/post/aviator-flathub/">&lt;p&gt;Aviator aims to enable simple, accessible AV1 encoding for everyone with a GUI powered by a fast AV1 encoder.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;h2 id=&quot;why&quot;&gt;Why?&lt;&#x2F;h2&gt;
&lt;p&gt;First, you can install
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;flathub.org&#x2F;apps&#x2F;details&#x2F;net.natesales.Aviator&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;To enable simple, accessible AV1 encoding for the masses via Flathub. See my
post about rAV1ator &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;giannirosato.com&#x2F;blog&#x2F;post&#x2F;aviator-1&#x2F;&quot;&gt;here&lt;&#x2F;a&gt; for
the rav1e + av1an based version of this utility.&lt;&#x2F;p&gt;
&lt;p&gt;Aviator has used SVT-AV1 &amp;amp; FFmpeg for a while, but has been missing a couple key
features including a progress bar, a stop encode button, and more buttons on the
header bar then just the close button. I&#x27;ve been glad to make this release
available on Flathub, but I think it has surfaced some serious issues with how
Flathub allows you to build applications.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;rav1ator-vs-aviator-flathub&quot;&gt;rAV1ator vs Aviator: Flathub&lt;&#x2F;h2&gt;
&lt;p&gt;There&#x27;s &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;flathub&#x2F;flathub&#x2F;issues&#x2F;3392&quot;&gt;an open issue&lt;&#x2F;a&gt;
regarding exactly my complaint here, which is that Flathub doesn&#x27;t allow
&lt;code&gt;--share=network&lt;&#x2F;code&gt; in the build-args. This places an undue burden on the
developer to jump through extra hoops to make &quot;reproducible builds,&quot; which I
could guess there is potential to do with crypto instead of putting it on the
dev. I champion Flatpak because of its superb user experience and seamless
integration with the Linux desktop, but from a developer standpoint it is a
nightmare of quirks &amp;amp; workarounds that require a depth of experience that many
simply do not possess. Even if I did have such experience, I&#x27;m not sure how I&#x27;d
be able to ship rAV1ator on Flathub with such a hurdle.&lt;&#x2F;p&gt;
&lt;p&gt;Aviator is great, and I&#x27;d easily recommend to anyone interested in video
encoding. But I&#x27;d like it to be the best I can make it, and Flathub is simply
not allowing this to happen. Hopefully with enough complaining we&#x27;ll be able to
fix things, but the light at the end of the tunnel doesn&#x27;t seem to be
approaching very quickly.&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>rAV1ator 1.0</title>
        <published>2023-02-08T00:00:00+00:00</published>
        <updated>2023-02-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Gianni Rosato
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://giannirosato.com/blog/post/aviator-1/"/>
        <id>https://giannirosato.com/blog/post/aviator-1/</id>
        <content type="html" xml:base="https://giannirosato.com/blog/post/aviator-1/">&lt;p&gt;rAV1ator, a powerful AV1 encoding GUI, sees its 1.0 release.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;Edit (Feb 11, 2023): This version of Aviator is now called &lt;em&gt;rAV1ator&lt;&#x2F;em&gt; &amp;amp; is
distributed outside of Flathub. Instructions to install are below, &amp;amp; the page
has been edited to reflect the sitation.&lt;&#x2F;p&gt;
&lt;p&gt;Edit 2 (Mar 8, 2024): rAV1ator is not to be confused with &lt;em&gt;rAV1ator CLI&lt;&#x2F;em&gt;, which
is a different tool based on different underlying technologies for the command
line. It does not share any &quot;DNA&quot; with this article&#x27;s rAV1ator.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-is-rav1ator&quot;&gt;What is rAV1ator?&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;manifesto&quot;&gt;Manifesto&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;codecs.wiki&#x2F;docs&#x2F;utilities&#x2F;rAV1ator&quot;&gt;rAV1ator&lt;&#x2F;a&gt; was designed to make
AV1 encoding accessible to the average person, but with the intention to teach
someone who is a total beginner the ins &amp;amp; outs of encoding AV1 videos. You can
install it by running
&lt;code&gt;flatpak --user remote-add --no-gpg-verify project-volo https:&#x2F;&#x2F;giannirosato.com&#x2F;repo &amp;amp;&amp;amp; flatpak --user install project-volo net.natesales.rAV1ator&lt;&#x2F;code&gt;.
I don&#x27;t need to make the pitch for the open source video codec AV1 - you can
find that &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;wikiless.org&#x2F;wiki&#x2F;AV1&quot;&gt;here&lt;&#x2F;a&gt; - but currently I believe a
large issue barring passionate interest in AV1 is how hard it is getting
started. There are tools out there that make it easy to use the SVT-AV1 encoder
with a GUI, but I think there&#x27;s a lack of other alternatives that are more
flexible and use cutting-edge technology while remaining easy to understand
(especially as it pertains to Linux where many are familiar with the command
line &amp;amp; don&#x27;t see the need for a GUI). Enter rAV1ator.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-does-it-work&quot;&gt;How does it work?&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;rav1e-av1an&quot;&gt;rav1e + av1an&lt;&#x2F;h3&gt;
&lt;p&gt;Behind the scenes, rAV1ator uses the Rust-based
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;BlueSwordM&#x2F;rav1e&quot;&gt;rav1e&lt;&#x2F;a&gt; encoder which has seen decent
increases in speed recently and is improving more every day. We decided to use
rav1e in order to give users a memory-safe AV1 encoder implementation that
prioritizes visual quality &amp;amp; &#x27;just works&#x27; for the most part. Specifically, we
are using BlueSwordM&#x27;s fork which has been tuned to improve visual quality.
Alongside rav1e, we are using a custom
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;natesales&#x2F;Av1an&quot;&gt;av1an&lt;&#x2F;a&gt; fork to split videos into multiple
chunks that can be encoded in parallel to increase speed &amp;amp; provide more options
to the user. av1an also allows us to use chroma noise with our grain synthesis
option which is discussed two sections down.&lt;&#x2F;p&gt;
&lt;p&gt;We&#x27;ve configured av1an to spawn chunks automatically, which it does based on
your CPU core count &amp;amp; RAM quantity. Each worker is allocated one thread to
account for systems that may not have multithreading. This default provides a
usable balance between not aggressively hogging system resources &amp;amp; still
performing well. That being said, AV1 encoding is heavy and will tax your
system.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;video&quot;&gt;Video&lt;&#x2F;h3&gt;
&lt;p&gt;The first screen is unassuming, with sane defaults that respect the fact that a
user might not know exactly what they are messing with right out of the box.
Helpful tooltips are provided upon hovering over almost anything, with detailed
descriptions of each function.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;aviator_video.webp&quot; alt=&quot;rAV1ator&amp;#39;s video screen&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Resolution will match the video source, &amp;amp; otherwise defaults to 1536x864.
rav1e&#x27;s quantizer is set to default to 100 internally, but I believe 80 is a
better option for the sake of providing better quality so that is what we use as
a default. Speed 6 is a bit on the faster side for my taste, but not cripplingly
slow on most systems. Grain synthesis is disabled by default, as it is not
always desirable.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;grain-synthesis&quot;&gt;Grain Synthesis&lt;&#x2F;h3&gt;
&lt;p&gt;AV1 provides a unique feature in the form of grain synthesis, which has largely
been reserved for use in the command line for those who know its benefits. Grain
synthesis aims to provide natural film grain to a video without hurting
compression efficiency by removing grain from the source &amp;amp; reapplying it at
decode time as a filter. As explained in the in-app tooltip, film grain levels
are equivalent to ISO noise from a camera. 1 would be ISO 100, 24 would be ISO
2400, continuing up to 64. Because of av1an, we are also able to apply this
noise as chroma noise which increases its realism.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;audio&quot;&gt;Audio&lt;&#x2F;h3&gt;
&lt;p&gt;The audio screen provides more sane defaults; these include compression via the
highly efficient &amp;amp; open source Opus codec, an option to downmix the output to
stereo from a larger number of channels, and a toggle for variable bit rate.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;aviator_audio.webp&quot; alt=&quot;rAV1ator&amp;#39;s audio screen&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;By default, the audio bitrate will be pulled from the source. If this isn&#x27;t
detected, it will default to 48kbps which is admittedly rather low but
ultimately not something I&#x27;ve heard many complaints about. I think it provides a
reasonable level of quality for more general applications, like screen
recordings or smartphone videos. Downmixing to stereo uses ffmpeg&#x27;s default
&lt;code&gt;-ac 2&lt;&#x2F;code&gt; flag which I&#x27;ve found to be decent and reliable despite discarding the
subwoofer channel(s).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;output&quot;&gt;Output&lt;&#x2F;h3&gt;
&lt;p&gt;The output screen has a file selector with two container options that are
clearly explained with a tooltip, and a big blue &#x27;Encode&#x27; button that eagerly
invites you to start encoding.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;aviator_output.webp&quot; alt=&quot;rAV1ator&amp;#39;s output screen&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Upon pressing the blue button, the progress bar will indicate to you the status
of the encode alongside a percentage.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;aviator_encoding.webp&quot; alt=&quot;rAV1ator&amp;#39;s encoding screen&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Eventually, you will get a notification when the encode is complete &amp;amp; the
progress bar will reflect the status of your encode with a message just in case
you have Do Not Disturb on or missed the notification by accident. If the av1an
process does not complete successfully or you manually halt your encode with the
Stop Encode button, you will be met with a different notification that the
status of the progress bar&#x27;s text will math.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;that-s-great-but-i-m-on-windows&quot;&gt;That&#x27;s great, but I&#x27;m on Windows&lt;&#x2F;h2&gt;
&lt;p&gt;Well, I&#x27;m sorry for you, and you should reconsider your choice of operating
system. That being said, there is a great Windows AV1 encoding GUI called
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;n00mkrad&#x2F;nmkoder&quot;&gt;nmkoder&lt;&#x2F;a&gt; that you should definitely try
out.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;you-re-missing-feature-x-y-z&quot;&gt;You&#x27;re missing feature X, Y, Z&lt;&#x2F;h2&gt;
&lt;p&gt;Right now, I&#x27;m going to let this release fester for a bit before diving back
into development. I would still love to see your suggestions for improvement
over at the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gianni-rosato&#x2F;aviator&#x2F;&quot;&gt;github page&lt;&#x2F;a&gt; where you
can put in issues for whatever you&#x27;d like to see us do in the future.&lt;&#x2F;p&gt;
&lt;p&gt;Regarding hardware encoding:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;compression_comparison.svg&quot; alt=&quot;rAV1ator&amp;#39;s output screen&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I did my
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.google.com&#x2F;spreadsheets&#x2F;d&#x2F;1fuKqYy7ZL28349nGs4DXwQGWcr5Cx2-I-ND6Bec6k0A&#x2F;edit?usp=sharing&quot;&gt;research&lt;&#x2F;a&gt;.
There&#x27;s a time and a place.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;special-thanks&quot;&gt;Special Thanks&lt;&#x2F;h2&gt;
&lt;p&gt;Many thanks to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;natesales&quot;&gt;Nate&lt;&#x2F;a&gt; for walking me through
development since I began learning python as my first real language a couple
weeks ago, and thanks to the people who are a part of the AV1 community for
their passion toward multimedia codecs that keeps our videos crispy, our storage
unconstrained, &amp;amp; our CPUs toasty. Particular thanks to
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;BlueSwordM&quot;&gt;Blue&lt;&#x2F;a&gt; &amp;amp;
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;master-of-zen&quot;&gt;MasterOfZen&lt;&#x2F;a&gt; for their tools that I use in &amp;amp;
out of this project and are indispensible to the community.&lt;&#x2F;p&gt;
&lt;p&gt;Enjoy rAV1ator, happy flying! ✈️&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gianni-rosato&quot;&gt;&lt;img src=&quot;&#x2F;static&#x2F;images&#x2F;my_gh_profile.avif&quot; alt=&quot;Gianni Rosato&quot; &#x2F;&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</content>
    </entry>
</feed>
