识别图片中的颜色

uniapp文件,如果是vue项目生命周期需要改一下

<template>
    <div>
      <canvas style="display: none" id="canvas"></canvas>
      <div
         id="extract-color-id"
         class="extract-color"
         style="display: flex;padding: 0 20px; justify-content:end;">
      </div>
    </div>
</template>
<script>
import colorExtraction from './colorExtraction.ts';
export default{
    onLoad() {
        const img = new Image();
        img.src = `图片地址`;
        img.crossOrigin = 'anonymous';
        img.onload = () => {
          colorExtraction.themeColor(1, img, 20, {x:22.058823529 , y:90.29411764} ,{width:22,height:22}, this.SetColor);
        };
    },
    methods: {
        SetColor(colorArr){
            //返回的颜色判断是离黑白哪个近一些返回哪一个
            // let newVal = colorExtraction.getClosestToWhiteOrBlack(colorArr);
            // console.log(newVal);
            
            //颜色取反
            // let newColorArr = colorExtraction.reverse(colorArr)
            // colorArr = newColorArr;
             // 初始化删除多余子节点
            const extractColor = document.querySelector('#extract-color-id');
            while (extractColor.firstChild) {
                extractColor.removeChild(extractColor.firstChild);
            }
            // 创建子节点
            for (let index = 0; index < colorArr.length; index++) {
                const bgc = '(' + colorArr[index][0] + ',' + colorArr[index][1] + ',' + colorArr[index][2] + ')';
                const colorBlock = document.createElement('div');
                colorBlock.id = `color-block-id${index}`;
                colorBlock.style.cssText = 'height: 50px;width: 50px;margin-right: 10px;border-radius: 50%;';
                colorBlock.style.backgroundColor = `rgb${bgc}`;
                extractColor.appendChild(colorBlock);
            }
        }
    },
}
</script>

colorExtraction.ts

/**
 * 颜色盒子类
 *
 * @param {Array} colorRange    [[rMin, rMax],[gMin, gMax], [bMin, bMax]] 颜色范围
 * @param {any} total   像素总数, imageData / 4
 * @param {any} data    像素数据集合
 */
class ColorBox {
    colorRange: unknown[];
    total: number;
    data: Uint8ClampedArray;
    volume: number;
    rank: number;
    constructor(colorRange: any[], total: number, data: Uint8ClampedArray) {
        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 = total * this.volume;
    }
    getColor() {
        const total = this.total;
        const data = this.data;
        let redCount = 0,
            greenCount = 0,
            blueCount = 0;

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

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

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

    return [arr1, arr2];
}

// 找到出现次数为中位数的颜色
const __quickSort = (arr: any[]): any => {
    if (arr.length <= 1) {
        return arr;
    }
    const pivotIndex = Math.floor(arr.length / 2);
    const pivot = arr.splice(pivotIndex, 1)[0];
    const left = [];
    const right = [];
    for (let 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));
}

const getMedianColor = (colorCountMap: Record<string, number>, total: number) => {

    const arr = [];
    for (const key in colorCountMap) {
        arr.push({
            color: parseInt(key),
            count: colorCountMap[key]
        })
    }

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

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

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

// 切割颜色盒子
const cutBox = (colorBox: { colorRange: number[][]; total: number; data: Uint8ClampedArray }) => {

    const colorRange = colorBox.colorRange;
    const cutSide = getCutSide(colorRange);
    const colorCountMap: Record<string, number> = {};
    const total = colorBox.total;
    const data = colorBox.data;

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

        if (colorCountMap[color]) {
            colorCountMap[color] += 1;
        }
        else {
            colorCountMap[color] = 1;
        }
    }

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

// 队列切割
const queueCut = (queue: any[], num: number) => {
    while (queue.length < num) {
        queue.sort((a: { rank: number }, b: { rank: number }) => {
            return a.rank - b.rank
        });
        const colorBox = queue.pop();
        const result = cutBox(colorBox);
        queue = queue.concat(result);
    }
    return queue.slice(0, num)
}

// 颜色去重
const colorFilter = (colorArr: number[][], difference: number) => {
    for (let i = 0; i < colorArr.length; i++) {
        for (let j = i + 1; j < colorArr.length; j++) {
            if (Math.abs(colorArr[i][0] - colorArr[j][0]) < difference && Math.abs(colorArr[i][1] - colorArr[j][1]) < difference && Math.abs(colorArr[i][2] - colorArr[j][2]) < difference) {
                colorArr.splice(j, 1)
                j--
            }
        }
    }
    return colorArr
}
//黑白判断
function getBrightness(r:any, g:any, b:any) {
    return 0.299 * r + 0.587 * g + 0.114 * b;
}
 
function getClosestToWhiteOrBlack(colorArr: number[][]) {
    let newArr:any = [];
    let color:String="";
    for (let index = 0; index < colorArr.length; index++) {
        const element = colorArr[index];
        const whiteBrightness = getBrightness(255, 255, 255);
        const blackBrightness = getBrightness(0, 0, 0);
        const currentBrightness = getBrightness(element[0], element[1], element[2]);
    
        if (Math.abs(whiteBrightness - currentBrightness) < Math.abs(blackBrightness - currentBrightness)) {
            color = 'White';
        } else {
            color =  'Black';
        }
        newArr.push(color);
    }
    return newArr; 
}

//颜色取反
const reverse = (colorArr: number[][]) => {
    let newArr:any = [];
    for (let index = 0; index < colorArr.length; index++) {
        const element = colorArr[index];
        newArr[index] = [255-element[0],255-element[1],255-element[2]]
    }
    return newArr;
}
/**
 * 提取颜色
 * @param colorNumber 提取最大颜色数量
 * @param img 需要提取的图片
 * @param difference 图片颜色筛选精准度
 * @param position 图片颜色的距离左上顶点位置百分比
 * @param distance 图片颜色识别的宽高
 * @param callback 回调函数
 */

const themeColor = (colorNumber: number, img: CanvasImageSource, difference: number, position:any = {x:0,y:0} ,distance:any={width:1,height:1},callback: (arg0: number[][]) => void) => {
    const canvas = document.createElement('canvas') as HTMLCanvasElement;
    const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
    let width = 0
    let height = 0
    let imageData = null
    canvas.width = img.width as number;
    width = canvas.width as number
    canvas.height = img.height as number
    height = canvas.height

    ctx.drawImage(img, 0, 0, width, height);
    if(!distance){
        distance = {
            width:width,
            height:height
        }
    }
    imageData = ctx.getImageData(img.width as number * (position.x / 100), img.height as number * (position.y / 100), distance.width, distance.height).data;

    const total = imageData.length / 4;

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

    // 获取范围
    for (let i = 0; i < total; i++) {
        const red = imageData[i * 4];
        const green = imageData[i * 4 + 1];
        const 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;
        }
    }

    const colorRange = [[rMin, rMax], [gMin, gMax], [bMin, bMax]];
    const colorBox = new ColorBox(colorRange, total, imageData);
    const colorBoxArr = queueCut([colorBox], colorNumber);
    let colorArr = [];

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

    colorArr = colorFilter(colorArr, difference)

    callback(colorArr);
}

export default {
    themeColor,
    reverse,
    getClosestToWhiteOrBlack
}

参考:https://segmentfault.com/a/1190000041438074

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容