基于canvas完成图片裁剪工具 您所在的位置:网站首页 qt裁剪图片 基于canvas完成图片裁剪工具

基于canvas完成图片裁剪工具

2023-04-14 19:35| 来源: 网络整理| 查看: 265

前言

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

canvas模糊问题

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

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

解决方案:

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

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

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,将其作为img的src。通过ctx.drawImage将图片绘画到canvas上。

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

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将其绘画在已有图像的上方。

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,然后重新走上面的流程,即重新绘画的过程。

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

效果图:

图片旋转处理

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

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

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

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

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(); }; 复制代码

效果图:

图片缩放处理

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

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

// 修改 scaleImg 得到scaleImgWidth & scaleImgHeight ctx.drawImage( img, (canvasWidth - scaleImgWidth) / 2, (canvasHeight - scaleImgHeight) / 2, scaleImgWidth, scaleImgHeight ); 复制代码

效果图:

图片灰度处理

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

像素数据,对于每个像素都有四个方面的信息,分别是Red,Green,Blue,Alpha。

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

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/height和canvas width/height的值,得到对应原始图片的截选部分的x y 通过getImageData得到ImageData,并判断是否需要灰度处理 然后重新修改上面创建的canvas的width/height为选中图片部分的putW putH 将ImageData通过putImageData放入canvas中 通过toBlob获取到blob 最后通过window.URL.createObjectURL获取到DOMString 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,下载就完事儿了。(当然还有很多其他下载方式,就不一一列举了)

完整代码

已上传github



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有