cropper.js 17 KB

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