cropper.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. // pages/piano/cropper/cropper.js
  2. const { BtCmd } = require('../../../devices/bluetooth/bt_cmd');
  3. import EventManager from '../../../utils/event_bus'
  4. import { EnumCmdEvent, CmdEvent } from '../../../devices/cmd_key_event';
  5. import route_util from '../../../utils/route_util';
  6. const { BtHelper } = require('../../../devices/bt_helper');
  7. Page({
  8. /**
  9. * 页面的初始数据
  10. */
  11. data: {
  12. navbarData: {
  13. showCapsule: 1, //是否显示左上角图标 1表示显示 0表示不显示
  14. title: '壁纸设置', //导航栏 中间的标题
  15. },
  16. src: "",
  17. width: 320,//宽度
  18. height: 320,//高度
  19. showProgress: false,
  20. _chunks: [],
  21. progress: 0,
  22. _timer: null,
  23. _imgUrl: null,
  24. _imgMD5: null,
  25. _imgIndex: null,
  26. },
  27. cropper: null,
  28. callback() {
  29. let that = this
  30. if (this.data._chunks.length > 0) {
  31. wx.showModal({
  32. title: '保存壁纸中,确定要中断退出吗?',
  33. content: '',
  34. complete: (res) => {
  35. if (res.confirm) {
  36. that.endImage(2)
  37. wx.navigateBack({
  38. delta: 1,
  39. })
  40. }
  41. }
  42. })
  43. } else {
  44. wx.navigateBack({
  45. delta: 1,
  46. })
  47. }
  48. },
  49. convertImageDataToRGB565(imageData, width, height) {
  50. // 创建一个 Uint16Array 来存储 RGB565 数据
  51. const rgb565Data = new Uint16Array(width * height);
  52. // 将 RGBA 数据转换为 RGB565 数据
  53. for (let i = 0; i < imageData.length; i += 4) {
  54. const r = imageData[i];
  55. const g = imageData[i + 1];
  56. const b = imageData[i + 2];
  57. // const a = imageData[i + 3]; // 透明度,如果需要可以使用
  58. // 将 RGB 转换为 RGB565
  59. const rgb565 = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
  60. rgb565Data[i / 4] = rgb565;
  61. }
  62. return rgb565Data
  63. },
  64. generateLVGLBinFile(rgb565Data, width, height) {
  65. const header = new ArrayBuffer(32);
  66. const headerView = new DataView(header);
  67. // 十六进制字符串
  68. // const hexString = "04 80 07 3c ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff";
  69. const hexString = "04 80 07 3c";
  70. // 将十六进制字符串转换为字节数组
  71. const bytes = hexString.split(' ').map(byte => parseInt(byte, 10));
  72. console.log(bytes);
  73. // 将字节数组写入 headerView
  74. bytes.forEach((byte, index) => {
  75. headerView[index] = byte
  76. });
  77. // 创建文件数据
  78. const fileData = new Uint8Array(header.byteLength + rgb565Data.byteLength);
  79. console.log("fileData:", fileData.length, fileData.byteLength);
  80. fileData.set(new Uint8Array(header), 0); // 添加头部信息
  81. fileData.set(new Uint8Array(rgb565Data.buffer), header.byteLength); // 添加 RGB565 数据
  82. return fileData;
  83. },
  84. // 转换 RGBA 数据为 RGB565
  85. imageDataToRGB565(rgbaData, width, height) {
  86. const pixelCount = width * height;
  87. // 十六进制字符串
  88. const hexString = "04 80 07 3c";
  89. // 将十六进制字符串转换为字节数组
  90. // const bytes = hexString.split(' ').map(byte => parseInt(byte, 10));
  91. const bytes = ["04", "80", "07", "3c"];
  92. console.log(bytes);
  93. const outputData = new Uint8Array(pixelCount * 2 + bytes.length); // 文件头 + 每像素 4 字节 (RGB888 + Alpha)
  94. // 将字节数组写入 headerView
  95. bytes.forEach((byte, index) => {
  96. outputData[index] = parseInt(byte, 16)
  97. });
  98. // 写入像素数据
  99. let offset = bytes.length;
  100. for (let i = 0; i < pixelCount; i++) {
  101. const r = rgbaData[i * 4];
  102. const g = rgbaData[i * 4 + 1];
  103. const b = rgbaData[i * 4 + 2];
  104. // 转换为 RGB565 格式
  105. const r5 = (r >> 3) & 0x1F;
  106. const g6 = (g >> 2) & 0x3F;
  107. const b5 = (b >> 3) & 0x1F;
  108. const rgb565 = (r5 << 11) | (g6 << 5) | b5;
  109. // 写入 RGB565 (小端序)
  110. outputData[offset++] = rgb565 & 0xFF; // 低字节
  111. outputData[offset++] = (rgb565 >> 8) & 0xFF; // 高字节
  112. }
  113. console.log('最终偏移:', offset)
  114. return outputData;
  115. },
  116. cropperload(e) {
  117. console.log('cropper加载完成');
  118. },
  119. upload() {
  120. let that = this;
  121. wx.chooseMedia({
  122. count: 1,
  123. mediaType: ['image'],
  124. sourceType: ['album'],
  125. success(res) {
  126. wx.showLoading({
  127. title: '加载中',
  128. })
  129. const tempFilePaths = res.tempFiles[0].tempFilePath;
  130. // //重置壁纸角度、缩放、位置
  131. that.cropper.imgReset();
  132. that.setData({
  133. src: tempFilePaths
  134. });
  135. // that.testUpload(tempFilePaths)
  136. }, fail(res) {
  137. console.log(res)
  138. }
  139. })
  140. },
  141. testUpload(url) {
  142. const fs = wx.getFileSystemManager();
  143. wx.showLoading({
  144. title: '壁纸裁剪中',
  145. })
  146. let _this = this
  147. // app.globalData.imgSrc = obj.url;
  148. console.log("裁剪壁纸:", url);
  149. _this.data._imgUrl = url
  150. fs.readFile({
  151. filePath: url,
  152. encoding: '', // 不指定编码以获取原始二进制数据
  153. success: (obj) => {
  154. fs.getFileInfo({
  155. "filePath": url, "digestAlgorithm": "md5", success: (res) => {
  156. console.log("md5:", res.digest)
  157. console.log("加载文件成功:", obj.data.byteLength, obj.data.length, obj.getFileInfo)
  158. let binData = obj.data;
  159. _this.sliceDataIntoChunks(binData, 64);
  160. console.log("加载文件成功2:", binData.byteLength, binData.length)
  161. _this.data._imageBufferLength = binData.byteLength ?? binData.length
  162. wx.hideLoading();
  163. wx.showLoading({
  164. title: '开始传输壁纸',
  165. })
  166. _this.data._imgMD5 = res.digest
  167. _this.startImage()
  168. }
  169. })
  170. },
  171. fail: (err) => {
  172. console.error('读取 .bin 文件失败:', err);
  173. },
  174. });
  175. },
  176. loadimage(e) {
  177. wx.hideLoading();
  178. console.log('壁纸', e);
  179. this.cropper.imgReset();
  180. },
  181. clickcut(e) {
  182. console.log("clickcut:", e.detail);
  183. //壁纸预览
  184. wx.previewImage({
  185. current: e.detail.url,
  186. urls: [e.detail.url]
  187. })
  188. },
  189. cancel() {
  190. wx.navigateBack({
  191. delta: -1
  192. })
  193. },
  194. submit() {
  195. let _this = this
  196. // 读取裁剪的jpg图片
  197. const fs = wx.getFileSystemManager();
  198. this.cropper.getImg((obj) => {
  199. wx.showLoading({
  200. title: '壁纸裁剪中',
  201. })
  202. // app.globalData.imgSrc = obj.url;
  203. console.log("裁剪壁纸:", obj);
  204. _this.data._imgUrl = obj.url
  205. fs.readFile({
  206. filePath: obj.url,
  207. encoding: '', // 不指定编码以获取原始二进制数据
  208. success: (obj) => {
  209. console.log("加载文件成功:", obj.data.byteLength, obj.data.length)
  210. fs.getFileInfo({
  211. "filePath": url, "digestAlgorithm": "md5", success: (res) => {
  212. console.log("md5:", res)
  213. console.log("加载文件成功:", obj.data.byteLength, obj.data.length, obj.getFileInfo)
  214. let binData = obj.data;
  215. _this.sliceDataIntoChunks(binData, 64);
  216. console.log("加载文件成功2:", binData.byteLength, binData.length)
  217. _this.data._imageBufferLength = binData.byteLength ?? binData.length
  218. wx.hideLoading();
  219. wx.showLoading({
  220. title: '开始传输壁纸',
  221. })
  222. _this.startImage()
  223. }
  224. })
  225. },
  226. fail: (err) => {
  227. console.error('读取 .bin 文件失败:', err);
  228. },
  229. });
  230. });
  231. // 651kb的
  232. // _this.downloadAndSaveFile("https://music-play.oss-cn-shenzhen.aliyuncs.com/backOss/file/8770a6097d9940b59032d099b2fdde3b.bin");
  233. },
  234. // 下载网络文件并保存到本地
  235. downloadAndSaveFile(url) {
  236. const fs = wx.getFileSystemManager();
  237. let _this = this;
  238. wx.downloadFile({
  239. url: url, // 网络文件的 URL
  240. success: (res) => {
  241. if (res.statusCode === 200) {
  242. // 下载成功,res.tempFilePath 是临时文件路径
  243. const tempFilePath = res.tempFilePath;
  244. console.log("下载文件:", res)
  245. fs.readFile({
  246. filePath: tempFilePath,
  247. // encoding: '', // 不指定编码以获取原始二进制数据
  248. success: (res) => {
  249. console.log("下载文件成功:", res.data)
  250. let rgbData = res.data
  251. _this.data._imageBufferLength = rgbData.byteLength
  252. wx.hideLoading();
  253. wx.showLoading({
  254. title: '开始传输壁纸',
  255. })
  256. _this.startImage(rgbData);
  257. },
  258. fail: (err) => {
  259. console.error('读取 .bin 文件失败:', err);
  260. },
  261. });
  262. } else {
  263. console.error('下载文件失败:', res.statusCode);
  264. }
  265. },
  266. fail: (err) => {
  267. console.error('下载文件失败:', err);
  268. },
  269. });
  270. },
  271. checkAndRequestImagePermission: function () {
  272. const _this = this;
  273. // 检查用户是否已经授权访问相册
  274. wx.getSetting({
  275. success(res) {
  276. if (!res.authSetting['scope.writePhotosAlbum']) {
  277. // 用户未授权访问相册,请求用户授权
  278. wx.authorize({
  279. scope: 'scope.writePhotosAlbum',
  280. success() {
  281. // 用户同意授权
  282. console.log('用户已授权访问相册1');
  283. _this.upload()
  284. // 可以在这里执行访问相册的操作
  285. },
  286. fail() {
  287. // 用户拒绝授权
  288. console.log('用户拒绝授权访问相册');
  289. wx.showModal({
  290. title: '提示',
  291. content: '您拒绝了访问相册的权限,无法上传壁纸',
  292. showCancel: false,
  293. success(res) {
  294. if (res.confirm) {
  295. // 跳转到设置页面
  296. wx.openSetting({
  297. success(settingRes) {
  298. if (settingRes.authSetting['scope.writePhotosAlbum']) {
  299. console.log('用户已在设置中开启访问相册的权限');
  300. // 可以在这里执行访问相册的操作
  301. _this.upload(); //上传壁纸
  302. } else {
  303. console.log('用户仍未授权访问相册');
  304. }
  305. }
  306. });
  307. }
  308. }
  309. });
  310. }
  311. });
  312. } else {
  313. // 用户已授权访问相册
  314. console.log('用户已授权访问相册2');
  315. // 可以在这里执行访问相册的操作
  316. }
  317. }
  318. });
  319. },
  320. async startImage() {
  321. BtHelper.sendCallBack(BtCmd.wallPaper(1), function (res) {
  322. if (!res) {
  323. wx.hideLoading()
  324. wx.showToast({
  325. title: '发送失败',
  326. icon: 'none'
  327. })
  328. }
  329. });
  330. },
  331. sliceDataIntoChunks(data, chunkSize) {
  332. let buffer = data;
  333. console.log("发送壁纸数据:", buffer.byteLength, buffer.length)
  334. let total = buffer.byteLength || buffer.length
  335. const chunks = [];
  336. // for (let i = 0; i < data.length; i += chunkSize) {
  337. for (let i = 0; i < total; i += chunkSize) {
  338. const chunk = buffer.slice(i, i + chunkSize);
  339. chunks.push(chunk);
  340. }
  341. // const sliceSize = chunkSize / 2; // 每个 RGB565 占 2 字节
  342. // const chunks = [];
  343. // for (let i = 0; i < total; i += sliceSize) {
  344. // chunks.push(buffer.slice(i, i + sliceSize));
  345. // }
  346. this.data._chunks = chunks;
  347. },
  348. sendImageMD5() {
  349. let md5Code = BtCmd.stringToUint8Array(this.data._imgMD5)
  350. let lengthCode = BtCmd.intToHex(this.data.length)
  351. let value = BtCmd.sendWiFiInfo(md5Code, lengthCode)
  352. BtHelper.sendCallBack(BtCmd.wallPaperMD5(value), function (res) {
  353. if (!res) {
  354. wx.hideLoading()
  355. wx.showToast({
  356. title: '发送失败',
  357. icon: 'none'
  358. })
  359. }
  360. });
  361. },
  362. async sendImage(i) {
  363. let _this = this
  364. wx.hideLoading()
  365. let chunks = _this.data._chunks
  366. let next = 0;
  367. let total = _this.data._imageBufferLength;
  368. let btHelper = BtHelper.getInstance();
  369. // let i = 0;
  370. // _this.data._timer = setInterval(async () => {
  371. if (i > chunks.length - 1) {
  372. wx.showModal({
  373. title: '壁纸上传成功:' + i + "总:" + chunks.length,
  374. showCancel: false,
  375. success(res) {
  376. if (res.confirm) {
  377. const pages = getCurrentPages();
  378. // 获取上一级页面实例
  379. const prevPage = pages[pages.length - 2];
  380. // 传递参数
  381. prevPage.setData({
  382. topImg: {
  383. "pic": _this.data._imgUrl,
  384. }
  385. });
  386. wx.navigateBack({
  387. delta: 1,
  388. })
  389. }
  390. }
  391. })
  392. _this.endImage(0)
  393. return;
  394. }
  395. const chunk = chunks[i];
  396. next += (chunk.byteLength ?? chunk.length);
  397. // let uint8Array = new Uint8Array(chunk);
  398. // let unit16 = uint8Array.map(item => {
  399. // return item.toString(16)
  400. // });
  401. console.log("发送壁纸数据1:", i, ":", next, ":", chunk.length, ":", chunk.byteLength, ":", total, chunks.length)
  402. let res = await btHelper.wallPaperSyncData(chunk);
  403. // let res = true;
  404. // btHelper.wallPaperData(chunk);
  405. if (!res) {
  406. wx.showModal({
  407. title: '壁纸上传失败',
  408. showCancel: false
  409. })
  410. _this.endImage(2)
  411. }
  412. _this.updateProgress(next, total);
  413. i++;
  414. _this.data._imgIndex = i;
  415. // }, 30);
  416. },
  417. async delay(ms) {
  418. return new Promise(resolve => setTimeout(resolve, ms));
  419. },
  420. endImage(value) {
  421. let _this = this
  422. // console.log("结束壁纸上传:", value)
  423. if (_this.data._timer) {
  424. clearInterval(_this.data._timer)
  425. _this.data._timer = null
  426. }
  427. _this.setData({
  428. progress: 0,
  429. showProgress: false
  430. })
  431. _this.data._chunks = []
  432. BtHelper.getInstance().wallPaper(value);
  433. },
  434. startProgress() {
  435. this.setData({
  436. progress: 0,
  437. showProgress: true
  438. })
  439. },
  440. updateProgress(chunk, total) {
  441. let progress = parseFloat((chunk / total * 100).toFixed(2));
  442. let _this = this
  443. if (chunk >= total) {
  444. _this.setData({
  445. progress: 100,
  446. showCropImg: false
  447. });
  448. } else {
  449. _this.setData({
  450. progress: progress,
  451. });
  452. }
  453. },
  454. failSend(showToast) {
  455. let _this = this
  456. _this.data._chunks = null
  457. _this.data._imgUrl = null
  458. _this.data._imgMD5 = null
  459. _this.data._imgIndex = 0
  460. wx.hideLoading()
  461. if (showToast) {
  462. wx.showModal({
  463. title: '壁纸上传失败了',
  464. showCancel: false
  465. })
  466. }
  467. _this.setData({
  468. progress: 0,
  469. showCropImg: false
  470. });
  471. },
  472. /**
  473. * 生命周期函数--监听页面加载
  474. */
  475. onLoad(options) {
  476. this.checkAndRequestImagePermission()
  477. this.cropper = this.selectComponent("#image-cropper");
  478. this.cropper.imgReset();
  479. // this.setData({
  480. // scr:json,
  481. // })
  482. this.upload(); //上传壁纸
  483. let _this = this;
  484. EventManager.addNotification(CmdEvent.eventName, function (event) {
  485. let name = event.cmdEvent;
  486. console.log("裁剪页:", EnumCmdEvent)
  487. let value = event.wallpaper;
  488. switch (name) {
  489. case EnumCmdEvent.wallpaper:
  490. let kind = event.heiJiaoKind;
  491. console.log("裁剪页:", value, kind)
  492. // 小程序:0x78, 1。 开启壁纸
  493. // 黑胶回:0x78, 2, 1 ,1。 0x78, 2, 0, 2 。 1成功,2失败
  494. // 小程序:0x80, [参考wifi指令], [0x22,总长度,0x33,包长,包数,0x44,md5长,md5]
  495. // 黑胶回:0x80, 1, 1。 0x80, 0, 2。 1成功,2失败
  496. // 小程序:0x79, 数据长度,数据
  497. // 黑胶回:0x79, 1, 1 。0x79, 0, 2 。 1成功,2失败
  498. // 小程序:0x78, 0。结束了
  499. // 黑胶回:0x78, 2, 0, 1 。 0x78, 2, 0, 3 。 0流程成功,3流程失败
  500. // 小程序: 0x78, 2。异常结束
  501. if (value === 1 && kind == 1) {
  502. // 开始校验md5
  503. _this.sendImageMD5()
  504. // _this.sendImage()
  505. _this.startProgress()
  506. } else if (value === 0 && (kind == 2 || kind == 3)) {
  507. // 发送失败
  508. _this.failSend(true)
  509. } else if (value === 0 && kind == 1) {
  510. // 发送完成
  511. _this.failSend(false)
  512. wx.showToast({
  513. title: '壁纸发送成功',
  514. icon: 'success'
  515. })
  516. }
  517. break;
  518. case EnumCmdEvent.wallPaperMD5:
  519. console.log("开始发送MD5")
  520. // 收到回复md5,开始发送图片
  521. if (kind == 1 && value === 1) {
  522. _this.sendImage(0)
  523. } else {
  524. _this.failSend(true)
  525. }
  526. break;
  527. case EnumCmdEvent.wallPaperData:
  528. // 收到回复md5,开始发送图片
  529. if (kind == 1 && value === 1) {
  530. _this.sendImage(_this.data._imgIndex)
  531. } else {
  532. _this.failSend(true)
  533. }
  534. break;
  535. default:
  536. break;
  537. }
  538. }, this)
  539. },
  540. /**
  541. * 生命周期函数--监听页面初次渲染完成
  542. */
  543. onReady() {
  544. },
  545. /**
  546. * 生命周期函数--监听页面显示
  547. */
  548. onShow() {
  549. },
  550. /**
  551. * 生命周期函数--监听页面隐藏
  552. */
  553. onHide() {
  554. },
  555. /**
  556. * 生命周期函数--监听页面卸载
  557. */
  558. onUnload() {
  559. EventManager.removeNotification(CmdEvent.eventName, this);
  560. },
  561. /**
  562. * 页面相关事件处理函数--监听用户下拉动作
  563. */
  564. onPullDownRefresh() {
  565. },
  566. /**
  567. * 页面上拉触底事件的处理函数
  568. */
  569. onReachBottom() {
  570. },
  571. /**
  572. * 用户点击右上角分享
  573. */
  574. onShareAppMessage() {
  575. }
  576. })