This commit is contained in:
cnwhy
2026-08-02 23:28:49 +08:00
parent c6f33bf5c9
commit f799ae2b40
12 changed files with 531 additions and 24 deletions
+140 -7
View File
@@ -1,5 +1,5 @@
/*!
* qr-decode v0.0.5
* qr-decode v0.0.6
* (c) cnwhy <w.why@163.com>
* Released under the ISC License.
*/
@@ -2410,6 +2410,10 @@ var Pixel = function Pixel(data, base) {
throw "point error";
}
var point = x * 4 + y * base.width * 4;
// 透明/半透明像素按白色背景处理,避免透明区域被误判为黑色模块
if (data[point + 3] < 128) {
return 255;
}
return (data[point] * 33.33 + data[point + 1] * 33.33 + data[point + 2] * 33.33) / 100;
};
};
@@ -2424,13 +2428,142 @@ var binarize = function binarize(data, base, th) {
}
return ret;
};
function decode(imageDate, debugfn) {
var base = {
// 默认二值化阈值
var DEFAULT_THRESHOLD = 153;
// 图像预处理策略:输入 ImageData,输出 {data, width, height, threshold?}
// threshold 可以是单值或数组(多阈值依次尝试)
// 后续优化(自适应二值化、放大等)只需在此数组追加策略即可
var preprocessStrategies = [null,
// 0. 原图原阈值
invertImage,
// 1. 颜色反转(适配深色背景的反色二维码)
otsuStrategy,
// 2. Otsu 自动阈值(适配整体偏亮/偏暗的图)
invertedOtsuStrategy,
// 3. 反转 + Otsu(适配整体偏暗的反色二维码)
multiThresholdStrategy // 4. 多阈值扫描(兜底 Otsu 盲区:深色装饰背景等多峰分布美化二维码)
];
// 多阈值扫描集合:仅在前 4 个策略全部失败后触发
// 覆盖 Otsu 无法处理的多峰分布图(如“深色背景+灰模块+白间隔”三峰,Otsu 会选错谷),
// 以及带过渡带的低对比度图(Otsu 分割点偏低,需更高阈值才能获得清晰模块)
// 步长 15 覆盖全区间:o3.png 有效区间 125~150o2.png 有效区间 180~240
var multiThresholdValues = [60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240];
// 多阈值策略:复用原图数据,对多阈值集合依次尝试
function multiThresholdStrategy(imageDate) {
return {
data: imageDate.data,
width: imageDate.width,
height: imageDate.height,
debugfn: debugfn
threshold: multiThresholdValues
};
return process(imageDate.data, base);
}
// 颜色反转:RGB 取反,alpha 保持不变
function invertImage(imageDate) {
var data = new Uint8ClampedArray(imageDate.data);
for (var i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i];
data[i + 1] = 255 - data[i + 1];
data[i + 2] = 255 - data[i + 2];
}
return {
data: data,
width: imageDate.width,
height: imageDate.height
};
}
// Otsu 大津法:穷举 0-255 阈值,求类间方差最大者作为最优全局阈值
function otsuThreshold(imageDate) {
var data = imageDate.data;
// 1. 灰度直方图(一次扫描,跳过透明/半透明像素)
// 透明区域在二值化时按白处理(见 Pixel),不影响检测;
// 但统计时计入会拉偏阈值,故只统计真实可见像素
var hist = new Array(256);
for (var i = 0; i < 256; i++) hist[i] = 0;
var total = 0;
for (var i = 0; i < data.length; i += 4) {
if (data[i + 3] < 128) continue;
var gray = (data[i] * 33.33 + data[i + 1] * 33.33 + data[i + 2] * 33.33) / 100 | 0;
hist[gray]++;
total++;
}
// 2. 遍历阈值,找类间方差最大的
var sum = 0; // 全局灰度加权和
for (var i = 0; i < 256; i++) sum += i * hist[i];
var sumB = 0; // 背景类灰度加权和
var wB = 0; // 背景类像素数
var maxVar = -1;
var bestT = DEFAULT_THRESHOLD;
for (var t = 0; t < 256; t++) {
wB += hist[t];
if (wB === 0) continue;
var wF = total - wB;
if (wF === 0) break;
sumB += t * hist[t];
var mB = sumB / wB; // 背景类均值
var mF = (sum - sumB) / wF; // 前景类均值
var between = wB * wF * (mB - mF) * (mB - mF); // 类间方差
// 用 >= 在方差平坦时取最亮侧;阈值取两类均值中点,
// 避免双离散值图(类间方差平台)时阈值卡在类边界导致模块误判为白
if (between >= maxVar) {
maxVar = between;
bestT = Math.floor((mB + mF) / 2);
}
}
return bestT;
}
// Otsu 策略:复用原图数据,仅提供自动计算的阈值(由 process 的 threshold 参数消费)
function otsuStrategy(imageDate) {
return {
data: imageDate.data,
width: imageDate.width,
height: imageDate.height,
threshold: otsuThreshold(imageDate)
};
}
// 反转 + Otsu 策略:先反转,再对反转后的图计算 Otsu 阈值
// 覆盖“深色不够深、浅色不够浅”的偏暗反色二维码(固定阈值反转覆盖不了)
function invertedOtsuStrategy(imageDate) {
var inv = invertImage(imageDate);
return {
data: inv.data,
width: inv.width,
height: inv.height,
threshold: otsuThreshold(inv)
};
}
function decode(imageDate, debugfn) {
var lastErr;
for (var i = 0; i < preprocessStrategies.length; i++) {
var img = preprocessStrategies[i] ? preprocessStrategies[i](imageDate) : imageDate;
var base = {
width: img.width,
height: img.height,
debugfn: debugfn
};
// 支持多阈值:threshold 为数组时依次尝试,任一成功即返回
var thresholds = img.threshold;
if (!(thresholds instanceof Array)) {
thresholds = [thresholds];
}
for (var j = 0; j < thresholds.length; j++) {
try {
return process(img.data, base, thresholds[j]);
} catch (e) {
lastErr = e;
}
}
}
throw lastErr;
}
// ECI 声明中常见的字符集(QR 规范:ECI 编号 -> 字符集)
@@ -2556,11 +2689,11 @@ var bytesToString = function bytesToString(bytes, eci) {
// 4. 兜底默认 UTF-8
return decodeUTF8(bytes);
};
var process = function process(data, base) {
var process = function process(data, base, threshold) {
// var start = new Date().getTime();
// var image = grayScaleToBitmap(grayscale(),base);
// var image = binarize(128);
var image = binarize(data, base, 153); //转为位图;
var image = binarize(data, base, threshold || DEFAULT_THRESHOLD); //转为位图;
// 位图转为QR矩阵
var detector = new Detector_1(image, base);
+140 -7
View File
@@ -1,5 +1,5 @@
/*!
* qr-decode v0.0.5
* qr-decode v0.0.6
* (c) cnwhy <w.why@163.com>
* Released under the ISC License.
*/
@@ -2416,6 +2416,10 @@
throw "point error";
}
var point = x * 4 + y * base.width * 4;
// 透明/半透明像素按白色背景处理,避免透明区域被误判为黑色模块
if (data[point + 3] < 128) {
return 255;
}
return (data[point] * 33.33 + data[point + 1] * 33.33 + data[point + 2] * 33.33) / 100;
};
};
@@ -2430,13 +2434,142 @@
}
return ret;
};
function decode(imageDate, debugfn) {
var base = {
// 默认二值化阈值
var DEFAULT_THRESHOLD = 153;
// 图像预处理策略:输入 ImageData,输出 {data, width, height, threshold?}
// threshold 可以是单值或数组(多阈值依次尝试)
// 后续优化(自适应二值化、放大等)只需在此数组追加策略即可
var preprocessStrategies = [null,
// 0. 原图原阈值
invertImage,
// 1. 颜色反转(适配深色背景的反色二维码)
otsuStrategy,
// 2. Otsu 自动阈值(适配整体偏亮/偏暗的图)
invertedOtsuStrategy,
// 3. 反转 + Otsu(适配整体偏暗的反色二维码)
multiThresholdStrategy // 4. 多阈值扫描(兜底 Otsu 盲区:深色装饰背景等多峰分布美化二维码)
];
// 多阈值扫描集合:仅在前 4 个策略全部失败后触发
// 覆盖 Otsu 无法处理的多峰分布图(如“深色背景+灰模块+白间隔”三峰,Otsu 会选错谷),
// 以及带过渡带的低对比度图(Otsu 分割点偏低,需更高阈值才能获得清晰模块)
// 步长 15 覆盖全区间:o3.png 有效区间 125~150o2.png 有效区间 180~240
var multiThresholdValues = [60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240];
// 多阈值策略:复用原图数据,对多阈值集合依次尝试
function multiThresholdStrategy(imageDate) {
return {
data: imageDate.data,
width: imageDate.width,
height: imageDate.height,
debugfn: debugfn
threshold: multiThresholdValues
};
return process(imageDate.data, base);
}
// 颜色反转:RGB 取反,alpha 保持不变
function invertImage(imageDate) {
var data = new Uint8ClampedArray(imageDate.data);
for (var i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i];
data[i + 1] = 255 - data[i + 1];
data[i + 2] = 255 - data[i + 2];
}
return {
data: data,
width: imageDate.width,
height: imageDate.height
};
}
// Otsu 大津法:穷举 0-255 阈值,求类间方差最大者作为最优全局阈值
function otsuThreshold(imageDate) {
var data = imageDate.data;
// 1. 灰度直方图(一次扫描,跳过透明/半透明像素)
// 透明区域在二值化时按白处理(见 Pixel),不影响检测;
// 但统计时计入会拉偏阈值,故只统计真实可见像素
var hist = new Array(256);
for (var i = 0; i < 256; i++) hist[i] = 0;
var total = 0;
for (var i = 0; i < data.length; i += 4) {
if (data[i + 3] < 128) continue;
var gray = (data[i] * 33.33 + data[i + 1] * 33.33 + data[i + 2] * 33.33) / 100 | 0;
hist[gray]++;
total++;
}
// 2. 遍历阈值,找类间方差最大的
var sum = 0; // 全局灰度加权和
for (var i = 0; i < 256; i++) sum += i * hist[i];
var sumB = 0; // 背景类灰度加权和
var wB = 0; // 背景类像素数
var maxVar = -1;
var bestT = DEFAULT_THRESHOLD;
for (var t = 0; t < 256; t++) {
wB += hist[t];
if (wB === 0) continue;
var wF = total - wB;
if (wF === 0) break;
sumB += t * hist[t];
var mB = sumB / wB; // 背景类均值
var mF = (sum - sumB) / wF; // 前景类均值
var between = wB * wF * (mB - mF) * (mB - mF); // 类间方差
// 用 >= 在方差平坦时取最亮侧;阈值取两类均值中点,
// 避免双离散值图(类间方差平台)时阈值卡在类边界导致模块误判为白
if (between >= maxVar) {
maxVar = between;
bestT = Math.floor((mB + mF) / 2);
}
}
return bestT;
}
// Otsu 策略:复用原图数据,仅提供自动计算的阈值(由 process 的 threshold 参数消费)
function otsuStrategy(imageDate) {
return {
data: imageDate.data,
width: imageDate.width,
height: imageDate.height,
threshold: otsuThreshold(imageDate)
};
}
// 反转 + Otsu 策略:先反转,再对反转后的图计算 Otsu 阈值
// 覆盖“深色不够深、浅色不够浅”的偏暗反色二维码(固定阈值反转覆盖不了)
function invertedOtsuStrategy(imageDate) {
var inv = invertImage(imageDate);
return {
data: inv.data,
width: inv.width,
height: inv.height,
threshold: otsuThreshold(inv)
};
}
function decode(imageDate, debugfn) {
var lastErr;
for (var i = 0; i < preprocessStrategies.length; i++) {
var img = preprocessStrategies[i] ? preprocessStrategies[i](imageDate) : imageDate;
var base = {
width: img.width,
height: img.height,
debugfn: debugfn
};
// 支持多阈值:threshold 为数组时依次尝试,任一成功即返回
var thresholds = img.threshold;
if (!(thresholds instanceof Array)) {
thresholds = [thresholds];
}
for (var j = 0; j < thresholds.length; j++) {
try {
return process(img.data, base, thresholds[j]);
} catch (e) {
lastErr = e;
}
}
}
throw lastErr;
}
// ECI 声明中常见的字符集(QR 规范:ECI 编号 -> 字符集)
@@ -2562,11 +2695,11 @@
// 4. 兜底默认 UTF-8
return decodeUTF8(bytes);
};
var process = function process(data, base) {
var process = function process(data, base, threshold) {
// var start = new Date().getTime();
// var image = grayScaleToBitmap(grayscale(),base);
// var image = binarize(128);
var image = binarize(data, base, 153); //转为位图;
var image = binarize(data, base, threshold || DEFAULT_THRESHOLD); //转为位图;
// 位图转为QR矩阵
var detector = new Detector_1(image, base);
+2 -2
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "qr-decode",
"version": "0.0.5",
"version": "0.0.6",
"description": "QRCode parser/decode",
"main": "src/QRDecode.js",
"files": [
@@ -12,6 +12,7 @@
],
"scripts": {
"test": "node test/node.js",
"test:regression": "node test/all_regression.js",
"demo": "parcel ./demo/index.html",
"build": "bili browser.js --format umd,umd-min,es --module-name qrDecode --file-name qr-decode.[format][min][ext] --banner"
},
+136 -6
View File
@@ -14,6 +14,10 @@ var Pixel = function (data,base) {
throw "point error";
}
var point = (x * 4) + (y * base.width * 4)
// 透明/半透明像素按白色背景处理,避免透明区域被误判为黑色模块
if (data[point + 3] < 128) {
return 255;
}
return (data[point] * 33.33 + data[point + 1] * 33.33 + data[point + 2] * 33.33) / 100;
}
}
@@ -110,13 +114,139 @@ var grayScaleToBitmap = function (image,base) {
return bitmap;
}
function decode(imageDate,debugfn){
var base = {
// 默认二值化阈值
var DEFAULT_THRESHOLD = 153;
// 图像预处理策略:输入 ImageData,输出 {data, width, height, threshold?}
// threshold 可以是单值或数组(多阈值依次尝试)
// 后续优化(自适应二值化、放大等)只需在此数组追加策略即可
var preprocessStrategies = [
null, // 0. 原图原阈值
invertImage, // 1. 颜色反转(适配深色背景的反色二维码)
otsuStrategy, // 2. Otsu 自动阈值(适配整体偏亮/偏暗的图)
invertedOtsuStrategy, // 3. 反转 + Otsu(适配整体偏暗的反色二维码)
multiThresholdStrategy // 4. 多阈值扫描(兜底 Otsu 盲区:深色装饰背景等多峰分布美化二维码)
];
// 多阈值扫描集合:仅在前 4 个策略全部失败后触发
// 覆盖 Otsu 无法处理的多峰分布图(如“深色背景+灰模块+白间隔”三峰,Otsu 会选错谷),
// 以及带过渡带的低对比度图(Otsu 分割点偏低,需更高阈值才能获得清晰模块)
// 步长 15 覆盖全区间:o3.png 有效区间 125~150o2.png 有效区间 180~240
var multiThresholdValues = [60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240];
// 多阈值策略:复用原图数据,对多阈值集合依次尝试
function multiThresholdStrategy(imageDate) {
return {
data: imageDate.data,
width: imageDate.width,
height: imageDate.height,
debugfn: debugfn
threshold: multiThresholdValues
};
}
// 颜色反转:RGB 取反,alpha 保持不变
function invertImage(imageDate) {
var data = new Uint8ClampedArray(imageDate.data);
for (var i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i];
data[i + 1] = 255 - data[i + 1];
data[i + 2] = 255 - data[i + 2];
}
return process(imageDate.data,base)
return {
data: data,
width: imageDate.width,
height: imageDate.height
};
}
// Otsu 大津法:穷举 0-255 阈值,求类间方差最大者作为最优全局阈值
function otsuThreshold(imageDate) {
var data = imageDate.data;
// 1. 灰度直方图(一次扫描,跳过透明/半透明像素)
// 透明区域在二值化时按白处理(见 Pixel),不影响检测;
// 但统计时计入会拉偏阈值,故只统计真实可见像素
var hist = new Array(256);
for (var i = 0; i < 256; i++) hist[i] = 0;
var total = 0;
for (var i = 0; i < data.length; i += 4) {
if (data[i + 3] < 128) continue;
var gray = (data[i] * 33.33 + data[i + 1] * 33.33 + data[i + 2] * 33.33) / 100 | 0;
hist[gray]++;
total++;
}
// 2. 遍历阈值,找类间方差最大的
var sum = 0; // 全局灰度加权和
for (var i = 0; i < 256; i++) sum += i * hist[i];
var sumB = 0; // 背景类灰度加权和
var wB = 0; // 背景类像素数
var maxVar = -1;
var bestT = DEFAULT_THRESHOLD;
for (var t = 0; t < 256; t++) {
wB += hist[t];
if (wB === 0) continue;
var wF = total - wB;
if (wF === 0) break;
sumB += t * hist[t];
var mB = sumB / wB; // 背景类均值
var mF = (sum - sumB) / wF; // 前景类均值
var between = wB * wF * (mB - mF) * (mB - mF); // 类间方差
// 用 >= 在方差平坦时取最亮侧;阈值取两类均值中点,
// 避免双离散值图(类间方差平台)时阈值卡在类边界导致模块误判为白
if (between >= maxVar) {
maxVar = between;
bestT = Math.floor((mB + mF) / 2);
}
}
return bestT;
}
// Otsu 策略:复用原图数据,仅提供自动计算的阈值(由 process 的 threshold 参数消费)
function otsuStrategy(imageDate) {
return {
data: imageDate.data,
width: imageDate.width,
height: imageDate.height,
threshold: otsuThreshold(imageDate)
};
}
// 反转 + Otsu 策略:先反转,再对反转后的图计算 Otsu 阈值
// 覆盖“深色不够深、浅色不够浅”的偏暗反色二维码(固定阈值反转覆盖不了)
function invertedOtsuStrategy(imageDate) {
var inv = invertImage(imageDate);
return {
data: inv.data,
width: inv.width,
height: inv.height,
threshold: otsuThreshold(inv)
};
}
function decode(imageDate, debugfn) {
var lastErr;
for (var i = 0; i < preprocessStrategies.length; i++) {
var img = preprocessStrategies[i] ? preprocessStrategies[i](imageDate) : imageDate;
var base = {
width: img.width,
height: img.height,
debugfn: debugfn
};
// 支持多阈值:threshold 为数组时依次尝试,任一成功即返回
var thresholds = img.threshold;
if (!(thresholds instanceof Array)) {
thresholds = [thresholds];
}
for (var j = 0; j < thresholds.length; j++) {
try {
return process(img.data, base, thresholds[j]);
} catch (e) {
lastErr = e;
}
}
}
throw lastErr;
}
// ECI 声明中常见的字符集(QR 规范:ECI 编号 -> 字符集)
@@ -247,11 +377,11 @@ var bytesToString = function (bytes, eci) {
return decodeUTF8(bytes);
}
var process = function (data,base) {
var process = function (data,base,threshold) {
// var start = new Date().getTime();
// var image = grayScaleToBitmap(grayscale(),base);
// var image = binarize(128);
var image = binarize(data,base,153); //转为位图;
var image = binarize(data,base,threshold || DEFAULT_THRESHOLD); //转为位图;
debug && base.debugfn && base.debugfn(image,base.width);
+106
View File
@@ -0,0 +1,106 @@
// 综合回归测试:标准图 + 特殊图 + 构造场景(策略链各层)
// 用法:node test/all_regression.js
var server = require('../server');
var imgDecode = require('../src/imageDecode');
var qrDecode = require('../src/QRDecode');
var fs = require('fs');
var path = require('path');
var passCount = 0;
var failCount = 0;
function check(label, actual, expected) {
if (actual === expected) {
passCount++;
console.log('PASS ' + label + ' => ' + actual);
} else {
failCount++;
console.log('FAIL ' + label + ' => ' + actual + '(期望 ' + expected + '');
}
}
// ---------- 文件用例:标准图 + 特殊图 ----------
var fileCases = [
{ path: './img/16.bmp', label: 'bmp16', expect: '12345' },
{ path: './img/24.bmp', label: 'bmp24', expect: '12345' },
{ path: './img/32.bmp', label: 'bmp32', expect: '12345' },
{ path: './img/lx.jpg', label: 'jpg_lx', expect: '12345' },
{ path: './img/yh.jpg', label: 'jpg_yh', expect: '12345' },
{ path: './img/8.png', label: 'png8', expect: '12345' },
{ path: './img/24.png', label: 'png24', expect: '12345' },
{ path: './img/jt.gif', label: 'gif_jt', expect: '12345' },
{ path: './img/dt.gif', label: 'gif_dt', expect: '12345' },
{ path: './img/otsu.png', label: 'otsu.png 渐变光照', expect: '中国人' },
{ path: './img/o1.png', label: 'o1.png 透明背景美化码', expect: '中国人' },
{ path: './img/o2.png', label: 'o2.png 深色背景美化码', expect: '中国人' },
{ path: './img/o3.png', label: 'o3.png 低对比度图', expect: 'https://mpl.qzz.io/' }
];
// ---------- 构造场景用例:验证策略链各层 ----------
// 基于 8.png 构造,覆盖:Otsu / 反转 / 反转+Otsu / 数据未污染
var img8 = (function () {
var buffer = fs.readFileSync(path.join(__dirname, './img/8.png'));
var imageData = imgDecode.png(buffer);
if (Array.isArray(imageData)) imageData = imageData[0];
return imageData;
})();
var builtCases = [];
// 暗化图:RGB × 0.5 -> 需要 Otsu 策略
(function () {
var data = new Uint8ClampedArray(img8.data.length);
for (var i = 0; i < img8.data.length; i += 4) {
data[i] = img8.data[i] * 0.5 | 0;
data[i + 1] = img8.data[i + 1] * 0.5 | 0;
data[i + 2] = img8.data[i + 2] * 0.5 | 0;
data[i + 3] = img8.data[i + 3];
}
builtCases.push({ img: { data: data, width: img8.width, height: img8.height }, label: '暗化图(Otsu)', expect: '12345' });
})();
// 反色码:黑底白码 -> 需要反转策略
(function () {
var data = new Uint8ClampedArray(img8.data.length);
for (var i = 0; i < img8.data.length; i += 4) {
var gray = (img8.data[i] * 33.33 + img8.data[i + 1] * 33.33 + img8.data[i + 2] * 33.33) / 100;
var v = gray <= 153 ? 255 : 0;
data[i] = v; data[i + 1] = v; data[i + 2] = v; data[i + 3] = 255;
}
builtCases.push({ img: { data: data, width: img8.width, height: img8.height }, label: '反色码(反转)', expect: '12345' });
})();
// 暗反色码:黑底灰模块 -> 需要反转+Otsu 策略
(function () {
var data = new Uint8ClampedArray(img8.data.length);
for (var i = 0; i < img8.data.length; i += 4) {
var gray = (img8.data[i] * 33.33 + img8.data[i + 1] * 33.33 + img8.data[i + 2] * 33.33) / 100;
var v = gray <= 153 ? 95 : 8;
data[i] = v; data[i + 1] = v; data[i + 2] = v; data[i + 3] = 255;
}
builtCases.push({ img: { data: data, width: img8.width, height: img8.height }, label: '暗反色码(反转+Otsu)', expect: '12345' });
})();
// 原图再识别:验证预处理未污染原始数据
builtCases.push({ img: img8, label: '原图再识别(数据未污染)', expect: '12345' });
// ---------- 执行 ----------
var tasks = [];
fileCases.forEach(function (c) {
tasks.push(server.decodeByPath(path.join(__dirname, c.path))
.then(function (txt) { check(c.label, txt, c.expect); })
.catch(function (e) { failCount++; console.log('FAIL ' + c.label + ' => ' + e); }));
});
builtCases.forEach(function (c) {
tasks.push(Promise.resolve().then(function () {
try { check(c.label, qrDecode(c.img), c.expect); }
catch (e) { failCount++; console.log('FAIL ' + c.label + ' => ' + e); }
}));
});
Promise.all(tasks).then(function () {
console.log('');
console.log('========================');
console.log('综合回归:通过 ' + passCount + ' / 失败 ' + failCount);
process.exit(failCount > 0 ? 1 : 0);
});
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

+4
View File
@@ -39,4 +39,8 @@ Promise.resolve()
.then(function () { return test('./img/24.png', 'png24 ') })
.then(function () { return test('./img/jt.gif', 'gif_jt') }) //单帧
.then(function () { return test('./img/dt.gif', 'gif_dt') }) //多帧
.then(function () { return test('./img/otsu.png', 'otsu ') }) //渐变光照图
.then(function () { return test('./img/o1.png', 'o1 ') }) //透明背景美化码
.then(function () { return test('./img/o2.png', 'o2 ') }) //深色背景美化码
.then(function () { return test('./img/o3.png', 'o3 ') }) //低对比度图