天天看点

小程序制作证件照过程

小程序制作证件照过程

利用canvas制作生活中常用的证件照,压缩图片,修改图片dpi。希望给大家带来方便。

证件照小程序制作要点
  1. 上传合适的图片,方便制作证件照
  2. 调用AI接口,将图像进行人像分割。这里我用的是百度AI
  3. 调用人体关键点为分析图片中头部和肩部的位置信息。为后满裁剪图片提供依据
  4. 利用canvas 将头部和肩部位置制作为新的证件照尺寸照片
  5. 改变图片的背景颜色,生成不同要求的背景证件照
  6. 导出图品前将图片修改为符合打印要求的dpi。
  7. 下载最终生成好的证件照
上传合适的图片,方便制作证件照
selectImg(selectid){
  let _this = this
  let typelist = selectid === 1 ? ['camera'] : ['album']
  uni.chooseImage({
    count: 1,
    sourceType: typelist,
    success: (res)=> {
      
    }
  });
}      
调用AI接口,把图像进行人像分割,分析图像中头部肩部位置信息
  1. [参考链接地址] ​​https://cloud.baidu.com/doc/BODY/s/Fk3cpyxua​​
  2. 小程序制作证件照过程
  3. 该接口中要求上传的图片格式为base64 格式,大小不超过4M. 并且需要access_token
  4. 小程序制作证件照过程
  5. 获取access_token 参照百度AI 的文档 ​​https://ai.baidu.com/ai-doc/REFERENCE/Ck3dwjhhu​​
  6. 小程序制作证件照过程
  7. 定义好请求地址。和请求的请求方法
const baseUrl = 'https://picapp.gxwj123.top/prod-api/'
const baidubce = 'https://aip.baidubce.com/rest/2.0/image-classify/v1/'
export const tokenUrl = `${baseUrl}txy/zjz/token`
export const body_seg_url = `${baidubce}body_seg?access_token=`
export const body_analysis_url = `${baidubce}body_analysis?access_token=`

import {tokenUrl, body_seg_url, body_analysis_url} from './url.js'
export const request = async (url) => {
  let header = {
    'Content-Type': 'application/json',
  };
  let result = await new Promise((resolve, reject) => {
    uni.request({
      url: url,
      method: 'post',
      header: header,
      success(res) {
        if (res.statusCode == 200 && res.data.code == 200) {
          resolve(res.data.data);
        }
      },
      fail(err) {
        reject(err);
      }
    });
  });
  return result
};
export const baiduRequest = async (url, data) => {
  let header = {
    'Content-Type': 'application/x-www-form-urlencoded',
  };
  let result = await new Promise((resolve, reject) => {
    uni.request({
      url: url,
      method: 'post',
      header: header,
      data: {
        image: data.image
      },
      success(res) {
        resolve(res);
      },
      fail(err) {
        reject(err);
      }
    });
  });
  return result
};
export const getAccessToken = (data) => {
  return request(tokenUrl, data,)
}
export const body_seg = (data) => {
  let url = `${body_seg_url}${data.access_token}`;
  return baiduRequest(url, data)
}
export const body_analysis = (data) => {
  let url = `${body_analysis_url}${data.access_token}`;
  return baiduRequest(url, data)
}

export const getImageInfos = (data) => {
  return new Promise((resolve, reject) => {
    Promise.all([body_seg(data), body_analysis(data)]).then(([seg, analysis]) => {
      console.log(seg, analysis)
      if (seg.statusCode == 200 && analysis.statusCode == 200) {
        let data = {
          bodySeg: seg.data,
          bodyAns: analysis.data
        }
        resolve(data)
      }else {
        reject('请求任务出错')
      }
    })
  })
}      
  1. 上传的图片格式调整为base64
toBase64(file) {
  let _this = this
  uni.getFileSystemManager().readFile({
      filePath: file, //选择图片返回的相对路径
      encoding: 'base64', //编码格式
      success: res => {
          // 成功的回调
          // 'data:image/jpeg;base64,'
          let base64 = res.data;
          _this.getImgInfos(base64)
      }
  });
},      
将人像分割接口返回的图片和人体位置信息分析的坐标结合。生成用于制作证件照的素材。下面的将使用1寸证件照的尺寸和dpi 来进行分析。
  1. 从位置信息分析接口中取出要使用的位置,比如头部,肩部。人像分析中取foreground,为去掉原图中人物信息以外的图片
initImgData(bodyAns,bodySeg) {
  if (bodyAns.person_num > 1) {
    uni.showToast({
      title: '图片检测到多个人像,请重新上传',
      icon:'none',
      duration: 2000
    });
    return
  }
  if (bodyAns.person_num == 0) {
    uni.showToast({
      title: '图片未检测到人像,请重新上传',
      icon:'none',
      duration: 2000
    });
    return
  }
  let widthInfo = bodyAns.person_info[0]
  let location = this.imgwidthsum(widthInfo)
  this.location = location
  let foreground = bodySeg.foreground
  this.foreground = foreground
  this.previewImg('data:image/png;base64,' + foreground, location).then(filePath => {
    this.canvasImages = filePath
    this.buildOver = true
  })
},
imgwidthsum(data) {
  let body_parts = data.body_parts
  return {
    top_head: body_parts.top_head, 
    left_shoulder: body_parts.left_shoulder,
    right_shoulder: body_parts.right_shoulder
  }
},      
  1. 使用uni.getImageInfo 读取图片,需要先将上一步中base64d 图片转为本地图片
const fsm = wx.getFileSystemManager();
const FILE_BASE_NAME = 'tmp_base64src';

const base64src = function(base64data, pathName) {
  return new Promise((resolve, reject) => {
    const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64data) || [];
    if (!format) {
      reject(new Error('ERROR_BASE64SRC_PARSE'));
    }
    const filePath = `${wx.env.USER_DATA_PATH}/${FILE_BASE_NAME+pathName}.${format}`;
    const buffer = wx.base64ToArrayBuffer(bodyData);
    fsm.writeFile({
      filePath,
      data: buffer,
      encoding: 'binary',
      success() {
        resolve(filePath);
      },
      fail() {
        reject(new Error('ERROR_BASE64SRC_WRITE'));
      },
    });
  });
};

export default base64src;      
  1. 将图片按照要一定的比列绘制在canvas 中
let IMG_RATIO
let ratio = 295/413
let initWidth = 295
let initHeight = 413
let scrollTop = 250

let IMG_REAL_W,IMG_REAL_H
IMG_REAL_H = initHeight
IMG_REAL_W = IMG_REAL_H*IMG_RATIO
let canH = imgW * IMG_REAL_H / IMG_REAL_W
const ctx = uni.createCanvasContext("myselfCanvas", _this);
if (color) {
  ctx.setFillStyle(color)
  ctx.fillRect(0,0,IMG_REAL_W,IMG_REAL_H)
}
// 绘制的时候将选中的背景颜色填充到画布中
ctx.drawImage(res.path, 0, 0, IMG_REAL_W, IMG_REAL_H);      
  1. 根据原图中头像位置坐标。换算出需要在原图上裁剪出来的区域
let x = location.right_shoulder.x //右肩位置的坐标 x
let y = location.top_head.y - scrollTop // 头部坐标位置 减去一定比列的坐标 y 
let x1 = location.left_shoulder.x // 左肩位置坐标 x

var canvasW = ((x1 - x) / imgW) * IMG_REAL_W;
// 左肩坐标 减去右肩坐标 和原图的宽度比列 计算出 在上一步绘制的图中裁剪的宽度
var canvasH = canvasW/ratio // 根据证件照的比列 计算出 裁剪的高度
var canvasL = (x / imgW) * IMG_REAL_W;
var canvasT = (y / imgH) * IMG_REAL_H;
// 计算裁剪的起始坐标位置      
  1. 在canvas 绘制图后导出证件照需要的尺寸
ctx.draw(false,(ret)=>{             
  uni.showToast({
    icon:'success',
    mask:true,
    title: '绘制完成',
  });
  
  uni.canvasToTempFilePath({ // 保存canvas为图片
    x: canvasL,
    y: canvasT,
    width: canvasW, //canvasH,
    height: canvasH, //canvasH,
    destWidth: initWidth,
    destHeight: initHeight,
    canvasId: 'myselfCanvas',
    quality: 1,
    fileType: color? 'jpg': 'png',
    complete: function(res) {
      resolve(res.tempFilePath)    
    } ,
  })
});      
导出证件照之前,还需要修改图片的dpi
  1. 修改图片dpi 是将图片转为base64 格式后修改。本项目使用changedpi 插件
  2. npm install changedpi
import {changeDpiDataUrl} from 'changedpi'
export const changeDpi = (url, dpi) => {
  return new Promise((resolve) => {
    if (dpi) {
      uni.getFileSystemManager().readFile({
          filePath: url, //选择图片返回的相对路径
          encoding: 'base64', //编码格式
          success: res => {
              // 成功的回调
              // 'data:image/jpeg;base64,'
              
              let base64 = res.data;
              let str = changeDpiDataUrl('data:image/jpeg;base64,' + base64, dpi)
              base64src(str).then(filePath => {
  
                resolve(filePath)
              })
          }
      });
    }else {
      resolve(url)
    }
  })
}      
  1. 在小程序中使用需要注意 插件中直接使用了btoa atob 两个函数。 但是在小程序是不支持直接调用的。需要重写这两个方法
  2. 重写的方法
(function(f) {

  'use strict';

  /* istanbul ignore else */
  if (typeof exports === 'object' && exports != null &&
      typeof exports.nodeType !== 'number') {
    module.exports = f ();
  } else if (typeof define === 'function' && define.amd != null) {
    define ([], f);
  } else {
    var base64 = f ();
    var global = typeof self !== 'undefined' ? self : $.global;
    if (typeof global.btoa !== 'function') global.btoa = base64.btoa;
    if (typeof global.atob !== 'function') global.atob = base64.atob;
  }

} (function() {

  'use strict';

  var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';

  function InvalidCharacterError(message) {
    this.message = message;
  }
  InvalidCharacterError.prototype = new Error ();
  InvalidCharacterError.prototype.name = 'InvalidCharacterError';

  // encoder
  // [https://gist.github.com/999166] by [https://github.com/nignag]
  function btoa(input) {
    var str = String (input);
    for (
      // initialize result and counter
      var block, charCode, idx = 0, map = chars, output = '';
      // if the next str index does not exist:
      //   change the mapping table to "="
      //   check if d has no fractional digits
      str.charAt (idx | 0) || (map = '=', idx % 1);
      // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8
      output += map.charAt (63 & block >> 8 - idx % 1 * 8)
    ) {
      charCode = str.charCodeAt (idx += 3 / 4);
      if (charCode > 0xFF) {
        throw new InvalidCharacterError ("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.");
      }
      block = block << 8 | charCode;
    }
    return output;
  }

  // decoder
  // [https://gist.github.com/1020396] by [https://github.com/atk]
  function atob(input) {
    var str = (String (input)).replace (/[=]+$/, ''); // #31: ExtendScript bad parse of /=
    // if (str.length % 4 === 1) {
    //   throw new InvalidCharacterError ("'atob' failed: The string to be decoded is not correctly encoded.");
    // }
    for (
      // initialize result and counters
      var bc = 0, bs, buffer, idx = 0, output = '';
      // get next character
      buffer = str.charAt (idx++); // eslint-disable-line no-cond-assign
      // character found in table? initialize bit storage and add its ascii value;
      ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
        // and if not first of each 4 characters,
        // convert the first 8 bits to one ascii character
        bc++ % 4) ? output += String.fromCharCode (255 & bs >> (-2 * bc & 6)) : 0
    ) {
      // try to find character in table (0-63, not found => -1)
      buffer = chars.indexOf (buffer);
    }
    return output;
  }

  return {btoa: btoa, atob: atob};

}));      
  1. 在源码中修改调用
const polyfill = require('../../../util/btoa.js');
const {btoa, atob} = polyfill;      
下载证件照到手机相册
export const savePoster = (url) => {
  const that = this
  wx.saveImageToPhotosAlbum({
      filePath: url,
      success: function() {
          wx.showToast({
              title: '保存成功',
              icon: 'none',
              duration: 1500
          });
      },
      fail(err) {
        if (err.errMsg === "saveImageToPhotosAlbum:fail:auth denied" || err.errMsg === "saveImageToPhotosAlbum:fail auth deny" || err.errMsg === "saveImageToPhotosAlbum:fail authorize no response") {
          wx.showModal({
            title: '提示',
            content: '需要您授权保存相册',
            showCancel: false,
            success: modalSuccess => {
              wx.openSetting({
                success(settingdata) {
                  if (settingdata.authSetting['scope.writePhotosAlbum']) {
                      wx.saveImageToPhotosAlbum({
                          filePath: url,
                          success: function () {
                            wx.showToast({
                              title: '保存成功',
                              icon: 'success',
                              duration: 2000
                            })
                          },
                      })
                  } else {
                      wx.showToast({
                          title: '授权失败,请稍后重新获取',
                          icon: 'none',
                          duration: 1500
                      });
                  }
                }
              })
            }
          })
        }
      }
    })
}      
下面是利用canvas 做的小应用,欢迎大家扫描体验,并提出建议。让我们共同进步
小程序制作证件照过程