cropper.js 16 KB

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