# png图片自动新旧对比
# 背景
由于控制器包体积过大,其中大部分又是由图片大小导致的,所以之前对图片进行了压缩。现在遇到的问题是回归测试时,无法手动测试覆盖全(改动了一千多的图片),所以需要自动化的方法进行测试。
# 方案
在想怎么对比新旧图片时,忽然想到Ixs组件库的UI测试就是用页面截图去跟之前保存的截图进行对比,判断是否发生了变化。
于是顺藤摸瓜,找到了类似的纯图片对比的工具:pixelmatch
github: https://github.com/mapbox/pixelmatch
该库可以对png进行像素级别的对比,返回差异的像素数量,并可以画出差异的地方。
对于我们的项目,只需要确认差异值小于误差范围即可。
流程如下:
- 循环修改后的代码目录,找出所有png
- 根据png路径在老代码目录找出对应png,将两个图片传给
pixelmatch
进行对比。区分的敏感度threshold
为0.1,最灵敏的程度,保证能找出区别。 - 这里的差异值范围我们取图片总像素的千分之一,如果差异值大于千分之一则记录下来,再人工进行对比
下面给出代码:
需要注意的是,存在非png的图片改为了png后缀的,所以需要先检查下真实的格式后再做对比。
import fs from 'fs';
import path from 'path';
import { PNG } from 'pngjs';
import pixelmatch from 'pixelmatch';
// 修改后的文件夹路径
const folder1 = '';
// 修改前的文件夹路径
const folder2 = '';
const outputFile = 'diffImages.txt'; // 记录差异大的图片路径的文件
let diffImagesLength = 0; // 记录差异大的图片数量
const isValidPng = (filePath) => {
const buffer = fs.readFileSync(filePath);
const pngSignature = [137, 80, 78, 71, 13, 10, 26, 10];
for (let i = 0; i < pngSignature.length; i++) {
if (buffer[i] !== pngSignature[i]) {
return false;
}
}
return true;
};
// 递归读取指定文件夹中的 PNG 文件
const getPngFilesRecursively = (folder) => {
let pngFiles = [];
const files = fs.readdirSync(folder);
for (const file of files) {
const filePath = path.join(folder, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
// 如果是目录,递归调用
pngFiles = pngFiles.concat(getPngFilesRecursively(filePath));
} else if (path.extname(file).toLowerCase() === '.png') {
pngFiles.push(filePath);
}
}
return pngFiles;
};
// 对比两个 PNG 图片,并返回差异数量
const compareImages = (img1Path, img2Path, relativePath) => {
const img1 = PNG.sync.read(fs.readFileSync(img1Path));
const img2 = PNG.sync.read(fs.readFileSync(img2Path));
const { width, height } = img1;
// 确保两张图片大小相同
if (width !== img2.width || height !== img2.height) {
console.log(`图片大小不匹配: ${relativePath}`);
return;
}
const diff = pixelmatch(img1.data, img2.data, null, width, height, {
threshold: 0.1,
includeAA: true,
});
// 计算允许的阈值,0.1%
const totalPixels = width * height;
const allowedDifference = totalPixels * 0.001;
if (diff > allowedDifference) {
diffImagesLength++;
fs.appendFileSync(outputFile, `差异图片: ${relativePath},差异像素数量: ${diff}\n`);
}
return diff;
};
process.stdout.write(`正在查找png文件`);
// 读取文件夹中的 PNG 文件
const folder1Files = getPngFilesRecursively(folder1);
const totalFiles = folder1Files.length;
process.stdout.clearLine();
// 进行对比
folder1Files.forEach((file1, index) => {
const relativePath = path.relative(folder1, file1);
// 在第二个文件夹中生成对应的完整路径
const file2 = path.join(folder2, relativePath);
let diff = 0;
if (fs.existsSync(file2)) {
if (isValidPng(file1) && isValidPng(file2)) {
diff = compareImages(file1, file2, relativePath);
// 显示进度
const progress = ((index + 1) / totalFiles * 100).toFixed(2);
process.stdout.clearLine();
process.stdout.write(`总进度: ${progress}% 图片像素差异: ${diff} 对比图片:${relativePath} \r`);
} else {
console.log(`无效的 PNG 文件: ${relativePath}`);
}
} else {
console.log(`未找到对应文件: ${file2}`);
}
});
process.stdout.clearLine();
if (diffImagesLength > 0) {
console.log(`存在差异过大的图片,已记录到 ${outputFile}。`);
} else {
console.log('两个文件夹中的图片没有明显差异。');
}
# 总结
通过工具,1分钟即可完成上千张图片的像素级别的对比,既解决了手动无法覆盖全的问题,也保证了质量问题。
后续有图片对比的需求,也可参照该方案实现。