👓

Nounsドット絵の秘密

2023/06/07に公開

今回は、NounsDAOのドット絵の仕組みを解説していきます。

まずNounですが、2023/6/7時点で図柄の異なる736個が発行されています。

Nounの画像は、背景・ボディ・ヘッド・アクセサリ・グラスの5種類のパーツがランダムに組み合わさっていますが、この画像がどのように生成されるか、を説明していきます。

まず初めに画像形式ですが、SVGという形式で生成されています。
SVGは Scalable Vector Graphicsの略で、四角・円・線などの図形を組み合わせて生成する画像です。

SVGでは、四角を表すRect、円を表すcircle、線を表すline、自由度の高い線を描画できるpathなどの要素を組み合わせて一つの画像を表現しています。
Nounsは、この中でも四角を表すRectだけを使用して画像を組み合わせています。

Nounsの画像は、描画エリアを320x320の正方形、一つのRectサイズを10x10とし、縦32列横32列に画像ごとの配色のRectを当てはめています。

ファイルの中身はこんな感じになっています。

<svg width="320" height="320" viewBox="0 0 320 320" xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges">
<rect width="100%" height="100%" fill="#e1d7d5" />
<rect width="140" height="10" x="90" y="210" fill="#9f21a0" />
<rect width="140" height="10" x="90" y="220" fill="#9f21a0" />
<rect width="140" height="10" x="90" y="230" fill="#9f21a0" />
<rect width="140" height="10" x="90" y="240" fill="#9f21a0" />
<rect width="20" height="10" x="90" y="250" fill="#9f21a0" />
<rect width="110" height="10" x="120" y="250" fill="#9f21a0" />
<rect width="20" height="10" x="90" y="260" fill="#9f21a0" />
<rect width="110" height="10" x="120" y="260" fill="#9f21a0" />
<rect width="20" height="10" x="90" y="270" fill="#9f21a0" />
<rect width="110" height="10" x="120" y="270" fill="#9f21a0" />
(続く)

次に各パーツをどのように描画していくか説明します。
まずトークンIDとブロック番号からランダムな数字を生成します。スマートコントラクト内に背景・ボディ・ヘッド・アクセサリ・グラスの全部品が格納されており、生成したランダムな数字からパーツごとにどの番号の部品を使用するか決定しています。

Nounsの画像は、後ろ面から背景・ボディ・アクセサリ・ヘッド・グラスのレイヤー構成になっています。よって先ほど説明した描画エリアに、背景→ボディ→アクセサリ→ヘッド→グラスの順で描画していけば、それぞれのパーツ画像が組み合わさったひとつの画像が完成します。

さらにマニアックな領域に踏み込んでいきましょう。

<rect width="140" height="10" x="90" y="210" fill="#9f21a0" />

これは画像の中の横並びの単色を表すRectですが、この文字列をそのままブロックチェーンに格納するとSVGといえどもデータ量が多くなり、ガス代が高くなってしまいます。
このため、各パーツはRun-length encoding (RLE) という形式で圧縮してブロックチェーンに格納し、ブロックチェーンから取り出すときに上記のようなRect形式に復元しています。

RLEは同じものが順番に並ぶ場合に有効な圧縮形式です。例えば黒と白のグラスのパーツを描こうとすると、このような感じで黒と白が連続します。(適当なのはご愛嬌)

    黒黒黒黒黒黒黒白黒黒黒黒黒黒黒
    黒白白白白白黒白黒白白白白白黒
黒黒黒白白白白白黒黒黒白白白白白黒
黒  黒白白白白白黒白黒白白白白白黒
黒  黒黒黒黒黒黒黒白黒黒黒黒黒黒黒

一列目に着目すると、黒が7個、白が1個、黒が7個となっています。
これをRLE形式では"7黒1白7黒"と表現します。15文字から6文字に圧縮することができました。

実際の例で見てみましょう。
これはglasses-hip-roseというグラスです。

これをSVGで格納すると、3,351バイトになります。

<svg width="320" height="320" viewBox="0 0 320 320" xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges">
	<rect width="100%" height="100%" fill="none" />
	<rect width="60" height="10" x="100" y="110" fill="#ff638d" shape-rendering="crispEdges" />
	<rect width="60" height="10" x="170" y="110" fill="#ff638d" shape-rendering="crispEdges" />
	<rect width="10" height="10" x="100" y="120" fill="#ff638d" shape-rendering="crispEdges" />
	<rect width="20" height="10" x="110" y="120" fill="#ffffff" shape-rendering="crispEdges" />
	<rect width="20" height="10" x="130" y="120" fill="#000000" shape-rendering="crispEdges" />
	<rect width="10" height="10" x="150" y="120" fill="#ff638d" shape-rendering="crispEdges" />
	<rect width="10" height="10" x="170" y="120" fill="#ff638d" shape-rendering="crispEdges" />
	<rect width="20" height="10" x="180" y="120" fill="#ffffff" shape-rendering="crispEdges" />
	<rect width="20" height="10" x="200" y="120" fill="#000000" shape-rendering="crispEdges" />
	<rect width="10" height="10" x="220" y="120" fill="#ff638d" shape-rendering="crispEdges" />
	<rect width="40" height="10" x="70" y="130" fill="#ff638d" shape-rendering="crispEdges" />
	<rect width="20" height="10" x="110" y="130" fill="#ffffff" shape-rendering="crispEdges" />
	<rect width="20" height="10" x="130" y="130" fill="#000000" shape-rendering="crispEdges" />
	<rect width="30" height="10" x="150" y="130" fill="#ff638d" shape-rendering="crispEdges" />
	<rect width="20" height="10" x="180" y="130" fill="#ffffff" shape-rendering="crispEdges" />
	<rect width="20" height="10" x="200" y="130" fill="#000000" shape-rendering="crispEdges" />
	<rect width="10" height="10" x="220" y="130" fill="#ff638d" shape-rendering="crispEdges" />
	<rect width="40" height="10" x="70" y="140" fill="#ff638d" shape-rendering="crispEdges" />
	<rect width="20" height="10" x="110" y="140" fill="#ffffff" shape-rendering="crispEdges" />
	<rect width="20" height="10" x="130" y="140" fill="#000000" shape-rendering="crispEdges" />
	<rect width="30" height="10" x="150" y="140" fill="#ff638d" shape-rendering="crispEdges" />
	<rect width="20" height="10" x="180" y="140" fill="#ffffff" shape-rendering="crispEdges" />
	<rect width="20" height="10" x="200" y="140" fill="#000000" shape-rendering="crispEdges" />
	<rect width="10" height="10" x="220" y="140" fill="#ff638d" shape-rendering="crispEdges" />
	<rect width="10" height="10" x="70" y="150" fill="#ff638d" shape-rendering="crispEdges" />
	<rect width="10" height="10" x="100" y="150" fill="#ff638d" shape-rendering="crispEdges" />
	<rect width="20" height="10" x="110" y="150" fill="#ffffff" shape-rendering="crispEdges" />
	<rect width="20" height="10" x="130" y="150" fill="#000000" shape-rendering="crispEdges" />
	<rect width="10" height="10" x="150" y="150" fill="#ff638d" shape-rendering="crispEdges" />
	<rect width="10" height="10" x="170" y="150" fill="#ff638d" shape-rendering="crispEdges" />
	<rect width="20" height="10" x="180" y="150" fill="#ffffff" shape-rendering="crispEdges" />
	<rect width="20" height="10" x="200" y="150" fill="#000000" shape-rendering="crispEdges" />
	<rect width="10" height="10" x="220" y="150" fill="#ff638d" shape-rendering="crispEdges" />
	<rect width="60" height="10" x="100" y="160" fill="#ff638d" shape-rendering="crispEdges" />
	<rect width="60" height="10" x="170" y="160" fill="#ff638d" shape-rendering="crispEdges" />
</svg>

これをNounsのRLE形式で格納するとこうなります。

0x000b1710070300062001000620030001200201022301200100012002010223012004200201022303200201022301200420020102230320020102230120012002000120020102230120010001200201022301200300062001000620

184バイトで済んでしまいました。約5%まで圧縮することができています。

NounsDAOは、オークションで得た資金を投票で有効活用することとメガネをかけたキャラクターが特徴的ですが、このキャラクターを生み出す裏側には今回解説したような超絶テクニックが潜んでいるんですね。
では今回はこの辺で。


今回の記事はこちらを参考にしています。
https://nouns.center/dev/nouns-protocol

Discussion