天天看點

小程式制作證件照過程

小程式制作證件照過程

利用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 做的小應用,歡迎大家掃描體驗,并提出建議。讓我們共同進步
小程式制作證件照過程