cropper.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  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)
  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. console.log("md5 2:", _this.data._imgMD5)
  168. _this.startImage()
  169. }
  170. })
  171. },
  172. fail: (err) => {
  173. console.error('读取 .bin 文件失败:', err);
  174. },
  175. });
  176. },
  177. loadimage(e) {
  178. wx.hideLoading();
  179. console.log('壁纸', e);
  180. this.cropper.imgReset();
  181. },
  182. clickcut(e) {
  183. console.log("clickcut:", e.detail);
  184. //壁纸预览
  185. wx.previewImage({
  186. current: e.detail.url,
  187. urls: [e.detail.url]
  188. })
  189. },
  190. cancel() {
  191. wx.navigateBack({
  192. delta: -1
  193. })
  194. },
  195. submit() {
  196. let _this = this
  197. // 读取裁剪的jpg图片
  198. const fs = wx.getFileSystemManager();
  199. this.cropper.getImg((obj) => {
  200. wx.showLoading({
  201. title: '壁纸裁剪中',
  202. })
  203. // app.globalData.imgSrc = obj.url;
  204. console.log("裁剪壁纸:", obj);
  205. _this.data._imgUrl = obj.url
  206. fs.readFile({
  207. filePath: obj.url,
  208. encoding: '', // 不指定编码以获取原始二进制数据
  209. success: (obj) => {
  210. console.log("加载文件成功:", obj.data.byteLength, obj.data.length)
  211. fs.getFileInfo({
  212. "filePath": _this.data._imgUrl, "digestAlgorithm": "md5", success: (res) => {
  213. console.log("md5:", res)
  214. console.log("加载文件成功:", obj.data.byteLength, obj.data.length, obj.getFileInfo)
  215. let binData = obj.data;
  216. _this.sliceDataIntoChunks(binData, 64);
  217. console.log("加载文件成功2:", binData.byteLength, binData.length)
  218. _this.data._imageBufferLength = binData.byteLength ?? binData.length
  219. wx.hideLoading();
  220. wx.showLoading({
  221. title: '开始传输壁纸',
  222. })
  223. _this.data._imgMD5 = res.digest
  224. console.log("md5 2:", _this.data._imgMD5)
  225. _this.startImage()
  226. }, fail: (err) => {
  227. console.error('getFileInfo失败:', err);
  228. },
  229. })
  230. },
  231. fail: (err) => {
  232. console.error('读取 .bin 文件失败:', err);
  233. },
  234. });
  235. });
  236. // 651kb的
  237. // _this.downloadAndSaveFile("https://music-play.oss-cn-shenzhen.aliyuncs.com/backOss/file/8770a6097d9940b59032d099b2fdde3b.bin");
  238. },
  239. // 下载网络文件并保存到本地
  240. downloadAndSaveFile(url) {
  241. const fs = wx.getFileSystemManager();
  242. let _this = this;
  243. wx.downloadFile({
  244. url: url, // 网络文件的 URL
  245. success: (res) => {
  246. if (res.statusCode === 200) {
  247. // 下载成功,res.tempFilePath 是临时文件路径
  248. const tempFilePath = res.tempFilePath;
  249. console.log("下载文件:", res)
  250. fs.readFile({
  251. filePath: tempFilePath,
  252. // encoding: '', // 不指定编码以获取原始二进制数据
  253. success: (res) => {
  254. console.log("下载文件成功:", res.data)
  255. let rgbData = res.data
  256. _this.data._imageBufferLength = rgbData.byteLength
  257. wx.hideLoading();
  258. wx.showLoading({
  259. title: '开始传输壁纸',
  260. })
  261. _this.startImage(rgbData);
  262. },
  263. fail: (err) => {
  264. console.error('读取 .bin 文件失败:', err);
  265. },
  266. });
  267. } else {
  268. console.error('下载文件失败:', res.statusCode);
  269. }
  270. },
  271. fail: (err) => {
  272. console.error('下载文件失败:', err);
  273. },
  274. });
  275. },
  276. checkAndRequestImagePermission: function () {
  277. const _this = this;
  278. // 检查用户是否已经授权访问相册
  279. wx.getSetting({
  280. success(res) {
  281. if (!res.authSetting['scope.writePhotosAlbum']) {
  282. // 用户未授权访问相册,请求用户授权
  283. wx.authorize({
  284. scope: 'scope.writePhotosAlbum',
  285. success() {
  286. // 用户同意授权
  287. console.log('用户已授权访问相册1');
  288. _this.upload()
  289. // 可以在这里执行访问相册的操作
  290. },
  291. fail() {
  292. // 用户拒绝授权
  293. console.log('用户拒绝授权访问相册');
  294. wx.showModal({
  295. title: '提示',
  296. content: '您拒绝了访问相册的权限,无法上传壁纸',
  297. showCancel: false,
  298. success(res) {
  299. if (res.confirm) {
  300. // 跳转到设置页面
  301. wx.openSetting({
  302. success(settingRes) {
  303. if (settingRes.authSetting['scope.writePhotosAlbum']) {
  304. console.log('用户已在设置中开启访问相册的权限');
  305. // 可以在这里执行访问相册的操作
  306. _this.upload(); //上传壁纸
  307. } else {
  308. console.log('用户仍未授权访问相册');
  309. }
  310. }
  311. });
  312. }
  313. }
  314. });
  315. }
  316. });
  317. } else {
  318. // 用户已授权访问相册
  319. console.log('用户已授权访问相册2');
  320. // 可以在这里执行访问相册的操作
  321. }
  322. }
  323. });
  324. },
  325. async startImage() {
  326. console.log("开始发送壁纸数据: 0x78",)
  327. BtHelper.sendCallBack(BtCmd.wallPaper(1), function (res) {
  328. if (!res) {
  329. wx.hideLoading()
  330. wx.showToast({
  331. title: '发送失败',
  332. icon: 'none'
  333. })
  334. }
  335. });
  336. },
  337. sliceDataIntoChunks(data, chunkSize) {
  338. let buffer = data;
  339. console.log("发送壁纸数据:", buffer.byteLength, buffer.length)
  340. let total = buffer.byteLength || buffer.length
  341. const chunks = [];
  342. // for (let i = 0; i < data.length; i += chunkSize) {
  343. for (let i = 0; i < total; i += chunkSize) {
  344. const chunk = buffer.slice(i, i + chunkSize);
  345. chunks.push(chunk);
  346. }
  347. // const sliceSize = chunkSize / 2; // 每个 RGB565 占 2 字节
  348. // const chunks = [];
  349. // for (let i = 0; i < total; i += sliceSize) {
  350. // chunks.push(buffer.slice(i, i + sliceSize));
  351. // }
  352. this.data._chunks = chunks;
  353. },
  354. sendImageMD5() {
  355. let lengthCode = this.data._chunks.length
  356. console.log("发送壁纸MD5 1:", lengthCode, this.data._imgMD5)
  357. let value = BtCmd.sendWiFiInfo("99", this.data._imgMD5)
  358. console.log("发送壁纸MD5 2:", value)
  359. BtHelper.sendCallBack(BtCmd.wallPaperMD5(value), function (res) {
  360. if (!res) {
  361. wx.hideLoading()
  362. wx.showToast({
  363. title: '发送失败',
  364. icon: 'none'
  365. })
  366. }
  367. });
  368. },
  369. async sendImage(i) {
  370. let _this = this
  371. wx.hideLoading()
  372. let chunks = _this.data._chunks
  373. let next = 0;
  374. let total = _this.data._imageBufferLength;
  375. let btHelper = BtHelper.getInstance();
  376. // let i = 0;
  377. // _this.data._timer = setInterval(async () => {
  378. if (i > chunks.length - 1) {
  379. wx.showModal({
  380. title: '壁纸上传成功:' + i + "总:" + chunks.length,
  381. showCancel: false,
  382. success(res) {
  383. if (res.confirm) {
  384. const pages = getCurrentPages();
  385. // 获取上一级页面实例
  386. const prevPage = pages[pages.length - 2];
  387. // 传递参数
  388. prevPage.setData({
  389. topImg: {
  390. "pic": _this.data._imgUrl,
  391. }
  392. });
  393. wx.navigateBack({
  394. delta: 1,
  395. })
  396. }
  397. }
  398. })
  399. _this.endImage(0)
  400. return;
  401. }
  402. const chunk = chunks[i];
  403. next += (chunk.byteLength ?? chunk.length);
  404. // let uint8Array = new Uint8Array(chunk);
  405. // let unit16 = uint8Array.map(item => {
  406. // return item.toString(16)
  407. // });
  408. console.log("发送壁纸数据1:", i, ":", next, ":", chunk.length, ":", chunk.byteLength, ":", total, chunks.length)
  409. let res = await btHelper.wallPaperSyncData(chunk);
  410. // let res = true;
  411. // btHelper.wallPaperData(chunk);
  412. if (!res) {
  413. wx.showModal({
  414. title: '壁纸上传失败',
  415. showCancel: false
  416. })
  417. _this.endImage(2)
  418. }
  419. _this.updateProgress(next, total);
  420. i++;
  421. _this.data._imgIndex = i;
  422. // }, 30);
  423. },
  424. async delay(ms) {
  425. return new Promise(resolve => setTimeout(resolve, ms));
  426. },
  427. endImage(value) {
  428. let _this = this
  429. // console.log("结束壁纸上传:", value)
  430. if (_this.data._timer) {
  431. clearInterval(_this.data._timer)
  432. _this.data._timer = null
  433. }
  434. _this.setData({
  435. progress: 0,
  436. showProgress: false
  437. })
  438. _this.data._chunks = []
  439. BtHelper.getInstance().wallPaper(value);
  440. },
  441. startProgress() {
  442. this.setData({
  443. progress: 0,
  444. showProgress: true
  445. })
  446. },
  447. updateProgress(chunk, total) {
  448. let progress = parseFloat((chunk / total * 100).toFixed(2));
  449. let _this = this
  450. if (chunk >= total) {
  451. _this.setData({
  452. progress: 100,
  453. showCropImg: false
  454. });
  455. } else {
  456. _this.setData({
  457. progress: progress,
  458. });
  459. }
  460. },
  461. failSend(showToast) {
  462. let _this = this
  463. _this.data._chunks = null
  464. _this.data._imgUrl = null
  465. _this.data._imgMD5 = null
  466. _this.data._imgIndex = 0
  467. wx.hideLoading()
  468. if (showToast) {
  469. wx.showModal({
  470. title: '壁纸上传失败了',
  471. showCancel: false
  472. })
  473. }
  474. _this.setData({
  475. progress: 0,
  476. showCropImg: false
  477. });
  478. },
  479. /**
  480. * 生命周期函数--监听页面加载
  481. */
  482. onLoad(options) {
  483. this.checkAndRequestImagePermission()
  484. this.cropper = this.selectComponent("#image-cropper");
  485. this.cropper.imgReset();
  486. // this.setData({
  487. // scr:json,
  488. // })
  489. this.upload(); //上传壁纸
  490. let _this = this;
  491. EventManager.addNotification(CmdEvent.eventName, function (event) {
  492. let name = event.cmdEvent;
  493. console.log("裁剪页:", EnumCmdEvent)
  494. let value = event.wallpaper;
  495. switch (name) {
  496. case EnumCmdEvent.wallpaper:
  497. let kind = event.heiJiaoKind;
  498. console.log("裁剪页:", value, kind)
  499. // 小程序:0x78, 1。 开启壁纸
  500. // 黑胶回:0x78, 2, 1 ,1。 0x78, 2, 0, 2 。 1成功,2失败
  501. // 小程序:0x80, [参考wifi指令], [0x22,总长度,0x33,包长,包数,0x44,md5长,md5]
  502. // 黑胶回:0x80, 1, 1。 0x80, 0, 2。 1成功,2失败
  503. // 小程序:0x79, 数据长度,数据
  504. // 黑胶回:0x79, 1, 1 。0x79, 0, 2 。 1成功,2失败
  505. // 小程序:0x78, 0。结束了
  506. // 黑胶回:0x78, 2, 0, 1 。 0x78, 2, 0, 3 。 0流程成功,3流程失败
  507. // 小程序: 0x78, 2。异常结束
  508. if (value === 1 && kind == 1) {
  509. // 开始校验md5
  510. _this.sendImageMD5()
  511. // _this.sendImage()
  512. _this.startProgress()
  513. } else if (value === 0 && (kind == 2 || kind == 3)) {
  514. // 发送失败
  515. _this.failSend(true)
  516. } else if (value === 0 && kind == 1) {
  517. // 发送完成
  518. _this.failSend(false)
  519. wx.showToast({
  520. title: '壁纸发送成功',
  521. icon: 'success'
  522. })
  523. }
  524. break;
  525. case EnumCmdEvent.wallPaperMD5:
  526. console.log("开始发送MD5")
  527. // 收到回复md5,开始发送图片
  528. if (kind == 1 && value === 1) {
  529. _this.sendImage(0)
  530. } else {
  531. _this.failSend(true)
  532. }
  533. break;
  534. case EnumCmdEvent.wallPaperData:
  535. // 收到回复md5,开始发送图片
  536. if (kind == 1 && value === 1) {
  537. _this.sendImage(_this.data._imgIndex)
  538. } else {
  539. _this.failSend(true)
  540. }
  541. break;
  542. default:
  543. break;
  544. }
  545. }, this)
  546. },
  547. /**
  548. * 生命周期函数--监听页面初次渲染完成
  549. */
  550. onReady() {
  551. },
  552. /**
  553. * 生命周期函数--监听页面显示
  554. */
  555. onShow() {
  556. },
  557. /**
  558. * 生命周期函数--监听页面隐藏
  559. */
  560. onHide() {
  561. },
  562. /**
  563. * 生命周期函数--监听页面卸载
  564. */
  565. onUnload() {
  566. EventManager.removeNotification(CmdEvent.eventName, this);
  567. },
  568. /**
  569. * 页面相关事件处理函数--监听用户下拉动作
  570. */
  571. onPullDownRefresh() {
  572. },
  573. /**
  574. * 页面上拉触底事件的处理函数
  575. */
  576. onReachBottom() {
  577. },
  578. /**
  579. * 用户点击右上角分享
  580. */
  581. onShareAppMessage() {
  582. }
  583. })