图片取色-八叉树颜色量化

第一次尝试着算法类功能,实现图片取色功能,采用颜色量化使用较多的八叉树算法量化,记录下

rgbaster.js 文件代码


var reducible = [];
var leafNum = 0;  //叶子节点数
 
// 八叉树节点 
/**
  * #FF7800
  * R: 1111 1111
  * G: 0111 1000
  * B: 0000 0000
  * RGB 通道逐列黏合之后的值就是其在某一层节点的子节点编号了。
  * 每一列一共是三位,那么取值范围就是 0 ~ 7 也就是一共有八种情况。
  * 这就是为什么这种算法要开八叉树来计算的原因了。
  * 上述颜色的第一位黏合起来是100(2),转化为十进制就是 4,所以这个颜色在第一层是放在根节点的第五个子节点当中;
  * 第二位是 110(2) 也就是 6,那么它就是根节点的第五个儿子的第七个儿子。
  */
var OctreeNode = function () {
    this.isLeaf = false; // 表明该节点是否为叶子节点。
    this.pixelCount = 0; //在该节点的颜色一共插入了几次。
    this.red = 0; //该节点 R 通道累加值。
    this.green = 0; //G 累加值。
    this.blue = 0; // B 累加值。
 
    this.children = new Array(8); // 八个子节点指针。
    for (var i = 0; i < this.children.length; i++) this.children[i] = null;
 
    // 这里的 next 不是指兄弟链中的 next 指针
    // 而是在 reducible 链表中的下一个节点
    this.next = null;
    // 注意:我们将会把该颜色的 RGB 分量分别累加到该节点的各分量值中,以便最终求平均数。
};
 
for (var i = 0; i < 7; i++) reducible.push(null);
 
var root = new OctreeNode();
 
/**
  * 创建节点
  * createNode
  *
  * @param {OctreeNode} parent the parent node of the new node
  * @param {Number} idx child index in parent of this node
  * @param {Number} level node level
  * @return {OctreeNode} the new node  
  */
function createNode(parent, idx, level) {
    var node = new OctreeNode();
    //根据层数level判断当前节点是否是叶子节点  是则isLeaf为true,leafNum叶子节点数
    if (level === 7) {
        node.isLeaf = true;
        leafNum++;
    } else {
        // 将其丢到第 level 层的 reducible 链表中
        node.next = reducible[level];
        reducible[level] = node;
    }
     
    return node;
}
 
/**
  * addColor
  *  插入某种颜色
  * @param {OctreeNode} node the octree node
  * @param {Object} color color object
  * @param {Number} level node level
  * @return {undefined}
  */
function addColor(node, color, level) {
    // 判断是否是叶子节点
    if (node.isLeaf) {
        node.pixelCount++;
        node.red += color.r;
        node.green += color.g;
        node.blue += color.b;
    } else {
        // 由于 js 内部都是以浮点型存储数值,所以位运算并没有那么高效
        // 在此使用直接转换字符串的方式提取某一位的值
        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 idx = parseInt(str, 2);
 
        if (null === node.children[idx]) {
            node.children[idx] = createNode(node, idx, level + 1);
        }
 
        if (undefined === node.children[idx]) {
            console.log(color.r.toString(2));
        }
        // 递归将数据完全放入八叉树中
        addColor(node.children[idx], color, level + 1);
    }
}
 
/**
  * reduceTree
  *  合并颜色 合并就先去最底层的 reducible 链表中寻找一个可以合并的节点,把它从链表中删除之后合并叶子节点并且删除其叶子节点
  * @return {undefined}
  */
function reduceTree() {
    // 找到最深层次的并且有可合并节点的链表
    var lv = 6;
    while (null === reducible[lv]) lv--;
 
    // 取出链表头并将其从链表中移除
    var node = reducible[lv];
    reducible[lv] = 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++;
}
 
/**
  * buildOctree
  * 过程就是遍历一遍传入的像素颜色信息,对于每个颜色都插入到八叉树当中;
  * 并且每一次插入之后都判断下叶子节点数有没有溢出,如果满出来的话需要及时合并。
  * @param {Array} pixels        获取的像素数组
  * @param {Number} maxColors    最大保留合并后颜色数组的长度
  * @return {undefined}
  */
function buildOctree(pixels, maxColors) {
    for (var i = 0; i < pixels.length; i++) {
        // 添加颜色
        addColor(root, pixels[i], 0);
        // 合并叶子节点
        while (leafNum > maxColors) reduceTree();
    }
}
 
/**
  * 颜色统计
  *
  * @param {OctreeNode} node 节点将是stats
  * @param {Object} object 颜色统计信息
  * @return {undefined}
  */
function colorsStats(node, object) {
    // 判断是否是叶子节点
    if (node.isLeaf) {
        // 计算当前颜色的平均,并转换成16进制数
        var r = parseInt(node.red / node.pixelCount).toString(16);
        var g = parseInt(node.green / node.pixelCount).toString(16);
        var b = parseInt(node.blue / node.pixelCount).toString(16);
        if (r.length === 1) r = '0' + r;
        if (g.length === 1) g = '0' + g;
        if (b.length === 1) b = '0' + b;
        var color = r + g + b;
        // console.log('color:', color);
        // console.log('object:', object);
        // 统计当前颜色合并累计的次数
        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);
        }
    }
}
/**
  * Helper functions. 根据宽高生成canvas画布
  * @param {*} width 
  * @param {*} height 
  * @returns 
  */
var getContext = function (width, height) {
    var canvas = document.createElement('canvas');
    canvas.setAttribute('width', width);
    canvas.setAttribute('height', height);
    return canvas.getContext('2d');
};
 
/**
  * 
  * @param {string} img 
  * @param {Function} loaded 
  */
var getImageData = function (img, loaded) {
    // 初始化imge对象
    var imgObj = new Image();
    var imgSrc = img.src || img;
 
    if (imgSrc.substring(0, 5) !== 'data:') imgObj.crossOrigin = 'Anonymous';
 
    imgObj.onload = () => {
        // 初始化画布
        var context = getContext(imgObj.width, imgObj.height);
        // 将图片赋值到画布上
        context.drawImage(imgObj, 0, 0);
        // 获取画布像素点信息
        var imageData = context.getImageData(0, 0, imgObj.width, imgObj.height);
        loaded && loaded(imageData.data);
    };
    imgObj.src = imgSrc;
};

/**
  * @param {object} param                   参数
  * @param {param.img} img                  图片路径
  * @param {param.lenth} lenth              获取颜色数组的最大长度   
  * @callback {Function}                    成功回调
  */
export default function RGBaster(param, callback) {
    let {img, lenth} = param;
    getImageData(img, (data) => {
        var array = [];
        // 将颜色像素点分割成rgb格式
        for (var i = 0; i < data.length; i += 4) {
            var r = data[i];
            var g = data[i + 1];
            var b = data[i + 2];
            array.push({ r: r, g: g, b: b });
        }
        // console.log('将颜色像素点分割成rgb格式:', array);
        // 将每个颜色像素点放入的八叉树中
        buildOctree(array, lenth);
        // console.log('==========', root);
        var colors = {};
        // 将筛选过后的颜色转换至16进制数
        colorsStats(root, colors);
 
        var result = [];
        for (var key in colors) {
            result.push({ color: key, count: colors[key] });
        }
        // 按合并数排序
        result.sort((a, b) => b.count - a.count);

        // 返回筛选结果
        callback(result);
    });
}
 
 
//引入
import RGBaster from '@/lib/rgbaster/rgbaster';

// 调用
RGBaster({img: this.img, lenth: 8}, color => {
       this.colorData = color;
       console.log(color);
});

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值