请注意,此滤镜会丢失绝大部分图片细节;另外,当图片分辨率较大时,可能需要加大HALFTONE_DOT_GRID_SIZE的值以达到同样的视觉效果
HALFTONE_DOT_GRID_SIZE值为16,图片分辨率为2560*1097
HALFTONE_DOT_GRID_SIZE值为8。
HALFTONE_DOT_GRID_SIZE值为8,图片分辨率800*800
HTML
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>HalftoneDotImage</title>
<style>
/* 极简样式,仅作演示 */
html,body{margin:0;height:100%;background:#000;display:flex;justify-content:center;align-items:center}
#halftone_canvas{border:1px solid #333}
</style>
</head>
<body>
<canvas id="halftone_canvas"></canvas>
<script>
/* ========== 配置域 ========== */
const HALFTONE_IMAGE_SRC = 'https://picsum.photos/600/400';
const HALFTONE_DOT_GRID_SIZE = 8; // 每 8×8 物理像素 = 1 个逻辑网格
const HALFTONE_DOT_MIN_RADIUS = 0.5;
const HALFTONE_DOT_MAX_RADIUS = HALFTONE_DOT_GRID_SIZE * 0.45;
/* ========== 类定义 ========== */
class HalftoneDotRenderer {
constructor(
canvasElement,
imageSource,
gridSize,
minRadius,
maxRadius
) {
this.canvas = canvasElement;
this.ctx = canvasElement.getContext('2d');
this.imageSource = imageSource;
this.gridSize = gridSize;
this.minRadius = minRadius;
this.maxRadius = maxRadius;
}
async loadImageAsync(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
}
createLowResCanvas(originalImage) {
const logicWidth = Math.ceil(originalImage.width / this.gridSize);
const logicHeight = Math.ceil(originalImage.height / this.gridSize);
const lowCanvas = document.createElement('canvas');
lowCanvas.width = logicWidth;
lowCanvas.height = logicHeight;
const lowCtx = lowCanvas.getContext('2d');
lowCtx.imageSmoothingEnabled = false;
lowCtx.drawImage(originalImage, 0, 0, logicWidth, logicHeight);
return { canvas: lowCanvas, ctx: lowCtx };
}
drawDot(centerX, centerY, radius, r, g, b) {
this.ctx.beginPath();
this.ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
this.ctx.fillStyle = `rgb(${r},${g},${b})`;
this.ctx.fill();
}
async render() {
const image = await this.loadImageAsync(this.imageSource);
// 1. 生成低分辨率缩略图
const { ctx: lowCtx } = this.createLowResCanvas(image);
const { data, width: logicW, height: logicH } = lowCtx.getImageData(0, 0, lowCtx.canvas.width, lowCtx.canvas.height);
// 2. 适配主画布尺寸
this.canvas.width = image.width;
this.canvas.height = image.height;
this.ctx.fillStyle = '#000';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
// 3. 遍历逻辑网格并绘制网点
for (let yGrid = 0; yGrid < logicH; yGrid++) {
for (let xGrid = 0; xGrid < logicW; xGrid++) {
const idx = (yGrid * logicW + xGrid) * 4;
const [r, g, b] = [data[idx], data[idx + 1], data[idx + 2]];
const luminance = (r + g + b) / (3 * 255);
const radius = this.minRadius + luminance * (this.maxRadius - this.minRadius);
if (radius < 0.5) continue;
const centerX = xGrid * this.gridSize + this.gridSize / 2;
const centerY = yGrid * this.gridSize + this.gridSize / 2;
this.drawDot(centerX, centerY, radius, r, g, b);
}
}
}
}
/* ========== 入口 ========== */
const renderer = new HalftoneDotRenderer(
document.getElementById('halftone_canvas'),
HALFTONE_IMAGE_SRC,
HALFTONE_DOT_GRID_SIZE,
HALFTONE_DOT_MIN_RADIUS,
HALFTONE_DOT_MAX_RADIUS
);
renderer.render().catch(console.error);
</script>
</body>
</html>
如果您想在同一个页面里添加多个此滤镜图片,只需按如下格式,将变量名稍作更改即可:
HTML
<style>
#halftone_canvas2{border:1px solid #333}
</style>
<canvas id="halftone_canvas2"></canvas>
<script>
const renderer2 = new HalftoneDotRenderer(
document.getElementById('halftone_canvas2'), /* ========== id名 ========== */
'http://www.wenzhimo.xyz/wp-content/uploads/2024/10/1726630100056-scaled.jpg',/* ========== 图片网址 ========== */
8, /* ========== HALFTONE_DOT_GRID_SIZE ========== */
0.5, /* ========== HALFTONE_DOT_MIN_RADIUS ========== */
8*0.45 /* ========== HALFTONE_DOT_MAX_RADIUS ========== */
);
renderer2.render().catch(console.error);
</script>
发表回复