首页
学习
关于
友链
Search
1
小程序 蓝牙连接(出现的问题和一些解决方法)
315 阅读
2
颜色空间:RGB、HSV和HSL
143 阅读
3
小程序_连接蓝牙设备根据平台去打开定位权限
136 阅读
4
小程序 加快安卓手机向蓝牙设备发送大数据
93 阅读
5
侧边菜单切换的显示与隐藏,图标的 + 与 -
90 阅读
全部
感想
旅行
生活
学习
登录
Search
标签搜索
css
javascript
jquery
html
小程序
github
图床
假期
发布订阅
typecho
第一次
未来
快乐与忧伤
努力
奋斗
PicGo
倒计时
元旦
svg
vue
逸曦穆泽
累计撰写
35
篇文章
累计收到
2
条评论
首页
栏目
全部
感想
旅行
生活
学习
页面
学习
关于
友链
搜索到
1
篇与
图片
的结果
2022-12-17
小程序_图片转canvas,上传和rgb888转rgb565发送到设备
序: 小程序需要将图片上传到蓝牙设备,那么图片要转为二进制数据,转为二进制的图片数据不能过大,就要裁剪图片,因为要尽量减少图片的二进制数据,要将图片的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转为rgb565var 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个字节,看你需求,过大会产生一些问题,并且每包发送也可以设置一下延迟多少毫秒,也可以避免传输过程中断连问题。
2022年12月17日
45 阅读
0 评论
0 点赞