小程序canvas实现图片压缩

JesseLuo
本来用小程序写了一个本地化的图片应用,不存在服务端的交互行为

结果提交审核的时候还是被打回了

好的!马上整改

应用的交互大概就是这样

我们需要在选择图片后

对图片做一次安全校验

启用云开发

现在我们需要一个 后端接口 来实现图片的 安全校验 功能

这时候临时搭个Node服务好像不太现实

又不是什么正经项目

于是就想到了微信的云开发功能

用起来真实方便快捷

至于图片的校验方法

直接用云函数调用 security.imgSecCheck 接口就好了

流程

chooseImage() {
  /// 用户选择图片
  wx.chooseImage({
    count: 1,
    sizeType: ['original', 'compressed'],
    sourceType: ['album', 'camera'],
    success: async res => {
      if (res.errMsg === 'chooseImage:ok') {
        wx.showLoading({ title: '图片加载中' })
        // 获取图片临时地址
        const path = res.tempFilePaths[0]
        // 将图片地址实例化为图片
        const image = await loadImage(path, this.canvas)
        // 压缩图片
        const filePath = await compress.call(this, image, 'canvas_compress')
        // 校验图片合法性
        const imgValid = await checkImage(filePath)
        wx.hideLoading()
        if (!imgValid) return
        // 图片安全检测通过,执行后续操作
        ...
    }
  })
}

图片压缩

由于 security.imgSecCheck 对图片有尺寸限制

所以在图片上传前要先对超出尺寸的图片进行压缩处理
基本逻辑就是

超出尺寸的图片等比例缩小就好了

我们先要有一个canvas元素

用来处理需要压缩的图片

<template>
  <view class="menu-background">
    <view class="item replace" bindtap="chooseImage">
      <i class="iconfont icon-image"></i>
      <text class="title">图片</text>
      <text class="sub-title">图片仅供本地使用</text>
    </view>
    //
    // canvas
    //
    <canvas
      type="2d"
      id="canvas_compress"
      class="canvas-compress"
      style="width: {{canvasCompress.width}}px; height: {{canvasCompress.height}}px"
    />
  </view>
</template>

将canvas移到视野不可见到位置

.canvas-compress
    position absolute
    left 0
    top 1000px

图片进行压缩处理

/**
 * 压缩图片
 * 将尺寸超过规范的图片最小限度压缩
 * @param {Image} image 需要压缩的图片实例
 * @param {String} canvasId 用来处理压缩图片的canvas对应的canvasId
 * @param {Object} config 压缩的图片规范 -> { maxWidth 最大宽度, maxHeight 最小宽度 }
 * @return {Promise} promise返回 压缩后的 图片路径
 */
export default function (image, canvasId, config = { maxWidth: 750, maxHeight: 1334 }) {
  // 引用的组件传入的this作用域
  const _this = this
  return new Promise((resolve, reject) => {
    // 获取图片原始宽高
    let width = image.width
    let height = image.height
    // 宽度 > 最大限宽 -> 重置尺寸
    if (width > config.maxWidth) {
      const ratio = width / config.maxWidth
      width = config.maxWidth
      height = height / ratio
    }
    // 高度 > 最大限高度 -> 重置尺寸
    if (height > config.maxHeight) {
      const ratio = height / config.maxHeight
      height = config.maxHeight
      width = width / ratio
    }
    // 设置canvas的css宽高
    _this.canvasCompress.width = width
    _this.canvasCompress.height = height
    const query = this.createSelectorQuery()
    query
      .select(`#${canvasId}`)
      .fields({ node: true, size: true })
      .exec(async res => {
        // 获取 canvas 实例
        const canvas = res[0].node
        // 获取 canvas 绘图上下文
        const ctx = canvas.getContext('2d')
        // 根据设备dpr处理尺寸
        const dpr = wx.getSystemInfoSync().pixelRatio
        canvas.width = width * dpr
        canvas.height = height * dpr
        ctx.scale(dpr, dpr)
        // 将图片绘制到 canvas
        ctx.drawImage(image, 0, 0, width, height)
        // 将canvas图片上传到微信临时文件
        wx.canvasToTempFilePath({
          canvas,
          x: 0,
          y: 0,
          destWidth: width,
          destHeight: height,
          complete (res) {
            if (res.errMsg === 'canvasToTempFilePath:ok') {
              // 返回临时文件路径
              resolve(res.tempFilePath)
            }
          },
          fail(err) {
            reject(err)
          }
        })
      })
  })
}

图片安全校验

云函数 checkImage.js

const cloud = require('wx-server-sdk')
cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
})
/**
 * 校验图片合法性
 * @param {*} event.fileID 微信云存储的图片ID
 * @return {Number} 0:校验失败;1:校验通过
 */
exports.main = async (event, context) => {
  const contentType = 'image/png'
  const fileID = event.fileID
  try {
    // 根据fileID下载图片
    const file = await cloud.downloadFile({
      fileID
    })
    const value = file.fileContent
    // 调用 imgSecCheck 借口,校验不通过接口会抛错
    // 必要参数 media { contentType, value }
    const result = await cloud.openapi.security.imgSecCheck({
      media: {
        contentType,
        value
      }
    })
    return 1
  } catch (err) {
    return 0
  }
}

组件调用云函数封装

/**
 * 校验图片是否存在敏感信息
 * @param { String } filePath
 * @return { Promise } promise返回校验结果
 */
export default function (filePath) {
  return new Promise((resolve, reject) => {
    // 先将图片上传到云开发存储
    wx.cloud.uploadFile({
      cloudPath: `${new Date().getTime()}.png`,
      filePath,
      success (res) {
        // 调用云函数-checkImage
        wx.cloud.callFunction({
          name: 'checkImage',
          data: {
            fileID: res.fileID
          },
          success (res) {
            // res.result -> 0:存在敏感信息;1:校验通过
            resolve(res.result)
            if (!res.result) {
              wx.showToast({
                title: '图片可能含有敏感信息, 请重新选择',
                icon: 'none'
              })
            }
          },
          fail (err) {
            reject(err)
          }
        })
      },
      fail (err) {
        reject(err)
      }
    })
  })
}
本文demo

本文代码仓库

https://github.com/luosijie/f...

谢谢阅读!!!

阅读 3k

前端随笔
[链接]

前端工程师

1.8k 声望
139 粉丝
0 条评论
你知道吗?

前端工程师

1.8k 声望
139 粉丝
文章目录
宣传栏