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

时间:2016-08-26 17:33:11

标签: javascript canvas colors

我有一个raindow HSV渐变画布,当你点击它时,会在该位置添加一个元素,其背景为所点击像素的颜色。

我想要的是让它反向运作。例如,如果你有一个十六进制颜色,我想在画布上找到那个像素并在那个位置创建一个元素。

我的第一个想法是以某种方式使用矩阵/象限系统。我的下一个想法是,因为我使用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

2 个答案:

答案 0 :(得分:3)

要获取您需要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轴上的位置,饱和度会给你从上到中的y,而Lightness会给你y轴从中到下的位置(以你的例子为准);

答案 1 :(得分:1)

您可以遍历整个ImageData并比较两种颜色。

var findPixelByHex = function(imageData, hex) {
  var d = imageData.data
  var w = imageData.width
  var h = imageData.height

  for (var y = 0; y < h; ++y) {
    for (var x = 0; x < w; ++x) {
      var i = (y * w + x) * 4
      if (hex === rgbToHex(d[i], d[i + 1], d[i + 2])) {
        setColorAtPixel(x, y, hex)
      }
    }
  }
}

请注意,这非常慢,不是最好的主意。