背景
在公司接手了一个项目关于需要用到对图片进行压缩尺寸大小,花了比较多时间在压缩和把压缩的图片进行上传上,因此记录下来。
H5端的实现方法
利用canvas画布,将图片进行压缩,使用的是vue中的方法,使用的组件是vant-ui
的uploader
组件,最后通过base64数据转为blob对象,使用js自带的File类进行上传
beforeRead(file, detail) {
let _this = this;
console.log("before-upload ==> ", file)
let testmsg = file.name.substring(file.name.lastIndexOf('.') + 1)
const extension = testmsg === 'png'
const extension2 = testmsg === 'jpg'
const extension3 = testmsg === 'jpeg'
if(!extension && !extension2 && !extension3) {
this.$toast({
message: '上传文件只能是 png、jpg、jpeg格式!',
type: 'error'
});
return extension || extension2 || extension3
}
this.imgSize = (file.size / 1024).toFixed(1);
console.log("img size ==> ", this.imgSize)
if(this.imgSize < 30) {
this.$toast({
message: '上传文件大小不能小于 30KB!',
type: 'error'
});
return this.imgSize > 30
}
if(this.imgSize / 1024 > 2) {
this.scale = true
}
this.$operationLog({
btnId: "pc-upload image",
type: '0'
})
//如果超过4M就执行压缩算法
if(this.imgSize / 1024 > 4) {
return new Promise((resolve, reject) => {
const fileNames = file.name.split('.');
const type = fileNames[fileNames.length - 1];
this.handleCompressImage(file, type).then(res => {
console.log("res ==> ", res)
resolve(res)
})
})
}
else {
return new Promise((resolve, reject) => {
const fileNames = file.name.split('.');
const type = fileNames[fileNames.length - 1];
this.handleImageSize(file, type).then(res => {
resolve(res)
})
})
}
},
handleCompressImage(img, type){
let vm = this;
let resultBlob = "";
// 读取文件
let reader = new FileReader()
reader.readAsDataURL(img);
return new Promise((resolve, reject) => {
reader.onload = function(e) {
let image = new Image(); //新建一个img标签
image.src = e.target.result;
image.onload = function() {
let canvas = document.createElement('canvas');
let context = canvas.getContext('2d');
// 定义 canvas 大小,也就是压缩后下载的图片大小
while(image.width > 2000 || image.height > 2000) {
let percent = 0.5;
if(image.width / 2000 > 1) {
percent = 2000 / image.width;
image.width = image.width * percent;
image.height = image.height * percent;
}
else if(image.height / 2000 > 1) {
percent = 2000 / image.height;
image.width = image.width * percent;
image.height = image.height * percent;
}
}
let imageWidth = image.width; //压缩后图片的大小
let imageHeight = image.height;
canvas.width = imageWidth;
canvas.height = imageHeight;
// 图片不压缩,全部加载展示
context.drawImage(image, 0, 0, imageWidth, imageHeight);
console.log(imageWidth, "height ==> ", imageHeight)
// 图片按压缩尺寸载入
// let imageWidth = 500; //压缩后图片的大小
// let imageHeight = 200;
// context.drawImage(image, 0, 0, 500, 200);
// 图片去截取指定位置载入
// context.drawImage(image,100, 100, 100, 100, 0, 0, imageWidth, imageHeight);
if(type == 'jpg') {
type = 'jpeg'
}
vm.imgBase64 = canvas.toDataURL(`image/${type}`, 0.5);
resultBlob = vm.dataURItoBlob(vm.imgBase64);
if(type == 'jpeg' || type == 'jpg') {
resolve(new File([resultBlob], new Date().getTime() + '.jpg'))
}
else if(type == 'png') {
resolve(new File([resultBlob], new Date().getTime() + '.png'))
}
};
}
})
},
/* base64转Blob对象 */
dataURItoBlob(data) {
let byteString;
if(data.split(',')[0].indexOf('base64') >= 0) {
byteString = atob(data.split(',')[1])
}else {
byteString = unescape(data.split(',')[1])
}
let mimeString = data
.split(',')[0]
.split(':')[1]
.split(';')[0];
let ia = new Uint8Array(byteString.length)
for( let i = 0; i < byteString.length; i += 1) {
ia[i] = byteString.charCodeAt(i)
}
return new Blob([ia], {type: mimeString})
},
微信小程序端
小程序端由于canvas不能隐藏,因此要在wxml上提前写好canvas,而且最新版的小程序canvas支持2d渲染了,也就是说方式和H5端一样这里用的是uni-app,如果是原生小程序开发把uni类换为wx类,我的上传方法调用的是wx.chooseMedia
<canvas canvas-id="canvas2d" type="2d" id="handle-canvas" :style="{width: cWidth + 'px', height: cHeight + 'px'}" style="position: absolute;right: 0;top:-4000px;"></canvas>
<canvas canvas-id="canva2d-test" type="2d" id="test-canvas" :style="{width: previewWidth + 'px', height: previewHeight + 'px'}" style="position: absolute;right: 0;top:-4000px;"></canvas>
beforeRead(file) {
let _this = this;
let testmsg = file.fileType;
console.log(file)
this.imgSize = (file.size / 1024).toFixed(1);
if(this.imgSize < 30) {
console.log('size small')
this.$toast({
message: '上传文件大小不能小于 30KB!',
type: 'error'
});
return this.imgSize > 30
}
if(that.imgSize / 1024 > 2) {
that.scale = true
}
that.$operationLog({
btnId: "wxminiprogram-upload image",
type: '0'
})
if(that.imgSize / 1024 > 4) {
return new Promise((resolve, reject) => {
// #ifdef H5
const fileNames = file.name.split('.');
const type = fileNames[fileNames.length - 1];
// #endif
// #ifdef MP-WEIXIN
const type = 'image'
// #endif
console.log('超大了')
// 如果超大了,则压缩图片
that.handleCompressImage(file, type).then(res => {
console.log("res ==> ", res)
file.url = res
resolve(file)
})
})
}
else {
return new Promise((resolve, reject) => {
// #ifdef H5
const fileNames = file.name.split('.');
const type = fileNames[fileNames.length - 1];
// #endif
// #ifdef MP-WEIXIN
const type = 'image'
// #endif
that.handleImageSize(file, type).then(res => {
file.url = res
resolve(file)
})
})
}
// return file
},
/* 图片压缩方法-canvas压缩 */
handleCompressImage(imgFile, type){
let vm = this;
// 图片的文件对象
uni.showLoading({
title: '正在上传中',
mask: true
});
// #ifdef MP-WEIXIN
return new Promise((resolve, reject) => {
//这个方法获取图片的原生大小,这里传入的是路径
wx.getImageInfo({
src: imgFile.path || imgFile.url || imgFile.tempFilePath,
success: function(res) {
const query = uni.createSelectorQuery().in(vm);
query.select("#handle-canvas").fields({
node: true,
size: true
}).exec(ret => {
const canvas = ret[0].node;
let percent = 0.5;
const ctx1 = canvas.getContext('2d');
let image = canvas.createImage()
image.src = imgFile.path || imgFile.url || imgFile.tempFilePath;
let canvasWidth = res.width;
let canvasHeight = res.height;
if(res.width > 2000 || res.height > 2000) {
while(canvasWidth > 2000 || canvasHeight > 2000) {
if(canvasWidth / 2000 > 1) {
percent = 2000 / res.width;
canvasWidth = Math.trunc(res.width * percent);
canvasHeight = Math.trunc(res.height * percent);
}
if(canvasHeight / 2000 > 1) {
percent = 2000 / res.height;
canvasWidth = Math.trunc(res.width * percent)
canvasHeight = Math.trunc(res.height * percent)
}
}
}
canvas.width = canvasWidth;
canvas.height = canvasHeight;
image.onload = function() {
// 与H5不一样的是这个图片对象不需要指定宽高,自动适配,只需要设定src属性即可
ctx1.drawImage(image, 0, 0, canvasWidth, canvasHeight);
let dataURL = canvas.toDataURL("image/png", 1);
let dataURL300DPI = changeDpiDataUrl(dataURL, 300)
let imgPath = wx.env.USER_DATA_PATH + "/tempImage" + random_int_str(2) + '.png';
let imageData = dataURL300DPI.replace(/^data:image\/\w+;base64,/, "");
//利用微信的文件系统写入一个图片
let fs = wx.getFileSystemManager();
fs.writeFileSync(imgPath, imageData , "base64");
// 这个路径就是你要上传的目标文件路径
resolve(imgPath)
}
})
// end
}
})
})
// #endif
},
changeDpiDataUrl方法源码
这个是从npm包changedpi
那里复制出来的方法
'use strict';
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
// Regular expression to check formal correctness of base64 encoded strings
b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/;
function btoa(string) {
string = String(string);
var bitmap, a, b, c,
result = "",
i = 0,
rest = string.length % 3; // To determine the final padding
for (; i < string.length;) {
if ((a = string.charCodeAt(i++)) > 255 ||
(b = string.charCodeAt(i++)) > 255 ||
(c = string.charCodeAt(i++)) > 255)
throw new TypeError("Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.");
bitmap = (a << 16) | (b << 8) | c;
result += b64.charAt(bitmap >> 18 & 63) + b64.charAt(bitmap >> 12 & 63) +
b64.charAt(bitmap >> 6 & 63) + b64.charAt(bitmap & 63);
}
// If there's need of padding, replace the last 'A's with equal signs
return rest ? result.slice(0, rest - 3) + "===".substring(rest) : result;
}
function atob(string) {
// atob can work with strings with whitespaces, even inside the encoded part,
// but only \t, \n, \f, \r and ' ', which can be stripped.
string = String(string).replace(/[\t\n\f\r ]+/g, "");
if (!b64re.test(string))
throw new TypeError("Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.");
// Adding the padding if missing, for semplicity
string += "==".slice(2 - (string.length & 3));
var bitmap, result = "",
r1, r2, i = 0;
for (; i < string.length;) {
bitmap = b64.indexOf(string.charAt(i++)) << 18 | b64.indexOf(string.charAt(i++)) << 12 |
(r1 = b64.indexOf(string.charAt(i++))) << 6 | (r2 = b64.indexOf(string.charAt(i++)));
result += r1 === 64 ? String.fromCharCode(bitmap >> 16 & 255) :
r2 === 64 ? String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255) :
String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255, bitmap & 255);
}
return result;
}
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.changeDpiBlob = changeDpiBlob;
exports.changeDpiDataUrl = changeDpiDataUrl;
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function createPngDataTable() {
/* Table of CRCs of all 8-bit messages. */
var crcTable = new Int32Array(256);
for (var n = 0; n < 256; n++) {
var c = n;
for (var k = 0; k < 8; k++) {
c = c & 1 ? 0xedb88320 ^ c >>> 1 : c >>> 1;
}
crcTable[n] = c;
}
return crcTable;
}
function calcCrc(buf) {
var c = -1;
if (!pngDataTable) pngDataTable = createPngDataTable();
for (var n = 0; n < buf.length; n++) {
c = pngDataTable[(c ^ buf[n]) & 0xFF] ^ c >>> 8;
}
return c ^ -1;
}
var pngDataTable = void 0;
var PNG = 'image/png';
var JPEG = 'image/jpeg';
// those are 3 possible signature of the physBlock in base64.
// the pHYs signature block is preceed by the 4 bytes of lenght. The length of
// the block is always 9 bytes. So a phys block has always this signature:
// 0 0 0 9 p H Y s.
// However the data64 encoding aligns we will always find one of those 3 strings.
// this allow us to find this particular occurence of the pHYs block without
// converting from b64 back to string
var b64PhysSignature1 = 'AAlwSFlz';
var b64PhysSignature2 = 'AAAJcEhZ';
var b64PhysSignature3 = 'AAAACXBI';
var _P = 'p'.charCodeAt(0);
var _H = 'H'.charCodeAt(0);
var _Y = 'Y'.charCodeAt(0);
var _S = 's'.charCodeAt(0);
function changeDpiBlob(blob, dpi) {
// 33 bytes are ok for pngs and jpegs
// to contain the information.
var headerChunk = blob.slice(0, 33);
return new Promise(function (resolve, reject) {
var fileReader = new FileReader();
fileReader.onload = function () {
var dataArray = new Uint8Array(fileReader.result);
var tail = blob.slice(33);
var changedArray = changeDpiOnArray(dataArray, dpi, blob.type);
resolve(new Blob([changedArray, tail], { type: blob.type }));
};
fileReader.readAsArrayBuffer(headerChunk);
});
}
function changeDpiDataUrl(base64Image, dpi) {
var dataSplitted = base64Image.split(',');
var format = dataSplitted[0];
var body = dataSplitted[1];
var type = void 0;
var headerLength = void 0;
var overwritepHYs = false;
if (format.indexOf(PNG) !== -1) {
type = PNG;
var b64Index = detectPhysChunkFromDataUrl(body);
// 28 bytes in dataUrl are 21bytes, length of phys chunk with everything inside.
if (b64Index >= 0) {
headerLength = Math.ceil((b64Index + 28) / 3) * 4;
overwritepHYs = true;
} else {
headerLength = 33 / 3 * 4;
}
}
if (format.indexOf(JPEG) !== -1) {
type = JPEG;
headerLength = 18 / 3 * 4;
}
// 33 bytes are ok for pngs and jpegs
// to contain the information.
var stringHeader = body.substring(0, headerLength);
var restOfData = body.substring(headerLength);
var headerBytes = atob(stringHeader);
var dataArray = new Uint8Array(headerBytes.length);
for (var i = 0; i < dataArray.length; i++) {
dataArray[i] = headerBytes.charCodeAt(i);
}
var finalArray = changeDpiOnArray(dataArray, dpi, type, overwritepHYs);
//
var base64Header = btoa(String.fromCharCode.apply(String, _toConsumableArray(finalArray)));
//
// var base64Header = weappJwt(String.fromCharCode.apply(String, _toConsumableArray(finalArray)))
return [format, ',', base64Header, restOfData].join('');
}
function detectPhysChunkFromDataUrl(data) {
var b64index = data.indexOf(b64PhysSignature1);
if (b64index === -1) {
b64index = data.indexOf(b64PhysSignature2);
}
if (b64index === -1) {
b64index = data.indexOf(b64PhysSignature3);
}
// if b64index === -1 chunk is not found
return b64index;
}
function searchStartOfPhys(data) {
var length = data.length - 1;
// we check from the end since we cut the string in proximity of the header
// the header is within 21 bytes from the end.
for (var i = length; i >= 4; i--) {
if (data[i - 4] === 9 && data[i - 3] === _P && data[i - 2] === _H && data[i - 1] === _Y && data[i] === _S) {
return i - 3;
}
}
}
function changeDpiOnArray(dataArray, dpi, format, overwritepHYs) {
if (format === JPEG) {
dataArray[13] = 1; // 1 pixel per inch or 2 pixel per cm
dataArray[14] = dpi >> 8; // dpiX high byte
dataArray[15] = dpi & 0xff; // dpiX low byte
dataArray[16] = dpi >> 8; // dpiY high byte
dataArray[17] = dpi & 0xff; // dpiY low byte
return dataArray;
}
if (format === PNG) {
var physChunk = new Uint8Array(13);
// chunk header pHYs
// 9 bytes of data
// 4 bytes of crc
// this multiplication is because the standard is dpi per meter.
dpi *= 39.3701;
physChunk[0] = _P;
physChunk[1] = _H;
physChunk[2] = _Y;
physChunk[3] = _S;
physChunk[4] = dpi >>> 24; // dpiX highest byte
physChunk[5] = dpi >>> 16; // dpiX veryhigh byte
physChunk[6] = dpi >>> 8; // dpiX high byte
physChunk[7] = dpi & 0xff; // dpiX low byte
physChunk[8] = physChunk[4]; // dpiY highest byte
physChunk[9] = physChunk[5]; // dpiY veryhigh byte
physChunk[10] = physChunk[6]; // dpiY high byte
physChunk[11] = physChunk[7]; // dpiY low byte
physChunk[12] = 1; // dot per meter....
var crc = calcCrc(physChunk);
var crcChunk = new Uint8Array(4);
crcChunk[0] = crc >>> 24;
crcChunk[1] = crc >>> 16;
crcChunk[2] = crc >>> 8;
crcChunk[3] = crc & 0xff;
if (overwritepHYs) {
var startingIndex = searchStartOfPhys(dataArray);
dataArray.set(physChunk, startingIndex);
dataArray.set(crcChunk, startingIndex + 13);
return dataArray;
} else {
// i need to give back an array of data that is divisible by 3 so that
// dataurl encoding gives me integers, for luck this chunk is 17 + 4 = 21
// if it was we could add a text chunk contaning some info, untill desired
// length is met.
// chunk structur 4 bytes for length is 9
var chunkLength = new Uint8Array(4);
chunkLength[0] = 0;
chunkLength[1] = 0;
chunkLength[2] = 0;
chunkLength[3] = 9;
var finalHeader = new Uint8Array(54);
finalHeader.set(dataArray, 0);
finalHeader.set(chunkLength, 33);
finalHeader.set(physChunk, 37);
finalHeader.set(crcChunk, 50);
return finalHeader;
}
}
}