finish dither post

This commit is contained in:
David Eisinger
2024-02-06 23:47:01 -05:00
parent 51873c5a57
commit 1f2eb6c352

View File

@@ -1,16 +1,8 @@
---
title: "Encrypt and Dither Photos in Hugo"
date: 2024-02-05T09:47:45-05:00
date: 2024-02-06T23:00:00-05:00
draft: false
references:
- title: "Elliot Jay Stocks | 2023 in review"
url: https://elliotjaystocks.com/blog/2023-in-review
date: 2024-02-02T15:51:48Z
file: elliotjaystocks-com-fcit8u.txt
- title: "Encrypt and decrypt a file using SSH keys"
url: https://www.bjornjohansen.com/encrypt-file-using-ssh-key
date: 2024-02-05T14:50:24Z
file: www-bjornjohansen-com-hqud3x.txt
- title: "Ditherpunk — The article I wish I had about monochrome image dithering — surma.dev"
url: https://surma.dev/things/ditherpunk/
date: 2024-02-05T14:50:25Z
@@ -19,6 +11,14 @@ references:
url: https://solar.lowtechmagazine.com/about/the-solar-website/
date: 2024-02-05T14:50:28Z
file: solar-lowtechmagazine-com-vj7kk5.txt
- title: "Elliot Jay Stocks | 2023 in review"
url: https://elliotjaystocks.com/blog/2023-in-review
date: 2024-02-02T15:51:48Z
file: elliotjaystocks-com-fcit8u.txt
- title: "Encrypt and decrypt a file using SSH keys"
url: https://www.bjornjohansen.com/encrypt-file-using-ssh-key
date: 2024-02-05T14:50:24Z
file: www-bjornjohansen-com-hqud3x.txt
---
I encrypted all the photos on this site and wrote a tiny image server that decrypts and dithers the photos, then created a Hugo shortcode to display dithered images in posts. It keeps high-res photos of my kid off the web, and it looks cool.
@@ -30,39 +30,42 @@ I encrypted all the photos on this site and wrote a tiny image server that decry
When I was first setting up this site, I considered giving all the photos a monochrome [dithered][1] treatment à la [Low-tech Magazine][2]. Hugo has impressive [image manipulation functionality][3] but doesn't include dithering and [seems unlikely to add it][4]. I opted for full-color photos and went on with my life.
[1]: https://surma.dev/things/ditherpunk/
[2]: https://solar.lowtechmagazine.com/
[2]: https://solar.lowtechmagazine.com/about/the-solar-website/#dithered-images
[3]: https://gohugo.io/content-management/image-processing/
[4]: https://github.com/gohugoio/hugo/issues/8598
Most of what I post on this site are these monthly [dispatches][5] that start with what my family's been up to in the last month and include several high-resolution photos. Last week, I was reading Elliot Jay Stocks' "[2023 in review][6]," and he's adament about not posting photos of his kids. That inspired me to take another crack at getting dithered images working -- I take a lot of joy out of documenting our family life, and low-res, dithered images seemed like a good balance between giant full-color photos and not showing people in photos at all. And to add another wrinkle: this site is [open source][7], so I also needed to ensure that the source images wouldn't be available on GitHub.
Most of what I post on this site are these monthly [dispatches][5] that start with what my family's been up to in the last month and include several high-resolution photos. Last week, I was reading Elliot Jay Stocks' "[2023 in review][6]," and he's adament about not posting photos of his kids. That inspired me to take another crack at getting dithered images working -- I take a lot of joy out of documenting our family life, and low-res, dithered images strike a good balance between giant full-color photos and not showing people in photos at all. And to add another wrinkle: this site is [open source][7], so I also needed to ensure that the source images wouldn't be available on GitHub.
[5]: /tags/dispatch/
[6]: https://elliotjaystocks.com/blog/2023-in-review
[7]: https://github.com/dce/davideisinger.com
I tried treating the full-size images with ImageMagick on the command line and then letting Hugo resize the result, but I wasn't happy with the output -- there's still way too much data in a dithered 3000x2000px image, so when you scale it down, it just looks like a crappy black-and-white photo. Furthermore, the encoding wasn't properly optimizing for two-color images and so the files were larger than I wanted.
I tried treating the full-size images with ImageMagick on the command line and then letting Hugo resize the result, but I wasn't happy with the output -- there's still way too much data in a dithered full-sized image, so when you scale it down, it just looks like a crappy black-and-white photo. Furthermore, the encoding wasn't properly optimizing for two-color images and so the files were larger than I wanted.
I needed to find some way to scale the images to the appropriate size and _then_ apply the dither. Fortunately, Hugo has the ability to [fetch remote images][8], which got me thinking about a separate image processing service. After a late night of coding, I've got a solution I'm quite pleased with. Read on for more details.
I needed to find some way to scale the images to the appropriate size and _then_ apply the dither. Fortunately, Hugo has the ability to [fetch remote images][8], which got me thinking about a separate image processing service. After a late night of coding, I've got a solution I'm quite pleased with. Read on for more details, and if you want to follow along, you'll need to have Ruby installed (I recommend [asdf][9] if you're on a Unix-y OS) as well as ImageMagick and OpenSSL.
[8]: https://gohugo.io/content-management/image-processing/#remote-resource
[9]: https://asdf-vm.com/
### 1. Encrypt all images
We'll use OpenSSL to encrypt our images. I used [this guide][9] to get started. First, generate a secret key (the `-hex` option gives us something we can paste into a GitHub secret later):
We'll use OpenSSL to encrypt our images ([here's a guide][10]). First, we'll generate a secret key (the `-hex` option gives us something we can paste into a GitHub secret later):
[9]: https://github.com/dce/davideisinger.com/blob/7285c58add56e2ac6b5f7bf62914f0615ac23c9f/.github/workflows/deploy.yml
[10]: https://www.bjornjohansen.com/encrypt-file-using-ssh-key
```sh
openssl rand -hex -out secret.key 32
```
Don't forget to `gitignore` the key:
[Make a backup][11] of the key and then `gitignore` it:
[11]: https://bitwarden.com/
```sh
echo secret.key >> .gitignore
```
Then use it to encrypt all the images in the `content` folder (I use an interactive Ruby shell for this sort of thing because I'm not very good at shell scripting):
Then we'll use the key to encrypt all the images in the `content` folder. I use an interactive Ruby shell for this sort of thing because I'm not very good at shell scripting:
```ruby
Dir.glob("content/**/*.{jpg,jpeg,png}").each do |path|
@@ -79,27 +82,30 @@ end
### 2. Build a tiny image server
I wrote a [very simple image server][10] using [Sinatra][11] and [MiniMagick][12] that takes a path to an image and an optional geometry string and returns a dithered image. I won't paste the entire file here but it's really pretty short and simple.
I wrote a [very simple image server][12] using [Sinatra][13] and [MiniMagick][14] that takes a path to an image and an optional geometry string and returns a dithered image. I won't paste the entire file here but it's really pretty short and simple.
[10]: https://github.com/dce/davideisinger.com/blob/bf5238dd56b6dfe9ee2f1d629d017b2075750663/bin/dither/dither.rb
[11]: https://sinatrarb.com/
[12]: https://github.com/minimagick/minimagick
[12]: https://github.com/dce/davideisinger.com/blob/bf5238dd56b6dfe9ee2f1d629d017b2075750663/bin/dither/dither.rb
[13]: https://sinatrarb.com/
[14]: https://github.com/minimagick/minimagick
If you want to run it yourself, copy down everything in the [`bin/dither`][15] folder and then run the following:
[15]: https://github.com/dce/davideisinger.com/tree/bf5238dd56b6dfe9ee2f1d629d017b2075750663/bin/dither
Run it like this:
```sh
cd bin/dither && \
bundle install && \
ROOT=[SITE_ROOT]/content \
KEY=[SITE_ROOT]/secret.key \
> cd bin/dither
> bundle install
> ROOT=../../content \
KEY=../../secret.key \
bundle exec ruby dither.rb
````
```
Then you should be able to visit <http://localhost:4567/path/to/file.jpg?geo=400x300> in your browser (assuming you have an encrypted image at `content/path/to/file.jpg.enc`) to see it working.
Then, assuming you have an encrypted image at `content/path/to/file.jpg.enc`, you should be able to visit [localhost:4567/path/to/file.jpg?geo=400x300](http://localhost:4567/path/to/file.jpg?geo=400x300) in your browser to see it working.
### 3. Create a Hugo shortcode to fetch dithered images
We need to tell Hugo where to find our dither server. Give Hugo access to the `DITHER_SERVER` environment variable in `config.toml`:
We need to tell Hugo where to find our image server, which we'll supply with an environment variable. First, we'll give Hugo access to `DITHER_SERVER` in `config.toml`:
```toml
[security]
@@ -113,7 +119,7 @@ Then start Hugo like this:
DITHER_SERVER=http://localhost:4567 hugo server
```
Now we'll create the shortcode ([`layouts/shortcodes/dither.html`][13]):
Now we'll create the shortcode ([`layouts/shortcodes/dither.html`][16]):
```html
{{ $file := printf "%s%s" .Page.File.Dir (.Get 0) }}
@@ -141,7 +147,7 @@ Adjust for your needs, but the gist is:
2. Use `resources.GetRemote` to fetch the image
3. Display as appropriate
[13]: https://github.com/dce/davideisinger.com/blob/2cda4b8f4e98bb9df84747da283d13075aac4d41/themes/v2/layouts/shortcodes/dither.html
[16]: https://github.com/dce/davideisinger.com/blob/2cda4b8f4e98bb9df84747da283d13075aac4d41/themes/v2/layouts/shortcodes/dither.html
Use it like this:
@@ -151,7 +157,7 @@ Use it like this:
### 4. Delete the unencrypted images from the repository
Now that everything's working, let's remove all the uncrypted images from the repository. It's not enough to just `git rm` them, since they'd still be present in the git history, so we'll use [`git filter-repo`][14] to rewrite the history as if they never existed.
Now that everything's working, let's remove all the uncrypted images from the repository. It's not enough to just `git rm` them, since they'd still be present in the history, so we'll use [`git filter-repo`][17] to rewrite the history as if they never existed.
```ruby
Dir.glob("content/**/*.{jpg,jpeg,png}") do |path|
@@ -159,11 +165,11 @@ Dir.glob("content/**/*.{jpg,jpeg,png}") do |path|
end
```
[14]: https://github.com/newren/git-filter-repo
[17]: https://github.com/newren/git-filter-repo
### 5. Tweak site styles
The resulting images will be entirely black and white. If your site, like mine, doesn't use a perfectly white background, you can improve the display of the dithered images by setting `mix-blend-mode` to `multiply`:
The resulting images will be entirely black and white, and this site doesn't use a pure white background color. We can improve the display of the dithered images with some CSS that sets `mix-blend-mode` to `multiply`:
```css
img {
@@ -171,7 +177,7 @@ img {
}
```
The blacks will still show as black, but the whites will now be the background color of your site.
The blacks will still show as black, but the whites will now be the background color of the site.
### 6. Update the deploy workflow
@@ -182,12 +188,14 @@ This site uses GitHub Actions to deploy on pushes to the `main` branch, and we n
* Install Ruby and the required Gem dependencies
* Write the secret key to a file
* Start the dither server as a background task (i.e. with `&`)
* Add the `DITHER_SERVER` environment variable so that Hugo knows where to find it
* Add the `DITHER_SERVER` environment variable to the build step so that Hugo knows where to find it
[Here's the deploy workflow for this site][15] for reference.
[Here's the deploy workflow for this site][18] for reference.
[15]: https://github.com/dce/davideisinger.com/blob/7285c58add56e2ac6b5f7bf62914f0615ac23c9f/.github/workflows/deploy.yml
[18]: https://github.com/dce/davideisinger.com/blob/7285c58add56e2ac6b5f7bf62914f0615ac23c9f/.github/workflows/deploy.yml
***
I'm 41 years old, and this stuff still gives me a buzz like it did when I was 14.
This was super fun to build, and I'm really happy with [the result][19]. It makes the local authoring and deploy processes a bit more complicated since we have to run the separate image server, but I think the result is worth it. Hope you found this interesting, and please [reach out](mailto:hello@davideisinger.com) if you have any thoughts or questions.
[19]: /journal/dispatch-12-february-2024/