<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.2.2">Jekyll</generator><link href="https://www.makeworld.space/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.makeworld.space/" rel="alternate" type="text/html" /><updated>2024-10-15T23:46:18-04:00</updated><id>https://www.makeworld.space/feed.xml</id><title type="html">www.makeworld.space</title><subtitle>makeworld&apos;s website.</subtitle><entry><title type="html">You’re already using the best voting system</title><link href="https://www.makeworld.space/2024/10/best_voting_system.html" rel="alternate" type="text/html" title="You’re already using the best voting system" /><published>2024-10-06T00:00:00-04:00</published><updated>2024-10-05T23:53:26-04:00</updated><id>https://www.makeworld.space/2024/10/best_voting_system</id><content type="html" xml:base="https://www.makeworld.space/2024/10/best_voting_system.html"><![CDATA[<p>Here in North America we vote for our politicians using <a href="https://en.wikipedia.org/wiki/First-past-the-post_voting" class="web-link">plurality</a>,
also called First Past The Post (FPTP).
It’s a bad system, mainly because of <a href="https://en.wikipedia.org/wiki/Spoiler_effect" class="web-link">vote splitting</a>.
It’s been obvious for a long time that change is needed, but what voting system
should we use instead?</p>

<h2 id="irv">IRV</h2>

<p>Let’s get this out of the way first: <a href="https://en.wikipedia.org/wiki/Instant-runoff_voting" class="web-link">instant-runoff voting</a> (IRV, RCV)
is not the solution. There are many issues, but I’ll boil it down to two: complexity, and the center squeeze.</p>

<h3 id="complexity">Complexity</h3>

<p>IRV is significantly more complex than FPTP, and this has several negative effects.</p>

<ul>
  <li>Voters find it harder to vote, discouraging participation</li>
  <li>Election results/processes are harder to understand, increasing distrust &amp; animosity
    <ul>
      <li>Multiple rounds increase confusion and delay</li>
    </ul>
  </li>
  <li>Voting machines must be replaced or fully reprogrammed</li>
</ul>

<p>If you’re a social choice theorist or other form of nerd, you don’t care about any of
this, which is why groups such as <a href="https://www.debian.org/vote/" class="web-link">Debian</a> vote
using possibly the most accurate and complex voting system, the <a href="https://en.wikipedia.org/wiki/Schulze_method" class="web-link">Schulze method</a>.</p>

<p>But here in the real world these concerns matter a lot. Please try explaining how IRV
works to your oldest relative. Seriously, I tried and it didn’t go over great. Or imagine
explaining it to your least favourite local politician. Being able to understand the
voting system of your country really matters!</p>

<p>Changing voting machines feels like a small thing but it’s probably not. At least it’s
a cost that would be worth eliminating if possible.</p>

<h3 id="the-center-squeeze">The center squeeze</h3>

<p>Worse than being complex, IRV has a glaring technical issue.</p>

<p>The <a href="https://en.wikipedia.org/wiki/Center_squeeze" class="web-link">center squeeze</a> is a specific kind of
vote splitting. From Wikipedia:</p>

<blockquote>
  <p>In a center squeeze, a majority-preferred and socially-optimal candidate is eliminated
in favor of a more extreme alternative. Extreme or polarizing candidates who focus on
appealing to a small base of core supporters can thus “squeeze” broadly-popular candidates
trapped between them</p>
</blockquote>

<p>Understanding that the usage of “extreme” doesn’t mean extremist and “center” doesn’t mean centrist,
it’s easy to recall examples of this. Here in Canada there is often vote splitting between the Liberal
Party (center-left) and the NDP (left) that results in the right-wing Conservative Party winning seats.
Even if the majority of voters would prefer the Liberals win over the Conservatives, they may lose due
to votes lost to the NDP. This discourages voters from voting for the NDP in the first place (strategic voting),
resulting in two-party domination.</p>

<p>You are familiar with this for FPTP, but it unfortunately occurs with IRV also. Imagine some results like this:</p>

<table>
  <thead>
    <tr>
      <th>#voters</th>
      <th>Their Vote</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>45</td>
      <td>Worst&gt;Middling&gt;Best</td>
    </tr>
    <tr>
      <td>10</td>
      <td>Middling&gt;Worst&gt;Best</td>
    </tr>
    <tr>
      <td>10</td>
      <td>Middling&gt;Best&gt;Worst</td>
    </tr>
    <tr>
      <td>35</td>
      <td>Best&gt;Middling&gt;Worst</td>
    </tr>
  </tbody>
</table>

<p>It is hopefully clear that “Middling” should win, as it is the party that appeals to the most people.
But with IRV, Middling is eliminated immediately
for having the fewest first-round votes. In the end “Worst” wins. This once agains encourages strategic voting
where Best fans put Middling first to prevent Worst from winning, see <a href="https://rangevoting.org/TarrIrv.html" class="web-link">here</a>
for details.</p>

<p>Empirically, this has happened before, for example <a href="https://en.wikipedia.org/wiki/Center_squeeze#2022_Alaska_special_election" class="web-link">in Alaska</a>.
There are few countries that use IRV nationally: Australia is one and they end up having a
<a href="https://en.wikipedia.org/wiki/List_of_political_parties_in_Australia" class="web-link">two party system</a>.
Ireland’s president is elected using IRV and this is pretty much a two party deal
<a href="https://en.wikipedia.org/wiki/President_of_Ireland#List_of_presidents_of_Ireland" class="web-link">as well</a>.</p>

<p>IRV discourages candidates with broad appeal, and tends towards two-party states.</p>

<h2 id="how-you-vote-already">How you vote already</h2>

<p>Before talking about the fix for this, let’s look at a type of voting I do much more often than election voting.</p>

<p><img src="/assets/images/discord_vote.png" alt="a screenshot of Discord, showing several messages with dates and heart emoji reactions"></p>

<p>Probably you’ve done the same! It’s just the easiest way to pick event dates, the movie to watch, where to go for dinner, etc.
You can select everything you want, not just your favourite, and the results are easy to determine.</p>

<p>This method of voting happens to be superior to IRV, and you already know how to use it. It’s called
<a href="https://en.wikipedia.org/wiki/Approval_voting" class="web-link">approval voting</a>.</p>

<h2 id="approval-voting">Approval Voting</h2>

<p><a href="https://commons.wikimedia.org/wiki/File:Approval_ballot.svg" class="web-link"><img src="/assets/images/approval_ballot.png" alt="mockup of an approval voting ballot with multiple candidates selected"></a></p>

<p>In approval voting, you select all the options you approve of, instead of choosing one as with plurality.
The option/candidate with the most votes wins. It’s that simple. This is better than FPTP, avoids the
issues with IRV, and doesn’t introduce novel issues.</p>

<p>Let’s revisit our criticisms of IRV from before and see if they apply:</p>

<ul>
  <li>
<strong>Voters find it harder to vote:</strong> not really, and voting for only one candidate like before is still allowed</li>
  <li>
<strong>Election results are harder to understand:</strong> nope, votes are just tallied
    <ul>
      <li>There is only one round</li>
    </ul>
  </li>
  <li>
<strong>Voting machines must be replaced/reprogrammed:</strong> nope, only minor code differences should be necessary</li>
  <li>
<strong>Center squeeze:</strong> doesn’t happen with approval voting in practice! Votes are not split
among candidates since voters can vote for more than one candidate.</li>
</ul>

<p>From a more scientific perspective, approval voting has been tested in voting simulations and it does quite well
(click for sources):</p>

<p><a href="https://rangevoting.org/BayRegsFig.html" class="web-link"><img src="/assets/images/bayesian_regret_2000.png" alt="chart showing approval voting doing pretty well on regret ratings, better than IRV but not as good as range"></a></p>

<p><a href="https://electionscience.github.io/vse-sim/VSEbasic" class="web-link"><img src="/assets/images/5vse.png" alt="graph showing approval voting doing well compared to other voting systems"></a></p>

<p>For that last graph, the author Jameson Quinn <a href="https://electionscience.github.io/vse-sim/VSEbasic" class="web-link">wrote</a>:</p>

<blockquote>
  <p>[Approval voting’s] VSE [voter satisfaction efficiency] is around 89-95% for most levels of voter strategy.
That’s not the best of the methods I tested, but it certainly is the best “bang for the buck”;
a simple reform, with basically no downsides, which improves outcomes hugely.</p>
</blockquote>

<p>That just about sums up how I feel about it. There is a massive electoral improvement just sitting around,
waiting to be adopted. And once you consider the complexity that theoretically better methods entail,
approval voting stands out as a clear winner to me.</p>

<h3 id="usage">Usage</h3>

<p>You can take a look at <a href="https://en.wikipedia.org/wiki/Approval_voting#Usage" class="web-link">Wikipedia</a>
for details, but approval voting in practice is mainly limited to two cities, both in the USA:
St. Louis and Fargo. If you’re in the US and want to improve this, the
<a href="https://electionscience.org/" class="web-link">Center for Election Science</a> is a good place to start.</p>

<p>Unfortunately I’m not aware of any Canadian-specific resources. The best I can find is that Ontario MPP Ted Hsu
wrote about it <a href="https://www.ipolitics.ca/news/yes-we-need-a-referendum-on-electoral-reform-but-not-just-any-referendum" class="web-link">one time</a>,
and there was a <a href="https://www.ourcommons.ca/content/Committee/421/ERRE/Brief/BR8547008/br-external/KochWarren-e.pdf" class="web-link">passionate submission</a>
to Trudeau’s 2016 <a href="https://en.wikipedia.org/wiki/Canadian_House_of_Commons_Special_Committee_on_Electoral_Reform" class="web-link">Committee on Electoral Reform</a>
by a computer science student. Clearly there is work to be done.</p>

<p>If you’d like to create approval voting polls online, one site I’ve found is <a href="https://strawpoll.vote/" class="web-link">strawpoll.vote</a>,
which supports approval voting under the name “multiple choice”.</p>

<p>You can find more resources regarding voting systems in my <a href="/garden/Voting%20Systems.html" class="web-link">digital garden</a>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>IRV is better than FPTP, but why stop there? Approval voting is simply better than
both. I will not be endorsing or promoting IRV in the future, and will instead try to raise
awareness of approval voting.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Here in North America we vote for our politicians using plurality, also called First Past The Post (FPTP). It’s a bad system, mainly because of vote splitting. It’s been obvious for a long time that change is needed, but what voting system should we use instead?]]></summary></entry><entry><title type="html">Where I write</title><link href="https://www.makeworld.space/2024/02/where_i_write.html" rel="alternate" type="text/html" title="Where I write" /><published>2024-02-03T00:00:00-05:00</published><updated>2024-02-03T20:00:30-05:00</updated><id>https://www.makeworld.space/2024/02/where_i_write</id><content type="html" xml:base="https://www.makeworld.space/2024/02/where_i_write.html"><![CDATA[<p>Many months ago, I made a goal to write at least one blog post a month on here.
I thought it would do me good to practice writing, and to publish things that were useful to
other people. I still think that, however I clearly haven’t acheived this goal recently.
But where did I write that goal? And where else have I been writing? In lieu of a hard hitting technical
blog post, I’ll give an overview of some other places I’ve been putting down my thoughts.</p>

<h2 id="writing">Writing</h2>

<p>The place that first comes to mind where I write things is my journal, <a href="https://daylio.net" class="web-link">Daylio</a>.
In 2023 I wrote 13.4k+ words there, or 1100+ words per month. That’s more than my last blog post.
Mostly these are just thoughts at the top of my mind that day, or interpersonal
things I’ve been trying to work through. Writing things out can really clear my mind,
and helps me feel like I’m not running through life forgetting those abstract social things,
like “X really surprised me today when…”</p>

<p>Key features of Daylio are logging your mood and activities. I’ve found this to be helpful
personally, to stay more aware of my mood and how I spend my time.
Daylio can use this data to draw correlations or generate reports for you, but personally
these are not that useful since I don’t make myself write an entry every day. Having
that level of data collection is appealing, but only journaling when I feel like it’s 
important helps it not feel like it’s a chore.</p>

<p>Journaling through my phone instead of paper seems little funny, but in practice it’s
very convenient, and quickly typing my thoughts on my phone is already something I’m used to
from messaging friends.</p>

<p>I also use <a href="https://obsidian.md" class="web-link">Obsidian</a>. I previously had a large collection of “cool stuff”-type
bookmarks that I never looked at, and now most of these have been transferred into a much more
accessible <a href="https://maggieappleton.com/garden-history" class="web-link">“personal wiki”</a>, most of which is
<a href="/garden/" class="web-link">public</a>.</p>

<p>I have been contributing content to these pages (beyond the original bookmark links) for the past
several months, mostly adding bits here and there, but occasionally doing deep dives like on
<a href="/garden/Local%20Currencies.html" class="web-link">Local Currencies</a> or <a href="/garden/Computers/Software%20Licensing.html" class="web-link">Software Licensing</a>.</p>

<p>Finally, I set up a self-hosted instance of <a href="https://www.monicahq.com/" class="web-link">Monica</a> in 2023, mostly
just to get my contacts in order. I will write things in here sometimes, like when I’m getting
a lot of information about someone and would like to remember/review all of it, such as when dating.</p>

<h2 id="goal-setting">Goal setting</h2>

<p>Moving on to smaller word counts, there’s goal setting. Discrete tasks that I’m trying to track
or remind myself of all go in the <a href="https://loophabits.org/" class="web-link">Loop Habit Tracker</a> app. It has no
annoying gamification, but the simple act of tracking my progress (or lack thereof) has definitely
helped me stay aware of whatever I’m trying to do. The phrase that’s come to mind is
“tracking shapes behaviour”.</p>

<p>Longer term and more abstract goals go in a private Obsidian note. For 2023 I wrote out a laundry
list of goals across many categories, but nearing the end of the year I realized annual goals
is too long a timeline, at least for my life currently. Starting in winter 2023-24, I’ve set
goals for the season (<a href="https://en.wikipedia.org/wiki/Season#Meteorological" class="web-link">meteorological</a>).
For example in winter (Dec 1) I set five goals, and on the first of each month (Jan, Feb, Mar)
I reflect how well progress is going with these goals. On March 1st I will also set the next
few goals for the spring.</p>

<p>It hasn’t even been one full season yet, so I might shift this system, but so far I like it a 
lot. Reflecting monthly is helpful, but more importantly having seasonal goals feels way
more useful than any other time frame. My life and goals are heavily affected by the seasons,
 and it’s useful to be able to shift my focus like that.</p>

<h3 id="synergy️">Synergy⁉️</h3>

<p>The systems described thus far can combine in helpful ways. For example
I like to look over my Daylio entries and moods for the month when
doing my goal reflection, to remind myself of how the last month went.
 If one of my goals is “do activity X at least Y times per month”,
I can look at my habit tracking app to see whether I completed it, then write some of my own
feelings on the topic.</p>

<h2 id="productivity">Productivity</h2>

<p>At the absolute minimum of writing there’s productivity tools. I use Todoist for day-to-day tasks
and a digital calendar to keep track of events. These feel like fundamental, bedrock parts of
daily life that would be hard to live without, to the point that I rarely think about the fact
that I use them.</p>

<h2 id="conclusion">Conclusion</h2>

<p>If there’s one thing I can take away from all these systems, it’s that
it’s truly very helpful to write things down. Grappling with your thoughts can feel like trying
to do mental math, and the solution is the same in both cases: write it out!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Many months ago, I made a goal to write at least one blog post a month on here. I thought it would do me good to practice writing, and to publish things that were useful to other people. I still think that, however I clearly haven’t acheived this goal recently. But where did I write that goal? And where else have I been writing? In lieu of a hard hitting technical blog post, I’ll give an overview of some other places I’ve been putting down my thoughts.]]></summary></entry><entry><title type="html">Don’t prefill config files</title><link href="https://www.makeworld.space/2024/02/no_prefill_config.html" rel="alternate" type="text/html" title="Don’t prefill config files" /><published>2024-02-01T00:00:00-05:00</published><updated>2024-02-01T14:34:24-05:00</updated><id>https://www.makeworld.space/2024/02/no_prefill_config</id><content type="html" xml:base="https://www.makeworld.space/2024/02/no_prefill_config.html"><![CDATA[<p>The biggest design mistake I made with <a href="https://github.com/makew0rld/amfora" class="web-link">Amfora</a>,
my first community open source project, was autogenerating config files.
On startup, the application looks for a config file, and creates one if it doesn’t
exist, full of all the application defaults. At the time, I thought this was great,
as it documents all the existing options, and makes them very visible to the user
in case they want to change them. In the end, this decision created a lot of
headaches and is not something I’d ever do again.</p>

<p>It feels obvious now, but this issue wasn’t to me at the time. The problem is
<em>you can never change any defaults</em>. Here’s a real issue from Amfora: Alice installs
v1 of my code, that uses search engine A. The search engine is of course configurable,
and so by default <code class="language-plaintext highlighter-rouge">search_engine = A</code> gets put in her config file. A little while later,
search engine A goes down forever, and so the wise developer (me) changes the default
search engine to fancy new search engine B. But when Alice installs v2 of the application,
it reads <code class="language-plaintext highlighter-rouge">search_engine = A</code> in her config file and will never use the new default!
Alice would have to read the release notes and manually make the change herself,
which is a pretty big ask.</p>

<p>This gets worse for interrelated config keys, like for theming. Amfora has multiple
config keys to set the colour of various UI elements, and occasionally new ones are
added. If all these keys were set in the config file by default, then the default
theme could never be changed. For example, imagine a v3 that adds a new theme key
like <code class="language-plaintext highlighter-rouge">button5_colour</code>, and also changes the default theme. For users with the 
application preinstalled, they would see the old theme like before from their config file,
except the new
“button 5” would have a colour from the new theme, looking weird or even invisible.
In Amfora’s case this was thankfully avoided as theme keys were not kept in the
default config.</p>

<h2 id="the-solution">The solution</h2>

<p>Many applications on my laptop and server appear to solve this problem in a half-baked
manner, where all the configuration keys are indeed written to the config file, but
as comments. Technically this avoids the problem described above, but I’m not a fan
since this can mislead the user. When the application is updated and the defaults
change, these comments will then be out of date, possibly leading to some frustating
debugging sessions. (“I swear IPv4 was already disabled!”)</p>

<p>In my opinion the solution to this is simple: the user has to write the config
options themselves. At most the application could create the file with
just a few comments, instructions on how to find documentation (manual, web page).
This way the only options in the config file are the ones that the user 
manually written in. Only those will remain in place
when application defaults are changed.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[The biggest design mistake I made with Amfora, my first community open source project, was autogenerating config files. On startup, the application looks for a config file, and creates one if it doesn’t exist, full of all the application defaults. At the time, I thought this was great, as it documents all the existing options, and makes them very visible to the user in case they want to change them. In the end, this decision created a lot of headaches and is not something I’d ever do again.]]></summary></entry><entry><title type="html">It’s time for timestamping</title><link href="https://www.makeworld.space/2023/09/time_for_timestamping.html" rel="alternate" type="text/html" title="It’s time for timestamping" /><published>2023-09-07T00:00:00-04:00</published><updated>2023-09-07T18:45:20-04:00</updated><id>https://www.makeworld.space/2023/09/time_for_timestamping</id><content type="html" xml:base="https://www.makeworld.space/2023/09/time_for_timestamping.html"><![CDATA[<p>Recently I’ve been thinking a lot about <a href="https://en.wikipedia.org/wiki/Trusted_timestamping" class="web-link">trusted timestamping</a>.
Normally timestamping is very easy: simply adding a timestamp field to your metadata and calling <code class="language-plaintext highlighter-rouge">time.Now()</code> or similar.
But trusted timestamping is concerned with proving that the provided timestamp is actually correct, and wasn’t forged
after the fact.</p>

<p>This is simple for one direction: proving some data existed <em>after</em> a certain point in time. That can be easily
achieved by including unguessable current information, such as news headlines and stock prices.
A <a href="https://tvtropes.org/pmwiki/pmwiki.php/Main/AuthenticationByNewspaper" class="web-link">classic</a> pop culture
example is kidnappers proving a hostage is still alive by having them pose with the day’s newspaper.</p>

<p>But trusted timestamping is concerned with the opposite direction: proving data existed <em>before</em>
a certain time. This is not so easily done, but it is possible, and the use cases are quite important in my opinion.</p>

<h2 id="purpose">Purpose</h2>

<p>I see three main reasons for doing trusted timestamping.</p>

<ol>
  <li>Verifying old cryptographic signatures</li>
  <li>Data integrity</li>
  <li>Proof of technology ✨</li>
</ol>

<p>In addition to these there is the obvious one: authenticity. Trusted timestamping can prove that a document that claims
to have been made on a certain date actually was.</p>

<h3 id="verifying-signatures">Verifying signatures</h3>

<p>This is the mainstream industry use case, as far as I can tell. For example Apple <a href="https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/resolving_common_notarization_issues" class="web-link">requires</a>
timestamping as part of its code-signing process. The reasoning is simple: sometimes private keys
get leaked or revoked. After that happens, how can we validate any previous signatures? They might have been
made by a third party using the leaked key. But if the signature was timestamped, we can know that the signature
was created before the key was made invalid. This is a <a href="https://security.stackexchange.com/a/172650" class="web-link">real problem</a>
that trusted timestamping solves.</p>

<h3 id="data-integrity">Data integrity</h3>

<p>A basic method of ensuring data integrity is storing hashes. But what if those hashes are tampered with?
Using signatures instead allows us to verify that no one but a person/org we trust has modified the data.
But what if we don’t want to trust that person, or their ability to guard their keys? Using signatures
adds an extra moving part and security risk that in some cases may not even be needed. If we can agree
that the data was valid and unmodified at a certain point in time, trusted timestamping will provide
data integrity forever. An example use case is backups, or storing org-internal archival data like legal agreements.</p>

<h3 id="proof-of-technology">Proof of technology</h3>

<p>This is my personal, special, modern use case. As AI-generated content gets better and better, being able
to prove that a piece of media was generated before certain AI software even existed will become extremely
important. Trusted timestamping has the ability to end all these new uncomfortable arguments that such-and-such picture was
created whole-cloth by some incrutable algorithm, and instead bring us back into old school discussions about
whether it looks “PhotoShopped”.</p>

<p>Of course, it can be argued we are too far gone at this point, AI image generation is too good. But AI video
remains unsolved for now, and the images are not truly perfect yet. As the saying goes, the best time to 
<del>plant a tree</del> timestamp data was 20 years ago, the second best time is now.</p>

<h2 id="software">Software</h2>

<p>There currently exist two viable trusted timestamping methods I am aware of. There is <a href="https://opentimestamps.org/" class="web-link">OpenTimestamps</a>,
and there is the Time-Stamp Protocol, or <a href="https://datatracker.ietf.org/doc/html/rfc3161" class="web-link">RFC 3161</a>.
The former timestamps using Bitcoin (but efficiently, not one timestamp per block) and the latter uses the signature
of a trusted service (Time Stamp Authority).</p>

<p>Here is a quick comparison:</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th style="text-align: center">OpenTimestamps</th>
      <th style="text-align: center">RFC 3161</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Requires trusted third-party</td>
      <td style="text-align: center">❌</td>
      <td style="text-align: center">✅</td>
    </tr>
    <tr>
      <td>Uses third-party</td>
      <td style="text-align: center">✅</td>
      <td style="text-align: center">✅</td>
    </tr>
    <tr>
      <td>Blockchain sync to verify</td>
      <td style="text-align: center">✅</td>
      <td style="text-align: center">❌</td>
    </tr>
    <tr>
      <td>Free</td>
      <td style="text-align: center">✅</td>
      <td style="text-align: center">✅</td>
    </tr>
    <tr>
      <td>Small proof</td>
      <td style="text-align: center">✅</td>
      <td style="text-align: center">✅</td>
    </tr>
    <tr>
      <td>Proof under &lt;1 KiB</td>
      <td style="text-align: center">✅</td>
      <td style="text-align: center">❌</td>
    </tr>
    <tr>
      <td>Deniability</td>
      <td style="text-align: center">✅</td>
      <td style="text-align: center">✅</td>
    </tr>
    <tr>
      <td>Data privacy</td>
      <td style="text-align: center">✅</td>
      <td style="text-align: center">✅</td>
    </tr>
    <tr>
      <td>Fast</td>
      <td style="text-align: center">✅</td>
      <td style="text-align: center">✅</td>
    </tr>
    <tr>
      <td>Industry support</td>
      <td style="text-align: center">❌</td>
      <td style="text-align: center">✅</td>
    </tr>
    <tr>
      <td>Can verify using web CA store</td>
      <td style="text-align: center">❌</td>
      <td style="text-align: center">✅</td>
    </tr>
  </tbody>
</table>

<p>You can learn more about OpenTimestamps on the website. Beyond the actual RFC linked above, you can learn
more about RFC 3161 by playing around with OpenSSL, see <code class="language-plaintext highlighter-rouge">man openssl-ts</code>.</p>

<p>I’m not going to dive any deeper into these tools than the above table, but my general recommendation is to
use RFC 3161 with a good third-party unless not needing to trust anyone is actually a requirement
for your project. And you could always do both!</p>

<h2 id="usage">Usage</h2>

<p>OpenTimestamps has a CLI tool and libraries in several programming languages, see <a href="https://opentimestamps.org/#code-repositories" class="web-link">here</a>.
It also has a GUI <a href="https://opentimestamps.org/#stamp-and-verify" class="web-link">on the website</a>.</p>

<p>RFC 3161 is supported by OpenSSL as a CLI tool with the <code class="language-plaintext highlighter-rouge">openssl ts</code> command. To make RFC 3161 timestamping on the CLI easier (as it’s not just one OpenSSL command), I’ve created a Bash script each for timestamping and verifying. You can get them
<a href="https://github.com/makew0rld/rfcts" class="web-link">here</a>.</p>

<p>The only GUI option for RFC 3161 I’m aware of is on the <a href="https://freetsa.org/" class="web-link">freeTSA.org</a> website,
although personally I have no reason to trust freeTSA. Better than nothing though, for anyone
who doesn’t use the commandline.</p>

<p>For trusted third-parties there are
<a href="https://gist.github.com/Manouchehri/fd754e402d98430243455713efada710" class="web-link">various</a>
options, including reputable companies such as <a href="https://knowledge.digicert.com/generalinformation/INFO4231.html" class="web-link">DigiCert</a>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>It’s time for trusted timestamping! The cost of this is basically zero (storing a few kilobytes of proof at most),
but the advantage can be very large. It’s one of those things that you’ll wish you had done at the start.</p>

<p>Many groups can act on this (in increasing order of importance):</p>

<ul>
  <li>
<strong>General public</strong>: timestamp your important documents and media to prove their authenticity. You can do this today on your phone with
<a href="https://proofmode.org/about" class="web-link">ProofMode</a>, and on the desktop using CLI or GUI tools as discussed above.</li>
  <li>
<strong>Archivists</strong>: timestamp everything you archive, for integrity and authenticity. For redundancy consider using both methods mentioned above and
<a href="https://gist.github.com/Manouchehri/fd754e402d98430243455713efada710" class="web-link">many</a> RFC 3161 timestamp authorities.</li>
  <li>
<strong>Developers</strong>: if you
work on software where data or signature integrity is at all important, add trusted timestamping to
your application.
And if you use Git signing, you can easily start timestamping all those signatures, see
<a href="https://github.com/opentimestamps/opentimestamps-client/blob/master/doc/git-integration.md" class="web-link">here</a>.</li>
</ul>

<p>If you create software or projects related to trusted timestamping, feel free to let me know about them!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Recently I’ve been thinking a lot about trusted timestamping. Normally timestamping is very easy: simply adding a timestamp field to your metadata and calling time.Now() or similar. But trusted timestamping is concerned with proving that the provided timestamp is actually correct, and wasn’t forged after the fact.]]></summary></entry><entry><title type="html">Bye, Gemini</title><link href="https://www.makeworld.space/2023/08/bye_gemini.html" rel="alternate" type="text/html" title="Bye, Gemini" /><published>2023-08-05T00:00:00-04:00</published><updated>2023-08-05T20:09:37-04:00</updated><id>https://www.makeworld.space/2023/08/bye_gemini</id><content type="html" xml:base="https://www.makeworld.space/2023/08/bye_gemini.html"><![CDATA[<p>The <a href="https://gemini.circumlunar.space/" class="web-link">Gemini protocol</a> was a big part of my COVID-19 lockdown experience.
Discovering this underground, small protocol, having long discussions on the mailing list, and most
importantly for me, developing <a href="https://github.com/search?q=owner%3Amakew0rld+gemini&amp;type=repositories" class="web-link">software</a>.
My terminal Gemini browser, <a href="https://github.com/makew0rld/amfora" class="web-link">Amfora</a>, was my first public
FOSS project, something actually intended for a wider audience to use.
It succeeded beyond my expectations (but within my hopes), and I’m proud to say it now
has thousands of downloads.</p>

<p>In the early days of Gemini, I had a lot of fun reading posts as they emerged from various <abbr title="Gemini [b]logs">gemlogs</abbr>,
responses to those posts, and writing my own posts. It felt new and fresh and fun, an exciting secret place full of potential.
Eventually, as the quarantine stage of the pandemic winded down and my life shifted, things fell into
a more natural pace for me, casually checking in with gemlog updates and reading people’s posts every
so often. Over time, the novelty of the protocol wore off, which is perfectly normal. When I wasn’t
actually developing code, what I was left with was simply the experience of Gemini (and my browser) as it
actually was, without added feelings around meta topics like protocol specifications.</p>

<p>As much as I want to like and use Gemini, in practice I simply don’t. Over the months (and now years!),
it just doesn’t keep me coming back for more. I first <a href="https://sunbeam.city/@makeworld/106191749002615329" class="web-link">mentioned</a>
this feeling all the way back in May 2021, but I’ve wrestled with writing this blog post because I couldn’t pin down the
reasons. Why don’t I use it? Am I too warped by modern convenience to be able to enjoy a slow Internet?</p>

<h2 id="the-reason">The reason</h2>

<p>After a lot of thinking, I’ve realized there is one main reason I don’t keep coming back to Gemini:
<strong>it offers no advantage over how I already use the Web</strong>.</p>

<p>In practice, the Web already has all the Gemini content I’m interested in from various people, and then of course
everything else.
Having everything in one place (whether my web browser or feed reader) makes for a much nicer experience.</p>

<p>Gemini is a reaction to bloated modern websites, but in fact I don’t actually visit that many gross websites like that.
When I do, my
<a href="https://ublockorigin.com/" class="web-link">ad blocker</a> and <a href="https://gitlab.com/magnolia1234/bypass-paywalls-firefox-clean" class="web-link">paywall bypasser</a>
usually make them decent again. Otherwise, I spend the majority of my non-work Internet time on
lightweight sites like my feed reader and Hacker News, and some time on sites that Gemini can’t emulate:
YouTube, Reddit, Discord.
The reality is that Gemini just wouldn’t actually improve
this experience for me.</p>

<p>Gemini also has downsides.
I enjoy
being able to express a bit of personality on my website through CSS, which is just not possible with Gemini.
And I’m come to realize I really do appreciate the unique design of 
different people’s websites as well.
If someone’s design is unbearable, using my feed reader or Firefox’s reader view gives me the choice
not to experience it, but with Gemini I don’t have the choice at all.</p>

<p>Finally, a more personal issue is that reading long form text in monospace really sucks, and
of course that’s all Amfora can do, operating in the terminal. This makes developing and
testing my browser easy, but makes actually using it for reading gemlog posts not ideal —
for me anyway. Long reading on my computer is already annoying for me, and this doesn’t help.</p>

<h2 id="the-future-of-my-gemini-software">The future of my Gemini software</h2>

<p>So, what does this mean for my software repos that deal with Gemini? Mostly I will be making
official what has been obvious for some time: active development is not happening.
Here is the rundown:</p>

<ul>
  <li>
<a href="https://github.com/makew0rld/amfora" class="web-link">Amfora</a>: maintenance mode. When possible, I’ll make/merge bug fixes, and maybe slowly
merge feature PRs by others.</li>
  <li>
<a href="https://github.com/makew0rld/md2gemini" class="web-link">md2gemini</a>: archived. It’s always been a pile of hacks,
and now I’m not that invested in fixing it.</li>
  <li>
<a href="https://github.com/makew0rld/go-gemini" class="web-link">go-gemini</a>: maintenance mode. Bug and spec fixes only.
The code should be pretty simple to continue maintaining in case anyone is using it.</li>
  <li>
<a href="https://github.com/makew0rld/gemget" class="web-link">gemget</a>: maintenance mode. Being able to simply download
Gemini content will still occasionally be useful to me.</li>
  <li>
<a href="https://github.com/makew0rld/gemlikes" class="web-link">gemlikes</a>: archived. It’s always been a toy, and now
will no longer see any further development.</li>
  <li>
<a href="https://github.com/makew0rld/go-gemini-socks5" class="web-link">go-gemini-socks5</a>: maintenance mode. I somehow
don’t expect <a href="https://github.com/makew0rld/go-gemini-socks5/blob/main/gemsocks5.go" class="web-link">this single file</a>
will need many changes.</li>
</ul>

<h2 id="the-future-of-my-capsule">The future of my capsule</h2>

<p><a href="gemini://makeworld.space" class="web-link">gemini://makeworld.space</a> will remain online for the foreseeable future, but I will no longer be
syndicating my blog posts or site updates there. I will leave existing gemlog posts at least, but
other pages that would require updating may be removed.</p>

<h2 id="bye-gemini">Bye, Gemini</h2>

<p>Goodbye Gemini! I hope you continue to exist for those who want it, and to remind people that Web
alternatives are possible.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[The Gemini protocol was a big part of my COVID-19 lockdown experience. Discovering this underground, small protocol, having long discussions on the mailing list, and most importantly for me, developing software. My terminal Gemini browser, Amfora, was my first public FOSS project, something actually intended for a wider audience to use. It succeeded beyond my expectations (but within my hopes), and I’m proud to say it now has thousands of downloads.]]></summary></entry><entry><title type="html">Thoughts on the Spring ‘83 protocol draft</title><link href="https://www.makeworld.space/2022/06/thoughts_spring_83_draft.html" rel="alternate" type="text/html" title="Thoughts on the Spring ‘83 protocol draft" /><published>2022-06-14T00:00:00-04:00</published><updated>2023-08-05T19:09:45-04:00</updated><id>https://www.makeworld.space/2022/06/thoughts_spring_83_draft</id><content type="html" xml:base="https://www.makeworld.space/2022/06/thoughts_spring_83_draft.html"><![CDATA[<p>Right this morning when I woke up, before I even had a glass of water, I started reading a
<a href="https://maya.land/responses/2022/06/12/robin-sloan-spring-83-initial-musing.html" class="web-link">blog post</a>
by <a href="https://occult.institute/@maya" class="web-link">Maya</a> discussing a new protocol. I was immediately intrigued,
and starting reading the original post, <a href="https://www.robinsloan.com/lab/specifying-spring-83/" class="web-link">Specifying Spring ‘83</a>.
After reading through the blog post, I excitedly moved on to the actual
<a href="https://github.com/robinsloan/spring-83-spec-draft/blob/main/draft-20220609.md" class="web-link">spec draft</a>.
I was amazed. Possibly because it was the first thing my brain had processed all day, but still,
I really loved the idea. What follows is some thoughts on the spec draft, to add to the ongoing
<a href="https://www.robinsloan.com/lab/specifying-spring-83/#discussion" class="web-link">discussion</a>.</p>

<p><em>I am reviewing the spec from commit <a href="https://github.com/robinsloan/spring-83-spec/blob/cc49e599fb2c2e3f4c1c849ff752bdde791a4bba/draft-20220609.md" class="web-link"><code class="language-plaintext highlighter-rouge">1a37b2f</code></a>,
which is from June 10, 2022.</em></p>

<h2 id="design">Design</h2>

<p>Various protocol design comments below.</p>

<h3 id="yaml">YAML</h3>

<p>Please don’t use YAML for the peer list. YAML is <a href="https://www.arp242.net/yaml-config.html" class="web-link">not great</a>.
A simple JSON list would be better. A separate Markdown file can provide human-readable metadata if
needed.</p>

<h3 id="key-rotation">Key rotation</h3>

<p>The key rotation scheme is definitely interesting. I like the idea of limiting the damage a leaked key
can do, similar to TLS cert renewal. However I think the user experience of having to generate a new
key every two years and tell all your friends about it is quite bad, and will make people wonder why
they have to do this. I get the idea of culling old followers, but the reality is that this is a one-way
protocol will no follow notifications, so the effect of this culling won’t be felt.</p>

<p>One solution is to have the majority of people not actually manage their own keys, but that doesn’t
feel great either. I think it would probably be better to just let keys stick around with no expiry. This seems to have
worked out okay for Scuttlebutt.</p>

<h3 id="server-to-server-communication">Server-to-server communication</h3>

<p>First off, it’s not explicitly mentioned that one server sending a board to another triggers that server
to send the board to others, etc. It seems obvious this is the case in retrospect, but I think it would
be good to mention to make the gossip nature of this part of the protocol clear.</p>

<p>More importantly, I think there is a large issue with this push-only server communication. Imagine the
following scenario:</p>

<ol>
  <li>Bob publishes a new version of his board.</li>
  <li>Server A receives and stores this version.</li>
  <li>Alice can view Bob’s latest post using Server A, and all is well.</li>
  <li>Server A goes offline, perhaps for a day or two.</li>
  <li>Bob publishes a new version of his board. Server A never receives this update.</li>
  <li>Server A comes back online.</li>
  <li>Alice uses Server A, and <em>thinks</em> she is getting Bob’s latest post, but she is not.</li>
</ol>

<p>Obviously it’s <a href="https://en.wikipedia.org/wiki/CAP_theorem" class="web-link">impossible</a> to always have perfect
consistency, and in the scenario described things would eventually work out, but I still think this
is a problem. It is easily solved by speccing a server-to-server pull mechanism as well.</p>

<p>This could be something like:</p>

<blockquote>
  <p>On server start up and at every interval X, do a GET request for each key this server is aware of
to two random servers in the realm.</p>
</blockquote>

<p>X should be something long like several days.</p>

<p>This would require all servers to allow GET <code class="language-plaintext highlighter-rouge">/&lt;key&gt;</code> requests for anyone, which goes against a
later part of the spec:</p>

<blockquote>
  <p>Servers are not obligated to provide this endpoint for all requesters; they might provide it only
to users of a particular client, or only to users who have paid for access, or according to any
other scheme.</p>
</blockquote>

<p>I get the idea behind this, and so I’m not sure how to reconcile it with what I’ve said above. I
lean towards requiring all servers to be open to everyone. This has the advantage of making all
clients server-agnostic as well, if they want to be.</p>

<p>Finally, I’m no gossip protocol expert, but it seems like it would be easy for servers to be missed
when only 5 are getting selected at random. It would be nice for someone who knows the math for
these things to be able to explain the probability of servers being left behind.</p>

<h3 id="difficulty-factor">Difficulty factor</h3>

<p>The difficulty factor stuff is definitely cool. But I’m not sure if increasing difficulty only once
most boards are used up
makes sense. Without a single point-of-truth like a blockchain, this can introduce
data races, as others have pointed out. Additionally, due to TTLs board will be disappearing all the
time, so there would need to be literally millions of <em>active</em> users for the difficulty to become
meaningful.</p>

<p>What if this difficulty factor was baked in from the start? Preventing spam from the inception of
the protocol/realm seems better than only near the end. For example the spec could define a
difficulty based on a numerical threshold, or keys needing to have X preceding zeros, etc. Whatever
works in a way that doesn’t weaken Ed25519 (!!). That number would be picked to have a meaningful
difficulty for modern computers, taking maybe 10 minutes or more.</p>

<h3 id="client---server-interaction">Client -&gt; Server interaction</h3>

<p>I understand this is a draft, but I just thought I’d mention that the spec doesn’t explain how
clients are supposed to talk to servers, beyond the APIs available.</p>

<p>For example, when a client user updates their board, what servers does that client send the new
board to? Just its favourite one? A random selection from the realm list? Etc.</p>

<p>One section of the spec alludes to clients maintaining lists of trusted servers, so I expect details
on all this are coming. Looking forward to hearing them!</p>

<blockquote>
  <p>If the signature is not valid, the client must drop the response and remove the server from its
list of trustworthy peers.</p>
</blockquote>

<h3 id="the-agony-and-ecstasy-of-public-key-cryptography">“The agony and ecstasy of public key cryptography”</h3>

<p>I love
<a href="https://github.com/robinsloan/spring-83-spec/blob/cc49e599fb2c2e3f4c1c849ff752bdde791a4bba/draft-20220609.md#2-the-agony-and-ecstasy-of-public-key-cryptography" class="web-link">this section</a>.
Cryptography is beautiful magic, and I’m glad to see it used and appreciated here.</p>

<h2 id="nitpicks">Nitpicks</h2>

<p>Here are some small comments or questions I have about the spec. They don’t change my feelings overall.</p>

<ul>
  <li>Why is the limit 2217 bytes? I’m unable to find any meaning behind this number online. It seems
to me that this should probably be larger, or at least well explained and justified.</li>
  <li>Limiting to HTTP/1.1 seems unecessary, allowing HTTP/2 to be used transparently by servers that
support it would do no harm</li>
  <li>How do clients enforce the board aspect ratio? I don’t know enough about HTML/CSS to understand
how boards wouldn’t just be able to override this with <code class="language-plaintext highlighter-rouge">!important</code> or something.</li>
  <li>The client is supposed to “open links in new windows or tabs” — how does that work when the
board HTML might specifically say to open in the same tab?</li>
  <li>It would be nice to have the correct Content Security Policy in the spec, as they look tricky
to get right.</li>
  <li>The protocol mentions TLS but doesn’t specifically disallow non-TLS servers. Would be good to see
that spelled out.</li>
  <li>Putting the exact CORS requirements needed in the spec would be helpful, as CORS is tricky to get right.</li>
  <li>It’s not mentioned, but it seems like servers should reject PUT requests if the last-modified
meta tag is older or equal to the timestamp of the server’s version of the board, same is
<code class="language-plaintext highlighter-rouge">If-Unmodified-Since</code>
</li>
  <li>The <code class="language-plaintext highlighter-rouge">Prefer</code> header in the spec looks like a big fancy official header like <code class="language-plaintext highlighter-rouge">Vary</code> or <code class="language-plaintext highlighter-rouge">Accept</code> and
I was surprised to look it up and find out it’s not. I could see it becoming one in the future
though, so it might be better to use something like <code class="language-plaintext highlighter-rouge">Spring-Prefer</code>.</li>
  <li>2217 bytes * 10 million is indeed 22.17 GB, but it is also 20.65 GiB, and the latter is more
meaningful for computers.</li>
</ul>

<h2 id="odds-and-ends">Odds and ends</h2>

<p>Here are few thoughts that popped into my head while reading, I couldn’t fit them into other sections.</p>

<ul>
  <li>Nothing mentions how often servers should update their realm list</li>
  <li>DNS could be used to get around having to share public keys with people
    <ul>
      <li>Set a TXT record on <code class="language-plaintext highlighter-rouge">_spring83.mydomain.com</code> with your public key</li>
      <li>Then clients will resolve that, so you can just tell your friends to follow you by entering <code class="language-plaintext highlighter-rouge">mydomain.com</code>
</li>
    </ul>
  </li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>I’m quite excited about this protocol, and will be following its development closely. As usual, I’d
like to dive in over my head and start developing a client and server, but I will hold off this time,
until the spec is a bit more finalized, and likely until Robin releases his software as well. I’ve
got my hands full working on a secret DNS project for now anyway 😉. But I will definitely be participating
when the time comes. Having a little space of HTML to share, with strict limits — it
sounds like an awesome way to experiment and get creative. I look forward to it!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Right this morning when I woke up, before I even had a glass of water, I started reading a blog post by Maya discussing a new protocol. I was immediately intrigued, and starting reading the original post, Specifying Spring ‘83. After reading through the blog post, I excitedly moved on to the actual spec draft. I was amazed. Possibly because it was the first thing my brain had processed all day, but still, I really loved the idea. What follows is some thoughts on the spec draft, to add to the ongoing discussion.]]></summary></entry><entry><title type="html">Wordle on the Badger 2040</title><link href="https://www.makeworld.space/2022/03/wordle_badger2040.html" rel="alternate" type="text/html" title="Wordle on the Badger 2040" /><published>2022-03-24T00:00:00-04:00</published><updated>2023-08-05T19:10:43-04:00</updated><id>https://www.makeworld.space/2022/03/wordle_badger2040</id><content type="html" xml:base="https://www.makeworld.space/2022/03/wordle_badger2040.html"><![CDATA[<p><img src="/assets/images/wordle_badger2040/main_shot.jpg" alt="photo of badger 2040 with completed wordle game" style="height: 60vh;"></p>

<p>When I first saw the <a href="https://shop.pimoroni.com/products/badger-2040" class="web-link">Badger 2040</a> on
Hacker News, I knew I had to get it. It seemed like a great little package to hack on,
with an e-ink screen, buttons, and an LED, all for a good price. And finally
I’d get my hands on an
<a href="https://www.raspberrypi.com/documentation/microcontrollers/rp2040.html" class="web-link">RP2040</a>,
which has interested me since it first came out.</p>

<h2 id="development">Development</h2>

<p>The development experience on the Badger 2040 is really fantastic, and great for beginners.
As someone who’s only experienced embedded development from the side of firmware loading
and C++, this was a welcome change. Here’s how I add a feature to some code on the Badger
2040:</p>

<ol>
  <li>Plug it in</li>
  <li>Open <a href="https://thonny.org/" class="web-link">Thonny</a>
</li>
  <li>Open the <code class="language-plaintext highlighter-rouge">main.py</code> file (or any other file on the device) from within Thonny</li>
  <li>Change the code</li>
  <li>Press the Run button in Thonny and watch the Badger reset instantly and load my new code</li>
  <li>Unplug the Badger once I’m satisfied with my changes</li>
</ol>

<p>It’s super simple and fast, and makes for really efficient development cycles. Combine this
with MicroPython, and overall writing code for the Badger is really fun and fast, which is
exactly what I want for a hobby project. 😁</p>

<p>See the <a href="https://learn.pimoroni.com/article/getting-started-with-badger-2040" class="web-link">getting started guide</a>
for more about coding on the Badger 2040.</p>

<h2 id="wordle">Wordle</h2>

<p>Although the Badger is designed to be used as a badge, I’m not going to a conference any
time soon. So I decided to make a project that subverted things a little bit. The obvious
choice was Wordle, which seems to be the project du jour. As someone who plays Wordle on
the computer, I was excited to create a portable Wordle-playing device. Sure phones exist,
but playing on e-ink is way cooler!</p>

<p>Here’s a demo video. Note I’m using my Badger on its side, the marketed orientation is landscape.</p>

<video controls="" style="height: 80vh;">
<source src="/assets/videos/badger2040_wordle_web.mp4" type="video/mp4"></source>
</video>

<p>This input method obviously has some limitations, but it’s not as tedious as I thought
it would be. It takes longer to enter, but in my experience not so much longer that it’s
annoying to use.</p>

<p>You can play as many games as you like. With no RTC or Wi-Fi support, there was no way to
keep track of the what day it was anyway, so I figured I might as well turn it into a feature.</p>

<p>I was impressed with the update speed. There are a variety of options, and the fastest
one (<code class="language-plaintext highlighter-rouge">UPDATE_TURBO</code>) is really quite fast, and works well for scrolling through letters.
There is definitely some ghosting (although it looks worse in the video than in person), but
that goes away after a slightly longer update when moving to the next cell.</p>

<h2 id="words">Words</h2>

<p>For word data, I extracted the two wordlists from the original Wordle javascript: the list of
winning words and the list of all five-letter English words. With some grep, sed, and Python,
I converted these into two text files, where words were stored concatenated. Since they’re
all 5 bytes long, there’s no need for any separator.</p>

<p>Strangely, the winning words were missing from the total wordlist, so I added them back in and
resorted the list, before saving to a text file. In total the two files were 76,405 bytes
(~75 KiB), but the Badger has lots of flash space so I didn’t need to apply any compression.</p>

<p>To load the files on to the Badger, all I had to do was upload them to the filesystem
through Thonny. Having a native filesystem makes loading data like this a breeze. And I can
read data from the files just like I would with regular Python, using <code class="language-plaintext highlighter-rouge">open()</code> and
<code class="language-plaintext highlighter-rouge">.read()</code>. This is a big difference from what I’m used to with embedded development, where
I’d have to put the data in a variable in some header file somewhere, or use some sort of
preprocessor that would add in the data.</p>

<p>I also ended up having to implement a binary search, which I used to check if the word
submitted for that row was actually a valid word. Another fun part of embedded
developement: getting back to basics!</p>

<h2 id="improvements">Improvements</h2>

<p>There are a few improvements I’d like to make to the project. One abstract improvement would
be to improve the UX/UI — making it easier to tell which cell you’re currently working on,
for example, and/or highlighting the current row.</p>

<p>I’d also like to switch to using partial updates instead of fullscreen updates where
possible. I don’t think these are any faster, but it would make for a less jarring effect
than the whole screen flashing in front of you. Unfortunately for some reason the Y and
height of the update region both have to be a multiple of 8, so it’s not as simple as just
updating the area that changed.</p>

<h2 id="badger-library">Badger library</h2>

<p>Python documentation for the Badger 2040 is available
<a href="https://github.com/pimoroni/pimoroni-pico/tree/main/micropython/modules/badger2040" class="web-link">here</a>.</p>

<p>The dev seems to be on top of things and responded very quickly to my issues, so that was
great. Overall the API is good, I was impressed to see things like decimal text
scaling and arbitrary text rotation. But it would be nice to have the ability to provide
your own custom font, like Adafruit display libs allow. The fonts provided are nice, but
none of them are monospace, for example. And they all seem to be converted from vector
fonts. Using a bitmap font, where pixels are hand-chosen for a specific size, would provide
much cleaner glyphs, at the expense of decimal scaling of course.</p>

<h2 id="conclusion">Conclusion</h2>

<p>I had a lot of fun working on this simple self-contained project, and was happy to fully
complete a personal project. Thanks to Pimoroni for creating this product! I hope to see
people doing cool things with the Badger 2040 in the future as more people get their hands
on them, I think they have a lot of potential.</p>

<p>Code is available on GitHub <a href="https://github.com/makeworld-the-better-one/wordle-badger2040" class="web-link">here</a>.</p>

<p>Thanks for reading!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">How to create Windows EXEs for Python on Linux, using PyInstaller and WINE</title><link href="https://www.makeworld.space/2021/10/linux-wine-pyinstaller.html" rel="alternate" type="text/html" title="How to create Windows EXEs for Python on Linux, using PyInstaller and WINE" /><published>2021-10-04T00:00:00-04:00</published><updated>2021-10-04T17:47:48-04:00</updated><id>https://www.makeworld.space/2021/10/linux-wine-pyinstaller</id><content type="html" xml:base="https://www.makeworld.space/2021/10/linux-wine-pyinstaller.html"><![CDATA[<p>Packaging a Python project into a standalone executable is often a struggle. Thankfully we
have tools like <a href="https://www.pyinstaller.org/" class="web-link">PyInstaller</a> to make this easier. Recently
I ran into another struggle of my own, which was that it seemed impossible to “cross-compile”
with PyInstaller. That is to say, run PyInstaller on one OS, and have it generate an executable
for another. It was hinted at that Linux → Windows was possible using Wine, but I couldn’t find any guides on it.</p>

<p>Eventually, I got it working, so here is that guide. Hope this helps! This guide <em>might</em> also work
on macOS, but I have not tested it.</p>

<hr>

<p><em>Thanks to Noah Broyles for the <a href="https://github.com/noahbroyles/pyinstaller-wine/blob/master/main.py" class="web-link">script</a> that helped me figure this all out.</em></p>

<hr>

<p>First off, I was only able to use Python 3.8. I tried with 3.9, but I was not able to install it
under WINE. If you are able to, feel free to use that, and change references to <code class="language-plaintext highlighter-rouge">38</code> in this post
to <code class="language-plaintext highlighter-rouge">39</code>. I was also using PyInstaller 4.5.1, but other versions should work fine.</p>

<p>The first step is to install WINE for your Linux system. WINE is a compatibility layer that allows
the running of Windows software on non-Windows systems. You can install WINE easily enough from
your distro’s package manager. I am using <code class="language-plaintext highlighter-rouge">wine-6.16</code>, but most likely your distro’s version will
work fine.</p>

<p>Next, download the Python 3.8 Windows installer. You can download that directly from the official
Python Software Foundation <a href="https://www.python.org/ftp/python/3.8.9/python-3.8.9.exe" class="web-link">here</a>. With
it downloaded, you can run <code class="language-plaintext highlighter-rouge">wine python-3.8.9.exe</code> to begin the installation. WINE may prompt you
to install something like .NET or Mono, you should click Yes and let it install.</p>

<p>You should not use the default installation, follow these steps:</p>

<ol>
  <li>Check “Add Python 3.8 to PATH”</li>
  <li>Click “Customize installation</li>
  <li>Click “Next”</li>
  <li>Click “Install for all users”</li>
  <li>Set the install location as <code class="language-plaintext highlighter-rouge">C:\\Python38</code>
</li>
  <li>Click “Install”</li>
  <li>Close the window.</li>
</ol>

<p>Using the <code class="language-plaintext highlighter-rouge">C:\\Python38</code> path helps keep things standard for this blog post.</p>

<p>Now Python 3.8 should be successfully installed in WINE. You can check this by running
<code class="language-plaintext highlighter-rouge">wine C:/Python38/python.exe --version</code>, which will run Windows Python through WINE. Amazing!</p>

<p>Next we’ll need to upgrade pip, to make sure things will install fine. Run
<code class="language-plaintext highlighter-rouge">wine C:/Python38/python.exe -m pip install --upgrade pip</code>. WINE might spit out a lot of warnings
about <code class="language-plaintext highlighter-rouge">libgnutls</code> or something or other, but as long as Pip doesn’t report any errors it’s fine.</p>

<p>Now if your project has any dependencies, you should export them into the standard Python 
<code class="language-plaintext highlighter-rouge">requirements.txt</code> file. You can then install them into your WINE Python with this command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wine C:/Python38/python.exe <span class="nt">-m</span> pip <span class="nb">install</span> <span class="nt">-r</span> requirements.txt
</code></pre></div></div>

<p>If you’re using a superior dependency
manager like <a href="https://python-poetry.org/" class="web-link">Poetry</a>, you should be able to install and use that in WINE,
although I haven’t tested that.</p>

<p>In any case, you’ll need PyInstaller to be installed, so run this command if PyInstaller wasn’t in
<code class="language-plaintext highlighter-rouge">requirements.txt</code> already:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wine C:/Python38/python.exe <span class="nt">-m</span> pip <span class="nb">install </span>pyinstaller
</code></pre></div></div>

<p>Now you can run PyInstaller and an EXE will be built! Just run</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wine C:/Python38/Scripts/pyinstaller.exe ...
</code></pre></div></div>

<p>and replace <code class="language-plaintext highlighter-rouge">...</code> with whatever arguments you used
to build a Linux executable.</p>

<p>Enjoy!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Packaging a Python project into a standalone executable is often a struggle. Thankfully we have tools like PyInstaller to make this easier. Recently I ran into another struggle of my own, which was that it seemed impossible to “cross-compile” with PyInstaller. That is to say, run PyInstaller on one OS, and have it generate an executable for another. It was hinted at that Linux → Windows was possible using Wine, but I couldn’t find any guides on it.]]></summary></entry><entry><title type="html">NFTs</title><link href="https://www.makeworld.space/2021/03/nfts.html" rel="alternate" type="text/html" title="NFTs" /><published>2021-03-19T00:00:00-04:00</published><updated>2021-03-19T10:43:50-04:00</updated><id>https://www.makeworld.space/2021/03/nfts</id><content type="html" xml:base="https://www.makeworld.space/2021/03/nfts.html"><![CDATA[<p>I just looove NFTs, don’t you?!! No problems or concerns, just the perfect way to share the value of art with everyone.
And make some good honest money in the process!!</p>

<p>That’s why I’ve launched my own NFT marketplace, one that’s better than all the rest!! No fuss, no waiting, <del>no crypto</del>,
no trouble at all. Check it out!</p>

<p><a href="https://enoughtea.makeworld.space/" class="web-link">Enough Tea 🍵</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[I just looove NFTs, don’t you?!! No problems or concerns, just the perfect way to share the value of art with everyone. And make some good honest money in the process!!]]></summary></entry><entry><title type="html">Ditherpunk 2 — beyond 1-bit</title><link href="https://www.makeworld.space/2021/02/dithering.html" rel="alternate" type="text/html" title="Ditherpunk 2 — beyond 1-bit" /><published>2021-02-12T00:00:00-05:00</published><updated>2023-08-05T19:10:27-04:00</updated><id>https://www.makeworld.space/2021/02/dithering</id><content type="html" xml:base="https://www.makeworld.space/2021/02/dithering.html"><![CDATA[<p><em>Last updated: Dec. 25, 2022</em></p>

<hr>

<p><em>This post contains some images that need to be viewed at 100% size to be seen correctly. If you normally browse
at a higher zoom level than 100%, please zoom out when you get to any of the images.</em></p>

<p><em>Unfortunately some of these images are out of date. See <a href="https://github.com/makeworld-the-better-one/dither/tree/master/images" class="web-link">here</a>
for up to date images, with slightly improved results.</em></p>

<hr>

<p>Just yesterday, I released my <a href="https://github.com/makeworld-the-better-one/dither" class="web-link">dithering library</a>, written in Go.
It’s the product of many hours of research, experimentation, and refactoring. I’m
excited that it’s finally out, and to see what people create with it. Personally I’d like to create a CLI dithering
tool at some point that uses it.</p>

<p>Creating this library required research into the different algorithms for dithering, and how to implement them.
Unfortunately, not all of that knowledge is easily found. It’s spread across blog posts, Wikipedia pages without
citations, old books, and code comments. Recently, Surma wrote an article called
<a href="https://surma.dev/things/ditherpunk/" class="web-link">Ditherpunk — The article I wish I had about monochrome image dithering</a>.
It is an invaluable resource that combines lots of knowledge about dithering into one place. The main thing missing
from the post, however, is going beyond “monochrome”. Surma shows us how to dither with a 1-bit palette of black and
white, but what about more shades of gray? What about colour images? With this blog post I’d like to explore those
techniques, taking them out of my code and into English, so you can easily apply them elsewhere.</p>

<p><strong>First off, please read Surma’s post.</strong> It explains what dithering is, how it works, and many different algorithms.
I don’t feel the need to explain these again, but merely add on what I’ve learned.</p>

<h2 id="before-we-start">Before we start</h2>

<p>An HN commenter <a href="https://news.ycombinator.com/item?id=26122642" class="web-link">informed me</a> that you cannot accurately represent
linear RGB with just 8-bits (0-255), you need at least 12. Because of this, I have updated the blog post to use
16-bit color (0-65535), and will be updating the library shortly. Make sure you do this in your code too!</p>

<h2 id="overview">Overview</h2>

<p>Dithering operations consist of at least two steps, applied to each pixel. Sometimes there are further steps, like
“modify these nearby pixels”, but these are the basic ones.</p>

<ol>
  <li>Modify the pixel’s colour value.</li>
  <li>Find the colour in the palette that is “closest” to that modified value and use that on the output image.</li>
</ol>

<p>Now, we know from Surma’s post that step 1 must be done with linear RGB values. Otherwise, different values will be
affected differently — for example adding 5 to each colour won’t affect all colours the same way.</p>

<p>But what about step 2? How do we find the closest colour in the palette? In 1-bit dithering we don’t have to worry
about this, because anything above 0.5 is white, and anything below is black. But when your palette colours can be
anyhere in a 3D space, it is something that needs to be figured out.</p>

<p>Perhaps surprisingly, we’re not looking for a way that matches human perception. In fact, we are using Euclidean
distance with linear RGB values, which doesn’t match human perception at all! Why is this?
Thomas Mansencal (<a href="https://github.com/KelSolaar" class="web-link">@KelSolaar</a>) explains it best:</p>

<blockquote>
  <p>You can factor out the observer [the human] because what you are interested in here is basically energy
conservation. The idea being that for a given pool of radiant power emitters, if you remove a certain number of
them, by how much must the radiant power of the remaining ones be increased to be the same as that of the full
pool. It is really a ratio and doing those operations in a linear space is totally appropriate!</p>
</blockquote>

<p>This helped it click for me. Dithering can be thought of as trying to reconstruct the “radiant power” of the original
pixel colours, while restricted to a certain set of “emitters”, aka the palette colours. It is only with linearized
RGB values that we can properly measure the radiant power.</p>

<hr>

<p><strong>Update December 2022</strong>: After agonizing over <a href="https://github.com/makeworld-the-better-one/didder/issues/14" class="web-link">several</a>
<a href="https://github.com/makeworld-the-better-one/dither/issues/9" class="web-link">issues</a> with my dithering code, I eventually
<a href="https://computergraphics.stackexchange.com/q/13170/" class="web-link">discovered</a> an improvement over just Euclidean distance.</p>

<p>Weight each channel distance according to how humans percieve its luminance. This is better than just
“radiant power” from a physics perspective, because human luminance does not treat red, green, and blue equally.
Green is weighted the highest for example, because humans are the most sensitive to green.</p>

<p>The weight values can come
from Rec.709 / sRGB, so now distance is calculated using the weightings <code class="language-plaintext highlighter-rouge">0.2126 R + 0.7152 G + 0.0722 B</code>, where
<code class="language-plaintext highlighter-rouge">R</code>, <code class="language-plaintext highlighter-rouge">G</code>, and <code class="language-plaintext highlighter-rouge">B</code> are the elements in the Euclidean distance calculation. (Squared difference in each channel between
the colour in the palette and the modified pixel) All values are still linearized!</p>

<p>The exact code changes I made for this improvement can be seen <a href="https://github.com/makeworld-the-better-one/dither/pull/12/files" class="web-link">here</a>.</p>

<hr>

<h2 id="random-noise-grayscale">Random Noise (grayscale)</h2>

<p>Random noise is a good one to start with, to show the differences between 1-bit dithering and larger palettes.</p>

<p>Surma does this by basically just adding a random number from -0.5 to 0.5, and then thresholding it.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">grayscaleImage</span><span class="p">.</span><span class="nx">mapSelf</span><span class="p">(</span><span class="nx">brightness</span> <span class="o">=&gt;</span>
  <span class="nx">brightness</span> <span class="o">+</span> <span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">-</span> <span class="mf">0.5</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mf">0.5</span> 
    <span class="p">?</span> <span class="mf">1.0</span> 
    <span class="p">:</span> <span class="mf">0.0</span>
<span class="p">);</span>
</code></pre></div></div>

<p>In my library, there are two separate random noise functions. One is for grayscale, and one is for RGB. The grayscale
one looks like this:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">roundClamp</span><span class="p">(</span><span class="kt">float32</span><span class="p">(</span><span class="n">gray</span><span class="p">)</span> <span class="o">+</span> <span class="m">65535.0</span><span class="o">*</span><span class="p">(</span><span class="n">rand</span><span class="o">.</span><span class="n">Float32</span><span class="p">()</span><span class="o">*</span><span class="p">(</span><span class="n">max</span><span class="o">-</span><span class="n">min</span><span class="p">)</span><span class="o">+</span><span class="n">min</span><span class="p">))</span>
</code></pre></div></div>

<p>The math with <code class="language-plaintext highlighter-rouge">min</code> and <code class="language-plaintext highlighter-rouge">max</code> is just to put the random value in the desired range. If we clean that up it’s more
understandable:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">roundClamp</span><span class="p">(</span><span class="kt">float32</span><span class="p">(</span><span class="n">gray</span><span class="p">)</span> <span class="o">+</span> <span class="m">65535.0</span><span class="o">*</span><span class="n">rand</span><span class="p">())</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">rand()</code> just represents a function that does whatever range we want, -0.5 to 0.5 in this case.</p>

<p>So, obviously this is quite similar to what Surma does. The heavy lifing of the dithering in this case is done by the
other code, the part that finds the best palette colour.</p>

<p>The main thing that’s worth noting here is how the rounding works. <code class="language-plaintext highlighter-rouge">roundClamp</code> rounds the value to an integer, and then
clamps it to the range 0-65535. But how is the rounding done?</p>

<p>Another <a href="https://news.ycombinator.com/item?id=26122642" class="web-link">HN commenter</a> shared <a href="http://www.cplusplus.com/articles/1UCRko23/" class="web-link">this link</a>
which discusses different rounding methods, and the problems with the way rounding is often done. The best solution is to
use a rounding function that does this:</p>

<blockquote>
  <p>Given a number exactly halfway between two values, round to the even value (zero is considered even here).</p>

  <p>round( 1.7 ) –&gt; 2 round( 2.7 ) –&gt; 3<br>
round( 1.5 ) –&gt; 2 round( 2.5 ) –&gt; 2<br>
round( 1.3 ) –&gt; 1 round( 2.3 ) –&gt; 2</p>
</blockquote>

<p>This is not really about dithering, but this is a pretty important point to get things right mathematically.
Make sure you do it! Otherwise your outputs will be biased.</p>

<h2 id="random-noise-rgb">Random Noise (RGB)</h2>

<p>Back to random noise, but for colour this time.</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">r</span> <span class="o">=</span> <span class="n">roundClamp</span><span class="p">(</span><span class="kt">float32</span><span class="p">(</span><span class="n">r</span><span class="p">)</span> <span class="o">+</span> <span class="m">65535.0</span><span class="o">*</span><span class="p">(</span><span class="n">rand</span><span class="o">.</span><span class="n">Float32</span><span class="p">()</span><span class="o">*</span><span class="p">(</span><span class="n">maxR</span><span class="o">-</span><span class="n">minR</span><span class="p">)</span><span class="o">+</span><span class="n">minR</span><span class="p">))</span>
<span class="n">g</span> <span class="o">=</span> <span class="n">roundClamp</span><span class="p">(</span><span class="kt">float32</span><span class="p">(</span><span class="n">g</span><span class="p">)</span> <span class="o">+</span> <span class="m">65535.0</span><span class="o">*</span><span class="p">(</span><span class="n">rand</span><span class="o">.</span><span class="n">Float32</span><span class="p">()</span><span class="o">*</span><span class="p">(</span><span class="n">maxG</span><span class="o">-</span><span class="n">minG</span><span class="p">)</span><span class="o">+</span><span class="n">minG</span><span class="p">))</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">roundClamp</span><span class="p">(</span><span class="kt">float32</span><span class="p">(</span><span class="n">b</span><span class="p">)</span> <span class="o">+</span> <span class="m">65535.0</span><span class="o">*</span><span class="p">(</span><span class="n">rand</span><span class="o">.</span><span class="n">Float32</span><span class="p">()</span><span class="o">*</span><span class="p">(</span><span class="n">maxB</span><span class="o">-</span><span class="n">minB</span><span class="p">)</span><span class="o">+</span><span class="n">minB</span><span class="p">))</span>
</code></pre></div></div>

<p>And simplified:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">r</span> <span class="o">=</span> <span class="n">roundClamp</span><span class="p">(</span><span class="kt">float32</span><span class="p">(</span><span class="n">r</span><span class="p">)</span> <span class="o">+</span> <span class="m">65535.0</span><span class="o">*</span><span class="n">randR</span><span class="p">())</span>
<span class="n">g</span> <span class="o">=</span> <span class="n">roundClamp</span><span class="p">(</span><span class="kt">float32</span><span class="p">(</span><span class="n">g</span><span class="p">)</span> <span class="o">+</span> <span class="m">65535.0</span><span class="o">*</span><span class="n">randG</span><span class="p">())</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">roundClamp</span><span class="p">(</span><span class="kt">float32</span><span class="p">(</span><span class="n">b</span><span class="p">)</span> <span class="o">+</span> <span class="m">65535.0</span><span class="o">*</span><span class="n">randB</span><span class="p">())</span>
</code></pre></div></div>

<p>Pretty simple, it just adds randomness in each channel. This can be done with grayscale images too, but it won’t
work very well, because grayscale colours only actually have one dimension. So it will just not add as much
randomness as you would expect.</p>

<p>Also note that usually you’ll want the random ranges to be the same for R, G, and B.</p>

<p>Here’s an example:</p>

<section class="carousel">

<figure>
<img class="dithered" src="/assets/images/dithering/peppers.png">
<figcaption>
Original image
</figcaption>
</figure>

<figure>
<img class="dithered" src="/assets/images/dithering/random_noise_rgb_red-green-black.png">
<figcaption>
Random Noise (palette is red, green, black)
</figcaption>
</figure>

</section>

<h2 id="ordered-dithering">Ordered Dithering</h2>

<h3 id="modifying-threshold-matrices">Modifying threshold matrices</h3>

<p>Most of the resources online that talk about ordered dithering talk about a “threshold matrix”. “Thresholding” is
how these matrices are applied for 1-bit dithering. You divide the matrix by whatever number is specified, scale
it to the colour value range, and compare it to each pixel in the image. If it’s less than the matrix value, make
it black, otherwise white. Obviously this doesn’t work with any other kind of palette. So what’s the solution?</p>

<p><a href="https://en.wikipedia.org/wiki/Ordered_dithering" class="web-link">Wikipedia</a> offers an answer. Unfortunately there’s no citation,
but I’ve confirmed independently that it works. Here it is with some of my own math added as well.</p>

<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mo stretchy="false">(</mo><mn>65535</mn><mo>×</mo><mi>s</mi><mi>t</mi><mi>r</mi><mi>e</mi><mi>n</mi><mi>g</mi><mi>t</mi><mi>h</mi><mo stretchy="false">)</mo><mo>×</mo><mrow><mo fence="true">(</mo><mfrac><mrow><mi>c</mi><mi>e</mi><mi>l</mi><mi>l</mi><mo>+</mo><mn>1</mn></mrow><mrow><mi>m</mi><mi>a</mi><mi>x</mi></mrow></mfrac><mo>−</mo><mn>0.5</mn><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">(65535 \times strength) \times \left( \frac{cell + 1}{max} - 0.5 \right)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord">65535</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span><span class="mord mathnormal">re</span><span class="mord mathnormal">n</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mord mathnormal">t</span><span class="mord mathnormal">h</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">(</span></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3714em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">ma</span><span class="mord mathnormal">x</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">ce</span><span class="mord mathnormal" style="margin-right:0.01968em;">ll</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">0.5</span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">)</span></span></span></span></span></span></span>

<p><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>c</mi><mi>e</mi><mi>l</mi><mi>l</mi></mrow><annotation encoding="application/x-tex">cell</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">ce</span><span class="mord mathnormal" style="margin-right:0.01968em;">ll</span></span></span></span> is a single cell of the matrix.</p>

<p><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi><mi>t</mi><mi>r</mi><mi>e</mi><mi>n</mi><mi>g</mi><mi>t</mi><mi>h</mi></mrow><annotation encoding="application/x-tex">strength</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span><span class="mord mathnormal">re</span><span class="mord mathnormal">n</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mord mathnormal">t</span><span class="mord mathnormal">h</span></span></span></span> is a percentage, usually a float from <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0</mn></mrow><annotation encoding="application/x-tex">0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">0</span></span></span></span> to <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1.0</mn></mrow><annotation encoding="application/x-tex">1.0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1.0</span></span></span></span>. It’s the amount the matrix will be applied to the
image. The closer to zero it is, the smaller the range of input colors that will be dithered. Colors outside
that range will be quantized. Usually you’ll want a strength of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1.0</mn></mrow><annotation encoding="application/x-tex">1.0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1.0</span></span></span></span>, to apply the matrix and dither fully, but
sometimes reducing it can be useful, to reduce noise in the output image. It is inversely proportional to contrast –
that is, when you reduce the <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi><mi>t</mi><mi>r</mi><mi>e</mi><mi>n</mi><mi>g</mi><mi>t</mi><mi>h</mi></mrow><annotation encoding="application/x-tex">strength</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span><span class="mord mathnormal">re</span><span class="mord mathnormal">n</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mord mathnormal">t</span><span class="mord mathnormal">h</span></span></span></span>, it is visually similar to increasing the contrast.</p>

<p><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi><mi>t</mi><mi>r</mi><mi>e</mi><mi>n</mi><mi>g</mi><mi>t</mi><mi>h</mi></mrow><annotation encoding="application/x-tex">strength</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span><span class="mord mathnormal">re</span><span class="mord mathnormal">n</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mord mathnormal">t</span><span class="mord mathnormal">h</span></span></span></span> can also be negative, from <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0</mn></mrow><annotation encoding="application/x-tex">0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">0</span></span></span></span> to <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo>−</mo><mn>1.0</mn></mrow><annotation encoding="application/x-tex">-1.0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7278em;vertical-align:-0.0833em;"></span><span class="mord">−</span><span class="mord">1.0</span></span></span></span>. This is useful in certain cases where the matrix usually
makes things bright, like what Surma describes with Bayer matrices.</p>

<p>Note that <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>65535</mn></mrow><annotation encoding="application/x-tex">65535</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">65535</span></span></span></span> is multiplied by <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi><mi>t</mi><mi>r</mi><mi>e</mi><mi>n</mi><mi>g</mi><mi>t</mi><mi>h</mi></mrow><annotation encoding="application/x-tex">strength</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span><span class="mord mathnormal">re</span><span class="mord mathnormal">n</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mord mathnormal">t</span><span class="mord mathnormal">h</span></span></span></span> because the colours in my code are in the range <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">[</mo><mn>0</mn><mo separator="true">,</mo><mn>65535</mn><mo stretchy="false">]</mo></mrow><annotation encoding="application/x-tex">[0, 65535]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">[</span><span class="mord">0</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">65535</span><span class="mclose">]</span></span></span></span>. If yours
are different you can change that number.</p>

<p><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>m</mi><mi>a</mi><mi>x</mi></mrow><annotation encoding="application/x-tex">max</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">ma</span><span class="mord mathnormal">x</span></span></span></span> is the value the entire matrix is divided by. It represents the maximum value of the matrix, and normalizes
it by dividing. Usually this is the product of the dimensions of the matrix. It can also be the
largest value in the matrix plus one.</p>

<p>The result of applying this operation to each cell of the matrix is a new, precalculated matrix, which can be added
to a pixel’s colour value for dithering. Adding 0.5 does not need to happen in this case. In my library, I call the
function that does this <code class="language-plaintext highlighter-rouge">convThresholdToAddition</code>, because that’s essentially the purpose of this — converting
a threshold matrix into one that can be used for addition.</p>

<p><strong>Note:</strong> This is designed for matrices that range from <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0</mn></mrow><annotation encoding="application/x-tex">0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">0</span></span></span></span> to <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>m</mi><mi>a</mi><mi>x</mi><mo>−</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">max - 1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">ma</span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1</span></span></span></span>. If you’re using a matrix you found that
starts at <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1</mn></mrow><annotation encoding="application/x-tex">1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1</span></span></span></span>, you’ll usually just want to subtract one from each value when you program it, then apply the operation
I described above.</p>

<h3 id="using-a-modified-matrix">Using a modified matrix</h3>

<p>Now that you’ve modified the matrix so it can be used for addition, it needs to be applied to the image. This is pretty
simple. Use modulus so the matrix values are tiled across the image, and add the same value in the R, G, and B channels.
If you’re using a grayscale image you can just apply it in the one channel, or still use RGB. Since the same value is
being added it doesn’t make much of a difference. Like always, make sure to clamp the values to the proper colour range.</p>

<p>Doing all of this definitely works with colour images, but it’s not the greatest. Here’s an example, where the palette
is red, green, and black.</p>

<section class="carousel">

<figure>
<img class="dithered" src="/assets/images/dithering/peppers.png">
<figcaption>
Original image
</figcaption>
</figure>

<figure>
<img class="dithered" src="/assets/images/dithering/bayer_16x16_red-green-black.png">
<figcaption>
Ordered dithering (Bayer 16x16)
</figcaption>
</figure>

<figure>
<img class="dithered" src="/assets/images/dithering/floyd-steinberg_red-green-black.png">
<figcaption>
Error diffusion dithering (Floyd-Steinberg)
</figcaption>
</figure>

</section>

<p>Here it is where the palette is red, green, black, and yellow.</p>

<section class="carousel">

<figure>
<img class="dithered" src="/assets/images/dithering/bayer_16x16_red-green-yellow-black.png">
<figcaption>
Ordered dithering (Bayer 16x16)
</figcaption>
</figure>

<figure>
<img class="dithered" src="/assets/images/dithering/floyd-steinberg_red-green-yellow-black.png">
<figcaption>
Error diffusion dithering (Floyd-Steinberg)
</figcaption>
</figure>

</section>

<p>As you can see, it doesn’t really emulate any of the yellow in the first example, while Floyd-Steinberg can. Once yellow is added
to the palette it looks pretty good though.</p>

<h2 id="error-diffusion-dithering">Error diffusion dithering</h2>

<p>Error diffusion dithering is done pretty much the same way as Surma described. The main difference is that
the quantization error is measured for R, G, and B, instead of a single gray value. The final bit of (pseudo) code
looks like this:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">roundClamp</span><span class="p">(</span><span class="n">r</span> <span class="o">+</span> <span class="n">er</span> <span class="o">*</span> <span class="n">matrix</span><span class="p">[</span><span class="n">y</span><span class="p">][</span><span class="n">x</span><span class="p">])</span>
<span class="n">roundClamp</span><span class="p">(</span><span class="n">g</span> <span class="o">+</span> <span class="n">eg</span> <span class="o">*</span> <span class="n">matrix</span><span class="p">[</span><span class="n">y</span><span class="p">][</span><span class="n">x</span><span class="p">])</span>
<span class="n">roundClamp</span><span class="p">(</span><span class="n">b</span> <span class="o">+</span> <span class="n">eb</span> <span class="o">*</span> <span class="n">matrix</span><span class="p">[</span><span class="n">y</span><span class="p">][</span><span class="n">x</span><span class="p">])</span>
</code></pre></div></div>

<p>The variables starting with <code class="language-plaintext highlighter-rouge">e</code> represent the current quantization error.</p>

<h3 id="serpentine">Serpentine</h3>

<p>This is unrelated to color dithering, but it’s something my library does that Surma didn’t mention, and something I couldn’t
really find information on online — serpentine dithering.</p>

<p>This is when the horizontal direction of error diffusion dithering 
(usually left-to-right) reverses for every other line, going right-to-left. This is simple to implement, and improves results
by removing line artifacts.</p>

<p>The one thing that was hard to find any info on was how you need to reflect the error diffusion
matrix horizontally every time you go backwards as well. Otherwise you modify pixels to your right that have already been
finalized.</p>

<p>Here’s an example of the difference serpentine dithering makes, using the simple 2D error diffusion matrix, as it has the worst
artifacts.</p>

<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>m</mi><mi>a</mi><mi>t</mi><mi>r</mi><mi>i</mi><mi>x</mi><mo>:</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mo lspace="0em" rspace="0em">∗</mo></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0.5</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0.5</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">matrix:
\left(\begin{array}{cc}
* &amp; 0.5 \\
0.5 &amp; 0 \\
\end{array}
\right)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6595em;"></span><span class="mord mathnormal">ma</span><span class="mord mathnormal">t</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">i</span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">(</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">∗</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0.5</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0.5</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">)</span></span></span></span></span></span></span>

<section class="carousel">

<figure>
<img class="dithered" src="/assets/images/dithering/gradient.png">
<figcaption>
Original image
</figcaption>
</figure>

<figure>
<img class="dithered" src="/assets/images/dithering/simple2d.png">
<figcaption>
Regular error diffusion dithering
</figcaption>
</figure>

<figure>
<img class="dithered" src="/assets/images/dithering/simple2d_serpentine.png">
<figcaption>
Serpentine error diffusion dithering
</figcaption>
</figure>

</section>

<p>In my experience, it makes sense to always do serpentine dithering for the best results.</p>

<h3 id="strength">Strength</h3>

<p>In the ordered dithering section I talked about how a <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi><mi>t</mi><mi>r</mi><mi>e</mi><mi>n</mi><mi>g</mi><mi>t</mi><mi>h</mi></mrow><annotation encoding="application/x-tex">strength</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span><span class="mord mathnormal">re</span><span class="mord mathnormal">n</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mord mathnormal">t</span><span class="mord mathnormal">h</span></span></span></span> variable could be used to control
how much dithering was applied. This can also be done for error diffusion, and it appears some
graphical suites like ImageMagick even have this built-in, as is described <a href="https://learn.adafruit.com/preparing-graphics-for-e-ink-displays?view=all" class="web-link">here</a>.</p>

<p>Here is the modified pseudocode for making use of a strength value:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">roundClamp</span><span class="p">(</span><span class="n">r</span> <span class="o">+</span> <span class="n">er</span> <span class="o">*</span> <span class="n">strength</span> <span class="o">*</span> <span class="n">matrix</span><span class="p">[</span><span class="n">y</span><span class="p">][</span><span class="n">x</span><span class="p">])</span>
<span class="n">roundClamp</span><span class="p">(</span><span class="n">g</span> <span class="o">+</span> <span class="n">eg</span> <span class="o">*</span> <span class="n">strength</span> <span class="o">*</span> <span class="n">matrix</span><span class="p">[</span><span class="n">y</span><span class="p">][</span><span class="n">x</span><span class="p">])</span>
<span class="n">roundClamp</span><span class="p">(</span><span class="n">b</span> <span class="o">+</span> <span class="n">eb</span> <span class="o">*</span> <span class="n">strength</span> <span class="o">*</span> <span class="n">matrix</span><span class="p">[</span><span class="n">y</span><span class="p">][</span><span class="n">x</span><span class="p">])</span>
</code></pre></div></div>

<p>And here’s an example of different strength values with Floyd-Steinberg dithering, with a palette of
red, green, yellow, and black.</p>

<section class="carousel">

<figure>
<img class="dithered" src="/assets/images/dithering/peppers.png">
<figcaption>
Original image
</figcaption>
</figure>

<figure>
<img class="dithered" src="/assets/images/dithering/floyd-steinberg_red-green-yellow-black.png">
<figcaption>
strength = 1.0
</figcaption>
</figure>

<figure>
<img class="dithered" src="/assets/images/dithering/floyd-steinberg_strength_08.png">
<figcaption>
strength = 0.8
</figcaption>
</figure>

<figure>
<img class="dithered" src="/assets/images/dithering/floyd-steinberg_strength_05.png">
<figcaption>
strength = 0.5
</figcaption>
</figure>

<figure>
<img class="dithered" src="/assets/images/dithering/floyd-steinberg_strength_02.png">
<figcaption>
strength = 0.2
</figcaption>
</figure>

</section>

<p>As you can see, the strength variable is pretty much inversely proportional with contrast. A value like <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0.8</mn></mrow><annotation encoding="application/x-tex">0.8</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">0.8</span></span></span></span>
might be good to reduce some noise in the image, but go too low and the colours will just be inaccurate. Sticking
to <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1.0</mn></mrow><annotation encoding="application/x-tex">1.0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1.0</span></span></span></span> is a reasonable default.</p>

<p>However this does not apply to Bayer dithering of color images. As Surma mentions, Bayer matrices are fundamentally
biased to making images brighter. For color images, the bias occurs in all three RGB channels, and can distort
image colors overall, making the final dithered image look inaccurate. Several sites recommend a value like <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0.64</mn></mrow><annotation encoding="application/x-tex">0.64</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">0.64</span></span></span></span>
(256/4), and in my experience it’s a good default for color image dithering, with Bayer as well as ordered dithering
in general.</p>

<h2 id="other-things">Other things</h2>

<p>This post is pretty long now, but I do have a few other things to share.</p>

<p>One is about clustered-dot dithering. It looks like this:</p>

<div class="sidebyside">
<img class="dithered" src="/assets/images/dithering/david.png">
<img class="dithered" src="/assets/images/dithering/david_ClusteredDotDiagonal8x8.png">
</div>

<p>Currently my library just does clustered-dot dithering with <a href="https://github.com/makeworld-the-better-one/dither/blob/v1.0.0/ordered_ditherers.go" class="web-link">some preprogrammed matrices</a>
I’ve found around the Internet and in a couple books. I was unable to find any proper instructions on how to generate
a clustered-dot dither matrix, so that they can be created on the fly, and in any size. The books
I read hinted that it was possible, but I couldn’t find anything. <strong>If you know how to do this, please contact me!</strong>
I will update my library and this post if I figure anything out.</p>

<p>The other thing is that I used Joel Yuliluoma’s per-cell algorithm for creating Bayer matrices, as written <a href="https://bisqwit.iki.fi/story/howto/dither/jy/#Appendix%202ThresholdMatrix" class="web-link">here</a>
(the second code block). Since I had to rewrite it in Go, it’s cleaner to read and understand than the original C.
If you’re interested in implementing this yourself, you might prefer reading it. My implementation is <a href="https://github.com/makeworld-the-better-one/dither/blob/v1.0.0/pixelmappers.go#L102" class="web-link">here</a>.
It’s been tested to make sure it works the exact same as Yuliluoma’s.</p>

<h2 id="thanks">Thanks</h2>

<p>Thanks for reading! I hope this is helpful to anyone else who is interested in dithering. And if you know Go,
check out <a href="https://github.com/makeworld-the-better-one/dither" class="web-link">my library</a>! Make something cool with it. 😄</p>

<p><br></p>

<p>Looks like this is getting some traction on Hacker News! Feel free to comment <a href="https://news.ycombinator.com/item?id=26120631" class="web-link">there</a>.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Last updated: Dec. 25, 2022]]></summary></entry></feed>