You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
367 lines
6.7 KiB
367 lines
6.7 KiB
|
|
/*! |
|
* Stylus - RGBA |
|
* Copyright (c) Automattic <developer.wordpress.com> |
|
* MIT Licensed |
|
*/ |
|
|
|
/** |
|
* Module dependencies. |
|
*/ |
|
|
|
var Node = require('./node') |
|
, HSLA = require('./hsla') |
|
, functions = require('../functions') |
|
, adjust = functions.adjust |
|
, nodes = require('./'); |
|
|
|
/** |
|
* Initialize a new `RGBA` with the given r,g,b,a component values. |
|
* |
|
* @param {Number} r |
|
* @param {Number} g |
|
* @param {Number} b |
|
* @param {Number} a |
|
* @api public |
|
*/ |
|
|
|
var RGBA = exports = module.exports = function RGBA(r,g,b,a){ |
|
Node.call(this); |
|
this.r = clamp(r); |
|
this.g = clamp(g); |
|
this.b = clamp(b); |
|
this.a = clampAlpha(a); |
|
this.name = ''; |
|
this.rgba = this; |
|
}; |
|
|
|
/** |
|
* Inherit from `Node.prototype`. |
|
*/ |
|
|
|
RGBA.prototype.__proto__ = Node.prototype; |
|
|
|
/** |
|
* Return an `RGBA` without clamping values. |
|
* |
|
* @param {Number} r |
|
* @param {Number} g |
|
* @param {Number} b |
|
* @param {Number} a |
|
* @return {RGBA} |
|
* @api public |
|
*/ |
|
|
|
RGBA.withoutClamping = function(r,g,b,a){ |
|
var rgba = new RGBA(0,0,0,0); |
|
rgba.r = r; |
|
rgba.g = g; |
|
rgba.b = b; |
|
rgba.a = a; |
|
return rgba; |
|
}; |
|
|
|
/** |
|
* Return a clone of this node. |
|
* |
|
* @return {Node} |
|
* @api public |
|
*/ |
|
|
|
RGBA.prototype.clone = function(){ |
|
var clone = new RGBA( |
|
this.r |
|
, this.g |
|
, this.b |
|
, this.a); |
|
clone.raw = this.raw; |
|
clone.name = this.name; |
|
clone.lineno = this.lineno; |
|
clone.column = this.column; |
|
clone.filename = this.filename; |
|
return clone; |
|
}; |
|
|
|
/** |
|
* Return a JSON representation of this node. |
|
* |
|
* @return {Object} |
|
* @api public |
|
*/ |
|
|
|
RGBA.prototype.toJSON = function(){ |
|
return { |
|
__type: 'RGBA', |
|
r: this.r, |
|
g: this.g, |
|
b: this.b, |
|
a: this.a, |
|
raw: this.raw, |
|
name: this.name, |
|
lineno: this.lineno, |
|
column: this.column, |
|
filename: this.filename |
|
}; |
|
}; |
|
|
|
/** |
|
* Return true. |
|
* |
|
* @return {Boolean} |
|
* @api public |
|
*/ |
|
|
|
RGBA.prototype.toBoolean = function(){ |
|
return nodes.true; |
|
}; |
|
|
|
/** |
|
* Return `HSLA` representation. |
|
* |
|
* @return {HSLA} |
|
* @api public |
|
*/ |
|
|
|
RGBA.prototype.__defineGetter__('hsla', function(){ |
|
return HSLA.fromRGBA(this); |
|
}); |
|
|
|
/** |
|
* Return hash. |
|
* |
|
* @return {String} |
|
* @api public |
|
*/ |
|
|
|
RGBA.prototype.__defineGetter__('hash', function(){ |
|
return this.toString(); |
|
}); |
|
|
|
/** |
|
* Add r,g,b,a to the current component values. |
|
* |
|
* @param {Number} r |
|
* @param {Number} g |
|
* @param {Number} b |
|
* @param {Number} a |
|
* @return {RGBA} new node |
|
* @api public |
|
*/ |
|
|
|
RGBA.prototype.add = function(r,g,b,a){ |
|
return new RGBA( |
|
this.r + r |
|
, this.g + g |
|
, this.b + b |
|
, this.a + a); |
|
}; |
|
|
|
/** |
|
* Subtract r,g,b,a from the current component values. |
|
* |
|
* @param {Number} r |
|
* @param {Number} g |
|
* @param {Number} b |
|
* @param {Number} a |
|
* @return {RGBA} new node |
|
* @api public |
|
*/ |
|
|
|
RGBA.prototype.sub = function(r,g,b,a){ |
|
return new RGBA( |
|
this.r - r |
|
, this.g - g |
|
, this.b - b |
|
, a == 1 ? this.a : this.a - a); |
|
}; |
|
|
|
/** |
|
* Multiply rgb components by `n`. |
|
* |
|
* @param {String} n |
|
* @return {RGBA} new node |
|
* @api public |
|
*/ |
|
|
|
RGBA.prototype.multiply = function(n){ |
|
return new RGBA( |
|
this.r * n |
|
, this.g * n |
|
, this.b * n |
|
, this.a); |
|
}; |
|
|
|
/** |
|
* Divide rgb components by `n`. |
|
* |
|
* @param {String} n |
|
* @return {RGBA} new node |
|
* @api public |
|
*/ |
|
|
|
RGBA.prototype.divide = function(n){ |
|
return new RGBA( |
|
this.r / n |
|
, this.g / n |
|
, this.b / n |
|
, this.a); |
|
}; |
|
|
|
/** |
|
* Operate on `right` with the given `op`. |
|
* |
|
* @param {String} op |
|
* @param {Node} right |
|
* @return {Node} |
|
* @api public |
|
*/ |
|
|
|
RGBA.prototype.operate = function(op, right){ |
|
if ('in' != op) right = right.first |
|
|
|
switch (op) { |
|
case 'is a': |
|
if ('string' == right.nodeName && 'color' == right.string) { |
|
return nodes.true; |
|
} |
|
break; |
|
case '+': |
|
switch (right.nodeName) { |
|
case 'unit': |
|
var n = right.val; |
|
switch (right.type) { |
|
case '%': return adjust(this, new nodes.String('lightness'), right); |
|
case 'deg': return this.hsla.adjustHue(n).rgba; |
|
default: return this.add(n,n,n,0); |
|
} |
|
case 'rgba': |
|
return this.add(right.r, right.g, right.b, right.a); |
|
case 'hsla': |
|
return this.hsla.add(right.h, right.s, right.l); |
|
} |
|
break; |
|
case '-': |
|
switch (right.nodeName) { |
|
case 'unit': |
|
var n = right.val; |
|
switch (right.type) { |
|
case '%': return adjust(this, new nodes.String('lightness'), new nodes.Unit(-n, '%')); |
|
case 'deg': return this.hsla.adjustHue(-n).rgba; |
|
default: return this.sub(n,n,n,0); |
|
} |
|
case 'rgba': |
|
return this.sub(right.r, right.g, right.b, right.a); |
|
case 'hsla': |
|
return this.hsla.sub(right.h, right.s, right.l); |
|
} |
|
break; |
|
case '*': |
|
switch (right.nodeName) { |
|
case 'unit': |
|
return this.multiply(right.val); |
|
} |
|
break; |
|
case '/': |
|
switch (right.nodeName) { |
|
case 'unit': |
|
return this.divide(right.val); |
|
} |
|
break; |
|
} |
|
return Node.prototype.operate.call(this, op, right); |
|
}; |
|
|
|
/** |
|
* Return #nnnnnn, #nnn, or rgba(n,n,n,n) string representation of the color. |
|
* |
|
* @return {String} |
|
* @api public |
|
*/ |
|
|
|
RGBA.prototype.toString = function(){ |
|
function pad(n) { |
|
return n < 16 |
|
? '0' + n.toString(16) |
|
: n.toString(16); |
|
} |
|
|
|
// special case for transparent named color |
|
if ('transparent' == this.name) |
|
return this.name; |
|
|
|
if (1 == this.a) { |
|
var r = pad(this.r) |
|
, g = pad(this.g) |
|
, b = pad(this.b); |
|
|
|
// Compress |
|
if (r[0] == r[1] && g[0] == g[1] && b[0] == b[1]) { |
|
return '#' + r[0] + g[0] + b[0]; |
|
} else { |
|
return '#' + r + g + b; |
|
} |
|
} else { |
|
return 'rgba(' |
|
+ this.r + ',' |
|
+ this.g + ',' |
|
+ this.b + ',' |
|
+ (+this.a.toFixed(3)) + ')'; |
|
} |
|
}; |
|
|
|
/** |
|
* Return a `RGBA` from the given `hsla`. |
|
* |
|
* @param {HSLA} hsla |
|
* @return {RGBA} |
|
* @api public |
|
*/ |
|
|
|
exports.fromHSLA = function(hsla){ |
|
var h = hsla.h / 360 |
|
, s = hsla.s / 100 |
|
, l = hsla.l / 100 |
|
, a = hsla.a; |
|
|
|
var m2 = l <= .5 ? l * (s + 1) : l + s - l * s |
|
, m1 = l * 2 - m2; |
|
|
|
var r = hue(h + 1/3) * 0xff |
|
, g = hue(h) * 0xff |
|
, b = hue(h - 1/3) * 0xff; |
|
|
|
function hue(h) { |
|
if (h < 0) ++h; |
|
if (h > 1) --h; |
|
if (h * 6 < 1) return m1 + (m2 - m1) * h * 6; |
|
if (h * 2 < 1) return m2; |
|
if (h * 3 < 2) return m1 + (m2 - m1) * (2/3 - h) * 6; |
|
return m1; |
|
} |
|
|
|
return new RGBA(r,g,b,a); |
|
}; |
|
|
|
/** |
|
* Clamp `n` >= 0 and <= 255. |
|
* |
|
* @param {Number} n |
|
* @return {Number} |
|
* @api private |
|
*/ |
|
|
|
function clamp(n) { |
|
return Math.max(0, Math.min(n.toFixed(0), 255)); |
|
} |
|
|
|
/** |
|
* Clamp alpha `n` >= 0 and <= 1. |
|
* |
|
* @param {Number} n |
|
* @return {Number} |
|
* @api private |
|
*/ |
|
|
|
function clampAlpha(n) { |
|
return Math.max(0, Math.min(n, 1)); |
|
}
|
|
|