1
0

canvasResize.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. /*
  2. *
  3. * canvasResize
  4. *
  5. * Version: 1.2.0
  6. * Date (d/m/y): 02/10/12
  7. * Update (d/m/y): 14/05/13
  8. * Original author: @gokercebeci
  9. * Licensed under the MIT license
  10. * - This plugin working with binaryajax.js and exif.js
  11. * (It's under the MPL License http://www.nihilogic.dk/licenses/mpl-license.txt)
  12. * Demo: http://canvasResize.gokercebeci.com/
  13. *
  14. * - I fixed iOS6 Safari's image file rendering issue for large size image (over mega-pixel)
  15. * using few functions from https://github.com/stomita/ios-imagefile-megapixel
  16. * (detectSubsampling, )
  17. * And fixed orientation issue by using https://github.com/jseidelin/exif-js
  18. * Thanks, Shinichi Tomita and Jacob Seidelin
  19. */
  20. (function($) {
  21. var pluginName = 'canvasResize',
  22. methods = {
  23. newsize: function(w, h, W, H, C) {
  24. var c = C ? 'h' : '';
  25. if ((W && w > W) || (H && h > H)) {
  26. var r = w / h;
  27. if ((r >= 1 || H === 0) && W && !C) {
  28. w = W;
  29. h = (W / r) >> 0;
  30. } else if (C && r <= (W / H)) {
  31. w = W;
  32. h = (W / r) >> 0;
  33. c = 'w';
  34. } else {
  35. w = (H * r) >> 0;
  36. h = H;
  37. }
  38. }
  39. return {
  40. 'width': w,
  41. 'height': h,
  42. 'cropped': c
  43. };
  44. },
  45. dataURLtoBlob: function(data) {
  46. var mimeString = data.split(',')[0].split(':')[1].split(';')[0];
  47. var byteString = atob(data.split(',')[1]);
  48. var ab = new ArrayBuffer(byteString.length);
  49. var ia = new Uint8Array(ab);
  50. for (var i = 0; i < byteString.length; i++) {
  51. ia[i] = byteString.charCodeAt(i);
  52. }
  53. var bb = (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder);
  54. if (bb) {
  55. // console.log('BlobBuilder');
  56. bb = new (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder)();
  57. bb.append(ab);
  58. return bb.getBlob(mimeString);
  59. } else {
  60. // console.log('Blob');
  61. bb = new Blob([ab], {
  62. 'type': (mimeString)
  63. });
  64. return bb;
  65. }
  66. },
  67. /**
  68. * Detect subsampling in loaded image.
  69. * In iOS, larger images than 2M pixels may be subsampled in rendering.
  70. */
  71. detectSubsampling: function(img) {
  72. var iw = img.width, ih = img.height;
  73. if (iw * ih > 1048576) { // subsampling may happen over megapixel image
  74. var canvas = document.createElement('canvas');
  75. canvas.width = canvas.height = 1;
  76. var ctx = canvas.getContext('2d');
  77. ctx.drawImage(img, -iw + 1, 0);
  78. // subsampled image becomes half smaller in rendering size.
  79. // check alpha channel value to confirm image is covering edge pixel or not.
  80. // if alpha value is 0 image is not covering, hence subsampled.
  81. return ctx.getImageData(0, 0, 1, 1).data[3] === 0;
  82. } else {
  83. return false;
  84. }
  85. },
  86. /**
  87. * Update the orientation according to the specified rotation angle
  88. */
  89. rotate: function(orientation, angle) {
  90. var o = {
  91. // nothing
  92. 1: {90: 6, 180: 3, 270: 8},
  93. // horizontal flip
  94. 2: {90: 7, 180: 4, 270: 5},
  95. // 180 rotate left
  96. 3: {90: 8, 180: 1, 270: 6},
  97. // vertical flip
  98. 4: {90: 5, 180: 2, 270: 7},
  99. // vertical flip + 90 rotate right
  100. 5: {90: 2, 180: 7, 270: 4},
  101. // 90 rotate right
  102. 6: {90: 3, 180: 8, 270: 1},
  103. // horizontal flip + 90 rotate right
  104. 7: {90: 4, 180: 5, 270: 2},
  105. // 90 rotate left
  106. 8: {90: 1, 180: 6, 270: 3}
  107. };
  108. return o[orientation][angle] ? o[orientation][angle] : orientation;
  109. },
  110. /**
  111. * Transform canvas coordination according to specified frame size and orientation
  112. * Orientation value is from EXIF tag
  113. */
  114. transformCoordinate: function(canvas, width, height, orientation) {
  115. switch (orientation) {
  116. case 5:
  117. case 6:
  118. case 7:
  119. case 8:
  120. canvas.width = height;
  121. canvas.height = width;
  122. break;
  123. default:
  124. canvas.width = width;
  125. canvas.height = height;
  126. }
  127. var ctx = canvas.getContext('2d');
  128. switch (orientation) {
  129. case 1:
  130. // nothing
  131. break;
  132. case 2:
  133. // horizontal flip
  134. ctx.translate(width, 0);
  135. ctx.scale(-1, 1);
  136. break;
  137. case 3:
  138. // 180 rotate left
  139. ctx.translate(width, height);
  140. ctx.rotate(Math.PI);
  141. break;
  142. case 4:
  143. // vertical flip
  144. ctx.translate(0, height);
  145. ctx.scale(1, -1);
  146. break;
  147. case 5:
  148. // vertical flip + 90 rotate right
  149. ctx.rotate(0.5 * Math.PI);
  150. ctx.scale(1, -1);
  151. break;
  152. case 6:
  153. // 90 rotate right
  154. ctx.rotate(0.5 * Math.PI);
  155. ctx.translate(0, -height);
  156. break;
  157. case 7:
  158. // horizontal flip + 90 rotate right
  159. ctx.rotate(0.5 * Math.PI);
  160. ctx.translate(width, -height);
  161. ctx.scale(-1, 1);
  162. break;
  163. case 8:
  164. // 90 rotate left
  165. ctx.rotate(-0.5 * Math.PI);
  166. ctx.translate(-width, 0);
  167. break;
  168. default:
  169. break;
  170. }
  171. },
  172. /**
  173. * Detecting vertical squash in loaded image.
  174. * Fixes a bug which squash image vertically while drawing into canvas for some images.
  175. */
  176. detectVerticalSquash: function(img, iw, ih) {
  177. var canvas = document.createElement('canvas');
  178. canvas.width = 1;
  179. canvas.height = ih;
  180. var ctx = canvas.getContext('2d');
  181. ctx.drawImage(img, 0, 0);
  182. var data = ctx.getImageData(0, 0, 1, ih).data;
  183. // search image edge pixel position in case it is squashed vertically.
  184. var sy = 0;
  185. var ey = ih;
  186. var py = ih;
  187. while (py > sy) {
  188. var alpha = data[(py - 1) * 4 + 3];
  189. if (alpha === 0) {
  190. ey = py;
  191. } else {
  192. sy = py;
  193. }
  194. py = (ey + sy) >> 1;
  195. }
  196. var ratio = py / ih;
  197. return ratio === 0 ? 1 : ratio;
  198. },
  199. callback: function(d) {
  200. return d;
  201. },
  202. extend: function() {
  203. var target = arguments[0] || {}, a = 1, al = arguments.length, deep = false;
  204. if (target.constructor === Boolean) {
  205. deep = target;
  206. target = arguments[1] || {};
  207. }
  208. if (al === 1) {
  209. target = this;
  210. a = 0;
  211. }
  212. var prop;
  213. for (; a < al; a++)
  214. if ((prop = arguments[a]) !== null)
  215. for (var i in prop) {
  216. if (target === prop[i])
  217. continue;
  218. if (deep && typeof prop[i] === 'object' && target[i])
  219. methods.extend(target[i], prop[i]);
  220. else if (prop[i] !== undefined)
  221. target[i] = prop[i];
  222. }
  223. return target;
  224. }
  225. },
  226. defaults = {
  227. width: 300,
  228. height: 0,
  229. crop: false,
  230. quality: 80,
  231. rotate: 0,
  232. 'callback': methods.callback
  233. };
  234. function Plugin(file, options) {
  235. this.file = file;
  236. // EXTEND
  237. this.options = methods.extend({}, defaults, options);
  238. this._defaults = defaults;
  239. this._name = pluginName;
  240. this.init();
  241. }
  242. Plugin.prototype = {
  243. init: function() {
  244. //this.options.init(this);
  245. var $this = this;
  246. var file = this.file;
  247. var reader = new plus.io.FileReader();
  248. reader.onloadend = function(e) {
  249. var dataURL = e.target.result;
  250. var byteString = atob(dataURL.split(',')[1]);
  251. var binary = new BinaryFile(byteString, 0, byteString.length);
  252. var exif = EXIF.readFromBinaryFile(binary);
  253. var img = new Image();
  254. img.onload = function(e) {
  255. var orientation = exif['Orientation'] || 1;
  256. orientation = methods.rotate(orientation, $this.options.rotate);
  257. // CW or CCW ? replace width and height
  258. var size = (orientation >= 5 && orientation <= 8)
  259. ? methods.newsize(img.height, img.width, $this.options.width, $this.options.height, $this.options.crop)
  260. : methods.newsize(img.width, img.height, $this.options.width, $this.options.height, $this.options.crop);
  261. var iw = img.width, ih = img.height;
  262. var width = size.width, height = size.height;
  263. var canvas = document.createElement("canvas");
  264. var ctx = canvas.getContext("2d");
  265. ctx.save();
  266. methods.transformCoordinate(canvas, width, height, orientation);
  267. // over image size
  268. if (methods.detectSubsampling(img)) {
  269. iw /= 2;
  270. ih /= 2;
  271. }
  272. var d = 1024; // size of tiling canvas
  273. var tmpCanvas = document.createElement('canvas');
  274. tmpCanvas.width = tmpCanvas.height = d;
  275. var tmpCtx = tmpCanvas.getContext('2d');
  276. var vertSquashRatio = methods.detectVerticalSquash(img, iw, ih);
  277. var sy = 0;
  278. while (sy < ih) {
  279. var sh = sy + d > ih ? ih - sy : d;
  280. var sx = 0;
  281. while (sx < iw) {
  282. var sw = sx + d > iw ? iw - sx : d;
  283. tmpCtx.clearRect(0, 0, d, d);
  284. tmpCtx.drawImage(img, -sx, -sy);
  285. var dx = Math.floor(sx * width / iw);
  286. var dw = Math.ceil(sw * width / iw);
  287. var dy = Math.floor(sy * height / ih / vertSquashRatio);
  288. var dh = Math.ceil(sh * height / ih / vertSquashRatio);
  289. ctx.drawImage(tmpCanvas, 0, 0, sw, sh, dx, dy, dw, dh);
  290. sx += d;
  291. }
  292. sy += d;
  293. }
  294. ctx.restore();
  295. tmpCanvas = tmpCtx = null;
  296. // if rotated width and height data replacing issue
  297. var newcanvas = document.createElement('canvas');
  298. newcanvas.width = size.cropped === 'h' ? height : width;
  299. newcanvas.height = size.cropped === 'w' ? width : height;
  300. var x = size.cropped === 'h' ? (height - width) * .5 : 0;
  301. var y = size.cropped === 'w' ? (width - height) * .5 : 0;
  302. newctx = newcanvas.getContext('2d');
  303. newctx.drawImage(canvas, x, y, width, height);
  304. // console.log(file, file.type);
  305. var data = null;
  306. if (file.type === "image/png") {
  307. data = newcanvas.toDataURL(file.type);
  308. } else {
  309. data = newcanvas.toDataURL("image/jpeg", ($this.options.quality * .01));
  310. }
  311. // CALLBACK
  312. $this.options.callback(data, newcanvas.width, newcanvas.height);
  313. // });
  314. };
  315. img.src = dataURL;
  316. // =====================================================
  317. };
  318. reader.readAsDataURL(file);
  319. //reader.readAsBinaryString(file);
  320. }
  321. };
  322. $[pluginName] = function(file, options) {
  323. // console.log(file.size);
  324. if (typeof file === 'string')
  325. return methods[file](options);
  326. else
  327. new Plugin(file, options);
  328. };
  329. })(window);