使用canvas对图片进行点位截取的hooks

858 阅读3分钟

背景

我在忙碌且枯燥且乏味日子里,突然有那么一天,产品经理提了这么一个需求:由于算法引擎想提升效率,本来由他们截取的图片现在由前端去截取。我突然感觉,我的摸鱼日子要被打破了。终于有了一个有意思的需求了。
我盯着需求盯了5分钟,陷入了沉思。又沉思了3分钟。。。我不会。

src=http___wx4.sinaimg.cn_large_9e31678cgy1fkel7ly37bj20k00f0q3c.jpg&refer=http___wx4.sinaimg.jpeg

可是产品经理提的,总不能直接说不做吧。这也太有失我前端five形象了!

u=2753860582,1141882953&fm=26&fmt=auto&gp=0.webp

我又陷入了沉思。。突然,我沙包一样的脑袋里突然蹦出了一个词:canvas
对啊,canva!然后我就去 MDN 里搜索了相关的API,确实可行。那就抄起我的24k纯金键盘一把梭吧。

初步实现

通过查阅MDN文档,canvas首先需要一个画布空间

<canvas id="canvas"></canvas>

接着javascript部分需要获取这个元素的引用,并且获得元素的context,图像将会在此完成渲染。

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

ok,到这一步,canvas初始化的步骤已经完成。但是我们处理的,是图像呀。所以,我们需要初识化image对象。

// 初始化image对象
const img = new Image();
// 本地环境下,带有域名的图片地址存在跨域,设置crossOrigin,允许跨域
img.setAttribute("crossOrigin", "Anonymous");
img.src = url;
img.onload = function () {
// TODO
};

现在,所有的步骤都以初识化完成。接下来就是把两个步骤结合起来。

// 初始化image对象
const img = new Image();
// 本地环境下,带有域名的图片地址存在跨域,设置crossOrigin,允许跨域
img.setAttribute("crossOrigin", "Anonymous");
img.src = url;
img.onload = function () {
    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d");
    if (!ctx) {
      console.error("cannot init screenshot canvas");
      return;
    }
    // TODO
};

这里之所以不采用创建HTML的结构的原因是我们这个是react呀,怎么能用这么low的方式呢

基本完成

现在我们来看一下canvas怎么实现截图功能。canvas提供了两个API:

  • drawImage()
  • toDataURL drawImage用于绘制图像,toDataURL用于导出图像。具体怎么用请移步 MDN 查看。

有人可能会问,canvas实现裁剪有专门的API clip,为什么不用呢?

u=3707218270,2471847270&fm=26&fmt=auto&gp=0.webp

首先,clip 这个方法确实能裁剪图片,但是需要创建裁剪路径。当然,对于复杂图形,比如五角星这样图形,用clip这个方式会更好,但是我们这里只处理矩形这一种情况。所以如果想更深入的改进这个方法,可以自行研究补充。
现在我们根据点位,来赋给canvas所要裁剪的大小。点位如图所示。

Snipaste_2021-08-30_23-26-00_gaitubao_776x459.png

红色标注出来的即为点位。接下来,就是计算drawImage所需要的所有参数了

let dx = 0;
let dy = 0;
let width = 0;
let height = 0;

dx = _.get(points, "[0][0]", 0);
dy = _.get(points, "[0][1]", 0);
const ex = _.get(points, "[1][0]", 0);
const ey = _.get(points, "[1][1]", 0);
width = ex - dx;
height = ey - dy;

到此为止,所有的工作都完成啦,奖励自己一颗小星星 ✨

究极封装

我们既然用react,当然得把这些代码react化啦,hooks当之无愧!以下,就是全部代码:

import { useState } from "react";

import _ from "lodash";

export const useCutImg = (url: string | undefined, points: number[][]) => {
  const [result, setResult] = useState<string | undefined>();
  if (points.length > 3 || points.length < 1) {
    console.error("points's length should be 2");
    return [""];
  }
  if (!url) {
    console.error("can not find image url");
    return [""];
  }
  let dx = 0;
  let dy = 0;
  let width = 0;
  let height = 0;

  dx = _.get(points, "[0][0]", 0);
  dy = _.get(points, "[0][1]", 0);
  const ex = _.get(points, "[1][0]", 0);
  const ey = _.get(points, "[1][1]", 0);
  width = ex - dx;
  height = ey - dy;

  const img = new Image();
  // 本地环境下,带有域名的图片地址存在跨域,设置crossOrigin,允许跨域
  img.setAttribute("crossOrigin", "Anonymous");
  img.src = url;
  img.onload = function () {
    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d");
    if (!ctx) {
      console.error("cannot init screenshot canvas");
      return;
    }
    canvas.width = width;
    canvas.height = height;
    ctx.drawImage(img, dx, dy, width, height, 0, 0, width, height);
    const data = canvas.toDataURL("image/png");
    setResult(data);
  };
  return [result];
};

最后

封装完成后,用法就很简单了,引入这个hooks,然后

const [result] = useCutImg('xxx.img',[[200,100],[1000,800]])

谢谢大家看到这里!觉得有用的,可以点赞收藏哟,笔芯~