node-puzzle
Version:
Node.js 生成滑块验证码的拼图和背景图。
104 lines (101 loc) • 3.78 kB
JavaScript
import { getRandomPoints, drawPuzzle } from 'create-puzzle';
import { randomInt } from 'ut2';
import { loadImage, createCanvas } from '@napi-rs/canvas';
async function createPuzzle(input, options = {}) {
const {
// 拼图
borderWidth = 1, borderColor = 'rgba(255,255,255,0.7)', fillColor = 'rgba(255,255,255,0.7)', points: outPoints, width = 60, height = 60, x: outX, y: outY, margin = 2, equalHeight = true, format = 'png', quality = 80, avifConfig,
// 背景图
bgWidth: outBgWidth, bgHeight: outBgHeight, bgOffset = [0, 0], bgFormat = 'jpeg', bgQuality: outBgQuality, bgAvifConfig: outBgAvifConfig } = options;
const bgQuality = outBgQuality || quality;
const bgAvifConfig = outBgAvifConfig || avifConfig;
const originImg = await loadImage(input);
// 拼图点
const points = typeof outPoints === 'number' || !outPoints ? getRandomPoints(outPoints) : outPoints;
const bgWidth = typeof outBgWidth === 'number' && outBgWidth > 0
? outBgWidth > width
? outBgWidth
: width
: originImg.width;
const bgHeight = typeof outBgHeight === 'number' && outBgHeight > 0
? outBgHeight > height
? outBgHeight
: height
: originImg.height;
const maxOffsetX = bgWidth - width;
const maxOffsetY = bgHeight - height;
let x = typeof outX === 'undefined' ? randomInt(width, maxOffsetX) : outX || 0;
let y = typeof outY === 'undefined' ? randomInt(0, maxOffsetY) : outY || 0;
if (x < 0) {
x = 0;
}
else if (x > maxOffsetX) {
x = maxOffsetX;
}
if (y < 0) {
y = 0;
}
else if (y > maxOffsetY) {
y = maxOffsetY;
}
// 背景图
const bgCanvas = createCanvas(bgWidth, bgHeight);
const bgCtx = bgCanvas.getContext('2d');
bgCtx.clearRect(0, 0, bgWidth, bgHeight);
bgCtx.drawImage(originImg, bgOffset[0], bgOffset[1], bgWidth, bgHeight, 0, 0, bgWidth, bgHeight);
// 拼图
const puzzleCanvasHeight = equalHeight ? bgHeight : height;
const puzzleY = equalHeight ? y : 0;
const puzzleCanvas = createCanvas(width, puzzleCanvasHeight);
const puzzleCtx = puzzleCanvas.getContext('2d');
puzzleCtx.strokeStyle = borderColor;
puzzleCtx.lineWidth = borderWidth;
puzzleCtx.clearRect(0, 0, width, puzzleCanvasHeight);
drawPuzzle(puzzleCtx, {
x: 0,
y: puzzleY,
w: width,
h: height,
points,
margin
});
puzzleCtx.clip();
puzzleCtx.drawImage(bgCanvas, x, y, width, height, 0, puzzleY, width, height);
// 背景图添加遮罩
const maskCanvas = createCanvas(width, height);
const maskCtx = maskCanvas.getContext('2d');
maskCtx.clearRect(0, 0, width, height);
maskCtx.fillStyle = fillColor;
maskCtx.fillRect(0, 0, width, height);
bgCtx.strokeStyle = borderColor;
bgCtx.lineWidth = borderWidth;
drawPuzzle(bgCtx, {
x,
y,
w: width,
h: height,
points,
margin
});
bgCtx.clip();
bgCtx.drawImage(maskCanvas, x, y, width, height);
const puzzleCanvasToData = format === 'png'
? puzzleCanvas.encode(format)
: format === 'avif'
? puzzleCanvas.encode(format, avifConfig)
: puzzleCanvas.encode(format, quality);
const bgCanvasToData = bgFormat === 'png'
? bgCanvas.encode(bgFormat)
: bgFormat === 'avif'
? bgCanvas.encode(bgFormat, bgAvifConfig)
: bgCanvas.encode(bgFormat, bgQuality);
// export canvas as image
const [puzzle, bg] = await Promise.all([puzzleCanvasToData, bgCanvasToData]);
return {
bg,
puzzle,
x,
y: equalHeight ? 0 : y
};
}
export { createPuzzle as default };