canvas 壓縮圖片並保存原圖片exif信息

<template> <div> <input type="file" id="avatar" name="avatar" accept="image/png, image/jpeg" @change="loadFile" /> </div> </template> <script> //@ canvas 壓縮圖片並保存原圖片exif信息 核心思路 //@ 保存原圖 Exif 信息,待圖片壓縮完成後,將原圖 Exif 信息拼接到壓縮圖上。 //@ 校驗圖片base64圖片信息 地址: http://code.ciaoca.com/javascript/exif-js/demo/base64 //@ 參考: http://www.dirk.wang/2019/08/16/canvas%E5%8E%8B%E7%BC%A9jpg%E4%B8%A2%E5%A4%B1exif/ import EXIF from "exif-js"; var BasePage = zj.widgets.BasePage; export default { mixins: [BasePage], components: {}, data() { return { exifInfo: "", }; }, methods: { loadFile(event) { let file = event.target.files[0]; //讀取圖片的元信息 var orientation; EXIF.getData(file, function () { orientation = EXIF.getTag(this, "Orientation"); // 圖片旋轉的方向 }); let reader = new FileReader(); let that = this; reader.onload = function () { let result = this.result; that.imgSrc = result; // 保存原有圖片 exif信息 let orignBuffer = that.base64ToArrayBuffer(result); let segments = that.getSegments(orignBuffer); //分割片段 that.exifInfo = that.getEXIF(segments); //使用exif that.getImgData(this.result, orientation, (data) => { //這裏可以使用校正後的圖片data了 var img = new Image(); img.src = data; //圖片加載完畢之後進行壓縮,然後上傳 if (img.complete) { callback(); } else { img.onload = callback; } function callback() { var data = that.compress(img); // 將原始圖片exif信息拼接到壓縮後的圖片base64之後 let orignBuffer = that.base64ToArrayBuffer(data); let newImg = that.insertEXIF(orignBuffer, that.exifInfo); let base64Img = that.transformArrayBufferToBase64(newImg); console.log("base64Img", base64Img); that.upload(base64Img, file.type, file.name); } }); }; reader.readAsDataURL(file); return false; }, //上傳圖片 upload(basestr, type, name) { let text = window.atob(basestr.split(",")[1]); let buffer = new ArrayBuffer(text.length); let ubuffer = new Uint8Array(buffer); for (let i = 0; i < text.length; i++) { ubuffer[i] = text.charCodeAt(i); } let Builder = window.WebKitBlobBuilder || window.MozBlobBuilder; let blob; if (Builder) { let builder = new Builder(); builder.append(buffer); blob = builder.getBlob(type); } else { blob = new window.Blob([buffer], { type: type }); } let formdata = new FormData(); // formdata.append(model, blob, name); }, //壓縮圖片 compress(img) { //用於壓縮圖片的canvas let canvas = document.createElement("canvas"); let ctx = canvas.getContext("2d"); // 瓦片canvas var tCanvas = document.createElement("canvas"); var tctx = tCanvas.getContext("2d"); let initSize = img.src.length; let width = img.width; let height = img.height; //如果圖片大於四百萬像素,計算壓縮比並將大小壓至400萬以下 var ratio; if ((ratio = (width * height) / 4000000) > 1) { ratio = Math.sqrt(ratio); width /= ratio; height /= ratio; } else { ratio = 1; } canvas.width = width * 2; canvas.height = height * 2; //鋪底色 ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, canvas.width, canvas.height); //如果圖片像素大於100萬則使用瓦片繪製 var count; if ((count = (width * height) / 4000000) > 1) { count = ~~(Math.sqrt(count) + 1); //計算要分成多少塊瓦片 //計算每塊瓦片的寬和高 var nw = ~~(width / count); var nh = ~~(height / count); tCanvas.width = nw; tCanvas.height = nh; for (var i = 0; i < count; i++) { for (var j = 0; j < count; j++) { tctx.drawImage( img, i * nw * ratio, j * nh * ratio, nw * ratio * 2, nh * ratio * 2, 0, 0, nw, nh ); ctx.drawImage(tCanvas, i * nw, j * nh, nw * 2, nh * 2); } } } else { ctx.drawImage(img, 0, 0, width * 2, height * 2); } //進行最小壓縮 let ndata = canvas.toDataURL("image/jpeg", 1); console.log( "壓縮前:" + initSize + ",壓縮後:" + ndata.length + ",壓縮率:" + ~~((100 * (initSize - ndata.length)) / initSize) + "%" ); return ndata; }, getImgData(img, dir, next) { // @param {string} img 圖片的base64 // @param {int} dir exif獲取的方向信息 // @param {function} next 回調方法,返回校正方向後的base64 var image = new Image(); image.onload = function () { var degree = 0, drawWidth, drawHeight, width, height; drawWidth = this.naturalWidth; drawHeight = this.naturalHeight; //以下改變一下圖片大小 var maxSide = Math.max(drawWidth, drawHeight); if (maxSide > 1024) { var minSide = Math.min(drawWidth, drawHeight); minSide = (minSide / maxSide) * 1024; maxSide = 1024; if (drawWidth > drawHeight) { drawWidth = maxSide; drawHeight = minSide; } else { drawWidth = minSide; drawHeight = maxSide; } } var canvas = document.createElement("canvas"); canvas.width = width = drawWidth; canvas.height = height = drawHeight; var context = canvas.getContext("2d"); //判斷圖片方向,重置canvas大小,確定旋轉角度,iphone默認的是home鍵在右方的橫屏拍攝方式 switch (dir) { //iphone橫屏拍攝,此時home鍵在左側 case 3: degree = 180; drawWidth = -width; drawHeight = -height; break; //iphone豎屏拍攝,此時home鍵在下方(正常拿手機的方向) case 6: canvas.width = height; canvas.height = width; degree = 90; drawWidth = width; drawHeight = -height; break; //iphone豎屏拍攝,此時home鍵在上方 case 8: canvas.width = height; canvas.height = width; degree = 270; drawWidth = -width; drawHeight = height; break; } //使用canvas旋轉校正 context.rotate((degree * Math.PI) / 180); context.drawImage(this, 0, 0, drawWidth, drawHeight); //返回校正圖片 next(canvas.toDataURL("image/jpeg", 0.4)); }; image.src = img; }, //@ canvas 壓縮圖片保存原圖片exif核心代碼 // 工具函數 將 base64 轉 ArrayBuffer base64ToArrayBuffer(base64, contentType) { contentType = contentType || base64.match(/^data\:([^\;]+)\;base64,/im)[1] || ""; // e.g. 'data:image/jpeg;base64,...' => 'image/jpeg' base64 = base64.replace(/^data\:([^\;]+)\;base64,/gim, ""); // btoa是binary to ascii,將binary的數據用ascii碼錶示,即Base64的編碼過程 // atob則是ascii to binary,用於將ascii碼解析成binary數據 var binary = atob(base64); var len = binary.length; var buffer = new ArrayBuffer(len); var view = new Uint8Array(buffer); for (var i = 0; i < len; i++) { view[i] = binary.charCodeAt(i); } return buffer; }, transformArrayBufferToBase64(buffer) { var binary = ""; var bytes = new Uint8Array(buffer); for (var len = bytes.byteLength, i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return `data:image/jpeg;base64,${window.btoa(binary)}`; }, // 獲取 0xFFE0~0xFFEF 開頭的應用標記片段 getSegments(arrayBuffer) { var head = 0, segments = []; var length, endPoint, seg; var arr = [].slice.call(new Uint8Array(arrayBuffer), 0); while (true) { // SOS(Start of Scan, 由 0xff 0xda 開頭) // 遍歷到 SOS 表示已經遍歷完所有標記,再往下就是圖像數據流了,直接 break if (arr[head] === 0xff && arr[head + 1] === 0xda) { break; } // SOI(Start of Image)是 JPG 文件的開頭內容,由 0xff 0xd8 開頭 if (arr[head] === 0xff && arr[head + 1] === 0xd8) { head += 2; } // 找出每個標記片段 else { // 每個標記開頭後跟着的兩個字節記錄了該標記所記錄內容的長度 length = arr[head + 2] * 256 + arr[head + 3]; // 內容長度 endPoint = head + length + 2; // 內容結束位置 // 從0xff開頭,到標記數據內容結束全部截出來 seg = arr.slice(head, endPoint); head = endPoint; // push整個標記信息 segments.push(seg); } if (head > arr.length) { break; } } return segments; }, // 從標記片段篩選 & 取出 exif 信息 getEXIF(segments) { if (!segments.length) { return []; } var seg = []; for (var x = 0; x < segments.length; x++) { var s = segments[x]; // 0xff 0xe1開頭的纔是 exif數據(即app1) if (s[0] === 0xff && s[1] === 0xe1) { // app1 exif 0xff 0xe1 seg = seg.concat(s); } } return seg; }, // 拼接 Exif 到壓縮後的 base64 中: // 插入 Exif 信息 insertEXIF(resizedImg, exifArr) { var arr = [].slice.call(new Uint8Array(resizedImg), 0); //不是標準的JPEG文件 if (arr[2] !== 0xff || arr[3] !== 0xe0) { return resizedImg; } var app0_length = arr[4] * 256 + arr[5]; //兩個字節 // 拼接文件 SOI + EXIF + 去除APP0的圖像信息 var newImg = [0xff, 0xd8].concat(exifArr, arr.slice(4 + app0_length)); return new Uint8Array(newImg); }, }, }; </script>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章