BLOG

Tech Teardown 038|nibnib:6クロックサイクルのために設計された4bpp画像フォーマット

Yongliang Talks AI
AI
广告 · Advertisement

何事:極簡なデコード、極繁雑なエンコードの画像フォーマット

2026年10月30日、Raph Levien(GitHubアカウント:raphlinus)は新しい画像フォーマットとそれに対応するRustの参照実装「nibnib」を公開しました。リポジトリの簡潔な説明は「Image compression at 4bpp with extreme decompression speed」となっており、4bppでの画像圧縮と極めて速い解圧速度を意味します。リポジトリが公開されて約21分後、彼はその投稿をHacker Newsに投稿し、88ポイントを獲得しました。2026年11月初旬時点で、リポジトリには58の星、2つのフォーク、0件のopen issue、何のリリースもなく、全歴史で10件のコミットがあり、Apache-2.0またはMITの二重ライセンスを採用しています。

nibnibの目標は圧縮率や画質ではなく、一つのことです:デコードコストを極限まで下げること。画像をピクセルあたり4ビットに圧縮し、デコード時には1バイトから2ピクセルを一度に復元します。コア内のループは単純な乗算と加算のみです。その代償として、エンコード側は極端に非対称になります。READMEには「現在のエンコーダはデコーダよりも数桁遅い」と明記されています。

作者についてもう少し詳しく説明する必要があります。このプロジェクトには彼の数十年にわたる技術が反映されています。彼の個人ページによると、Levienは2020年12月から2025年10月までGoogle Fontsチームで働き、主にGPUフォントのレンダリングと関連技術の研究を行っていました。さらに早くからFuchsiaチームでテキスト処理とRustを担当しており、現在もRustコミュニティで活躍し、Xilemシリーズのプロジェクトに参加しています。彼はGhostscriptのメンテナーでもあり、博士論文はインタラクティブなカーブの設計に関するもので、Inconsolataフォントも彼の手によるものです。nibnibと直接関係するもう一つの詳細があります:彼はインクジェットプリンター向けの高品質な半色調アルゴリズム「Even Toned Screening」を開発しました。半色調とエラースプレッディングは同じ技術の一部であり、nibnibのエンコーダはFloyd-Steinbergエラースプレッディングに基づいています。これは異分野への転身ではなく、経験豊富な人が自分の得意分野に戻ることです。

なぜ今:マイクロコントロールとピクセルあたり6つのクロック周期の予算

READMEの冒頭で起源が説明されています:nibnibは実験から生まれました。DVIビデオ出力がRP2350マイクロコントロール上で動作する際、画質と性能の限界は何かを知りたいという目的でした。

RP2350は低コストのマイクロコントロールであり、DVIビデオ信号を直接出力できるため、各ピクセルに割り当てられる計算リソースは非常に少ないです。この種のデバイスでは通常、低ビット深度の画像とカラーテーブルの検索が行われます。Levienは明確な予算を設定しました:ピクセルあたり約6つのクロック周期。6つの周期で何ができるか?ピクセルを取得し、カラーテーブルを検索し、行バッファにデータを書き込むだけです。余裕はありません。

この制約は、ほぼすべての主流のフォーマットを排除してしまいます。JPEGを解凍するにはHuffmanデコード、逆量子化、IDCT、色度上采样が必要ですが、IDCTだけでも1ピクセルあたり6周期の予算を大幅に超えてしまいます。PNGで使われるDEFLATEはビットストリーム上の並列アルゴリズムであり、同様に収容できません。6周期の予算の下では、デコードは一つの形態しかありません:分岐がない、エントロピーがない、テーブルを検索し加算する単純なコードです。

nibnibはこの問題の答えです。RP2350上でのリアルタイムデコード実装は、Levienの別のリポジトリvic-2350のimage demoにあり、フォーマットとターゲットハードウェアは一緒に作られています。コミット履歴から見ると、このプロジェクトは2025年12月5日から14日にかけて主に作成され、10日間でInitial version、Gaussian palette、Weight RGB error、Floyd-Steinberg、Semi-structured palette、Tune error balanceが連続してコミットされました。その後約10ヶ月間中断され、2026年10月22日に行の整列変更が加えられ、10月30日にREADMEが完成し正式に公開されました。典型的な「10日間の実験、1年後のリリース」です。

HNのコメント欄には、このような非対称設計の適用範囲をより広く説明するユーザーがいます:ブロードキャスト型のコンテンツ配布では、中央ソースが一度書き込み、多くの端末が複数回読み取ります。書き込み側は遅くてもよいですが、読み取り側は速くなければなりません。このようなシナリオは、この設計の利点を自然に享受できます。これはマイクロコントローラ専用の技術ではなく、複雑さをどちらに配置するかという一般的な選択です。

実施方法:delta、ピクセル対応と一行乗算加算

nibnibの核心は3つの設計決定であり、それぞれが6周期の予算に対応しています。

第一に、パレットにはdeltaを保存し、絶対色を保存しない。従来のインデックス色画像(GIF、PNGのパレットモード)では、パレットは絶対色を保存し、ピクセルは256色のうちの一つしか取れません。滑らかなグラデーションは色の数を硬直的に増やす必要があります。nibnibでは、パレットは前のピクセルとの差分を保存します:左のピクセルを解凍した後、右のピクセルは「左のピクセル + delta」となります。このように少量の小さな差分で広い柔らかい領域を埋めることができます。READMEによると、これにより通常のパレット画像よりも画質が向上し、柔らかい領域が最も恩恵を受けます。

第二に、1バイトで2ピクセルを解凍し、4bppを得る。デコードはピクセル対応で進められます:前の16ビットのピクセルを32ビットレジスタの上下両半にコピーし、入力ストリームから1バイトを読み取り、パレットテーブルから32ビットのdeltaを取得し、戻って加算します。結果の下16ビットは左のピクセル、上16ビットは右のピクセルで、どちらもRGB565形式です。内部ループの核心は一行です(READMEのRustコード):

pix = ((pix >> 16) * 0x10001).wrapping_add(pal[inp as usize]);

その中で、(pix >> 16) * 0x10001は「ピクセルコピー」を実行し、16ビットの値を32ビットの上下両端に配置します。wrapping_addはオーバーフローを恐れないラップ加算です。各ピクセルのペアには、1回のバイト読み込み、1回のテーブル検索、1回のシフト、1回の乗算、1回の加算、2回の保存が含まれ、条件分岐はありません。デコードの確実性が高く、どの実装でも同じバイトストリームに対して逐位で同じ出力が得られます(命令数は私のソースコードの解釈によるもので、公式統計ではありません)。

第三に、ファイルフォーマットは非常にシンプルで、2バイト幅、2バイト高さ、256個の32ビットカラーピクセル項目(合計1028バイトのヘッダー)から始まり、その後データが行ごとに処理されます。各行は2つのRGB565シードピクセルで4バイト開始し、(width - 2) / 2個のインデックスバイトを続け、行間を4バイトの倍数に調整します。バージョン番号も、チェックサムも、メタデータもありません。READMEには明確に、「このフォーマットはアーカイブ用ではなく、デコーダーと密接に結びついている可能性が高い」と記されています。

エンコーディングの側面は別世界です。READMEには明確に「nibnibはデコーディングプロセスによって定義される」と述べられています——デコーディングは確実でシンプルですが、エンコーディングの本質は検索であり、可能なすべてのエンコーディング結果の中から最も元の画像に似たものを探します。検索には2つの次元があります。

一つはカラーピクセルの選択です。現在のバージョンのカラーピクセルは固定構造で、133個のcube項目と123個のランダム項目があります。cube部分は3つのdeltaスケール(1、2、4)で各3×5×3の幾何学的組み合わせを取り、0付近やフルスケールに十分な範囲が得られ、オーバーフローが起こらないようにします。READMEには、cube部分を削除すると悪いアーチファクトが生じることが明確に述べられており、リポジトリには純粋なランダムカラーピクセルとcube+ランダムのエンコーディングの比較画像が添付されています。ランダム部分は加重ガウスサンプリングを使用し、スケールは対数均一分布に設定されます。誤差の尺度は0.3r² + 0.7g² + 0.1b²で、グリーン色の重みは赤と青の約2倍以上で、人間の目が緑色に敏感であることに対応しています(これらの定数はencode.rsのソースコードから取得されています)。READMEはまた、このランダムプロセスが合理的な値に基づいてad hocで調整されたものであり、カラーピクセルは現在入力画像に全く適応していないと率直に認めており、これが最も明確な改善方向であると著者自身が指摘しています。

二はcodewordを選択し、Floyd-Steinberg誤差拡散を使用する。各ピクセルペアに対して、エンコーダは256のカラーピックテーストをすべて試し、最も小さいデコード誤差のものを選択する。その後、誤差をFloyd-Steinbergの古典的な重み(7/16、1/16、5/16、3/16)で右隣りと次の行の3つの位置に拡散する。後続ピクセルのフィッティング対象は元の値ではなく、元の値に累積誤差を加えたものである。古典的な誤差拡散との違いは、古典的方法が一つずつピクセルを処理するのに対し、nibnibは一度に2つを生成するため、2つのピクセルの合計誤差が最小となるcodewordを選択し、2つのピクセルの誤差の和の重みが差よりも高いこと、そして人間の目が低空間周波数に敏感であるという事実をモデル化している点にある。HN上のユーザー(zahlman)は、これが「圧縮」の定義を歪めていると疑問を呈し、単なるdeltaフィルタとカラーピック量子化に過ぎないと述べたが、同時にその効果が良いことにも驚いている。なぜなら、直感的にdeltaフィルタは累積誤差を蓄積するからである。誤差が制御不能になる理由は、フィッティング対象にある:エンコーダは各ステップで累積誤差を目標に取り入れ、ドリフトが継続的に吸収される(この半句は私のソースコードからの推測である)。

ハッキングセービングには既知のコストがある。READMEには、現在のアパーチャーが鋭い境界の右側に尾を引くことが記載されている。著者は改善の方向を提示している:エンコーディング順序を右から左へ、あるいは蛇行(各行が交互に方向を変える)に変更する。なぜなら、加法ステップは実際には可逆だからである。または、より深い検索を行う。このピクセルの2つの選択肢の誤差が相対的に大きい場合、そのうちの一方が次のピクセルをより簡単にする可能性がある。これらの改善によりエンコーダは遅くなるが、デコード速度は変わらない。READMEの言葉をそのまま引用すると、「すべての改善は既存のデコーダで実行可能である」とのことだ。

## 実測結果とデータ:倉庫から数字を提供していないので、自分で1回実行した

nibnibの倉庫にはバッテンネルがなく、これはHNのコメントセクションで直接指摘された。ユーザーのaugment_meは、極端なデコード速度を強調しながらJPEGと比較する数字を提供しないのは不適切だと考えている。別のユーザーDoctorOetkerは、JPEGをリアルタイムでデコードできないプラットフォームをターゲットにしている点が意義があると応答した。

私は自分のマシン(Apple silicon Mac、Rust 1.96、release構成)で参照実装をコンパイルし、倉庫に付属の例図flowers7.png(1000×750、RGB8bit)を実行した。以下の数字はすべて私のマシンでの測定結果であり、公式の統計ではない:

- エンコード出力は379,028バイトで、フォーマット公式1028 + 750 × 504と厳密に一致しています。nibnibは固定長エンコーディングで、エントロピーエンコーディングは含まれておらず、出力サイズは画像のサイズによってのみ決まります。
- 有効ビット深度は4.043bppです(パレット、各行のシードピクセル、行の整列によるオーバーヘッドが名目上の4bppよりわずかに高い)。RGB565の裸データに対して約3.96倍、RGB888に対して約5.94倍のコンバート率です。リポジトリにあるPNGは792,470バイトですが、PNGは無損失ファイルなので、両者の圧縮率は比較できません。
- 画質:デコード出力のPSNRは元の画像と比べて約36.5dB低下しています。主観的に見ると、滑らかな領域のジッターノイズが最も目立ち、鋭いエッジの右側に拖尾があり、これはREADMEの説明と一致しています。
- 時間:コマンドラインでのエンコーディングには約0.5秒のユーザートラッキング時間がかかり、PNGのデコードと出力には約0.12秒のワールドタイムがかかります。これらの数字では、PNGのI/OとCLIのオーバーヘッドが大きな割合を占めており、デスクトップCPUもその目的プラットフォームではないため、単なる量級の参考に過ぎません。

2つの構造的な観察点は数字よりも有用です。1つ目は、固定長と行の整列により、完全なファイルが到着するまで待たずにエンコード・デコードが可能であり、これはvic-2350の「on-the-flyデコーダ」の位置づけと一致しています。2つ目は、エンコーディングの全コストをオフラインで支払うことができる点です:デスクトップマシンで一度エンコードし、端末デバイスではデコードにのみコストがかかります。raphlinus自身はHNでコメントに返答しており(相手はRP2350に最適化されたコードは現代のデスクトップコアにはほとんど適していないと指摘していました)、彼の回答は「Absolutely. This is optimized for highly resource constrained devices, not modern machines.」でした。高度にリソース制限のあるデバイス向けに最適化されており、現代のマシンには最適化されていないということです。

## 横断的比較:JPEG、Amiga HAM、BCn

JPEGとの比較により、nibnibの設計哲学を明確に理解できます。READMEには肯定的な比較があります:JPEGのDCT変換は数学的に可逆で、量子化も容易であり、ベースライン下でのエンコードとデコードの速度はほぼ同等です。検索はパラメータの微調整や画質の向上のためだけに使用されます。nibnibは完全に逆で、デコードは非常にシンプルであり、エンコードは数桁のスケールで検索問題です。実際には、これらは同じ問題の2つの答えであり、複雑さをどちら側に置くかということです。JPEGは複雑さを両端に均等に配置し、汎用性とエコシステムを得ています。一方、nibnibは複雑さをすべてエンコーディング側に押し付け、端末でのデコードコストを最小限に抑えています。代償として、同じビット深度で画質の上限がJPEGより低くなっています:256色のダイアラムパレットと565色の色深さしか持たず、周波数領域の変換は使用できません(これは私の判断であり、フォーマット仕様に基づいており、実測の比較ではありません)。

HNのコメント欄でその祖先が明らかになった:2人のユーザー(wiml、vardump)が、nibnibがAmigaに似たHAM(Hold-And-Modify)表示モードを持っていることを指摘した。コメント欄に記載されたパラメータによると、HAMはOCSチップセット上で6ビットの入力で12ビットの4096色を表示し、後のAGAチップセットでは8ビットで262144色を表示していた。考え方は「前のピクセルの情報を持ち続け、小さな変更のみを伝える」というもので、横方向のストライプ状のアーチファクトも似ている。違いは、HAMが表示モードであり色数の制限を解決しているのに対し、nibnibは保存フォーマットであり、デコーディング速度の制限を解決している点だ。また、deltaはRGBの3チャンネルに同時に作用し、1回に2ピクセルを処理する。1985年の家庭用機器での技術が、40年後にマイクロコントローラーのDVI出力で再びプロジェクトオプションとして使われるというのは、非常に興味深い。

もう一つの注目すべき点はGPUテクスチャ圧縮BCnシリーズだ。augment_meがコメント欄で言及したように、BCxは本質的に同じ種類のcodebook/paletteを実行し、2つの端点間でのインテルサーを許容し、より多くの色数と効率的な処理を実現している。nibnibはインテルサーを行わず、256個の32ビットdeltaが全てであり、その代わりにデコーディング内ループでインテルサーのロジックを必要としない。画質のビットあたりがピクセルの周期に譲り、選択は明確だ。

最後に、「フォーマットはデコーディングプロセスによって定義される」という哲学を独立して取り上げる。これはnibnibの発明ではなく、JPEG、PNGの規格も同様にデコーディング側を基準とし、エンコーディング側に自由を与えている。しかしnibnibはそれを極端なものにした:デコーダが十分にシンプルなため、エンコーダの改善の余地が大きく、全てが後向きに互換性があるからだ。READMEで想定されているアダプティブカラーマップ、より深い検索、蛇行スキャンは、既存のデコーダを1バイトも変更することなく実現できる。まだエコシステムがない実験的なフォーマットに対して、デコーディング側を固定することが正しいプロジェクトの順序である。

## 使い方と冷静な面

使い方は非常にシンプルだ。リポジトリは標準のRustプロジェクトで、ソースコードは3つのファイル、合計351行(main.rs、decode.rs、encode.rs)のみで、cargo build --releaseを実行するとコマンドラインツールが得られる:

```bash
nibnib input.png output.nib -c    # 圧縮
nibnib output.nib decoded.png -d  # デコーディングしてPNGに変換

入力はPNGのみ対応する。-cまたは-dを付けない場合は、一度エンコード後にデコードされるため、画質を直接比較するのに便利だ。RP2350 DVI出力を行う場合は、リアルタイムデコーダをvic-2350リポジトリのimage demoから取得する。

冷静な面は使い方よりも重要であり、私の判断を一つずつ説明する:

第一に、成熟度が実験品である。10のコミット、単独プロジェクト、リリースなし、crate公開なし、C言語実装なし、どんな画像ライブラリもサポートしていない。これを学習サンプルや自作の組み込みプロジェクトの素材として使うのは適切だが、現段階ではどんな生産環境にも導入するべきではない。それはPNG、JPEGのエコシステムの蓄積とは比べ物にならない。

第二、適用範囲が狭く、著者自身が明確に述べています。nibnibは、クロック周期で計算されるエンドユーザーのデコーディング予算と、素材がオフラインで事前処理可能なシナリオを対象としています。組み込みデバイスのUIリソース、レトロゲームのテクスチャ、マイクロコントロールビデオ出力の画像素材がこのカテゴリーに属します。写真アーカイブ、ウェブページ配布、印刷用素材は含まれません。READMEの原文によると、このフォーマットはアーカイブ目的で設計されていません。

第三、画質の上限が高くなく、エンコーダも自身の上限に到達していません。4bppに固定された非自動適応型カラーパレットを使用することで、複雑な色付き画像には必ずジッターノイズやエッジの引き延びが生じます。著者が挙げた2つの主要な改善方向(自動適応型カラーパレット、より深い検索)はまだ実施されていません。READMEの現状評価も正直です:「What’s presented is a basic, working image compression technique.」つまり、基本的で機能する画像圧縮技術です。

第四、熱度の数字を過大評価しないでください。58星、88ポイントは「興味深いアイデア」レベルの熱度であり、「新しい標準」レベルの熱度ではありません。それが集める注目は、クリーンな設計思想と著者のグラフィック分野での経験から来ており、明日それが実際に使われるかどうかとは別の問題です。

私の結論は一言で言えば:nibnibの価値は、追加された画像フォーマットがあることではなく、デコーディングコストを優先するフォーマット設計がどれだけクリーンであるかを示していることにあります。まずデコーディング側を固定し、すべての複雑さをエンコーディング側に押し付け、フォーマットをデコーディングプロセスによって定義します。組み込みグラフィック、レトロハードウェア、ビデオ出力玩具を作るエンジニアにとって、この351行のRustコードは一行ずつ読む価値があります。他の読者にとっては、この設計哲学を覚えておけば十分です。

参照資料

署名:永亮

广告 · Advertisement

よくある質問

nibnibの開発者は誰ですか?

nibnibはRaph Levienが開発しました。

nibnibのエンコードとデコードの違いは何ですか?

nibnibのエンコードは非対称的で、デコードは高速です。エンコードはデコーダよりも数桁遅いとされています。

nibnibがどのような用途で使用される予定ですか?

nibnibはRP2350マイクロコントロール上でのリアルタイムデコードに使用される予定です。