做了之前几篇文章,感觉自己可以了,突然想做一个浏览器的ps可以吗?然后上网搜了一下,发现有公司已经做出产品了。。。all right,伟大构思慢了一步,出生太晚了。
思路 还是常见的API,canvas的使用。基本上和马赛克思路一样,马赛克的鼠标滑动选中后修改一定区域的颜色,抠图做的是保存选中区域然后绘制到另一个目标canvas上。绑定事件的画笔逻辑、获取位置信息与马赛克逻辑一样。主要替换的逻辑是具体画马赛克的部分,画马赛克变成获取ImageData然后绘制到另外一个canvas【resCtx.putImageData(imgData, left, top)】
实现 创建canvas 首先应该实现创建canvas,从原图上取ImageData出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 class MasaicComponent { constructor (options ) { if (!options.file) { throw new Error ('musted have file in the options as source picture' ) } if (!options.el) { throw new Error ('musted have el in the options as place to work ' ) } this .file = options.file this .el = options.el this .targetEl = options.targetEl this .canvas = null this .resultCanvas = null this .init() } async init ( ) { this .canvas = await this .createCanvas() this .resultCanvas = document .createElement('canvas' ) this .resultCanvas.width = this .canvas.width this .resultCanvas.height = this .canvas.height this .el.innerHTML = '' this .el.appendChild(this .canvas) this .targetEl.appendChild(this .resultCanvas) } async createCanvas ( ) { const fileData = await readFile(this .file) const imgEl = await createImgEl(fileData) const canvas = document .createElement('canvas' ) const ctx = canvas.getContext('2d' ) canvas.position = 'relative' const containerStyle = window .getComputedStyle(this .el) const maxWidth = containerStyle.width.slice(0 , -2 ) const maxHeight = containerStyle.height.slice(0 , -2 ) let width = imgEl.naturalWidth let height = imgEl.naturalHeight if (width > maxWidth) { width = maxWidth height = Math .floor((width / maxWidth) * maxHeight) } else if (height > maxHeight) { width = Math .floor((height * maxWidth) / maxHeight) height = maxHeight } canvas.width = width canvas.height = height ctx.drawImage(imgEl, 0 , 0 , width, height) return canvas }
画笔ui相关 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 class MasaicComponent { constructor (options ) { if (!options.file) { throw new Error ('musted have file in the options as source picture' ) } if (!options.el) { throw new Error ('musted have el in the options as place to work ' ) } this .file = options.file this .el = options.el this .canvas = null this .init() } async init ( ) { this .panEl = document .createElement('div' ) this .panEl.style.width = this .pan.radius + 'px' this .panEl.style.height = this .pan.radius + 'px' this .panEl.style.borderRadius = '50%' this .panEl.style.position = 'absolute' this .panEl.style.backgroundColor = 'yellow' this .panEl.style.transform = 'translate(-50%,-50%)' this .panEl.style.pointerEvents = 'none' this .panEl.style.display = 'none' this .panEl.style.opacity = 0.5 this .panOffest = { left: this .canvas.offsetLeft, top: this .canvas.offsetTop, } this .el.appendChild(this .panEl) this .drawPan = this ._drawPan.bind(this ) this .bindEvent() } async createCanvas ( ) { } setPan (options ) { this .pan = { ...this.pan, ...options, } this .panEl.style.width = this .pan.radius + 'px' this .panEl.style.height = this .pan.radius + 'px' } _drawPan ( ) { this .panEl.style.left = `${this .panOffest.left + this .pan.x} px` this .panEl.style.top = `${this .panOffest.top + this .pan.y} px` this .raf = window .requestAnimationFrame(this .drawPan) } }
绑定事件 点击移动时获取抠图(绘制图片)的位置数据和触发绘制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 class MasaicComponent { constructor (options ) { } async init ( ) { this .bindEvent() } async createCanvas ( ) { } bindEvent ( ) { let isDraw = false this .canvas.addEventListener('mousedown' , (e ) => { isDraw = true }) this .canvas.addEventListener('mousemove' , (e ) => { this .pan.x = e.offsetX this .pan.y = e.offsetY if (isDraw) { this .cutter() } }) this .canvas.addEventListener('mouseup' , (e ) => { isDraw = false }) this .raf = null this .canvas.addEventListener('mouseover' , (e ) => { this .panEl.style.display = 'block' this .raf = window .requestAnimationFrame(this .drawPan) }) this .canvas.addEventListener('mouseout' , (e ) => { this .panEl.style.display = 'none' window .cancelAnimationFrame(this .raf) }) } cutter ( ) { } setPan (options ) { } _drawPan ( ) { } }
抠图的逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class MasaicComponent { constructor (options ) { } async init ( ) { } async createCanvas ( ) { } bindEvent ( ) { } cutter ( ) { const ctx = this .canvas.getContext('2d' ) const resCtx = this .resultCanvas.getContext('2d' ) let left = this .pan.x - Math .floor(this .pan.radius / 2 ) left = left == 0 ? 0 : left let top = this .pan.y - Math .floor(this .pan.radius / 2 ) top = top == 0 ? 0 : top const imgData = ctx.getImageData( left, top, this .pan.radius, this .pan.radius ) resCtx.putImageData(imgData, left, top) } setPan (options ) { } _drawPan ( ) { } }
详细代码 查看demo
系列推荐:
图片压缩
图片裁剪
图片马赛克
参考 ImageData——MDN