cropper.js 17 KB

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