图片表现成像素网点的滤镜效果

请注意,此滤镜会丢失绝大部分图片细节;另外,当图片分辨率较大时,可能需要加大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>

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注