现在的设备发达了,图片拍下来动辄 5MB 10MB,单反相机歘欻欻一张经能达到 40MB,手机的内部储存也跟着很大,随便一个手机都 100G 。
但对于我来讲,反而不舒服。一张照片,占用 5mb 10mb 的空间真的合适吗?不舒服不是因为居安思危,杞人忧天,觉得体积大未来会把地球憋爆炸,而是觉得一张图片可以比文字占得空间大点,但 5mb 10mb 着实不配它占。它不配。
文字,真的太节约体积了,余华花很久写个《活着》,全书保存成 GBK 编码也不过占用 500kb,一张图片,用那么多体积确实不合算。大概看个样子就行了,只有极少数像素会经过中枢神经前额叶意识区域的处理。
另一方面,体积大了,在本地还行,硬盘上千个 GB,不碍事,但在服务器上,网页上,体积小的需求还是挺大,要不然谷歌也不会研制 webp 什么格式,emlog、七牛、阿里云也不会刻意考虑为图片压缩尺寸等措施。
所以,在适当情况下,如果图片能压缩到一定程度,确实是网络从业者的福音。尤其对我这种,以前特别害怕在网站上传图片,因为即使是 CDN ,也是花钱的,当然钱是小事,5 块 10 块够我用好几年,主要是如果一张图片 5M 的话,到时候七牛云倒闭,迁移资源时,工程量可大了!如果一张图片就几十 KB 的话,加起来那么多图也就几十 mB ,几百 mb,简洁可爱!(就像七八年前的 微信 一样,可惜物是人非今不如昔)
当然,图片压缩从来不是卡脖子的技术,微信、各种 APP 、PS 都能灵活的压缩图片,甚至 AI。即使懒得下载,点击,直接打开万能的互联网浏览器,搜索在线压缩图片,也能,不过还是效率不够行,不够方便。
中文互联网真的,处处都是注册、、、而且没啥技术含量,纯粹抄袭的别人的东西。为了更自由,我决定自己做一个,使用 JavaScript。而且使用的都是浏览器自带的 API ,什么 canvas API ,blob API....
功夫不负有心人,花了一傍晚的时间,我做出来了。
就是这个链接。 https://www.ccgxk.com/249.html 【导航】---【小工具】---【图片超级压缩】。
(压缩我的头像)
(压缩上面那张截图「压缩我的头像」)
由图可见,这种压缩效率还是很厉害的,虽然原图才 几十几百 kb,但如果原图是 5M 10M 也是可以压缩到 20 --- 30 kb 的。说实话 20 kb 的图,虽然模糊点,但足够把很多信息传递明白了。
其实,这个主要是有文字,模糊起来会看不清。如果是「风景图片」的话,越模糊,越有意境哈哈。
代码的话,还是花了很多功夫的。不一段一段讲了,先直接上最终的 html + javascript 。
<style>
.c {
margin-top: 20px;
margin-inline: auto;
}
i {
color:#c9c9c9;
}
.e2 {
background: aliceblue;
border: 0px;
}
.markdown {
text-align: inherit;
}
</style>
<div class="c" >
复制图片,在下面蓝框中粘贴,会自动按照下面设置的规则来压缩图片体积
<br><br>
<i>注意,直接鼠标复制处理后的图片,其体积会增长一部分(因浏览器本身特性),获取真实压缩图片应单击「下载最终结果」。</i>
<br><br>
<textarea class="e2" style="width: 100%;" rows="2" id="output"></textarea>
<br><br>
<button id="img_download" onclick="base64ToFile(out_base64, 'download.jpeg')">下载最终结果</button>(<span id="img_size"></span>):
<p id="imga">
<img id="testimg" src="" alt="" />
</p>
</div>
<fieldset style="width: 230px">
<legend>压缩规则</legend>
最大宽度 (px)<input type="number" id="in_maxwidth" onchange="re_config()" value="400" /><br><br>
质量 (1 - 10)<input type="range" name="points" min="1" max="100" id="in_quality" onchange="re_config()" value="50" /><span id="in_q_msg">5</span><br><br>
是否黑白化<input type="checkbox" id="in_balck" onchange="re_config()" checked /><br>
</fieldset>
<script>
/* 配置区 */
let drawWidth = 400; // 统一宽度值
let imgQuality = 0.5; // 质量
let is_balck = true; // 黑白
// ----------------
const c=document.createElement("canvas");
const ctx=c.getContext("2d");
let domImg;
let s_imgSize;
let r_imgSize;
let base64data;
let out_base64;
/* 程序入口 */
function drawimg(base64data){
creatDomImg(base64data);
setTimeout(function(){canvdraw()}, 1000);
}
/* 把图片弄到 domImg 中 */
function creatDomImg(base64data){
s_imgSize = parseInt(base64data.length / 1024 * 0.75) + "kb";
domImg = document.createElement("img");
domImg.src = base64data;
}
function canvdraw(){
/* 计算画布的宽高值 */
let scale = domImg.height / domImg.width;
let domImg_w = (domImg.width > drawWidth) ? drawWidth : domImg.width;
let domImg_h = (domImg.width > drawWidth) ? drawWidth * scale : domImg.height;
/* 画布生成 */
c.width = domImg_w;
c.height = domImg_h;
/* 在画布画图 */
ctx.drawImage(domImg, 0, 0, domImg_w, domImg_h);
/* 黑白化 */
if(is_balck){
const imgArrData = ctx.getImageData(0, 0, domImg_w, domImg_h);
for (let i = 0; i < imgArrData.data.length; i += 4) {
let r = imgArrData.data[i],
g = imgArrData.data[i + 1],
b = imgArrData.data[i + 2];
const avg = (r + g + b) / 3;
imgArrData.data[i] = imgArrData.data[i + 1] = imgArrData.data[i + 2] = avg;
}
ctx.putImageData(imgArrData, 0, 0);
}
/* 图片展示 */
out_base64 = c.toDataURL('image/jpeg', imgQuality);
testImg = document.getElementById("testimg");
testImg.src = out_base64;
/* 处理后的大小 */
r_imgSize = parseInt(out_base64.length / 1024 * 0.75) + "kb";
img_size.innerHTML = s_imgSize + " -> " + r_imgSize;
}
/* 粘贴事件后:获取粘贴图片,把 base64 数据扔给 drawimg() */
document.getElementById("output").addEventListener("paste", function (e) {
if ( !(e.clipboardData && e.clipboardData.items) ) return
var pasteData = e.clipboardData || window.clipboardData
pasteAnalyseResult = new Array
for(var i = 0; i < pasteData.items.length; i++) {
var item = pasteData.items[i]
if((item.kind == "file") && (item.type.match('^image/'))){
let imgData = item.getAsFile();
if (imgData.size === 0) return;
let reader = new FileReader();
reader.readAsDataURL(imgData);
reader.onload = function(){
base64data = this.result;
drawimg(base64data); // 获得图片 base64 数据,开始处理
}
break;
};
}
}, false);
/* 用户在界面自定义配置 */
function re_config(){
drawWidth = in_maxwidth.value;
imgQuality = Math.floor(in_quality.value) / 100;
in_q_msg.innerHTML = (imgQuality * 10).toString().match(/^\d+(?:\.\d{0,1})?/);
is_balck = in_balck.checked;
if(typeof base64data === 'undefined') return
img_size.innerHTML = "处理中...";
drawimg(base64data);
}
/* 下载 */
function base64ToFile(base,fileName) {
console.log(base)
if(typeof base === 'undefined') return
const arr = base.split(',');
const mime = arr[0].match(/:(.*?);/)[1];
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
if (window.navigator.msSaveBlob) {
// for ie 10 and later
try {
const blobObject = new Blob([u8arr], { type: mime });
window.navigator.msSaveBlob(blobObject, 'aaa.xls');
} catch (e) {
console.log(e);
}
} else {
const url = window.URL.createObjectURL(new Blob([u8arr], { type: mime }));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link); // 下载完成移除元素
window.URL.revokeObjectURL(url); // 释放掉blob对象
}
}
</script>
获取剪切板的程序,是固定写法,那么一大群 addEventListener ,复制粘贴就行。
最终获取剪切板里图片的 base64 ,放到 drawimg(base64data);
里。
然后就要过流水线了。先创建一个虚拟的 DOM < img > 放内存里,(创建实体 也行,但没必要)。然后图片 src 就是这个 base64,这样,就有了这个 img 元素了。
为什么创建 img ,因为目前我只知道 < canvas > 画照片的办法,就是得有 < img > 才行。然后按照 api 方式,画图就行。
不过很可惜,这两个不能同时进行,创建了 < img >,还得等一段时间,可能这是单进程吧,所以我写了个延迟函数 setTimeout(function(){canvdraw()}, 1000);
,1s 后再画。当然视觉效果就好像是,机器处理了 1 s 才放出来,其实不是,机器不到 10 毫秒就基本完成了.......
/* 在画布画图 */
// ctx.drawImage(domImg, 0, 0, domImg_w, domImg_h);
ctx.drawImage(图的< img >, 起画点左坐标, 起画点上坐标, 落笔点右坐标, 落笔点下坐标);
这些照着手册写就行。关键是下面 3 点。
第一点,canvas 转 base64 好说。现成的 API
out_base64 = c.toDataURL('image/jpeg', imgQuality);
这一句就行了,c 是那个 < canvas >,后面的第二个属性是质量,也就是导出 JPEG 的质量 0 -- 1 之间。压缩质量。比如 0.5。
至于下载独立文件,从网上复制粘贴了个 base64ToFile() 函数就好了。
第二句图片黑白化。这个可让我真的见识到 JavaScript 是多么快的了。我注释 /* 黑白化 */
下面的句子,把像素点从 < canvas > 一个个取出来,一个个加减乘除分析,就那个 for 循环。诸位可知,随便处理一张图片,这个句子在谈笑间能跑多少次吗?我还专门写了个 console.log ,我的头像,就跑了 20 多万次......
这,要是让我笔算,就我这计算力,一年都算不完。
原理也很简单,就是每个像素点都有 R G B 三个值,只要让 R G B 三个值相等,且等于它们三者的平均数就行。这就是黑白原理了。
第三句,如何计算图片体积?其实已经能拿到图片的 base64 源码了,那离计算其体积就不远了。
根据 base64 的编码原理,六位二进制 101010 可以代表一个字母,但文本格式的 base 64 则需要 10101010 八位二进制才能表示。体积会增长 \frac{4}{3}
也就是说,6 kb 的内容,转成 base 64 会变成 8 kb,那直接把 base64 的长度 乘上 0.75 就是文件体积了。
代码如下。
/* 处理后的大小 */
r_imgSize = parseInt(out_base64.length / 1024 * 0.75) + "kb";
至此,程序就完成了。
以后,写文章上传图片,就能上传很小的图片了,太爽了。
不过,以后,也可以再加个 自定义文件名 的功能。这样也便于整理。或者做成 emlog 插件.....
因为压缩完的图片,还得再进行下载才行,直接复制会失真...... 目前还没找到把独立文件放到剪切板里的办法,估计这样做也有安全问题。能下载就很不戳了。