Demo
(没想到这个 Demo 竟然写了 400 行... 导致完全没心思写这篇文章了...很潦草,以后也大概率不会填坑)
Why?
网上有很多只用前端压缩图片的教程,都是调用了 Canvas.toBlob() 或者 Canvas.toDataurl()。查了一下没有发现讲述压缩原理的文章... 从知乎一个回答获得了灵感,直接去翻 chrome 源码,翻到最后发现这两个压缩 Jpg 都是调用了 libjpeg...
查了一下目前压缩 Jpg 只有 libjpeg 和 Google Guetzli。压缩 Jpg 其实是个挺蛋疼的问题,限制全球几百亿设备跑的 Jpeg 解码器是一样的,所以限制了你最后编码的形式,也就是必须在频域去掉一些信息,使得整体信息熵下降,然后编码。频域去信息完全限制了压缩的上限,各家的做法无非是去掉频域里的哪些信息。libjpeg 先转化为人眼不敏感的色度信息,然后去掉最高频的频域信息,Guetzli 则是根据评估人眼对各个颜色的敏感度,对不同颜色不同频率不同处理,但是这个评估部分极其缓慢,需要几分钟的量级...
话说到这里,也就是说 Jpg 的解码,也就是 Jpg 这种格式完全限制了压缩率,所以各家推出了 Webp 等等格式希望取代 Jpg,然而 Safari 不支持就很要命了。最近深度学习方法也在做压缩,各种比赛说是压缩率超过一个付费压缩算法,但是资源消耗就不提了……最主要还不如 Webp 普及,难道要每台手机绑个 1080Ti 解码吗... 【也许以后移动端浮点并行计算真能达到这个高度吧...
所以目前来看最快的压缩算法肯定是 libjpeg,可能稍微好一点但是慢一万倍的是 Guezli,在 Demo 里面都实现了。因为服务器太垃圾了,设计了一个 Code 才能用 Guezli...【我自己都不会用...
Code
我真的不想实现中的坑和细节了...
<div class="scale">
<div class="outboard">
<div id="show_board" style="height: 64.15%; width: 100%; margin: auto auto; position: relative;">
<div class="after_div" id="after_div" draggable="false"></div>
<div id="before_div" draggable="false"
style="height: 100%; width: 50%; position: absolute; left: 0%; top: 0%; background-image: url('https://test-1253607195.cos.ap-shanghai.myqcloud.com/2019-7-3/193178.jpg'); background-size: cover; border: 0.1rem solid #eea2a4;">
</div>
<div class="before_tag_div">Before</div>
<div class="after_tag_div">After</div>
<input class="adjust_input" id="adjust_input"
style="position: absolute; left: -1.3%; top: 50%; width: 102%; margin: 0; -webkit-appearance: none; padding: 0; border: 0;"
type="range" />
</div>
<div class="control_board">
<div class="info_div">
<div class="single_info_div">
<div class="word_div">raw: <small id="raw_small">741kb</small></div>
<div
style="height: 0.6rem; width: 70%; background-color: #d3d3d3; margin-left: 30%; position: relative; top: 0.4rem;">
</div>
</div>
<div class="single_info_div">
<div class="word_div">now: <small id="now_small">135kb</small></div>
<div id="now_length"
style="height: 0.6rem; width: 12.75%; background-color:#eea2a4; margin-left: 30%; position: relative; top: 0.4rem;">
</div>
</div>
</div>
<div class="upload_div">
<select class="level_select" , id="level_select" onchange="level_select_change(this)">
<option value="none" style="display: none">Level</option>
<option value="0.2">0.2</option>
<option value="0.4">0.4</option>
<option value="0.6">0.6</option>
<option value="0.8">0.8</option>
<option value="0.92">0.92</option>
</select>
<form class="upload_form">
<input class="password_input" id="password_input" type="text" name="password" placeholder="Code" />
<input class="hidden_input" id="hidden_input" type="file" name="input_img"
onchange="hidden_input_change(this)" accept="image/jpeg,image/png"></input>
<label class="upload_label" for="hidden_input"></label>
</form>
<button class="download_button" id="download_button" type="button"
onclick="download_button_click(this)"></button>
</div>
</div>
</div>
</div>
.scale {
width: 100%;
height: 0vmin;
padding-bottom: 100%;
position: relative;
margin: auto auto;
}
.outboard {
width: 100%;
height: 100%;
position: absolute;
}
.after_div {
height: 100%;
width: 100%;
position: absolute;
left: 0%;
top: 0%;
background-image: url("https://test-1253607195.cos.ap-shanghai.myqcloud.com/2019-7-3/193178_small.jpg");
background-size: cover;
border: 0.1rem solid #eea2a4;
}
.adjust_input::-webkit-slider-runnable-track {
height: 0;
}
.adjust_input::-webkit-slider-thumb {
box-shadow: 0.05rem 0.05rem 0.05rem #000031, 0 0 0.05rem #00004b;
border: 0.1rem solid #eea2a4;
-webkit-appearance: none;
height: 1rem;
width: 1rem;
border-radius: 1rem;
background: #ffffff;
cursor: pointer;
margin-left: 0.15rem;
}
.adjust_input:focus {
outline: none;
}
.control_board {
height: 10%;
width: 100%;
position: relative;
margin-top: 2%;
min-height: 4rem;
}
.info_div {
height: 100%;
width: 42%;
float: left;
margin-left: 2%;
}
.single_info_div {
height: 50%;
width: 100%;
}
.word_div {
height: 100%;
width: 30%;
float: left;
}
.upload_div {
height: 100%;
width: 42%;
float: right;
margin-right: 2%;
}
.level_select{
height: 40%;
width: 30%;
float: left;
border: solid 0.1rem #eea2a4;
appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
padding-right: 1rem;
background-image: url("https://test-1253607195.cos.ap-shanghai.myqcloud.com/2019-7-3/select_bg.png");
background-size: 100% 100%;
text-align: center;
text-align-last: center;
outline:none;
}
.upload_form{
height: 40%;
width: 66%;
float: right;
}
.password_input{
height: 100%;
width: 45%;
border: solid 0.1rem #eea2a4;
float: left;
text-align: center;
text-align-last: center;
outline:none;
}
.upload_label{
height: 100%;
width: 45%;
float: right;
color: white;
text-align: center;
background-image: url("https://test-1253607195.cos.ap-shanghai.myqcloud.com/2019-7-3/upload.png");
background-size: 100% 100%;
border-radius: 0.2rem;
cursor: pointer;
}
/* .hidden_input:focus+.upload_label, */
/* handle uploading maybe? */
.hidden_input+.upload_label:hover{
border: 0.1rem solid #f07c82;
}
.download_button{
height: 40%;
width: 30%;
position: relative;
top: 10%;
left: 70%;
color: white;
text-align: center;
background-image: url("https://test-1253607195.cos.ap-shanghai.myqcloud.com/2019-7-3/download.png");
background-size: 100% 100%;
border: none;
outline: none;
cursor: pointer;
border-radius: 0.2rem;
box-shadow: none;
}
.download_button:hover{
border: 0.1rem solid #f07c82;
}
.hidden_input {
width: 0.1px;
height: 0.1px;
opacity: 0;
overflow: hidden;
position: absolute;
z-index: -1;
}
.before_tag_div{
width: 6%;
height: 1.6rem;
background-color: rgba(255,255,255,0.6);
position: absolute;
left: 5%;
top: 5%;
color: #f07c82;
text-align: center;
}
.after_tag_div{
width: 6%;
height: 1.6rem;
background-color: rgba(255,255,255,0.6);
position: absolute;
right: 5%;
top: 5%;
color: #f07c82;
text-align: center;
}
var show_board = document.getElementById("show_board");
var after_div = document.getElementById("after_div");
var before_div = document.getElementById("before_div");
var adjust_input = document.getElementById("adjust_input");
var level_select = document.getElementById("level_select");
var password_input = document.getElementById("password_input");
var hidden_input = document.getElementById("hidden_input");
var raw_small = document.getElementById("raw_small");
var now_small = document.getElementById("now_small");
var now_length = document.getElementById("now_length");
var result_file_name = "output"
adjust_input.oninput = function () {
before_div.style.width = this.value + "%";
};
function url2bgurl(url) {
return "url('" + url + "')";
}
function bgurl2url(bgurl) {
return bgurl.substr(5, bgurl.length - 7);
}
function download_button_click() {
try {
var parts = bgurl2url(after_div.style.backgroundImage).split(';base64,');
var contentType = parts[0].split(':')[1];
var raw = window.atob(parts[1]);
var rawLength = raw.length;
var uInt8Array = new Uint8Array(rawLength);
for (var i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
var blob = new Blob([uInt8Array], { type: contentType });
var aLink = document.createElement('a');
var evt = document.createEvent("HTMLEvents");
evt.initEvent("click", true, true);
aLink.download = result_file_name + ".jpg";
aLink.href = URL.createObjectURL(blob);
aLink.click();
}
catch (e) {
console.log("download error");
console.log(e);
}
}
function url2kb(url_len) {
var head = "data:image/jpeg;base64,";
var imgFileSize = Math.round((url_len - head.length) * 3 / 4 / 1024);
return imgFileSize;
}
function change_height() {
var img = new Image;
img.src = bgurl2url(after_div.style.backgroundImage);
img.onload = function () {
show_board.style.height = img.height / img.width * 100 + "%";
}
};
function change_size() {
raw_size = url2kb(before_div.style.backgroundImage.length);
now_size = url2kb(after_div.style.backgroundImage.length);
raw_small.innerText = raw_size.toString() + "kb";
now_small.innerText = now_size.toString() + "kb";
now_length.style.width = (now_size / raw_size * 70).toString() + "%";
}
function compress_local(level) {
var img = new Image();
var canvas = document.createElement('canvas');
img.src = bgurl2url(before_div.style.backgroundImage);
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);
after_div.style.backgroundImage = url2bgurl(canvas.toDataURL("image/jpeg", parseFloat(level)));
change_size();
change_height();
}
}
function compress_server(data_url, level) {
$.ajax({
url: "http://34.80.117.205/wp-content/my-pages/compressor/compressor.php",
method: "post",
data: {
level: level,
raw_img: data_url
},
crossDomain: true,
success: function (after_url) {
before_div.style.backgroundImage = url2bgurl(data_url);
after_div.style.backgroundImage = url2bgurl(after_url);
change_size();
change_height();
}, error: function () {
console.log("Server Failed");
}
})
}
function level_select_change() {
try {
compress_local(level_select.value);
}
catch (e) {
console.log(e);
};
}
function hidden_input_change() {
var level = "0.92";
for (var i = 0; i < level_select.options.length; i++) {
if (level_select.options[i].selected) {
var value = level_select.options[i].value;
if (value != "none") {
level = value;
}
}
}
var code = password_input.value;
var reader = new FileReader();
f = hidden_input.files[0];
result_file_name = f.name.split(".")[0];
reader.readAsDataURL(f);
reader.onload = function (e) {
before_url = this.result;
if (code == "****") {
compress_server(before_url, level);
}
else {
before_div.style.backgroundImage = url2bgurl(before_url);
compress_local(level);
}
};
};
$(window).on('load', function () {
if (/Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent)) {
now_small.style.visibility = "hidden";
raw_small.style.visibility = "hidden";
}
})
<?php
// server.php
header("Access-Control-Allow-Origin:*");
header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept');
function generateRandomString($length = 10) {
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, strlen($characters) - 1)];
}
return $randomString;
}
$level = $_POST["level"];
$raw_img = $_POST["raw_img"];
$level = ((float)$level)*16+84;
$level = (string)((int)$level);
$img_type = explode(";", $raw_img)[0];
$img_type = explode("/", $img_type)[1];
$input_path = "./".generateRandomString().".".$img_type;
$output_path = "./".generateRandomString().".jpg";
file_put_contents($input_path, file_get_contents($raw_img));
shell_exec("/var/www/guetzli/bin/Release/./guetzli --quality ".$level." ".$input_path." ".$output_path);
shell_exec("/var/www/html/wp-content/my-pages/compressor/./delete.sh");
$result = file_get_contents($output_path);
$result = "data:image/jpeg;base64,".base64_encode($result);
echo $result;
?>
#!/bin/sh
start=$(date +%y-%m-%d-%H%M%m)
FilePath=/var/www/html/wp-content/my-pages/compressor/
day=2
find $FilePath -mtime +$day -name *.jpg* -exec rm -rf {} \;
find $FilePath -mtime +$day -name *.png* -exec rm -rf {} \;
Comments | NOTHING