前書き

画像処理ツール nao1215/truss を開発したので、紹介します。読みは、「トラス」です。

truss は、画像の変換(拡張子変更)、リサイズ、ぼかし、透かし、切り抜き等ができるツールであり、JPEG、PNG、WebP、AVIF、BMP、SVG に対応しています。クロスプラットフォーム対応(Linux, macOS, Windows)であり、CLI・サーバー・Wasm で動作します。

下図が示す通り、CLI・サーバー・Wasm はコアとなるライブラリを共有しており、サーバーはローカルストレージか、クラウドストレージ(AWS、Azure、Google Cloud)を利用できます。ただし、クラウドストレージはモックを使ったテストしかしておらず、実環境での動作はまだ未確認です。Wasmは、GitHub Pages でデモが動いています。

  flowchart TB
    CLI["CLI<br/>(truss convert)"] --> Core
    Server["HTTP Server<br/>(truss serve)"] --> Core
    WASM["WASM<br/>(browser)"] --> Core

    subgraph Core["Shared Rust core"]
        direction LR
        Sniff["Detect format"] --> Transform["Crop / resize / blur / sharpen / watermark"]
        Transform --> Encode["Encode output"]
    end

    Server --> Storage

    subgraph Storage["Storage backends"]
        FS["Local filesystem"]
        S3["S3"]
        GCS["GCS"]
        Azure["Azure Blob"]
    end

truss がサポートする画像変換・フォーマット

基本的な機能をふんわりと理解していただくために、入出力に関してサンプルを列挙します。

サポートするフォーマット変換

Input \ OutputJPEGPNGWebPAVIFBMPTIFFSVG
JPEGYesYesYesYesYesYes-
PNGYesYesYesYesYesYes-
WebPYesYesYesYesYesYes-
AVIFYesYesYesYesYesYes-
BMPYesYesYesYesYesYes-
TIFFYesYesYesYesYesYes-
SVGYesYesYesYesYesYesYes

切り抜き・回転・背景色指定

OriginalCropRotateBackground
originalcroppedrotatedbackground

フィッティング

Original (640 × 427)contain 300 × 300cover 300 × 300fill 300 × 300inside 300 × 300
originalcontaincoverfillinside

ぼかし・シャープネス・透かし

OriginalGaussian BlurSharpenWatermark
originalblurredsharpenedwatermarked

配置変更

--position top-left--position center--position bottom-right
top-leftcenterbottom-right

最適化

Quality 90 (95 KB)Original (80 KB)Quality 30 (27 KB)
q90originalq30

サーバーとしての特徴

下図が示すように、truss の前段に CDN を配置する構成を想定して実装してあります。

  flowchart LR
    Viewer -->|HTTPS request| CloudFront
    CloudFront -->|cache hit| Viewer
    CloudFront -->|cache miss| ALB["ALB / nginx / Envoy"]
    ALB --> truss
    truss -->|read source| Storage["Local storage<br/>or remote URL origin"]

CDN 無しで truss を前面に出すには、一部の機能が足りていません。具体的には、キャッシュ機構、TLS終端処理、API レートリミットが実装されていません。CDN のメリットは地理的分散だと認識しており、truss が地理的分散を実現する手段がないので CDN ありきで設計してあります。

ただし、「安価にインフラを構築したい(CDN すら導入したくない)」という要望に答えるために、CDN なし構成を前提とした機能を追加する可能性はあります(とはいえ、個人サービスで CDN コストが気になるケースはない筈)。


truss が提供する API の詳細は、OpenAPI(swagger)ドキュメントをご参照ください。以下に代表的なものを示します。

Public Endpoints (Signed URL)

EndpointDescription
GET /images/by-path署名付きURLで認証し、ストレージからパス指定で画像を取得・変換する
GET /images/by-url署名付きURLで認証し、リモートURLから画像を取得・変換する

Private Endpoints (Bearer Token)

EndpointDescription
POST /images:transformストレージまたはリモートURLの画像を変換する
POST /imagesマルチパートフォームで画像をアップロードし変換する

Infrastructure Endpoints

EndpointDescription
GET /health/liveLivenessプローブ(常に200を返す)
GET /health/readyReadinessプローブ(ドレイン中、ディスク満杯、メモリ制限超過時は503を返す)
GET /metricsPrometheusメトリクス(テキスト形式)

開発を思い立った理由

turss の開発を思い立った理由は、前職の人から連絡を受けたのがキッカケです。

※ ここに色々裏話を書きましたが、前職に関する非公開情報なので公開前に削除しました。前職から「作ってください」と言われたワケではありません。何らかの連想ゲームがありました。


名前の由来

truss の名前の由来は、建築で用いられるトラス構造です。

トラス構造は、三角形をひとつの単位として組合わせた構造のことで、東京タワーや鉄道の鉄橋で用いられています。「画像を多様な形に作り直せるイメージがトラス構造と似ている」と、こじつけな着想を得ました。

truss の構想は、前職時代からありました。前職の社名の由来はフラーレンであり、フラーレンは安定していながらも化学反応に富む性質を持ちます。前職の旧ロゴがトラス構造っぽかったので、そのロゴともリンクさせました。余談ですが、私の中では「ツールやライブラリの名前を真剣に考えた場合は、完成しない」というジンクスがあります。テキトーな名前の方が完成します。


Rust を採用した理由

画像配信・画像処理という要件を考えた時、Go はベストな選択ではなく、Rust が最適と判断しました(注:私は Go ユーザーであり、Rust は素人)。画像処理では、低レイテンシ・メモリ制御・SIMDが大事なポイントであり、GCが発生したりSIMDがほぼ使えないGoを積極的に採用する理由がありません。

Rust を採用するのであれば、Wasm 対応でデモを公開しやすいと考え、現在の CLI・サーバー・Wasm 構成にたどり着きました。


LLM の利用

設計と実装では Claude Code、レビューでは Codex および Code Rabbit を利用しました。

Claude Code の利用では、Anthropic 公式のベストプラクティスおよび 逆瀬川氏(@gyakuse)逆瀬川ちゃんのブログを参考に、環境を整えました。

基本的には、Anthropic 公式の anthropics/skills で公開されている skill-creator を用いて、スキルを増やしながら開発を行いました。例えば、調査・設計・実装・レビューの各工程で、異なるサブエージェントを立ち上げ、それぞれに批判的なレビューをさせながら開発を進めさせました。Pull Request を作れる状態になった後は、Rust、インフラ、QA、画像処理のエキスパートエージェントが総合的なレビューを行い、そのレビューをクリアしたら Codex、Code Rabbit にレビューをさせていました。

なにか問題があれば、GitHub Issueに記録を書き出してもらい、作業が途切れないようにほぼ自立して開発してもらいました。私の存在意義は……トークンを購入する ATM でしょうかね……


高速な実装を耐え抜く

最近は、LLM のおかげで実装速度が異常な速さです。

その弊害で、README や Design Doc、その他の各種ドキュメントの陳腐化が早いです。truss の例で言えば、OpenAPI(swagger)の定義ファイルは高頻度で実装と乖離しました。コードの挙動も、いつの間にか壊れがちです。対策としては、コード自体から何かを生成したり、テストコードで動作保証しなければなりません。

truss におけるガードレールの例として E2Eテストがあります。

  • CLI E2E テスト:shellspec/shellspec を利用
  • API E2E テスト:k1LoW/runn を利用
  • クラウドストレージ(AWS、Azure、Google Cloud)に関する動作をモックで検証

ここでの問題点は、Docker を利用したり、テスト数が増えたりするので、GitHub Actions の実行時間が10分を超え始めたことです。個人的には、5分以上かかる CI は開発体験が悪いです。明確な待ち時間が発生するので、遅いと感じます。解決策としては、ワークフローを見直す方法、GitHub-hosted runners を高性能にする方法があります。前者は限界があり、後者はマネーが必要です。私は前者で頑張ってます。

話を脱線させると、ドマイナー言語である Vala 言語のコアライブラリ nao1215/libvalacore を開発していた際、カバレッジ計測に40分かかって「この言語は駄目だ」と思いました。


信頼と実績はお金で買えない

truss には、実績がありません。

実績がない truss をサーバーとして利用する人は、まず居ないでしょう。しかし、開発者としては、せっかく作ったのだから、第三者に利用していただきたいです。そうなると、自分で実績を作るしかありません。今年中に、truss を使ったアプリをリリースします。

2026.03.14 追記

サーバー用途として使うより、Wasm として使って貰った方が筋が良いと結論づけました。単なる画像変換をしたい場合、Wasm で対応すればサーバーコストがかかりません。頭の中で何個かサービスを考えたのですが、「それ、Wasm でよくね」となりました。まずは、Wasm としての価値を高める方針で改良します。


最後に

LLM 時代は、「引き算」と「発想のセンス」が大事だなと感じています。

今回の truss は、完全に LLM のパワーで足し算しているだけのツールであり、誰でも作れる印象です。実際、truss は何も課題を解決していません。「既存の cshum/imagorimgproxy/imgproxy の代わりに、truss を使う理由は?」と問われれば、答えに窮するでしょう。現在は、ユーザーに刺さらない機能ばかり足している状況です。ユーザーの課題を意識してから、開発したいものです(私は、思いつくと直ぐに手が動いてしまうタイプ)。

また、LLM のおかげで開発が一瞬なので、要求やニーズの発想が大事な時代になりました。私が最近「発想がいいな」と感動したツールは、以下です。単純に便利だったり面白かったりで、普段使いできるツールなんですよね。