<!DOCTYPE HTML>
<html>

<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta charset="utf-8" />

    <title>smartcrop.js 内容感知图像裁剪</title>
</head>

<body>
    <h2><a href="https://github.com/jwagner/smartcrop.js/">smartcrop.js</a> 内容感知图像裁剪</h2>
    <div>
        <form>
            <div class=drop>
                <h2>拖拽图片进行分析</h2>
                <input type=file name=file accept="image/*" />
            </div>
            <label>宽度<div><input name=width type="range" min=50 max=500 step=1 value=250 /><span class=value>250</span>px</div></label>
            <label>高度<div><input name=height type="range" min=50 max=500 step=1 value=250 /><span class=value>250</span>px</div></label>
            <label>最小比例<div><input name=minScale type="range" min=0.5 max=1.0 step=0.1 value=1 /><span class=value>1.0</span></div></label>
            <p><label>三分法<input name=ruleOfThirds type="checkbox" value=1 checked /></label></p>
            <p><strong>人脸检测</strong></p>
            <div><label><input name=faceDetection type=radio value=off checked />关</label></div>
            <div>
                <label><input name=faceDetection type=radio value=jquery />使用 <a href="http://facedetection.jaysalvat.com/">jquery.facedetection</a></label>
            </div>
            <div>
                <label><input name=faceDetection type=radio value=tracking />使用 <a href="https://trackingjs.com/">tracking.js</a></label>
            </div>
            <div>
                <label class=opencv-loading><input name=faceDetection type=radio value=opencv disabled />使用 <a href="https://opencv.org/">opencv.js</a></label>
            </div>
        </form>
    </div>

    <div class=output>
        <canvas id="c"></canvas>
    </div>

    <div class=sidebar>
        <div id=debug></div>
        <h4>All Crops</h4>
        <div class=crops>
        </div>
    </div>

    <script src="https://npm.elemecdn.com/jquery@3.6.0/dist/jquery.min.js"></script>
    <script src=https://cdn.jsdelivr.net/gh/jwagner/smartcrop.js@2.0.3/examples/underscore.js></script>
    <script src="https://cdn.jsdelivr.net/gh/jwagner/smartcrop.js@2.0.3/smartcrop.js"></script>
    <script src=https://cdn.jsdelivr.net/gh/jwagner/smartcrop.js@2.0.3/examples/smartcrop-debug.js></script>
    <script src=https://cdn.jsdelivr.net/gh/jwagner/smartcrop.js@2.0.3/examples/tracking-min.js></script>
    <script src=https://cdn.jsdelivr.net/gh/jwagner/smartcrop.js@2.0.3/examples/tracking-face-min.js></script>
    <script src=https://cdn.jsdelivr.net/gh/jwagner/smartcrop.js@2.0.3/examples/jquery.facedetection.min.js></script>
    <script async src="https://npm.elemecdn.com/opencv.js@1.2.1/opencv.js" defer onload="openCvReady()">
    </script>
</body>

</html>
(function () {
    var canvas = $('canvas')[0];
    var form = document.forms[0];
    var ctx = canvas.getContext('2d');
    var img;

    $('html')
        .on('dragover', function (e) {
            e.preventDefault();
            return false;
        })
        .on('drop', function (e) {
            var files = e.originalEvent.dataTransfer.files;
            handleFiles(files);
            return false;
        });

    $('input[type=file]').change(function () {
        handleFiles(this.files);
    });

    function handleFiles(files) {
        if (files.length > 0) {
            var file = files[0];
            if (
                typeof FileReader !== 'undefined' &&
                file.type.indexOf('image') != -1
            ) {
                var reader = new FileReader();
                // Note: addEventListener doesn't work in Google Chrome for this event
                reader.onload = function (evt) {
                    load(evt.target.result);
                };
                reader.readAsDataURL(file);
            }
        }
    }

    load('https://s1.netnr.com/static/wallpaper/01.jpg');

    $('input[type=range]').on(
        'input',
        _.debounce(function () {
            $(this)
                .next('.value')
                .text($(this).val());
            run();
        }, 500)
    );

    $('input[type=radio], input[type=checkbox]').on(
        'change',
        _.debounce(function () {
            run();
        })
    );

    function load(src) {
        fetch(src, {
            method: 'get',
            responseType: 'blob'
        }).then(res => {
            return res.blob();
        }).then(blob => {
            img = new Image();
            img.onload = function () {
                run();
            };
            img.src = window.URL.createObjectURL(blob);
        })
    }

    function run() {
        if (!img) return;
        var options = {
            width: form.width.value * 1,
            height: form.height.value * 1,
            minScale: form.minScale.value * 1,
            ruleOfThirds: form.ruleOfThirds.checked,
            debug: true
        };

        var faceDetection = $('input[name=faceDetection]:checked', form).val();

        if (faceDetection === 'tracking') {
            faceDetectionTracking(options, function () {
                analyze(options);
            });
        } else if (faceDetection === 'jquery') {
            faceDetectionJquery(options, function () {
                analyze(options);
            });
        } else if (faceDetection === 'opencv') {
            faceDetectionOpenCV(options, function () {
                analyze(options);
            });
        } else {
            analyze(options);
        }
    }

    function prescaleImage(image, maxDimension, callback) {
        // tracking.js is very slow on big images so make sure the image is reasonably small
        var width = image.naturalWidth || image.width;
        var height = image.naturalHeight || image.height;
        if (width < maxDimension && height < maxDimension)
            return callback(image, 1);
        var scale = Math.min(maxDimension / width, maxDimension / height);
        var canvas = document.createElement('canvas');
        canvas.width = ~~(width * scale);
        canvas.height = ~~(height * scale);
        canvas.getContext('2d').drawImage(image, 0, 0, canvas.width, canvas.height);
        var result = document.createElement('img');
        result.onload = function () {
            callback(result, scale);
        };
        result.src = canvas.toDataURL();
    }

    function faceDetectionOpenCV(options, callback) {
        prescaleImage(img, 768, function (img, scale) {
            var src = cv.imread(img);
            var gray = new cv.Mat();
            cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0);
            var faces = new cv.RectVector();
            var faceCascade = new cv.CascadeClassifier();
            // load pre-trained classifiers
            faceCascade.load('haarcascade_frontalface_default.xml');
            console.log(faceCascade);
            // detect faces
            var msize = new cv.Size(0, 0);
            // let c = document.createElement('canvas');
            // cv.imshow(c, gray);
            // document.body.appendChild(c)
            faceCascade.detectMultiScale(gray, faces, 1.1, 3, 0, msize, msize);
            options.boost = [];
            for (var i = 0; i < faces.size(); ++i) {
                var face = faces.get(i);
                options.boost.push({
                    x: face.x / scale,
                    y: face.y / scale,
                    width: face.width / scale,
                    height: face.height / scale,
                    weight: 1.0
                });
            }
            src.delete();
            gray.delete();
            faceCascade.delete();
            faces.delete();
            callback();
        });
    }

    function faceDetectionTracking(options, callback) {
        prescaleImage(img, 768, function (img, scale) {
            var tracker = new tracking.ObjectTracker('face');
            tracking.track(img, tracker);
            tracker.on('track', function (event) {
                console.log(
                    'tracking.js detected ' + event.data.length + ' faces',
                    event.data
                );
                options.boost = event.data.map(function (face) {
                    return {
                        x: face.x / scale,
                        y: face.y / scale,
                        width: face.width / scale,
                        height: face.height / scale,
                        weight: 1.0
                    };
                });

                callback();
            });
        });
    }

    function faceDetectionJquery(options, callback) {
        $(img).faceDetection({
            complete: function (faces) {
                if (faces === false) {
                    return console.log('jquery.facedetection returned false');
                }
                console.log(
                    'jquery.facedetection detected ' + faces.length + ' faces',
                    faces
                );
                options.boost = Array.prototype.slice
                    .call(faces, 0)
                    .map(function (face) {
                        return {
                            x: face.x,
                            y: face.y,
                            width: face.width,
                            height: face.height,
                            weight: 1.0
                        };
                    });

                callback();
            }
        });
    }

    function analyze(options) {
        console.log(options);
        smartcrop.crop(img, options, draw);
    }

    function draw(result) {
        var selectedCrop = result.topCrop;
        $('.crops')
            .empty()
            .append(
                _.sortBy(result.crops, function (c) {
                    return -c.score.total;
                }).map(function (crop) {
                    return $('<p>')
                        .text(
                            'Score: ' +
                            ~~(crop.score.total * 10000000) +
                            ', ' +
                            crop.x +
                            'x' +
                            crop.y
                        )
                        .hover(
                            function () {
                                drawCrop(crop);
                            },
                            function () {
                                drawCrop(selectedCrop);
                            }
                        )
                        .click(function () {
                            selectedCrop = crop;
                            drawCrop(selectedCrop);
                        })
                        .data('crop', crop);
                })
            );

        drawCrop(selectedCrop);
        $('#debug')
            .empty()
            .append(debugDraw(result, true));
    }

    function drawCrop(crop) {
        canvas.width = img.width;
        canvas.height = img.height;
        ctx.drawImage(img, 0, 0);
        ctx.strokeStyle = 'red';
        ctx.lineWidth = 4;
        ctx.strokeRect(crop.x, crop.y, crop.width, crop.height);
    }

    window.openCvReady = function () {
        console.log('opencv code ready');
        loadCascade(
            'haarcascade_frontalface_default.xml',
            'https://npm.elemecdn.com/opencv.js@1.2.1/tests/haarcascade_frontalface_default.xml',
            function () {
                console.log('opencv ready');
                document.querySelector('.opencv-loading input').disabled = false;
                document.querySelector('.opencv-loading').className = '';
            }
        );
    };

    function loadCascade(path, url, callback) {
        var request = new XMLHttpRequest();
        request.open('GET', url, true);
        request.responseType = 'arraybuffer';
        request.onload = function () {
            if (request.readyState === 4) {
                if (request.status === 200) {
                    var data = new Uint8Array(request.response);
                    cv.FS_createDataFile('/', path, data, true, false, false);
                    callback();
                } else {
                    self.printError(
                        'Failed to load ' + url + ' status: ' + request.status
                    );
                }
            }
        };
        request.send();
    }
})();
html {
    width: 100%;
    min-height: 100%;
}
body {
    font-family: sans-serif;
    max-width: 1400px;
    padding: 8px;
    margin: auto;
}
.dark body {
    max-width: 1240px;
}
.dark {
    background: #111;
    color: #888;
}
.dark a {
    color: #8af;
}

h1 {
    font-weight: normal;
}

p {
    max-width: 900px;
}
input[type="file"] {
    display: block;
}
input[type="range"] {
    width: 300px;
}

img,
canvas {
    padding: 8px;
    box-sizing: border-box;
    max-width: 100%;
    margin: auto;
}

.output {
    width: 50%;
    float: left;
}

.testsuite img {
    max-width: 40%;
}
.testsuite > div {
    margin-top: 2em;
}

.testsuite > .testsuite-image-title {
    margin-top: 0;
}

.sidebar {
    width: 50%;
    float: left;
    height: 600px;
}

#slide,
#prev-slide {
    max-width: 100%;
    width: 1280px;
    height: 720px;
    overflow: hidden;
}
#slide {
    background: #ccc;
}
#prev-slide {
    position: absolute;
    z-index: 1;
}

#prev-slide img {
    transition: all 1s linear;
    -webkit-transition: all 1s linear;
    opacity: 0.9;
    max-width: none;
    padding: 0;
    margin: 0;
}

#slide img {
    transition: all 5s linear;
    -webkit-transition: all 5s linear;
    /*transition-delay: 1s;*/
    /*-webkit-transition-delay: 1s; [> Safari <]*/
    opacity: 1;
    max-width: none;
    padding: 0;
    margin: 0;
}

.opencv-loading {
    color: #666;
    position: relative;
}

@keyframes spinner {
    to {
        transform: rotate(360deg);
    }
}

.opencv-loading:after {
    content: "";
    box-sizing: border-box;
    display: inline-block;
    width: 1ex;
    height: 1ex;
    margin-left: 1em;
    border-radius: 50%;
    border: 2px solid #ccc;
    border-top-color: #333;
    animation: spinner 0.6s linear infinite;
}

.drop {
    background: #eee;
    padding: 1em;
    margin-bottom: 2em;
}