Cryptocurrency Research Lab.

Cryptocurrency関連技術についての調査・研究

Thumbnail of all 10,000 BWC NFTs

日曜日は少し時間があったので、Baby Wealthy Clubの10,000 NFTのばらつきを視覚化してみようと思ってThumbnailをつくってみました。

画像処理は久しぶりすぎて、思わず VRAM (A800h, B000h, B800h, E000h) へ直接書き込もうとしちゃいましたが、色々と調べてみた結果、お気に入りのJavaScriptランタイム実行環境『Deno』にも、JavaScriptCanvasクラス相当のライブラリがあることが分かりましたので使ってみることにしました。

自動作成するツールもありますが、今回はToken Idと画像の紐づけが目的でしたので、自分で書いてみました。Thumbnailをつくりたい方に少しでも参考になればさいわいです。

(1) PFP画像ダウンロード

まずはThumbnailをつくるための画像データが必要です。NFT画像は一般に公開されていますから、シェルスクリプトなどで一括ダウンロードを行うと良いでしょう。ダウンロードするときは十分にインターバルをおき、くれぐれもサーバ側に負担をかけないようご注意ください。

1つのディレクトリに10,000ファイルも格納するのは、個人的に歯扱いにくいように思いますので、Idの100単位でディレクトリを分けることにします。

#!/bin/bash

IMAGE_HOME="$(dirname $0)/images"

for ((id=1;id<=10000;id++)) {
  dir="${IMAGE_HOME}/$(printf "%05d" $(((id/100)*100)))"
  if [ ! -d "${dir}" ]; then
    mkdir "${dir}"
  fi

  fileName="$(printf "%05d" $id).png"
  filePath="${dir}/${fileName}"
  if [ -f "${filePath}" ]; then
    continue
  fi

  url="https://★取得先URL★/${id}.png"

  echo "${fileName}"
  wget -O "${filePath}" "${url}"
  sleep 10
}

(2) PFP画像縮小

BWCのNFT画像サイズは1,200 × 1,200 ピクセル と大きいため、縮小することを考えます。

10,000個のNFTを100×100個に並べることを考えたとき、画像が識別できるくらいの大きさ、かつ、PNGとして1辺が大きすぎないことを考えて、画像サイズは64×64が妥当と考えました。

こちらも、単純繰り返しなので、シェルスクリプトからconvert (imagemagick)を呼び出してresizeしました。

#bin/bash

find images/ -name '*.png' | while read line
do
  destPath="${line/images/thumbnails}"
  parentDir="${destPath%/*}"
  if [ ! -d "${parentDir}" ]; then
    mkdir -p "${parentDir}"
  fi

  if [ ! -f "${destPath}" ]; then
    echo "${destPath}"
    convert -resize 64x64 "${line}" "${destPath}"
  fi
  
done

(3) ラベリング

もともとThumbnailを用意しようと考えたのは、Token Idと画像を紐づけることが目的でした。そのため、各サムネイル画像にToken Idを付与を行うことにしました。DenoのCanvasライブラリを利用することにしました。

ところで、BWCをはじめいくつかのGenerative NFTでは、MintとRevealのタイミングをそれぞれ別となっているため、なるべく公平性を保てるよう、内部の管理IDとToken Idの対応づけがReveal時点で決定されるような仕組みが考えられました。

https://twitter.com/BabyWealthyClub/status/1537389416175374337

(Token Id + 6205) % 10000  ※ただし 0→10,000

これを踏まえて、以下のような感じで、labeledディレクトリに、TokenId順に格納しておくようにしました。

label.ts
import { createCanvas, loadImage } from "https://deno.land/x/canvas/mod.ts";

for (let tokenId=1; tokenId<=10000; tokenId++) {
  let bwcId = (tokenId + 6205) % 10000;
  if (bwcId == 0) {
    bwcId = 10000;
  }

  const srcPath = `./thumbnails/${String(Math.floor(bwcId/100) * 100).padStart(5, '0')}/${String(bwcId).padStart(5, '0')}.png`;
  const dstDir  = `./labeled/${String(Math.floor(tokenId/100) * 100).padStart(5, '0')}`;
  const dstPath = `./labeled/${String(Math.floor(tokenId/100) * 100).padStart(5, '0')}/${String(tokenId).padStart(5, '0')}.png`;
  try {
    await Deno.mkdir(dstDir);
  } catch (e) {
    // dir already created
  }

  console.log(`${srcPath} --> ${dstPath}`);
  label(srcPath, dstPath, tokenId);
}

async function label(srcPath:string, dstPath:string, index:number) {
  const canvas = createCanvas(64, 64);
  const ctx = canvas.getContext("2d");

  const image = await loadImage(srcPath);
  ctx.drawImage(image, 0, 0);

  const x = 33;
  const y = 10;

  ctx.font = '10px verdana';
  ctx.fillStyle = 'rgba(0, 0, 0)';

  const text = String(index).padStart(5, ' ');

  ctx.fillText(text, x+1, y+1);
  ctx.fillText(text, x+1, y-1);
  ctx.fillText(text, x-1, y+1);
  ctx.fillText(text, x-1, y-1);

  ctx.fillStyle = 'rgba(200, 200, 255)';
  ctx.fillText(text, x, y);

  await Deno.writeFile(dstPath, canvas.toBuffer());
}
$ deno run --allow-read --allow-write --allow-net label.ts

(4) サムネイルの結合

imagemagickを使えば、横結合や縦結合などができるようですが、ディレクトリを分けたりしてファイル指定が大変になっているので、プログラムで書くことにしました。HTMLのCanvasライクに画像加工ができるので便利ですね。

concat.ts
import { createCanvas, loadImage } from "https://deno.land/x/canvas/mod.ts";

const canvas = createCanvas(64 *100, 64 *100);
const ctx = canvas.getContext("2d");

let tokenId = 1;
for (let y=0; y<100; y++) {
  for (let x=0; x<100; x++, tokenId++) {
    const imagePath = `./labeled/${String(Math.floor(tokenId/100) * 100).padStart(5, '0')}/${String(tokenId).padStart(5, '0')}.png`;
    const image = await loadImage(imagePath);
    ctx.drawImage(image, x * 64, y * 64);
  }
}

await Deno.writeFile("BWC100x100.png", canvas.toBuffer());

完成

Baby Wealthy ClubのThumbnailを用意しました。著作権について問題ないことを確認したうえで公開しておりますので、ご自由にお使いください。

きっとお気に入りの1枚が見つかるでしょう。