Hi头像

Hi头像

  • 网站首页
  • 技术博客
  • 教程小册

›4-实战开发

标题

  • 《从0到1开发一个智能头像识别小程序》

1-前言

  • 1.1 产品需求设计、页面流程图及项目规划
  • 1.2 技术选型的依据

2-项目配置

  • 2.1 小程序开启云开发模式
  • 2.2 人脸五官分析的环境配置

3-知识储备

  • 3.1 图片裁剪、模糊、保存、分享的技巧
  • 3.2 Canvas绘制技巧,包含小程序与 Web 端
  • 3.2 云开发图像处理的使用方法

4-实战开发

  • 4.1.1 上篇:借助云存储和图像处理扩展人脸智能裁剪
  • 4.1.2 下篇:图像识别流程——图片裁剪、图片压缩、base64转换
  • 4.2 云开发必备——图像安全审核
  • 4.3 通过五官分析实现为人脸佩戴贴纸
  • 4.4 人脸贴纸移动技巧
  • 4.5 头像绘制和图片保存弹窗
  • 4.6【未写】海报分享页面
  • 4.7 CMS扩展实战-节日主题贴纸放在云开发
  • 4.8 【第二期】个人中心的制作
  • 4.9【第二期】小程序与Web端的账户体系摸索

云开发-公众号外发

  • 图片识别加速,从10秒变为1秒,是怎么做到的呢?
  • 简单几步,让微信小程序变身 H5 网页
  • 【草稿】借助云存储,让大图识别转换真正加速
  • 云开发CMS实战-Hi头像节日主题贴纸大升级
  • 小程序云开发环境部署静态网站托管的实战
  • Web 云开发实战之盲水印添加工具(核心版)
  • Web 云开发实战之盲水印添加工具(步骤版)

更多探索

  • 【草稿】深色模式在Web端的适配技巧,附带小程序侧的思考
  • 【草稿】小程序框架:Taro 2.0与Taro 3.0的核心分析

4.5 头像绘制和图片保存弹窗

知识点

  • 设置 canvas 元素和生成的头像图片元素
    • 绘制头像的 canvas 画布
    • 导出头像图片的画布
  • 头像弹窗
    • 预览图片
    • 长按图片可分享给朋友
    • 保存图片
    • 分享给朋友

设置 canvas 元素和生成的头像图片元素

在《通过五官分析实现为人脸佩戴贴纸》中,我提及了借助 rpx单位巧妙来完成图形布局和移动。在用户需要保存图片时,需要借助canvas来合成图片。

直观的理解是,需要宽高为600px的图片,那就在直接展示给用户一个600px宽的canvas,但其实不这么简单。

我们需要一个“隐藏”的宽高为 600px 的 canvas 画布来完成,最后生成一个我们所需的头像图片。

/* canvas 隐藏画板 */
<Canvas className='canvas-shape' style={{ width: '6px', height: '600px' }} canvasId='canvasShape' />

// 生成好的图片,图片路径为 posterSrc
<Image className='poster-image' src={posterSrc} onClick={this.previewPoster} showMenuByLongpress />
.canvas-shape {
  position: absolute;
  left: 0;
  top: -9999px;
}

注意点:

  • 与 “Web 端支持canvas动态创建”不同的是,小程序必须提前创建好 canvas 元素
  • 小程序上的canvas不可以设置为 display:none,设置后,画布并不会被绘制出来
  • 在小程序上,canvas元素并不支持缩放,也就是,600px的宽高会被小程序边缘裁减掉只剩下屏幕宽度

绘制头像的 canvas 画布

请仔细看代码中的注释

// 生成图片宽高为600px
const SAVE_IMAGE_WIDTH = 600

// 绘制 canvas
drawCanvas = async () => {
  // cutImageSrc 裁剪后的头像底图
  // shapeList 图形列表
  const { cutImageSrc, shapeList } = this.state

  // 获取 canvas 的 context
  const pc = Taro.createCanvasContext('canvasShape')

  // 清空画布
  pc.clearRect(0, 0, SAVE_IMAGE_WIDTH, SAVE_IMAGE_WIDTH)

  // getImg 获取图片,注意获取图片在小程序与Web端的不同
  let tmpCutImage = await getImg(cutImageSrc)
  // 绘制裁剪后的头像底图
  pc.drawImage(tmpCutImage, 0, 0, SAVE_IMAGE_WIDTH, SAVE_IMAGE_WIDTH)

  // 遍历列表,绘制图形贴纸
  for (let index = 0; index < shapeList.length; index++) {
    // 保存绘图上下文
    pc.save()

    // 获取贴纸的细节
    const {
      // 图形宽
      shapeWidth: shapeSize,
      // 旋转角度
      rotate,
      // 图形中心点 X轴
      shapeCenterX,
      // 图形中心点 Y轴
      shapeCenterY,
      // 图形正向图片地址
      imageUrl,
      // 图形反向图片地址
      imageReverseUrl,
      // 是否旋转
      reserve: isReserve,
    } = shapeList[index]

    // 移动到图形中心
    pc.translate(shapeCenterX, shapeCenterY)
    // 旋转画布角度
    pc.rotate((rotate * Math.PI) / 180)

    // 获取图形地址
    let oneImgSrc = await getImg(isReserve < 0 ? (imageReverseUrl || imageUrl) : imageUrl)

    // 绘制贴纸
    pc.drawImage(
      oneImgSrc,
      -shapeSize / 2,
      -shapeSize / 2,
      shapeSize,
      shapeSize
    )

    // 恢复之前保存的绘图上下文
    pc.restore()
  }

  // 绘制图形到画布上
  pc.draw(false, () => {
    // 绘制完要继续完成的操作
  })
}

注意点

1、小程序上的 drawImage 所要绘制的图片资源(网络图片要通过 getImageInfo / downloadFile 先下载),所以我封装了一个 getImg 方法来完成这个操作

/**
 * 获取图片
 * @param {*} src 图片地址
 */
export const getImg = async (src) => {
  // web端 获取图片Image元素
  if (isH5Page) {
    let image = await getH5Image(src)
    return image
  }

  // 将base64存储为本地路径
  if (src.includes(';base64,')) {
    return await base64src(src)
  }

  try {
    // 将网络图片转换为本地路径
    const res = await Taro.getImageInfo({
      src,
    })
    return res.path
    
  } catch (error) {
    console.log('error :', error);
    throw error
    
  }
}

2、在 iPhone手机上,canvas绘制图片不支持水平翻转,这里需要正向和反向的两个图片

let oneImgSrc = await getImg(isReserve < 0 ? (imageReverseUrl || imageUrl) : imageUrl)

导出头像图片的画布

pc.draw(false, () => {
  Taro.canvasToTempFilePath({
    canvasId: 'canvasShape',
    x: 0,
    y: 0,
    height: SAVE_IMAGE_WIDTH * 3,
    width: SAVE_IMAGE_WIDTH * 3,
    // 图片类型
    fileType: 'jpg',
    // 压缩质量
    quality: 0.9,
    success: async (res) => {

      // 保存图片到云数据库
      await this.onSaveImageToCloud(res.tempFilePath)

      Taro.hideLoading()
      // 设置海报图片
      this.setState({
        posterSrc: res.tempFilePath
      }, () => {
        // 展示海报弹窗
        this.posterRef.onShowPoster()
      })

    },
    fail: () => {
      Taro.hideLoading()
      Taro.showToast({
        title: '图片生成失败,请重试'
      })
    }
  })
})

至此,头像图片的临时路径已经被保存到 posterSrc 变量中,并且可以在头像弹窗中展示出来。

头像弹窗

将 posterSrc展示到弹窗中有如下原因

  • 弹窗内的逻辑相对独立
  • 可以关闭弹窗后再继续编辑头像上的贴纸图形的位置

render(){
    const { isShowPoster } = this.state
    const { posterSrc, isH5Page, forCheck } = this.props

    return (
      <View className={`poster-dialog ${posterSrc && isShowPoster ? 'show' : ''}`}>
        <View className='poster-dialog-main'>
          {!!posterSrc && <Image className='poster-image' src={posterSrc} onClick={this.previewPoster} showMenuByLongpress></Image>}
          <View className='poster-image-tips'>点击可预览大图,长按可分享图片</View>
          <View className='poster-dialog-close' onClick={this.onHidePoster} />
          <View className='poster-footer-btn'>
            <View className='poster-btn-save' onClick={this.savePoster}>
              保存到相册
            </View>
            <Button className='poster-btn-share' openType='share' data-poster-src={posterSrc}>
              分享给朋友
            </Button>
          </View>
        </View>

      </View>
    )
  }

预览图片

在小程序内查看图片基本上都小于 375px,所以预览宽高为600px的图片还是需要预览图片。

previewPoster = () => {
  const { posterSrc } = this.props
  if (posterSrc !== '') Taro.previewImage({ urls: [posterSrc] })
}

长按图片可分享给朋友

微信小程序上分享图片只能分享页面,并不能直接分享图片。那如何做到呢?那就是长按图片后的菜单上有“发送给朋友”的选项。

<Image className='poster-image' src={posterSrc} onClick={this.previewPoster} showMenuByLongpress></Image>

保存图片

其实这里的保存图片才是真正将图片保存到相册内。

savePoster = () => {
  const { posterSrc } = this.props

  if (posterSrc) {
    this.saveImageToPhotosAlbum(posterSrc)
  }
}

分享给朋友

分享给朋友时,也是有注意点的。在微信小程序“…”菜单上分享的是挡墙页面,而分享弹窗上其实触发分享的页面是头像分享页。

onShareAppMessage({ from, target }) {
  const DEFAULT_SHARE_COVER = 'https://n1image.hjfile.cn/res7/2020/04/26/2041af2867f22e62f8fce32b29cd1fb0.png'
  const { themeData } = this.state
  let { shareImage = DEFAULT_SHARE_COVER, shareTitle = '给女神戴上皇冠吧!' } = themeData

  let shareUrl = '/pages/avatar-edit/avatar-edit'
  if (from === 'button') {
    const { dataset = {} } = target
    const { posterSrc = '' } = dataset

    console.log('posterSrc :', posterSrc);

    if (posterSrc) {
      shareImage = posterSrc
      const { shareUUID } = this.state
      if (shareUUID) {
        shareUrl = `/pages/avatar-poster/avatar-poster?uuid=${shareUUID}`
      }
    }

  }

  console.log('shareUrl :', shareUrl);
  return {
    title: shareTitle,
    imageUrl: shareImage,
    path: shareUrl
  }
}

Taro 补充知识点

如果你的图片是放在 images 文件夹内的,那在 Taro 项目中,canvas 画图的drawImage(src, 0, 0, 300, 300)方法中 图片 src 为require('../..https://www.xiaoxili.com/images/xxx.png')的话,一定要记住修改以下配置,否则小程序就会报 http 500错误。

{
  mini: {
    imageUrlLoaderOption: {
      limit: 0
    }
}
← 4.4 人脸贴纸移动技巧4.6【未写】海报分享页面 →
  • 设置 canvas 元素和生成的头像图片元素
    • 绘制头像的 canvas 画布
    • 导出头像图片的画布
  • 头像弹窗
    • 预览图片
    • 长按图片可分享给朋友
    • 保存图片
    • 分享给朋友
  • Taro 补充知识点
Hi头像
文档
开篇介绍
相关资源
云开发 CloudBaseTaro
关于
联系我们GitHub主页Star
Copyright © 2020. All Rights Reserved.
沪ICP备2022023998号