Initial attempt to implement HUSL color space as an interpolator.
Last active
August 29, 2015 14:23
-
-
Save yinshanyang/e90f8830c48f07a2085c to your computer and use it in GitHub Desktop.
D3 HUSL Interpolator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){(function(){var L_to_Y,Y_to_L,conv,distanceFromPole,dotProduct,epsilon,fromLinear,getBounds,intersectLineLine,kappa,lengthOfRayUntilIntersect,m,m_inv,maxChromaForLH,maxSafeChromaForL,refU,refV,refX,refY,refZ,rgbPrepare,root,round,toLinear;m={R:[3.240969941904521,-1.537383177570093,-.498610760293],G:[-.96924363628087,1.87596750150772,.041555057407175],B:[.055630079696993,-.20397695888897,1.056971514242878]};m_inv={X:[.41239079926595,.35758433938387,.18048078840183],Y:[.21263900587151,.71516867876775,.072192315360733],Z:[.019330818715591,.11919477979462,.95053215224966]};refX=.95045592705167;refY=1;refZ=1.089057750759878;refU=.19783000664283;refV=.46831999493879;kappa=903.2962962;epsilon=.0088564516;getBounds=function(L){var bottom,channel,j,k,len1,len2,m1,m2,m3,ref,ref1,ref2,ret,sub1,sub2,t,top1,top2;sub1=Math.pow(L+16,3)/1560896;sub2=sub1>epsilon?sub1:L/kappa;ret=[];ref=["R","G","B"];for(j=0,len1=ref.length;j<len1;j++){channel=ref[j];ref1=m[channel],m1=ref1[0],m2=ref1[1],m3=ref1[2];ref2=[0,1];for(k=0,len2=ref2.length;k<len2;k++){t=ref2[k];top1=(284517*m1-94839*m3)*sub2;top2=(838422*m3+769860*m2+731718*m1)*L*sub2-769860*t*L;bottom=(632260*m3-126452*m2)*sub2+126452*t;ret.push([top1/bottom,top2/bottom])}}return ret};intersectLineLine=function(line1,line2){return(line1[1]-line2[1])/(line2[0]-line1[0])};distanceFromPole=function(point){return Math.sqrt(Math.pow(point[0],2)+Math.pow(point[1],2))};lengthOfRayUntilIntersect=function(theta,line){var b1,len,m1;m1=line[0],b1=line[1];len=b1/(Math.sin(theta)-m1*Math.cos(theta));if(len<0){return null}return len};maxSafeChromaForL=function(L){var b1,j,len1,lengths,m1,ref,ref1,x;lengths=[];ref=getBounds(L);for(j=0,len1=ref.length;j<len1;j++){ref1=ref[j],m1=ref1[0],b1=ref1[1];x=intersectLineLine([m1,b1],[-1/m1,0]);lengths.push(distanceFromPole([x,b1+x*m1]))}return Math.min.apply(Math,lengths)};maxChromaForLH=function(L,H){var hrad,j,l,len1,lengths,line,ref;hrad=H/360*Math.PI*2;lengths=[];ref=getBounds(L);for(j=0,len1=ref.length;j<len1;j++){line=ref[j];l=lengthOfRayUntilIntersect(hrad,line);if(l!==null){lengths.push(l)}}return Math.min.apply(Math,lengths)};dotProduct=function(a,b){var i,j,ref,ret;ret=0;for(i=j=0,ref=a.length-1;0<=ref?j<=ref:j>=ref;i=0<=ref?++j:--j){ret+=a[i]*b[i]}return ret};round=function(num,places){var n;n=Math.pow(10,places);return Math.round(num*n)/n};fromLinear=function(c){if(c<=.0031308){return 12.92*c}else{return 1.055*Math.pow(c,1/2.4)-.055}};toLinear=function(c){var a;a=.055;if(c>.04045){return Math.pow((c+a)/(1+a),2.4)}else{return c/12.92}};rgbPrepare=function(tuple){var ch,j,k,len1,len2,n,results;tuple=function(){var j,len1,results;results=[];for(j=0,len1=tuple.length;j<len1;j++){n=tuple[j];results.push(round(n,3))}return results}();for(j=0,len1=tuple.length;j<len1;j++){ch=tuple[j];if(ch<-1e-4||ch>1.0001){throw new Error("Illegal rgb value: "+ch)}if(ch<0){ch=0}if(ch>1){ch=1}}results=[];for(k=0,len2=tuple.length;k<len2;k++){ch=tuple[k];results.push(Math.round(ch*255))}return results};conv={xyz:{},luv:{},lch:{},husl:{},huslp:{},rgb:{},hex:{}};conv.xyz.rgb=function(tuple){var B,G,R;R=fromLinear(dotProduct(m.R,tuple));G=fromLinear(dotProduct(m.G,tuple));B=fromLinear(dotProduct(m.B,tuple));return[R,G,B]};conv.rgb.xyz=function(tuple){var B,G,R,X,Y,Z,rgbl;R=tuple[0],G=tuple[1],B=tuple[2];rgbl=[toLinear(R),toLinear(G),toLinear(B)];X=dotProduct(m_inv.X,rgbl);Y=dotProduct(m_inv.Y,rgbl);Z=dotProduct(m_inv.Z,rgbl);return[X,Y,Z]};Y_to_L=function(Y){if(Y<=epsilon){return Y/refY*kappa}else{return 116*Math.pow(Y/refY,1/3)-16}};L_to_Y=function(L){if(L<=8){return refY*L/kappa}else{return refY*Math.pow((L+16)/116,3)}};conv.xyz.luv=function(tuple){var L,U,V,X,Y,Z,varU,varV;X=tuple[0],Y=tuple[1],Z=tuple[2];varU=4*X/(X+15*Y+3*Z);varV=9*Y/(X+15*Y+3*Z);L=Y_to_L(Y);if(L===0){return[0,0,0]}U=13*L*(varU-refU);V=13*L*(varV-refV);return[L,U,V]};conv.luv.xyz=function(tuple){var L,U,V,X,Y,Z,varU,varV;L=tuple[0],U=tuple[1],V=tuple[2];if(L===0){return[0,0,0]}varU=U/(13*L)+refU;varV=V/(13*L)+refV;Y=L_to_Y(L);X=0-9*Y*varU/((varU-4)*varV-varU*varV);Z=(9*Y-15*varV*Y-varV*X)/(3*varV);return[X,Y,Z]};conv.luv.lch=function(tuple){var C,H,Hrad,L,U,V;L=tuple[0],U=tuple[1],V=tuple[2];C=Math.pow(Math.pow(U,2)+Math.pow(V,2),1/2);Hrad=Math.atan2(V,U);H=Hrad*360/2/Math.PI;if(H<0){H=360+H}return[L,C,H]};conv.lch.luv=function(tuple){var C,H,Hrad,L,U,V;L=tuple[0],C=tuple[1],H=tuple[2];Hrad=H/360*2*Math.PI;U=Math.cos(Hrad)*C;V=Math.sin(Hrad)*C;return[L,U,V]};conv.husl.lch=function(tuple){var C,H,L,S,max;H=tuple[0],S=tuple[1],L=tuple[2];if(L>99.9999999){return[100,0,H]}if(L<1e-8){return[0,0,H]}max=maxChromaForLH(L,H);C=max/100*S;return[L,C,H]};conv.lch.husl=function(tuple){var C,H,L,S,max;L=tuple[0],C=tuple[1],H=tuple[2];if(L>99.9999999){return[H,0,100]}if(L<1e-8){return[H,0,0]}max=maxChromaForLH(L,H);S=C/max*100;return[H,S,L]};conv.huslp.lch=function(tuple){var C,H,L,S,max;H=tuple[0],S=tuple[1],L=tuple[2];if(L>99.9999999){return[100,0,H]}if(L<1e-8){return[0,0,H]}max=maxSafeChromaForL(L);C=max/100*S;return[L,C,H]};conv.lch.huslp=function(tuple){var C,H,L,S,max;L=tuple[0],C=tuple[1],H=tuple[2];if(L>99.9999999){return[H,0,100]}if(L<1e-8){return[H,0,0]}max=maxSafeChromaForL(L);S=C/max*100;return[H,S,L]};conv.rgb.hex=function(tuple){var ch,hex,j,len1;hex="#";tuple=rgbPrepare(tuple);for(j=0,len1=tuple.length;j<len1;j++){ch=tuple[j];ch=ch.toString(16);if(ch.length===1){ch="0"+ch}hex+=ch}return hex};conv.hex.rgb=function(hex){var b,g,j,len1,n,r,ref,results;if(hex.charAt(0)==="#"){hex=hex.substring(1,7)}r=hex.substring(0,2);g=hex.substring(2,4);b=hex.substring(4,6);ref=[r,g,b];results=[];for(j=0,len1=ref.length;j<len1;j++){n=ref[j];results.push(parseInt(n,16)/255)}return results};conv.lch.rgb=function(tuple){return conv.xyz.rgb(conv.luv.xyz(conv.lch.luv(tuple)))};conv.rgb.lch=function(tuple){return conv.luv.lch(conv.xyz.luv(conv.rgb.xyz(tuple)))};conv.husl.rgb=function(tuple){return conv.lch.rgb(conv.husl.lch(tuple))};conv.rgb.husl=function(tuple){return conv.lch.husl(conv.rgb.lch(tuple))};conv.huslp.rgb=function(tuple){return conv.lch.rgb(conv.huslp.lch(tuple))};conv.rgb.huslp=function(tuple){return conv.lch.huslp(conv.rgb.lch(tuple))};root={};root.fromRGB=function(R,G,B){return conv.rgb.husl([R,G,B])};root.fromHex=function(hex){return conv.rgb.husl(conv.hex.rgb(hex))};root.toRGB=function(H,S,L){return conv.husl.rgb([H,S,L])};root.toHex=function(H,S,L){return conv.rgb.hex(conv.husl.rgb([H,S,L]))};root.p={};root.p.toRGB=function(H,S,L){return conv.xyz.rgb(conv.luv.xyz(conv.lch.luv(conv.huslp.lch([H,S,L]))))};root.p.toHex=function(H,S,L){return conv.rgb.hex(conv.xyz.rgb(conv.luv.xyz(conv.lch.luv(conv.huslp.lch([H,S,L])))))};root.p.fromRGB=function(R,G,B){return conv.lch.huslp(conv.luv.lch(conv.xyz.luv(conv.rgb.xyz([R,G,B]))))};root.p.fromHex=function(hex){return conv.lch.huslp(conv.luv.lch(conv.xyz.luv(conv.rgb.xyz(conv.hex.rgb(hex)))))};root._conv=conv;root._round=round;root._rgbPrepare=rgbPrepare;root._getBounds=getBounds;root._maxChromaForLH=maxChromaForLH;root._maxSafeChromaForL=maxSafeChromaForL;if(!(typeof module!=="undefined"&&module!==null||typeof jQuery!=="undefined"&&jQuery!==null||typeof requirejs!=="undefined"&&requirejs!==null)){this.HUSL=root}if(typeof module!=="undefined"&&module!==null){module.exports=root}if(typeof jQuery!=="undefined"&&jQuery!==null){jQuery.husl=root}if(typeof requirejs!=="undefined"&&requirejs!==null&&(typeof define!=="undefined"&&define!==null)){define(root)}}).call(this)},{}],2:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports["default"]=interpolateHusl;function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{"default":obj}}var _husl=require("husl");var _husl2=_interopRequireDefault(_husl);function interpolateHusl(a,b){a=toHusl(a);b=toHusl(b);var ah=a[0],as=a[1],al=a[2],bh=b[0]-ah,bs=b[1]-as,bl=b[2]-al;if(isNaN(bs))bs=0,as=isNaN(as)?b.s:as;if(isNaN(bh))bh=0,ah=isNaN(ah)?b.h:ah;else if(bh>180)bh-=360;else if(bh<-180)bh+=360;return function(t){return _husl2["default"].toHex(ah+bh*t,as+bs*t,al+bl*t)}}function toHusl(format){var r=0,g=0,b=0,color,m1,m2;m1=/([a-z]+)\((.*)\)/i.exec(format);if(m1){m2=m1[2].split(",");if(m1[1]==="hsl"){return[parseFloat(m2[0]),parseFloat(m2[1]),parseFloat(m2[2])]}}if(format!=null&&format.charAt(0)==="#"&&!isNaN(color=parseInt(format.slice(1),16))){if(format.length===4){r=(color&3840)>>4;r=r>>4|r;g=color&240;g=g>>4|g;b=color&15;b=b<<4|b}else if(format.length===7){r=(color&16711680)>>16;g=(color&65280)>>8;b=color&255}}return _husl2["default"].fromRGB(r/255,g/255,b/255)}module.exports=exports["default"]},{husl:1}],3:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports["default"]=interpolateHuslp;function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{"default":obj}}var _husl=require("husl");var _husl2=_interopRequireDefault(_husl);function interpolateHuslp(a,b){a=toHusl(a);b=toHusl(b);var ah=a[0],as=a[1],al=a[2],bh=b[0]-ah,bs=b[1]-as,bl=b[2]-al;if(isNaN(bs))bs=0,as=isNaN(as)?b.s:as;if(isNaN(bh))bh=0,ah=isNaN(ah)?b.h:ah;else if(bh>180)bh-=360;else if(bh<-180)bh+=360;return function(t){return _husl2["default"].p.toHex(ah+bh*t,as+bs*t,al+bl*t)}}function toHusl(format){var r=0,g=0,b=0,color,m1,m2;m1=/([a-z]+)\((.*)\)/i.exec(format);if(m1){m2=m1[2].split(",");if(m1[1]==="hsl"){return[parseFloat(m2[0]),parseFloat(m2[1]),parseFloat(m2[2])]}}if(format!=null&&format.charAt(0)==="#"&&!isNaN(color=parseInt(format.slice(1),16))){if(format.length===4){r=(color&3840)>>4;r=r>>4|r;g=color&240;g=g>>4|g;b=color&15;b=b<<4|b}else if(format.length===7){r=(color&16711680)>>16;g=(color&65280)>>8;b=color&255}}return _husl2["default"].fromRGB(r/255,g/255,b/255)}module.exports=exports["default"]},{husl:1}],4:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:true});function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{"default":obj}}var _husl=require("./husl");var _husl2=_interopRequireDefault(_husl);var _huslp=require("./huslp");var _huslp2=_interopRequireDefault(_huslp);if(typeof window!=="undefined"){if(window.d3!==undefined){window.d3.interpolateHusl=_husl2["default"];window.d3.interpolateHuslp=_huslp2["default"]}}exports.interpolateHusl=_husl2["default"];exports.interpolateHuslp=_huslp2["default"]},{"./husl":2,"./huslp":3}]},{},[4]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
body { | |
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | |
} | |
.space { | |
position: absolute; | |
} | |
.space div { | |
position: absolute; | |
top: 0; | |
left: 20px; | |
} | |
</style> | |
<body> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> | |
<script src="d3-interpolate-husl.min.js"></script> | |
<script> | |
var spaces = [ | |
{name: "HSL", interpolate: d3.interpolateHsl}, | |
{name: "HCL", interpolate: d3.interpolateHcl}, | |
{name: "Lab", interpolate: d3.interpolateLab}, | |
{name: "RGB", interpolate: d3.interpolateRgb}, | |
{name: "HUSL", interpolate: d3.interpolateHusl}, | |
{name: "HUSLp", interpolate: d3.interpolateHuslp} | |
]; | |
var y = d3.scale.ordinal() | |
.domain(spaces.map(function(d) { return d.name; })) | |
.rangeRoundBands([0, 500], .09); | |
var margin = y.range()[0], | |
width = 960 - margin - margin, | |
height = y.rangeBand(); | |
var color = d3.scale.linear() | |
.domain([0, width]) | |
.range(["hsl(20,100%,60%)", "hsl(125, 100%,60%)"]); | |
var space = d3.select("body").selectAll(".space") | |
.data(spaces) | |
.enter().append("div") | |
.attr("class", "space") | |
.style("width", width + "px") | |
.style("height", height + "px") | |
.style("left", margin + "px") | |
.style("top", function(d, i) { return y(d.name) + "px"; }); | |
space.append("canvas") | |
.attr("width", width) | |
.attr("height", 1) | |
.style("width", width + "px") | |
.style("height", height + "px") | |
.each(render); | |
space.append("div") | |
.style("line-height", height + "px") | |
.text(function(d) { return d.name; }); | |
function render(d) { | |
var context = this.getContext("2d"), | |
image = context.createImageData(width, 1); | |
color.interpolate(d.interpolate); | |
for (var i = 0, j = -1, c; i < width; ++i) { | |
c = d3.rgb(color(i)); | |
image.data[++j] = c.r; | |
image.data[++j] = c.g; | |
image.data[++j] = c.b; | |
image.data[++j] = 255; | |
} | |
context.putImageData(image, 0, 0); | |
} | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment