照片裁剪✂✂✂-基础裁剪、裁剪器裁剪、固定规格裁剪、不规则裁剪

779 阅读7分钟

写在开头

哈喽吖,各位倔友们好吖!😋

2024年7月11日晚,小编又多一个外甥女,龙宝宝🐲,真是令人开心啊,现在家里的小孩子加起来是三个吞金兽了😁,真是热闹,就是稍微有点忧愁了呀,过年红包又要+1了。😂

当然,不仅是家人,身边同届的同学、朋友也...😂

image.png

想想时间过得真快啊,东流逝水,叶落纷纷,岁月不待人,看着身边人一个一个结婚,生小孩......

再回到自己,Em...🙈。

又想到了这张图:

c60c55796c39c8c1a0236ca82b3abb7.png

多少是带点伤感的。😔

不过呢,今,天,已,经,是,周,五,了!快乐起来吧!美好的周末就要来了!

我要去出去玩、去耍、然后躺平、食好吃的,缓解缓解内心,嘿嘿。

077FF9AC.gif

回到正题哈,这次要分享是照片裁剪的内容,算是上一篇的续集吧,具体效果如下,请诸君按需食用。

基础裁剪

照片裁剪一个很常见的功能了,今天咱们来手撸一个耍耍看。👻

当前,照片裁剪在很大程度上已经转向基于 Canvas 来实现,这样做有几个好处:

  • 性能:Canvas 能通过GPU加速进行图像操作,这通常比CPU处理更快。
  • 灵活性:Canvas API 提供了丰富的绘图和图像处理能力,可以轻松实现复杂的裁剪、旋转、缩放等操作。
  • 易于集成、无需依赖:Canvas 可以轻易无缝集成到各种库/框架(如 React/Vue 等)中,不需要安装额外的插件。
  • ...

咱们来看一个最简单的裁剪案例:

<!DOCTYPE html>
<html>
<head>
  <style>
    canvas { border: 1px solid #ccc;  margin-top: 10px; }
  </style>
</head>
<body>
  <input id="file" type="file">
  <canvas id="canvas"></canvas>
  <canvas id="canvasCropping"></canvas>
  <script>
    document.addEventListener('DOMContentLoaded', () => {
      const file = document.getElementById('file');
      const canvas = document.getElementById('canvas');
      const canvasCtx = canvas.getContext('2d');
      const canvasCropping = document.getElementById('canvasCropping');
      const canvasCroppingCtx = canvasCropping.getContext('2d');
      
      const image = new Image();

      file.addEventListener('change', (e) => {
        const file = e.target.files[0];
        image.src = URL.createObjectURL(file);
        image.onload = () => {
          // 将画布大小调整成照片大小
          const { width, height } = image;
          canvas.width = width;
          canvas.height = height;
          // 将图片绘制到画布中
          canvasCtx.drawImage(image, 0, 0, width, height);
          
          // 裁剪出坐标为(0, 0),width与height都为100的照片
          const croppingImageData = canvasCtx.getImageData(0, 0, 100, 100);
          // 将裁剪到的照片绘制到新的画布中预览
          canvasCroppingCtx.putImageData(croppingImageData, 0, 0)
        };
      })
    });
  </script>
</body>
</html>

很简单,咱们上传了一个照片,将它完整的绘制在左边的 Canvas 中,然后进行裁剪,将裁剪得到的照片绘制到了右边的 Canvas 中。 🙈

效果如下:

2024712-1.gif

你可以直接在右边的 Canvas 上鼠标右键将照片下载下来,也可以通过代码,去调用 CanvastoDataURL 方法,将裁剪的照片下载下来了,这样就能得到你想要的裁剪照片了。

从上面代码中,可以看到咱们是调用了两个 Canvas 的相关API来完成裁剪的,如下:

ctx.getImageData(x, y, width, height):用于获取画布中的某个区域内容,能得到一个 ImageData 对象。

image.png

ctx.putImageData(imagedata, x, y):能将 ImageData 对象绘制到画布中。

裁剪器裁剪

平常的照片裁剪功能一般都是有一个裁剪器,它能帮助我们裁剪需要的部分,正好,咱们在上一篇文章 🔪🔪🔪图片裁剪器✂✂✂ 已经完成了裁剪器这个功能。如下:

2024711-2.gif

这里咱们就直接拿过来用用,裁剪器源码可以看这里。传送门 👈👈👈

来看看这个裁剪器要如何与具体裁剪功能结合起来:

<button id="btn">裁剪</button>
<canvas id="canvasCropping"></canvas>
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
  // currentDimention: top/right/bottom/left
  if (currentDimention.length > 0) {
    // 图片的宽度与高度
    const { width: imageWidth, height: imageHeight } = bgImage.getBoundingClientRect();
    // 创建一个canvas用于裁剪操作,其实也可以将bgImage本身变成一个canvas,直接在上面操作就行
    const canvas = document.createElement('canvas');
    const canvasCtx = canvas.getContext('2d');
    canvas.width = imageWidth;
    canvas.height = imageHeight;
    // 绘制照片
    canvasCtx.drawImage(bgImage, 0, 0, imageWidth, imageHeight);
    // 裁剪照片
    const x = currentDimention[LEFT];
    const y = currentDimention[TOP];
    const width = imageWidth - currentDimention[LEFT] - currentDimention[RIGHT];
    const height = imageHeight - currentDimention[TOP] - currentDimention[BOTTOM];
    const croppingImageData = canvasCtx.getImageData(x, y, width, height);
    // 通过canvas展示裁剪的照片
    const canvasCropping = document.getElementById('canvasCropping');
    const canvasCroppingCtx = canvasCropping.getContext('2d');
    canvasCropping.width = croppingImageData.width;
    canvasCropping.height = croppingImageData.height;
    // 清空一下画布
    canvasCroppingCtx.clearRect(0, 0, croppingImageData.width, croppingImageData.height);
    //将裁剪到的照片绘制到新的画布中预览
    canvasCroppingCtx.putImageData(croppingImageData, 0, 0);
  }
})

上面仅是裁剪功能的代码,关于 currentDimention/bgImage/LEFT... 等变量是什么,最好还是要先去看看上一篇文章 🔪🔪🔪图片裁剪器✂✂✂ 的内容。

其实,咱们主要目的就是为了得到 ctx.getImageData(x, y, width, height) API所需的参数信息,裁剪器的目的也是如此。

还有,由于是不断往 Canvas 上绘制新裁剪照片,每次绘制之前记得清空一下画布哦😋:ctx.clearRect(x, y, width, height)

效果如下:

2024712-2.gif

固定规格裁剪

固定规格裁剪,这应该算是裁剪器那边的小功能,等于就是提前固定了裁剪器的一些规格尺寸。

效果如下:

2024712-3.gif

实现也很简单:

<button id="btn-small"></button>
<button id="btn-middel"></button>
<button id="btn-big"></button>
function createCropper() {
  mask.style.display = "block";
  cropper.style.display = "block";
  setDimention(initDimention);
  // 增加这行
  currentDimention = initDimention;
  // ...
}
btnSmall.addEventListener("click", () => {
  currentDimention = [150, 150, 150, 150];
  setDimention(currentDimention);
})
btnMiddel.addEventListener("click", () => {
  currentDimention = [100, 100, 100, 100];
  setDimention(currentDimention);
})
btnBig.addEventListener("click", () => {
  currentDimention = [10, 10, 10, 10];
  setDimention(currentDimention);
})

这部分其实是属于一个铺垫?抛砖引玉?🤡 (反正就是为了继续引出下文内容吧。)

不规则裁剪

是不是有点意犹未尽?咋固定规格全是矩形?能否搞个圆形?三角形?或者其他形状?

08B6B4AB.png

当然是可以的,接下来就要来聊聊不规则的裁剪。

要实现不规则裁剪,会用到很多 Canvas 相关的API的,其中最关键的就是 ctx.clip() ,它是裁剪的关键。

先来看看实现效果:

2024712-4.gif

具体实现:

<!DOCTYPE html>
<html>
<body>
  <input id="file" type="file" />
  <canvas id="canvas"></canvas>
  <script>
    document.addEventListener('DOMContentLoaded', () => {
      const file = document.getElementById("file");
      const canvas = document.getElementById("canvas");
      const ctx = canvas.getContext("2d");
      const width = 600;
      const height = 400;
      const image = new Image();
      // 照片上传后绘制到canvas中展示
      file.addEventListener("change", (e) => {
        const target = e.target.files[0];
        const imgURL = URL.createObjectURL(target);
        image.src = imgURL;
        image.onload = () => {
          canvas.width = width;
          canvas.height = height;
          ctx.drawImage(image, 0, 0, width, height);
        }
      });

      // 裁剪路径集合
      const pathMap = [];
      // 裁剪中
      let cropping = false;
      
      // 监听canvas的鼠标按下事件
      canvas.addEventListener('mousedown', e => {
        cropping = true;
        pathMap.push({
          // 点和线的坐标不能一样
          offsetXPoint: e.offsetX,
          offsetYPoint: e.offsetY,
          offsetY: e.offsetY,
          offsetY: e.offsetY,
        });
        draw();
      });
      // 监听canvas的鼠标移动事件
      canvas.addEventListener('mousemove', (e) => {
        if (cropping) {
          // 拿最后一个点,根据鼠标移动不断改变偏移量
          pathMap[pathMap.length - 1].offsetX = e.offsetX;
          pathMap[pathMap.length - 1].offsetY = e.offsetY;
          // 标记是有效的点
          pathMap[pathMap.length - 1].effectived = true;
        }
      });
      // 监听canvas的鼠标双击事件
      canvas.addEventListener('dblclick', (e) => {
        cropping = false;
        if (!pathMap[pathMap.length - 1].effectived) {
          // 删除双击带来的多余点
          pathMap.pop();
        }
        // 裁剪照片
        setTimeout(async () => {
          // 清空原画布
          ctx.clearRect(0, 0, width, height);
          // 开始绘制
          ctx.beginPath();
          pathMap.forEach((item, index) => {
            if (index === 0) {
              ctx.moveTo(item.offsetXPoint, item.offsetYPoint);
            }
            ctx.lineTo(item.offsetX, item.offsetY);
          });
          // 裁剪目标区域
          ctx.clip();
          // 在裁剪的区域内绘制图片
          ctx.drawImage(image, 0, 0, width, height);
        }, 200);
      });

      /** @name 绘制 **/
      function draw() {
        // 先清空画布
        ctx.clearRect(0, 0, width, height);
        // 绘制照片
        ctx.drawImage(image, 0, 0, width, height);
        // 开始绘制
        ctx.beginPath();
        // 设置画笔的颜色
        ctx.strokeStyle = 'yellow';
        pathMap.forEach((item, index) => {
          if (index === 0) {
            // 端点位置
            ctx.moveTo(item.offsetXPoint, item.offsetYPoint);
          }
          ctx.lineTo(item.offsetX, item.offsetY);
        })
        // 将路径绘制到画布上
        ctx.stroke();
        // 通过requestAnimationFrame去不断执行绘制
        cropping && requestAnimationFrame(draw);
      }
    });
  </script>
</body>
</html>

案例代码不多,但需要你足够了解 Canvas 的知识才行,特别得多关注一下 ctx.moveToctx.lineTo

这两个API可以瞅一瞅这个图:

image.png

应该足够说明两者的作用了。

当然,这还没完😐,不是说要裁剪个圆形吗?

其实当你理解上面的案例后,这个问题就是手到擒来了,比如:

canvas.addEventListener('mousedown', e => {
  // 圆形
  ctx.clearRect(0, 0, width, height);
  ctx.beginPath();
  ctx.arc(width/2, height/2, 100, 0, Math.PI * 2);
  ctx.clip();
  ctx.drawImage(image, 0, 0, width, height)
});

咱们稍微修改一下 mousedown 事件的逻辑,效果如下:

2024712-5.gif

不过,看似完成了不规则裁剪的功能了,但实际好像又没全部完成吧?🙊

没错😪,如果...我说如果,不规则裁剪能像上面的矩形裁剪器一样,可以调整大小或移动的话,那是不是就很妙了?

确实,但...还是比较麻烦,在网上看了一圈,仔细想了一下,Em...挺难实现的。。。😂

当前,就先这样子吧,俺再想想。😋

这里看到一篇不错的文章,如果想继续扩展下去,应该能所有帮助,可以瞅瞅。传送门





至此,本篇文章就写完啦,撒花撒花。

image.png

希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
老样子,点赞+评论=你会了,收藏=你精通了。