ggwfnyqqqfoa

ggwfnyqqqfoa 查看完整档案

上海编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

ggwfnyqqqfoa 提出了问题 · 8月5日

electron 中 http 和node不一样,总是获取不完整

这个是node中的代码
`

const https = require("https");

const url = "https://www.baidu.com/";
var req = https.request(url, function (response) {
    var chunks = [];
    response.on("data", function (chunk) {
        console.log("===================================");
        chunks.push(chunk);
    });
    response.on("end", function () {
        var buffer = Buffer.concat(chunks);
        console.log(buffer.toString());
    });
});

req.on("error", function (e) {
    console.log(e.message);
});
req.end();

`

这个可以获取完整的html, 但是吧这个代码贴进 electron 就中不行了, 获取的html不完整并且还乱码。
请问是怎么回事?

关注 4 回答 2

ggwfnyqqqfoa 回答了问题 · 7月30日

解决webpack-dev-server --host 0.0.0.0 问题

为什么我配置了 0.0.0.0 没有出来 ip, 给我出来 0.0.0.0
微信截图_20200730101907.png

关注 4 回答 3

ggwfnyqqqfoa 收藏了文章 · 1月14日

JS禁止打开控制台

主要为了通过禁止打开控制台,防止别人进行代码调试。


1、禁止右键查看源码和F12

//禁止F12键盘事件
document.addEventListener('keydown', function(event){
   return 123 != event.keyCode || (event.returnValue = false)
});
//禁止右键、选择、复制
document.addEventListener(‘'contextmenu'’, function(event){
   return event.returnValue = false
})

破解:还可以使用浏览器菜单中的开发者工具打开控制台

2、通过页面宽度变化监测控制台

浏览宽高变化监测主要是监测浏览器可视区域的宽高:window.innerWidth / window.innerHeight(滚动条和内容区)和浏览器宽高:window.outerWidth / window.outerHeight(inner的基础上加上工具条的宽高)之间的差值。

因为我们不知道浏览器是否开启了工具条及工具条的宽高,所以我们设置一个阈值如200,如果outer – inner 大于200,我们就认为开启了控制台。

function resize(){
    var threshold = 200;
    var widthThreshold = window.outerWidth - window.innerWidth > threshold;
    var heightThreshold = window.outerHeight - window.innerHeight > threshold;
    if(widthThreshold || heightThreshold){
        console.log('控制台打开了')
    }
}
window.addEventListener('resize', resize);
resize()

关于检测窗口大小,有人专门针对此写了个库:https://github.com/sindresorh...,感兴趣的可以去看一下。

破解:监测浏览器宽高变化的缺点是非常明显的,因为这种监测只能针对控制台内嵌的情形,但是很多浏览器都支持独立窗口式的控制台。

3、利用控制台特性改写对象toString

对于一些浏览器,如果控制台输出的是对象,则保留对象的引用,每次打开控制台的时候,如果对象类型是function、date等(以前还有regexp,现在已失效),都会重新调用一下对象的toString()方法,将返回结果打印到控制台上。

经过测试:
1)、先声明对象,再重写toString,最后打印对象,那么toString会在开始时多运行一次,所以可以使用一个计数器来判断哪次有效
2)、先声明对象,再打印对象,最后重写toString,那么如果初始化时控制台是开启状态,会检测不到这一次的状态
3)、先声明对象,再重写toString,最后打印对象,但是对象不作为第一个参数,此时就可以成功监测每一次控制台状态了
4)、console.log、console.info、console.error等均有效
5)、只在chrome内核浏览器有效,firefox、ie失效

var devtools = new Date(); //function(){};
devtools.toString = function() {
    console.log('控制台打开了');
    //或执行一段死循环
    window.open("about:blank", "_self"); 
}
console.log('', devtools);

破解:可通过标签注入js代码清空控制台(添加一个网页标签,标签网址为javascript:console.clear();,进入网页后,点击该标签页,就会运行里面的代码),如果是定时器执行上述代码,还需要重写清空console(javascript:console.clear();for(var k in console){if(typeof console[k] == 'function'){console[k] = function(){}}};)。

4、利用控制台特性进行监听dom属性

大部分浏览器在打印dom元素的时候,如果控制台处于关闭状态,不会获取元素属性,但是如果控制台处于开启状态,就会自动获取dom属性,从而触发监听事件

function observerConsole(){
    //这里使用dom元素,在打开控制台时才会计算id
    var dom = document.createElement("div")
    Object.defineProperty(dom, "id", {
        get: function(){
            console.log('控制台打开了')
        }
    })
    
    //ie不支持console.table
    //console.info(dom);
    console.log(dom);
}

除了使用console.log,我们还可以使用console.info,console.dir和console.error等等,需要注意的是ie不支持console.table

破解:通过标签注入js代码清空控制台,如果是定时器执行打印dom的操作,还需要重写清空console。

上述方法需要注意浏览器对于defineProperty的支持,另外在firefox浏览器失效,因为firefox浏览器对于对象中监听的属性不会取值,需要手动点开才会触发。所以对于firefox需要另辟蹊跷才行,这里我选择使用debugger语句来实现,debugger 语句调用任何可用的调试功能,可以阻断代码执行,如果没有调试功能可用,则此语句不起作用。所以我们可以在debugger前记录时间,如果debugger没有触发,运行几条语句的时间几乎为0,但是如果被触发,那间隔时间就不是几十、几百毫秒了。

function observerConsole(){
    var obj = Object.create(null), t = Date.now();
    Object.defineProperty(obj, "a", {
        get: function() {
            if(Date.now() - t > 100){
                console.log('控制台打开了')
            }
        }
    })
    setInterval(function(){
        t = Date.now();
        (function(){})["constructor"]("debugger")();//debugger;
        console.log(obj.a);
    }, 200)
}

缺点:如果浏览器取消了debugger调式,那么就毫无意义了。
chrome:
chrome.png
Firefox:
firefox.png

汇总3、4,可以做如下封装:

var observerConsole = {
    openCallback: function(){
        console.log('控制台打开了');
        try {
            window.open("about:blank", "_self")
        } catch(e) {
            var btn = document.createElement("button");
            btn.onclick = function() {
                window.open("about:blank", "_self")
            }
            btn.click()
        }
    },
    observer: function(){
        //这里使用dom元素,在打开控制台时才会计算id
        var dom = document.createElement("div"), that = this;
        Object.defineProperty(dom, "id", {
            get: function(){
                that.openCallback()
            }
        })
        //ie不支持console.table
        //console.info(dom);
        console.log(dom);
    },
    observerF: function(){
        var obj = Object.create(null), t = Date.now(), that = this;
        Object.defineProperty(obj, "a", {
            get: function() {
                if(Date.now() - t > 100){
                    that.openCallback()
                }
            }
        })
        setInterval(function(){
            t = Date.now();
            (function(){})["constructor"]("debugger")();//debugger;
            console.log(obj.a);
        }, 200)
    },
    init: function(){
        var t = window.navigator.userAgent.toLowerCase();
        t.indexOf("firefox") >= 0 ? this.observerF() : this.observer();
    }
}
ConsoleManager.init()

破解:通过标签注入js代码清空控制台、取消console.log等
反破解:对console.log等进行重写再包装,如

let _console = {
      log : console.log,
      info : console.info,
      warn : console.warn,
      error : console.error
};

然后使用_console.log等替换上面的console.log。这里可以使用闭包,防止别人对_console的再重写。

代码测试仅测试了firefox、ie、chrome浏览器及部分chrome内核浏览器(如360、qq浏览器、UC浏览器、搜狗浏览器)

5、利用debugger的特性,无限递归

这个方法不能监测控制台被打开,但是能达到不让别人浏览你代码的目的。

上面也说了:debugger 语句调用任何可用的调试功能,可以阻断代码执行,如果没有调试功能可用,则此语句不起作用。

另外:每个浏览器都有其最大调用栈,如果超出就会抛出Maximum call stack size exceeded的错误并终止程序。

利用上面讲的特性组合成下面的代码:

function check() {
    function doCheck(a) {
        (function() {}["constructor"]("debugger")()); //debugger
        doCheck(++a);
    }
    try {
        doCheck(0)
    } catch(err) {
        console.log(err)
    }
};

上面代码check运行时,如果控制台未开启,debugger 不会起作用,但是doCheck会不断循环,直至爆栈,抛出错误,中止本次check运行;如果控制台开启,则会不断的进行断点调试和循环doCheck的调用,直至爆栈;如果控制台开启,但是取消了debugger调式,虽然此时debugger 不会起作用,但递归是依然存在的,而且此时网页性能与未开启控制台相比会大幅度下降,严重的话,可能会卡死浏览器。

未开启控制台时代码运行时间:
Chrome:30-50ms
Firefox:200-400ms
Ie:10-30ms

开启控制台但取消debugger时代码运行时间:
Chrome:1000-2000ms
Firefox:页面直接卡死

从上面的测试结果来看,我们可以设置一个间隔2000ms的定时器来不断执行check,这样当控制台开启时,不论是否取消debugger调式,都会使页面卡住。另外我们还可以对代码进行混淆,增加阅读困难度,我们还可以利用闭包完成上面操作,防止别人在控制台重置check:check=function(){}。

!function(){
    var _0x1cbb = ["tor", "struc", "call", "ger", "con", "bug", "de", "apply"];
    setInterval(check, 2e3);
    function check() {
        function doCheck(_0x1834ff) {
            if (('' + _0x1834ff / _0x1834ff)['length'] !== 0x1 || _0x1834ff % 0x14 === 0x0) {
                (function() {return !![]}[
                    _0x1cbb[0x4] + _0x1cbb[0x1] + _0x1cbb[0x0]
                ](
                    _0x1cbb[0x6] + _0x1cbb[0x5] + _0x1cbb[0x3]
                )[_0x1cbb[0x2]]());
            } else {
                (function() {return ![]}[
                    _0x1cbb[0x4] + _0x1cbb[0x1] + _0x1cbb[0x0]
                ](
                    _0x1cbb[0x6] + _0x1cbb[0x5] + _0x1cbb[0x3]
                )[_0x1cbb[0x7]]());
            }
            doCheck(++_0x1834ff);
        }
        try {
            doCheck(0)
        } catch(err) { }
    };
}();

优点:兼容性比较好,不易破解
缺点:会影像浏览器性能,造成页面卡顿
破解:想办法重置check函数

6、练手网站推荐

下面推荐几个网站,他们都用了上面讲的某种方法来禁止打开控制台,感兴趣的话可以研究下他们是怎么做到的,该如何破解(破解方法在上面已经给出了)。

以下网站推荐不具有针对性,如有冒犯,十分抱歉,可联系我进行删除

1、http://marsgis.cn/project/3d/...

2、https://z1.m1907.cn/

3、https://music.qugeek.com/page...

查看原文

ggwfnyqqqfoa 提出了问题 · 2019-08-20

electron中如何拦截所有的http请求结果?并对结果进行修改

electron中如何拦截所有的http请求结果?并对结果进行修改.
翻遍了electron文档, 只有request, 没有对Response做处理的, 求教这个怎么解决?

关注 2 回答 1

ggwfnyqqqfoa 回答了问题 · 2019-07-24

解决koa2跨域

好像是顺序的问题, 你应该吧 app.use(cors()) 放在所有的 router 之前。

关注 6 回答 5

ggwfnyqqqfoa 赞了回答 · 2019-05-25

vscode代码格式化如何配置花括号不换行?

😥设置里面搜一下就有了啊……

// 定义控制块的左括号是否放置在新的一行。
"typescript.format.placeOpenBraceOnNewLineForControlBlocks": false,

// 定义函数的左大括号是否放置在新的一行。
"typescript.format.placeOpenBraceOnNewLineForFunctions": false,

关注 3 回答 3

ggwfnyqqqfoa 提出了问题 · 2019-04-09

wireshark 抓到的PSH 怎么还原成数据?

我用wireshark 抓一个TCP的流, 然后追踪TCP流,但是data显示的是16进制, 我要怎么转换成正常的数据?

图片描述

关注 2 回答 1

ggwfnyqqqfoa 关注了专栏 · 2019-03-21

cc长空

前端小白

关注 3

ggwfnyqqqfoa 赞了回答 · 2019-03-21

解决苹果手机上table中如果太宽文字就会被放大

Font Inflation(字体膨胀) ,你上网查查这个关键词看看是不是这个问题。

https://www.lilnong.top/stati...
https://www.lilnong.top/stati...

关注 2 回答 1

ggwfnyqqqfoa 收藏了文章 · 2019-03-21

如何对前端图片主题色进行提取?这篇文章详细告诉你

本文由云+社区发表

图片主题色在图片所占比例较大的页面中,能够配合图片起到很好视觉效果,给人一种和谐、一致的感觉。同时也可用在图像分类,搜索识别等方面。通常主题色的提取都是在后端完成的,前端将需要处理的图片以链接或id的形式提供给后端,后端通过运行相应的算法来提取出主题色后,再返回相应的结果。

这样可以满足大多数展示类的场景,但对于需要根据用户“定制”、“生成”的图片,这样的方式就有了一个上传图片---->后端计算---->返回结果的时间,等待时间也许就比较长了。由此,我尝试着利用 canvas在前端进行图片主题色的提取。

一、主题色算法

目前比较常用的主题色提取算法有:最小差值法、中位切分法、八叉树算法、聚类、色彩建模法等。其中聚类和色彩建模法需要对提取函数和样本、特征变量等进行调参和回归计算,用到 python的数值计算库 numpy和机器学习库 scikit-learn,用 python来实现相对比较简单,而目前这两种都没有成熟的js库,并且js本身也不擅长回归计算这种比较复杂的计算。我也就没有深入的研究,而主要将目光放在了前面的几个颜色量化算法上。

而最小差值法是在给定给定调色板的情况下找到与色差最小的颜色,使用的场景比较小,所以我主要看了中位切分法和八叉树算法,并进行了实践。

中位切分法

中位切分法通常是在图像处理中降低图像位元深度的算法,可用来将高位的图转换位低位的图,如将24bit的图转换为8bit的图。我们也可以用来提取图片的主题色,其原理是是将图像每个像素颜色看作是以R、G、B为坐标轴的一个三维空间中的点,由于三个颜色的取值范围为0~255,所以图像中的颜色都分布在这个颜色立方体内,如下图所示。

img

之后将RGB中最长的一边从颜色统计的中位数一切为二,使得到的两个长方体所包含的像素数量相同,如下图所示

img

重复这个过程直到切出长方体数量等于主题色数量为止,最后取每个长方体的中点即可。

img

在实际使用中如果只是按照中点进行切割,会出现有些长方体的体积很大但是像素数量很少的情况。解决的办法是在切割前对长方体进行优先级排序,排序的系数为体积 * 像素数。这样就可以基本解决此类问题了。

八叉树算法

八叉树算法也是在颜色量化中比较常见的,主要思路是将R、G、B通道的数值做二进制转换后逐行放下,可得到八列数字。如 #FF7880转换后为

R: 1111 1111
G: 0111 1000
B: 0000 0000

再将RGB通道逐列粘合,可以得到8个数字,即为该颜色在八叉树中的位置,如图。

img

在将所有颜色插入之后,再进行合并运算,直到得到所需要的颜色数量为止。

在实际操作中,由于需要对图像像素进行遍历后插入八叉树中,并且插入过程有较多的递归操作,所以比中位切分法要消耗更长的时间。

二、中位切分法实践

根据之前的介绍和网上的相关资料,此处贴上我自己理解实现的中位切分法代码,并且找了几张图片将结果与QQ音乐已有的魔法色相关算法进行比较,图一为中位切分法结果,图二为后台cgi返回结果

图一

img

图二

img

img

可以看到有一定的差异,但是差值相对都还比较小的,处理速度在pc上面还是比较快的,三张图分别在70ms,100ms,130ms左右。这里贴上代码,待后续批量处理进行对比之后再分析。

(function () {

    /**
     * 颜色盒子类
     *
     * @param {Array} colorRange    [[rMin, rMax],[gMin, gMax], [bMin, bMax]] 颜色范围
     * @param {any} total   像素总数, imageData / 4
     * @param {any} data    像素数据集合
     */
    function ColorBox(colorRange, total, data) {
        this.colorRange = colorRange;
        this.total = total;
        this.data = data;
        this.volume = (colorRange[0][1] - colorRange[0][0]) * (colorRange[1][1] - colorRange[1][0]) * (colorRange[2][1] - colorRange[2][0]);
        this.rank = this.total * (this.volume);
    }

    ColorBox.prototype.getColor = function () {
        var total = this.total;
        var data = this.data;

        var redCount = 0,
            greenCount = 0,
            blueCount = 0;

        for (var i = 0; i < total; i++) {
            redCount += data[i * 4];
            greenCount += data[i * 4 + 1];
            blueCount += data[i * 4 + 2];
        }

        return [parseInt(redCount / total), parseInt(greenCount / total), parseInt(blueCount / total)];
    }

    // 获取切割边
    function getCutSide(colorRange) {   // r:0,g:1,b:2
        var arr = [];
        for (var i = 0; i < 3; i++) {
            arr.push(colorRange[i][1] - colorRange[i][0]);
        }
        return arr.indexOf(Math.max(arr[0], arr[1], arr[2]));
    }

    // 切割颜色范围
    function cutRange(colorRange, colorSide, cutValue) {
        var arr1 = [];
        var arr2 = [];
        colorRange.forEach(function (item) {
            arr1.push(item.slice());
            arr2.push(item.slice());
        })
        arr1[colorSide][1] = cutValue;
        arr2[colorSide][0] = cutValue;
        return [arr1, arr2];
    }

    // 找到出现次数为中位数的颜色
    function getMedianColor(colorCountMap, total) {
        var arr = [];
        for (var key in colorCountMap) {
            arr.push({
                color: parseInt(key),
                count: colorCountMap[key]
            })
        }

        var sortArr = __quickSort(arr);
        var medianCount = 0;
        var medianColor = 0;
        var medianIndex = Math.floor(sortArr.length / 2)

        for (var i = 0; i <= medianIndex; i++) {
            medianCount += sortArr[i].count;
        }

        return {
            color: parseInt(sortArr[medianIndex].color),
            count: medianCount
        }

        // 另一种切割颜色判断方法,根据数量和差值的乘积进行判断,自己试验后发现效果不如中位数方法,但是少了排序,性能应该有所提高
        // var count = 0;
        // var colorMin = arr[0].color;
        // var colorMax = arr[arr.length - 1].color
        // for (var i = 0; i < arr.length; i++) {
        //     count += arr[i].count;

        //     var item = arr[i];

        //     if (count * (item.color - colorMin) > (total - count) * (colorMax - item.color)) {
        //         return {
        //             color: item.color,
        //             count: count
        //         }
        //     }
        // }

        return {
            color: colorMax,
            count: count
        }



        function __quickSort(arr) {
            if (arr.length <= 1) {
                return arr;
            }
            var pivotIndex = Math.floor(arr.length / 2),
                pivot = arr.splice(pivotIndex, 1)[0];

            var left = [],
                right = [];
            for (var i = 0; i < arr.length; i++) {
                if (arr[i].count <= pivot.count) {
                    left.push(arr[i]);
                }
                else {
                    right.push(arr[i]);
                }
            }
            return __quickSort(left).concat([pivot], __quickSort(right));
        }
    }

    // 切割颜色盒子
    function cutBox(colorBox) {
        var colorRange = colorBox.colorRange,
            cutSide = getCutSide(colorRange),
            colorCountMap = {},
            total = colorBox.total,
            data = colorBox.data;

        // 统计出各个值的数量
        for (var i = 0; i < total; i++) {
            var color = data[i * 4 + cutSide];

            if (colorCountMap[color]) {
                colorCountMap[color] += 1;
            }
            else {
                colorCountMap[color] = 1;
            }
        }
        var medianColor = getMedianColor(colorCountMap, total);
        var cutValue = medianColor.color;
        var cutCount = medianColor.count;
        var newRange = cutRange(colorRange, cutSide, cutValue);
        var box1 = new ColorBox(newRange[0], cutCount, data.slice(0, cutCount * 4)),
            box2 = new ColorBox(newRange[1], total - cutCount, data.slice(cutCount * 4))
        return [box1, box2];
    }

    // 队列切割
    function queueCut(queue, num) {

        while (queue.length < num) {

            queue.sort(function (a, b) {
                return a.rank - b.rank
            });
            var colorBox = queue.pop();
            var result = cutBox(colorBox);
            queue = queue.concat(result);
        }

        return queue.slice(0, 8)
    }

    function themeColor(img, callback) {

        var canvas = document.createElement('canvas'),
            ctx = canvas.getContext('2d'),
            width = 0,
            height = 0,
            imageData = null,
            length = 0,
            blockSize = 1,
            cubeArr = [];

        width = canvas.width = img.width;
        height = canvas.height = img.height;

        ctx.drawImage(img, 0, 0, width, height);

        imageData = ctx.getImageData(0, 0, width, height).data;

        var total = imageData.length / 4;

        var rMin = 255,
            rMax = 0,
            gMin = 255,
            gMax = 0,
            bMin = 255,
            bMax = 0;

        // 获取范围
        for (var i = 0; i < total; i++) {
            var red = imageData[i * 4],
                green = imageData[i * 4 + 1],
                blue = imageData[i * 4 + 2];

            if (red < rMin) {
                rMin = red;
            }

            if (red > rMax) {
                rMax = red;
            }

            if (green < gMin) {
                gMin = green;
            }

            if (green > gMax) {
                gMax = green;
            }

            if (blue < bMin) {
                bMin = blue;
            }

            if (blue > bMax) {
                bMax = blue;
            }
        }

        var colorRange = [[rMin, rMax], [gMin, gMax], [bMin, bMax]];
        var colorBox = new ColorBox(colorRange, total, imageData);

        var colorBoxArr = queueCut([colorBox], 8);

        var colorArr = [];
        for (var j = 0; j < colorBoxArr.length; j++) {
            colorBoxArr[j].total && colorArr.push(colorBoxArr[j].getColor())
        }

        callback(colorArr);
    }

    window.themeColor = themeColor

})()

三、八叉树算法实践

也许是我算法实现的问题,使用八叉树算法得到的最终结果并不理想,所消耗的时间相对于中位切分法也长了不少,平均时间分别为160ms,250ms,400ms还是主要看八叉树算法吧...同样贴上代码

img

(function () {

    var OctreeNode = function () {
        this.isLeaf = false;
        this.pixelCount = 0;
        this.red = 0;
        this.green = 0;
        this.blue = 0;
        this.children = [null, null, null, null, null, null, null, null];
        this.next = null;
    }

    var root = null,
        leafNum = 0,
        colorMap = null,
        reducible = null;

    function createNode(index, level) {
        var node = new OctreeNode();
        if (level === 7) {
            node.isLeaf = true;
            leafNum++;
        } else {
            // 将其丢到第 level 层的 reducible 链表中
            node.next = reducible[level];
            reducible[level] = node;
        }

        return node;
    }

    function addColor(node, color, level) {
        if (node.isLeaf) {
            node.pixelCount += 1;
            node.red += color.r;
            node.green += color.g;
            node.bllue += color.b;
        }
        else {
            var str = "";
            var r = color.r.toString(2);
            var g = color.g.toString(2);
            var b = color.b.toString(2);
            while (r.length < 8) r = '0' + r;
            while (g.length < 8) g = '0' + g;
            while (b.length < 8) b = '0' + b;

            str += r[level];
            str += g[level];
            str += b[level];

            var index = parseInt(str, 2);

            if (null === node.children[index]) {
                node.children[index] = createNode(index, level + 1);
            }

            if (undefined === node.children[index]) {
                console.log(index, level, color.r.toString(2));
            }

            addColor(node.children[index], color, level + 1);
        }
    }

    function reduceTree() {

        // 找到最深层次的并且有可合并节点的链表
        var level = 6;
        while (null == reducible[level]) {
            level -= 1;
        }

        // 取出链表头并将其从链表中移除
        var node = reducible[level];
        reducible[level] = node.next;

        // 合并子节点
        var r = 0;
        var g = 0;
        var b = 0;
        var count = 0;
        for (var i = 0; i < 8; i++) {
            if (null === node.children[i]) continue;
            r += node.children[i].red;
            g += node.children[i].green;
            b += node.children[i].blue;
            count += node.children[i].pixelCount;
            leafNum--;
        }

        // 赋值
        node.isLeaf = true;
        node.red = r;
        node.green = g;
        node.blue = b;
        node.pixelCount = count;
        leafNum++;
    }

    function buidOctree(imageData, maxColors) {
        var total = imageData.length / 4;
        for (var i = 0; i < total; i++) {
            // 添加颜色
            addColor(root, {
                r: imageData[i * 4],
                g: imageData[i * 4 + 1],
                b: imageData[i * 4 + 2]
            }, 0);

            // 合并叶子节点
            while (leafNum > maxColors) reduceTree();
        }
    }

    function colorsStats(node, object) {
        if (node.isLeaf) {
            var r = parseInt(node.red / node.pixelCount);
            var g = parseInt(node.green / node.pixelCount);
            var b = parseInt(node.blue / node.pixelCount);

            var color = r + ',' + g + ',' + b;
            if (object[color]) object[color] += node.pixelCount;
            else object[color] = node.pixelCount;
            return;
        }

        for (var i = 0; i < 8; i++) {
            if (null !== node.children[i]) {
                colorsStats(node.children[i], object);
            }
        }
    }

    window.themeColor = function (img, callback) {
        var canvas = document.createElement('canvas'),
            ctx = canvas.getContext('2d'),
            width = 0,
            height = 0,
            imageData = null,
            length = 0,
            blockSize = 1;

        width = canvas.width = img.width;
        height = canvas.height = img.height;

        ctx.drawImage(img, 0, 0, width, height);

        imageData = ctx.getImageData(0, 0, width, height).data;

        root = new OctreeNode();
        colorMap = {};
        reducible = {};
        leafNum = 0;

        buidOctree(imageData, 8)

        colorsStats(root, colorMap)

        var arr = [];
        for (var key in colorMap) {
            arr.push(key);
        }
        arr.sort(function (a, b) {
            return colorMap[a] - colorMap[b];
        })
        arr.forEach(function (item, index) {
            arr[index] = item.split(',')
        })
        callback(arr)
    }
})()

四、结果对比

在批量跑了10000张图片之后,得到了下面的结果

平均耗时对比(js-cgi)

img

可以看到在不考虑图片加载时间的情况下,用中位切分法提取的耗时相对较短,而图片加载的耗时可以说是难以逾越的障碍了(整整拖慢了450ms),不过目前的代码还有不错的优化空间,比如间隔采样,绘制到canvas时减小图片尺寸,优化切割点查找等,就需要后续进行更深一点的探索了。

颜色偏差

img

所以看来准确性还是可以的,约76%的颜色与cgi提取结果相近,在大于100的中抽查后发现有部分图片两者提取到的主题色各有特点,或者平分秋色,比如

img

img

五、小结

总结来看,通过canvas的中位切分法与cgi提取的结果相似程度还是比较高的,也有许多图片有很大差异,需要在后续的实践中不断优化。同时,图片加载时间也是一个难以逾越的障碍,不过目前的代码还有不错的优化空间,比如间隔采样,绘制到canvas时减小图片尺寸,优化切割点查找等,就需要后续进行更深一点的探索了。

参考文章

http://acm.nudt.edu.cn/~twcou...

https://xcoder.in/2014/09/17/...

http://blog.rainy.im/2015/11/...

https://xinyo.org/archives/66115

https://xinyo.org/archives/66352

https://github.com/lokesh/col...

http://y.qq.com/m/demo/2018/m...

此文已由作者授权腾讯云+社区在各渠道发布

获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号

查看原文

认证与成就

  • 获得 8 次点赞
  • 获得 46 枚徽章 获得 0 枚金徽章, 获得 15 枚银徽章, 获得 31 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-02-25
个人主页被 406 人浏览