UNPKG

jimu-mobile

Version:

积木组件库助力移动端开发

372 lines (357 loc) 12.1 kB
/* * * canvasResize * * Version: 1.2.0 * Date (d/m/y): 02/10/12 * Update (d/m/y): 14/05/13 * Original author: @gokercebeci * Licensed under the MIT license * - This plugin working with binaryajax.js and exif.js * (It's under the MPL License http://www.nihilogic.dk/licenses/mpl-license.txt) * Demo: http://canvasResize.gokercebeci.com/ * * - I fixed iOS6 Safari's image file rendering issue for large size image (over mega-pixel) * using few functions from https://github.com/stomita/ios-imagefile-megapixel * (detectSubsampling, ) * And fixed orientation issue by using https://github.com/jseidelin/exif-js * Thanks, Shinichi Tomita and Jacob Seidelin */ /* global BinaryFile, EXIF */ (function ($) { var pluginName = 'canvasResize' var methods = { newsize: function (w, h, W, H, C) { var c = C ? 'h' : '' if ((W && w > W) || (H && h > H)) { var r = w / h if ((r >= 1 || H === 0) && W && !C) { w = W h = (W / r) >> 0 } else if (C && r <= (W / H)) { w = W h = (W / r) >> 0 c = 'w' } else { w = (H * r) >> 0 h = H } } return { 'width': w, 'height': h, 'cropped': c } }, dataURLtoBlob: function (data) { var mimeString = data.split(',')[0].split(':')[1].split(';')[0] var byteString = atob(data.split(',')[1]) var ab = new ArrayBuffer(byteString.length) var ia = new Uint8Array(ab) for (var i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i) } var bb = (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder) if (bb) { // console.log('BlobBuilder'); bb = new (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder)() bb.append(ab) return bb.getBlob(mimeString) } else { // console.log('Blob'); bb = new Blob([ab], { 'type': (mimeString) }) return bb } }, /** * Detect subsampling in loaded image. * In iOS, larger images than 2M pixels may be subsampled in rendering. */ detectSubsampling: function (img) { var iw = img.width var ih = img.height if (iw * ih > 1048576) { // subsampling may happen over megapixel image var canvas = document.createElement('canvas') canvas.width = canvas.height = 1 var ctx = canvas.getContext('2d') ctx.drawImage(img, -iw + 1, 0) // subsampled image becomes half smaller in rendering size. // check alpha channel value to confirm image is covering edge pixel or not. // if alpha value is 0 image is not covering, hence subsampled. return ctx.getImageData(0, 0, 1, 1).data[3] === 0 } else { return false } }, /** * Update the orientation according to the specified rotation angle */ rotate: function (orientation, angle) { var o = { // nothing 1: {90: 6, 180: 3, 270: 8}, // horizontal flip 2: {90: 7, 180: 4, 270: 5}, // 180 rotate left 3: {90: 8, 180: 1, 270: 6}, // vertical flip 4: {90: 5, 180: 2, 270: 7}, // vertical flip + 90 rotate right 5: {90: 2, 180: 7, 270: 4}, // 90 rotate right 6: {90: 3, 180: 8, 270: 1}, // horizontal flip + 90 rotate right 7: {90: 4, 180: 5, 270: 2}, // 90 rotate left 8: {90: 1, 180: 6, 270: 3} } return o[orientation][angle] ? o[orientation][angle] : orientation }, /** * Transform canvas coordination according to specified frame size and orientation * Orientation value is from EXIF tag */ transformCoordinate: function (canvas, width, height, orientation) { switch (orientation) { case 5: case 6: case 7: case 8: canvas.width = height canvas.height = width break default: canvas.width = width canvas.height = height } var ctx = canvas.getContext('2d') switch (orientation) { case 1: // nothing break case 2: // horizontal flip ctx.translate(width, 0) ctx.scale(-1, 1) break case 3: // 180 rotate left ctx.translate(width, height) ctx.rotate(Math.PI) break case 4: // vertical flip ctx.translate(0, height) ctx.scale(1, -1) break case 5: // vertical flip + 90 rotate right ctx.rotate(0.5 * Math.PI) ctx.scale(1, -1) break case 6: // 90 rotate right ctx.rotate(0.5 * Math.PI) ctx.translate(0, -height) break case 7: // horizontal flip + 90 rotate right ctx.rotate(0.5 * Math.PI) ctx.translate(width, -height) ctx.scale(-1, 1) break case 8: // 90 rotate left ctx.rotate(-0.5 * Math.PI) ctx.translate(-width, 0) break default: break } }, /** * Detecting vertical squash in loaded image. * Fixes a bug which squash image vertically while drawing into canvas for some images. */ detectVerticalSquash: function (img, iw, ih) { var canvas = document.createElement('canvas') canvas.width = 1 canvas.height = ih var ctx = canvas.getContext('2d') ctx.drawImage(img, 0, 0) var data = ctx.getImageData(0, 0, 1, ih).data // search image edge pixel position in case it is squashed vertically. var sy = 0 var ey = ih var py = ih while (py > sy) { var alpha = data[(py - 1) * 4 + 3] if (alpha === 0) { ey = py } else { sy = py } py = (ey + sy) >> 1 } var ratio = py / ih return ratio === 0 ? 1 : ratio }, callback: function (d) { return d }, extend: function () { var target = arguments[0] || {} var a = 1 var al = arguments.length var deep = false if (target.constructor === Boolean) { deep = target target = arguments[1] || {} } if (al === 1) { target = this a = 0 } var prop for (; a < al; a++) { if ((prop = arguments[a]) !== null) { for (var i in prop) { if (target === prop[i]) { continue } if (deep && typeof prop[i] === 'object' && target[i]) { methods.extend(target[i], prop[i]) } else if (prop[i] !== undefined) { target[i] = prop[i] } } } } return target } } var defaults = { width: 300, height: 0, crop: false, quality: 0.8, rotate: 0, 'callback': methods.callback } function Plugin (file, options) { this.file = file // EXTEND this.options = methods.extend({}, defaults, options) this._defaults = defaults this._name = pluginName this.init() } Plugin.prototype = { init: function () { // this.options.init(this); var $this = this var file = this.file var reg = /(.*)(?:\.([^.]+$))/ var fileAccept = file.name.match(reg)[2] var dataAccept = '' switch (fileAccept) { case 'jpg': dataAccept = 'data:image/jpeg;' break case 'gif': dataAccept = 'data:image/gif;' break case 'png': dataAccept = 'data:image/png;' break default: break } var reader = new FileReader() reader.onloadend = function (e) { var dataURL = e.target.result var originalBase64Length = dataURL.substr(dataURL.indexOf('base64') + 7).length if (!file.type.length) { dataURL = dataURL.replace('data:', dataAccept) } var byteString = atob(dataURL.split(',')[1]) var binary = new BinaryFile(byteString, 0, byteString.length) var exif = EXIF.readFromBinaryFile(binary) var img = new Image() img.onload = function (e) { var orientation = exif['Orientation'] || 1 orientation = methods.rotate(orientation, $this.options.rotate) // CW or CCW ? replace width and height var size = (orientation >= 5 && orientation <= 8) ? methods.newsize(img.height, img.width, $this.options.width, $this.options.height, $this.options.crop) : methods.newsize(img.width, img.height, $this.options.width, $this.options.height, $this.options.crop) var iw = img.width var ih = img.height var width = size.width var height = size.height var canvas = document.createElement('canvas') var ctx = canvas.getContext('2d') ctx.save() methods.transformCoordinate(canvas, width, height, orientation) // over image size if (methods.detectSubsampling(img)) { iw /= 2 ih /= 2 } var d = 1024 // size of tiling canvas var tmpCanvas = document.createElement('canvas') tmpCanvas.width = tmpCanvas.height = d var tmpCtx = tmpCanvas.getContext('2d') var vertSquashRatio = methods.detectVerticalSquash(img, iw, ih) var sy = 0 while (sy < ih) { var sh = sy + d > ih ? ih - sy : d var sx = 0 while (sx < iw) { var sw = sx + d > iw ? iw - sx : d tmpCtx.clearRect(0, 0, d, d) tmpCtx.drawImage(img, -sx, -sy) var dx = Math.floor(sx * width / iw) var dw = Math.ceil(sw * width / iw) var dy = Math.floor(sy * height / ih / vertSquashRatio) var dh = Math.ceil(sh * height / ih / vertSquashRatio) ctx.drawImage(tmpCanvas, 0, 0, sw, sh, dx, dy, dw, dh) sx += d } sy += d } ctx.restore() tmpCanvas = tmpCtx = null // if rotated width and height data replacing issue var newcanvas = document.createElement('canvas') newcanvas.width = size.cropped === 'h' ? height : width newcanvas.height = size.cropped === 'w' ? width : height var x = size.cropped === 'h' ? (height - width) * 0.5 : 0 var y = size.cropped === 'w' ? (width - height) * 0.5 : 0 var newctx = newcanvas.getContext('2d') newctx.drawImage(canvas, x, y, width, height) var data if (file.type === 'image/png') { data = newcanvas.toDataURL(file.type) } else { data = newcanvas.toDataURL('image/jpeg', ($this.options.quality)) } var b64Str = data.substr(data.indexOf('base64') + 7) // CALLBACK $this.options.callback(data, newcanvas.width, newcanvas.height, { originalWidth: img.width, originalHeight: img.height, originalBase64Length: originalBase64Length, compressedBase64Length: b64Str.length, orientation: exif['Orientation'] || 1, fileType: fileAccept, compressedFileType: file.type === 'image/png' ? 'png' : 'jpeg' }) } img.src = dataURL } reader.onerror = $this.options.onerror || function (e) { console.log(e) } reader.readAsDataURL(file) // reader.readAsBinaryString(file); } } $[pluginName] = function (file, options) { if (typeof file === 'string') { return methods[file](options) } else { return new Plugin(file, options) } } })(window)