Demo

demo link

PC_PHONE_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 {} \;

Time and Tide wait for no man.