1

SOLVED! see EDITs

I built a color picker app. Very simple; you click on the rgb color palette, and it creates a swatch with RGB values, HSL values, and HEX values.
I used this formula for the conversions.

Basically, I built my HSL values from the x and y mouse positions with a static 99% saturation.

From there, I created the color palette to pick from by converting HSL to RGB.

A click event on the palette will create RGB, HSL, and HEX swatches along with the values for each.

Since I can't get the RGB and HSL values to match I haven't incorporated the HEX values yet.

I finally found a working solution. Can someone tell me where my calculations drift away from the working solution? I don't just want to accept the working solution and move on; I want to know where my logic starts to break down.

Thanks so much for any help!

EDIT: So Bob__ suggested I use a better way to normalize my RGB values instead of just adding or subtracting 1 depending on their values. I added this function to make the RGB channels based on the hue value(-0.333 - 360.333)

      function normalizeRGB(color){
         var newVal = (360.333 - (-0.333))/(360.333 - (-0.333)) *   
                      (color- (-0.333) + (-0.333));
        return newVal;
      }

index.html:

            <!DOCTYPE html>
            <html lang="en">
            <head>
                <meta charset="UTF-8">
                <title>Color Picker, Bro</title>
                <link href='http://fonts.googleapis.com/css?  family=Raleway:700,300' rel='stylesheet' type='text/css'>
                <link rel="stylesheet" href="style.css">
                <script src='https://code.jquery.com/jquery-2.1.3.min.js'></script>
                <script type="text/javascript" src='myColorPickerSol.js'></script>
                <!-- <script type="text/javascript" src='actualWorkingColorPickerSol.js'></script> -->
            </head>
            <body>

                <div id='container'>
                    <h1>Color Picker, bro</h1>
                    <section id='canvas'></section>
                        <section id='readout'>
                            <p>HSL: <span id='hsl'></span></p>
                            <section id='swatchhsl'></section>
                            <p>RGB: <span id='rgb'></span></p>
                            <section id='swatchrgb'></section>
                            <p>HEX: <span id='hex'></span></p>
                        </section>
                    </section>
                </div>

            </body>
            </html>

style.css:

             body {
                      background:   url(http://subtlepatterns.com/patterns/subtle_white_mini_waves.png);
             }

             #container {
                margin: 0 auto;
                width: 800px;
                height: inherit;
                text-align: center;
                font-family: Raleway;
                font-weight: 300;
            }
            #canvas {
                margin: 0 auto;
                border: 5px solid black;
                box-sizing: border-box;
                height: 360px;
                width: 360px;
            }
            #readout {
                background: rgba(117,117,117, .2);
                margin: 20px auto;
                height: 400px;
                width: 360px;
                border: 1px #333 solid;
                box-sizing: border-box;
                border-radius: 20px;
            }
            #swatchhsl,#swatchrgb {
                margin: 0 auto;
                height: 75px;
                width: 95%;
                border-radius: 20px;
            }
            p, span {
                letter-spacing: 1px;
            }
            p {
                font-weight: 700;
            }

            span {
                font-weight: 300;
            }

myColorPickerSol.js

     $(document).ready(function(){
    var canvas = $('#canvas');
  //swatch matches closest when either pure blue, green or red; loses all accuracy when colors mix.
  // dark blue gets really close. Purple gets really close, which makes me suspect the Green channel value is where the problem lies.

    // y-axis as Luminace(0-100%)
    // x-axis as Hue(0-360)


    var yPos;
    var lum;
    var hue;// aka xPos;

    var temp1;//for hslToRGB
    var temp2;//for hslToRGB
    var tempR;
    var tempG;
    var tempB;

    var red;
    var blue;
    var green;
    var realColVal;

    $('#canvas').mousemove(function(event){
      hue = Math.abs(event.offsetX);

      hueForRGB = (hue/360);

      yPos = Math.abs(event.offsetY);

      lum = (yPos/360);
      // console.log(lum + ' lum');

      $(canvas).css({'background-color':'hsl('+ event.offsetX + ',99%,'+ Math.round(lum *100) + '%)'});





    });
  // swatch listener
    $(canvas).click(function(event){

      hsl2RGB(lum);

      $('#rgb').text(red + ','+ green + ',' + blue);
      $('#hsl').text(hue + ',99%,' + Math.round(lum * 100) + '%');
      $(canvas).css({'background-color':'rgb('+ red + ','+ green + ','+ blue + ')'});
    });


  //red channel must be in upper third; green in middle third; blue in lower third.
    function hsl2RGB(lum){

      tempR = (hueForRGB + 0.333);
      tempG = hueForRGB;
      tempB = (hueForRGB - 0.333);
    // set temporary lum based on whether it is above/below 50%
      temp1 = lumMorOrLess50(lum);

  // set secondary temporary lum value
      temp2 = ((2.0 * (lum)) - temp1);

 //-----------EDIT ----------------------------- 
 // used the formula to make the tempR|G|B values between 0 and 1
 // tempR = makeRGB01(tempR);
//  tempG = makeRGB01(tempG);
//  tempB = makeRGB01(tempB);  
 //-----------------------------------------------

      red   = Math.round(convert2RGB(tempR,temp1,temp2));
      green = Math.round(convert2RGB(tempG,temp1,temp2));
      blue  = Math.round(convert2RGB(tempB,temp1,temp2));

  //swatch appears on click for hsl and rgb
      $('#swatchhsl').css({'background-color':'hsl('+ hue + ',99%,'+ Math.round(lum * 100 )+ '%)'});
      $('#swatchrgb').css({'background-color':'rgb('+ red + ','+ green + ','+ blue + ')'});

    };

    //force tempR|G|B to be between 0-1
    function makeRGB01(input) {
      if(input > 1){
        input -= 1.0;
      } else if(input < 0){
        input += 1.0;
      };
      return input;
    };

    //get value for each rgb channel
    function convert2RGB(tempColVal, val1, val2){
       //first convert tempColVal to between 0 and 1 then make it an RGB value
       tempColVal = makeRGB01(tempColVal);

      //next run 3 test;
        if(6.0 * tempColVal < 1){
          realColVal = (val2 + (val1 - val2) * 6 * tempColVal);
          console.log(realColVal + 'test 1; val1: '+ val1 + 'val2: ' + val2  );
       //-------EDIT ------------------------------------------
       // test2 will set realColVal to val1 instead of tempColVal
       //-------------------------------------------------------
        } else if(2.0 * tempColVal < 1){
          realColVal = val1;
          console.log(realColVal + 'test 2');
        } else if(3.0 * tempColVal < 2){
          realColVal = (val2 + (val1 - val2)*(0.666 - tempColVal) * 6.0);
          console.log(realColVal + 'test 3');
        } else {
          realColVal = val2;
          console.log(realColVal + 'realColVal = default (temp 2)');
        };

       //-------EDIT ------------------------------------------
       // normalize value before multiplying by 255

       realColVal = normalizeRGB(realColVal); 
       //-------------------------------------------------------
      // force value between 0 and 1 then set it to RGB scale and    
      // return
      return  (Math.abs(realColVal) * 255.0));

    };


    //configure temporary luminance value, temp1,  based on luminance
    function lumMorOrLess50(val){
      if(val < 0.50){
        return ((1.0 + 0.99) * val);
      } else {
        return ((.99 + val) - (val * .99));
      };
    };



  });

This is the working solution, actualColorPickerSol.js What did I do differently?

            $(function() {
                console.log('Loaded, bro');
                colorPicker();
            });

            function colorPicker() {
                var canvas = $('#canvas');
                canvas.on('mousemove', changeCanvasBackground);
                canvas.on('click', printColorReadout);
            }

            function changeCanvasBackground(event) {
                var xCoord = Math.abs(event.offsetX);
                var yCoord = Math.abs(event.offsetY);
                var rgbValues = 'rgb(' + rgb(hsl(xCoord, yCoord)) + ')';
                $(this).css('background', rgbValues);
            }

            function printColorReadout(event) {
                var xCoord = event.offsetX;
                var yCoord = event.offsetY;
                var hslValues = hsl(xCoord, yCoord);
                var rgbValues = rgb(hslValues);
                var hexValues = hex(rgbValues);

                var hslString = parseHSL(hslValues);
                var rgbString = parseRGB(rgbValues);
                var hexString = parseHEX(hexValues);

                $('#hsl').text(hslString);
                $('#rgb').text(rgbString);
                $('#hex').text(hexString);
                $('#swatchhsl').css('background', hslString);
                $('#swatchrgb').css('background', rgbString);
            }

            function hsl(xCoord, yCoord) {
                // HSL = hsl(hue, saturation, luminance)
                var hsl;
                var hue = xCoord;
                var luminance = Math.round(((yCoord / 360) * 100));

                return [hue, 100, luminance];
            }

            function rgb(hslValues) {
                var hue = hslValues[0];
                var sat = hslValues[1] / 100;
                var lum = hslValues[2] / 100;
                var tempLum1, tempLum2, tempHue, tempR, tempG, tempB;

                if (lum < .50) {
                    tempLum1 = lum * (1 + sat);
                } else {
                    tempLum1 = (lum + sat) - (lum * sat);
                }

                tempLum2 = (2 * lum) - tempLum1;
                tempHue = hue / 360;

                tempR = tempHue + .333;
                tempG = tempHue;
                tempB = tempHue - .333;

             //This is the only part I think I did differently. 
             //The code below makes sure the green and blue values 
             //are between 0 and 1, then it checks all the colors to 
             //make sure they are between 0 and 1.  I tried this, 
            // and there was no change in the effect; 
           // the hsl and rgb values were still different.

                if (tempG < 0) { tempG += 1};
                if (tempG > 1) { tempG -= 1};
                if (tempB < 0) { tempB += 1};
                if (tempB > 1) { tempB -= 1};

                var normalizedRGB = [tempR, tempG, tempB].map(function(color, idx) {
                    if (color < 0) { return color += 1};
                    if (color > 1) { return color -= 1};
                    return color;
                });

                var rgbArray = normalizedRGB.map(function(color) {
                    if (colorCondition1(color)) {
                        return tempLum2 + ( tempLum1 - tempLum2 ) * 6 * color;
                    } else if (colorCondition2(color)) {
                        return tempLum1;
                    } else if (colorCondition3(color)) {
                        return tempLum2 + (tempLum1 - tempLum2) * (.666 - color) * 6;
                    }   else {
                        return tempLum2;
                    }
                });

                var rgbValues = rgbArray.map(function(color, idx) {
                    var convertedVal = color * 255;
                    return Math.round(convertedVal);
                });

                return rgbValues;
            }

            function hex(rgbValues) {
                var r = rgbValues[0];
                var g = rgbValues[1];
                var b = rgbValues[2];

                return [numToHex(r), numToHex(g), numToHex(b)];
            }

            function numToHex(num) {
                var hexCode = num.toString(16);
                if (hexCode.length < 2) { hexCode = "0" + hexCode; }
                return hexCode;
            }

            function colorCondition1(val) {
                return 6 * val < 1;
            }

            function colorCondition2(val) {
                return 2 * val < 1;
            }

            function colorCondition3(val) {
                return 3 * val < 2;
            }

            function parseHSL(hslValues) {
                return [
                    "hsl(",
                    hslValues[0], ", ",
                    hslValues[1], "%, ",
                    hslValues[2], "%)"
                ].join('');
            }

            function parseRGB(rgbValues) {
                return "rgb(" + rgbValues.join(', ') + ")";
            }

            function parseHEX(hexValues) {
                return "#" + hexValues.join('');
            }
BenTheHumanMan
  • 327
  • 2
  • 11
  • When you "force" RGB values to be in the range 0-1 you should really normalize them, transforming their range from (min,max) to (0,1), not applying that sort of shift you do – Bob__ Dec 16 '15 at 16:35
  • @Bob__ thanks. So you're saying that I should not use something like ------ ` newvalue= (max'-min')/(max-min)*(value-min)+min' ` ---------- instead of adding or subtracting 1 depending on the value? – BenTheHumanMan Dec 16 '15 at 16:45
  • I'm reading the link you posted and it actually uses your method after calculating tempR, tempG, temp R. In your working solution you do that twice for G and B and once for R. – Bob__ Dec 16 '15 at 16:49
  • In `convert2RGB` the case 2*tCV < 1 should be rCV = val1 – Bob__ Dec 16 '15 at 16:55
  • and make that 6 a 6.0 just in case... – Bob__ Dec 16 '15 at 16:56
  • Is the `return (makeRGB01(realColVal) * 255.0);` that should be a real normalization to 0-255 – Bob__ Dec 16 '15 at 16:59
  • @Bob__ holy cow! it's almost perfect now that I added your change to convert2RGB. like most of the time it matches. Thanks! I'll keep tweaking it. – BenTheHumanMan Dec 16 '15 at 17:02
  • You should keep your `makeRGB01()` function to control the values of `tempR`, `tempG`, `tempB` before calling `convert2RGB()` as in the algorythm posted. It is the return value of that function that must be "normalized" to 0-255. They do that by simply multiplying by 255. – Bob__ Dec 16 '15 at 19:23

1 Answers1

0

Comparing your functions to the algorythm presented in the link you posted, I think that a more correct implementation of convert2RGB, as I told in my comments, may be:

function convert2RGB(tempColVal, val1, val2){
   //first convert tempColVal to between 0 and 1 then make it an RGB value
   tempColVal = makeRGB01(tempColVal);

  //next run 3 test;
    if(6.0 * tempColVal < 1){
      realColVal = (val2 + (val1 - val2) * 6.0 * tempColVal);
  //    console.log(realColVal + 'test 1; val1: '+ val1 + 'val2: ' + val2  );
    } else if(2.0 * tempColVal < 1){
      realColVal = val1;
  //    console.log(realColVal + 'test 2');
    } else if(3.0 * tempColVal < 2){
      realColVal = (val2 + (val1 - val2)*(0.666 - tempColVal) * 6.0);
  //    console.log(realColVal + 'test 3');
    } else {
      realColVal = val2;
  //    console.log(realColVal + 'realColVal = default (temp 2)');
    };
      // Convert them to 8-bit by multiply them with 255 and return
  return  Math.round(realColVal * 255.0);

};

Edit: Don't change your makeRGB01(), in that algorythm is used in the same way you did before calling convert2RGB(). Your mistake is applying that to returned corrected value too.

Bob__
  • 12,361
  • 3
  • 28
  • 42
  • I made those changes, and the accuracy is perfect until the HSL value gets around 360, 99%, 55%(reddish) and the RGB outputs around 27, 27, 48 (navy blueish). But it looks loads better. I wish I knew why. Thanks again and again! – BenTheHumanMan Dec 16 '15 at 18:33