这几天,对图片进行了大量的研究。
获取图片的真实大小
const img = new Image();
img.src = 'https://www.baidu.com/img/bd_logo1.png';
img.onload = () => {
console.log(img.width, img.height);
};
但是,这个方法可能获取到得是图片的显示大小,而不是真实大小。真实大小可以用 img.naturalWidth
和 img.naturalHeight
来获取。
const img = new Image();
img.src = 'https://www.baidu.com/img/bd_logo1.png';
img.onload = () => {
console.log(img.naturalWidth, img.naturalHeight);
};
提取 Exif 数据
可以用 exif-js 库 - https://github.com/exif-js/exif-js
const EXIF = require('exif-js');
const img = new Image();
img.src = 'https://www.baidu.com/img/bd_logo1.png';
img.onload = () => {
EXIF.getData(img, function () {
const allMetaData = EXIF.getAllTags(this);
console.log(allMetaData);
});
};
设置图片的 Exif 信息
可以用 Piexifjs 库 - https://github.com/hMatoba/piexifjs
这个文章做了大量的研究和介绍 - https://auth0.com/blog/read-edit-exif-metadata-in-photos-with-javascript/
npm install piexifjs@1.0.4
import piexifjs from 'piexifjs';
// let tiff = {};
// let gps = {};
// tiff[piexif.ImageIFD.Make] = key;//生产商
// tiff[piexif.ImageIFD.Model] = key;//型号
// tiff[piexif.ImageIFD.DateTime] = key;//日期
// tiff[piexif.ImageIFD.Artist] = key;//作者
// tiff[piexif.ImageIFD.ImageWidth] = key;//图像宽度
// tiff[piexif.ImageIFD.ImageHeight] = key;//图像高度
// gps["GPSVersionID": key];//GPS 版本
// gps["GPSLatitude": key];//纬度
// gps["GPSLongitude": key];//经度
// gps["GPSAltitude": key];//海拔
// gps["GPSDestBearing": key];//目标方位
// gps["GPSDestDistance": key];//目标距离
// let exifObj = {"tiff":tiff,"gps":gps};
// let exifStr = piexif.dump(exifObj);//获取 exif 信息字符串
// let resultBase64 = piexif.insert(exifStr, base64);//将 exif 信息插入到 base64 中
document.getElementById('saveButton').onclick = function () {
// make exif data
var zerothIfd = {};
var exifIfd = {};
var gpsIfd = {};
zerothIfd[piexif.ImageIFD.Make] = 'Maker Name';
zerothIfd[piexif.ImageIFD.XResolution] = [777, 1];
zerothIfd[piexif.ImageIFD.YResolution] = [777, 1];
zerothIfd[piexif.ImageIFD.Software] = 'Piexifjs';
exifIfd[piexif.ExifIFD.DateTimeOriginal] = '2010:10:10 10:10:10';
exifIfd[piexif.ExifIFD.LensMake] = 'Lens Maker';
exifIfd[piexif.ExifIFD.Sharpness] = 777;
exifIfd[piexif.ExifIFD.LensSpecification] = [
[1, 1],
[1, 1],
[1, 1],
[1, 1],
];
gpsIfd[piexif.GPSIFD.GPSVersionID] = [7, 7, 7, 7];
gpsIfd[piexif.GPSIFD.GPSDateStamp] = '1999:99:99 99:99:99';
var lat = 59.43553989213321;
var lng = 24.73842144012451;
gpsIfd[piexif.GPSIFD.GPSLatitudeRef] = lat < 0 ? 'S' : 'N';
gpsIfd[piexif.GPSIFD.GPSLatitude] = piexif.GPSHelper.degToDmsRational(lat);
gpsIfd[piexif.GPSIFD.GPSLongitudeRef] = lng < 0 ? 'W' : 'E';
gpsIfd[piexif.GPSIFD.GPSLongitude] = piexif.GPSHelper.degToDmsRational(lng);
var exifObj = { '0th': zerothIfd, Exif: exifIfd, GPS: gpsIfd };
// get exif binary as "string" type
var exifBytes = piexif.dump(exifObj);
// get JPEG image from canvas
var jpegData = document.getElementById('canvas').toDataURL('image/jpeg', 1.0);
// insert exif binary into JPEG binary(DataURL)
var exifModified = piexif.insert(exifBytes, jpegData);
// show JPEG modified exif
var image = new Image();
image.src = exifModified;
image.width = 200;
// for Modern IE
if (saveJpeg) {
var jpegBinary = atob(exifModified.split(',')[1]);
var data = [];
for (var p = 0; p < jpegBinary.length; p++) {
data[p] = jpegBinary.charCodeAt(p);
}
var ua = new Uint8Array(data);
var blob = new Blob([ua], { type: 'image/jpeg' });
image.onclick = saveJpeg(blob);
}
var el = $('<div></div>').append(image);
$('#resized').prepend(el);
};
裁剪图片(浏览器)
可以用 cropper.js 库 -
裁剪图片(NodeJS) - sharp
npm install sharp
import sharp from 'sharp';
sharp('input.jpg')
.resize(200, 300)
.toFile('output.jpg', (err, info) => {
// output.jpg is a 200 pixels wide and 300 pixels high image
// containing a scaled and cropped version of input.jpg
});
微信小程序提取图片 base64
参考文章:
- 微信小程序提取图片 base64
- 还有转成 blob 上传
- https://blog.csdn.net/qq_36875339/article/details/81086205
- https://zhuanlan.zhihu.com/p/38593007
- https://www.cnblogs.com/china-fanny/p/11213746.html
- http://blog.mokevip.top/2021/04/01/miniprogram-picture-code/
- https://juejin.cn/post/7055597030642548772
- https://juejin.cn/post/6934956172160008223
wx.canvasToTempFilePath({
canvasId: 'canvas',
success: function (res) {
console.log(res.tempFilePath);
wx.getFileSystemManager().readFile({
filePath: res.tempFilePath,
encoding: 'base64',
success: (res) => {
console.log(res.data);
},
});
},
});
上面这个方法有个问题,就是它给出的 base64 为 png 格式,而不是 jpg 格式
但是后来我发现一些更好的方法,可以直接获取 jpg 格式的 base64
wx.canvasGetImageData({
canvasId: 'canvas',
x: 0,
y: 0,
width: 100,
height: 100,
success: function (res) {
console.log(res.data);
},
});
微信小程序 - 本地临时文件转 base64
// 本地文件转 base64
function wxfileTobase64(url, ishead = false) {
return new Promise((reslove, reject) => {
wx.getFileSystemManager().readFile({
filePath: url, //选择图片返回的相对路径
encoding: 'base64', //编码格式
success: (res) => {
//成功的回调
reslove((ishead ? 'data:image/jpeg;base64,' : '') + res.data);
},
fail: (err) => {
reject(err);
},
});
});
}
// 使用
wx.chooseImage({
count: 4,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success(res) {
wxfileTobase64(res.tempFilePaths[0]).then((base64) => {
console.log(base64);
});
},
});
微信小程序 - base64 转本地临时文件
function base64ToWxfile(base64, fileName) {
const path = wx.env.USER_DATA_PATH + '/' + fileName;
return new Promise((reslove, reject) => {
wx.getFileSystemManager().writeFile({
filePath: path,
data: base64,
encoding: 'base64',
success: (res) => {
reslove(path);
},
fail: (err) => {
reject(err);
},
});
});
}
微信小程序 - URL 转 base64
function urlTobase64(url, ishead = false) {
return new Promise((reslove, reject) => {
wx.request({
url: url,
responseType: 'arraybuffer', //最关键的参数,设置返回的数据格式为 arraybuffer
success: (res) => {
//把 arraybuffer 转成 base64
let base64 = wx.arrayBufferToBase64(res.data);
//不加上这串字符,在页面无法显示的哦
base64 = (ishead ? 'data:image/jpeg;base64,' : '') + base64;
//打印出 base64 字符串,可复制到网页校验一下是否是你选择的原图片呢
reslove(base64);
},
});
});
}
微信小程序 - canvas 转本地临时文件
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: 350,
height: 450,
canvasId: 'myCanvas',
success: function (res) {
// 获取到临时文件路径,我们也可以使用这个路径将 canvas 保存到本地
let img = res.tempFilePath;
},
fail(err) {
console.log(err);
},
});
bas64 转 blob 上传
/**
* 根据 base64 内容 取得 bolb
*
* @param dataURI
* @returns Blob
*/
getBlobBydataURI(dataURI, type) {
var binary = atob(dataURI.split(',')[1]);
var array = [];
for (var i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
return new Blob([new Uint8Array(array)], { type: type });
},
/**
* 上传
*/
base64Upload(base64, url) {
// base64 转 blob
var $Blob = this.getBlobBydataURI(base64, 'image/png');
var formData = new FormData();
formData.append("file", $Blob, "avatar.png");
// 组建 XMLHttpRequest 上传文件
var request = new XMLHttpRequest();
// 上传连接地址
request.open("POST", url);
// return new Promise((resolve, reject) => {
request.onreadystatechange = function () {
if (request.readyState == 4) {
if (request.status == 200) {
console.log("上传成功", request);
} else {
console.log("上传失败,检查上传地址是否正确", request);
}
}
}
// })
request.send(formData);
}
JS 判断 Base64 图片格式
function getBase64ImageType(base64) {
var base64Data = base64.split(',')[1];
var dataBuffer = Buffer.from(base64Data, 'base64');
var type = fileType(dataBuffer);
return type ? type.ext : null;
}
但其实上面的方法不对,真正的 png 和 jpg 还是有差异的,这篇文章也说明了原因
- https://stackoverflow.com/questions/49664561/determine-if-a-base64-string-or-a-buffer-contains-jpeg-or-png-without-metadata
- 详述图片 base64 加密的原理,告诉你什么是“/9j/“
我在这篇文章中找到一个比较好的
/**
* @author PiaoZhenJia
*/
function base64FileHeaderMapper(fileBase64) {
let fileHeader = new Map();
//get the first 3 char of base64
fileHeader.set('/9j', 'JPG');
fileHeader.set('iVB', 'PNG');
fileHeader.set('Qk0', 'BMP');
fileHeader.set('SUk', 'TIFF');
fileHeader.set('JVB', 'PDF');
fileHeader.set('UEs', 'OFD');
let res = '';
fileHeader.forEach((v, k) => {
if (k == fileBase64.substr(0, 3)) {
res = v;
}
});
//if file is not supported
if (res == '') {
res = 'unknown file';
}
//return map value
return res;
}
JS 获取图片 EXIF 旋转角度
function getOrientation(file, callback) {
var reader = new FileReader();
reader.onload = function (e) {
var view = new DataView(e.target.result);
if (view.getUint16(0, false) != 0xffd8) return callback(-2);
var length = view.byteLength,
offset = 2;
while (offset < length) {
var marker = view.getUint16(offset, false);
offset += 2;
if (marker == 0xffe1) {
if (view.getUint32((offset += 2), false) != 0x45786966) {
return callback(-1);
}
var little = view.getUint16((offset += 6), false) == 0x4949;
offset += view.getUint32(offset + 4, little);
var tags = view.getUint16(offset, little);
offset += 2;
for (var i = 0; i < tags; i++)
if (view.getUint16(offset + i * 12, little) == 0x0112)
return callback(view.getUint16(offset + i * 12 + 8, little));
} else if ((marker & 0xff00) != 0xff00) break;
else offset += view.getUint16(offset, false);
}
return callback(-1);
};
reader.readAsArrayBuffer(file);
}
JS 旋转图片
function rotateImg(img, direction, canvas) {
//最小与最大旋转方向,图片旋转 4 次后回到原方向
const min_step = 0;
const max_step = 3;
if (img == null) return;
//img 的高度和宽度不能在 img 元素隐藏后获取,否则会出错
var height = img.height;
var width = img.width;
var step = 2;
if (step == null) {
step = min_step;
}
if (direction == 'right') {
step++;
//旋转到原位置,即超过最大值
step > max_step && (step = min_step);
} else {
step--;
step < min_step && (step = max_step);
}
//旋转角度以弧度值为参数
var degree = (step * 90 * Math.PI) / 180;
var ctx = canvas.getContext('2d');
switch (step) {
case 0:
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0);
break;
case 1:
canvas.width = height;
canvas.height = width;
ctx.rotate(degree);
ctx.drawImage(img, 0, -height);
break;
case 2:
canvas.width = width;
canvas.height = height;
ctx.rotate(degree);
ctx.drawImage(img, -width, -height);
break;
case 3:
canvas.width = height;
canvas.height = width;
ctx.rotate(degree);
ctx.drawImage(img, -width, 0);
break;
}
}
base64 to blob url
function dataURLtoBlob(dataurl) {
var arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
}
另外一种
// first convert to blob
const dataToBlob = async (imageData) => {
return await (await fetch(imageData)).blob();
};
const blob = await dataToBlob(destination.value);
// then create URL object
const url = URL.createObjectURL(blob);
base64 编码和解码
// base64 编码
function base64Encode(str) {
return btoa(
encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
return String.fromCharCode('0x' + p1);
})
);
}
// base64 解码
function base64Decode(str) {
return decodeURIComponent(
atob(str)
.split('')
.map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
})
.join('')
);
}
sharp 图片处理
// 使用 nodejs 裁剪图片并输出
const sharp = require('sharp');
const image = sharp(filename);
const bbox = predictionResultObj.bboxes[i];
const xMin = bbox[0];
const xMax = bbox[1];
const yMin = bbox[2];
const yMax = bbox[3];
// 获取图片的宽高
const metadata = await image.metadata();
const imageWidth = metadata.width;
const imageHeight = metadata.height;
// 计算裁剪的坐标
const left = Math.round(xMin * imageWidth);
const top = Math.round(yMin * imageHeight);
const width = Math.round((xMax - xMin) * imageWidth);
const height = Math.round((yMax - yMin) * imageHeight);
// 裁剪图片
await image
.extract({ left, top, width, height })
.toFile(`output/${label}${ids}.jpg`);
后记
- http://www.perry.cz/files/ExifRestorer.js
- https://github.com/gchudnov/inkjet
- 这还有个获取 exif 旋转照片的例子
- https://exiftool.org/TagNames/EXIF.html