import Exif from 'exif-js' import logUtil from '@/utils/logUtil' /** * 图片压缩处理工具 * * @hxq/utils打包为ES5后在安卓4.4.4版本中会报错。!!!! * 先拷贝到此。2019.12.28 * * `ImageUtil.handle(file, 0.1)` */ export class ImageUtil { /** * 处理图片 * @param file 文件 * @param quality 质量 0~1 */ public static handle (file: File, quality = 0.8): Promise<Blob> { const fileReader = FileReader return new Promise(async (resolve, reject) => { // 看支持不支持FileReader if (!file || !fileReader) { reject(new Error('不支持压缩')) } if (/^image/.test(file.type)) { try { const orientation = await ImageUtil.getOrientation(file) // 创建一个reader const reader = new FileReader() // 将图片转成 base64 格式 reader.readAsDataURL(file) // 读取成功后的回调 reader.onloadend = () => { const result: any = reader.result const img = new Image() img.src = result // 判断图片是否大于100K,是就压缩,反之直接上传 if (result.length <= (100 * 1024)) { resolve(file) } else { img.onload = () => { const baseData = ImageUtil.compress(img, orientation, quality) resolve(ImageUtil.b64toBlob(baseData)) } } } } catch (error) { reject(error) logUtil.report({ model: 'code', method: 'imageUtil-handler', code: '图片压缩', msg: error.stack // 上报栈信息 2020.04.08 }) } } else { reject(new Error('请上传图片文件: ' + file.type)) } }) } /** * base64转Blob * @param b64Data base64字符串 * @param sliceSize 分片大小 * @returns Blob 阿里上传图片需要Blob型 */ public static b64toBlob (baseData: string, sliceSize = 512) { const contentType = ImageUtil.base64ContentType(baseData) const realData = ImageUtil.base64RealData(baseData) const byteCharacters = atob(realData) const byteArrays = [] for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) { const slice = byteCharacters.slice(offset, offset + sliceSize) const byteNumbers = new Array(slice.length) for (let i = 0; i < slice.length; i++) { byteNumbers[i] = slice.charCodeAt(i) } const byteArray = new Uint8Array(byteNumbers) byteArrays.push(byteArray) } return new Blob(byteArrays, { type: contentType }) } /** * dataUrl图片类型 * @param b64Data base64字符串 */ public static base64ContentType (b64Data: string) { const block = b64Data.split(';') const contentType = block[0].split(':')[1] return contentType } /** * dataUrl中的base64数据 * @param b64Data base64字符串 */ public static base64RealData (b64Data: string) { const block = b64Data.split(';') const realData = block[1].split(',')[1] return realData } /** * 去获取拍照时的信息,解决拍出来的照片旋转问题 * @param file 文件对象 */ public static getOrientation (file: File): Promise<number> { return new Promise((resolve, reject) => { Exif.getData(file as any, () => { const orientation = Exif.getTag(file, 'Orientation') resolve(orientation) }) }) } /** * 旋转图片 * @param img 图片 * @param degreeNum 旋转角度,如:90、-90、180 * @param canvas 画布 */ public static rotateImg (img: HTMLImageElement, degreeNum: number, canvas: HTMLCanvasElement) { const width = canvas.width const height = canvas.height // 旋转角度以弧度值为参数 const degree = degreeNum * Math.PI / 180 const ctx = canvas.getContext('2d') as CanvasRenderingContext2D switch (degreeNum) { case 0: break case 90: canvas.width = height canvas.height = width ctx.rotate(degree) ctx.drawImage(img, 0, -height, width, height) break case 180: case -180: ctx.rotate(degree) ctx.drawImage(img, -width, -height, width, height) break case 270: case -90: canvas.width = height canvas.height = width ctx.rotate(degree) ctx.drawImage(img, -width, 0, width, height) break } } /** * 压缩图片 * @param img 图片 * @param orientation orientation * @param quality 质量 */ public static compress (img: HTMLImageElement, orientation: number, quality = 0.8) { const canvas: HTMLCanvasElement = document.createElement('canvas') const ctx = canvas.getContext('2d') as CanvasRenderingContext2D const initSize = img.src.length let width = img.width let height = img.height // 最长边控制到1024以下 const max = 1024 if (width > height) { if (width > max) { height = Math.floor(max / width * height) width = max } } else { if (height > max) { width = Math.floor(max / height * width) height = max } } canvas.width = width canvas.height = height // 铺底色 ctx.fillStyle = '#fff' ctx.fillRect(0, 0, canvas.width, canvas.height) // 修复ios上传图片的时候 被旋转的问题 if (orientation && orientation !== 1) { switch (orientation) { case 6: // 需要顺时针(向左)90度旋转 ImageUtil.rotateImg(img, 90, canvas) break case 8: // 需要逆时针(向右)90度旋转 ImageUtil.rotateImg(img, -90, canvas) break case 3: // 需要180度旋转 ImageUtil.rotateImg(img, 180, canvas) break } } else { ctx.drawImage(img, 0, 0, width, height) } // 进行最小压缩 const ndata = canvas.toDataURL('image/jpeg', quality) console.log('压缩前:' + initSize) console.log('压缩后:' + ndata.length) console.log('压缩率:' + ~~(100 * (initSize - ndata.length) / initSize) + '%') canvas.width = canvas.height = 0 return ndata } }