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入那就一样了)
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 2020-10-23