All files index.ts

91.11% Statements 41/45
80% Branches 8/10
88.89% Functions 8/9
93.02% Lines 40/43

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 831x 1x 1x 1x 1x     1x 1x 1x 1x   1x   1x 3x 4x 4x   4x         18x 18x 18x       18x     1x 1x 1x                 9x 9x 9x         9x 9x 9x           9x 5x     4x       4x 4x 4x 4x   4x   4x     1x 9x 9x      
import fs from "node:fs";
import path from "node:path";
import pixelmatch, { type PixelmatchOptions } from "pixelmatch";
import { PNG } from "pngjs";
import mkdirp from "mkdirp";
 
import type { ImageData, Decorder, ImgDiffOptions, ImgDiffResult } from "./types";
import decodePng from "./decode-png";
import decodeJpeg from "./decode-jpeg";
import decodeTiff from "./decode-tiff";
import expand from "./expand";
 
const extensionDecoderMap: Record<string, Decorder> = {};
 
export function registerDecoder(extensions: string[], decoder: Decorder) {
  extensions.forEach(extension => {
    if (!extension.startsWith(".")) {
      extension = `.${extension}`;
    }
    extensionDecoderMap[extension] = decoder;
  });
}
 
function decode(filename: string) {
  const ext = path.extname(filename);
  const decoder = extensionDecoderMap[ext];
  Iif (!ext || !decoder) {
    const exts = Object.keys(extensionDecoderMap).join(", ");
    throw new Error("File name should be end with " + exts);
  }
  return decoder(filename);
}
 
registerDecoder(["png"], decodePng);
registerDecoder(["jpg", "jpeg"], decodeJpeg);
registerDecoder(["tiff"], decodeTiff);
 
function compare(
  img1: ImageData,
  img2: ImageData,
  diffFilename?: string,
  generateOnlyDiffFile: boolean = false,
  options: PixelmatchOptions = { threshold: 0.1, includeAA: false },
): Promise<ImgDiffResult> {
  const { dataList, width, height } = expand(img1, img2);
  const diff = new PNG({ width, height });
  const pmOpt: PixelmatchOptions = {
    threshold: 0,
    ...options,
  };
 
  const count = pixelmatch(dataList[0], dataList[1], diff.data, width, height, pmOpt);
  const imagesAreSame = count === 0;
  const result = {
    width,
    height,
    imagesAreSame,
    diffCount: count,
  };
  if (!diffFilename) {
    return Promise.resolve(result);
  }
 
  Iif (imagesAreSame && generateOnlyDiffFile) {
    return Promise.resolve(result);
  }
 
  mkdirp.sync(path.dirname(diffFilename));
  const out = fs.createWriteStream(diffFilename);
  const p = new Promise<ImgDiffResult>((resolve, reject) => {
    out.on("finish", () => resolve(result)).on("error", err => reject(err));
  });
  diff.pack().pipe(out);
 
  return p;
}
 
export function imgDiff(opt: ImgDiffOptions) {
  return Promise.all([decode(opt.actualFilename), decode(opt.expectedFilename)]).then(imgs => {
    return compare(imgs[0], imgs[1], opt.diffFilename, opt.generateOnlyDiffFile, opt.options);
  });
}