Canvas依存処理をテストする

 9月15日の日報です。今日もなんかnode弄って終わりましたね…

Canvas依存処理をテストする

「クライアント側で画像をリサイズして、特定サイズのサムネイルを作りたい」という思いがあるとして、コードはこんな感じになると思います1

 なお「それはサーバー側でやれよ」みたいな話はなかったものとします。

export interface IImageSize {
  width: number;
  height: number;
}

export const resizeImage = (
  base64: string,
  size: IImageSize,
  options = {
    createImage: Image,
    createCanvas: () => document.createElement("canvas")
  }
): Promise<string> => {
  // return result as promise
  return new Promise<string>((resolve, reject) => {
    // create canvas object
    const canvas = options.createCanvas() as HTMLCanvasElement;

    // create image object
    const image = new options.createImage();

    // set event to resize
    image.onload = function() {
      // create required objects
      const context = canvas.getContext("2d");
      if (!context) {
        return reject(
          new Error("resizeImage failed: canvas context is blank")
        );
      }

      // set canvas size
      canvas.height = size.height;
      canvas.width = size.width;

      // resize
      context.drawImage(
        image,
        0,
        0,
        image.width,
        image.height,
        0,
        0,
        canvas.width,
        canvas.height
      );

      resolve(canvas.toDataURL());
    };

    image.onerror = function(error: ErrorEvent) {
      reject(error);
    };

    // set image data
    image.src = base64;
  });
};

gitlab

 素晴らしい、Canvas Image ごり押しで無理やり変換できましたね! コピペ元はこちらで、最初こんな感じでしたがちょいちょい修正して終わり。

 ただどう見てもブラウザー実装依存なので、node.jsでテストできなさそうな感じですが、引数 options で色々誤魔化しているので大丈夫です(説明放棄)

import { IImageSize, resizeImage } from "~/store/uploader";
import fs from "fs";
import NodeCanvas from "canvas";

const getSampleBuffer = (): Buffer => {
  const path = `${__dirname}/test-neko.png`;
  return fs.readFileSync(path);
};

describe("resizeImage", () => {
  let base64: string;
  let size: IImageSize;

  beforeEach(() => {
    // get base64 string from sample image file
    base64 = `data:image/png;base64,${getSampleBuffer().toString("base64")}`;

    // set image size
    // original: 70 x 125
    size = { height: 56, width: 100 };
  });

  it("returns base64", async () => {
    const result = await resizeImage(base64, size, {
      createImage: NodeCanvas.Image,
      createCanvas: () => new NodeCanvas()
    });
    expect(result).toMatch(/data:image\/png;.+/);
  });

  it("returns resized data", async () => {
    const result = await resizeImage(base64, size, {
      createImage: NodeCanvas.Image,
      createCanvas: () => new NodeCanvas()
    });
    console.log(result)
    expect(result.length).toBeLessThan(base64.length * 0.85);
  });
});

gitlab

 node-canvas と依存ライブラリーのインストールが必要になるので、実はこのコードをコピペしただけでは動かないのですが2公式ドキュメントを見れば多分なんとかなります。

テストの中身は別問題

 これで一応テストは書けるんですが、実際にどういうテストを書くかというと… 正直すこしも思いつかなかったので「文字列としてちょっと短くなっているか」みたいな雑極まりないテスト1件で済ませています。

 base64文字列ではなく image を返して image.width image.height 検証するくらいなら思いつきますね。ただ、ここで欲しいのはあくまでエンコードされた文字列なので、その変更はあんまりやりたくない感じです。

 「見た感じちゃんとリサイズされてるじゃん!」というテストを書くのは(私には)無理そうなので、まあ当面これでオッケーということにします。解決。


  1. 変換すべきサイズはこんな感じで適当にそれっぽく計算しておく

  2. と言いつつ対応を忘れていたのでCI落ちました