image-cropper.js 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219
  1. Component({
  2. properties: {
  3. /**
  4. * 图片路径
  5. */
  6. 'imgSrc': {
  7. type: String
  8. },
  9. /**
  10. * 裁剪框高度
  11. */
  12. 'height': {
  13. type: Number,
  14. value: 200
  15. },
  16. /**
  17. * 裁剪框宽度
  18. */
  19. 'width': {
  20. type: Number,
  21. value: 200
  22. },
  23. /**
  24. * 裁剪框最小尺寸
  25. */
  26. 'min_width': {
  27. type: Number,
  28. value: 100
  29. },
  30. 'min_height': {
  31. type: Number,
  32. value: 100
  33. },
  34. /**
  35. * 裁剪框最大尺寸
  36. */
  37. 'max_width': {
  38. type: Number,
  39. value: 300
  40. },
  41. 'max_height': {
  42. type: Number,
  43. value: 300
  44. },
  45. /**
  46. * 裁剪框禁止拖动
  47. */
  48. 'disable_width': {
  49. type: Boolean,
  50. value: true
  51. },
  52. 'disable_height': {
  53. type: Boolean,
  54. value: true
  55. },
  56. /**
  57. * 锁定裁剪框比例
  58. */
  59. 'disable_ratio': {
  60. type: Boolean,
  61. value: false
  62. },
  63. /**
  64. * 生成的图片尺寸相对剪裁框的比例
  65. */
  66. 'export_scale': {
  67. type: Number,
  68. value: 3
  69. },
  70. /**
  71. * 生成的图片质量0-1
  72. */
  73. 'quality': {
  74. type: Number,
  75. value: 1
  76. },
  77. 'cut_top': {
  78. type: Number,
  79. value: null
  80. },
  81. 'cut_left': {
  82. type: Number,
  83. value: null
  84. },
  85. /**
  86. * canvas上边距(不设置默认不显示)
  87. */
  88. 'canvas_top': {
  89. type: Number,
  90. value: null
  91. },
  92. /**
  93. * canvas左边距(不设置默认不显示)
  94. */
  95. 'canvas_left': {
  96. type: Number,
  97. value: null
  98. },
  99. /**
  100. * 图片宽度
  101. */
  102. 'img_width': {
  103. type: null,
  104. value: null
  105. },
  106. /**
  107. * 图片高度
  108. */
  109. 'img_height': {
  110. type: null,
  111. value: null
  112. },
  113. /**
  114. * 图片缩放比
  115. */
  116. 'scale': {
  117. type: Number,
  118. value: 1
  119. },
  120. /**
  121. * 图片旋转角度
  122. */
  123. 'angle': {
  124. type: Number,
  125. value: 0
  126. },
  127. /**
  128. * 最小缩放比
  129. */
  130. 'min_scale': {
  131. type: Number,
  132. value: 0.5
  133. },
  134. /**
  135. * 最大缩放比
  136. */
  137. 'max_scale': {
  138. type: Number,
  139. value: 2
  140. },
  141. /**
  142. * 是否禁用旋转
  143. */
  144. 'disable_rotate': {
  145. type: Boolean,
  146. value: true
  147. },
  148. /**
  149. * 是否限制移动范围(剪裁框只能在图片内)
  150. */
  151. 'limit_move': {
  152. type: Boolean,
  153. value: false
  154. }
  155. },
  156. data: {
  157. el: 'image-cropper', //暂时无用
  158. info: wx.getSystemInfoSync(),
  159. MOVE_THROTTLE: null, //触摸移动节流settimeout
  160. MOVE_THROTTLE_FLAG: true, //节流标识
  161. INIT_IMGWIDTH: 0, //图片设置尺寸,此值不变(记录最初设定的尺寸)
  162. INIT_IMGHEIGHT: 0, //图片设置尺寸,此值不变(记录最初设定的尺寸)
  163. TIME_BG: null, //背景变暗延时函数
  164. TIME_CUT_CENTER: null,
  165. _touch_img_relative: [{
  166. x: 0,
  167. y: 0
  168. }], //鼠标和图片中心的相对位置
  169. _flag_cut_touch: false, //是否是拖动裁剪框
  170. _hypotenuse_length: 0, //双指触摸时斜边长度
  171. _flag_img_endtouch: false, //是否结束触摸
  172. _flag_bright: true, //背景是否亮
  173. _canvas_overflow: true, //canvas缩略图是否在屏幕外面
  174. _canvas_width: 200,
  175. _canvas_height: 200,
  176. origin_x: 0.5, //图片旋转中心
  177. origin_y: 0.5, //图片旋转中心
  178. _cut_animation: false, //是否开启图片和裁剪框过渡
  179. _img_top: wx.getSystemInfoSync().windowHeight / 2, //图片上边距
  180. _img_left: wx.getSystemInfoSync().windowWidth / 2, //图片左边距
  181. watch: {
  182. //监听截取框宽高变化
  183. width(value, that) {
  184. if (value < that.data.min_width) {
  185. that.setData({
  186. width: that.data.min_width
  187. });
  188. }
  189. that._computeCutSize();
  190. },
  191. height(value, that) {
  192. if (value < that.data.min_height) {
  193. that.setData({
  194. height: that.data.min_height
  195. });
  196. }
  197. that._computeCutSize();
  198. },
  199. angle(value, that) {
  200. //停止居中裁剪框,继续修改图片位置
  201. that._moveStop();
  202. if (that.data.limit_move) {
  203. if (that.data.angle % 90) {
  204. that.setData({
  205. angle: Math.round(that.data.angle / 90) * 90
  206. });
  207. return;
  208. }
  209. }
  210. },
  211. _cut_animation(value, that) {
  212. //开启过渡300毫秒之后自动关闭
  213. clearTimeout(that.data._cut_animation_time);
  214. if (value) {
  215. that.data._cut_animation_time = setTimeout(() => {
  216. that.setData({
  217. _cut_animation: false
  218. });
  219. }, 300)
  220. }
  221. },
  222. limit_move(value, that) {
  223. if (value) {
  224. if (that.data.angle % 90) {
  225. that.setData({
  226. angle: Math.round(that.data.angle / 90) * 90
  227. });
  228. }
  229. that._imgMarginDetectionScale();
  230. !that.data._canvas_overflow && that._draw();
  231. }
  232. },
  233. canvas_top(value, that) {
  234. that._canvasDetectionPosition();
  235. },
  236. canvas_left(value, that) {
  237. that._canvasDetectionPosition();
  238. },
  239. imgSrc(value, that) {
  240. that.pushImg();
  241. },
  242. cut_top(value, that) {
  243. that._cutDetectionPosition();
  244. if (that.data.limit_move) {
  245. !that.data._canvas_overflow && that._draw();
  246. }
  247. },
  248. cut_left(value, that) {
  249. that._cutDetectionPosition();
  250. if (that.data.limit_move) {
  251. !that.data._canvas_overflow && that._draw();
  252. }
  253. }
  254. }
  255. },
  256. attached() {
  257. this.data.info = wx.getSystemInfoSync();
  258. //启用数据监听
  259. this._watcher();
  260. this.data.INIT_IMGWIDTH = this.data.img_width;
  261. this.data.INIT_IMGHEIGHT = this.data.img_height;
  262. this.setData({
  263. _canvas_height: this.data.height,
  264. _canvas_width: this.data.width,
  265. });
  266. this._initCanvas();
  267. this.data.imgSrc && (this.data.imgSrc = this.data.imgSrc);
  268. //根据开发者设置的图片目标尺寸计算实际尺寸
  269. this._initImageSize();
  270. //设置裁剪框大小>设置图片尺寸>绘制canvas
  271. this._computeCutSize();
  272. //检查裁剪框是否在范围内
  273. this._cutDetectionPosition();
  274. //检查canvas是否在范围内
  275. this._canvasDetectionPosition();
  276. //初始化完成
  277. this.triggerEvent('load', {
  278. cropper: this
  279. });
  280. },
  281. methods: {
  282. /**
  283. * 上传图片
  284. */
  285. upload() {
  286. let that = this;
  287. wx.chooseMedia({
  288. count: 1,
  289. mediaType: ['image'],
  290. sourceType: ['album'],
  291. // camera: 'back',
  292. success(res) {
  293. const tempFilePaths = res.tempFiles[0].tempFilePath;
  294. that.pushImg(tempFilePaths);
  295. wx.showLoading({
  296. title: '加载中...'
  297. })
  298. }
  299. })
  300. },
  301. /**
  302. * 返回图片信息
  303. */
  304. getImg(getCallback) {
  305. this._draw(() => {
  306. wx.canvasToTempFilePath({
  307. width: this.data.width * this.data.export_scale,
  308. height: Math.round(this.data.height * this.data.export_scale),
  309. destWidth: this.data.width * this.data.export_scale,
  310. destHeight: Math.round(this.data.height) * this.data.export_scale,
  311. fileType: 'jpg',
  312. quality: this.data.quality,
  313. canvasId: this.data.el,
  314. success: (res) => {
  315. console.log("裁剪图片1", res.tempFilePath);
  316. getCallback({
  317. url: res.tempFilePath,
  318. width: this.data.width * this.data.export_scale,
  319. height: this.data.height * this.data.export_scale
  320. });
  321. },
  322. fail:(err) => {
  323. wx.showToast({
  324. title: '壁纸裁剪失败'+err,
  325. })
  326. }
  327. }, this)
  328. });
  329. },
  330. /**
  331. * 返回图片数据data
  332. */
  333. getImgData(getCallback) {
  334. this._draw(() => {
  335. wx.canvasGetImageData({
  336. canvasId: this.data.el,
  337. x: 0,
  338. y: 0,
  339. width: this.data.width * this.data.export_scale,
  340. height: Math.round(this.data.height * this.data.export_scale),
  341. destWidth: this.data.width * this.data.export_scale,
  342. destHeight: Math.round(this.data.height) * this.data.export_scale,
  343. quality: this.data.quality,
  344. success: (res) => {
  345. getCallback({
  346. data: res.data,
  347. width: this.data.width * this.data.export_scale,
  348. height: this.data.height * this.data.export_scale
  349. });
  350. }
  351. }, this)
  352. });
  353. },
  354. convertToStandardRGB(getCallback) {
  355. try {
  356. this.getImgData((res) => {
  357. console.log("获取像素数据成功", res.data);
  358. const rgbData = this._drawRGB565(res.data, res.width, res.height);
  359. console.log("转换后的 RGB 数据", rgbData.length, rgbData.byteLength);
  360. // 将转换后的 RGB 数据绘制回 Canvas
  361. wx.canvasPutImageData({
  362. canvasId: this.data.el,
  363. data: rgbData,
  364. x: 0,
  365. y: 0,
  366. width: res.width,
  367. height: res.height,
  368. success: (res2) => {
  369. console.log("绘制 RGB 数据成功", res2);
  370. wx.canvasToTempFilePath({
  371. width: res.width,
  372. height: res.height,
  373. destWidth: res.width,
  374. destHeight: res.height,
  375. fileType: 'jpg',
  376. quality: this.data.quality,
  377. canvasId: this.data.el,
  378. success: (res) => {
  379. console.log("转换后的图片路径", res.tempFilePath);
  380. getCallback({
  381. url: res.tempFilePath,
  382. width: res.width,
  383. height: res.height
  384. });
  385. },
  386. fail: (err) => {
  387. console.error('转换失败:', err);
  388. }
  389. }, this);
  390. },
  391. fail: (err) => {
  392. console.error('绘制 RGB 数据失败:', err);
  393. }
  394. }, this);
  395. });
  396. } catch (e) {
  397. console.log(e)
  398. }
  399. },
  400. // 将 16 位 RGB 565 数据绘制回 Canvas
  401. _drawRGB565(data, width, height, callback) {
  402. const ctx = wx.createCanvasContext(this.data.el, this);
  403. const imageData = ctx.createImageData(width, height);
  404. console.log("转换前的 RGB 数据", data.length, data.byteLength);
  405. for (let i = 0, j = 0; i < data.length; i++) {
  406. const pixel = data[i];
  407. const r = (pixel >> 11) & 0x1F;
  408. const g = (pixel >> 5) & 0x3F;
  409. const b = pixel & 0x1F;
  410. imageData.data[j++] = (r << 3) | (r >> 2); // R
  411. imageData.data[j++] = (g << 2) | (g >> 4); // G
  412. imageData.data[j++] = (b << 3) | (b >> 2); // B
  413. imageData.data[j++] = 255; // Alpha 设置为 255,确保没有透明通道
  414. }
  415. console.log("转换后的 RGB 数据", imageData.length, imageData.byteLength);
  416. ctx.putImageData(imageData, 0, 0);
  417. ctx.draw(false, callback);
  418. },
  419. /**
  420. * 设置图片动画
  421. * {
  422. * x:10,//图片在原有基础上向下移动10px
  423. * y:10,//图片在原有基础上向右移动10px
  424. * angle:10,//图片在原有基础上旋转10deg
  425. * scale:0.5,//图片在原有基础上增加0.5倍
  426. * }
  427. */
  428. setTransform(transform) {
  429. if (!transform) return;
  430. if (!this.data.disable_rotate) {
  431. this.setData({
  432. angle: transform.angle ? this.data.angle + transform.angle : this.data.angle
  433. });
  434. }
  435. var scale = this.data.scale;
  436. if (transform.scale) {
  437. scale = this.data.scale + transform.scale;
  438. scale = scale <= this.data.min_scale ? this.data.min_scale : scale;
  439. scale = scale >= this.data.max_scale ? this.data.max_scale : scale;
  440. }
  441. this.data.scale = scale;
  442. let cutX = this.data.cut_left;
  443. let cutY = this.data.cut_top;
  444. if (transform.cutX) {
  445. this.setData({
  446. cut_left: cutX + transform.cutX
  447. });
  448. this.data.watch.cut_left(null, this);
  449. }
  450. if (transform.cutY) {
  451. this.setData({
  452. cut_top: cutY + transform.cutY
  453. });
  454. this.data.watch.cut_top(null, this);
  455. }
  456. this.data._img_top = transform.y ? this.data._img_top + transform.y : this.data._img_top;
  457. this.data._img_left = transform.x ? this.data._img_left + transform.x : this.data._img_left;
  458. //图像边缘检测,防止截取到空白
  459. this._imgMarginDetectionScale();
  460. //停止居中裁剪框,继续修改图片位置
  461. this._moveDuring();
  462. this.setData({
  463. scale: this.data.scale,
  464. _img_top: this.data._img_top,
  465. _img_left: this.data._img_left
  466. });
  467. !this.data._canvas_overflow && this._draw();
  468. //可以居中裁剪框了
  469. this._moveStop(); //结束操作
  470. },
  471. /**
  472. * 设置剪裁框位置
  473. */
  474. setCutXY(x, y) {
  475. this.setData({
  476. cut_top: y,
  477. cut_left: x
  478. });
  479. },
  480. /**
  481. * 设置剪裁框尺寸
  482. */
  483. setCutSize(w, h) {
  484. this.setData({
  485. width: w,
  486. height: h
  487. });
  488. this._computeCutSize();
  489. },
  490. /**
  491. * 设置剪裁框和图片居中
  492. */
  493. setCutCenter() {
  494. let cut_top = (this.data.info.windowHeight - this.data.height) * 0.5;
  495. let cut_left = (this.data.info.windowWidth - this.data.width) * 0.5;
  496. //顺序不能变
  497. this.setData({
  498. _img_top: this.data._img_top - this.data.cut_top + cut_top,
  499. cut_top: cut_top, //截取的框上边距
  500. _img_left: this.data._img_left - this.data.cut_left + cut_left,
  501. cut_left: cut_left, //截取的框左边距
  502. });
  503. },
  504. _setCutCenter() {
  505. let cut_top = (this.data.info.windowHeight - this.data.height) * 0.5;
  506. let cut_left = (this.data.info.windowWidth - this.data.width) * 0.5;
  507. this.setData({
  508. cut_top: cut_top, //截取的框上边距
  509. cut_left: cut_left, //截取的框左边距
  510. });
  511. },
  512. /**
  513. * 设置剪裁框宽度-即将废弃
  514. */
  515. setWidth(width) {
  516. this.setData({
  517. width: width
  518. });
  519. this._computeCutSize();
  520. },
  521. /**
  522. * 设置剪裁框高度-即将废弃
  523. */
  524. setHeight(height) {
  525. this.setData({
  526. height: height
  527. });
  528. this._computeCutSize();
  529. },
  530. /**
  531. * 是否锁定旋转
  532. */
  533. setDisableRotate(value) {
  534. this.data.disable_rotate = value;
  535. },
  536. /**
  537. * 是否限制移动
  538. */
  539. setLimitMove(value) {
  540. this.setData({
  541. _cut_animation: true,
  542. limit_move: !!value
  543. });
  544. },
  545. /**
  546. * 初始化图片,包括位置、大小、旋转角度
  547. */
  548. imgReset() {
  549. this.setData({
  550. scale: 1,
  551. angle: 0,
  552. _img_top: wx.getSystemInfoSync().windowHeight / 2,
  553. _img_left: wx.getSystemInfoSync().windowWidth / 2,
  554. })
  555. },
  556. /**
  557. * 加载(更换)图片
  558. */
  559. pushImg(src) {
  560. if (src) {
  561. this.setData({
  562. imgSrc: src
  563. });
  564. //发现是手动赋值直接返回,交给watch处理
  565. return;
  566. }
  567. // getImageInfo接口传入 src: '' 会导致内存泄漏
  568. if (!this.data.imgSrc) return;
  569. wx.getImageInfo({
  570. src: this.data.imgSrc,
  571. success: (res) => {
  572. this.data.imageObject = res;
  573. //图片非本地路径需要换成本地路径
  574. if (this.data.imgSrc.search(/tmp/) == -1) {
  575. this.setData({
  576. imgSrc: res.path
  577. });
  578. }
  579. //计算最后图片尺寸
  580. this._imgComputeSize();
  581. if (this.data.limit_move) {
  582. //限制移动,不留空白处理
  583. this._imgMarginDetectionScale();
  584. }
  585. this._draw();
  586. },
  587. fail: (err) => {
  588. this.setData({
  589. imgSrc: ''
  590. });
  591. }
  592. });
  593. },
  594. imageLoad(e) {
  595. setTimeout(() => {
  596. this.triggerEvent('imageload', this.data.imageObject);
  597. }, 1000)
  598. },
  599. /**
  600. * 设置图片放大缩小
  601. */
  602. setScale(scale) {
  603. if (!scale) return;
  604. this.setData({
  605. scale: scale
  606. });
  607. !this.data._canvas_overflow && this._draw();
  608. },
  609. /**
  610. * 设置图片旋转角度
  611. */
  612. setAngle(angle) {
  613. if (!angle) return;
  614. this.setData({
  615. _cut_animation: true,
  616. angle: angle
  617. });
  618. this._imgMarginDetectionScale();
  619. !this.data._canvas_overflow && this._draw();
  620. },
  621. _initCanvas() {
  622. //初始化canvas
  623. if (!this.data.ctx) {
  624. console.log("创建canvas0");
  625. this.data.ctx = wx.createCanvasContext("image-cropper", this);
  626. }
  627. },
  628. /**
  629. * 根据开发者设置的图片目标尺寸计算实际尺寸
  630. */
  631. _initImageSize() {
  632. //处理宽高特殊单位 %>px
  633. if (this.data.INIT_IMGWIDTH && typeof this.data.INIT_IMGWIDTH == "string" && this.data.INIT_IMGWIDTH.indexOf("%") != -1) {
  634. let width = this.data.INIT_IMGWIDTH.replace("%", "");
  635. this.data.INIT_IMGWIDTH = this.data.img_width = this.data.info.windowWidth / 100 * width;
  636. }
  637. if (this.data.INIT_IMGHEIGHT && typeof this.data.INIT_IMGHEIGHT == "string" && this.data.INIT_IMGHEIGHT.indexOf("%") != -1) {
  638. let height = this.data.img_height.replace("%", "");
  639. this.data.INIT_IMGHEIGHT = this.data.img_height = this.data.info.windowHeight / 100 * height;
  640. }
  641. },
  642. /**
  643. * 检测剪裁框位置是否在允许的范围内(屏幕内)
  644. */
  645. _cutDetectionPosition() {
  646. let _cutDetectionPositionTop = () => {
  647. //检测上边距是否在范围内
  648. if (this.data.cut_top < 0) {
  649. this.setData({
  650. cut_top: 0
  651. });
  652. }
  653. if (this.data.cut_top > this.data.info.windowHeight - this.data.height) {
  654. this.setData({
  655. cut_top: this.data.info.windowHeight - this.data.height
  656. });
  657. }
  658. },
  659. _cutDetectionPositionLeft = () => {
  660. //检测左边距是否在范围内
  661. if (this.data.cut_left < 0) {
  662. this.setData({
  663. cut_left: 0
  664. });
  665. }
  666. if (this.data.cut_left > this.data.info.windowWidth - this.data.width) {
  667. this.setData({
  668. cut_left: this.data.info.windowWidth - this.data.width
  669. });
  670. }
  671. };
  672. //裁剪框坐标处理(如果只写一个参数则另一个默认为0,都不写默认居中)
  673. if (this.data.cut_top == null && this.data.cut_left == null) {
  674. this._setCutCenter();
  675. } else if (this.data.cut_top != null && this.data.cut_left != null) {
  676. _cutDetectionPositionTop();
  677. _cutDetectionPositionLeft();
  678. } else if (this.data.cut_top != null && this.data.cut_left == null) {
  679. _cutDetectionPositionTop();
  680. this.setData({
  681. cut_left: (this.data.info.windowWidth - this.data.width) / 2
  682. });
  683. } else if (this.data.cut_top == null && this.data.cut_left != null) {
  684. _cutDetectionPositionLeft();
  685. this.setData({
  686. cut_top: (this.data.info.windowHeight - this.data.height) / 2
  687. });
  688. }
  689. },
  690. /**
  691. * 检测canvas位置是否在允许的范围内(屏幕内)如果在屏幕外则不开启实时渲染
  692. * 如果只写一个参数则另一个默认为0,都不写默认超出屏幕外
  693. */
  694. _canvasDetectionPosition() {
  695. if (this.data.canvas_top == null && this.data.canvas_left == null) {
  696. this.data._canvas_overflow = false;
  697. this.setData({
  698. canvas_top: -5000,
  699. canvas_left: -5000
  700. });
  701. } else if (this.data.canvas_top != null && this.data.canvas_left != null) {
  702. if (this.data.canvas_top < -this.data.height || this.data.canvas_top > this.data.info.windowHeight) {
  703. this.data._canvas_overflow = true;
  704. } else {
  705. this.data._canvas_overflow = false;
  706. }
  707. } else if (this.data.canvas_top != null && this.data.canvas_left == null) {
  708. this.setData({
  709. canvas_left: 0
  710. });
  711. } else if (this.data.canvas_top == null && this.data.canvas_left != null) {
  712. this.setData({
  713. canvas_top: 0
  714. });
  715. if (this.data.canvas_left < -this.data.width || this.data.canvas_left > this.data.info.windowWidth) {
  716. this.data._canvas_overflow = true;
  717. } else {
  718. this.data._canvas_overflow = false;
  719. }
  720. }
  721. },
  722. /**
  723. * 图片边缘检测-位置
  724. */
  725. _imgMarginDetectionPosition(scale) {
  726. if (!this.data.limit_move) return;
  727. let left = this.data._img_left;
  728. let top = this.data._img_top;
  729. var scale = scale || this.data.scale;
  730. let img_width = this.data.img_width;
  731. let img_height = this.data.img_height;
  732. if (this.data.angle / 90 % 2) {
  733. img_width = this.data.img_height;
  734. img_height = this.data.img_width;
  735. }
  736. left = this.data.cut_left + img_width * scale / 2 >= left ? left : this.data.cut_left + img_width * scale / 2;
  737. left = this.data.cut_left + this.data.width - img_width * scale / 2 <= left ? left : this.data.cut_left + this.data.width - img_width * scale / 2;
  738. top = this.data.cut_top + img_height * scale / 2 >= top ? top : this.data.cut_top + img_height * scale / 2;
  739. top = this.data.cut_top + this.data.height - img_height * scale / 2 <= top ? top : this.data.cut_top + this.data.height - img_height * scale / 2;
  740. this.setData({
  741. _img_left: left,
  742. _img_top: top,
  743. scale: scale
  744. })
  745. },
  746. /**
  747. * 图片边缘检测-缩放
  748. */
  749. _imgMarginDetectionScale() {
  750. if (!this.data.limit_move) return;
  751. let scale = this.data.scale;
  752. let img_width = this.data.img_width;
  753. let img_height = this.data.img_height;
  754. if (this.data.angle / 90 % 2) {
  755. img_width = this.data.img_height;
  756. img_height = this.data.img_width;
  757. }
  758. if (img_width * scale < this.data.width) {
  759. scale = this.data.width / img_width;
  760. }
  761. if (img_height * scale < this.data.height) {
  762. scale = Math.max(scale, this.data.height / img_height);
  763. }
  764. this._imgMarginDetectionPosition(scale);
  765. },
  766. _setData(obj) {
  767. let data = {};
  768. for (var key in obj) {
  769. if (this.data[key] != obj[key]) {
  770. data[key] = obj[key];
  771. }
  772. }
  773. this.setData(data);
  774. return data;
  775. },
  776. /**
  777. * 计算图片尺寸
  778. */
  779. _imgComputeSize() {
  780. let img_width = this.data.img_width,
  781. img_height = this.data.img_height;
  782. if (!this.data.INIT_IMGHEIGHT && !this.data.INIT_IMGWIDTH) {
  783. //默认按图片最小边 = 对应裁剪框尺寸
  784. img_width = this.data.imageObject.width;
  785. img_height = this.data.imageObject.height;
  786. if (img_width / img_height > this.data.width / this.data.height) {
  787. img_height = this.data.height;
  788. img_width = this.data.imageObject.width / this.data.imageObject.height * img_height;
  789. } else {
  790. img_width = this.data.width;
  791. img_height = this.data.imageObject.height / this.data.imageObject.width * img_width;
  792. }
  793. } else if (this.data.INIT_IMGHEIGHT && !this.data.INIT_IMGWIDTH) {
  794. img_width = this.data.imageObject.width / this.data.imageObject.height * this.data.INIT_IMGHEIGHT;
  795. } else if (!this.data.INIT_IMGHEIGHT && this.data.INIT_IMGWIDTH) {
  796. img_height = this.data.imageObject.height / this.data.imageObject.width * this.data.INIT_IMGWIDTH;
  797. }
  798. this.setData({
  799. img_width: img_width,
  800. img_height: img_height
  801. });
  802. },
  803. //改变截取框大小
  804. _computeCutSize() {
  805. if (this.data.width > this.data.info.windowWidth) {
  806. this.setData({
  807. width: this.data.info.windowWidth,
  808. });
  809. } else if (this.data.width + this.data.cut_left > this.data.info.windowWidth) {
  810. this.setData({
  811. cut_left: this.data.info.windowWidth - this.data.cut_left,
  812. });
  813. };
  814. if (this.data.height > this.data.info.windowHeight) {
  815. this.setData({
  816. height: this.data.info.windowHeight,
  817. });
  818. } else if (this.data.height + this.data.cut_top > this.data.info.windowHeight) {
  819. this.setData({
  820. cut_top: this.data.info.windowHeight - this.data.cut_top,
  821. });
  822. } !this.data._canvas_overflow && this._draw();
  823. },
  824. //开始触摸
  825. _start(event) {
  826. this.data._flag_img_endtouch = false;
  827. if (event.touches.length == 1) {
  828. //单指拖动
  829. this.data._touch_img_relative[0] = {
  830. x: (event.touches[0].clientX - this.data._img_left),
  831. y: (event.touches[0].clientY - this.data._img_top)
  832. }
  833. } else {
  834. //双指放大
  835. let width = Math.abs(event.touches[0].clientX - event.touches[1].clientX);
  836. let height = Math.abs(event.touches[0].clientY - event.touches[1].clientY);
  837. this.data._touch_img_relative = [{
  838. x: (event.touches[0].clientX - this.data._img_left),
  839. y: (event.touches[0].clientY - this.data._img_top)
  840. }, {
  841. x: (event.touches[1].clientX - this.data._img_left),
  842. y: (event.touches[1].clientY - this.data._img_top)
  843. }];
  844. this.data._hypotenuse_length = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
  845. } !this.data._canvas_overflow && this._draw();
  846. },
  847. _move_throttle() {
  848. //安卓需要节流
  849. if (this.data.info.platform == 'android') {
  850. clearTimeout(this.data.MOVE_THROTTLE);
  851. this.data.MOVE_THROTTLE = setTimeout(() => {
  852. this.data.MOVE_THROTTLE_FLAG = true;
  853. }, 1000 / 40)
  854. return this.data.MOVE_THROTTLE_FLAG;
  855. } else {
  856. this.data.MOVE_THROTTLE_FLAG = true;
  857. }
  858. },
  859. _move(event) {
  860. if (this.data._flag_img_endtouch || !this.data.MOVE_THROTTLE_FLAG) return;
  861. this.data.MOVE_THROTTLE_FLAG = false;
  862. this._move_throttle();
  863. this._moveDuring();
  864. if (event.touches.length == 1) {
  865. //单指拖动
  866. let left = (event.touches[0].clientX - this.data._touch_img_relative[0].x),
  867. top = (event.touches[0].clientY - this.data._touch_img_relative[0].y);
  868. //图像边缘检测,防止截取到空白
  869. this.data._img_left = left;
  870. this.data._img_top = top;
  871. this._imgMarginDetectionPosition();
  872. this.setData({
  873. _img_left: this.data._img_left,
  874. _img_top: this.data._img_top
  875. });
  876. } else {
  877. //双指放大
  878. let width = (Math.abs(event.touches[0].clientX - event.touches[1].clientX)),
  879. height = (Math.abs(event.touches[0].clientY - event.touches[1].clientY)),
  880. hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)),
  881. scale = this.data.scale * (hypotenuse / this.data._hypotenuse_length),
  882. current_deg = 0;
  883. scale = scale <= this.data.min_scale ? this.data.min_scale : scale;
  884. scale = scale >= this.data.max_scale ? this.data.max_scale : scale;
  885. //图像边缘检测,防止截取到空白
  886. this.data.scale = scale;
  887. this._imgMarginDetectionScale();
  888. //双指旋转(如果没禁用旋转)
  889. let _touch_img_relative = [{
  890. x: (event.touches[0].clientX - this.data._img_left),
  891. y: (event.touches[0].clientY - this.data._img_top)
  892. }, {
  893. x: (event.touches[1].clientX - this.data._img_left),
  894. y: (event.touches[1].clientY - this.data._img_top)
  895. }];
  896. if (!this.data.disable_rotate) {
  897. let first_atan = 180 / Math.PI * Math.atan2(_touch_img_relative[0].y, _touch_img_relative[0].x);
  898. let first_atan_old = 180 / Math.PI * Math.atan2(this.data._touch_img_relative[0].y, this.data._touch_img_relative[0].x);
  899. let second_atan = 180 / Math.PI * Math.atan2(_touch_img_relative[1].y, _touch_img_relative[1].x);
  900. let second_atan_old = 180 / Math.PI * Math.atan2(this.data._touch_img_relative[1].y, this.data._touch_img_relative[1].x);
  901. //当前旋转的角度
  902. let first_deg = first_atan - first_atan_old,
  903. second_deg = second_atan - second_atan_old;
  904. if (first_deg != 0) {
  905. current_deg = first_deg;
  906. } else if (second_deg != 0) {
  907. current_deg = second_deg;
  908. }
  909. }
  910. this.data._touch_img_relative = _touch_img_relative;
  911. this.data._hypotenuse_length = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
  912. //更新视图
  913. this.setData({
  914. angle: this.data.angle + current_deg,
  915. scale: this.data.scale
  916. });
  917. } !this.data._canvas_overflow && this._draw();
  918. },
  919. //结束操作
  920. _end(event) {
  921. this.data._flag_img_endtouch = true;
  922. this._moveStop();
  923. },
  924. //点击中间剪裁框处理
  925. _click(event) {
  926. if (!this.data.imgSrc) {
  927. //调起上传
  928. this.upload();
  929. return;
  930. }
  931. this._draw(() => {
  932. let x = event.detail ? event.detail.x : event.touches[0].clientX;
  933. let y = event.detail ? event.detail.y : event.touches[0].clientY;
  934. if ((x >= this.data.cut_left && x <= (this.data.cut_left + this.data.width)) && (y >= this.data.cut_top && y <= (this.data.cut_top + this.data.height))) {
  935. //生成图片并回调
  936. wx.canvasToTempFilePath({
  937. width: this.data.width * this.data.export_scale,
  938. height: Math.round(this.data.height * this.data.export_scale),
  939. destWidth: this.data.width * this.data.export_scale,
  940. destHeight: Math.round(this.data.height) * this.data.export_scale,
  941. fileType: 'jpg',
  942. quality: this.data.quality,
  943. canvasId: this.data.el,
  944. success: (res) => {
  945. console.log("裁剪图片", res.tempFilePath);
  946. this.triggerEvent('tapcut', {
  947. url: res.tempFilePath,
  948. width: this.data.width * this.data.export_scale,
  949. height: this.data.height * this.data.export_scale
  950. });
  951. }
  952. }, this)
  953. }
  954. });
  955. },
  956. //渲染
  957. _draw(callback) {
  958. if (!this.data.imgSrc) return;
  959. let draw = () => {
  960. //图片实际大小
  961. let img_width = this.data.img_width * this.data.scale * this.data.export_scale;
  962. let img_height = this.data.img_height * this.data.scale * this.data.export_scale;
  963. //canvas和图片的相对距离
  964. var xpos = this.data._img_left - this.data.cut_left;
  965. var ypos = this.data._img_top - this.data.cut_top;
  966. //旋转画布
  967. this.data.ctx.translate(xpos * this.data.export_scale, ypos * this.data.export_scale);
  968. this.data.ctx.rotate(this.data.angle * Math.PI / 180);
  969. this.data.ctx.drawImage(this.data.imgSrc, -img_width / 2, -img_height / 2, img_width, img_height);
  970. this.data.ctx.draw(false, () => {
  971. callback && callback();
  972. });
  973. }
  974. if (this.data.ctx.width != this.data.width || this.data.ctx.height != this.data.height) {
  975. //优化拖动裁剪框,所以必须把宽高设置放在离用户触发渲染最近的地方
  976. this.setData({
  977. _canvas_height: this.data.height,
  978. _canvas_width: this.data.width,
  979. }, () => {
  980. //延迟40毫秒防止点击过快出现拉伸或裁剪过多
  981. setTimeout(() => {
  982. draw();
  983. }, 40);
  984. });
  985. } else {
  986. draw();
  987. }
  988. },
  989. //裁剪框处理
  990. _cutTouchMove(e) {
  991. if (this.data._flag_cut_touch && this.data.MOVE_THROTTLE_FLAG) {
  992. if (this.data.disable_ratio && (this.data.disable_width || this.data.disable_height)) return;
  993. //节流
  994. this.data.MOVE_THROTTLE_FLAG = false;
  995. this._move_throttle();
  996. let width = this.data.width,
  997. height = this.data.height,
  998. cut_top = this.data.cut_top,
  999. cut_left = this.data.cut_left,
  1000. size_correct = () => {
  1001. width = width <= this.data.max_width ? width >= this.data.min_width ? width : this.data.min_width : this.data.max_width;
  1002. height = height <= this.data.max_height ? height >= this.data.min_height ? height : this.data.min_height : this.data.max_height;
  1003. },
  1004. size_inspect = () => {
  1005. if ((width > this.data.max_width || width < this.data.min_width || height > this.data.max_height || height < this.data.min_height) && this.data.disable_ratio) {
  1006. size_correct();
  1007. return false;
  1008. } else {
  1009. size_correct();
  1010. return true;
  1011. }
  1012. };
  1013. height = this.data.CUT_START.height + ((this.data.CUT_START.corner > 1 && this.data.CUT_START.corner < 4 ? 1 : -1) * (this.data.CUT_START.y - e.touches[0].clientY));
  1014. switch (this.data.CUT_START.corner) {
  1015. case 1:
  1016. width = this.data.CUT_START.width + this.data.CUT_START.x - e.touches[0].clientX;
  1017. if (this.data.disable_ratio) {
  1018. height = width / (this.data.width / this.data.height)
  1019. }
  1020. if (!size_inspect()) return;
  1021. cut_left = this.data.CUT_START.cut_left - (width - this.data.CUT_START.width);
  1022. break
  1023. case 2:
  1024. width = this.data.CUT_START.width + this.data.CUT_START.x - e.touches[0].clientX;
  1025. if (this.data.disable_ratio) {
  1026. height = width / (this.data.width / this.data.height)
  1027. }
  1028. if (!size_inspect()) return;
  1029. cut_top = this.data.CUT_START.cut_top - (height - this.data.CUT_START.height)
  1030. cut_left = this.data.CUT_START.cut_left - (width - this.data.CUT_START.width)
  1031. break
  1032. case 3:
  1033. width = this.data.CUT_START.width - this.data.CUT_START.x + e.touches[0].clientX;
  1034. if (this.data.disable_ratio) {
  1035. height = width / (this.data.width / this.data.height)
  1036. }
  1037. if (!size_inspect()) return;
  1038. cut_top = this.data.CUT_START.cut_top - (height - this.data.CUT_START.height);
  1039. break
  1040. case 4:
  1041. width = this.data.CUT_START.width - this.data.CUT_START.x + e.touches[0].clientX;
  1042. if (this.data.disable_ratio) {
  1043. height = width / (this.data.width / this.data.height)
  1044. }
  1045. if (!size_inspect()) return;
  1046. break
  1047. }
  1048. if (!this.data.disable_width && !this.data.disable_height) {
  1049. this.setData({
  1050. width: width,
  1051. cut_left: cut_left,
  1052. height: height,
  1053. cut_top: cut_top,
  1054. })
  1055. } else if (!this.data.disable_width) {
  1056. this.setData({
  1057. width: width,
  1058. cut_left: cut_left
  1059. })
  1060. } else if (!this.data.disable_height) {
  1061. this.setData({
  1062. height: height,
  1063. cut_top: cut_top
  1064. })
  1065. }
  1066. this._imgMarginDetectionScale();
  1067. }
  1068. },
  1069. _cutTouchStart(e) {
  1070. let currentX = e.touches[0].clientX;
  1071. let currentY = e.touches[0].clientY;
  1072. let cutbox_top4 = this.data.cut_top + this.data.height - 30;
  1073. let cutbox_bottom4 = this.data.cut_top + this.data.height + 20;
  1074. let cutbox_left4 = this.data.cut_left + this.data.width - 30;
  1075. let cutbox_right4 = this.data.cut_left + this.data.width + 30;
  1076. let cutbox_top3 = this.data.cut_top - 30;
  1077. let cutbox_bottom3 = this.data.cut_top + 30;
  1078. let cutbox_left3 = this.data.cut_left + this.data.width - 30;
  1079. let cutbox_right3 = this.data.cut_left + this.data.width + 30;
  1080. let cutbox_top2 = this.data.cut_top - 30;
  1081. let cutbox_bottom2 = this.data.cut_top + 30;
  1082. let cutbox_left2 = this.data.cut_left - 30;
  1083. let cutbox_right2 = this.data.cut_left + 30;
  1084. let cutbox_top1 = this.data.cut_top + this.data.height - 30;
  1085. let cutbox_bottom1 = this.data.cut_top + this.data.height + 30;
  1086. let cutbox_left1 = this.data.cut_left - 30;
  1087. let cutbox_right1 = this.data.cut_left + 30;
  1088. if (currentX > cutbox_left4 && currentX < cutbox_right4 && currentY > cutbox_top4 && currentY < cutbox_bottom4) {
  1089. this._moveDuring();
  1090. this.data._flag_cut_touch = true;
  1091. this.data._flag_img_endtouch = true;
  1092. this.data.CUT_START = {
  1093. width: this.data.width,
  1094. height: this.data.height,
  1095. x: currentX,
  1096. y: currentY,
  1097. corner: 4
  1098. }
  1099. } else if (currentX > cutbox_left3 && currentX < cutbox_right3 && currentY > cutbox_top3 && currentY < cutbox_bottom3) {
  1100. this._moveDuring();
  1101. this.data._flag_cut_touch = true;
  1102. this.data._flag_img_endtouch = true;
  1103. this.data.CUT_START = {
  1104. width: this.data.width,
  1105. height: this.data.height,
  1106. x: currentX,
  1107. y: currentY,
  1108. cut_top: this.data.cut_top,
  1109. cut_left: this.data.cut_left,
  1110. corner: 3
  1111. }
  1112. } else if (currentX > cutbox_left2 && currentX < cutbox_right2 && currentY > cutbox_top2 && currentY < cutbox_bottom2) {
  1113. this._moveDuring();
  1114. this.data._flag_cut_touch = true;
  1115. this.data._flag_img_endtouch = true;
  1116. this.data.CUT_START = {
  1117. width: this.data.width,
  1118. height: this.data.height,
  1119. cut_top: this.data.cut_top,
  1120. cut_left: this.data.cut_left,
  1121. x: currentX,
  1122. y: currentY,
  1123. corner: 2
  1124. }
  1125. } else if (currentX > cutbox_left1 && currentX < cutbox_right1 && currentY > cutbox_top1 && currentY < cutbox_bottom1) {
  1126. this._moveDuring();
  1127. this.data._flag_cut_touch = true;
  1128. this.data._flag_img_endtouch = true;
  1129. this.data.CUT_START = {
  1130. width: this.data.width,
  1131. height: this.data.height,
  1132. cut_top: this.data.cut_top,
  1133. cut_left: this.data.cut_left,
  1134. x: currentX,
  1135. y: currentY,
  1136. corner: 1
  1137. }
  1138. }
  1139. },
  1140. _cutTouchEnd(e) {
  1141. this._moveStop();
  1142. this.data._flag_cut_touch = false;
  1143. },
  1144. //停止移动时需要做的操作
  1145. _moveStop() {
  1146. //清空之前的自动居中延迟函数并添加最新的
  1147. clearTimeout(this.data.TIME_CUT_CENTER);
  1148. this.data.TIME_CUT_CENTER = setTimeout(() => {
  1149. //动画启动
  1150. if (!this.data._cut_animation) {
  1151. this.setData({
  1152. _cut_animation: true
  1153. });
  1154. }
  1155. this.setCutCenter();
  1156. }, 1000)
  1157. //清空之前的背景变化延迟函数并添加最新的
  1158. clearTimeout(this.data.TIME_BG);
  1159. this.data.TIME_BG = setTimeout(() => {
  1160. if (this.data._flag_bright) {
  1161. this.setData({
  1162. _flag_bright: false
  1163. });
  1164. }
  1165. }, 2000)
  1166. },
  1167. //移动中
  1168. _moveDuring() {
  1169. //清空之前的自动居中延迟函数
  1170. clearTimeout(this.data.TIME_CUT_CENTER);
  1171. //清空之前的背景变化延迟函数
  1172. clearTimeout(this.data.TIME_BG);
  1173. //高亮背景
  1174. if (!this.data._flag_bright) {
  1175. this.setData({
  1176. _flag_bright: true
  1177. });
  1178. }
  1179. },
  1180. //监听器
  1181. _watcher() {
  1182. Object.keys(this.data).forEach(v => {
  1183. this._observe(this.data, v, this.data.watch[v]);
  1184. })
  1185. },
  1186. _observe(obj, key, watchFun) {
  1187. var val = obj[key];
  1188. Object.defineProperty(obj, key, {
  1189. configurable: true,
  1190. enumerable: true,
  1191. set: (value) => {
  1192. val = value;
  1193. watchFun && watchFun(val, this);
  1194. },
  1195. get() {
  1196. if (val && '_img_top|img_left||width|height|min_width|max_width|min_height|max_height|export_scale|cut_top|cut_left|canvas_top|canvas_left|img_width|img_height|scale|angle|min_scale|max_scale'.indexOf(key) != -1) {
  1197. let ret = parseFloat(parseFloat(val).toFixed(3));
  1198. if (typeof val == "string" && val.indexOf("%") != -1) {
  1199. ret += '%';
  1200. }
  1201. return ret;
  1202. }
  1203. return val;
  1204. }
  1205. })
  1206. },
  1207. _preventTouchMove() { }
  1208. }
  1209. })