Pixelmatch – AWS Lambda 中进行图片比较
     发布在:JavaScript      浏览:45      评论:0 条评论

继上一篇: 《Puppeteer – AWS Lambda 中使用 puppeteer 页面截图》

新旧页面截图比较,若将截图和比较功能坐在一起,整个 Lambda 函数过于臃肿。因此将比较功能抽离出来。

widget_compare.js

'use strict';
const pixelmatch = require('pixelmatch');
const PNG = require('pngjs').PNG;
const fs = require('fs');
const urlRequire = require('url');
const aws = require('aws-sdk');
const AwsLambdaHttpResponse = require('aws-lambda-http-response');
const compareRepository = require('../repositories/repository_compare');
const path = require('path');
const utils = require('../utils/utils');
const tmp = require('tmp');
const urlBucket = process.env.BUCKET_URL;
const bucket = process.env.BUCKET_NAME;
const s3 = new aws.S3({apiVersion: process.env.S3_API_VERSION});
exports.handler = (event, context, callback) => {
// For keeping the browser launch
context.callbackWaitsForEmptyEventLoop = false;
return exports
.run(event)
.then((result) => {
return new AwsLambdaHttpResponse({callback}).success({
body: result
});
})
.catch((err) => {
console.log('handler', err);
return new AwsLambdaHttpResponse({callback}).error({
body: err
});
});
};
/**
* @name compare
* @description Integrate getFiles and generateDiff functions
* @param {Object} event
* Ex.
* {
*    url: '',
*    env: 'dev',
*    bulkInsert: 'false',
*    devices: []
* }
* @return {Promise}
*/
exports.run = function run(event) {
const url = event.url;
const env = event.env;
const bulkInsert = event.bulkInsert;
let devices = event.device == null || event.device == '' ? [] : [event.device];
if (event.devices) devices = event.devices;
return new Promise((resolve, reject) => {
const urlMap = urlRequire.parse(url, true).path;
const folder = urlMap.replace(/\//g, '>');
const getImageFromS3 = function(fileName, baseline) {
return new Promise((resolve, reject) => {
const params = {
Bucket: bucket,
Key: path.join(folder, fileName)
};
return s3.getObject(params, (error, data) => {
if (error) {
return reject(new Error(JSON.stringify({
Key: params.Key,
Msg: utils.errorFormatter(error).message
})));
}
return resolve({
name: fileName,
baseline: baseline,
img: PNG.sync.read(data.Body)
});
});
});
};
const getImagesByDevice = function(device) {
const baselineImageName = `baseline_${device.trim()}_screenshot.png`;
const currentImageName = `current_${env.trim()}_${device.trim()}_screenshot.png`;
const getImages = [
getImageFromS3(baselineImageName, true),
getImageFromS3(currentImageName, false)
];
return Promise.all(getImages);
};
const getImages = devices.map(getImagesByDevice);
return Promise.all(getImages)
.then((deviceImages) => {
const runDiffs = deviceImages.map(runDiff(urlMap, folder, env));
return Promise.all(runDiffs);
})
.then((handledCompares) => {
console.log('start insert compare results');
if (bulkInsert == true) return datas;
return compareRepository.createCompare({data: handledCompares});
})
.then((result) => {
return resolve(result);
})
.catch((error) => {
return reject(error);
});
});
};
/**
* @name validateParameters
* @description Validate parameters required.
* @param {Object} params Object with url, env, (device or devices).
* @return {Boolean}
*/
exports.validateParameters = function validateParameters(params) {
if (!params.host_current || !params.path || !params.env || (!params.device && !params.devices)) {
return false;
}
return true;
};

Function: runDiff


/** * @name runDiff * @param {String} urlMap * @param {String} folder * @param {String} env * @return {Promise} */ function runDiff(urlMap, folder, env) { return function(images) { let diffImageName = ''; let screenResolution = ''; let baselineImage = null; let currentImage = null; images.forEach((image) => { if (image.baseline) { screenResolution = image.name.replace('baseline_', '').replace('_screenshot.png', ''); baselineImage = image; } else { diffImageName = image.name.replace('current', 'diff'); currentImage = image; } }); if (!baselineImage) { return reject(new Error('Baseline Image not found.')); } if (!currentImage) { return reject(new Error('Current Image not found.')); } const handledCompare = { path: urlMap, resolution: screenResolution, environment: env, autoresult: 'Error', pixeldiff: 1, manualresult: '', notes: '', lastupdate: utils.getFormattedDateTime(), urlbaseline: urlBucket + folder + '/' + 'thumbnail_' + baselineImage.name, urlcurrent: urlBucket + folder + '/' + 'thumbnail_' + currentImage.name, urldiff: urlBucket + folder + '/' + 'thumbnail_' + diffImageName }; const diffImage = function diffImage(img1, img2) { return new Promise((resolve, reject) => { console.log('start diff image'); const baseImage = img1.height > img2.height ? img1 : img2; const diff = new PNG({width: img1.width, height: img1.height}); const pix = pixelmatch(img1.data, img2.data, diff.data, img1.width, img1.height, {threshold: 0.2}); const diffPix = pix + Math.abs(img1.height - img2.height) * baseImage.width; const tmpName = tmp.tmpNameSync(); return diff .pack() .pipe( fs.createWriteStream(tmpName).on('finish', () => {}) ) .on('close', () => { return utils .imgThumbnail(tmpName, 77, 144) .then((thumbnail) => { const moveToS3 = [ s3.putObject({ Bucket: bucket, Key: path.join(folder, diffImageName), Body: PNG.sync.write(diff), ContentType: 'image/png' }).promise(), s3.putObject({ Bucket: bucket, Key: path.join(folder, `thumbnail_${diffImageName}`), Body: thumbnail, ContentType: 'image/png' }).promise() ]; return Promise.all(moveToS3); }) .then(() => { const allPix = img1.width * img1.height; const percentage = (allPix - diffPix) / allPix; handledCompare.autoresult = percentage < 0.95 ? 'Error' : 'Success'; handledCompare.pixeldiff = `${(percentage * 100).toFixed(2)}%`; return resolve(handledCompare); }) .catch((error) => { return reject(err); }); }); }); }; return diffImage(baselineImage.img, currentImage.img); }; }

续篇

  1. 《Gm – 利用 gm 修改图片》
Responses