clipper.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. /**
  2. * name : vue 的图片裁减压缩处理
  3. * author : 陈威
  4. * date : 2017/6/20
  5. * dependence :
  6. * cropper 第三方的一个裁剪库
  7. * exif 获取图片源信息,解决ios图片旋转的问题
  8. * */
  9. import Cropper from 'cropperjs'
  10. // import Exif from 'exif-js'
  11. export default {
  12. install( Vue ){
  13. //初始化方法
  14. Vue.prototype.initilize = function( opt ){
  15. let self = this;
  16. self.botNav = false; //隐藏底部导航
  17. this.options = opt;
  18. //创建dom
  19. this.createElement();
  20. this.resultObj = opt.resultObj;
  21. //初始化裁剪对象
  22. this.cropper = new Cropper( this.preview , {
  23. aspectRatio : opt.aspectRatio || 1 ,
  24. autoCropArea : opt.autoCropArea || 0.8 ,
  25. viewMode : 1,
  26. guides : opt.aspectRatio == 'Free' ? false : true ,
  27. cropBoxResizable : opt.aspectRatio == 'Free' ? false : true ,
  28. cropBoxMovable : opt.aspectRatio == 'Free' ? false : true ,
  29. dragCrop : opt.aspectRatio == 'Free' ? false : true ,
  30. background : false,
  31. checkOrientation : true ,
  32. checkCrossOrigin : true ,
  33. zoomable : false,
  34. zoomOnWheel : false ,
  35. center : false ,
  36. toggleDragModeOnDblclick : false ,
  37. ready : function () {
  38. // console.log(self.cropper.rotate(90))
  39. if( opt.aspectRatio == 'Free' ){
  40. let cropBox = self.cropper.cropBox;
  41. cropBox.querySelector('span.cropper-view-box').style.outline = 'none';
  42. self.cropper.disable();
  43. }
  44. }
  45. });
  46. }
  47. //创建一些必要的DOM,用于图片裁剪
  48. Vue.prototype.createElement = function () {
  49. //初始化图片为空对象
  50. this.preview = null;
  51. let str = '<div><img id="clip_image" src="originUrl"></div><button type="button" id="cancel_clip">取消</button><button type="button" id="clip_button">确定</button>';
  52. str+= '<div class="crop_loading"><div class="crop_content"><img style="margin: 0 auto" src="./static/loading.gif"><div class="crop_text">图片上传中</div></div></div>';
  53. str+= '<div class="crop_success"><div class="crop_success_text">上传成功</div></div></div>';
  54. let body = document.getElementsByTagName('body')[0];
  55. this.reagion = document.createElement('div');
  56. this.reagion.id = 'clip_container';
  57. this.reagion.className = 'container';
  58. this.reagion.innerHTML = str;
  59. //添加创建好的DOM元素
  60. body.appendChild(this.reagion);
  61. this.preview = document.getElementById('clip_image');
  62. //绑定一些方法
  63. this.initFunction();
  64. }
  65. //初始化一些函数绑定
  66. Vue.prototype.initFunction = function () {
  67. let self =this;
  68. this.clickBtn = document.getElementById('clip_button');
  69. this.cancelBtn = document.getElementById('cancel_clip');
  70. //确定事件
  71. this.addEvent( this.clickBtn , 'click' , function () {
  72. self.crop();
  73. })
  74. //取消事件
  75. this.addEvent( this.cancelBtn , 'click' , function () {
  76. self.destoried();
  77. })
  78. //清空input的值
  79. this.addEvent( this.fileObj , 'click' , function () {
  80. this.value = '';
  81. })
  82. }
  83. //外部接口,用于input['file']对象change时的调用
  84. Vue.prototype.clip = function ( e , opt ) {
  85. console.log(Vue.prototype.clip)
  86. let self = this;
  87. this.fileObj = e.srcElement;
  88. let files = e.target.files || e.dataTransfer.files;
  89. if (!files.length) return false; //不是图片直接返回
  90. //调用初始化方法
  91. this.initilize( opt );
  92. //获取图片文件资源
  93. this.picValue = files[0];
  94. //去获取拍照时的信息,解决拍出来的照片旋转问题
  95. // Exif.getData( files[0] , function(){
  96. // self.Orientation = Exif.getTag( files[0], 'Orientation');
  97. // console.log(self.Orientation)
  98. // });
  99. // console.log(this.picValue)
  100. //调用方法转成url格式
  101. this.originUrl = this.getObjectURL( this.picValue );
  102. //每次替换图片要重新得到新的url
  103. if(this.cropper){
  104. this.cropper.replace(this.originUrl);
  105. }
  106. return this.picValue
  107. }
  108. //图片转码方法
  109. Vue.prototype.getObjectURL = function(file) {
  110. let url = null ;
  111. // console.log(file)
  112. if (window.createObjectURL!=undefined) { // basic
  113. url = window.createObjectURL(file) ;
  114. } else if (window.URL!=undefined) { // mozilla(firefox)
  115. url = window.URL.createObjectURL(file) ;
  116. } else if (window.webkitURL!=undefined) { // webkit or chrome
  117. url = window.webkitURL.createObjectURL(file) ;
  118. }
  119. return url ;
  120. }
  121. //点击确定进行裁剪
  122. Vue.prototype.crop = function () {
  123. let self = this;
  124. let image = new Image();
  125. let croppedCanvas;
  126. let roundedCanvas;
  127. // Crop
  128. document.querySelector('.crop_loading').style.display = 'block';
  129. setTimeout(function () {
  130. croppedCanvas = self.cropper.getCroppedCanvas();
  131. // Round
  132. roundedCanvas = self.getRoundedCanvas(croppedCanvas);
  133. let imgData = roundedCanvas.toDataURL();
  134. image.src = imgData;
  135. //判断图片是否大于100k,不大于直接上传,反之压缩
  136. if( imgData.length < (100 * 1024) ){
  137. self.resultObj.src = imgData;
  138. //图片上传
  139. self.postImg( imgData );
  140. // console.log(imgData)
  141. localStorage.setItem('ba64dataA',imgData)
  142. // console.log(imgData)
  143. }else{
  144. image.onload = function () {
  145. //压缩处理
  146. let data = self.compress( image , self.Orientation );
  147. self.resultObj.src = data;
  148. //图片上传
  149. self.postImg( data );
  150. localStorage.setItem('ba64dataA',data)
  151. }
  152. }
  153. },20)
  154. }
  155. //获取裁剪图片资源
  156. Vue.prototype.getRoundedCanvas = function(sourceCanvas) {
  157. let canvas = document.createElement('canvas');
  158. let context = canvas.getContext('2d');
  159. let width = sourceCanvas.width;
  160. let height = sourceCanvas.height;
  161. canvas.width = width;
  162. canvas.height = height;
  163. context.imageSmoothingEnabled = true;
  164. context.drawImage(sourceCanvas, 0, 0, width, height);
  165. context.globalCompositeOperation = 'destination-in';
  166. context.beginPath();
  167. context.rect(0 , 0 , width , height );
  168. context.fill();
  169. return canvas;
  170. }
  171. //销毁原来的对象
  172. Vue.prototype.destoried = function () {
  173. let self = this;
  174. this.loadingShow = false
  175. //移除事件
  176. this.removeEvent( this.clickBtn , 'click' , null );
  177. this.removeEvent( this.cancelBtn , 'click' , null );
  178. this.removeEvent( this.fileObj , 'click' , null );
  179. //移除裁剪框
  180. this.reagion.parentNode.removeChild(this.reagion);
  181. //销毁裁剪对象
  182. this.cropper.destroy();
  183. this.cropper = null;
  184. }
  185. //图片上传
  186. Vue.prototype.postImg = function( imageData ) {
  187. //这边写图片的上传
  188. let self = this;
  189. self.destoried();
  190. window.setTimeout( function () {
  191. document.querySelector('.crop_loading').style.display = 'none';
  192. document.querySelector('.crop_success').style.display = 'block';
  193. //裁剪完后摧毁对象
  194. self.destoried();
  195. },3000)
  196. this.loadingShow = true;
  197. // 判断是用户是选择的病害还是昆虫
  198. if (localStorage.getItem('insect') !== null) {
  199. //计时器
  200. // var totalTime = 10;
  201. // let timer = window.setInterval(() => {
  202. // totalTime--;
  203. // console.log(totalTime)
  204. // if (totalTime <= 0) {
  205. // window.clearInterval(timer);
  206. // this.$message({
  207. // type: "error",
  208. // message: "识别失败,您可以尝试图片上传!"
  209. // });
  210. // this.loadingShow = false;
  211. // // 识别失败发送记录
  212. // if (localStorage.getItem("insect") !== null) {
  213. // var classify = "insect";
  214. // } else if (localStorage.getItem("plant") !== null) {
  215. // var classify = "plant";
  216. // }
  217. // let form = new FormData();
  218. // form.append("file", this.picValue);
  219. // form.append("name", "1");
  220. // form.append("prevention", "");
  221. // form.append("course", "");
  222. // form.append("ret", classify);
  223. // form.append('img_urls', '');
  224. // form.append('addr', '');
  225. // form.append('lng', '');
  226. // form.append('lat', '');
  227. // this
  228. // .$axios({
  229. // method: "post",
  230. // url: "/record",
  231. // data: form,
  232. // headers: {
  233. // "Content-Type": "multipart/form-data"
  234. // }
  235. // })
  236. // .then(res => {
  237. // alert('已传输识别失败数据')
  238. // console.log('已传输识别失败数据')
  239. // })
  240. // .catch(error => {
  241. // alert('传输失败')
  242. // console.log('传输失败')
  243. // });
  244. // // const timeA = setInterval(() => {
  245. // // location.reload();
  246. // // }, 1500);
  247. // }
  248. // }, 1000);
  249. let form = new FormData();
  250. form.append("img_addr", this.picValue);
  251. this.$axios({
  252. method: "post",
  253. url: "/app_insect",
  254. anync: true,
  255. data: form,
  256. headers: {
  257. "Content-Type": "multipart/form-data"
  258. }
  259. }).then(res => {
  260. self.botNav = true; //隐藏底部导航
  261. // window.clearInterval(timer); //销毁计时器
  262. if (
  263. res.data.msg === "识别失败_!" ||
  264. res.data.msg === "" ||
  265. res.data.data === null
  266. ) {
  267. this.$message({
  268. type: "error",
  269. message: "识别失败!"
  270. });
  271. this.$router.push({name: 'show',params: {data: form.get('img_addr')}})
  272. localStorage.setItem('recordID', '1')
  273. this.loadingShow = false;
  274. } else {
  275. this.$notify({
  276. title: "成功",
  277. message: "识别成功",
  278. type: "success"
  279. });
  280. localStorage.setItem("axiosData", JSON.stringify(res.data.data[0]));
  281. }
  282. this.$router.push({name: 'show',params: {data: form.get('img_addr')}})
  283. localStorage.setItem('recordID', '1')
  284. this.loadingShow = false;
  285. }).catch(error => {
  286. // window.clearInterval(timer); //销毁计时器
  287. this.$message({
  288. type: "error",
  289. message: "识别失败!"
  290. });
  291. this.$router.push({name: 'show',params: {data: form.get('img_addr')}})
  292. localStorage.setItem('recordID', '1')
  293. this.loadingShow = false;
  294. })
  295. } else if (localStorage.getItem('plant') !== null) {
  296. //计时器
  297. // var totalTime = 10;
  298. // let timer = window.setInterval(() => {
  299. // totalTime--;
  300. // console.log(totalTime)
  301. // if (totalTime <= 0) {
  302. // window.clearInterval(timer);
  303. // this.$message({
  304. // type: "error",
  305. // message: "识别失败,您可以尝试图片上传!"
  306. // });
  307. // this.loadingShow = false;
  308. // // 识别失败发送记录
  309. // if (localStorage.getItem("insect") !== null) {
  310. // var classify = "insect";
  311. // } else if (localStorage.getItem("plant") !== null) {
  312. // var classify = "plant";
  313. // }
  314. // let form = new FormData();
  315. // form.append("file", this.picValue);
  316. // form.append("name", "1");
  317. // form.append("prevention", "");
  318. // form.append("course", "");
  319. // form.append("ret", classify);
  320. // form.append('img_urls', '');
  321. // form.append('addr', '');
  322. // form.append('lng', '');
  323. // form.append('lat', '');
  324. // this
  325. // .$axios({
  326. // method: "post",
  327. // url: "/record",
  328. // data: form,
  329. // headers: {
  330. // "Content-Type": "multipart/form-data"
  331. // }
  332. // })
  333. // .then(res => {
  334. // console.log('已传输识别失败数据')
  335. // alert('已传输识别失败数据')
  336. // })
  337. // .catch(error => {
  338. // alert('已传输识别失败数据')
  339. // console.log('传输失败')
  340. // });
  341. // // const timeA = setInterval(() => {
  342. // // location.reload();
  343. // // }, 1500);
  344. // }
  345. // }, 1000);
  346. let form = new FormData();
  347. form.append("img_addr", this.picValue);
  348. this.$axios({
  349. method: "post",
  350. url: "/app_plant",
  351. anync: true,
  352. data: form,
  353. headers: {
  354. "Content-Type": "multipart/form-data"
  355. }
  356. }).then(res => {
  357. self.botNav = true; //隐藏底部导航
  358. // window.clearInterval(timer); //销毁计时器
  359. if (
  360. res.data.msg === "识别失败_!" ||
  361. res.data.msg === "" ||
  362. res.data.data === null
  363. ) {
  364. this.$message({
  365. type: "error",
  366. message: "识别失败!"
  367. });
  368. this.loadingShow = false;
  369. this.$router.push({name: 'show',params: {data: form.get('img_addr')}})
  370. localStorage.setItem('recordID', '1')
  371. } else {
  372. this.$notify({
  373. title: "成功",
  374. message: "识别成功",
  375. type: "success"
  376. });
  377. localStorage.setItem("axiosData", JSON.stringify(res.data.data[0]));
  378. }
  379. this.$router.push({name: 'show',params: {data: form.get('img_addr')}})
  380. localStorage.setItem('recordID', '1')
  381. this.loadingShow = false;
  382. }).catch(error => {
  383. // window.clearInterval(timer); //销毁计时器
  384. this.$message({
  385. type: "error",
  386. message: "识别失败aaaaaaa!"
  387. });
  388. this.$router.push({name: 'show',params: {data: form.get('img_addr')}})
  389. localStorage.setItem('recordID', '1')
  390. this.loadingShow = false;
  391. })
  392. }
  393. }
  394. //图片旋转
  395. Vue.prototype.rotateImg = function( img , direction , canvas ) {
  396. //最小与最大旋转方向,图片旋转4次后回到原方向
  397. const min_step = 0;
  398. const max_step = 3;
  399. if (img == null )return;
  400. //img的高度和宽度不能在img元素隐藏后获取,否则会出错
  401. let height = img.height;
  402. let width = img.width;
  403. let step = 2;
  404. if (step == null) {
  405. step = min_step;
  406. }
  407. if ( direction == 'right') {
  408. step++;
  409. //旋转到原位置,即超过最大值
  410. step > max_step && (step = min_step);
  411. } else {
  412. step--;
  413. step < min_step && (step = max_step);
  414. }
  415. //旋转角度以弧度值为参数
  416. let degree = step * 90 * Math.PI / 180;
  417. let ctx = canvas.getContext('2d');
  418. switch (step) {
  419. case 0:
  420. canvas.width = width;
  421. canvas.height = height;
  422. ctx.drawImage(img, 0, 0);
  423. break;
  424. case 1:
  425. canvas.width = height;
  426. canvas.height = width;
  427. ctx.rotate(degree);
  428. ctx.drawImage(img, 0, -height);
  429. break;
  430. case 2:
  431. canvas.width = width;
  432. canvas.height = height;
  433. ctx.rotate(degree);
  434. ctx.drawImage(img, -width, -height);
  435. break;
  436. case 3:
  437. canvas.width = height;
  438. canvas.height = width;
  439. ctx.rotate(degree);
  440. ctx.drawImage(img, -width, 0);
  441. break;
  442. }
  443. }
  444. //图片压缩
  445. Vue.prototype.compress = function ( img , Orientation ) {
  446. let canvas = document.createElement("canvas");
  447. let ctx = canvas.getContext('2d');
  448. //瓦片canvas
  449. let tCanvas = document.createElement("canvas");
  450. let tctx = tCanvas.getContext("2d");
  451. let initSize = img.src.length;
  452. let width = img.width;
  453. let height = img.height;
  454. //如果图片大于四百万像素,计算压缩比并将大小压至400万以下
  455. let ratio;
  456. if ((ratio = width * height / 4000000) > 1) {
  457. console.log("大于400万像素")
  458. ratio = Math.sqrt(ratio);
  459. width /= ratio;
  460. height /= ratio;
  461. } else {
  462. ratio = 1;
  463. }
  464. canvas.width = width;
  465. canvas.height = height;
  466. // 铺底色
  467. // ctx.fillStyle = "#fff";
  468. ctx.fillStyle = "#000";
  469. ctx.fillRect(0, 0, canvas.width, canvas.height);
  470. //如果图片像素大于100万则使用瓦片绘制
  471. let count;
  472. if ((count = width * height / 1000000) > 1) {
  473. count = ~~(Math.sqrt(count) + 1); //计算要分成多少块瓦片
  474. // 计算每块瓦片的宽和高
  475. let nw = ~~(width / count);
  476. let nh = ~~(height / count);
  477. tCanvas.width = nw;
  478. tCanvas.height = nh;
  479. for (let i = 0; i < count; i++) {
  480. for (let j = 0; j < count; j++) {
  481. tctx.drawImage(img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh);
  482. ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh);
  483. }
  484. }
  485. } else {
  486. ctx.drawImage(img, 0, 0, width, height);
  487. }
  488. //修复ios上传图片的时候 被旋转的问题
  489. if( Orientation != "" && Orientation != 1){
  490. switch(Orientation){
  491. case 6://需要顺时针(向左)90度旋转
  492. this.rotateImg(img,'left',canvas);
  493. break;
  494. case 8://需要逆时针(向右)90度旋转
  495. this.rotateImg(img,'right',canvas);
  496. break;
  497. case 3://需要180度旋转
  498. this.rotateImg(img,'right',canvas);//转两次
  499. this.rotateImg(img,'right',canvas);
  500. break;
  501. }
  502. }
  503. //进行最小压缩
  504. let ndata = canvas.toDataURL( 'image/jpeg' , 0.1);
  505. // console.log('压缩前:' + initSize);
  506. // console.log('压缩后:' + ndata.length);
  507. // console.log('压缩率:' + ~~(100 * (initSize - ndata.length) / initSize) + "%");
  508. tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0;
  509. // console.log(ndata)
  510. return ndata;
  511. }
  512. //添加事件
  513. Vue.prototype.addEvent = function ( obj , type , fn ) {
  514. if( obj.addEventListener ){
  515. obj.addEventListener( type , fn , false );
  516. }else{
  517. obj.attachEvent( 'on' + type , fn );
  518. }
  519. }
  520. //移除事件
  521. Vue.prototype.removeEvent = function ( obj , type , fn ) {
  522. if( obj.removeEventListener ){
  523. obj.removeEventListener( type , fn , false );
  524. }else{
  525. obj.detachEvent( 'on' + type , fn );
  526. }
  527. }
  528. }
  529. }