javascript - 使用 Javascript 基于十六进制颜色计算渐变上的 X、Y 像素位置

标签 javascript canvas colors

我有一个 raindow HSV 渐变 Canvas ,当您单击它时,会在该位置添加一个元素,其背景为单击像素的颜色。

我想要的是让它也反向工作。例如,如果您有十六进制颜色,我想在 Canvas 上找到该像素并在该位置创建一个元素。

我的第一个想法是以某种方式使用矩阵/象限系统。我的下一个想法是,因为我使用的是 HSV,所以我可以使用我的 HSV 梯度定位点来找出位置。问题是我的观点彼此不等距,这使得它变得更难。最重要的是,我有一个白色渐变和黑色渐变覆盖主颜色渐变,我需要考虑到这一点。

所以我的问题是,如何仅使用十六进制代码找到颜色像素的位置或至少它最接近的匹配?

到目前为止,这是我的代码: http://codepen.io/shelbywhite/pen/EyqPWY?editors=1000

HTML:

<div class="container">
    <canvas class="colorSpectrum"></canvas>
    <div class="circle"></div>
</div>

CSS:

.container {
    background: grey;
    height: 350px;
    width: 400px;
}

.circle {
    background: transparent;
    box-shadow: 0 0 8px rgba(0,0,0,0.2);
    border-radius: 50%;
    border: 2px solid #fff;
    height: 20px;
    margin: -12px;
    width: 20px;
    position: absolute;
}

.colorSpectrum {
    display: block;  
    height: 100%;
    transform: translateZ(0);
    width: 100%;
}

Javascript:

$(function() {

    var closest = function(num, arr) {
        var curr = arr[0];
        var diff = Math.abs(num - curr);

        for (var val = 0; val < arr.length; val++) {
            var newdiff = Math.abs(num - arr[val]);
            if (newdiff < diff) {
                diff = newdiff;
                curr = arr[val];
            }
        }

        return curr;
    };


    var container = $('.container');
    var containerWidth = container.width();
    var containerHeight = container.height();

    var verticalGradientsHeight = Math.round(containerHeight * .34);
    console.log('verticalGradientsHeight', verticalGradientsHeight);
    var round = function(value, decimals) {
        return Number(Math.round(value+'e'+decimals)+'e-'+decimals);
    };


    // Draws the color spectrum onto the canvas
    var drawColorSpectrum = function() {

        // Cache canvas element
        var canvasElement = $('.colorSpectrum');

        // Cache javascript element
        var canvas = canvasElement[0];

        // Get canvas context
        var ctx = canvas.getContext('2d');

        // Cache page height
        var canvasWidth = containerWidth;

        // Cache page height
        var canvasHeight = containerHeight - 72;

        // Bottom gradient start position
        var blackStartYPos = canvasHeight - verticalGradientsHeight;

        // Bottom gradient end position
        var blackEndYPos = canvasHeight;

        // Create white gradient element
        var white = ctx.createLinearGradient(0, 0, 0, verticalGradientsHeight);

        // Create black gradient element
        var black = ctx.createLinearGradient(0, blackStartYPos, 0, blackEndYPos);

        // Create new instance of image
        var img = new Image();


        // Cache container
        _colorSpectrumContainer = canvasElement.parent();

        // Set global var
        spectrumCanvas = canvasElement;

        // Set width of canvas
        canvas.width = canvasWidth;

        // Set height of canvas
        canvas.height = canvasHeight;

        // Image load listener
        img.onload = function() {

            // Draw intial image
            ctx.drawImage(this, 0, 0, canvasWidth, canvasHeight);

            // Draw white to transparent gradient
            white.addColorStop(0, "hsla(0,0%,100%,1)");
            white.addColorStop(0.05, "hsla(0,0%,100%,1)");
            white.addColorStop(0.20, "hsla(0,0%,100%,0.89)");
            white.addColorStop(0.38, "hsla(0,0%,100%,0.69)");
            white.addColorStop(0.63, "hsla(0,0%,100%,0.35)");
            white.addColorStop(0.78, "hsla(0,0%,100%,0.18)");
            white.addColorStop(0.91, "hsla(0,0%,100%,0.06)");
            white.addColorStop(1, "hsla(0,0%,100%,0)");
            ctx.fillStyle = white;
            ctx.fillRect(0, 0, canvasWidth, verticalGradientsHeight);

            // Draw black to transparent gradient
            black.addColorStop(0, "hsla(0,0%,0%,0)");
            black.addColorStop(0.20, "hsla(0,0%,0%,0.01)");
            black.addColorStop(0.28, "hsla(0,0%,0%,0.04)");
            black.addColorStop(0.35, "hsla(0,0%,0%,0.09)");
            black.addColorStop(0.51, "hsla(0,0%,0%,0.26)");
            black.addColorStop(0.83, "hsla(0,0%,0%,0.69)");
            black.addColorStop(1, "hsla(0,0%,0%,1)");

            ctx.fillStyle = black;
            ctx.fillRect(0, blackStartYPos, canvasWidth, verticalGradientsHeight);
        }

        // Set image source
        img.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAAABCAYAAACbv+HiAAAA0ElEQVR4AYWSh2oDMAwFz6u7//+d2YmXalGBIBM47nnPIIEtmd8FGBTgDbPxDmbn49pX+cZX+Nz4mkZ2SECEAXTCAprlalntBC5whdUJnOfKEy5DjZYtB+o0D3XUMk0tkaZZEn2VuyiJQQQywS/P4c25ucTrfF3ndsoVdjmy3NMiuptR1eHfNcBFM2orW1ZXru00JZiBDrIII5AG5AlloX5TcG6/ywuuv0zAbyL4TWRZmIvU5TNBTjCPIIu5N3YgO7Wxtbot3q4+2LgTyFnZ/QHzBZD1KDpyqQAAAABJRU5ErkJggg==";
    };


    // 
    var hexToRgb = function(hex) {

        hex = hex.replace('#','');

        r = parseInt(hex.substring(0, 2), 16);
        g = parseInt(hex.substring(2, 4), 16);
        b = parseInt(hex.substring(4, 6), 16);

        return [r, g, b];
    };


    // 
    var rgbToHsb = function(r, g, b) {

        var rr, gg, bb,
            r = r / 255,
            g = g / 255,
            b = b / 255,
            h, s,
            v = Math.max(r, g, b),
            diff = v - Math.min(r, g, b),
            diffc = function(c){
                return (v - c) / 6 / diff + 1 / 2;
            };

        if (diff == 0) {
            h = s = 0;
        } else {
            s = diff / v;
            rr = diffc(r);
            gg = diffc(g);
            bb = diffc(b);

            if (r === v) {
                h = bb - gg;
            }else if (g === v) {
                h = (1 / 3) + rr - bb;
            }else if (b === v) {
                h = (2 / 3) + gg - rr;
            }
            if (h < 0) {
                h += 1;
            }else if (h > 1) {
                h -= 1;
            }
        }

        return {
            h: Math.round(h * 360),
            s: Math.round(s * 100),
            b: Math.round(v * 100)
        };
    };


    // Find hue in stop range
    var findHueInStopRange = function(hue) {

        // Array of hue stops with HSV, RGB, and HEX info
        var stops = [{
            h: 0,
            l: 0,
            s: 100,
            b: 100
        }, {
            h: 60,
            l: 21,
            s: 100,
            b: 100
        }, {
            h: 120,
            l: 40,
            s: 85,
            b: 85
        }, {
            h: 180,
            l: 56,
            s: 85,
            b: 85
        }, {
            h: 237,
            l: 72,
            s: 86,
            b: 96
        }, {
            h: 300,
            l: 89,
            s: 86,
            b: 96
        }, {
            h: 359,
            l: 100,
            s: 100,
            b: 100
        }];

        // Total number of stops
        var stopsLength = stops.length;

        // Loop through stops
        for (var i = 0; i < stopsLength; i += 1) {

            // Temp set
            var currentStop = stops[i];

            // Temp set
//             var nextStop = stops[i + 1];
            var nextStop = (i + 1 > stopsLength - 1) ? currentStop : stops[i + 1];

            // Location is a percentage
            var huePos;

            // Temp set
            var xPos = false;

            console.log('hue', currentStop.h, '>>', hue, '<<', nextStop.h);
            // Find which range of hue stops the current color is 
            // Hue is between current and next hue stop
            if (hue >= currentStop.h && hue <= nextStop.h) {

                // hue is current stop
                if (hue === currentStop.h) {

                    // Set as location
                    huePos = currentStop.l;

                    // hue is next stop
                } else if (hue === nextStop.h) {

                    // Set as location
                    huePos = nextStop.l;

                // Hue is somewhere between stops
                } else {

                    // Get percentage location between hue stops
                    var relativeHuePos = (hue - currentStop.h) / (nextStop.h - currentStop.h);

                    // Normalized to fit custom gradient stop locations
                    huePos = relativeHuePos * (nextStop.l - currentStop.l) + currentStop.l;
                }

                // A location was found
                if (huePos) {

                    // Convert from percentage to pixel position
                    xPos = Math.round(containerWidth * (huePos / 100));

                    return xPos;

                } else {

                    continue;
                }
            }
        }
    };


    // Find saturation in stop range
    var findSaturationInStopRange = function (saturation) {

        // Array of hue stops with HSV, RGB, and HEX info
        var stops = [{
            l: 0,
            s: 0
        }, {
            l: 0.05,
            s: 6
        }, {
            l: 0.20,
            s: 18
        }, {
            l: 0.38,
            s: 35
        }, {
            l: 0.63,
            s: 69
        }, {
            l: 0.78,
            s: 89,
        }, {
            l: 0.91,
            s: 100,
        }, {
            l: 1,
            s: 100,
        }];

        // Total number of stops
        var stopsLength = stops.length;

        // Loop through stops
        for (var i = 0; i < stopsLength; i += 1) {

            // Temp set
            var currentStop = stops[i];

            // Temp set
            var nextStop = (i + 1 > stopsLength - 1) ? currentStop : stops[i + 1];

            // Location is a percentage
            var satPos;

            // Temp set
            var yPos = false;

            // Convert location to percentage
            var currentStopLocation = currentStop.l * 100;

            // Convert location to percentage
            var nextStopLocation = nextStop.l * 100;


            // Find which range of hue stops the current color is 
            // Hue is between current and next hue stop
            if (saturation >= currentStop.s && saturation <= nextStop.s) {

                // hue is current stop
                if (saturation === currentStop.s) {

                    // Set as location
                    satPos = currentStopLocation;

                    // hue is next stop
                } else if (saturation === nextStop.s) {

                    // Set as location
                    satPos = nextStopLocation;

                // Hue is somewhere between stops
                } else {

                    // Get percentage location between gradient stops
                    var ratioBetweenSaturation = (saturation - currentStop.s) / (nextStop.s - currentStop.s);

                    // Normalized to fit custom gradient stop locations
                    satPos = ratioBetweenSaturation * (nextStopLocation - currentStopLocation) + currentStopLocation;
                }

                console.log('ratioBetweenSaturation', ratioBetweenSaturation);
                console.log('satPos', satPos);
                console.log('saturation', saturation, '>=', currentStop.s, saturation, '<=', nextStop.s);

                // A location was found
                if (satPos !== false) {

                    // Convert from percentage to pixel position
                    yPos = Math.round(verticalGradientsHeight * (satPos / 100));

                    return yPos;

                } else {

                    continue;
                }
            }
        }
    };


    // Find brightness in stop range
    var findBrightnessInStopRange = function (brightness) {

        // Array of hue stops with HSV, RGB, and HEX info
        var stops = [{
            l: 0,
            b: 100
        }, {
            l: 0.20,
            b: 88
        }, {
            l: 0.28,
            b: 69
        }, {
            l: 0.35,
            b: 26
        }, {
            l: 0.51,
            b: 9
        }, {
            l: 0.83,
            b: 4,
        }, {
            l: 1,
            b: 0,
        }];

        // Total number of stops
        var stopsLength = stops.length;

        // Loop through stops
        for (var i = 0; i < stopsLength; i += 1) {

            // Temp set
            var currentStop = stops[i];

            // Temp set
            var nextStop = (i + 1 > stopsLength - 1) ? currentStop : stops[i + 1];

            // Location is a percentage
            var brightPos;

            // Temp set
            var yPos = false;

            // Convert location to percentage
            var currentStopLocation = currentStop.l * 100;

            // Convert location to percentage
            var nextStopLocation = nextStop.l * 100;

            console.log('brightness', brightness, '>=', currentStop.b, brightness, '<=', nextStop.b);


            // Find which range of hue stops the current color is 
            // Hue is between current and next hue stop
            if (brightness <= currentStop.b && brightness >= nextStop.b) {

                // hue is current stop
                if (brightness === currentStop.b) {

                    // Set as location
                    brightPos = currentStopLocation;

                    // hue is next stop
                } else if (brightness === nextStop.b) {

                    // Set as location
                    brightPos = nextStopLocation;

                // Hue is somewhere between stops
                } else {

                    // Get percentage location between gradient stops
                    var ratioBetweenBrightness = (brightness - currentStop.b) / (nextStop.b - currentStop.b);

                    // Normalized to fit custom gradient stop locations
                    brightPos = ratioBetweenBrightness * (nextStopLocation - currentStopLocation) + currentStopLocation;
                }

                console.log('ratioBetweenBrightness', ratioBetweenBrightness);
                console.log('brightPos', brightPos);
                console.log('brightness', brightness, '>=', currentStop.b, brightness, '<=', nextStop.b);

                // A location was found
                if (brightPos !== false) {

                    // Convert from percentage to pixel position
                    yPos = Math.round(verticalGradientsHeight * (brightPos / 100));

                    return yPos;

                } else {

                    continue;
                }
            }
        }
    };


    // Get coordinates from hue, brightness, saturation
    var getColorCoordinates = function (hex) {

        // Convert hex to rgb
        var rgb = hexToRgb(hex);
        console.log('rgb', rgb);
        // Convert rgb to hsb
        var hsb = rgbToHsb(rgb[0], rgb[1], rgb[2]);
        console.log('hsb', hsb);

        // Set x position to position of hue
        var xPos = findHueInStopRange(hsb.h);
        var yPos = 0;


        //  if 100, get (containerHeight - verticalGradientHeight) + whatever position is set with bottom gradient

        // 

        // Saturation and brightness are both maxed
        if (hsb.s === 100 && hsb.b === 100) {

            // Set y position at center of container
            yPos = containerHeight * 0.5;

        } else {
                console.log('using nothing', hsb.s, hsb.b);

            //
            if (hsb.s < 100) {

                // Saturation y position (upper quadrant)
                yPos = findSaturationInStopRange(hsb.s);
                console.log('using saturation', yPos);

            } else if (hsb.b < 100) {

                // Brightness y position (lower quadrant)
                yPos = findBrightnessInStopRange(hsb.b);
                console.log('using brightness', yPos);
            }
        }

        return { x: xPos, y: yPos };
    }


    // Get hue location
    var position = false;

    // Temp set
    var hex = '42ad40';

    // Draw gradient
    drawColorSpectrum();

    // Find x position
    position = getColorCoordinates(hex); //91ff26

    console.log('location', position);

    // Draw line
    $('.circle').css({
        top: position.y + 'px',
        left: position.x + 'px',
        background: '#' + hex
    });
});

**更新**

我实际上是在 HSV 而不是 HSL 中尝试这样做。除了使用 photoshop 生成平滑渐变外,我没有其他偏好。

此外,我还添加了一个新示例,其中包含我创建的内容。下面的一个投票赞成的答案暗示了如何完成我正在尝试做的事情,但到目前为止我还没有能够成功地做到这一点。

更新的代码会在这个链接,我也更新了上面的代码: http://codepen.io/shelbywhite/pen/EyqPWY?editors=1000

最佳答案

要获取位置,您需要 HSL 值

// global 
var RGB = [0,0,0]; // holds the RGB values 0-255
var LSH = [0,0,0]; // holds the LSH values (note H is normalised to 0-255)
var rgbToLSH = function(){
    var r = RGB[0]/255;
    var g = RGB[1]/255;
    var b = RGB[2]/255;
    var min = Math.min(r,g,b);
    var max = Math.max(r,g,b);
    var lum = (min+max)/2;
    if(lum > 0.5){
        var sat = (max-min)/(max+min);
    }else{
        var sat = (max-min)/(2-max-min);
    }
    if(r >= b && r >= g){
        var hue = (g-b)/(max-min);
    }else
    if(b >= b && b >= g){
        var hue = 4.0 + (r-g)/(max-min);
    }else{
        var hue = 2.0 + (b-r)/(max-min);
    }
    hue *= 60;
    if(hue < 0) hue += 360;
    hue = (hue/360);
    lum = Math.min(1,Math.max(0,lum));
    sat = Math.min(1,Math.max(0,sat));
    hue = Math.min(1,Math.max(0,hue));
    LSH[0] = lum*255;
    LSH[1] = sat*255;
    LSH[2] = hue*255;

}

Hue 会给出 x 轴上的位置,Saturation 会给出从顶部到中间的 y 轴,Lightness 会给出从中间到底部的 y 轴位置(以你的例子为例);

关于javascript - 使用 Javascript 基于十六进制颜色计算渐变上的 X、Y 像素位置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39171824/

相关文章:

javascript - typescript Canvas

algorithm - 为调色板生成一组颜色的算法?

android - 从 R.color 中以编程方式检索颜色

android - 如何在 android 的布局上应用滤色器\覆盖?

javascript - 在 Angular 中使用 Jasmine 使用 *ngIf 指令时,如何对元素是否可见进行单元测试

javascript - Fabricjs:保持一个对象可编辑(其他对象锁定)直到以编程方式发布

javascript - 使用 JQuery/Javascript 将文本插入到 html div 表的特定单元格中?

javascript - Canvas - 在较大的背景图像上叠加小图像,合并为单个 Canvas 像素数据

javascript - 用正则表达式替换字符串中的随机数

javascript - 在 Powershell 中在此对象上找不到属性 'value'