ksp-image-cutter.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. <template>
  2. <view v-show="url" class="ksp-image-cutter">
  3. <canvas :style="{width: target.width + 'px', height: target.height + 'px'}" canvas-id="target"></canvas>
  4. <view class="body">
  5. <image v-if="url" class="image" @load="imageLoad" :style="{left: image.left + 'px', top: image.top + 'px', width: image.width + 'px', height: image.height + 'px'}" :src="url"></image>
  6. <view v-if="mask.show" class="mask"></view>
  7. <view @touchstart="touchStart($event, 'plank')" @touchmove="touchMove" @touchend="touchEnd" @touchcancel="touchCancel" class="plank">
  8. <view class="frame" @touchstart="touchStart($event, 'frame')" @touchstart.stop.prevent="touchHandle" :style="{left: frame.left + 'px', top: frame.top + 'px', width: frame.width + 'px', height: frame.height + 'px'}">
  9. <canvas v-if="mask.show" class="canvas" :style="{width: frame.width + 'px', height: frame.height + 'px'}" canvas-id="canvas"></canvas>
  10. <view class="rect"></view>
  11. <view class="line-one"></view>
  12. <view class="line-two"></view>
  13. <view class="line-three"></view>
  14. <view class="line-four"></view>
  15. <view @touchstart="touchStart($event, 'left')" @touchstart.stop.prevent="touchHandle" class="frame-left"></view>
  16. <view @touchstart="touchStart($event, 'right')" @touchstart.stop.prevent="touchHandle" class="frame-right"></view>
  17. <view @touchstart="touchStart($event, 'top')" @touchstart.stop.prevent="touchHandle" class="frame-top"></view>
  18. <view @touchstart="touchStart($event, 'bottom')" @touchstart.stop.prevent="touchHandle" class="frame-bottom"></view>
  19. <view @touchstart="touchStart($event, 'left-top')" @touchstart.stop.prevent="touchHandle" class="frame-left-top"></view>
  20. <view @touchstart="touchStart($event, 'left-bottom')" @touchstart.stop.prevent="touchHandle" class="frame-left-bottom"></view>
  21. <view @touchstart="touchStart($event, 'right-top')" @touchstart.stop.prevent="touchHandle" class="frame-right-top"></view>
  22. <view @touchstart="touchStart($event, 'right-bottom')" @touchstart.stop.prevent="touchHandle" class="frame-right-bottom"></view>
  23. </view>
  24. </view>
  25. </view>
  26. <view class="toolbar">
  27. <button @tap="oncancle" class="btn-cancel">返回</button>
  28. <button @tap="onok" class="btn-ok">确定</button>
  29. </view>
  30. </view>
  31. </template>
  32. <script>
  33. export default {
  34. props: {
  35. url: {
  36. type: String,
  37. default: ""
  38. },
  39. fixed: {
  40. type: Boolean,
  41. default: false
  42. },
  43. width: {
  44. type: Number,
  45. default: 200
  46. },
  47. height: {
  48. type: Number,
  49. default: 200
  50. },
  51. maxWidth: {
  52. type: Number,
  53. default: 1024
  54. },
  55. maxHeight: {
  56. type: Number,
  57. default: 1024
  58. },
  59. blob: {
  60. type: Boolean,
  61. default: true
  62. }
  63. },
  64. data() {
  65. return {
  66. mask: {
  67. show: false
  68. },
  69. frame: {
  70. left: 50,
  71. top: 50,
  72. width: this.width,
  73. height: this.height
  74. },
  75. image: {
  76. left: 20,
  77. top: 20,
  78. width: 300,
  79. height: 400
  80. },
  81. real: {
  82. width: 100,
  83. height: 100
  84. },
  85. target: {
  86. width: this.width,
  87. height: this.height
  88. },
  89. touches: [],
  90. type: "",
  91. start: {
  92. frame: {
  93. left: 0,
  94. top: 0,
  95. width: 0,
  96. height: 0
  97. },
  98. image: {
  99. left: 0,
  100. top: 0,
  101. width: 0,
  102. height: 0
  103. },
  104. },
  105. timeoutId: -1,
  106. context: null
  107. };
  108. },
  109. mounted() {
  110. //#ifdef H5
  111. this.$el.addEventListener("touchmove", (ev) => {
  112. ev.preventDefault();
  113. });
  114. // #endif
  115. this.context = uni.createCanvasContext("canvas", this);
  116. this.targetContext = uni.createCanvasContext("target", this);
  117. },
  118. methods: {
  119. imageLoad(ev) {
  120. this.mask.show = true;
  121. this.real.width = ev.detail.width;
  122. this.real.height = ev.detail.height;
  123. this.image.width = ev.detail.width;
  124. this.image.height = ev.detail.height;
  125. this.frame.width = this.width;
  126. this.frame.height = this.height;
  127. if (!this.fixed) {
  128. this.frame.width = this.image.width;
  129. this.frame.height = this.image.height;
  130. }
  131. var query = uni.createSelectorQuery().in(this);
  132. query.select(".body").boundingClientRect((data) => {
  133. var bw = data.width;
  134. var bh = data.height;
  135. var fw = this.frame.width;
  136. var fh = this.frame.height;
  137. var tw = bw * 0.8;
  138. var th = bh * 0.8;
  139. var sx = tw / fw;
  140. var sy = th / fh;
  141. var scale = sx;
  142. if (sx < sy) {
  143. scale = sy;
  144. }
  145. tw = fw * scale;
  146. th = fh * scale;
  147. var tx = (bw - tw) / 2;
  148. var ty = (bh - th) / 2;
  149. this.frame.width = tw;
  150. this.frame.height = th;
  151. this.frame.left = tx;
  152. this.frame.top = ty;
  153. var iw = this.image.width;
  154. var ih = this.image.height;
  155. sx = tw / iw;
  156. sy = th / ih;
  157. scale = sx;
  158. if (sx < sy) {
  159. scale = sy;
  160. }
  161. this.image.width = iw * scale;
  162. this.image.height = ih * scale;
  163. this.image.left = (bw - this.image.width) / 2;
  164. this.image.top = (bh - this.image.height) / 2;
  165. setTimeout(() => {
  166. this.trimImage();
  167. }, 100);
  168. }).exec();
  169. },
  170. touchHandle() {},
  171. touchStart(ev, type) {
  172. this.stopTime();
  173. this.mask.show = false;
  174. if (this.touches.length == 0) {
  175. this.type = type;
  176. this.start.frame.left = this.frame.left;
  177. this.start.frame.top = this.frame.top;
  178. this.start.frame.width = this.frame.width;
  179. this.start.frame.height = this.frame.height;
  180. this.start.image.left = this.image.left;
  181. this.start.image.top = this.image.top;
  182. this.start.image.width = this.image.width;
  183. this.start.image.height = this.image.height;
  184. }
  185. var touches = ev.changedTouches;
  186. for(var i = 0; i < touches.length; i++) {
  187. var touch = touches[i];
  188. // this.touches[touch.identifier] = touch;
  189. this.touches.push(touch);
  190. }
  191. },
  192. touchMove(ev) {
  193. this.stopTime();
  194. ev.preventDefault();
  195. var touches = ev.touches;
  196. if (this.touches.length == 1) {
  197. if (this.type == "plank" || this.type == "frame" || this.fixed) {
  198. this.moveImage(this.touches[0], touches[0]);
  199. } else {
  200. this.scaleFrame(this.touches[0], touches[0], this.type);
  201. }
  202. } else if (this.touches.length == 2 && touches.length == 2) {
  203. var ta = this.touches[0];
  204. var tb = this.touches[1];
  205. var tc = touches[0];
  206. var td = touches[1];
  207. if (ta.identifier != tc.identifier) {
  208. var temp = tc;
  209. tc = td;
  210. td = temp;
  211. }
  212. this.scaleImage(ta, tb, tc, td);
  213. }
  214. },
  215. touchEnd(ev) {
  216. this.type = "";
  217. this.touches = [];
  218. this.startTime();
  219. },
  220. touchCancel(ev) {
  221. this.type = "";
  222. this.touches = [];
  223. this.startTime();
  224. },
  225. startTime() {
  226. this.stopTime();
  227. this.timeoutId = setTimeout(() => {
  228. this.trimImage();
  229. }, 800);
  230. },
  231. stopTime() {
  232. if (this.timeoutId >= 0) {
  233. clearTimeout(this.timeoutId);
  234. this.timeoutId = -1;
  235. }
  236. },
  237. trimImage() {
  238. this.mask.show = true;
  239. var query = uni.createSelectorQuery().in(this);
  240. query.select(".body").boundingClientRect((data) => {
  241. var bw = data.width;
  242. var bh = data.height;
  243. var fw = this.frame.width;
  244. var fh = this.frame.height;
  245. var tw = bw * 0.8;
  246. var th = bh * 0.8;
  247. var sx = tw / fw;
  248. var sy = th / fh;
  249. var scale = sx;
  250. if (sx > sy) {
  251. scale = sy;
  252. }
  253. tw = fw * scale;
  254. th = fh * scale;
  255. var tx = (bw - tw) / 2;
  256. var ty = (bh - th) / 2;
  257. var ax = tx - this.frame.left + (this.frame.left - this.image.left) * (1 - scale);
  258. var ay = ty - this.frame.top + (this.frame.top - this.image.top) * (1 - scale);
  259. this.frame.width = tw;
  260. this.frame.height = th;
  261. this.frame.left = tx;
  262. this.frame.top = ty;
  263. this.image.width *= scale;
  264. this.image.height *= scale;
  265. this.image.left += ax;
  266. this.image.top += ay;
  267. }).exec();
  268. setTimeout(() => {
  269. var scale = this.image.width / this.real.width;
  270. var x = (this.frame.left - this.image.left) / scale;
  271. var y = (this.frame.top - this.image.top) / scale;
  272. var width = this.frame.width / scale;
  273. var height = this.frame.height / scale;
  274. this.context.drawImage(this.url, x, y, width, height, 0, 0, this.frame.width, this.frame.height);
  275. this.context.draw(false);
  276. }, 100);
  277. },
  278. moveImage(ta, tb) {
  279. var ax = tb.clientX - ta.clientX;
  280. var ay = tb.clientY - ta.clientY;
  281. this.image.left = this.start.image.left + ax;
  282. this.image.top = this.start.image.top + ay;
  283. if (this.image.left > this.frame.left) {
  284. this.image.left = this.frame.left;
  285. }
  286. if (this.image.top > this.frame.top) {
  287. this.image.top = this.frame.top;
  288. }
  289. if (this.image.left + this.image.width < this.frame.left + this.frame.width) {
  290. this.image.left = this.frame.left + this.frame.width - this.image.width;
  291. }
  292. if (this.image.top + this.image.height < this.frame.top + this.frame.height) {
  293. this.image.top = this.frame.top + this.frame.height - this.image.height;
  294. }
  295. },
  296. scaleImage(ta, tb, tc, td) {
  297. var x1 = ta.clientX;
  298. var y1 = ta.clientY;
  299. var x2 = tb.clientX;
  300. var y2 = tb.clientY;
  301. var x3 = tc.clientX;
  302. var y3 = tc.clientY;
  303. var x4 = td.clientX;
  304. var y4 = td.clientY;
  305. var ol = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
  306. var el = Math.sqrt((x3 - x4) * (x3 - x4) + (y3 - y4) * (y3 - y4));
  307. var ocx = (x1 + x2) / 2;
  308. var ocy = (y1 + y2) / 2;
  309. var ecx = (x3 + x4) / 2;
  310. var ecy = (y3 + y4) / 2;
  311. var ax = ecx - ocx;
  312. var ay = ecy - ocy;
  313. var scale = el / ol;
  314. if (this.start.image.width * scale < this.frame.width) {
  315. scale = this.frame.width / this.start.image.width;
  316. }
  317. if (this.start.image.height * scale < this.frame.height) {
  318. scale = this.frame.height / this.start.image.height;
  319. }
  320. if (this.start.image.width * scale < this.frame.width) {
  321. scale = this.frame.width / this.start.image.width;
  322. }
  323. this.image.left = this.start.image.left + ax - (ocx - this.start.image.left) * (scale - 1);
  324. this.image.top = this.start.image.top + ay - (ocy - this.start.image.top) * (scale - 1);
  325. this.image.width = this.start.image.width * scale;
  326. this.image.height = this.start.image.height * scale;
  327. if (this.image.left > this.frame.left) {
  328. this.image.left = this.frame.left;
  329. }
  330. if (this.image.top > this.frame.top) {
  331. this.image.top = this.frame.top;
  332. }
  333. if (this.image.left + this.image.width < this.frame.left + this.frame.width) {
  334. this.image.left = this.frame.left + this.frame.width - this.image.width;
  335. }
  336. if (this.image.top + this.image.height < this.frame.top + this.frame.height) {
  337. this.image.top = this.frame.top + this.frame.height - this.image.height;
  338. }
  339. },
  340. scaleFrame(ta, tb, type) {
  341. var ax = tb.clientX - ta.clientX;
  342. var ay = tb.clientY - ta.clientY;
  343. var x1 = this.start.frame.left;
  344. var y1 = this.start.frame.top;
  345. var x2 = this.start.frame.left + this.start.frame.width;
  346. var y2 = this.start.frame.top + this.start.frame.height;
  347. if (type == "left") {
  348. x1 += ax;
  349. } else if (type == "right") {
  350. x2 += ax;
  351. } else if (type == "top") {
  352. y1 += ay;
  353. } else if (type == "bottom") {
  354. y2 += ay;
  355. } else if (type == "left-top") {
  356. x1 += ax;
  357. y1 += ay;
  358. } else if (type == "left-bottom") {
  359. x1 += ax;
  360. y2 += ay;
  361. } else if (type == "right-top") {
  362. x2 += ax;
  363. y1 += ay;
  364. } else if (type == "right-bottom") {
  365. x2 += ax;
  366. y2 += ay;
  367. }
  368. if (x1 < this.image.left) {
  369. x1 = this.image.left;
  370. }
  371. if (y1 < this.image.top) {
  372. y1 = this.image.top;
  373. }
  374. if (x2 > this.image.left + this.image.width) {
  375. x2 = this.image.left + this.image.width;
  376. }
  377. if (y2 > this.image.top + this.image.height) {
  378. y2 = this.image.top + this.image.height;
  379. }
  380. this.frame.left = x1;
  381. this.frame.top = y1;
  382. this.frame.width = x2 - x1;
  383. this.frame.height = y2 - y1;
  384. },
  385. parseBlob(base64) {
  386. var arr = base64.split(',');
  387. var mime = arr[0].match(/:(.*?);/)[1];
  388. var bstr = atob(arr[1]);
  389. var n = bstr.length;
  390. var u8arr = new Uint8Array(n);
  391. for(var i = 0; i < n; i++) {
  392. u8arr[i] = bstr.charCodeAt(i);
  393. }
  394. var url = URL || webkitURL;
  395. return url.createObjectURL(new Blob([u8arr], {type: mime}));
  396. },
  397. onok() {
  398. var scale = this.image.width / this.real.width;
  399. var x = (this.frame.left - this.image.left) / scale;
  400. var y = (this.frame.top - this.image.top) / scale;
  401. var width = this.frame.width / scale;
  402. var height = this.frame.height / scale;
  403. var tw = width;
  404. var th = height;
  405. if (this.fixed) {
  406. tw = this.width / 2;
  407. th = this.height / 2;
  408. } else {
  409. if (tw > this.maxWidth / 2) {
  410. var sc = this.maxWidth / 2 / tw;
  411. tw = tw * sc;
  412. th = th * sc;
  413. }
  414. if (th > this.maxHeight / 2) {
  415. var sc = this.maxHeight / 2 / th;
  416. th = th * sc;
  417. tw = tw * sc;
  418. }
  419. }
  420. this.target.width = tw;
  421. this.target.height = th;
  422. uni.showLoading({
  423. title: "正在裁剪"
  424. });
  425. setTimeout(() => {
  426. this.targetContext.drawImage(this.url, x, y, width, height, 0, 0, tw, th);
  427. this.targetContext.draw(false, () => {
  428. uni.canvasToTempFilePath({
  429. canvasId: "target",
  430. success: (res) => {
  431. var path = res.tempFilePath;
  432. // #ifdef H5
  433. if (this.blob) {
  434. path = this.parseBlob(path);
  435. }
  436. // #endif
  437. this.$emit("ok", {
  438. path: path
  439. });
  440. },
  441. fail: (ev) => {
  442. console.log(ev);
  443. },
  444. complete: () => {
  445. uni.hideLoading();
  446. }
  447. }, this);
  448. });
  449. }, 100);
  450. },
  451. oncancle() {
  452. this.$emit("cancel");
  453. }
  454. }
  455. }
  456. </script>
  457. <style scoped>
  458. .ksp-image-cutter {
  459. position: absolute;
  460. width: 100%;
  461. height: 100%;
  462. top: 0;
  463. bottom: 0;
  464. z-index: 1000;
  465. }
  466. .toolbar {
  467. position: absolute;
  468. width: 100%;
  469. height: 100upx;
  470. left: 0upx;
  471. bottom: 0upx;
  472. box-sizing: border-box;
  473. border-bottom: 1px solid #C0C0C0;
  474. background: #F8F8F8;
  475. }
  476. .btn-cancel {
  477. position: absolute;
  478. left: 100upx;
  479. top: 12upx;
  480. font-size: 30upx;
  481. line-height: 30upx;
  482. padding: 20upx;
  483. color: #333333;
  484. }
  485. .btn-ok {
  486. position: absolute;
  487. right: 100upx;
  488. top: 12upx;
  489. font-size: 30upx;
  490. line-height: 30upx;
  491. padding: 20upx;
  492. color: #333333;
  493. }
  494. .body {
  495. position: absolute;
  496. left: 0upx;
  497. right: 0upx;
  498. top: 0upx;
  499. bottom: 100upx;
  500. background: black;
  501. overflow: hidden;
  502. }
  503. .mask {
  504. position: absolute;
  505. left: 0upx;
  506. right: 0upx;
  507. top: 0upx;
  508. bottom: 0upx;
  509. background: black;
  510. opacity: 0.4;
  511. }
  512. .plank {
  513. position: absolute;
  514. left: 0upx;
  515. right: 0upx;
  516. top: 0upx;
  517. bottom: 0upx;
  518. }
  519. .image {
  520. position: absolute;
  521. }
  522. .frame {
  523. position: absolute;
  524. }
  525. .canvas {
  526. position: absolute;
  527. display: block;
  528. left: 0px;
  529. top: 0px;
  530. }
  531. .rect {
  532. position: absolute;
  533. left: -2px;
  534. top: -2px;
  535. width: 100%;
  536. height: 100%;
  537. border: 2px solid white;
  538. }
  539. .line-one {
  540. position: absolute;
  541. width: 100%;
  542. height: 1px;
  543. background: white;
  544. left: 0;
  545. top: 33.3%;
  546. }
  547. .line-two {
  548. position: absolute;
  549. width: 100%;
  550. height: 1px;
  551. background: white;
  552. left: 0;
  553. top: 66.7%;
  554. }
  555. .line-three {
  556. position: absolute;
  557. width: 1px;
  558. height: 100%;
  559. background: white;
  560. top: 0;
  561. left: 33.3%;
  562. }
  563. .line-four {
  564. position: absolute;
  565. width: 1px;
  566. height: 100%;
  567. background: white;
  568. top: 0;
  569. left: 66.7%;
  570. }
  571. .frame-left {
  572. position: absolute;
  573. height: 100%;
  574. width: 8px;
  575. left: -4px;
  576. top: 0;
  577. }
  578. .frame-right {
  579. position: absolute;
  580. height: 100%;
  581. width: 8px;
  582. right: -4px;
  583. top: 0;
  584. }
  585. .frame-top {
  586. position: absolute;
  587. width: 100%;
  588. height: 8px;
  589. top: -4px;
  590. left: 0;
  591. }
  592. .frame-bottom {
  593. position: absolute;
  594. width: 100%;
  595. height: 8px;
  596. bottom: -4px;
  597. left: 0;
  598. }
  599. .frame-left-top {
  600. position: absolute;
  601. width: 20px;
  602. height: 20px;
  603. left: -6px;
  604. top: -6px;
  605. border-left: 4px solid red;
  606. border-top: 4px solid red;
  607. }
  608. .frame-left-bottom {
  609. position: absolute;
  610. width: 20px;
  611. height: 20px;
  612. left: -6px;
  613. bottom: -6px;
  614. border-left: 4px solid red;
  615. border-bottom: 4px solid red;
  616. }
  617. .frame-right-top {
  618. position: absolute;
  619. width: 20px;
  620. height: 20px;
  621. right: -6px;
  622. top: -6px;
  623. border-right: 4px solid red;
  624. border-top: 4px solid red;
  625. }
  626. .frame-right-bottom {
  627. position: absolute;
  628. width: 20px;
  629. height: 20px;
  630. right: -6px;
  631. bottom: -6px;
  632. border-right: 4px solid red;
  633. border-bottom: 4px solid red;
  634. }
  635. </style>