1、背景

今年国庆 渐变头像着实火了一把,看到微信里面的好友,很多都换上了新颜。

 如上图所示,一个渐变的头像。
作为码农,看到上面的效果,首先会想到这个是怎么实现的?我可不可以?
于是就有了今天这篇文章,记录一下自己动手做一个头像制作小工具。
话不多说,上效果图。

制作国庆头像的效果图:

操作步骤:

1、获取头像。
2、选择热门图片。
3、保存头像。

制作圣诞头像效果图:

操作步骤:

1、获取当前微信的用户头像。
2、选择喜欢的圣诞帽、调整圣诞帽角度。
3、保存图片。 
好了上面就是最终实现的效果,感兴趣的小伙伴可以扫描下面的小程序二维码进行体验:

2、实现原理

先介绍下实现原理,可以看到我们最终生成的头像是由两部分组成。
微信头像 + 选择的圣诞帽
在小程序中要实现这种将两张图片或者多张图片叠加到一起生成一张图片,用到了
Canvas组件。
官方文档链接如下:
https://developers.weixin.qq....
在我们这里主要用到了两个API:

1) drawImage(imageResource, dx, dy, dWidth, dHeight)

将图片绘制到画布上
参数介绍:
string imageResource
所要绘制的图片资源(网络图片要通过 getImageInfo / downloadFile 先下载)
number dx
imageResource的左上角在目标 canvas 上 x 轴的位置
number dy
imageResource的左上角在目标 canvas 上 y 轴的位置
number dWidth
在目标画布上绘制imageResource的宽度,允许对绘制的imageResource进行缩放
number dHeight
在目标画布上绘制imageResource的高度,允许对绘制的imageResource进行缩放

2) draw(boolean reserve, function callback)

将之前在绘图上下文中的描述(路径、变形、样式)画到 canvas 中。
参数
boolean reserve
本次绘制是否接着上一次绘制。即 reserve 参数为 false,则在本次调用绘制之前 native 层会先清空画布再继续绘制;若 reserve 参数为 true,则保留当前画布上的内容,本次调用 drawCanvas 绘制的内容覆盖在上面,默认 false。
function callback
绘制完成后执行的回调函数
掌握了上面的两个api就可以实现将两张图片叠加到一起了。

3、代码实现

工程结构:

images: 存放图片资源;
pages:主要的实现;
guoqing 文件夹实现国庆头像制作;
chrismas 文件夹实现圣诞头像制作;

1) 国庆头像制作代码

布局文件:guoqing.wxml


<!--pages/guoqing/guoqing.wxml-->
<!-- 画布大小按需定制 这里我按照背景图的尺寸定的  -->
<view style="margin-top:60px;margin-bottom:40px">
  <image src="../../images/20190906-logo2.png" height="50px" class="header"></image>
</view>

<view class="hot-biz" style="width: 90%;margin: 0 auto;border-radius: 10px;margin-bottom:15px;">
  <view class="hot-top">
    <view class="tx">
      热门
    </view>
  </view>

  <view class="hot-item-list">
    <scroll-view scroll-x>
      <view class="hot-biz-list" >
        <view class="item" wx:for="{{list}}" wx:key="id">
          <image bindtap='selectImg' data-id='{{item}}' data-src='../../images/hat{{item}}.png' src="../../images/hat{{item}}.png" mode='aspectFill'></image>
        </view>
      </view>
    </scroll-view>
  </view>
</view>

<view class="canvas-view">
<view style="width:150px;margin-left:20px;border: 2px solid #ffffff;">
  <canvas canvas-id="shareImg" style="width:150px;"></canvas>
</view>

<!-- 预览区域  -->
<view class='canvas-view-right'>
    <button bindtap="getUserProfile" class="btn1">获取头像</button>
    <button bindtap="save" class="btn1" disabled="{{!hasUserInfo}}">保存头像</button>
    <button open-type="share" bindtap='handleShare' class="btn1">分享好友</button>
</view>

</view>

样式文件: guoqing.wxss

/* pages/guoqing/guoqing.wxss */
page{
   background: #FF5651;
   display: flex;
   flex-direction: column;
   align-items: center;
   align-content: center;
  }

  .header{
    width: 315px!important;
    height: 125px!important;
  }

  .canvas-view{
    width: 100%;
    align-content: center;
    align-items: center;
    text-align: center;
    display: flex;
    flex-direction: row;
    justify-content: space-between;
  }

  .canvas-view-right{
    display: flex;
    flex-direction: column;
    margin: 10px;
  }

  .btn1{
    background-color:#EB9A41;
    border-radius: 50px;
    color:#ffffff;
    width: 130px!important;
    height: 40px!important;
    font-size: 32rpx;
    height: 50rpx;
    display: flex;
    justify-content: center;
    margin-top: 10px;
  }

  /* list公共 */
  .hot-biz{
    margin-top: 10px;
    background: #fff;
  }
  .hot-biz .tx{
    font-size: 15px;
    margin-left: 10px;
    padding: 9px 0;
    font-weight: 700;
    color: #FF5651;
  }
  .hot-top{
    display: flex;
  }

  /* 热门壁纸 */
  .hot-item-list{
    margin: 0 auto;
    width: 94%;
    margin-bottom: 20px;
    align-items: center;
  }
  .hot-biz-list {
    display: flex;
    justify-content: space-between;
    height: 100%;
    align-items: center;
    /* flex-wrap: wrap; */
  }
  .hot-biz-list .item {
    width: 50px;
    flex-direction: column;
    align-items: center;
    height: 50px;
    padding-right: 8px;
  }
  .hot-biz-list image {
    width: 50px;
    height: 50px;
    border-radius:5px;
    margin: 0 auto;
    display: block;
    border:1px solid rgb(235, 235, 245);
  }

逻辑文件: guoqing.js

// pages/guoqing/guoqing.js
const ctx = wx.createCanvasContext('shareImg');
const app = getApp();

Page({

    /**
     * 页面的初始数据
     */
    data: {
      prurl: '',

      defaultImg: 0,

      userInfo: {},
      hasUserInfo: false,

      list: [
        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
      ]
    },

    selectImg: function(e){
      var current = e.target.dataset.id;
      console.log(current);
      this.setData({
        defaultImg: current,
        prurl: ''
      });
      console.log("this:",this.data.userInfo);
      if(this.data.userInfo.avatarUrl){
        this.drawImg(this.data.userInfo.avatarUrl);
      } else {
        this.initCanvas(this.data.defaultImg);
      }
    },

    // 初始化
    initCanvas(index){
      let that = this;
      //主要就是计算好各个图文的位置
      let num = 150;
      // ctx.drawImage(res[0].path, 0, 0, num, num)
      ctx.drawImage(`../../images/hat${index}.png`, 0, 0, num, num)
      ctx.stroke()
      ctx.draw(false, () => {
        wx.canvasToTempFilePath({
          x: 0,
          y: 0,
          width: num,
          height: num,
          destWidth: 960,
          destHeight: 960,
          canvasId: 'shareImg',
          success: function(res) {
            that.setData({
              prurl: res.tempFilePath
            })
            // wx.hideLoading()
          },
          fail: function(res) {
            wx.hideLoading()
          }
        })
      })
    },


    // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认
    // 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
    getUserProfile(e) {
      let that = this;
      if(!that.data.userInfo.avatarUrl){
        console.log('-- 1 --');
        wx.getUserProfile({
          desc: '仅用于生成头像使用', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
          success: (res) => {
            //获取高清用户头像
            var url = res.userInfo.avatarUrl;
            while (!isNaN(parseInt(url.substring(url.length - 1, url.length)))) {
              url = url.substring(0, url.length - 1)
            }
            url = url.substring(0, url.length - 1) + "/0";
            res.userInfo.avatarUrl = url;
            console.log(JSON.stringify(res.userInfo));
            that.setData({
              userInfo: res.userInfo,
              hasUserInfo: true
            })

            that.drawImg(res.userInfo.avatarUrl);
            app.globalData.userInfo = res.userInfo;
          }
        });
      }else if(that.data.userInfo.avatarUrl){
        console.log('-- 2 --');
        that.drawImg(that.data.userInfo.avatarUrl);
      }

    },


    drawImg(avatarUrl){
      let that = this;
      console.log("-- drawImg --");
      // `${that.data.userInfo.avatarUrl}`
      let promise1 = new Promise(function(resolve, reject) {
        wx.getImageInfo({
          src: avatarUrl,
          success: function(res) {
            console.log("promise1", res)
            resolve(res);
          }
        })
      });
      var index = that.data.defaultImg;
      // ../../images/head${index}.png
      // hat0.png  avg.jpg
      let promise2 = new Promise(function(resolve, reject) {
        wx.getImageInfo({
          src: `../../images/hat${index}.png`,
          success: function(res) {
            console.log(res)
            resolve(res);
          }
        })
      });
      Promise.all([
        promise1, promise2
      ]).then(res => {
        console.log("Promise.all", res)
        //主要就是计算好各个图文的位置
        let num = 150;
        ctx.drawImage(res[0].path, 0, 0, num, num)
        ctx.drawImage('../../' + res[1].path, 0, 0, num, num)
        ctx.stroke()
        ctx.draw(false, () => {
          wx.canvasToTempFilePath({
            x: 0,
            y: 0,
            width: num,
            height: num,
            destWidth: 960,
            destHeight: 960,
            canvasId: 'shareImg',
            success: function(res) {
              that.setData({
                prurl: res.tempFilePath
              })
              // wx.hideLoading()
            },
            fail: function(res) {
              wx.hideLoading()
            }
          })
        })
      })

    },

    handleShare: function(){
      console.log('handleShare method');
      this.onShareAppMessage();
    },

    save: function() {
      var that = this;
      if(!that.data.prurl){
        wx.showToast({
          title: '请先生成专属头像',
        })
        return;
      }
      wx.saveImageToPhotosAlbum({
        filePath: that.data.prurl,
        success(res) {
          wx.showModal({
            content: '图片已保存到相册!',
            showCancel: false,
            success: function(res) {
              if (res.confirm) {
                console.log('用户点击确定');
              }
            }
          })
        }
      })
    },



    /**
     * 生命周期函数--监听页面加载
     */
    onLoad: function (options) {
      this.initCanvas(this.data.defaultImg);
    },

    /**
     * 用户点击右上角分享
     */
    onShareAppMessage: function () {
      return {
        title: '领取你的国庆专属头像',
        success: function (res) {
          // 转发成功
          console.log("转发成功:" + JSON.stringify(res));
        },
        fail: function (res) {
          // 转发失败
          console.log("转发失败:" + JSON.stringify(res));
        }
      }
    },

    /**
     * 用户点击右上角分享朋友圈
     */
    onShareTimeline(){

    }
  })

好了,国庆头像制作的主要代码就这么多。
github地址:https://github.com/YMAndroid/...

对下,下面是头像制作小程序的二维码,想制作圣诞头像的小伙伴可以扫描体验哟~

03-05 20:28