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;
}
canvas压缩、裁切图片和格式转换的方法的更多相关文章
- Vue directive自定义指令+canvas实现H5图片压缩上传-Base64格式
前言 最近优化项目-手机拍照图片太大,回显速度比较慢,使用了vue的自定义指令实现H5压缩上传base64格式的图片 canvas自定义指令 Vue.directive("canvas&qu ...
- 前端图片canvas,file,blob,DataURL等格式转换
将file转化成base64 方法一:利用URL.createObjectURL() <!DOCTYPE html> <html> <head> <title ...
- 利用canvas压缩图片
现在手机拍的照片动不动就是几M,当用户上传手机里的照片时一个消耗流量大,一个上传时间长,为了解决这个问题,就需要压缩图片: 想法:利用canvas重绘图片,保持宽高比不变,具体宽高根本具体情况而定. ...
- js移动端/H5同时选择多张图片上传并使用canvas压缩图片
最近在做一个H5的项目,里边涉及到拍照上传图片的功能以及识别图片的功能,这里对识别图片的功能不做赘述,不属本文范畴.我在做完并上线项目后,同事跟我提了一个要求是可不可以同时选择多张图片上传,我做的时候 ...
- vue实现PC端调用摄像头拍照人脸录入、移动端调用手机前置摄像头人脸录入、及图片旋转矫正、压缩上传base64格式/文件格式
进入正题 1. PC端调用摄像头拍照上传base64格式到后台,这个没什么花里胡哨的骚操作,直接看代码 (canvas + video) <template> <div> &l ...
- 【VC++技术杂谈007】使用GDI+进行图片格式转换
本文主要介绍如何使用GDI+对图片进行格式转换,可以转换的图片格式为bmp.jpg.png. 1.加载GDI+库 GDI+是GDI图形库的一个增强版本,提供了一系列Visual C++ API.为了使 ...
- CANVAS运用-对图片的压缩上传(仅针对移动浏览器)
最近在移动端设计头像上传功能时,原本是以<input type="file">直接通过formData上传,然而实际使用情况是:对于过大的图片(高像素手机所拍摄的照片等 ...
- 前台图片Canvas压缩上传小结
需求来源:之前有个提交审核表单的业务,表单中含有大量附件图片,大约有20多张吧,为了省事,采用的同步上传,一次需要上传很多照片,本来单张图片限制为200KB,这样子总图片大小约为5MB左右,想想也可以 ...
- vue上传图片 base64+canvas压缩图片
这是先将图片 base64转码 在拿canvas压缩的
随机推荐
- 【Python】2.11学习笔记 注释,print,input,数据类型,标识符
前面学了好多内存什么的知识,没什么用(我有眼不识泰山233 吐槽一句,这课简直就是讲给完全的编程小白听得 就从语言开始写吧(其实好多已经看过了,再来一遍 话说我已经忘了\(Markdown\)怎么写了 ...
- 如何使用域名访问自己的Windows服务器(Java web 项目)
如何使用域名访问自己的Windows服务器(Java web 项目) 写在前面 前段时间在阿里云弄了个学生服务器,就想着自己搭建一个网站试一试,在网上查阅相关资料时发现大部分都是基于服务器是Linux ...
- JavaScript数组排序(冒泡排序、选择排序、桶排序、快速排序)
* 以下均是以实现数组的从小到大排序为例 1.冒泡排序 先遍历数组,让相邻的两个元素进行两两比较 .如果要求小到大排:最大的应该在最后面,如果前面的比后面的大,就要换位置: 数组遍历一遍以后,也就是第 ...
- ES6编译问题SyntaxError: Unexpected token import
遇到SyntaxError: Unexpected token import 如何解决 ??? 究其原因是node es6问题这还不够,因为我们没有去配置babel,所以我们需要在.babelrc去做 ...
- 【简说Python WEB】flask-mail电子邮件
目录 flask-mail flask shell发送邮件 系统环境:Ubuntu 18.04.1 LTS Python使用的是虚拟环境:virutalenv Python的版本:Python 3.6 ...
- kubernetes 1.17.2结合ceph13.2.8 实现jenkins部署并用traefik2.1代理
注:关于ceph.kubernetes集群的部署在此不声明,相信搜到本篇博文,你一定对ceph.kubernetes的部署环节手刃有余. 注:本篇博文牵扯到的技术点有:ceph.kubernetes. ...
- [剑指offer]10.斐波那契数列+青蛙跳台阶问题
10- I. 斐波那契数列 方法一 Top-down 用递归实现 def fibonacci(n): if n <= 0: return 0 if n == 1: return 1 return ...
- django身份认证、权限认证、频率校验使用及源码分析
一. 身份认证源码分析 1.1 APIView源码的分析 APIView源码之前分析过https://www.cnblogs.com/maoruqiang/p/11135335.html,里面主要将r ...
- 初探elasticsearch
目录 安装elasticsearch elasticsearch中的层级结构与关系型数据库的对比 elasticsearch的分布式特性 集群和节点 为java用户提供的两种内置客户端 节点客户端(n ...
- dirname,basename的用法与用途
#dirname介绍 当对文件使用dirname时,返回文件的上级目录,输出是否是绝对路径取决于输入的文件名是绝对路径 如果对目录使用,则返回上级目录 basename命令与dirname相反,读取文 ...