we-cropper.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203
  1. /**
  2. * we-cropper v1.4.0
  3. * (c) 2021 dlhandsome
  4. * @license MIT
  5. */
  6. (function (global, factory) {
  7. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  8. typeof define === 'function' && define.amd ? define(factory) :
  9. (global.WeCropper = factory());
  10. }(this, (function () { 'use strict';
  11. var device = void 0;
  12. var TOUCH_STATE = ['touchstarted', 'touchmoved', 'touchended'];
  13. var adaptAPI = {
  14. strokeStyle: 'setStrokeStyle',
  15. fillStyle: 'setFillStyle',
  16. lineWidth: 'setLineWidth'
  17. };
  18. function firstLetterUpper (str) {
  19. return str.charAt(0).toUpperCase() + str.slice(1)
  20. }
  21. function setTouchState (instance) {
  22. var arg = [], len = arguments.length - 1;
  23. while ( len-- > 0 ) arg[ len ] = arguments[ len + 1 ];
  24. TOUCH_STATE.forEach(function (key, i) {
  25. if (arg[i] !== undefined) {
  26. instance[key] = arg[i];
  27. }
  28. });
  29. }
  30. function validator (instance, o) {
  31. Object.defineProperties(instance, o);
  32. }
  33. function getDevice () {
  34. if (!device) {
  35. device = wx.getSystemInfoSync();
  36. }
  37. return device
  38. }
  39. function adapt2d (context, handle, value) {
  40. if (context.type === '2d') {
  41. context.ctx[handle] = value;
  42. } else {
  43. context.ctx[adaptAPI[handle]](value);
  44. }
  45. }
  46. var tmp = {};
  47. var ref = getDevice();
  48. var pixelRatio = ref.pixelRatio;
  49. var DEFAULT = {
  50. id: {
  51. default: 'cropper',
  52. get: function get () {
  53. return tmp.id
  54. },
  55. set: function set (value) {
  56. if (typeof (value) !== 'string') {
  57. console.error(("id:" + value + " is invalid"));
  58. }
  59. tmp.id = value;
  60. }
  61. },
  62. width: {
  63. default: 750,
  64. get: function get () {
  65. return tmp.width
  66. },
  67. set: function set (value) {
  68. if (typeof (value) !== 'number') {
  69. console.error(("width:" + value + " is invalid"));
  70. }
  71. tmp.width = value;
  72. }
  73. },
  74. height: {
  75. default: 750,
  76. get: function get () {
  77. return tmp.height
  78. },
  79. set: function set (value) {
  80. if (typeof (value) !== 'number') {
  81. console.error(("height:" + value + " is invalid"));
  82. }
  83. tmp.height = value;
  84. }
  85. },
  86. pixelRatio: {
  87. default: pixelRatio,
  88. get: function get () {
  89. return tmp.pixelRatio
  90. },
  91. set: function set (value) {
  92. if (typeof (value) !== 'number') {
  93. console.error(("pixelRatio:" + value + " is invalid"));
  94. }
  95. tmp.pixelRatio = value;
  96. }
  97. },
  98. scale: {
  99. default: 2.5,
  100. get: function get () {
  101. return tmp.scale
  102. },
  103. set: function set (value) {
  104. if (typeof (value) !== 'number') {
  105. console.error(("scale:" + value + " is invalid"));
  106. }
  107. tmp.scale = value;
  108. }
  109. },
  110. zoom: {
  111. default: 5,
  112. get: function get () {
  113. return tmp.zoom
  114. },
  115. set: function set (value) {
  116. if (typeof (value) !== 'number') {
  117. console.error(("zoom:" + value + " is invalid"));
  118. } else if (value < 0 || value > 10) {
  119. console.error("zoom should be ranged in 0 ~ 10");
  120. }
  121. tmp.zoom = value;
  122. }
  123. },
  124. src: {
  125. default: '',
  126. get: function get () {
  127. return tmp.src
  128. },
  129. set: function set (value) {
  130. if (typeof (value) !== 'string') {
  131. console.error(("src:" + value + " is invalid"));
  132. }
  133. tmp.src = value;
  134. }
  135. },
  136. cut: {
  137. default: {},
  138. get: function get () {
  139. return tmp.cut
  140. },
  141. set: function set (value) {
  142. if (typeof (value) !== 'object') {
  143. console.error(("cut:" + value + " is invalid"));
  144. }
  145. tmp.cut = value;
  146. }
  147. },
  148. boundStyle: {
  149. default: {},
  150. get: function get () {
  151. return tmp.boundStyle
  152. },
  153. set: function set (value) {
  154. if (typeof (value) !== 'object') {
  155. console.error(("boundStyle:" + value + " is invalid"));
  156. }
  157. tmp.boundStyle = value;
  158. }
  159. },
  160. onReady: {
  161. default: null,
  162. get: function get () {
  163. return tmp.ready
  164. },
  165. set: function set (value) {
  166. tmp.ready = value;
  167. }
  168. },
  169. onBeforeImageLoad: {
  170. default: null,
  171. get: function get () {
  172. return tmp.beforeImageLoad
  173. },
  174. set: function set (value) {
  175. tmp.beforeImageLoad = value;
  176. }
  177. },
  178. onImageLoad: {
  179. default: null,
  180. get: function get () {
  181. return tmp.imageLoad
  182. },
  183. set: function set (value) {
  184. tmp.imageLoad = value;
  185. }
  186. },
  187. onBeforeDraw: {
  188. default: null,
  189. get: function get () {
  190. return tmp.beforeDraw
  191. },
  192. set: function set (value) {
  193. tmp.beforeDraw = value;
  194. }
  195. }
  196. };
  197. var ref$1 = getDevice();
  198. var windowWidth = ref$1.windowWidth;
  199. function prepare () {
  200. var self = this;
  201. // v1.4.0 版本中将不再自动绑定we-cropper实例
  202. self.attachPage = function () {
  203. var pages = getCurrentPages();
  204. // 获取到当前page上下文
  205. var pageContext = pages[pages.length - 1];
  206. // 把this依附在Page上下文的wecropper属性上,便于在page钩子函数中访问
  207. Object.defineProperty(pageContext, 'wecropper', {
  208. get: function get () {
  209. console.warn(
  210. 'Instance will not be automatically bound to the page after v1.4.0\n\n' +
  211. 'Please use a custom instance name instead\n\n' +
  212. 'Example: \n' +
  213. 'this.mycropper = new WeCropper(options)\n\n' +
  214. '// ...\n' +
  215. 'this.mycropper.getCropperImage()'
  216. );
  217. return self
  218. },
  219. configurable: true
  220. });
  221. };
  222. self.createCtx = function () {
  223. var id = self.id;
  224. var targetId = self.targetId;
  225. if (id) {
  226. self.ctx = self.ctx || wx.createCanvasContext(id);
  227. self.targetCtx = self.targetCtx || wx.createCanvasContext(targetId);
  228. // 2d 没有这个方法
  229. if (typeof self.ctx.setStrokeStyle !== 'function') {
  230. self.type = '2d';
  231. }
  232. } else {
  233. console.error("constructor: create canvas context failed, 'id' must be valuable");
  234. }
  235. };
  236. self.deviceRadio = windowWidth / 750;
  237. }
  238. /**
  239. * String type check
  240. */
  241. /**
  242. * Number type check
  243. */
  244. /**
  245. * Array type check
  246. */
  247. /**
  248. * undefined type check
  249. */
  250. /**
  251. * Function type check
  252. */
  253. var isFunc = function (v) { return typeof v === 'function'; };
  254. /**
  255. * Quick object check - this is primarily used to tell
  256. * Objects from primitive values when we know the value
  257. * is a JSON-compliant type.
  258. */
  259. /**
  260. * Perform no operation.
  261. * Stubbing args to make Flow happy without leaving useless transpiled code
  262. * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/)
  263. */
  264. /**
  265. * Check if val is a valid array index.
  266. */
  267. var EVENT_TYPE = ['ready', 'beforeImageLoad', 'beforeDraw', 'imageLoad'];
  268. function observer () {
  269. var self = this;
  270. self.on = function (event, fn) {
  271. if (EVENT_TYPE.indexOf(event) > -1) {
  272. if (isFunc(fn)) {
  273. event === 'ready'
  274. ? fn(self)
  275. : self[("on" + (firstLetterUpper(event)))] = fn;
  276. }
  277. } else {
  278. console.error(("event: " + event + " is invalid"));
  279. }
  280. return self
  281. };
  282. }
  283. function wxPromise (fn) {
  284. return function (obj) {
  285. var args = [], len = arguments.length - 1;
  286. while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ];
  287. if ( obj === void 0 ) obj = {};
  288. return new Promise(function (resolve, reject) {
  289. obj.success = function (res) {
  290. resolve(res);
  291. };
  292. obj.fail = function (err) {
  293. reject(err);
  294. };
  295. fn.apply(void 0, [ obj ].concat( args ));
  296. })
  297. }
  298. }
  299. function draw (ctx, reserve) {
  300. if ( reserve === void 0 ) reserve = false;
  301. return new Promise(function (resolve) {
  302. ctx.draw && ctx.draw(reserve, resolve);
  303. })
  304. }
  305. var getImageInfo = wxPromise(wx.getImageInfo);
  306. var canvasToTempFilePath = wxPromise(wx.canvasToTempFilePath);
  307. var loadCanvasImage = function (context, src) {
  308. return new Promise(function (resolve, reject) {
  309. if (context.type === '2d') {
  310. var img = context.canvas.createImage();
  311. img.onload = function () {
  312. resolve(img);
  313. };
  314. img.onerror = function (e) {
  315. reject(e);
  316. };
  317. img.src = src;
  318. } else {
  319. resolve(src);
  320. }
  321. })
  322. };
  323. var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
  324. function createCommonjsModule(fn, module) {
  325. return module = { exports: {} }, fn(module, module.exports), module.exports;
  326. }
  327. var base64 = createCommonjsModule(function (module, exports) {
  328. /*! http://mths.be/base64 v0.1.0 by @mathias | MIT license */
  329. (function(root) {
  330. // Detect free variables `exports`.
  331. var freeExports = 'object' == 'object' && exports;
  332. // Detect free variable `module`.
  333. var freeModule = 'object' == 'object' && module &&
  334. module.exports == freeExports && module;
  335. // Detect free variable `global`, from Node.js or Browserified code, and use
  336. // it as `root`.
  337. var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal;
  338. if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
  339. root = freeGlobal;
  340. }
  341. /*--------------------------------------------------------------------------*/
  342. var InvalidCharacterError = function(message) {
  343. this.message = message;
  344. };
  345. InvalidCharacterError.prototype = new Error;
  346. InvalidCharacterError.prototype.name = 'InvalidCharacterError';
  347. var error = function(message) {
  348. // Note: the error messages used throughout this file match those used by
  349. // the native `atob`/`btoa` implementation in Chromium.
  350. throw new InvalidCharacterError(message);
  351. };
  352. var TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
  353. // http://whatwg.org/html/common-microsyntaxes.html#space-character
  354. var REGEX_SPACE_CHARACTERS = /[\t\n\f\r ]/g;
  355. // `decode` is designed to be fully compatible with `atob` as described in the
  356. // HTML Standard. http://whatwg.org/html/webappapis.html#dom-windowbase64-atob
  357. // The optimized base64-decoding algorithm used is based on @atk’s excellent
  358. // implementation. https://gist.github.com/atk/1020396
  359. var decode = function(input) {
  360. input = String(input)
  361. .replace(REGEX_SPACE_CHARACTERS, '');
  362. var length = input.length;
  363. if (length % 4 == 0) {
  364. input = input.replace(/==?$/, '');
  365. length = input.length;
  366. }
  367. if (
  368. length % 4 == 1 ||
  369. // http://whatwg.org/C#alphanumeric-ascii-characters
  370. /[^+a-zA-Z0-9/]/.test(input)
  371. ) {
  372. error(
  373. 'Invalid character: the string to be decoded is not correctly encoded.'
  374. );
  375. }
  376. var bitCounter = 0;
  377. var bitStorage;
  378. var buffer;
  379. var output = '';
  380. var position = -1;
  381. while (++position < length) {
  382. buffer = TABLE.indexOf(input.charAt(position));
  383. bitStorage = bitCounter % 4 ? bitStorage * 64 + buffer : buffer;
  384. // Unless this is the first of a group of 4 characters…
  385. if (bitCounter++ % 4) {
  386. // …convert the first 8 bits to a single ASCII character.
  387. output += String.fromCharCode(
  388. 0xFF & bitStorage >> (-2 * bitCounter & 6)
  389. );
  390. }
  391. }
  392. return output;
  393. };
  394. // `encode` is designed to be fully compatible with `btoa` as described in the
  395. // HTML Standard: http://whatwg.org/html/webappapis.html#dom-windowbase64-btoa
  396. var encode = function(input) {
  397. input = String(input);
  398. if (/[^\0-\xFF]/.test(input)) {
  399. // Note: no need to special-case astral symbols here, as surrogates are
  400. // matched, and the input is supposed to only contain ASCII anyway.
  401. error(
  402. 'The string to be encoded contains characters outside of the ' +
  403. 'Latin1 range.'
  404. );
  405. }
  406. var padding = input.length % 3;
  407. var output = '';
  408. var position = -1;
  409. var a;
  410. var b;
  411. var c;
  412. var buffer;
  413. // Make sure any padding is handled outside of the loop.
  414. var length = input.length - padding;
  415. while (++position < length) {
  416. // Read three bytes, i.e. 24 bits.
  417. a = input.charCodeAt(position) << 16;
  418. b = input.charCodeAt(++position) << 8;
  419. c = input.charCodeAt(++position);
  420. buffer = a + b + c;
  421. // Turn the 24 bits into four chunks of 6 bits each, and append the
  422. // matching character for each of them to the output.
  423. output += (
  424. TABLE.charAt(buffer >> 18 & 0x3F) +
  425. TABLE.charAt(buffer >> 12 & 0x3F) +
  426. TABLE.charAt(buffer >> 6 & 0x3F) +
  427. TABLE.charAt(buffer & 0x3F)
  428. );
  429. }
  430. if (padding == 2) {
  431. a = input.charCodeAt(position) << 8;
  432. b = input.charCodeAt(++position);
  433. buffer = a + b;
  434. output += (
  435. TABLE.charAt(buffer >> 10) +
  436. TABLE.charAt((buffer >> 4) & 0x3F) +
  437. TABLE.charAt((buffer << 2) & 0x3F) +
  438. '='
  439. );
  440. } else if (padding == 1) {
  441. buffer = input.charCodeAt(position);
  442. output += (
  443. TABLE.charAt(buffer >> 2) +
  444. TABLE.charAt((buffer << 4) & 0x3F) +
  445. '=='
  446. );
  447. }
  448. return output;
  449. };
  450. var base64 = {
  451. 'encode': encode,
  452. 'decode': decode,
  453. 'version': '0.1.0'
  454. };
  455. // Some AMD build optimizers, like r.js, check for specific condition patterns
  456. // like the following:
  457. if (
  458. typeof undefined == 'function' &&
  459. typeof undefined.amd == 'object' &&
  460. undefined.amd
  461. ) {
  462. undefined(function() {
  463. return base64;
  464. });
  465. } else if (freeExports && !freeExports.nodeType) {
  466. if (freeModule) { // in Node.js or RingoJS v0.8.0+
  467. freeModule.exports = base64;
  468. } else { // in Narwhal or RingoJS v0.7.0-
  469. for (var key in base64) {
  470. base64.hasOwnProperty(key) && (freeExports[key] = base64[key]);
  471. }
  472. }
  473. } else { // in Rhino or a web browser
  474. root.base64 = base64;
  475. }
  476. }(commonjsGlobal));
  477. });
  478. function makeURI (strData, type) {
  479. return 'data:' + type + ';base64,' + strData
  480. }
  481. function fixType (type) {
  482. type = type.toLowerCase().replace(/jpg/i, 'jpeg');
  483. var r = type.match(/png|jpeg|bmp|gif/)[0];
  484. return 'image/' + r
  485. }
  486. function encodeData (data) {
  487. var str = '';
  488. if (typeof data === 'string') {
  489. str = data;
  490. } else {
  491. for (var i = 0; i < data.length; i++) {
  492. str += String.fromCharCode(data[i]);
  493. }
  494. }
  495. return base64.encode(str)
  496. }
  497. /**
  498. * 获取图像区域隐含的像素数据
  499. * @param canvasId canvas标识
  500. * @param x 将要被提取的图像数据矩形区域的左上角 x 坐标
  501. * @param y 将要被提取的图像数据矩形区域的左上角 y 坐标
  502. * @param width 将要被提取的图像数据矩形区域的宽度
  503. * @param height 将要被提取的图像数据矩形区域的高度
  504. * @param done 完成回调
  505. */
  506. function getImageData (canvasId, x, y, width, height, done) {
  507. wx.canvasGetImageData({
  508. canvasId: canvasId,
  509. x: x,
  510. y: y,
  511. width: width,
  512. height: height,
  513. success: function success (res) {
  514. done(res, null);
  515. },
  516. fail: function fail (res) {
  517. done(null, res);
  518. }
  519. });
  520. }
  521. /**
  522. * 生成bmp格式图片
  523. * 按照规则生成图片响应头和响应体
  524. * @param oData 用来描述 canvas 区域隐含的像素数据 { data, width, height } = oData
  525. * @returns {*} base64字符串
  526. */
  527. function genBitmapImage (oData) {
  528. //
  529. // BITMAPFILEHEADER: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183374(v=vs.85).aspx
  530. // BITMAPINFOHEADER: http://msdn.microsoft.com/en-us/library/dd183376.aspx
  531. //
  532. var biWidth = oData.width;
  533. var biHeight = oData.height;
  534. var biSizeImage = biWidth * biHeight * 3;
  535. var bfSize = biSizeImage + 54; // total header size = 54 bytes
  536. //
  537. // typedef struct tagBITMAPFILEHEADER {
  538. // WORD bfType;
  539. // DWORD bfSize;
  540. // WORD bfReserved1;
  541. // WORD bfReserved2;
  542. // DWORD bfOffBits;
  543. // } BITMAPFILEHEADER;
  544. //
  545. var BITMAPFILEHEADER = [
  546. // WORD bfType -- The file type signature; must be "BM"
  547. 0x42, 0x4D,
  548. // DWORD bfSize -- The size, in bytes, of the bitmap file
  549. bfSize & 0xff, bfSize >> 8 & 0xff, bfSize >> 16 & 0xff, bfSize >> 24 & 0xff,
  550. // WORD bfReserved1 -- Reserved; must be zero
  551. 0, 0,
  552. // WORD bfReserved2 -- Reserved; must be zero
  553. 0, 0,
  554. // DWORD bfOffBits -- The offset, in bytes, from the beginning of the BITMAPFILEHEADER structure to the bitmap bits.
  555. 54, 0, 0, 0
  556. ];
  557. //
  558. // typedef struct tagBITMAPINFOHEADER {
  559. // DWORD biSize;
  560. // LONG biWidth;
  561. // LONG biHeight;
  562. // WORD biPlanes;
  563. // WORD biBitCount;
  564. // DWORD biCompression;
  565. // DWORD biSizeImage;
  566. // LONG biXPelsPerMeter;
  567. // LONG biYPelsPerMeter;
  568. // DWORD biClrUsed;
  569. // DWORD biClrImportant;
  570. // } BITMAPINFOHEADER, *PBITMAPINFOHEADER;
  571. //
  572. var BITMAPINFOHEADER = [
  573. // DWORD biSize -- The number of bytes required by the structure
  574. 40, 0, 0, 0,
  575. // LONG biWidth -- The width of the bitmap, in pixels
  576. biWidth & 0xff, biWidth >> 8 & 0xff, biWidth >> 16 & 0xff, biWidth >> 24 & 0xff,
  577. // LONG biHeight -- The height of the bitmap, in pixels
  578. biHeight & 0xff, biHeight >> 8 & 0xff, biHeight >> 16 & 0xff, biHeight >> 24 & 0xff,
  579. // WORD biPlanes -- The number of planes for the target device. This value must be set to 1
  580. 1, 0,
  581. // WORD biBitCount -- The number of bits-per-pixel, 24 bits-per-pixel -- the bitmap
  582. // has a maximum of 2^24 colors (16777216, Truecolor)
  583. 24, 0,
  584. // DWORD biCompression -- The type of compression, BI_RGB (code 0) -- uncompressed
  585. 0, 0, 0, 0,
  586. // DWORD biSizeImage -- The size, in bytes, of the image. This may be set to zero for BI_RGB bitmaps
  587. biSizeImage & 0xff, biSizeImage >> 8 & 0xff, biSizeImage >> 16 & 0xff, biSizeImage >> 24 & 0xff,
  588. // LONG biXPelsPerMeter, unused
  589. 0, 0, 0, 0,
  590. // LONG biYPelsPerMeter, unused
  591. 0, 0, 0, 0,
  592. // DWORD biClrUsed, the number of color indexes of palette, unused
  593. 0, 0, 0, 0,
  594. // DWORD biClrImportant, unused
  595. 0, 0, 0, 0
  596. ];
  597. var iPadding = (4 - ((biWidth * 3) % 4)) % 4;
  598. var aImgData = oData.data;
  599. var strPixelData = '';
  600. var biWidth4 = biWidth << 2;
  601. var y = biHeight;
  602. var fromCharCode = String.fromCharCode;
  603. do {
  604. var iOffsetY = biWidth4 * (y - 1);
  605. var strPixelRow = '';
  606. for (var x = 0; x < biWidth; x++) {
  607. var iOffsetX = x << 2;
  608. strPixelRow += fromCharCode(aImgData[iOffsetY + iOffsetX + 2]) +
  609. fromCharCode(aImgData[iOffsetY + iOffsetX + 1]) +
  610. fromCharCode(aImgData[iOffsetY + iOffsetX]);
  611. }
  612. for (var c = 0; c < iPadding; c++) {
  613. strPixelRow += String.fromCharCode(0);
  614. }
  615. strPixelData += strPixelRow;
  616. } while (--y)
  617. var strEncoded = encodeData(BITMAPFILEHEADER.concat(BITMAPINFOHEADER)) + encodeData(strPixelData);
  618. return strEncoded
  619. }
  620. /**
  621. * 转换为图片base64
  622. * @param canvasId canvas标识
  623. * @param x 将要被提取的图像数据矩形区域的左上角 x 坐标
  624. * @param y 将要被提取的图像数据矩形区域的左上角 y 坐标
  625. * @param width 将要被提取的图像数据矩形区域的宽度
  626. * @param height 将要被提取的图像数据矩形区域的高度
  627. * @param type 转换图片类型
  628. * @param done 完成回调
  629. */
  630. function convertToImage (canvasId, x, y, width, height, type, done) {
  631. if ( done === void 0 ) done = function () {};
  632. if (type === undefined) { type = 'png'; }
  633. type = fixType(type);
  634. if (/bmp/.test(type)) {
  635. getImageData(canvasId, x, y, width, height, function (data, err) {
  636. var strData = genBitmapImage(data);
  637. isFunc(done) && done(makeURI(strData, 'image/' + type), err);
  638. });
  639. } else {
  640. console.error('暂不支持生成\'' + type + '\'类型的base64图片');
  641. }
  642. }
  643. var CanvasToBase64 = {
  644. convertToImage: convertToImage,
  645. // convertToPNG: function (width, height, done) {
  646. // return convertToImage(width, height, 'png', done)
  647. // },
  648. // convertToJPEG: function (width, height, done) {
  649. // return convertToImage(width, height, 'jpeg', done)
  650. // },
  651. // convertToGIF: function (width, height, done) {
  652. // return convertToImage(width, height, 'gif', done)
  653. // },
  654. convertToBMP: function (ref, done) {
  655. if ( ref === void 0 ) ref = {};
  656. var canvasId = ref.canvasId;
  657. var x = ref.x;
  658. var y = ref.y;
  659. var width = ref.width;
  660. var height = ref.height;
  661. if ( done === void 0 ) done = function () {};
  662. return convertToImage(canvasId, x, y, width, height, 'bmp', done)
  663. }
  664. };
  665. function methods () {
  666. var self = this;
  667. var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度
  668. var boundHeight = self.height; // 裁剪框默认高度,即整个画布高度
  669. var id = self.id;
  670. var targetId = self.targetId;
  671. var pixelRatio = self.pixelRatio;
  672. var ref = self.cut;
  673. var x = ref.x; if ( x === void 0 ) x = 0;
  674. var y = ref.y; if ( y === void 0 ) y = 0;
  675. var width = ref.width; if ( width === void 0 ) width = boundWidth;
  676. var height = ref.height; if ( height === void 0 ) height = boundHeight;
  677. self.updateCanvas = function (done) {
  678. if (self.croperTarget) {
  679. // 画布绘制图片
  680. self.ctx.drawImage(
  681. self.croperTarget,
  682. self.imgLeft,
  683. self.imgTop,
  684. self.scaleWidth,
  685. self.scaleHeight
  686. );
  687. }
  688. isFunc(self.onBeforeDraw) && self.onBeforeDraw(self.ctx, self);
  689. self.setBoundStyle(self.boundStyle); // 设置边界样式
  690. if (self.type !== '2d') {
  691. self.ctx.draw(false, done);
  692. }
  693. done && done();
  694. return self
  695. };
  696. self.pushOrigin = self.pushOrign = function (src) {
  697. self.src = src;
  698. isFunc(self.onBeforeImageLoad) && self.onBeforeImageLoad(self.ctx, self);
  699. return loadCanvasImage(self, src).then(function (img) {
  700. self.croperTarget = img;
  701. return getImageInfo({ src: src })
  702. .then(function (res) {
  703. var innerAspectRadio = res.width / res.height;
  704. var customAspectRadio = width / height;
  705. if (innerAspectRadio < customAspectRadio) {
  706. self.rectX = x;
  707. self.baseWidth = width;
  708. self.baseHeight = width / innerAspectRadio;
  709. self.rectY = y - Math.abs((height - self.baseHeight) / 2);
  710. } else {
  711. self.rectY = y;
  712. self.baseWidth = height * innerAspectRadio;
  713. self.baseHeight = height;
  714. self.rectX = x - Math.abs((width - self.baseWidth) / 2);
  715. }
  716. self.imgLeft = self.rectX;
  717. self.imgTop = self.rectY;
  718. self.scaleWidth = self.baseWidth;
  719. self.scaleHeight = self.baseHeight;
  720. self.update();
  721. return new Promise(function (resolve) {
  722. self.updateCanvas(resolve);
  723. })
  724. })
  725. .then(function () {
  726. isFunc(self.onImageLoad) && self.onImageLoad(self.ctx, self);
  727. })
  728. })
  729. };
  730. self.removeImage = function () {
  731. self.src = '';
  732. self.croperTarget = '';
  733. if (self.type === '2d') {
  734. return self.ctx.clearRect(0, 0, self.canvas.width, self.canvas.height)
  735. } else {
  736. return draw(self.ctx)
  737. }
  738. };
  739. self.getCropperBase64 = function (done) {
  740. if ( done === void 0 ) done = function () {};
  741. CanvasToBase64.convertToBMP({
  742. canvasId: id,
  743. x: x,
  744. y: y,
  745. width: width,
  746. height: height
  747. }, done);
  748. };
  749. self.getCropperImage = function (opt, fn) {
  750. var customOptions = Object.assign({fileType: 'jpg'}, opt);
  751. var callback = isFunc(opt) ? opt : isFunc(fn) ? fn : null;
  752. var canvasOptions = {
  753. canvasId: id,
  754. x: x,
  755. y: y,
  756. width: width,
  757. height: height
  758. };
  759. if (self.type === '2d') {
  760. canvasOptions.canvas = self.canvas;
  761. }
  762. var task = function () { return Promise.resolve(); };
  763. if (customOptions.original) {
  764. // original mode
  765. task = function () {
  766. self.targetCtx.drawImage(
  767. self.croperTarget,
  768. self.imgLeft * pixelRatio,
  769. self.imgTop * pixelRatio,
  770. self.scaleWidth * pixelRatio,
  771. self.scaleHeight * pixelRatio
  772. );
  773. canvasOptions = {
  774. canvasId: targetId,
  775. x: x * pixelRatio,
  776. y: y * pixelRatio,
  777. width: width * pixelRatio,
  778. height: height * pixelRatio
  779. };
  780. return draw(self.targetCtx)
  781. };
  782. }
  783. return task()
  784. .then(function () {
  785. Object.assign(canvasOptions, customOptions);
  786. var arg = canvasOptions.componentContext
  787. ? [canvasOptions, canvasOptions.componentContext]
  788. : [canvasOptions];
  789. return canvasToTempFilePath.apply(null, arg)
  790. })
  791. .then(function (res) {
  792. var tempFilePath = res.tempFilePath;
  793. return callback
  794. ? callback.call(self, tempFilePath, null)
  795. : tempFilePath
  796. })
  797. .catch(function (err) {
  798. if (callback) {
  799. callback.call(self, null, err);
  800. } else {
  801. throw err
  802. }
  803. })
  804. };
  805. }
  806. /**
  807. * 获取最新缩放值
  808. * @param oldScale 上一次触摸结束后的缩放值
  809. * @param oldDistance 上一次触摸结束后的双指距离
  810. * @param zoom 缩放系数
  811. * @param touch0 第一指touch对象
  812. * @param touch1 第二指touch对象
  813. * @returns {*}
  814. */
  815. var getNewScale = function (oldScale, oldDistance, zoom, touch0, touch1) {
  816. var xMove, yMove, newDistance;
  817. // 计算二指最新距离
  818. xMove = Math.round(touch1.x - touch0.x);
  819. yMove = Math.round(touch1.y - touch0.y);
  820. newDistance = Math.round(Math.sqrt(xMove * xMove + yMove * yMove));
  821. return oldScale + 0.001 * zoom * (newDistance - oldDistance)
  822. };
  823. function update () {
  824. var self = this;
  825. if (!self.src) { return }
  826. self.__oneTouchStart = function (touch) {
  827. self.touchX0 = Math.round(touch.x);
  828. self.touchY0 = Math.round(touch.y);
  829. };
  830. self.__oneTouchMove = function (touch) {
  831. var xMove, yMove;
  832. // 计算单指移动的距离
  833. if (self.touchended) {
  834. return self.updateCanvas()
  835. }
  836. xMove = Math.round(touch.x - self.touchX0);
  837. yMove = Math.round(touch.y - self.touchY0);
  838. var imgLeft = Math.round(self.rectX + xMove);
  839. var imgTop = Math.round(self.rectY + yMove);
  840. self.outsideBound(imgLeft, imgTop);
  841. self.updateCanvas();
  842. };
  843. self.__twoTouchStart = function (touch0, touch1) {
  844. var xMove, yMove, oldDistance;
  845. self.touchX1 = Math.round(self.rectX + self.scaleWidth / 2);
  846. self.touchY1 = Math.round(self.rectY + self.scaleHeight / 2);
  847. // 计算两指距离
  848. xMove = Math.round(touch1.x - touch0.x);
  849. yMove = Math.round(touch1.y - touch0.y);
  850. oldDistance = Math.round(Math.sqrt(xMove * xMove + yMove * yMove));
  851. self.oldDistance = oldDistance;
  852. };
  853. self.__twoTouchMove = function (touch0, touch1) {
  854. var oldScale = self.oldScale;
  855. var oldDistance = self.oldDistance;
  856. var scale = self.scale;
  857. var zoom = self.zoom;
  858. self.newScale = getNewScale(oldScale, oldDistance, zoom, touch0, touch1);
  859. // 设定缩放范围
  860. self.newScale <= 1 && (self.newScale = 1);
  861. self.newScale >= scale && (self.newScale = scale);
  862. self.scaleWidth = Math.round(self.newScale * self.baseWidth);
  863. self.scaleHeight = Math.round(self.newScale * self.baseHeight);
  864. var imgLeft = Math.round(self.touchX1 - self.scaleWidth / 2);
  865. var imgTop = Math.round(self.touchY1 - self.scaleHeight / 2);
  866. self.outsideBound(imgLeft, imgTop);
  867. self.updateCanvas();
  868. };
  869. self.__xtouchEnd = function () {
  870. self.oldScale = self.newScale;
  871. self.rectX = self.imgLeft;
  872. self.rectY = self.imgTop;
  873. };
  874. }
  875. var handle = {
  876. // 图片手势初始监测
  877. touchStart: function touchStart (e) {
  878. var self = this;
  879. var ref = e.touches;
  880. var touch0 = ref[0];
  881. var touch1 = ref[1];
  882. if (!self.src) { return }
  883. setTouchState(self, true, null, null);
  884. // 计算第一个触摸点的位置,并参照改点进行缩放
  885. self.__oneTouchStart(touch0);
  886. // 两指手势触发
  887. if (e.touches.length >= 2) {
  888. self.__twoTouchStart(touch0, touch1);
  889. }
  890. },
  891. // 图片手势动态缩放
  892. touchMove: function touchMove (e) {
  893. var self = this;
  894. var ref = e.touches;
  895. var touch0 = ref[0];
  896. var touch1 = ref[1];
  897. if (!self.src) { return }
  898. setTouchState(self, null, true);
  899. // 单指手势时触发
  900. if (e.touches.length === 1) {
  901. self.__oneTouchMove(touch0);
  902. }
  903. // 两指手势触发
  904. if (e.touches.length >= 2) {
  905. self.__twoTouchMove(touch0, touch1);
  906. }
  907. },
  908. touchEnd: function touchEnd (e) {
  909. var self = this;
  910. if (!self.src) { return }
  911. setTouchState(self, false, false, true);
  912. self.__xtouchEnd();
  913. }
  914. };
  915. function cut () {
  916. var self = this;
  917. var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度
  918. var boundHeight = self.height;
  919. // 裁剪框默认高度,即整个画布高度
  920. var ref = self.cut;
  921. var x = ref.x; if ( x === void 0 ) x = 0;
  922. var y = ref.y; if ( y === void 0 ) y = 0;
  923. var width = ref.width; if ( width === void 0 ) width = boundWidth;
  924. var height = ref.height; if ( height === void 0 ) height = boundHeight;
  925. /**
  926. * 设置边界
  927. * @param imgLeft 图片左上角横坐标值
  928. * @param imgTop 图片左上角纵坐标值
  929. */
  930. self.outsideBound = function (imgLeft, imgTop) {
  931. self.imgLeft = imgLeft >= x
  932. ? x
  933. : self.scaleWidth + imgLeft - x <= width
  934. ? x + width - self.scaleWidth
  935. : imgLeft;
  936. self.imgTop = imgTop >= y
  937. ? y
  938. : self.scaleHeight + imgTop - y <= height
  939. ? y + height - self.scaleHeight
  940. : imgTop;
  941. };
  942. /**
  943. * 设置边界样式
  944. * @param color 边界颜色
  945. */
  946. self.setBoundStyle = function (ref) {
  947. if ( ref === void 0 ) ref = {};
  948. var color = ref.color; if ( color === void 0 ) color = '#04b00f';
  949. var mask = ref.mask; if ( mask === void 0 ) mask = 'rgba(0, 0, 0, 0.3)';
  950. var lineWidth = ref.lineWidth; if ( lineWidth === void 0 ) lineWidth = 1;
  951. var half = lineWidth / 2;
  952. var boundOption = [
  953. {
  954. start: { x: x - half, y: y + 10 - half },
  955. step1: { x: x - half, y: y - half },
  956. step2: { x: x + 10 - half, y: y - half }
  957. },
  958. {
  959. start: { x: x - half, y: y + height - 10 + half },
  960. step1: { x: x - half, y: y + height + half },
  961. step2: { x: x + 10 - half, y: y + height + half }
  962. },
  963. {
  964. start: { x: x + width - 10 + half, y: y - half },
  965. step1: { x: x + width + half, y: y - half },
  966. step2: { x: x + width + half, y: y + 10 - half }
  967. },
  968. {
  969. start: { x: x + width + half, y: y + height - 10 + half },
  970. step1: { x: x + width + half, y: y + height + half },
  971. step2: { x: x + width - 10 + half, y: y + height + half }
  972. }
  973. ];
  974. // 绘制半透明层
  975. self.ctx.beginPath();
  976. adapt2d(self, 'fillStyle', mask);
  977. self.ctx.fillRect(0, 0, x, boundHeight);
  978. self.ctx.fillRect(x, 0, width, y);
  979. self.ctx.fillRect(x, y + height, width, boundHeight - y - height);
  980. self.ctx.fillRect(x + width, 0, boundWidth - x - width, boundHeight);
  981. self.ctx.fill();
  982. boundOption.forEach(function (op) {
  983. self.ctx.beginPath();
  984. adapt2d(self, 'strokeStyle', color);
  985. adapt2d(self, 'lineWidth', lineWidth);
  986. self.ctx.moveTo(op.start.x, op.start.y);
  987. self.ctx.lineTo(op.step1.x, op.step1.y);
  988. self.ctx.lineTo(op.step2.x, op.step2.y);
  989. self.ctx.stroke();
  990. });
  991. };
  992. }
  993. var version = "1.4.0";
  994. var WeCropper = function WeCropper (params) {
  995. var self = this;
  996. var _default = {};
  997. validator(self, DEFAULT);
  998. Object.keys(DEFAULT).forEach(function (key) {
  999. _default[key] = DEFAULT[key].default;
  1000. });
  1001. Object.assign(self, _default, params);
  1002. self.prepare();
  1003. self.attachPage();
  1004. self.createCtx();
  1005. self.observer();
  1006. self.cutt();
  1007. self.methods();
  1008. self.init();
  1009. self.update();
  1010. return self
  1011. };
  1012. WeCropper.prototype.init = function init () {
  1013. var self = this;
  1014. var src = self.src;
  1015. self.version = version;
  1016. typeof self.onReady === 'function' && self.onReady(self.ctx, self);
  1017. if (src) {
  1018. self.pushOrign(src);
  1019. } else {
  1020. self.updateCanvas();
  1021. }
  1022. setTouchState(self, false, false, false);
  1023. self.oldScale = 1;
  1024. self.newScale = 1;
  1025. return self
  1026. };
  1027. Object.assign(WeCropper.prototype, handle);
  1028. WeCropper.prototype.prepare = prepare;
  1029. WeCropper.prototype.observer = observer;
  1030. WeCropper.prototype.methods = methods;
  1031. WeCropper.prototype.cutt = cut;
  1032. WeCropper.prototype.update = update;
  1033. return WeCropper;
  1034. })));