ImageFilter图像滤镜效果

wen java案例 2
<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">ImageFilter · 图像滤镜效果</title>
  <!-- Font Awesome for icons (optional but adds style) -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
  <style>
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }
    body {
      font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      background: #0b0d15;
      min-height: 100vh;
      display: flex;
      justify-content: center;
      align-items: center;
      padding: 1.5rem;
    }
    .card {
      max-width: 880px;
      width: 100%;
      background: #14171f;
      border-radius: 2rem;
      padding: 1.8rem 1.8rem 2rem;
      box-shadow: 0 20px 40px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.03);
      transition: box-shadow 0.2s;
    }
    h1 {
      font-size: 1.8rem;
      font-weight: 500;
      letter-spacing: -0.01em;
      display: flex;
      align-items: center;
      gap: 0.6rem;
      color: #eef2f6;
      margin-bottom: 1.2rem;
      border-bottom: 1px solid #2a2f3a;
      padding-bottom: 0.75rem;
    }
    h1 i {
      color: #5f7cff;
      font-size: 1.6rem;
    }
    .main-layout {
      display: flex;
      flex-direction: row;
      gap: 1.5rem;
      flex-wrap: wrap;
    }
    .image-section {
      flex: 2;
      min-width: 260px;
    }
    .canvas-wrapper {
      background: #0e1017;
      border-radius: 1.2rem;
      padding: 0.5rem;
      box-shadow: inset 0 2px 6px rgba(0,0,0,0.6);
      display: flex;
      justify-content: center;
      align-items: center;
      border: 1px solid #2a2f3a;
    }
    #imageCanvas {
      display: block;
      width: 100%;
      height: auto;
      aspect-ratio: 1/1;
      object-fit: contain;
      background: #0a0c12;
      border-radius: 0.8rem;
      image-rendering: auto;
      transition: filter 0.2s ease;
    }
    .controls-section {
      flex: 1.2;
      min-width: 200px;
      display: flex;
      flex-direction: column;
      gap: 1.5rem;
    }
    .filter-group {
      background: #1c212b;
      border-radius: 1.2rem;
      padding: 1.2rem 1rem 1rem;
      border: 1px solid #2d3340;
    }
    .filter-group h3 {
      font-size: 0.8rem;
      text-transform: uppercase;
      letter-spacing: 0.04em;
      color: #8f99b0;
      margin-bottom: 0.8rem;
      display: flex;
      align-items: center;
      gap: 6px;
    }
    .filter-grid {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 0.5rem;
    }
    .filter-btn {
      background: #262d3a;
      border: none;
      padding: 0.5rem 0.2rem;
      border-radius: 0.8rem;
      color: #d6deee;
      font-size: 0.75rem;
      font-weight: 450;
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 6px;
      cursor: pointer;
      transition: all 0.15s;
      border: 1px solid transparent;
      box-shadow: 0 1px 3px rgba(0,0,0,0.3);
    }
    .filter-btn i {
      font-size: 0.8rem;
      color: #8a9ac0;
    }
    .filter-btn:hover {
      background: #313b4b;
      border-color: #5a6e92;
      color: white;
      transform: scale(0.97);
    }
    .filter-btn.active {
      background: #3f4d6b;
      border-color: #6f8cff;
      box-shadow: 0 0 0 1px #5f7cff;
      color: white;
    }
    .filter-btn.active i {
      color: #b6c8ff;
    }
    .action-row {
      display: flex;
      gap: 0.6rem;
      flex-wrap: wrap;
    }
    .action-btn {
      background: #262d3a;
      border: 1px solid #323a49;
      padding: 0.6rem 0.9rem;
      border-radius: 2rem;
      color: #d6deee;
      font-size: 0.8rem;
      display: flex;
      align-items: center;
      gap: 0.4rem;
      cursor: pointer;
      transition: 0.15s;
      flex: 1 0 auto;
      justify-content: center;
    }
    .action-btn:hover {
      background: #323d52;
      border-color: #5f7cff;
      color: white;
    }
    .action-btn i {
      color: #7f92bb;
    }
    .upload-label {
      background: #2d3648;
      border: 1px dashed #5a6e92;
      padding: 0.6rem 0.9rem;
      border-radius: 2rem;
      color: #d6deee;
      font-size: 0.8rem;
      display: flex;
      align-items: center;
      gap: 0.4rem;
      cursor: pointer;
      transition: 0.15s;
      flex: 1 0 auto;
      justify-content: center;
    }
    .upload-label:hover {
      background: #384461;
      border-color: #6f8cff;
      color: white;
    }
    #fileInput {
      display: none;
    }
    .footer-note {
      margin-top: 1rem;
      color: #5d6a82;
      font-size: 0.7rem;
      text-align: center;
      border-top: 1px solid #232833;
      padding-top: 1rem;
      display: flex;
      justify-content: space-between;
      flex-wrap: wrap;
    }
    @media (max-width: 600px) {
      .card { padding: 1rem; }
      .filter-grid { grid-template-columns: 1fr 1fr; }
    }
  </style>
</head>
<body>
<div class="card">
  <h1>
    <i class="fas fa-image"></i> 
    ImageFilter · 滤镜
  </h1>
  <div class="main-layout">
    <!-- 图像展示区 -->
    <div class="image-section">
      <div class="canvas-wrapper">
        <canvas id="imageCanvas" width="600" height="600"></canvas>
      </div>
      <!-- 辅助操作 -->
      <div class="action-row" style="margin-top: 0.8rem;">
        <label for="fileInput" class="upload-label">
          <i class="fas fa-cloud-upload-alt"></i> 上传图片
        </label>
        <button class="action-btn" id="resetBtn"><i class="fas fa-undo-alt"></i> 重置</button>
        <button class="action-btn" id="downloadBtn"><i class="fas fa-download"></i> 保存</button>
      </div>
    </div>
    <!-- 控制区 -->
    <div class="controls-section">
      <div class="filter-group">
        <h3><i class="fas fa-fill-drip"></i> 经典滤镜</h3>
        <div class="filter-grid" id="filterGrid">
          <button class="filter-btn active" data-filter="original"><i class="fas fa-circle"></i> 原图</button>
          <button class="filter-btn" data-filter="grayscale"><i class="fas fa-adjust"></i> 灰度</button>
          <button class="filter-btn" data-filter="sepia"><i class="fas fa-mug-hot"></i> 复古</button>
          <button class="filter-btn" data-filter="invert"><i class="fas fa-circle-notch"></i> 反色</button>
          <button class="filter-btn" data-filter="vintage"><i class="fas fa-camera-retro"></i> 怀旧</button>
          <button class="filter-btn" data-filter="cool"><i class="fas fa-snowflake"></i> 冷色调</button>
          <button class="filter-btn" data-filter="warm"><i class="fas fa-sun"></i> 暖色调</button>
          <button class="filter-btn" data-filter="vivid"><i class="fas fa-fire"></i> 鲜艳</button>
        </div>
      </div>
      <div style="display: flex; flex-direction: column; gap: 0.6rem;">
        <button class="action-btn" id="randomFilterBtn" style="background: #2a3349;"><i class="fas fa-random"></i> 随机滤镜</button>
      </div>
    </div>
  </div>
  <div class="footer-note">
    <span><i class="far fa-images"></i> 点击滤镜即时预览</span>
    <span><i class="fas fa-mouse-pointer"></i> 支持 JPG / PNG</span>
  </div>
</div>
<script>
  (function() {
    // DOM 元素
    const canvas = document.getElementById('imageCanvas');
    const ctx = canvas.getContext('2d', { willReadFrequently: true });
    const fileInput = document.getElementById('fileInput');
    const filterBtns = document.querySelectorAll('.filter-btn');
    const resetBtn = document.getElementById('resetBtn');
    const downloadBtn = document.getElementById('downloadBtn');
    const randomFilterBtn = document.getElementById('randomFilterBtn');
    // 状态
    let currentFilter = 'original';
    let originalImageData = null;      // 存储原始图像数据 (用于重置)
    let isImageLoaded = false;
    // ---------- 默认图片 (内置渐变/几何,确保有图可看) ----------
    function drawDefaultImage() {
      const w = canvas.width, h = canvas.height;
      ctx.clearRect(0, 0, w, h);
      // 绘制渐变圆形 + 星形 + 条纹 (方便观察滤镜效果)
      const grd = ctx.createRadialGradient(w*0.3, h*0.3, 10, w*0.5, h*0.5, w*0.6);
      grd.addColorStop(0, '#ff7eb3');
      grd.addColorStop(0.3, '#ff758c');
      grd.addColorStop(0.7, '#5f7cff');
      grd.addColorStop(1, '#2c2f4a');
      ctx.fillStyle = grd;
      ctx.fillRect(0, 0, w, h);
      // 几何图形
      ctx.beginPath();
      ctx.arc(w*0.7, h*0.25, 45, 0, Math.PI*2);
      ctx.fillStyle = '#ffdd77';
      ctx.shadowColor = 'rgba(255,200,50,0.5)';
      ctx.shadowBlur = 28;
      ctx.fill();
      ctx.shadowBlur = 0;
      ctx.beginPath();
      ctx.arc(w*0.2, h*0.7, 35, 0, Math.PI*2);
      ctx.fillStyle = '#6dd5a0';
      ctx.shadowBlur = 20;
      ctx.shadowColor = '#3e9c7a';
      ctx.fill();
      ctx.shadowBlur = 0;
      // 条纹
      ctx.fillStyle = '#ffffff30';
      for (let i=0; i<6; i++) {
        ctx.fillRect(i*28+10, h*0.8, 10, 30);
      }
      // 文字
      ctx.font = 'bold 26px "Inter", system-ui, sans-serif';
      ctx.fillStyle = '#f0f4ff';
      ctx.shadowColor = '#00000060';
      ctx.shadowBlur = 12;
      ctx.fillText('滤镜', w*0.1, h*0.45);
      ctx.shadowBlur = 0;
      // 保存为原始数据
      originalImageData = ctx.getImageData(0, 0, w, h);
      isImageLoaded = true;
    }
    // ---------- 加载图片到 canvas ----------
    function loadImageToCanvas(file) {
      if (!file) return;
      const reader = new FileReader();
      reader.onload = function(e) {
        const img = new Image();
        img.onload = function() {
          const w = canvas.width, h = canvas.height;
          ctx.clearRect(0, 0, w, h);
          // 绘制到canvas (保持比例,居中裁剪)
          const imgRatio = img.width / img.height;
          const canvasRatio = w / h;
          let sx, sy, sWidth, sHeight;
          if (imgRatio > canvasRatio) {
            // 图片更宽:裁剪左右
            sHeight = img.height;
            sWidth = img.height * canvasRatio;
            sx = (img.width - sWidth) / 2;
            sy = 0;
          } else {
            sWidth = img.width;
            sHeight = img.width / canvasRatio;
            sx = 0;
            sy = (img.height - sHeight) / 2;
          }
          ctx.drawImage(img, sx, sy, sWidth, sHeight, 0, 0, w, h);
          // 保存原始数据
          originalImageData = ctx.getImageData(0, 0, w, h);
          isImageLoaded = true;
          // 应用当前滤镜 (默认为original)
          applyFilter(currentFilter);
        };
        img.src = e.target.result;
      };
      reader.readAsDataURL(file);
    }
    // ---------- 核心滤镜处理 ----------
    function applyFilter(filterType) {
      if (!originalImageData) {
        // 如果没有原始数据 (极少情况) 重置默认
        drawDefaultImage();
        return;
      }
      // 复制原始数据 (避免累积极端修改)
      const srcData = new Uint8ClampedArray(originalImageData.data);
      const w = canvas.width, h = canvas.height;
      const frame = new ImageData(srcData, w, h);
      const output = ctx.createImageData(w, h);
      const data = output.data;
      // 根据滤镜类型处理像素
      if (filterType === 'original') {
        // 直接复制原始数据
        output.data.set(originalImageData.data);
      } 
      else if (filterType === 'grayscale') {
        for (let i = 0; i < srcData.length; i += 4) {
          const r = srcData[i], g = srcData[i+1], b = srcData[i+2];
          const gray = 0.299 * r + 0.587 * g + 0.114 * b;
          data[i] = gray;
          data[i+1] = gray;
          data[i+2] = gray;
          data[i+3] = srcData[i+3];
        }
      }
      else if (filterType === 'sepia') {
        for (let i = 0; i < srcData.length; i += 4) {
          const r = srcData[i], g = srcData[i+1], b = srcData[i+2];
          let tr = 0.393 * r + 0.769 * g + 0.189 * b;
          let tg = 0.349 * r + 0.686 * g + 0.168 * b;
          let tb = 0.272 * r + 0.534 * g + 0.131 * b;
          data[i] = Math.min(255, tr);
          data[i+1] = Math.min(255, tg);
          data[i+2] = Math.min(255, tb);
          data[i+3] = srcData[i+3];
        }
      }
      else if (filterType === 'invert') {
        for (let i = 0; i < srcData.length; i += 4) {
          data[i] = 255 - srcData[i];
          data[i+1] = 255 - srcData[i+1];
          data[i+2] = 255 - srcData[i+2];
          data[i+3] = srcData[i+3];
        }
      }
      else if (filterType === 'vintage') {
        // 复古暖黄 + 降低饱和度 + 对比略提升
        for (let i = 0; i < srcData.length; i += 4) {
          let r = srcData[i], g = srcData[i+1], b = srcData[i+2];
          // 褪色 + 暖色
          r = r * 0.9 + 25;
          g = g * 0.8 + 15;
          b = b * 0.6 + 10;
          // 提高对比
          r = (r > 128) ? Math.min(255, r*1.1) : r*0.9;
          g = (g > 128) ? Math.min(255, g*1.05) : g*0.9;
          b = (b > 128) ? Math.min(255, b*0.9) : b*0.85;
          data[i] = Math.min(255, Math.max(0, r));
          data[i+1] = Math.min(255, Math.max(0, g));
          data[i+2] = Math.min(255, Math.max(0, b));
          data[i+3] = srcData[i+3];
        }
      }
      else if (filterType === 'cool') {
        // 冷色调:蓝色增强,红色减弱
        for (let i = 0; i < srcData.length; i += 4) {
          let r = srcData[i], g = srcData[i+1], b = srcData[i+2];
          r = r * 0.7;
          g = g * 0.85;
          b = Math.min(255, b * 1.25 + 10);
          data[i] = Math.min(255, r);
          data[i+1] = Math.min(255, g);
          data[i+2] = Math.min(255, b);
          data[i+3] = srcData[i+3];
        }
      }
      else if (filterType === 'warm') {
        // 暖色调:红色增强,蓝色减弱
        for (let i = 0; i < srcData.length; i += 4) {
          let r = srcData[i], g = srcData[i+1], b = srcData[i+2];
          r = Math.min(255, r * 1.2 + 10);
          g = g * 1.02;
          b = b * 0.7;
          data[i] = Math.min(255, r);
          data[i+1] = Math.min(255, g);
          data[i+2] = Math.min(255, b);
          data[i+3] = srcData[i+3];
        }
      }
      else if (filterType === 'vivid') {
        // 鲜艳:饱和度大增,对比增强
        for (let i = 0; i < srcData.length; i += 4) {
          let r = srcData[i], g = srcData[i+1], b = srcData[i+2];
          const avg = (r + g + b) / 3;
          const amount = 1.5;
          r = avg + amount * (r - avg);
          g = avg + amount * (g - avg);
          b = avg + amount * (b - avg);
          // 对比加一点
          r = (r > 128) ? Math.min(255, r * 1.08) : r * 0.92;
          g = (g > 128) ? Math.min(255, g * 1.08) : g * 0.92;
          b = (b > 128) ? Math.min(255, b * 1.08) : b * 0.92;
          data[i] = Math.min(255, Math.max(0, r));
          data[i+1] = Math.min(255, Math.max(0, g));
          data[i+2] = Math.min(255, Math.max(0, b));
          data[i+3] = srcData[i+3];
        }
      }
      // 绘制到canvas
      ctx.putImageData(output, 0, 0);
    }
    // ---------- 切换滤镜 (更新UI + 应用) ----------
    function setFilter(filterType) {
      currentFilter = filterType;
      // 更新按钮状态
      filterBtns.forEach(btn => {
        const f = btn.dataset.filter;
        btn.classList.toggle('active', f === filterType);
      });
      applyFilter(filterType);
    }
    // ---------- 重置为原始图像 ----------
    function resetToOriginal() {
      if (originalImageData) {
        ctx.putImageData(originalImageData, 0, 0);
        setFilter('original');
        // 设置active状态
        filterBtns.forEach(btn => btn.classList.toggle('active', btn.dataset.filter === 'original'));
      } else {
        drawDefaultImage();
        setFilter('original');
      }
    }
    // ---------- 随机滤镜 ----------
    function applyRandomFilter() {
      const filterList = ['grayscale', 'sepia', 'invert', 'vintage', 'cool', 'warm', 'vivid'];
      // 排除 original 让体验更有趣, 但也可加入, 这里保留趣味性
      const randomIdx = Math.floor(Math.random() * filterList.length);
      const randomFilter = filterList[randomIdx];
      setFilter(randomFilter);
    }
    // ---------- 下载处理后的图片 ----------
    function downloadImage() {
      // 使用 canvas 内容
      const link = document.createElement('a');
      link.download = `imagefilter_${currentFilter}.png`;
      link.href = canvas.toDataURL('image/png');
      link.click();
    }
    // ---------- 事件绑定 ----------
    // 文件上传
    fileInput.addEventListener('change', function(e) {
      const file = e.target.files[0];
      if (file) {
        loadImageToCanvas(file);
        // 切换回原图显示 (但loadImageToCanvas内部会应用当前currentFilter,我们要重置active)
        // 在loadImageToCanvas最后会调用applyFilter(currentFilter),但此时originalImageData已经更新。
        // 我们将currentFilter强制设为original 使显示原图
        currentFilter = 'original';
        filterBtns.forEach(btn => btn.classList.toggle('active', btn.dataset.filter === 'original'));
        // 由于loadImageToCanvas里调用了applyFilter(currentFilter),但currentFilter刚改成original,所以显示原图
        // 但注意:loadImageToCanvas中调用applyFilter在img.onload里,此时currentFilter已经变为original
        // 需要保证顺序: 先设置currentFilter = 'original', 再触发loadImageToCanvas
        // 但loadImageToCanvas是异步,在reader.onload里,所以先改currentFilter可行
        // 但因loadImageToCanvas内部调用applyFilter时使用currentFilter,就是original。
        // 但是为了让UI同步,手动再设一下class
        // 不过由于可能存在异步,我们在loadImageToCanvas内部调用的applyFilter会再次设置,没问题。
        // 但是也要考虑原始图片加载完成后应用滤镜,稳妥:手动调用setFilter('original') 但是会覆盖。
        // 简单重新设置一下样式
        setTimeout(() => {
          setFilter('original');
        }, 20);
      }
    });
    // 滤镜按钮点击
    filterBtns.forEach(btn => {
      btn.addEventListener('click', function() {
        const filter = this.dataset.filter;
        setFilter(filter);
      });
    });
    // 重置
    resetBtn.addEventListener('click', resetToOriginal);
    // 下载
    downloadBtn.addEventListener('click', downloadImage);
    // 随机滤镜
    randomFilterBtn.addEventListener('click', applyRandomFilter);
    // ---------- 初始化 (绘制默认图) ----------
    drawDefaultImage();
    setFilter('original');
  })();
</script>
</body>
</html>

滤镜交互与操作说明

您可以通过点击滤镜按钮或上传图片,轻松探索不同视觉效果,其核心交互逻辑清晰直观。

ImageFilter图像滤镜效果

  • 滤镜选择与预览:页面提供了“灰度”、“复古”、“反色”等8种经典滤镜,您只需点击任意滤镜按钮,画布上的图像便会实时应用该效果,方便您快速对比和选择。
  • 图像加载与重置:您可以通过“上传图片”按钮导入本地图片进行处理,若想恢复原始状态,点击“重置”按钮即可清除所有滤镜效果,回到图像最初的样子。
  • 结果保存与随机探索:处理完成后,点击“保存”按钮可将当前滤镜效果的图像下载为PNG文件。“随机滤镜”功能为您提供了一种发现意外视觉效果的有趣方式。

抱歉,评论功能暂时关闭!