Color Picker research

Profile picture of MikeHe-creator

Draft

Apr 2, 2024

·

9 min read

·

168 Views


I had cost three days to research color picker. I wonder how does a painting web like Pixiv sketch, youidraw have a color picker. You know, if you ask a computer to calculate and display dense color values, the computer will probably crash.So it's a magical thing.

1. The Simplest way: use input.

HTML5 has supported you to pick color by

<input type="color">

This element has provided the best color picker. If you don't know how to do it by yourself. You could straightly use it.

2. Color Checker

Many picture painting or modifying apps has a color checker. Do you know how to do it? There is an idea, copy this picture which you want to know by canvas and then use canvas to analyze the data of color (RGBA, HEX, HSL)

The code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>pictocolour</title>
</head>
<body>
<div id='idc'>
    <img id="examplec" src="https://pic4.zhimg.com/80/v2-8fa25a11b26eff364f26b10969317c0b_720w.jpg" alt="example">
    <canvas width="6" height="6" id="colorp"></canvas>
</div>
<div>
    <p>The colour of point is</p><p id="pointin"></p>
    <div id="displayc"></div>
</div>
</body>
<style>
    #colorp{
        border: solid 2px;
        border-radius: 20px;
        z-index: 3;
        position: absolute;
        left: 18%;
        top: 35%;
        transform: translate(-50%, -50%);
    }
    #displayc{
        margin-left: 4px;
        border-style: solid;
        width: 40px;
        height: 20px;
    }
</style>
<script>
    const examplec=document.getElementById('examplec');
    const colorp=document.getElementById('colorp');
    const pointin=document.getElementById('pointin');
    const displayc=document.getElementById('displayc')

    examplec.addEventListener('click',(event)=>{
        const mouseX = event.clientX;
        const mouseY = event.clientY;

        const cRect = examplec.getBoundingClientRect();
        const cLeft = cRect.left;
        const cTop = cRect.top;

        if (mouseX >= cLeft && mouseX <= cLeft + examplec.width && mouseY >= cTop && mouseY <= cTop + examplec.height) {
            const colorpX = mouseX - cLeft;
            const colorpY = mouseY - cTop;
            colorp.style.left = (mouseX + 10) + 'px'; 
            colorp.style.top = (mouseY + 10) + 'px'; 
            colorinfor(colorpX,colorpY)
        }
    })

    function colorinfor(colorpX,colorpY){
        const ctx=colorp.getContext('2d');
        const img = new Image();
        img.src = 'https://pic4.zhimg.com/80/v2-8fa25a11b26eff364f26b10969317c0b_720w.jpg';
        img.crossOrigin = "Anonymous"; 
        img.onload = function(){
            ctx.clearRect(0, 0, colorp.width, colorp.height); 
            ctx.drawImage(img, -colorpX, -colorpY); 
            const pixelData = ctx.getImageData(0, 0, colorp.width, colorp.height).data;
            console.log('pixelData',pixelData)
            let redSum = 0, greenSum = 0, blueSum = 0;
            for (let i = 0; i < pixelData.length; i += 4) {
                redSum += pixelData[i];
                greenSum += pixelData[i + 1];
                blueSum += pixelData[i + 2];
            }
            const pixelCount = pixelData.length / 4;
            const red = Math.round(redSum / pixelCount);
            const green = Math.round(greenSum / pixelCount);
            const blue = Math.round(blueSum / pixelCount);
            console.log("R: " + red + ", G: " + green + ", B: " + blue);
            pointin.innerHTML=`rgb(${red},${green},${blue})`;
            displayc.style.backgroundColor=`rgb(${red},${green},${blue})`
        }
    }
</script>
</html>

3. Combine two or more layers

In the painting app especially in the professional one, the layer is very important.

So there is an operation to combine two layers. The idea is so simple, paint twice in the same canvas:


THE CODE:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>PictureCombine</title>
</head>
<body>
<div>
    <img src="https://hbimg.huaban.com/b4096cd152ac8db31a686e5af2c5e3ea5f0e6e8155a18-ff7QQn_fw658" alt="beachBackground" id="sea">
    <img src="下载2.png" alt="Ash Kuchetum" id="ash">
</div>
<div>
    <canvas id="combine"></canvas>
</div>
</body>
<style>
    #ash{
        margin-left: -750px;
    }
</style>
<script>
    const sea = document.getElementById('sea');
    const ash = document.getElementById('ash');
    const combine = document.getElementById('combine'); 
    window.onload = function() {
        const ctx = combine.getContext('2d');
        combine.width = sea.width;
        combine.height = sea.height;
        ctx.drawImage(sea, 0, 0);
        ctx.drawImage(ash, -125, -105);
    }
</script>
</html>

4. The Color Picker

You had the idea of color checker and the layer combination. Now you can use this two idea to make a color picker.

First, make two layers an element to display colorful colors. It's better to use canvas to design this two layer, because it's easier to combine into a picture by another canvas. Finally, use the idea of color checker to analyze the color.

THE CODE:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>colorpan</title>
</head>
<body>
<div>
    <p>stamp:</p>
    <input type="color">
</div>
<div>
    <p id="p1">The circle point is in</p><div id="pointin"></div><br>
    <p id="color">The color you pointed is</p><div id="colorname"></div>
</div>
<div>
    <div id="colorpan1">
        <div id="colorpan2"></div>
        <canvas id="circlepoint"></canvas>
    </div>
    <div id="colors">
        <div id="colorcard"></div>
    </div>
</div>
</body>
<style>
    #colorpan1{
        border-style: solid;
        width: 600px;
        height: 600px;
        cursor: pointer;
        position: relative;
        overflow-x: hidden;
        overflow-y: hidden;
        background: linear-gradient(to right, #fff, rgba(255,255,255,0));
    }
    #colorpan2{
        width: 600px;
        height: 600px;
        cursor: pointer;
        position: relative;
        background:linear-gradient(to top, #000, rgba(0,0,0,0));
    }
    #circlepoint {
        border-style: solid;
        width: 10px;
        height: 10px;
        border-radius: 20px;
        z-index: 3;
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
    }
    #p1, #pointin{
        display: inline-block;
    }
    #colors{
        margin-top: 10px;
        border-style: solid;
        width: 600px;
        height: 60px;
        cursor: pointer;
        position: relative;
        background: linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00);
    }
    #colorcard{
        margin-top: -6px;
        border-style: solid;
        width: 10px;
        height: 70px;
        background-color: white;
        position: absolute;
        left: 0;
        top: 0;
    }
    #color, #colorname{
       display: inline-block;
    }
    #colorname{
        margin-left: 4px;
        border-style: solid;
        width: 40px;
        height: 20px;
    }
</style>
<script>
    const circlepoint = document.getElementById('circlepoint');
    const colorpan1 = document.getElementById('colorpan1');
    const colorpan2 = document.getElementById('colorpan2');
    const pointin=document.getElementById('pointin');
    const colors=document.getElementById('colors');
    const colorcard = document.getElementById('colorcard');
    const colorname=document.getElementById('colorname');

    colorcard.addEventListener('mousedown', function(event) {
        event.preventDefault();
        const offsetX = event.clientX - colorcard.getBoundingClientRect().left;
        const offsetY = event.clientY - colorcard.getBoundingClientRect().top;

        document.addEventListener('mousemove', onMouseMove);
        document.addEventListener('mouseup', onMouseUp);

        function onMouseMove(event) {
            const x = event.clientX - offsetX;
            const y = event.clientY - offsetY;

            const colorsRect = colors.getBoundingClientRect();
            const colorsLeft = colorsRect.left;
            const colorsTop = colorsRect.top;
            const colorsRight = colorsRect.right - colorcard.offsetWidth;
            const colorsBottom = colorsRect.bottom - colorcard.offsetHeight;

            if (x >= colorsLeft && x <= colorsRight && y >= colorsTop && y <= colorsBottom) {
                colorcard.style.left = x + 'px';
                colorcard.style.top = y + 'px';
            }
        }

        function onMouseUp() {
            document.removeEventListener('mousemove', onMouseMove);
            document.removeEventListener('mouseup', onMouseUp);
        }
    })

    document.addEventListener('click', function(event) {
        const mouseX = event.clientX;
        const mouseY = event.clientY;
        const colorpanRect = colorpan1.getBoundingClientRect();
        const colorpanLeft = colorpanRect.left;
        const colorpanTop = colorpanRect.top;
        const colorpanRight = colorpanRect.right;
        const colorpanBottom = colorpanRect.bottom;

        if (mouseX >= colorpanLeft && mouseX <= colorpanRight && mouseY >= colorpanTop && mouseY <= colorpanBottom) {
            const circlepointX = mouseX - colorpanLeft;
            const circlepointY = mouseY - colorpanTop;
            circlepoint.style.left = circlepointX + 'px';
            circlepoint.style.top = circlepointY + 'px';
            pointin.innerText = `(${circlepointX},${circlepointY})`;

            getColorAtPoint(circlepointX, circlepointY);
            }
        colorcards(mouseX, mouseY);
    });

    function colorcards(mouseX, mouseY){
        const colorsRect = colors.getBoundingClientRect();
        const colorsLeft = colorsRect.left;
        const colorsTop = colorsRect.top;
        const colorsRight = colorsRect.right;
        const colorsBottom = colorsRect.bottom;

        if (mouseX >= colorsLeft && mouseX <= colorsRight-8 && mouseY >= colorsTop && mouseY <= colorsBottom) {
            const circlepointX = mouseX - colorsLeft;
            colorcard.style.left = circlepointX + 'px';
            const colorIndex = Math.floor(circlepointX / (colors.offsetWidth / 7));
            const colorsArray = ['#f00', '#ff0', '#0f0', '#0ff', '#00f', '#f0f', '#f00'];
            colorpan1.style.background =` linear-gradient(to right, #fff, ${colorsArray[colorIndex]})`;
        }
    }

    function getColorAtPoint(circlepointX, circlepointY) {
        const tempcanvas = document.createElement('canvas');
        tempcanvas.width=colorpan1.clientWidth;
        tempcanvas.height=colorpan1.clientHeight;
        const temctx = tempcanvas.getContext('2d');
        const computedStyle1 = getComputedStyle(colorpan1);
        const computedStyle2 = getComputedStyle(colorpan2);
        const background1 = computedStyle1.backgroundImage;
        console.log(background1)
        const background2 = computedStyle2.backgroundImage;
        let gradientObj1, gradientObj2;

        if (background1.includes('linear-gradient')) {
            const colors = background1.match(/rgba?\([^)]*\)|#[0-9a-fA-F]{3,6}/g);
            gradientObj1 = temctx.createLinearGradient(0, 0, tempcanvas.width, tempcanvas.height);
            for (let i = 0; i < colors.length; i++) {
                gradientObj1.addColorStop(i / (colors.length - 1), colors[i]);
            }
        }

        if (background2.includes('linear-gradient')) {
            const colors = background2.match(/rgba?\([^)]*\)|#[0-9a-fA-F]{3,6}/g);
            gradientObj2 = temctx.createLinearGradient(0, tempcanvas.height, 0, 0);
            for (let i = 0; i < colors.length; i++) {
                gradientObj2.addColorStop(i / (colors.length - 1), colors[i]);
            }
        }

        if (gradientObj1) {
            temctx.fillStyle = gradientObj1;
            temctx.fillRect(0, 0, tempcanvas.width, tempcanvas.height);
        }

        if (gradientObj2) {
            temctx.fillStyle = gradientObj2;
            temctx.fillRect(0, 0, tempcanvas.width, tempcanvas.height);
        }

        const ctx = circlepoint.getContext('2d');
        const img = new Image();
        img.src = tempcanvas.toDataURL();
        img.onload = function () {
            ctx.clearRect(0, 0, circlepoint.width, circlepoint.height);
            const offsetX = circlepointX - circlepoint.width / 2;
            const offsetY = circlepointY - circlepoint.height / 2;
            const sourceWidth = circlepoint.width;
            const sourceHeight = circlepoint.height;
            ctx.imageSmoothingEnabled = true;
            ctx.imageSmoothingQuality = 'high';
            ctx.drawImage(img, offsetX, offsetY, sourceWidth, sourceHeight, 0, 0, circlepoint.width, circlepoint.height);
            const pixelData = ctx.getImageData(0, 0, circlepoint.width, circlepoint.height).data;
            console.log('pixelData',pixelData)
            let redSum = 0, greenSum = 0, blueSum = 0;
            for (let i = 0; i < pixelData.length; i += 4) {
                redSum += pixelData[i];
                greenSum += pixelData[i + 1];
                blueSum += pixelData[i + 2];
            }
            const pixelCount = pixelData.length / 4;
            console.log('pixelCount',pixelCount);
            const r = Math.round(redSum / pixelCount);
            const g = Math.round(greenSum / pixelCount);
            const b = Math.round(blueSum / pixelCount);
            console.log("r,g,b",r,g,b);

            const hsl = rgbToHsl(r, g, b);
            const hex = rgbToHex(r, g, b);
            colorname.style.backgroundColor=hex;

            return {
                rgb: [r, g, b],
                hsl: hsl,
                hex: hex
            };
        }
    }

    function rgbToHsl(r, g, b) {
        r /= 255, g /= 255, b /= 255;
        const max = Math.max(r, g, b),
            min = Math.min(r, g, b);
        let h, s, l = (max + min) / 2;

        if (max === min) {
            h = s = 0; 
        } else {
            const d = max - min;
            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
            switch (max) {
                case r:
                    h = (g - b) / d + (g < b ? 6 : 0);
                    break;
                case g:
                    h = (b - r) / d + 2;
                    break;
                case b:
                    h = (r - g) / d + 4;
                    break;
            }
            h /= 6;
        }
        return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
    }

    function rgbToHex(r, g, b) {
        return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
    }
</script>
</html>

The code I showed has a small flaw, that is, the accuracy is not high enough.

This idea, in addition to painting, can also be used to deal with the design of handwriting keyboards, notes and other places that require color.

5. Project Code Address:

https://github.com/MikeHe-creator/ColorLearn


Profile picture of MikeHe-creator

Written By

Who-am-I?

No bio found