使用worker异步处理canvas

原创
2020/11/12 00:31
阅读数 104

 

原文链接: 使用worker异步处理canvas

http://ps.ahaoboy.com/

将左侧的输入canvas的处理放入canvas中, 如果已经有了一个执行任务, 则直接终止worker, 重新创建一个worker开始执行, 这样可以减少算力消耗

 

之所以不能在执行过程中禁止用户操作, 是因为这个禁止的体验不是很好, 因为如果用户操作了, 表示你正在进行的计算是无用的, 因为用户想看到的其实是最新的操作反馈

 

加入png检测, 如果所有图片都是jpg, 则关闭png导出, 加速图片生成

 

在绘制的时候, 防止worker绘制进行到一半就将worker干掉的行为, 要么在worker中创建新的画布, 只导出转换后的url, 要么保证worker中的绘制要么全部完成, 要么就没开始

 

对于最终图片的旋转, 只需要旋转拼接后的canvas即可, 不用重新走拼接的逻辑, 也就是 拼接后的canvas, 和拼接后旋转的canvas是独立的

 

transferControlToOffscreen  将页面中的canvas控制权转移到worker中时, 只能转换一个worker中

Uncaught (in promise) DOMException: Failed to execute 'transferControlToOffscreen' on 'HTMLCanvasElement': Cannot transfer control from a canvas for more than one time.

 

对于离线的canvas, 不能使用ctx进行draw

DOMException: Failed to execute 'postMessage' on 'Worker': An OffscreenCanvas could not be cloned because it had a rendering context.

 

postmessage中第二个数组参数中必须是buffer类型才能正确传递, imageData.data.buffer

 

采用这种方式引入的worker, 会因为存在core-js的require而执行报错, 需要将输出版本调高, 或者禁止使用core-js, 毕竟这个只需要在最新chrome中运行即可

import workerUrl from "url-loader?limit=0!./worker.js"

 

目前没有找到解决办法, 只能传入imageData, 传出一个blob的图片链接

    this.worker.postMessage( {
      pixels: pixels.data.buffer,
      width: this.canvas.width,
      height: this.canvas.height,
      channels: 4
    }, [pixels.data.buffer] );

 worker 中的canvas输出url

    canvas.convertToBlob().then(blob => {
      console.log("blob", blob)
      const url = URL.createObjectURL(blob)
      self.postMessage({ url })
 
    })

 

 

图片数据缓存, 根据图片信息和旋转信息缓存canvas和imageData, 这样在下次旋转到这个角度的时候不需要重新创建canvas和导出imageData, 注意这东西不要加到vue的data中, 会因为加上了监听导致拖慢性能

 

getImageData 和 putImageData的性能对比, get很慢, 所以需要缓存, 一个3M的jpg都这样了, 合成后的可能更慢

合成:

左边进行操作, 修改了图片的裁剪位置, 需要进行所有图片的拼接

旋转:
对合成完成后的输出进行旋转, 非高频操作

每次合成的计算量都比旋转大, 在主页面保存一个上一次合成的结果, 选择的时候直接旋转这个imageData

 

优化级问题:
对于左边的操作, 合成时不应该被右边的旋转打断, 因为此时合成未结束, 旋转传入的imageData不是最新的

但是旋转可以被合成打断, 因为旋转的数据已经是旧的了

为了实现简单, 规定在合成时不能进行旋转,  在旋转时进行合成的话, 会直接中断旋转

 

 

二级缓存

对图片_角度_四个方向 也就是每张图片参与的合成进行缓存, 考虑到一般的使用场景不多, 也就是大多数时候可以通过刷新来避免OOM

 

全是PNG时才到处PNG, 因为jpg不应该和png混着合成, 会损失透明层, 不过也可以提供一个配置, 这样有需求的话自己选就行

 

精度问题:
对于左边的图片来说, 高度一般不超过500, 如果原始高度是2000, 则每移动一个px, 在原始图片上是4px, 也就是我们不能在像素级别操作原始图片, 不过一般也够用了, 后序考虑点击图片进行放大, 可以进行精细的调整

 

 

现在的处理流程:
1, 上传时获取旋转角度为0的imageData

2, 左边操作时触发的拼接, 先去取需要拼接的imageData, 如果没有, 就用worker生成

3, 传入所有待拼接的imageData, 进行合成

4, 旋转合成后的图像, 并输出最终结果, 和下载链接

 

由于这些操作都是矩阵的拼接操作, 所以后面也可以考虑使用c++提高性能

 

左侧图片宽高处理, 之前对于特别长的图片使用了限制高度的做法, 这种情况下会使得宽度变小, 之前这么做是因为左边很容易很长, 导致在调整左边下方的图片时, 右边的输出看不到, 现在将右边的设置为fix已经解决了这个问题, 所以左边图片只需要限制宽度即可

 

对于快速旋转图片的问题

由于旋转图片需要使用worker生成预览的url, 所以必须在这个任务完成后才能在右侧预览, 之所以不使用css进行图片旋转, 主要是不太熟... 以及为了视图和数据能对上, 并且保证旋转后的数据加到缓存中

 

对于worker中消息的接收和发送必须进行校验, 即发送时带上一个唯一标示, worker接收并处理数据后将该标识和数据一起传回来, 这样可以避免多个任务同时进行时, 返回的时机不对造成的混乱

 

 

优化后已经可以处理超大的文件了, 当然速度不管咋样都是需要等待处理过程的, 加了loading, 在处理时显示, 并且这次不会阻塞主进程, 用户是可以进行其他操作的

 

不同的旋转角度图片的大小也相差很大, 估计是压缩算法的问题, 在横向上重复出现的比较多, 所以就容易压缩吧

 

好像在处理大图片时会崩溃 ... 估计是内存炸了吧, 但也不应该啊,  连续复制多个时已经干掉了worker, 行吧, 加个节流函数试试

 

应该是缓存太多了, 对于大图片直接存imageData还是太重了, 尤其是输出的图片也直接存的imageData, 这个左边没加一张, 相当于加了三遍... 原始的canvas 中的imageData, 待join的imageData 和输出中的imageData, 不过也有点奇怪, 我是直接按照md5缓存的, 这种直接复制的操作应该会命中缓存才对, 为什么内存还在一直增加呢?

 

20张1280 * 720 大概有3g左右... 

 

 

原来的做法就很小了, 基本上300M左右, 相差十倍 -_-||

 

好吧, 确实是内存泄漏的问题... 首先这种方式确实能在性能上有提升, 但是内存占用太大了...

 

行吧, 改回去, 用worker计算, 不进行缓存了 (╯‵□′)╯︵┻━┻

还是在看看能不能解决内存泄漏的问题... 

 

 

 

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部