6.3 图像处理与像素操作

6.3 图像处理与像素操作

Canvas不仅能够绘制矢量图形,还提供了强大的图像处理和像素级操作能力。本节将深入探讨图像加载、操作、合成以及直接像素处理等高级技术。

图像加载与基本操作

1. 图像加载与绘制

const canvas = document.getElementById('imageCanvas');
const ctx = canvas.getContext('2d');

// 创建Image对象
const img = new Image();
img.crossOrigin = 'Anonymous'; // 处理跨域图像
img.src = 'example.jpg';

img.onload = function() {
  // 基本绘制
  ctx.drawImage(img, 0, 0);
  
  // 缩放绘制
  ctx.drawImage(img, 0, 0, img.width/2, img.height/2);
  
  // 切片绘制
  ctx.drawImage(
    img, 
    50, 50, 100, 100, // 源图像切片区域
    200, 200, 150, 150 // 画布上的目标区域
  );
};

img.onerror = function() {
  console.error('图像加载失败');
};

2. 图像变形与变换

// 保存当前状态
ctx.save();

// 设置变换中心点
ctx.translate(150, 150);

// 旋转45度
ctx.rotate(Math.PI/4);

// 缩放图像
ctx.scale(0.8, 0.8);

// 绘制图像(中心对齐)
ctx.drawImage(img, -img.width/2, -img.height/2);

// 恢复状态
ctx.restore();

图像合成与混合模式

1. 全局合成操作

// 绘制背景
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 300, 150);

// 设置合成模式
ctx.globalCompositeOperation = 'lighter';

// 绘制重叠图形
ctx.fillStyle = 'rgba(0, 0, 255, 0.5)';
ctx.fillRect(50, 50, 200, 100);

常用合成模式:

  • source-over (默认):新图形覆盖原有内容
  • destination-over:新图形在原有内容下方
  • multiply:像素值相乘,产生变暗效果
  • screen:像素值反相相乘再反相,产生变亮效果
  • overlay:结合multiply和screen
  • lighter:颜色值相加

2. 裁剪路径与图像遮罩

// 创建圆形裁剪路径
ctx.beginPath();
ctx.arc(150, 150, 100, 0, Math.PI*2);
ctx.clip();

// 绘制图像(将被裁剪为圆形)
ctx.drawImage(img, 0, 0, 300, 300);

像素级操作

1. 获取与修改像素数据

// 获取ImageData对象
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data; // Uint8ClampedArray

// 遍历并修改像素(灰度化)
for(let i = 0; i < data.length; i += 4) {
  const avg = (data[i] + data[i+1] + data[i+2]) / 3;
  data[i] = avg;     // R
  data[i+1] = avg;   // G
  data[i+2] = avg;   // B
  // data[i+3]保持原Alpha值
}

// 将修改后的数据放回画布
ctx.putImageData(imageData, 0, 0);

2. 常见图像滤镜实现

// 反色滤镜
function invert(imageData) {
  const data = imageData.data;
  for(let i = 0; i < data.length; i += 4) {
    data[i] = 255 - data[i];     // R
    data[i+1] = 255 - data[i+1]; // G
    data[i+2] = 255 - data[i+2]; // B
  }
  return imageData;
}

// 怀旧滤镜
function sepia(imageData) {
  const data = imageData.data;
  for(let i = 0; i < data.length; i += 4) {
    const r = data[i];
    const g = data[i+1];
    const b = data[i+2];
    
    data[i] = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189));
    data[i+1] = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168));
    data[i+2] = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131));
  }
  return imageData;
}

3. 卷积滤镜与边缘检测

// 通用卷积函数
function convolute(imageData, kernel) {
  const width = imageData.width;
  const height = imageData.height;
  const data = imageData.data;
  const newData = new Uint8ClampedArray(data.length);
  
  const kernelSize = Math.sqrt(kernel.length);
  const half = Math.floor(kernelSize / 2);
  
  for(let y = 0; y < height; y++) {
    for(let x = 0; x < width; x++) {
      const px = (y * width + x) * 4;
      let r = 0, g = 0, b = 0;
      
      for(let ky = 0; ky < kernelSize; ky++) {
        for(let kx = 0; kx < kernelSize; kx++) {
          const cpx = ((y + ky - half) * width + (x + kx - half)) * 4;
          
          if(data[cpx] !== undefined) {
            const weight = kernel[ky * kernelSize + kx];
            r += data[cpx] * weight;
            g += data[cpx+1] * weight;
            b += data[cpx+2] * weight;
          }
        }
      }
      
      newData[px] = r;
      newData[px+1] = g;
      newData[px+2] = b;
      newData[px+3] = data[px+3]; // Alpha保持不变
    }
  }
  
  return new ImageData(newData, width, height);
}

// Sobel边缘检测
function sobelEdgeDetection(imageData) {
  const kernelX = [
    -1, 0, 1,
    -2, 0, 2,
    -1, 0, 1
  ];
  
  const kernelY = [
    -1, -2, -1,
     0,  0,  0,
     1,  2,  1
  ];
  
  const gx = convolute(imageData, kernelX);
  const gy = convolute(imageData, kernelY);
  
  const combined = new Uint8ClampedArray(imageData.data.length);
  for(let i = 0; i < imageData.data.length; i += 4) {
    // 计算梯度幅值
    combined[i] = Math.sqrt(
      gx.data[i] * gx.data[i] + 
      gy.data[i] * gy.data[i]
    );
    combined[i+1] = combined[i];
    combined[i+2] = combined[i];
    combined[i+3] = 255; // Alpha
  }
  
  return new ImageData(combined, imageData.width, imageData.height);
}

高级图像处理技术

1. 图像直方图分析

function getImageHistogram(imageData) {
  const histogram = {
    r: new Array(256).fill(0),
    g: new Array(256).fill(0),
    b: new Array(256).fill(0)
  };
  
  const data = imageData.data;
  for(let i = 0; i < data.length; i += 4) {
    histogram.r[data[i]]++;
    histogram.g[data[i+1]]++;
    histogram.b[data[i+2]]++;
  }
  
  return histogram;
}

// 直方图均衡化
function histogramEqualization(imageData) {
  const histogram = getImageHistogram(imageData);
  const data = imageData.data;
  const totalPixels = imageData.width * imageData.height;
  
  // 计算累积分布函数
  const cdf = { r: [], g: [], b: [] };
  for(let i = 0; i < 256; i++) {
    cdf.r[i] = (i === 0 ? 0 : cdf.r[i-1]) + histogram.r[i];
    cdf.g[i] = (i === 0 ? 0 : cdf.g[i-1]) + histogram.g[i];
    cdf.b[i] = (i === 0 ? 0 : cdf.b[i-1]) + histogram.b[i];
  }
  
  // 应用均衡化
  for(let i = 0; i < data.length; i += 4) {
    data[i] = Math.round(255 * cdf.r[data[i]] / totalPixels);     // R
    data[i+1] = Math.round(255 * cdf.g[data[i+1]] / totalPixels); // G
    data[i+2] = Math.round(255 * cdf.b[data[i+2]] / totalPixels); // B
  }
  
  return imageData;
}

2. 图像变形与液化效果

// 图像液化效果
function liquify(imageData, centerX, centerY, radius, strength) {
  const newData = new Uint8ClampedArray(imageData.data.length);
  const width = imageData.width;
  const height = imageData.height;
  
  for(let y = 0; y < height; y++) {
    for(let x = 0; x < width; x++) {
      const dx = x - centerX;
      const dy = y - centerY;
      const distance = Math.sqrt(dx*dx + dy*dy);
      
      if(distance < radius) {
        const amount = 1 - (distance / radius);
        const displacement = amount * amount * strength;
        
        const srcX = x - dx * displacement;
        const srcY = y - dy * displacement;
        
        // 双线性插值
        if(srcX >= 0 && srcX < width-1 && srcY >= 0 && srcY < height-1) {
          const x1 = Math.floor(srcX);
          const x2 = Math.ceil(srcX);
          const y1 = Math.floor(srcY);
          const y2 = Math.ceil(srcY);
          
          const tx = srcX - x1;
          const ty = srcY - y1;
          
          for(let c = 0; c < 4; c++) {
            const idx = (y * width + x) * 4 + c;
            const p1 = imageData.data[(y1 * width + x1) * 4 + c];
            const p2 = imageData.data[(y1 * width + x2) * 4 + c];
            const p3 = imageData.data[(y2 * width + x1) * 4 + c];
            const p4 = imageData.data[(y2 * width + x2) * 4 + c];
            
            newData[idx] = 
              p1 * (1-tx) * (1-ty) + 
              p2 * tx * (1-ty) + 
              p3 * (1-tx) * ty + 
              p4 * tx * ty;
          }
        }
      } else {
        // 复制原始像素
        for(let c = 0; c < 4; c++) {
          newData[(y * width + x) * 4 + c] = imageData.data[(y * width + x) * 4 + c];
        }
      }
    }
  }
  
  return new ImageData(newData, width, height);
}

性能优化与实践建议

  1. 离屏Canvas

    // 创建离屏Canvas处理复杂操作
    const offscreenCanvas = document.createElement('canvas');
    offscreenCanvas.width = 300;
    offscreenCanvas.height = 300;
    const offscreenCtx = offscreenCanvas.getContext('2d');
    
    // 在离屏Canvas上处理图像
    offscreenCtx.drawImage(img, 0, 0);
    const processedImage = offscreenCtx.getImageData(0, 0, 300, 300);
    
    // 将结果绘制到主Canvas
    ctx.putImageData(processedImage, 0, 0);
    
  2. Web Worker处理

    // 主线程
    const worker = new Worker('image-processor.js');
    worker.postMessage({
      imageData: ctx.getImageData(0, 0, canvas.width, canvas.height),
      operation: 'invert'
    });
    
    worker.onmessage = function(e) {
      ctx.putImageData(e.data, 0, 0);
    };
    
    // image-processor.js
    self.onmessage = function(e) {
      const imageData = e.data.imageData;
      // 处理图像...
      self.postMessage(processedImageData, [processedImageData.data.buffer]);
    };
    
  3. 性能敏感操作提示

    • 大尺寸图像处理考虑分块进行
    • 频繁操作使用requestAnimationFrame节流
    • 复杂滤镜考虑使用WebGL实现

实战案例:图片编辑器核心功能

<div class="image-editor">
  <canvas id="editorCanvas"></canvas>
  <div class="controls">
    <button id="btnGrayscale">灰度化</button>
    <button id="btnInvert">反色</button>
    <button id="btnBlur">模糊</button>
    <input type="range" id="rangeBrightness" min="-100" max="100" value="0">
    <label for="rangeBrightness">亮度</label>
  </div>
</div>

<script>
  const editorCanvas = document.getElementById('editorCanvas');
  const editorCtx = editorCanvas.getContext('2d');
  let originalImageData = null;
  
  // 加载图像
  const img = new Image();
  img.onload = function() {
    editorCanvas.width = img.width;
    editorCanvas.height = img.height;
    editorCtx.drawImage(img, 0, 0);
    originalImageData = editorCtx.getImageData(0, 0, editorCanvas.width, editorCanvas.height);
  };
  img.src = 'photo.jpg';
  
  // 应用滤镜
  document.getElementById('btnGrayscale').addEventListener('click', function() {
    const imageData = editorCtx.getImageData(0, 0, editorCanvas.width, editorCanvas.height);
    editorCtx.putImageData(grayscaleFilter(imageData), 0, 0);
  });
  
  // 亮度调整
  document.getElementById('rangeBrightness').addEventListener('input', function() {
    const value = parseInt(this.value);
    const imageData = cloneImageData(originalImageData);
    adjustBrightness(imageData, value);
    editorCtx.putImageData(imageData, 0, 0);
  });
  
  // 辅助函数
  function cloneImageData(imageData) {
    return new ImageData(
      new Uint8ClampedArray(imageData.data),
      imageData.width,
      imageData.height
    );
  }
  
  function adjustBrightness(imageData, value) {
    const data = imageData.data;
    for(let i = 0; i < data.length; i += 4) {
      data[i] = clamp(data[i] + value, 0, 255);     // R
      data[i+1] = clamp(data[i+1] + value, 0, 255); // G
      data[i+2] = clamp(data[i+2] + value, 0, 255); // B
    }
  }
  
  function clamp(value, min, max) {
    return Math.min(Math.max(value, min), max);
  }
</script>

通过本节学习,您已经掌握了Canvas图像处理的核心技术,从基本操作到高级像素处理,这些技能可以应用于照片编辑、计算机视觉、特效生成等多个领域。在实际应用中,请根据需求选择合适的处理方式,并始终关注性能优化。

#前端开发 分享于 2025-05-20

【 内容由 AI 共享,不代表本站观点,请谨慎参考 】