技术博客--微信小程序canvas实现图片编辑

我们的这个小程序不仅仅是想给用户提供一个保存和查找的平台,还希望能给用户一个展示自己创意的舞台,因此我们实现了图片的编辑部分。我们对对图片的编辑集成了很多个不同的模块。根据用户的需求,我们主要实现了6个不同的功能,包括添加文字,图片涂鸦,添加滤镜,拼接图片,宣传图片,裁切图片。

虽然这些都是比较基础的图片的编辑能力,但是通过我们的优化,我们能够比较方便直观的修改我们的表情。通过结合多个不同的编辑操作,我们可以比较方便的实现出很多酷炫的效果。我们实现了一个比较有逻辑的平台,用户对于表情的编辑都会累加到上面,因此,用户只需要不断的编辑,效果就会累加,直到用户满意为止。

下面,我们将重点的分享一下几个比较重点的处理方法。

1、前置知识

需要掌握有小程序中canvas的相关的方法使用,例如:

const ctx = wx.createCanvasContext("canvasId");//创建上下文
//scaleX水平缩放,scaleY垂直缩放,skewX水平倾斜,skewY垂直倾斜,translateX水平移动,translateY垂直移动,这个可以做缩放和移动
ctx.transform(scaleX, skewX, skewY, scaleY, translateX, translateY);
ctx.setFillStyle("red");//设置填充颜色为red
ctx.setStrokeStyle("red");//设置线条颜色
ctx.setLineWidth(10);//设置线条为10像素
ctx.fillRect(0,0,100,100);//在(0,0)位置画一个100*100像素的填充矩形
ctx.strokeRect(100,100,100,100);//在(100,100)位置画一个100*100像素的不填充矩形
ctx.moveTo(0, 100);//画线,起点
ctx.lineTo(100, 100);//画线,到哪
ctx.scale(2,2);//放大两倍,控制缩放
ctx.setFontSize(10);//设置字体大小
//在(100,100)的位置写"hello world",最后一个100是文本最大宽度,可以省略
ctx.fillText("hello world",100,100,100);
ctx.stroke();//对当前路径进行描边,说白了就是画线

这只是一些使用的简单例子,更加详细的使用说明可以看官方文档或者自行百度学习。

在设置好之后就可以使用ctx.draw()方法进行绘制,draw()方法可以直接使用,也可以加参数使用,参数情况下有两个参数,第一个是reserve,boolean类型,默认为false,表示是否清空当前画布进行重画,还有一个就是callback回调函数,注意在需要导出图片时最好是在draw内部使用wx.canvastotempfilepath函数作为回调函数来进行指定画布区域的内容的导出,不然很有可能由于画布上还未进行渲染而导出失败。

当然,对图片进行编辑最首要的是先获得图片,我们可以直接通过wx.chooseImage方法来从相册获得想要编辑的图片,参数信息如下:

2、图片旋转

图片旋转的实现较为简单,主要的方式就是使用canvas的旋转的接口rotate。rotate方法具体是以原点为中心旋转canvas的坐标轴,默认情况之下原点是canvas的左上角,因此要是不管不顾的直接使用rotate旋转某个角度就会出现部分图片旋转出了画布的显示范围,因此在使用时要根据我们需要旋转的角度调整好原点的位置和图片画布的大小,才能够完整的显示出整个图片。在我们的小程序中我们固定一次旋转是旋转90度,因此需要使用translate()方法来调整原点的位置到画布的右上角

 cxt.translate(that.data.cWidth,0);  //cWidth是canvas的宽

然后为了保证旋转后的图片完整的出现(长方形图片旋转后无法完整出现在画布中),对图片进行了一定的压缩,代码如下:

cxt.rotate(90 * Math.PI / 180);
cxt.drawImage(that.data.curImage, 0, 0, cHeight, cWidth); //改变绘制的长宽
cxt.draw(false,wx.canvasToTempFilePath({
canvasId: 'edit',
//destWidth: 3*cWidth,
//destHeight: 3*cHeight,
success: function(res) {
console.log("success!更新curImage")
console.log(res.tempFilePath);
that.setData({
curImage: res.tempFilePath
})
}
}));

3、滤镜实现

以下几个滤镜的实现主要用到的方法是wx.canvasGetImageData(OBJECT, this)和wx.canvasPutImageData(OBJECT, this),使用这两个方法来获取图片的数据信息并对图片的像素点进行修改从而实现图片的滤镜效果(利用修改图片每个像素的颜色RGBA来实现滤镜)。

  • 灰度滤镜

    效果图如下:

 wx.canvasGetImageData({
canvasId: 'canvas',
x: 0,
y: 0,
width: 300,
height: 300,
success(result) {
let data = result.data;
for (let i = 0; i < result.width * result.height;i++){
//********************只有这里有区别****************************
let R = data[i * 4 + 0];
let G = data[i * 4 + 1];
let B = data[i * 4 + 2];
let grey = R * 0.3 + G * 0.59 + B * 0.11;
data[i * 4 + 0] = grey;
data[i * 4 + 1] = grey;
data[i * 4 + 2] = grey;
//********************只有这里有区别****************************
}
wx.canvasPutImageData({
canvasId: 'canvasNew',
x: 0,
y: 0,
width: 300,
data: data,
success(res) {
console.log(res)
}
})
}
})
})
  • 黑白滤镜

    效果图如下:

    wx.canvasGetImageData({
    canvasId: 'canvas',
    x: 0,
    y: 0,
    width: 300,
    height: 300,
    success(result) {
    let data = result.data;
    for (let i = 0; i < result.width * result.height;i++){
    //********************只有这里有区别****************************
    let R = data[i * 4 + 0];
    let G = data[i * 4 + 1];
    let B = data[i * 4 + 2];
    let grey = R * 0.3 + G * 0.59 + B * 0.11;
    if (grey > 125){
    grey=255;
    } else {
    grey = 0;
    }
    data[i * 4 + 0] = grey;
    data[i * 4 + 1] = grey;
    data[i * 4 + 2] = grey;
    //********************只有这里有区别****************************
    }
    wx.canvasPutImageData({
    canvasId: 'canvasNew',
    x: 0,
    y: 0,
    width: 300,
    data: data,
    success(res) {
    console.log(res)
    }
    })
    }
    })
    })
  • 反相滤镜

    效果图如下:

    wx.canvasGetImageData({
    canvasId: 'canvas',
    x: 0,
    y: 0,
    width: 300,
    height: 300,
    success(result) {
    let data = result.data;
    for (let i = 0; i < result.width * result.height;i++){
    //********************只有这里有区别****************************
    let R = data[i * 4 + 0];
    let G = data[i * 4 + 1];
    let B = data[i * 4 + 2];
    data[i * 4 + 0] = 255-R;
    data[i * 4 + 1] = 255-G;
    data[i * 4 + 2] = 255-B;
    //********************只有这里有区别****************************
    }
    wx.canvasPutImageData({
    canvasId: 'canvasNew',
    x: 0,
    y: 0,
    width: 300,
    data: data,
    success(res) {
    console.log(res)
    }
    })
    }
    })
    })
  • 马赛克滤镜

    马赛克滤镜的实现方法稍有不同,主要思路是将整个图片分为多个小的像素块(例如10*10的像素块),取小像素块中的某个像素的颜色作为整个像素块的颜色填充,从而实现模糊马赛克的效果。

    效果图如下:

    wx.canvasGetImageData({
    canvasId: 'canvas',
    x: 0,
    y: 0,
    width: 300,
    height: 300,
    success(result) {
    let data = result.data;
    const size = 10;
    const totalnum = size*size;
    for(let i=0;i<result.height;i+=size){
    for(let j=0;j<result.width;j+=size){
    var totalR=0,totalG=0,totalB=0;
    for(let dx=0;dx<size;dx++){
    for(let dy=0;dy<size;dy++){
    var x = i+dx;
    var y = j+dy; var p = x * result.width + y;
    totalR += data[p * 4 + 0];
    totalG += data[p * 4 + 1];
    totalB += data[p * 4 + 2];
    }
    } var p = i * result.width + j;
    var resR = totalR / totalnum;
    var resG = totalG / totalnum;
    var resB = totalB / totalnum;
    for (let dx = 0; dx < size; dx++){
    for (let dy = 0; dy < size; dy++) {
    var x = i + dx;
    var y = j + dy; var p = x * result.width + y;
    data[p * 4 + 0] = resR;
    data[p * 4 + 1] = resG;
    data[p * 4 + 2] = resB;
    }
    }
    }
    }
    wx.canvasPutImageData({
    canvasId: 'canvasNew',
    x: 0,
    y: 0,
    width: 300,
    data: data,
    success(res) {
    console.log(res)
    }
    })
    }
    })
    })

4、图片涂鸦

画一条线的方法需要在canvas上绑定两个函数:touchstart和touchmove

<canvas canvas-id="myCanvas" disable-scroll="true" bindtouchstart="touchStart"
bindtouchmove="touchMove" wx:if="{{hasChoosedImg}}"
style="height: {{cHeight}}px; width: {{cWidth}}px;" />

touchstart记录下路线开始的点的坐标,并设置好线的颜色和宽度,然后在touchmove函数中,随着移动事件,记录下来移动过程中的每个点的坐标,这样子就能够得到一条线的路径,并将其存储下来,然后根据每次移动的两个点画出它们的二次贝塞尔曲线(使用ctx.quadraticCurveTo()方法),最后得到一条平滑的涂鸦出来的线条。

想要撤回时就可以简单的通过将存储路径数据结构清空最后一个再重新画图,就可以实现撤销的操作啦。

  touchStart: function (e) {
// 开始画图,隐藏所有的操作栏
this.setData({
color: false,
width: false,
canvasHeightLen: 0,
prevPosition: [e.touches[0].x, e.touches[0].y],
movePosition: [e.touches[0].x, e.touches[0].y],
});
const { r, g, b } = this.data;
let color = `rgb(${r},${g},${b})`;
let width = this.data.w;
startTouch(e, color, width, this.data.masaic);
}, touchMove: function (e) {
const { r, g, b, prevPosition, movePosition, eraser, w, } = this.data;
// 触摸,绘制中。。
const ctx = wx.createCanvasContext('myCanvas');
// 画笔的颜色
let color = `rgb(${r},${g},${b})`;
let width = w;
if (eraser) {
ctx.clearRect(e.touches[0].x, e.touches[0].y, 30, 30);
ctx.draw();
return;
} const [pX, pY, cX, cY] = [...prevPosition, e.touches[0].x, e.touches[0].y];
const drawPosition = [pX, pY, (cX + pX) / 2, (cY + pY) / 2];
if (this.data.masaic == true) {
ctx.setFillStyle('red')
ctx.fillRect(e.touches[0].x, e.touches[0].y, 10, 10)
ctx.fillRect(e.touches[0].x + 10, e.touches[0].y + 10, 10, 10)
ctx.setFillStyle('pink')
ctx.fillRect(e.touches[0].x + 10, e.touches[0].y, 10, 10)
ctx.fillRect(e.touches[0].x, e.touches[0].y + 10, 10, 10)
ctx.draw(true)
}else {
ctx.setLineWidth(width);
ctx.setStrokeStyle(color); ctx.setLineCap('round');
ctx.setLineJoin('round');
ctx.moveTo(...movePosition);
ctx.quadraticCurveTo(...drawPosition);
ctx.stroke();
ctx.draw(true);
} recordPointsFun(movePosition, drawPosition)
}

粗细的调整很简单,直接调整绘制时候的线的粗细就可以实现,颜色的调整则也还是通过调整像素点的rgb值来进行颜色的变动,颜色的rgb变动代码如下:

<view class="choose-box" wx:if="{{color}}">
<view class="color-box" style="background: {{'rgb(' + r + ', ' + g + ', ' + b + ')'}}; height: {{w}}px; border-radius: {{w/2}}px"></view>
<slider min="0" max="255" step="1" show-value="true" activeColor="red" value="{{r}}" data-color="r" bindchange="changeColor"/>
<slider min="0" max="255" step="1" show-value="true" activeColor="green" value="{{g}}" data-color="g" bindchange="changeColor"/>
<slider min="0" max="255" step="1" show-value="true" activeColor="blue" value="{{b}}" data-color="b" bindchange="changeColor"/>
</view>

再根据得到的rgb值,用如下代码就可以合成颜色:

let color = `rgb(${r},${g},${b})`;

效果图如下:

5、图片保存

在对所选择的图片完成了处理之后,我们还需要对处理后的图片进行保存,就可以直接使用wx.canvasToTempFilePath方法来将指定的画布区域的内容进行保存,所得到的新的图片的临时路径可以在其seccess的回调方法中获得,例如:

wx.canvasToTempFilePath({
canvasId: 'edit',
success: function(res) {
that.setData({
curImage: res.tempFilePath //res.tempFilePath就是该图片的临时路径
})
}
})

注意该方法最好作为draw的回调函数使用才能保证获得图片一定成功。

得到了图片的路径之后,就可以调用wx.saveImageToPhotosAlbum方法来将图片保存到本地啦。

wx.saveImageToPhotosAlbum({
filePath: path,
success (res) {
wx.showToast({
title: '保存成功',
})
}
})

总结一下,在这个板块的开发中,我们学到了很多图片处理的算法,这对于以前对此一无所知的我们来说是一个非常大的提升。同时,在开发完成这个板块的功能之后,我们感受非常有成就感。因为这个部分的一个一个的操作都是我们通过自己的代码实现。

技术博客--微信小程序canvas实现图片编辑的更多相关文章

  1. 技术博客——微信小程序UI的设计与美化

    技术博客--微信小程序UI的设计与美化 在alpha阶段的开发过后,我们的小程序也上线了.看到自己努力之后的成果大家都很开心,但对比已有的表情包小程序,我们的界面还有很大的提升空间,许多的界面都是各个 ...

  2. 技术博客——微信小程序的架构与原理

    技术博客--微信小程序的架构与原理 在两个月的微信小程序开发过程中,我曾走了不少弯路,也曾被很多现在看来十分可笑的问题所困扰.这些弯路与困扰,基本上都是由于当时对小程序的架构理解不够充分,对小程序的原 ...

  3. [技术博客] 微信小程序的formid获取

    微信小程序的formid获取 formId的触发 微信小程序可以通过收集用户的formid,获取formid给用户主动推送微信消息.获取formid有两个途径,一个是触发一次表单提交,或者触发一次支付 ...

  4. [技术博客]微信小程序审核的注意事项及企业版小程序的申请流程

    关于小程序审核及企业版小程序申请的一些问题 微信小程序是一个非常方便的平台.由于微信小程序可以通过微信直接进入,不需要下载,且可使用微信账号直接登录,因此具有巨大的流量优势.但是,也正是因为微信流量巨 ...

  5. [技术博客]微信小程序开发中遇到的两个问题的解决

    IDE介绍 微信web开发者工具 前端语言 微信小程序使用的语言为wxml和wss,使用JSON以及js逻辑进行页面之间的交互.与网页的html和css略有不同,微信小程序在此基础上添加了自己的改进, ...

  6. 腾讯技术分享:微信小程序音视频与WebRTC互通的技术思路和实践

    1.概述 本文来自腾讯视频云终端技术总监rexchang(常青)技术分享,内容分别介绍了微信小程序视音视频和WebRTC的技术特征.差异等,并针对两者的技术差异分享和总结了微信小程序视音视频和WebR ...

  7. 腾讯技术分享:微信小程序音视频技术背后的故事

    1.引言 微信小程序自2017年1月9日正式对外公布以来,越来越受到关注和重视,小程序上的各种技术体验也越来越丰富.而音视频作为高速移动网络时代下增长最快的应用形式之一,在微信小程序中也当然不能错过. ...

  8. 微信小程序--canvas画布实现图片的编辑

    技术:微信小程序   概述 上传图片,编辑图片大小,添加文字,改变文字颜色等 详细 代码下载:http://www.demodashi.com/demo/14789.html 概述 微信小程序--ca ...

  9. 原创:WeZRender:微信小程序Canvas增强组件

    WeZRender是一个微信小程序Canvas增强组件,基于HTML5 Canvas类库ZRender. 使用 WXML: <canvas style="width: 375px; h ...

随机推荐

  1. Robot Framework(10)- 使用资源文件

    如果你还想从头学起Robot Framework,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1770899.html 啥是资源文件 资 ...

  2. adb 常用命令大全(7)- 其他实用功能

    屏幕截图 adb exec-out screencap -p > sc.pn 截图保存到电脑执行该命令的目录下 如果指定文件名以 .png 结尾时可以省略 -p 参数 注意 如果 adb 版本较 ...

  3. Intel® QAT加速卡之逻辑实例

    Intel QAT加速卡逻辑实例 1. QAT相关的名词组织关系 在本手册中描述的平台上,处理器可以连接到一个或多个英特尔通信芯片组8925至8955系列(PCH)设备. 从软件角度来看,每个PCH设 ...

  4. SQL Server 使用bcp进行大数据量导出导入

    转载:http://www.cnblogs.com/gaizai/archive/2010/04/17/1714389.html SQL Server的导出导入方式有: 在SQL Server中提供了 ...

  5. tomcat快速发布备份脚本

    一.说明 我们每次在tomcat中发布新war包,总是要经历[备份-停机-上传-启动]这几个部分,其中上传的环节和网速有极大相关性,要是网速很慢,那么整个发布的时间就会很长. 如果我们不借助于自动化发 ...

  6. Java基础学习3

    Java语法学习3 基本运算符 关系运算符返回结果为布尔类型 %:取余 模运算 +.-.*./.% :二元运算符 两个变量间的运算 ++.-- 一元运算符 package Study; public ...

  7. python 打字小游戏

    最近随便用python的pygame编了这个打字小游戏 只要有字母调到窗口底部就结束 上代码: import pygame.freetype import sys import random pyga ...

  8. PHP的内置WEB服务器

    在很多时候,我们需要简单的运行一个小 demo 来验证一些代码或者轮子是否可用,是否可以运行起来,但是去配 nginx 或者 apache 都很麻烦,其实,PHP CLI 已经提供了一个简单的测试服务 ...

  9. 写SQL的套路

    定义问题 转化问题 如要解决的问题是:查出每门课程成绩都大于80分学生的姓名,可以转化为:只要学生最小分数的课程大于80分,就是所有课程成绩都大于80分. 查询同名同姓学生名单并统计同名人数--> ...

  10. 『GoLang』错误处理

    Go 没有像 Java 和 .NET 那样的 try/catch 异常机制:不能执行抛异常操作.但是有一套 defer-panic-and-recover 机制. Go 的设计者觉得 try/catc ...