-- Leo's gemini proxy

-- Connecting to mntn.xyz:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini

Dithering: an idea whose time has finally come?


📅 Published 2021-10-09


It seems that dithering is undergoing a renaissance on Gemini. Not only does it introduce a pleasant retro aesthetic, but for images with smaller dimensions, dithering with a reduced palette can both save bandwidth and make content more accessible to old devices. (Believe it or not, an Amiga 500 may not handle a massive 24-bit color JPEG very well.) I've even seen it used outside of Gemini; for instance, Low Tech Magazine hosts a solar powered version of their website with dithered images.


dithering

Low Tech Magazine


For all of you Gemini users, here's an example of a dithered image using an 8 color palette. (Web users will see a normal, boring, undithered image. Sorry about that. You should really try Gemini.)


Dithered image


Thanks to Openclipart for the image. More woodblock prints are available (free for any use) on the same site.


Openclipart

More woodblock prints


Dither your images with didder


In this context, I recently stumbled across makeworld's excellent tool "didder" for dithering images. It comes with a huge array of dithering algorithms, and it lets you tweak the results to create artistically minimalist graphics. It's also written in go and compiles to a nice small binary. A truly fantastic tool.


didder


After finding didder, I decided to put together a script to quickly dither all of the images on the Gemini version of my site. Not only will it make my Gemini site more accessible for devices with less memory, but it should really help users who have restricted bandwidth. Now I'm a big fan of retrocomputing, but this last part matters to me much more than supporting ancient devices and hacked Linux toasters. Those of us who care about decentralization need to be focused on building simple, low power, low cost alternative networks, and many of the most promising options here have strict bandwidth constraints. But that's a post for another day!


Dither your whole capsule with gemdither


I've packaged up my little script for anyone to use and named it "gemdither." Below I'll explain a little about what it can do and how it was built.


gemdither


The requirements


Although it is a great tool, didder is designed to operate on either one image at a time or a single directory of images. For a site with many images, scattered across multiple directories, running this command manually could be tedious. If you're like me, you prefer to automate this process, and that's where this script comes in.


I had a small set of must-have requirements:


Leave the source images alone, dithering only the images for the Gemini site and not for the WWW version

Shrink any large images to a maximum width and/or height to reduce file size

Automatically rewrite local image links in Gemtext files to point to the newly dithered images

Integrate the script into my automated build process (to be run after gmnhg)

Use Nix for dependency management to make it easier to integrate into my build process


I also had a couple of bonus requirements:


Reduce dependencies as much as possible

Make the script fairly robust and publish it for others to use


The result


After a bit of work, I ended up with a Bash script (source) that fulfills all the requirements. It's not a huge script, but I put a decent amount of work into it, and it should hold up under most conditions.


source


Why Bash? Well, in retrospect, I'm not sure if that was a great choice. It's nice that it's just a script, meaning that people can download and run it without compiling, as long as the dependencies are installed. But Bash is *loose* for writing code that you plan to distribute, and `shellcheck` can only do so much for you.


I've never published a Bash script that's designed to be a robust CLI command, with proper handling of most corner cases. I think I'm like a lot of other folks in that I usually just write shell scripts as fill-in-the-gap sysadmin tools for myself or a small team. That is a different experience entirely! In such a situation, you can be fairly certain of the runtime conditions, and you often don't have to be as picky.


But you know what? In the end it wasn't so bad. After adding a bunch of error checking and escaping, I think I managed to eliminate all the casual footguns. I'm not saying you can't break it if you try to--please don't feed it untrusted input--but it should be solid for personal use, even if you're pure evil and you name a file `#! hello {;} .jpg .gif`.


Using Nix for "building" a shell script, with dependencies


Since my own build process uses Nix to manage dependencies, I also wanted gemdither to be installable using Nix. I created a simple default.nix file that builds didder (which is not yet packaged) and pulls in dependencies.


default.nix


One thing that was not clear from the Nix documentation was how to package a shell script. After some digging, I found a couple of examples and adapted those. Sadly, it isn't quite as simple as using a hypothetical "buildShellScript" function. The most flexible approach, usable for people without Nix, is to create a Makefile to handle installation. Then you can have Nix's `stdenv.mkDerivation` patch the script with correct dependency paths during postFixup. (If you are new to Nix, you may want to browse the documentation on stdenv build phases to learn more about phases.)


stdenv build phases


The `mkDerivation` portion of `default.nix` looks like this:


stdenv.mkDerivation rec {

  inherit pname version;
  nativeBuildInputs = [];
  buildInputs = [ bash getopt didder file ];
  makeFlags = [ "DESTDIR=$(out)" ];
  src = builtins.path { path = ./.; name = "gemdither"; };

  postFixup = let
    runtimePath = lib.makeBinPath buildInputs;
  in
  ''
    sed -i "2 i export PATH=${runtimePath}:\$PATH" $out/bin/gemdither
  '';

  meta = with lib; {
    description = "Dither and resize images for a Gemini site and update internal image links";
    homepage = "https://sr.ht/~mntn/gemdither";
    license = licenses.gpl3;
    platforms = platforms.linux;
  };

}

I'll walk you through the most important parts below.


`buildInputs`: this defines the packages that will be used during build, and which will be referenced later by the `gemdither` script.

`makeFlags`: this sets the flags that `make` will use. In this case, it sets `$DESTDIR` to the default install directory `$out` that Nix will use for installing the package.

`src`: this sets the source path to the current directory.

`postFixup`: this sets up a script to run after the "fixup" phase, which rewrites executables and shell scripts to reference necessary libraries. Unfortunately for us, fixup only rewrites the shebang portion of shell scripts, which is why we need this script.

`runtimePath = lib.makeBinPath buildInputs`: this defines a variable that contains the paths of our dependencies from `buildInputs`.

`sed -i "2 i export PATH=${runtimePath}:\$PATH" $out/bin/gemdither`: this writes a line to the `gemdither` script that brings our binaries into its `$PATH`. This is what allows the script to find its dependencies as installed by Nix.


When working on a project like this, you can check that everything builds properly by running `nix-build` and inspecting the output. You can also use `nix-shell` to enter a shell and observe the results of the individual build phases. Pretty useful!


Integrating gemdither into the site build process


Now that we have a script, I have to include it into the build process. Since gemdither has its own `default.nix`, it is easy to include this as a package in my website's own `default.nix`:


gemdither = import (
  builtins.fetchTarball {
    name = "gemdither-0.0.5";
    url = "https://git.sr.ht/~mntn/gemdither/archive/0.0.5.tar.gz";
    sha256 = sha256:0a6q2zrx660akci2zz87a9w2q55hrr1lv1dnyrjbc071y14xzfs1;
}

This fetches the tagged tarball from the Sourcehut repository and checks the SHA256 hash. The package will be built automatically. Not bad! To see the complete context, you can view the default.nix source for my site.


default.nix source


Once gemdither is in place as a dependency, I simply updated my build aliases for local testing and my `.build.yml` file for automatic builds on Sourcehut. Now whenever I commit a change, the process will run automatically, and I have nicely dithered images on my Gemini site. Living like it's 1995 again!


---


Comments? Email the author: mntn at mntn.xyz


🌎 View this page on the web

☚ Back to the home page

-- Response ended

-- Page fetched on Sun May 19 03:46:56 2024