前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Web技术】774- 基于canvas完成图片裁剪工具

【Web技术】774- 基于canvas完成图片裁剪工具

作者头像
pingan8787
发布2020-11-19 15:46:19
1.1K0
发布2020-11-19 15:46:19
举报
文章被收录于专栏:前端自习课前端自习课

前言

本文是基于canvas去实现图片裁剪工具。因为canvas代码还是比较长的,尽量写思路,完整代码已放在github上。

canvas模糊问题

这个是写canvas必定接触的问题,网上关于这个的答案也到处都是,就不详细介绍了。

因为canvas不是矢量图,在Retina屏下,浏览器用多个像素点去渲染一个像素,导致canvas最后呈现出模糊问题。

解决方案:

  • 获取window.devicePixelRatio设备的物理像素分辨率与CSS像素分辨率的比值。
  • canvas context有个属性backingStorePixelRatio表示渲染canvas之前会用几个像素来存储画布信息。不过这个只在某些浏览器上有,例如safari
  • 通过设置canvas.width/heightcanvas.style.width/heightcanvas进行缩放处理,比例为devicePixelRatio/backingStorePixelRatio(ratio)。(canvas.width/height表示画布实际大小,而canvas.style.width/height表示在浏览器上渲染结果大小)
  • 最后再通过context.scale(ratio, ratio)canvas进行处理,修复他的呈现效果

如果用typescript的话,会报backingStorePixelRatio不存在错误,加上一个类型定义文件解决。

代码语言:javascript
复制
export const getPixelRatio = (context: CanvasRenderingContext2D) => {
  const backingStore =
    context.backingStorePixelRatio ||
    context.webkitBackingStorePixelRatio ||
    context.mozBackingStorePixelRatio ||
    context.msBackingStorePixelRatio ||
    context.oBackingStorePixelRatio || 1;
  return (window.devicePixelRatio || 1) / backingStore;
};
const calcCanvasSize = () => {
    //...dosth.
    canvasRef.current.style.width = `${canvasWidth}px`;
    canvasRef.current.style.height = `${canvasHeight}px`;
    canvasRef.current.width = canvasWidth * ratio;
    canvasRef.current.height = canvasHeight * ratio;
    ctx.scale(ratio, ratio);
};

给canvas画上img

这个其实就是,通过input获取到本地图片文件,通过window.URL.createObjectURL获取到DOMString,将其作为imgsrc。通过ctx.drawImage将图片绘画到canvas上。

因为对于图片裁剪工具而言,img是应该绘画在最底层,所以需要通过globalCompositeOperation,将其绘画在底层。(globalCompositeOperation表示如何将一个源(新的)图像绘制到目标(已有)的图像上。)

代码语言:javascript
复制
const handleChoiseImg = () => {
    if (createURL) {
      window.URL.revokeObjectURL(createURL);
    };

    createURL = window.URL.createObjectURL(inputRef.current!.files![0]);
    img = new Image();
    img.onload = () => {
      //initImageCanvas(img); 这个函数我是去获取img应该缩小比例和缩小宽高
      // calcCanvasSize(); 这个我是去获取canvas应该呈现的size
      drawImage();  //绘画img
    };
    img.src = createURL;
};

const drawImage = () => {
    // todo sth.
    ctx.save();
    ctx.globalCompositeOperation = 'destination-over';
    // ctx.translate(canvasWidth / 2, canvasHeight / 2);
    // ctx.rotate(Math.PI / 180 * rotate);
    // if (rotate % 180 !== 0) {
    //   [canvasWidth, canvasHeight] = [canvasHeight, canvasWidth];
    // };
    // ctx.translate(-canvasWidth / 2, - canvasHeight / 2);
    ctx.drawImage(
      img,
      (canvasWidth - scaleImgWidth) / 2, (canvasHeight - scaleImgHeight) / 2,
      scaleImgWidth, scaleImgHeight
    );
    // canvasWidth/Height表示canvas的宽高(style),scaleImgWidth/Height表示图片缩放后的宽高
    ctx.restore();
};

蒙层&选中框

蒙层绘制

还是利用globalCompositeOperation将其绘画在已有图像的上方。

代码语言:javascript
复制
const drawCover = () => {
    ctx.save();
    ctx.fillStyle = 'rgba(0,0,0,0.5)';
    ctx.fillRect(0, 0, canvasSize.width, canvasSize.height);
    ctx.globalCompositeOperation = 'source-atop';
    ctx.restore();
};

选中框绘制

其实选中框,就是通过clearRect清除某个区域的蒙层,然后绘画自己的框框style,最后将img绘画在底层。

canvas的动画都是一帧一帧绘画出来的,选中框的拖动过程,其实就是不断去clearRect整个canvas,然后重新走上面的流程,即重新绘画的过程。

代码语言:javascript
复制
const drawSelect = (x: number, y: number, w: number, h: number) => {
    ctx.clearRect(0, 0, canvasSize.width, canvasSize.height);
    //清空整个canvas
    drawCover();
    //绘画蒙层
    ctx.save();
    ctx.clearRect(x, y, w, h);
    //清空选中区域
    ctx.strokeStyle = '#5696f8';
    ctx.strokeRect(x, y, w, h);
    // 画选中框
    // todo sth. 给选中框加一些style
    ctx.restore();
    drawImage();
    // 绘画图片
};

选中框拖拽拉伸&边界处理

选中框拖拽拉伸就是,对mouse事件的处理,在mouseDown的时候,给其一个标志符,在mouseMove进行选中框不断刷新绘制,在mouseUp取消标志符(这个事件可以给外面容器)。

边界处理,就是对mouseMove处理过的选中框位置进行处理判断,若超出边界,则修复他。就是对offsetXoffsetY进行处理,然后在不同方向上去判断如何修改选中框,由于代码量比较大,完整可去github上看。

效果图:

图片旋转处理

canvas旋转中心是以左上角为中心,如果直接调用rotate,那么结果肯定不是我们想要的结果。那么就利用到了translate去移动canvas到中心点,然后再调用rotate旋转,旋转结束后再利用translatecanvas移回他的位置。

唯一的问题就是,弄清rotate后,你再translate平移canvas这个时候的x、y的值。

我这边对于图片裁剪工具的处理是,旋转后,去修改canvaswidth/height&style width/height。这个时候,canvas是旋转了,但是image重新绘画的时候,也要绘画旋转后的图,那么就利用上方讲的方法去旋转绘画。

还有就是别忘记通过save & restore去保存和恢复绘图状态。

代码语言:javascript
复制
const drawImage = () => {
    // todo sth.
    ctx.save();
    ctx.globalCompositeOperation = 'destination-over';
    ctx.translate(canvasWidth / 2, canvasHeight / 2);
    ctx.rotate(Math.PI / 180 * rotate);
    if (rotate % 180 !== 0) {
      [canvasWidth, canvasHeight] = [canvasHeight, canvasWidth];
    };
    ctx.translate(-canvasWidth / 2, - canvasHeight / 2);
    ctx.drawImage(
      img,
      (canvasWidth - scaleImgWidth) / 2, (canvasHeight - scaleImgHeight) / 2,
      scaleImgWidth, scaleImgHeight
    );
    ctx.restore();
};
代码语言:javascript
复制
效果图:

图片缩放处理

scale也是以左上角为缩放中心,然后如果缩放的话也需要save & restore,不然会对后续操作进行影响。

不过,我这里没有采用scale,而是手动修改图片缩放比例,然后重新得到scaleImgWidthscaleImgHeight,在去调用drawImage。因为代码上是将其显示在中心,所以就可以直接修改后调用。

代码语言:javascript
复制
// 修改 scaleImg 得到scaleImgWidth & scaleImgHeight
ctx.drawImage(
  img,
  (canvasWidth - scaleImgWidth) / 2, (canvasHeight - scaleImgHeight) / 2,
  scaleImgWidth, scaleImgHeight
);
代码语言:javascript
复制
效果图:

图片灰度处理

灰度处理就是通过getImageData获取canvasImageData即像素数据,可以对像素数据进行处理。然后再将这个处理后的像素数据,重新通过putImageData放回到canvas上。

像素数据,对于每个像素都有四个方面的信息,分别是RedGreenBlueAlpha

灰度处理公式还是挺多的,我这边就采用(R + 2G + B) >> 2

代码语言:javascript
复制
const imgData = ctx.getImageData(0, 0, canvasSize.width * ratio, canvasSize.height * ratio);
getGrayscaleData(imgData);
ctx.putImageData(imgData, 0, 0);

除此之外,还可以做很多类似的处理,比如,对比色处理,颜色选择器等等。

效果图:

实时显示截选的图片

如果仅仅是去截选canvas目前显示的部分,是不太友好的。应该是对应到原始图片的相应位置,去截选这个位置的图片才是比较友好的。

处理思路:

  • 新创建一个canvas,将img完整绘画在上面,并且完成旋转问题
  • 通过选中框的x y w h的值,还有img width/heightcanvas width/height的值,得到对应原始图片的截选部分的x y
  • 通过getImageData得到ImageData,并判断是否需要灰度处理
  • 然后重新修改上面创建的canvaswidth/height为选中图片部分的putW putH
  • ImageData通过putImageData放入canvas中 通过toBlob获取到blob
  • 最后通过window.URL.createObjectURL获取到DOMString
代码语言:javascript
复制
export const getPhotoData = () => {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
    // todo canvas处理

    ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
    // 处理获得putX putY putW putH
    const imgData = ctx.getImageData(putX, putY, putW, putH);
    if (grayscale) {    //灰度处理
        getGrayscaleData(imgData);
    };
    canvas.width = putW;
    canvas.height = putH;

    ctx.putImageData(imgData, 0, 0);
    return new Promise(res => {
        canvas.toBlob(e => res(e));
    });
};

const cancelChangeSelect = async () => {
    // todo sth.
    dataUrl && (window.URL.revokeObjectURL(dataUrl));
    const blob = await getPhotoData() as Blob;
    const newDataUrl = window.URL.createObjectURL(blob);
    setDataUrl(newDataUrl);
    // todo sth.
};
// 省去不关键代码

效果图:

下载截选图片

这个其实上面已经写的差不多了,获取到了dataUrl后,将其作为a标签的href,下载就完事儿了。(当然还有很多其他下载方式,就不一一列举了)

源自:https://segmentfault.com/a/1190000015288700

声明:文章著作权归作者所有,如有侵权,请联系小编删除。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-11-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端自习课 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • canvas模糊问题
  • 给canvas画上img
  • 蒙层&选中框
    • 蒙层绘制
      • 选中框绘制
        • 选中框拖拽拉伸&边界处理
        • 图片旋转处理
        • 图片缩放处理
        • 图片灰度处理
        • 实时显示截选的图片
        • 下载截选图片
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档