canvas压缩、裁切图片和格式转换的方法
按照大小压缩图片,或者按照特定分辨率裁切图片,转为blob数据。自动处理ios中可能存在的照片偏差90°问题。
例如,获取300*300大小的头像,实现以下效果:
使用方式:
<!-- 引入js文件 --> <script type="text/javascript" src="./compressImage.js"></script> <!-- input标签 --> <input type="file" id="avatar" name="avatar" accept="image/png, image/jpeg">
如果想通过npn引入,请参考 git说明 。
裁取特定分辨率的图片(如300*300):
compressImage({ input: document.getElementById('avatar'), width: 300, height: 300, callback: function(blob, fileName) { // blob是处理之后的图片二进制数据 // fileName是文件名,如"avatar.png" // ... } })
将图片压缩到指定大小,如500kb以下:
compressImage({ input: document.getElementById('avatar'), size: 500, callback: function(blob, fileName) { // ... } })
指定图片名字和格式(允许jpg和png格式互转):
compressImage({ input: document.getElementById('avatar'), size: 500, name: "user_avatar_1024687956" type: 'png', callback: function(blob, fileName) { // 此处得到的fileName就是 user_avatar_1024687956.png // ... } })
callback是图片处理之后的回调函数,图片会转为blob数据数据,blob的用法参考:
callback: function(blob, fileName) { var url = URL.createObjectURL(blob); /**** 通过<img>显示 ****/ var img = document.createElement("img"); img.src = url; document.body.append(img); /**** formData上传图片 ****/ var formData = new FormData(); formData.append("file", blob, fileName); $.ajax({ url: 'api/upload/img', type: 'POST', data: formData, success: function(returndata) { console.log("上传成功") formData = null; } }) /**** 下载图片 ****/ var a = document.createElement('a'); a.setAttribute('download', fileName); a.href = url; a.click(); }
compressImage方法:
/** * compressImage.js * 参数config:{ input, callback, name, type, quality, size, width, height} * input: 必填,input[type=file]的表单元素,支持multiple多张图片 * callback: 必填,处理之后的回调函数,参数(blob,fileName) * name: 非必填,自定义文件名,不包含后缀(如.jpg),默认原文件名 * type: 非必填,图片格式,可选png/jpg,默认原图片格式 * quality: 非必填,图片质量系数,默认0.92 * 只传size: 压缩图片至size(单位kb)大小 * 传width: 根据宽度压缩图片,高度自适应 * 传height: 根据高度压缩图片,宽度自适应 * 传width和height: 压缩图片,从中心位置裁取 * 不传size/width/height: 只进行格式转换,不压缩图片 * 同时传size和width/height: 会忽略size,根据width/height处理 **/ function compressImage(config) { if (!config.input || !config.input.files || config.input.files.length == 0) { console.log("compressImage: 无图片文件") return; } if (!config.callback) { console.log("compressImage: 缺少回调函数") return; } if (config.type && config.type != "png" && config.type != "jpg") { console.log("compressImage: 图片格式指定错误,请选择png或jpg") return; } config.quality = (config.quality && config.quality > 0 && config.quality <= 1) ? config.quality : 0.92; for (var i = 0; i < config.input.files.length; i++) { HANDLE_SINGLE_IMAGE(config.input.files[i], config) } } function HANDLE_SINGLE_IMAGE(file, config) { var idx = file.name.lastIndexOf("."); var imageName = file.name.substring(0, idx); var imageType = file.name.substring(idx + 1, file.name.length).toLowerCase(); if (imageType != "png" && imageType != "jpg" && imageType != "jpeg") { console.log("compressImage: 不支持的图片格式 - " + imageType) } else { // fileType: canvas.toBlob方法的参数 config.fileType = (!!config.type ? ("image/" + config.type.replace("jpg", "jpeg")) : file.type); // type: 文件名中的格式后缀 config.type = config.type || imageType.replace("jpeg", "jpg"); // fileName: 完整的文件名,将在callback中返回 config.fileName = (config.name ? (config.name + "." + config.type) : (imageName + "." + config.type)); // ios下的jpg文件需要修正照片方向 var isIOS = (/iphone|ipad|mac/).test(window.navigator.userAgent.toLowerCase()); if (isIOS && file.type == "image/jpeg") { var reader = new FileReader(); reader.readAsArrayBuffer(file); reader.onload = function() { var orientation = GET_ORIENTATION(this.result); alert(orientation) IMAGE_READER(file, config, orientation) } } else { IMAGE_READER(file, config) } } } function IMAGE_READER(file, config, orientation) { var reader = new FileReader(); reader.readAsDataURL(file); reader.onload = function() { var img = document.createElement("img"); img.src = this.result; img.onload = function() { if(orientation==6 || orientation==8){ var origin_width = parseInt(this.width); this.width = parseInt(this.height); this.height = origin_width; }else{ this.width = parseInt(this.width); this.height = parseInt(this.height); } // 标记是否只按照size要求去压缩 var bySize = false; // 缩放后图片的尺寸,canvas将从中裁切 var imgWidth = 0; var imgHeight = 0; // 目标尺寸,即最后生成的图片尺寸 var targetWidth = 0; var targetHeight = 0; // config有width/height时 if (config.width && config.height) { targetWidth = config.width; targetHeight = config.height; var ratio_x = this.width / targetWidth; var ratio_y = this.height / targetHeight; if (ratio_x > ratio_y) { imgWidth = this.width / ratio_y; imgHeight = targetHeight; } else { imgWidth = targetWidth; imgHeight = this.height / ratio_x; } } if (config.width && !config.height) { imgWidth = targetWidth = config.width; imgHeight = targetHeight = targetWidth / (this.width / this.height); } if (!config.width && config.height) { imgHeight = targetHeight = config.height; imgWidth = targetWidth = (this.width / this.height) * targetHeight; } if (targetWidth == 0 && targetHeight == 0) { // config有size时,根据大小进行压缩 if (config.size && config.size > 0 && file.size > config.size * 1024) { bySize = true; var ratio = Math.sqrt((config.size * 1024) / file.size).toFixed(2); if (ratio < 0.5) { ratio = 0.5; } imgWidth = targetWidth = parseInt(this.width * ratio); imgHeight = targetHeight = parseInt(this.height * ratio); } else { // 不压缩或者裁切,只将图片转为blob数据 imgWidth = targetWidth = this.width; imgHeight = targetHeight = this.height; } } else { targetWidth = parseInt(targetWidth); targetHeight = parseInt(targetHeight); } var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); canvas.width = targetWidth; canvas.height = targetHeight; // 矫正旋转方向 switch (orientation) { case 3: ctx.rotate(180 * Math.PI / 180); ctx.drawImage(this, (imgWidth-targetWidth)/2-imgWidth, (imgHeight-targetHeight)/2-imgHeight, imgWidth, imgHeight ); break; case 6: ctx.rotate(90 * Math.PI / 180); ctx.drawImage(this, (targetHeight-imgHeight)/2, (imgWidth-targetWidth)/2-imgWidth, imgHeight , imgWidth); break; case 8: ctx.rotate(270 * Math.PI / 180); ctx.drawImage(this, (imgHeight-targetHeight)/2-imgHeight, (targetWidth-imgWidth)/2, imgHeight , imgWidth); break; default: ctx.drawImage(this, (targetWidth-imgWidth)/2, (targetHeight-imgHeight)/2, imgWidth, imgHeight); } canvas.toBlob(function(blob) { if (bySize && blob.size >= config.size * 1024) { COMPRESS_BY_SIZE(blob, config, canvas, ctx) return; } config.callback(blob, config.fileName) }, config.fileType, config.quality); } } } //将图片按0.9倍缩小至目标size function COMPRESS_BY_SIZE(old_blob, config, canvas, ctx) { console.log("COMPRESS_BY_SIZE") config.quality = 0.98; var reader = new FileReader(); reader.readAsDataURL(old_blob); reader.onload = function() { var img = document.createElement("img"); img.src = this.result; img.onload = function() { width = parseInt(img.width * 0.9); height = parseInt(img.height * 0.9); canvas.width = width; canvas.height = height; ctx.drawImage(img, 0, 0, width, height); canvas.toBlob(function(blob) { if (blob.size >= config.size * 1024) { COMPRESS_BY_SIZE(blob, config, canvas, ctx); return; } config.callback(blob, config.fileName) }, config.fileType, config.quality); } } } /** * 获取iOS照片的旋转角度 * 1-0° 3-180° 6-90° 8-270° **/ function GET_ORIENTATION(arrayBuffer) { var dataView = new DataView(arrayBuffer); var length = dataView.byteLength; var orientation = 0; var exifIDCode; var tiffOffset; var firstIFDOffset; var littleEndian; var endianness; var app1Start; var ifdStart; var offset; var i; if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) { offset = 2; while (offset < length) { if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) { app1Start = offset; break; } offset++; } } if (app1Start) { exifIDCode = app1Start + 4; tiffOffset = app1Start + 10; if (GET_CHARCODE_STRING(dataView, exifIDCode, 4) === 'Exif') { endianness = dataView.getUint16(tiffOffset); littleEndian = endianness === 0x4949; if (littleEndian || endianness === 0x4D4D) { if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) { firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian); if (firstIFDOffset >= 0x00000008) { ifdStart = tiffOffset + firstIFDOffset; } } } } } if (ifdStart) { length = dataView.getUint16(ifdStart, littleEndian); for (i = 0; i < length; i++) { offset = ifdStart + i * 12 + 2; if (dataView.getUint16(offset, littleEndian) === 0x0112) { offset += 8; orientation = dataView.getUint16(offset, littleEndian); break; } } } return orientation; } function GET_CHARCODE_STRING(dataView, start, length) { var str = ''; var i; for (i = start, length += start; i < length; i++) { str += String.fromCharCode(dataView.getUint8(i)); } return str; }