cropper.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  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 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";
  87. const hexString = "04 80 07 3c";
  88. // 将十六进制字符串转换为字节数组
  89. const bytes = hexString.split(' ').map(byte => parseInt(byte, 10));
  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] = byte
  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. const fs = wx.getFileSystemManager();
  212. this.cropper.getImg((obj) => {
  213. wx.showLoading({
  214. title: '壁纸裁剪中',
  215. })
  216. // app.globalData.imgSrc = obj.url;
  217. console.log("裁剪壁纸:", obj);
  218. _this.data._imgUrl = obj.url
  219. fs.readFile({
  220. filePath: obj.url,
  221. encoding: '', // 不指定编码以获取原始二进制数据
  222. success: (obj) => {
  223. console.log("加载文件成功:", obj.data.byteLength, obj.data.length)
  224. let binData = obj.data;
  225. _this.sliceDataIntoChunks(binData, 64);
  226. console.log("加载文件成功2:", binData.byteLength, binData.length)
  227. _this.data._imageBufferLength = binData.byteLength ?? binData.length
  228. wx.hideLoading();
  229. wx.showLoading({
  230. title: '开始传输壁纸',
  231. })
  232. _this.startImage()
  233. },
  234. fail: (err) => {
  235. console.error('读取 .bin 文件失败:', err);
  236. },
  237. });
  238. });
  239. // 651kb的
  240. // _this.downloadAndSaveFile("https://music-play.oss-cn-shenzhen.aliyuncs.com/backOss/file/8770a6097d9940b59032d099b2fdde3b.bin");
  241. },
  242. // 下载网络文件并保存到本地
  243. downloadAndSaveFile(url) {
  244. const fs = wx.getFileSystemManager();
  245. let _this = this;
  246. wx.downloadFile({
  247. url: url, // 网络文件的 URL
  248. success: (res) => {
  249. if (res.statusCode === 200) {
  250. // 下载成功,res.tempFilePath 是临时文件路径
  251. const tempFilePath = res.tempFilePath;
  252. console.log("下载文件:", res)
  253. fs.readFile({
  254. filePath: tempFilePath,
  255. // encoding: '', // 不指定编码以获取原始二进制数据
  256. success: (res) => {
  257. console.log("下载文件成功:", res.data)
  258. let rgbData = res.data
  259. _this.data._imageBufferLength = rgbData.byteLength
  260. wx.hideLoading();
  261. wx.showLoading({
  262. title: '开始传输壁纸',
  263. })
  264. _this.startImage(rgbData);
  265. },
  266. fail: (err) => {
  267. console.error('读取 .bin 文件失败:', err);
  268. },
  269. });
  270. } else {
  271. console.error('下载文件失败:', res.statusCode);
  272. }
  273. },
  274. fail: (err) => {
  275. console.error('下载文件失败:', err);
  276. },
  277. });
  278. },
  279. checkAndRequestImagePermission: function () {
  280. const _this = this;
  281. // 检查用户是否已经授权访问相册
  282. wx.getSetting({
  283. success(res) {
  284. if (!res.authSetting['scope.writePhotosAlbum']) {
  285. // 用户未授权访问相册,请求用户授权
  286. wx.authorize({
  287. scope: 'scope.writePhotosAlbum',
  288. success() {
  289. // 用户同意授权
  290. console.log('用户已授权访问相册1');
  291. _this.upload()
  292. // 可以在这里执行访问相册的操作
  293. },
  294. fail() {
  295. // 用户拒绝授权
  296. console.log('用户拒绝授权访问相册');
  297. wx.showModal({
  298. title: '提示',
  299. content: '您拒绝了访问相册的权限,无法上传壁纸',
  300. showCancel: false,
  301. success(res) {
  302. if (res.confirm) {
  303. // 跳转到设置页面
  304. wx.openSetting({
  305. success(settingRes) {
  306. if (settingRes.authSetting['scope.writePhotosAlbum']) {
  307. console.log('用户已在设置中开启访问相册的权限');
  308. // 可以在这里执行访问相册的操作
  309. _this.upload(); //上传壁纸
  310. } else {
  311. console.log('用户仍未授权访问相册');
  312. }
  313. }
  314. });
  315. }
  316. }
  317. });
  318. }
  319. });
  320. } else {
  321. // 用户已授权访问相册
  322. console.log('用户已授权访问相册2');
  323. // 可以在这里执行访问相册的操作
  324. }
  325. }
  326. });
  327. },
  328. async startImage() {
  329. BtHelper.sendCallBack(BtCmd.wallPaper(1), function (res) {
  330. if (!res) {
  331. wx.hideLoading()
  332. wx.showToast({
  333. title: '发送失败',
  334. icon: 'none'
  335. })
  336. }
  337. });
  338. },
  339. sliceDataIntoChunks(data, chunkSize) {
  340. let buffer = data;
  341. console.log("发送壁纸数据:", buffer.byteLength, buffer.length)
  342. let total = buffer.byteLength || buffer.length
  343. const chunks = [];
  344. // for (let i = 0; i < data.length; i += chunkSize) {
  345. for (let i = 0; i < total; i += chunkSize) {
  346. const chunk = buffer.slice(i, i + chunkSize);
  347. chunks.push(chunk);
  348. }
  349. // const sliceSize = chunkSize / 2; // 每个 RGB565 占 2 字节
  350. // const chunks = [];
  351. // for (let i = 0; i < total; i += sliceSize) {
  352. // chunks.push(buffer.slice(i, i + sliceSize));
  353. // }
  354. this.data._chunks = chunks;
  355. },
  356. async sendImage() {
  357. let _this = this
  358. wx.hideLoading()
  359. let chunks = _this.data._chunks
  360. let next = 0;
  361. let total = _this.data._imageBufferLength;
  362. let btHelper = BtHelper.getInstance();
  363. let i = 0;
  364. _this.data._timer = setInterval(async () => {
  365. if (i > chunks.length - 1) {
  366. wx.showModal({
  367. title: '壁纸上传成功:' + i + "总:" + chunks.length,
  368. showCancel: false,
  369. success(res) {
  370. if (res.confirm) {
  371. const pages = getCurrentPages();
  372. // 获取上一级页面实例
  373. const prevPage = pages[pages.length - 2];
  374. // 传递参数
  375. prevPage.setData({
  376. topImg: {
  377. "pic": _this.data._imgUrl,
  378. }
  379. });
  380. wx.navigateBack({
  381. delta: 1,
  382. })
  383. }
  384. }
  385. })
  386. _this.endImage(0)
  387. return;
  388. }
  389. const chunk = chunks[i];
  390. next += (chunk.byteLength ?? chunk.length);
  391. // let uint8Array = new Uint8Array(chunk);
  392. // let unit16 = uint8Array.map(item => {
  393. // return item.toString(16)
  394. // });
  395. console.log("发送壁纸数据1:", i, ":", next, ":", chunk.length, ":", chunk.byteLength, ":", total, chunks.length)
  396. let res = await btHelper.wallPaperSyncData(chunk);
  397. // let res = true;
  398. // btHelper.wallPaperData(chunk);
  399. if (!res) {
  400. wx.showModal({
  401. title: '壁纸上传失败',
  402. showCancel: false
  403. })
  404. _this.endImage(2)
  405. }
  406. _this.updateProgress(next, total);
  407. i++;
  408. }, 5);
  409. },
  410. async delay(ms) {
  411. return new Promise(resolve => setTimeout(resolve, ms));
  412. },
  413. endImage(value) {
  414. let _this = this
  415. // console.log("结束壁纸上传:", value)
  416. if (_this.data._timer) {
  417. clearInterval(_this.data._timer)
  418. _this.data._timer = null
  419. }
  420. _this.setData({
  421. progress: 0,
  422. showProgress: false
  423. })
  424. _this.data._chunks = []
  425. BtHelper.getInstance().wallPaper(value);
  426. },
  427. startProgress() {
  428. this.setData({
  429. progress: 0,
  430. showProgress: true
  431. })
  432. },
  433. updateProgress(chunk, total) {
  434. let progress = parseFloat((chunk / total * 100).toFixed(2));
  435. let _this = this
  436. if (chunk >= total) {
  437. _this.setData({
  438. progress: 100,
  439. showCropImg: false
  440. });
  441. } else {
  442. _this.setData({
  443. progress: progress,
  444. });
  445. }
  446. },
  447. /**
  448. * 生命周期函数--监听页面加载
  449. */
  450. onLoad(options) {
  451. this.checkAndRequestImagePermission()
  452. this.cropper = this.selectComponent("#image-cropper");
  453. this.cropper.imgReset();
  454. // this.setData({
  455. // scr:json,
  456. // })
  457. this.upload(); //上传壁纸
  458. let _this = this;
  459. EventManager.addNotification(CmdEvent.eventName, function (event) {
  460. let name = event.cmdEvent;
  461. console.log("裁剪页:", EnumCmdEvent)
  462. switch (name) {
  463. case EnumCmdEvent.wallpaper:
  464. let otaCmd = event.wallpaper;
  465. let kind = event.heiJiaoKind;
  466. console.log("裁剪页:", otaCmd, kind)
  467. if (otaCmd === 1 && kind == 1) {
  468. // 开始发送
  469. _this.sendImage()
  470. _this.startProgress()
  471. } else if (otaCmd === 0 && kind == 1) {
  472. _this.setData({
  473. progress: 0,
  474. showCropImg: false
  475. });
  476. // 发送结束
  477. } else if (kind == 0) {
  478. wx.hideLoading()
  479. wx.showModal({
  480. title: '壁纸上传失败了',
  481. showCancel: false
  482. })
  483. _this.setData({
  484. progress: 0,
  485. showCropImg: false
  486. });
  487. if (_this.data._timer) {
  488. clearInterval(_this.data._timer)
  489. _this.data._timer = null
  490. }
  491. }
  492. break;
  493. default:
  494. break;
  495. }
  496. }, this)
  497. },
  498. /**
  499. * 生命周期函数--监听页面初次渲染完成
  500. */
  501. onReady() {
  502. },
  503. /**
  504. * 生命周期函数--监听页面显示
  505. */
  506. onShow() {
  507. },
  508. /**
  509. * 生命周期函数--监听页面隐藏
  510. */
  511. onHide() {
  512. },
  513. /**
  514. * 生命周期函数--监听页面卸载
  515. */
  516. onUnload() {
  517. EventManager.removeNotification(CmdEvent.eventName, this);
  518. },
  519. /**
  520. * 页面相关事件处理函数--监听用户下拉动作
  521. */
  522. onPullDownRefresh() {
  523. },
  524. /**
  525. * 页面上拉触底事件的处理函数
  526. */
  527. onReachBottom() {
  528. },
  529. /**
  530. * 用户点击右上角分享
  531. */
  532. onShareAppMessage() {
  533. }
  534. })