小程序_图片转canvas,上传和rgb888转rgb565发送到设备
侧边栏壁纸
  • 累计撰写 35 篇文章
  • 累计收到 1 条评论

小程序_图片转canvas,上传和rgb888转rgb565发送到设备

逸曦穆泽
2022-12-17 / 0 评论 / 21 阅读 / 正在检测是否收录...

序: 小程序需要将图片上传到蓝牙设备,那么图片要转为二进制数据,转为二进制的图片数据不能过大,就要裁剪图片,因为要尽量减少图片的二进制数据,要将图片的rgb888转为rgb565,也就是24位缩小为16位数据;例如:100像素*100像素的图片,就有1万个点,一个点包含rgba四个数,只需要rgb这三个数,rgb三个数各包含8位(也就是二进制,为rgb888),rgb888为三个字节,rgb565为两个字节,数据量传输就少了三分一,但也有几十K的数据了,分240个字节每包发送完也要十秒左右;

如果要上传,上传到后端的路径要是临时路径才可以,不能是本地路径,所以要使用 canvas 转一下得到 tempFilePath 的临时路径,

一、图片渲染为 canvas 得到临时路径,并上传

// 选择本地图片渲染成 canvas
locationImg(e) {
  let imgUrl = e.currentTarget.dataset.img;
  let that = this;
  gbImgComStr = "";
  wx.createSelectorQuery().select('#canvasImg').fields({
    node: true,
    size: true
  }).exec((res) => {
    const canvas = res[0].node;
    const ctx = canvas.getContext('2d');
    var dpr = wx.getSystemInfoSync().pixelRatio // 设备像素比
    canvas.width = res[0].width * dpr
    canvas.height = res[0].height * dpr
    ctx.scale(dpr, dpr)
    const img = canvas.createImage();
    img.onload = () => {
      ctx.drawImage(img, 0, 0, 200, 200);
      wx.canvasToTempFilePath({
        x: 0,
        y: 0,
        width: 200,
        height: 200,
        destWidth: 200,
        destHeight: 200,
        canvas: canvas,
        fileType: "jpg",
        quality: .8,
        success(res) {
          that.initImgData(res.tempFilePath); // 使用服务器
        }
      })
    }
    img.src = imgUrl;
  })
  // console.log(imgUrl)
},
 
// 图片上传到服务器
initImgData(imgUrl) {
  let that = this;
  let gbImgPath = "";
  wx.uploadFile({
    url: apiUrl+'/imgUpload', //你的后端上传路径
    filePath: imgUrl, 
    name: "imgPath", // 键值key
    header: { "content-type": "multipart/form-data"},
    success: function (res) {
      if (res.statusCode == 200) {
        gbImgPath = JSON.parse(res.data).imgPath; // 存放到服务器上的图片路径
      }
    },
    fail: function (err) {
      wx.showToast({
        title: "上传失败",
        icon: "none",
        duration: 2000
      })
    },
  })
  // 图片回显 或 gbImgPath
  that.setData({
    imgPath: imgUrl
  })
},

后端接口(PHP):

//对应的文件夹
public function dirFile($name){
    $dirName = "store/".$name."/".date("Ym");
    if(!file_exists($dirName)){
        mkdir(app()->getRootPath().'public/'.$dirName,0777,true);
    }
    return $dirName;
}
// 图片处理
function imgUpload(){
    if(request()->isPost()){
        $dirName = $this -> dirFile('image');
        $file = $_FILES['imgPath']; // 获取小程序传来的图片
        if(is_uploaded_file($file['tmp_name'])) {
            $type = $file["type"];//文件类型 如:image/png
            $ext = strrchr($type, '/');//切割字符串
            //类型判断
            if (in_array($ext, ['/jpeg', '/jpg', '/png', '/svg', '/gif','ico'])) {
                $filePrefix1 = strstr($file["name"], '.', true);// 文件名前缀 无.png
                $filestr = date("Ymdhis").$filePrefix1; // 避免同一时间调用该方法时产生同一个文件名
                $fileExt1 = strstr($file["name"], '.'); // 获取 .png
                $fileName = $dirName . "/" . md5($filestr) . $fileExt1; //拼接文件路径
                //判断文件是否存在
//                    if (file_exists($fileName)) {
//                        return json(["statusCode" => 200,"msg"=>"exist","imgPath" => $fileName]);
//                    } else {
                    //保存文件 ($_FILES["文件名"]["tmp_name"],newPath),(旧路径,新路径)
                    move_uploaded_file($file["tmp_name"], $fileName);
                    return json(["statusCode" => 200,"msg"=>"success","imgPath" => $fileName]);
//                    }
            }
            return json(["statusCode" => 402,"msg"=>"format error"]);
        }
        return json(["statusCode"=>401,"msg"=>"file error"]);
    }
    return json(["statusCode"=>400,"msg"=>"request error"]);
}

二、在小程序上将图片数据rgb888转为rgb565

var gbImgComStr = "";
// 图片:16进制补零 
function imgHexFill(hexStr) {
  let len = hexStr.length;
  var map = {
    1: "000",
    2: "00",
    3: "0"
  }
  if (map[len]) {
    hexStr = map[len] + hexStr;
  }
  return hexStr;
}
// 上为 Page 外,下为 Page 内
 
// 图片画成 canvas 获取数据
initImgData565: async function(imgUrl) {
  return new Promise((resolve, reject) => {
    let that = this;
    imgLimitNum = 0; 
    wx.createSelectorQuery().select('#canvasImg').fields({
      node: true,
      size: true
    }).exec((res) => {
      const canvas = res[0].node;
      const ctx = canvas.getContext('2d');
      // var dpr = wx.getSystemInfoSync().pixelRatio // 设备像素比
      var dpr = 1;
      canvas.width = res[0].width * dpr
      canvas.height = res[0].height * dpr
      // ctx.scale(dpr, dpr)
      let sideLen = 100;
      ctx.fillRect(0, 0, sideLen, sideLen);
      const img = canvas.createImage();
      img.onload = () => {
        ctx.drawImage(img, 0, 0, sideLen, sideLen);
        var imageData = ctx.getImageData(0, 0, sideLen, sideLen).data;
        // console.log(imageData.length)
        var rgbStr = "";
        var rgb = 0;
        var rgb565 = 0;
        var w = 0; var h = 0;
        // 逐列式 (逐行式则是Z扫描)
        for (var w = 0; w < sideLen; w++) {
          for (var h = 0; h < sideLen; h++) {
            var offset = (h*sideLen + w)*4;
            var red = imageData[offset];
            var green = imageData[offset + 1];
            var blue = imageData[offset + 2];
            rgb = 0xff000000 | (red << 16) | (green << 8) | blue;
            rgb565 = ((rgb & 0xf80000) >> 8) | ((rgb & 0xfc00) >> 5) | ((rgb & 0xf8) >> 3)
            rgbStr += imgHexFill(rgb565.toString(16));
          }
        }
        gbImgComStr = rgbStr.toUpperCase();
        imgUrl != "" ? resolve() : reject();
      }
      img.src = imgUrl;
    })
  })
},

由于数据过大,这些数据还要分包发送到蓝牙设备,首先,要发第一包数据告诉设备要发图片的指令了,可以加入长度,这个要和设备那边沟通,分包发送还不能全部直接发,不然蓝牙设备很容易造成丢包的,小程序发完了,设备那边可能才获取十分之一呢,要分20、30包发完后再发一个确定指令,如果返回正确的指令则继续分20、30包发下去再发一个确定指令,然后重复发完为止;

部分代码:

// 图片_下个数据包指令
imgNextCmd: async function(){
  let that = this;
  let sData = that.data;
  let comStr = "", roleData = "", command = "";
  let rlt = "";
  let packageNum = 30; // 连续发送包的个数
  var rowSum = ((imgPackageSum - imgIndex) >= packageNum) ? (imgIndex + packageNum) : imgPackageSum;
  for (let row = imgIndex; row < rowSum; row++) {
    comStr = gbImgComStr.substr((row)*(imgCharLen-10),(imgCharLen-10));
    roleData = "AC" + decToHexLenFill(row+1) + decToHex(comStr.length/2) + comStr;
    let res = new Uint8Array(roleData.match(/[\da-f]{2}/gi).map(function (h) {
      return parseInt(h, 16)
    }));
    command = roleData + arrSumCheck(res);
    imgIndex += 1;
    // await sleep(50);
    rlt = await that.sendCom(command, sData.deviceId, sData.serviceId, sData.characteristicId);
    if (rlt == "success") {
      // 包确认
      if(row == (rowSum-1)){
        // console.log(">>> sure",rowSum)// 成功
        let sureCom = "BCxxxx"+decToHexLenFill(row+1);
        let cs = new Uint8Array(sureCom.match(/[\da-f]{2}/gi).map(function (h) {
          return parseInt(h, 16)
        }));
        let sureCommand = sureCom + arrSumCheck(cs);
        that.writeBLECharacteristicValue(sureCommand);
      }
    }
  }
  if(rowSum == imgPackageSum && imgLimitNum == 0){
    imgLimitNum = 1; 
    let endCom = "xxx"; // 传输完毕
    that.writeBLECharacteristicValue(endCom);
  }
},

彩蛋: 一般一个包只能发20个字节,你觉得一张150*150的图片数据发完需要多久?要两分钟左右吧,这个还没考虑中途断连等情况呢!你觉得你是用户,你会接受吗?所以,可以先获取一下mtu是很有必要的,一般Android的是23(包含头部3个字节信息),iOS的是512,有部分Android手机是没有mtu值的,只能落空默认值了,Android可以协商mtu值 wx.setBLEMTU ,你可以一个包发256、300个字节,看你需求,过大会产生一些问题,并且每包发送也可以设置一下延迟多少毫秒,也可以避免传输过程中断连问题。

0

评论 (0)

取消