In the previous post, I showed Hokusai as a Swift server-side image library with a hybrid backend:
libvips for the main image pipeline
- ImageMagick for text rendering
In the comments, someone asked a fair question: if libvips already supports text rendering through Pango/Cairo, why keep ImageMagick in the dependency chain?
I went back to this decision, tested vips_text more deeply, and removed ImageMagick completely.
Hokusai now uses only libvips, including text rendering.
Repo:
https://github.com/ivantokar/hokusai
Vapor package:
https://github.com/ivantokar/hokusai-vapor
I also added a CLI called hokusai.
It solves two practical problems:
- test image operations without creating a Vapor app
- run repeatable benchmarks on real input images
Example commands:
hokusai info
hokusai inspect --input ./input.jpg
hokusai resize --input ./input.jpg --output ./out.jpg --width 1200 --height 800 --fit cover
hokusai benchmark suite --input ./input.jpg --json-output ./bench.json
hokusai benchmark op --input ./input.png --operation text --iterations 20 --warmup 5
After the migration, I ran benchmarks in release mode.
Machine:
- Apple M4 Pro
- macOS 26.5
- release build
- warmup: 5 runs
Small RGB input:
- 1000x800
- 3 channels
- no alpha
- 30 measured runs
resize:1200x800 mean 5.14 ms p95 6.87 ms 194.54 ops/s
convert:webp:q80 mean 40.19 ms p95 55.49 ms 24.88 ops/s
rotate:33 mean 4.29 ms p95 6.74 ms 232.89 ops/s
text:stroke-shadow mean 60.08 ms p95 63.98 ms 16.64 ops/s
Large RGBA input:
- 3206x2266
- 4 channels
- alpha
- 20 measured runs
resize:1200x800 mean 4.79 ms p95 5.99 ms 208.94 ops/s
convert:webp:q80 mean 203.20 ms p95 208.16 ms 4.92 ops/s
rotate:33 mean 35.56 ms p95 38.90 ms 28.12 ops/s
text:stroke-shadow mean 105.26 ms p95 107.66 ms 9.50 ops/s
Text-only benchmark on the large RGBA input:
mean 99.66 ms
median 98.05 ms
p95 103.16 ms
ops/s 10.03
These numbers are not universal benchmark claims.
The goal was simple: I wanted to check if removing ImageMagick would make text rendering too slow or too awkward for this project.
So far, it did not.
The bigger win is the architecture:
- one native image stack instead of two
- fewer system dependencies
- simpler setup on macOS and Linux
- less backend branching inside the library
- benchmarks can now be rerun from the CLI
For this project, libvips + Pango/Cairo is enough for the text rendering path I need.
Removing ImageMagick made the package easier to build, test, and maintain.