JavaScript HSL拾色器

HSL 和 HSV 在數學上定義爲在 RGB 空間中的顏色的 R, G 和 B 的座標的變換。

從 RGB 到 HSL 或 HSV 的轉換

設 (r, g, b) 分別是一個顏色的紅、綠和藍座標,它們的值是在 0 到 1 之間的實數。設 max 等價於 r, g 和 b 中的最大者。設 min 等於這些值中的最小者。要找到在 HSL 空間中的 (h, s, l) 值,這裏的 h ∈ [0, 360)是角度的色相角,而 s, l ∈ [0,1] 是飽和度和亮度,計算爲:

這裏寫圖片描述

h 的值通常規範化到位於 0 到 360°之間。而 h = 0 用於 max = min 的(就是灰色)時候而不是留下 h 未定義。
HSL 和 HSV 有同樣的色相定義,但是其他分量不同。HSV 顏色的 s 和 v 的值定義如下:

這裏寫圖片描述

從 HSL 到 RGB 的轉換

給定 HSL 空間中的 (h, s, l) 值定義的一個顏色,帶有 h 在指示色相角度的值域 [0, 360)中,分別表示飽和度和亮度的s 和 l 在值域 [0, 1] 中,相應在 RGB 空間中的 (r, g, b) 三原色,帶有分別對應於紅色、綠色和藍色的 r, g 和 b 也在值域 [0, 1] 中,它們可計算爲:
首先,如果 s = 0,則結果的顏色是非彩色的、或灰色的。在這個特殊情況,r, g 和 b 都等於 l。注意 h 的值在這種情況下是未定義的。
當 s ≠ 0 的時候,可以使用下列過程:

這裏寫圖片描述

對於每個顏色向量 Color = (ColorR, ColorG, ColorB) = (r, g, b),

這裏寫圖片描述

從 HSV 到 RGB 的轉換

類似的,給定在 HSV 中 (h, s, v) 值定義的一個顏色,帶有如上的 h,和分別表示飽和度和明度的 s 和 v 變化於 0 到 1 之間,在 RGB 空間中對應的 (r, g, b) 三原色可以計算爲:

這裏寫圖片描述

對於每個顏色向量 (r, g, b),

這裏寫圖片描述

<html>
    <style>
        .childDiv {
            display:inline-block;   
            vertical-align:middle;
            margin-left: 30px;
            margin-top: 10px;
            margin-bottom: 10px;
        }
        #colorPickDiv {
            background-color: WhiteSmoke;
            border: 1px solid LightGrey;
            padding-top: 20px;
            padding-bottom: 20px;
            padding-right: 30px;
        }
        #hueTipDiv {
            margin: 0 0 10px 70px;
        }
        #luminanceTipDiv {
            margin-top: 20px
        }
        #colorDiv {
            width: 100px;
            height: 100px;
            background-color: black;
        }
        #valueDiv {
            box-shadow: 0px -5px 10px LightGrey;
            background-color: WhiteSmoke;
            border: 1px solid LightGrey;
            border-top-width: 0;
            padding-right: 10px;
            padding-bottom: 10px;
        }
    </style>
    <body>
        <div>
            <div id="colorPickDiv">
                <div class="childDiv">
                    <div id="hueTipDiv">Hue:0</div>
                    <canvas id="canvas" width="200" height="200">Your browser does not support canvas</canvas>
                </div>
                <div class="childDiv">
                    <div id="saturationTipDiv" class="divMarginBottom">Saturation:0%</div>
                    <input id="saturationRange" onChange="onHSLRangeChange()" type="range" min="0" max="100" step="1" value="100"/>
                    <div id="luminanceTipDiv" class="divMarginBottom">Luminance:0%</div>
                    <input id="luminanceRange" onChange="onHSLRangeChange()" type="range" min="0" max="100" step="1" value="50"/>
                </div>
                <div id="colorDiv" class="childDiv"></div>
            </div>
            <div id="valueDiv">
                <div class="childDiv">
                    <div id="hexadecimalTipDiv" class="divMarginBottom">Hexadecimal:</div>
                    <input id="hexadecimalValueDiv" type="text" disabled="disabled"/>
                </div>
                <div class="childDiv">
                    <div id="rgbTipDiv" class="divMarginBottom">RGB:</div>
                    <input id="rgbValueDiv" type="text" readonly="readonly"/>
                </div>
                <div class="childDiv">
                    <div id="hslTipDiv" class="divMarginBottom">HSL:</div>
                    <input id="hslValueDiv" type="text" readonly="readonly"/>
                </div>
            </div>
        </div>
        <script>
            var c = document.getElementById("canvas");
            var ctx = c.getContext("2d");
            var colorDiv = document.getElementById("colorDiv");
            var hexadecimalValueDiv = document.getElementById("hexadecimalValueDiv");
            var rgbValueDiv = document.getElementById("rgbValueDiv");
            var hslValueDiv = document.getElementById("hslValueDiv");
            var hexadecimalTipDiv = document.getElementById("hexadecimalTipDiv");
            var saturationTipDiv = document.getElementById("saturationTipDiv");
            var saturationRange = document.getElementById("saturationRange");
            var luminanceTipDiv = document.getElementById("luminanceTipDiv");
            var luminanceRange = document.getElementById("luminanceRange");

            //十字光標顏色
            var crossCursorColor = "black";
            //十字光標線寬
            var crossCursorLineWidth = 2;
            //十字光標某一邊線段長
            var crossCursorHalfLineLen = 5;
            //十字光標中間斷裂處長度
            var crossCursorHalfBreakLineLen = 2;

            //畫布中心點X座標
            var centerX = c.width / 2;
            //畫布中心點Y座標
            var centerY = c.height / 2;
            //縮放繪製比例
            var scaleRate = 10;
            //畫布的內切圓半徑(之所以減去一個數是爲了可以顯示完整的十字光標)
            var innerRadius = Math.min(centerX, centerY) - crossCursorHalfLineLen - crossCursorHalfBreakLineLen;
            //內切圓半徑的平方
            var pow2InnerRadius = Math.pow(innerRadius, 2);
            //縮放繪製時的繪製半徑,即畫布的外徑除以縮放比例
            var scaledRadius = Math.sqrt(Math.pow(c.width / 2, 2) + Math.pow(c.height / 2, 2)) / scaleRate;
            //由於該圓是由繞圓心的多條線段組成,該值表示將圓分割的份數
            var count = 360;
            //一整個圓的弧度值
            var doublePI = Math.PI * 2;
            //由於圓心處是多條線段的交匯點,Composite是source-over模式,所以後繪製的線段會覆蓋前一個線段。另外由於採用線段模擬圓,英雌
            var deprecatedRadius = innerRadius * 0.3;
            //廢棄圓半徑的平方
            var pow2DeprecatedRadius = Math.pow(deprecatedRadius, 2);

            //色相(0-360)
            var hue;
            //飽和度(0%-100%)
            var saturation; 
            //亮度luminance或明度lightness(0%-100%)
            var luminance;

            //當前色相位置X座標
            var currentHuePosX = centerX + innerRadius - 1;
            //當前色相位置Y座標
            var currentHuePosY = centerY;

            //填充圓
            function fillCircle(cx, cy, r, color) {
                ctx.fillStyle = color;
                ctx.beginPath();
                ctx.arc(cx, cy, r, 0, doublePI);
                ctx.fill();
            }

            //繪製線條
            function strokeLine(x1, y1, x2, y2) {
                ctx.beginPath();
                ctx.moveTo(x1, y1);
                ctx.lineTo(x2, y2);
                ctx.stroke();
            }

            //將整數轉爲16進制,至少保留2位
            function toHexString(intValue) {
                var str = intValue.toString(16);
                if(str.length == 1) {
                    str = "0" + str;
                }
                return str;
            }

            //判斷座標(x,y)是否在合法的區域內
            function isInValidRange(x, y) {
                var pow2Distance = Math.pow(x-centerX, 2) + Math.pow(y-centerY, 2);
                return pow2Distance >= pow2DeprecatedRadius && pow2Distance <= pow2InnerRadius;
            }

            //繪製十字光標
            function strokeCrossCursor(x, y) {
                ctx.globalCompositeOperation = "source-over";
                ctx.strokeColor = crossCursorColor;
                ctx.lineWidth = crossCursorLineWidth;
                strokeLine(x, y-crossCursorHalfBreakLineLen, x, y-crossCursorHalfBreakLineLen-crossCursorHalfLineLen);
                strokeLine(x, y+crossCursorHalfBreakLineLen, x, y+crossCursorHalfBreakLineLen+crossCursorHalfLineLen);
                strokeLine(x-crossCursorHalfBreakLineLen, y, x-crossCursorHalfBreakLineLen-crossCursorHalfLineLen, y);
                strokeLine(x+crossCursorHalfBreakLineLen, y, x+crossCursorHalfBreakLineLen+crossCursorHalfLineLen, y);
            }

            //將對象中的hsl分量組成一個hsl顏色(h在0到360之間,s與l均在0到1之間)
            function formHslColor(obj) {
                return "hsl(" + obj.h + "," + Math.round(obj.s * 1000)/10 + "%," + Math.round(obj.l * 1000)/10 + "%)"; 
            }

            //將對象中的rgb分量組成一個rgb顏色(r,g,b在0到255之間)
            function formRgbColor(obj) {
                return "rgb(" + [obj.r, obj.g, obj.b].join(",") + ")";
            }

            //從畫布的某點獲取存儲RGB的對象
            function getRgbObj(x, y) {
                var w = 1;
                var h = 1;
                var imgData = ctx.getImageData(x,y,w,h);
                var obj = {
                    r: imgData.data[0],
                    g: imgData.data[1],
                    b: imgData.data[2],
                    a: imgData.data[3]
                }
                return obj;
            }

            //將rgb轉換爲hsl對象()
            function rgbToHslObj(r, g, b) {
                r /= 255;
                g /= 255;
                b /= 255;
                var max = Math.max(r, g, b);
                var min = Math.min(r, g, b);
                var diff = max - min;
                var twoValue = max + min;
                var obj = {h:0, s:0, l:0};
                if(max == min) {
                    obj.h = 0;
                } else if(max == r && g >= b) {
                    obj.h = 60 * (g - b) / diff;
                } else if(max == r && g < b) {
                    obj.h = 60 * (g - b) / diff + 360;
                } else if(max == g) {
                    obj.h = 60 * (b - r) / diff + 120;
                } else if(max == b) {
                    obj.h = 60 * (r - g) / diff + 240;
                }
                obj.l = twoValue / 2;
                if(obj.l == 0 || max == min) {
                    obj.s = 0;
                } else if(0 < obj.l && obj.l <= 0.5) {
                    obj.s = diff / twoValue;
                    //obj.s = diff / (2 * obj.l);
                } else {
                    obj.s = diff / (2 - twoValue);
                    //obj.s = diff / (2 - 2 * obj.l);
                }
                obj.h = Math.round(obj.h);
                return obj;
            }

            //創建Hue顏色圓環
            function createHueRing() {
                ctx.globalCompositeOperation = "source-over";
                ctx.clearRect(0,0,c.width,c.height);
                ctx.save();
                //將繪製原點移動到畫布中心
                ctx.translate(centerX, centerY);
                //將畫布放大相應比例,restore後,繪製內容會縮小
                ctx.scale(scaleRate, scaleRate);
                for(var i=0; i<count; i++) {
                    var degree = i / count * 360;
                    var radian = Math.PI * degree / 180;
                    var x = scaledRadius * Math.cos(radian);
                    var y = scaledRadius * Math.sin(radian);
                    ctx.lineWidth=1;
                    ctx.strokeStyle = "hsl(" + degree +"," + saturation + "," + luminance + ")";
                    ctx.beginPath();
                    ctx.moveTo(x, y);
                    ctx.lineTo(0,0);
                    ctx.stroke();
                }
                ctx.restore();
                ctx.globalCompositeOperation = "destination-out";
                fillCircle(centerX, centerY, deprecatedRadius, "black");

                ctx.globalCompositeOperation = "destination-in";
                fillCircle(centerX, centerY, innerRadius, "black");
            }

            //點擊canvas中的Hue拾色圈
            function onCanvasClick() {
                var x = event.offsetX;
                var y = event.offsetY;
                if(!isInValidRange(x, y)) {
                    return;
                }
                currentHuePosX = x;
                currentHuePosY = y;
                //創建hue背景圓環
                createHueRing();
                setColorValue(x, y);
                strokeCrossCursor(x, y);
            }

            function setColorValue(x, y) {
                //獲取包含rgb的顏色對象
                var rgbObj = getRgbObj(x, y);
                var rgbColor = formRgbColor(rgbObj);
                colorDiv.style.backgroundColor = rgbColor;
                rgbValueDiv.value = rgbColor;
                var hex = "#" + toHexString(rgbObj.r) + toHexString(rgbObj.g) + toHexString(rgbObj.b);
                hexadecimalValueDiv.value = hex;

                var hslObj = rgbToHslObj(rgbObj.r, rgbObj.g, rgbObj.b);
                hslValueDiv.value = formHslColor(hslObj);
                hueTipDiv.innerHTML = ("Hue:" + hslObj.h);
            }

            function onHSLRangeChange() {
                //event.target.value;
                saturation = saturationRange.value + "%";
                luminance = luminanceRange.value + "%";
                saturationTipDiv.innerHTML = ("Saturation:" + saturation);
                luminanceTipDiv.innerHTML = ("Luminance:" + luminance);
                createHueRing();
                setColorValue(currentHuePosX, currentHuePosY)
                strokeCrossCursor(currentHuePosX, currentHuePosY);
            }

            function init() {
                c.addEventListener("click", onCanvasClick);
                onHSLRangeChange();
            }

            init();

        </script>
    </body>
</html>

這裏寫圖片描述

這裏寫圖片描述

有幾個缺陷:
- 不能根據顏色值來設置HSL。
- 由於HSL的值是根據從Hue環形調色板中取出的RGB顏色值換算爲HSL的,因此跟滑動條上的值可能會有出入(如果是5舍6入那就一樣了)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章