'use strict';
const Rac = require('../Rac');
const utils = require('../util/utils');
/**
* Color with RBGA values, each one in the *[0,1]* range.
*
* ### `instance.Color`
*
* Instances of `Rac` contain a convenience
* [`rac.Color` function]{@link Rac#Color} to create `Color` objects with
* fewer parameters. This function also contains ready-made convenience
* objects, like [`rac.Color.red`]{@link instance.Color#red}, listed
* under [`instance.Color`]{@link instance.Color}.
*
* @example
* let rac = new Rac()
* // new instance with constructor
* let color = new Rac.Color(rac, 0.2, 0.4, 0.6)
* // or convenience function
* let otherColor = rac.Color(0.2, 0.4, 0.6)
*
* @see [`rac.Color`]{@link Rac#Color}
* @see [`instance.Color`]{@link instance.Color}
*
* @alias Rac.Color
*/
class Color {
/**
* Creates a new `Color` instance.
*
* @param {Rac} rac - Instance to use for drawing and creating other objects
* @param {Number} r - The red channel value, in the *[0,1]* range
* @param {Number} g - The green channel value, in the *[0,1]* range
* @param {Number} b - The blue channel value, in the *[0,1]* range
* @param {Number} [a=1] - The alpha channel value, in the *[0,1]* range
*/
constructor(rac, r, g, b, a = 1) {
utils.assertExists(rac, r, g, b, a);
utils.assertNumber(r, g, b, a);
/**
* Instance of `Rac` used for drawing and passed along to any created
* object.
*
* @type {Rac}
*/
this.rac = rac;
/**
* The red channel of the color, in the *[0,1]* range.
* @type {Number}
*/
this.r = r;
/**
* The green channel of the color, in the *[0,1]* range.
* @type {Number}
*/
this.g = g;
/**
* The blue channel of the color, in the *[0,1]* range.
* @type {Number}
*/
this.b = b;
/**
* The alpha channel of the color, in the *[0,1]* range.
* @type {Number}
*/
this.a = a;
}
/**
* Returns a string representation intended for human consumption.
*
* @example
* rac.Color(0.1, 0.2, 0.3, 0.4).toString()
* // returns: 'Color(0.1,0.2,0.3,0.4)'
*
* @param {Number} [digits] - The number of digits to print after the
* decimal point, when ommited all digits are printed
* @returns {String}
*/
toString(digits = null) {
const rStr = utils.cutDigits(this.r, digits);
const gStr = utils.cutDigits(this.g, digits);
const bStr = utils.cutDigits(this.b, digits);
const aStr = utils.cutDigits(this.a, digits);
return `Color(${rStr},${gStr},${bStr},${aStr})`;
}
/**
* Returns `true` when the difference with `otherColor` for each channel
* is under [`rac.equalityThreshold`]{@link Rac#equalityThreshold};
* otherwise returns `false`.
*
* When `otherColor` is any class other that `Rac.Color`, returns `false`.
*
* Values are compared using [`rac.unitaryEquals`]{@link Rac#unitaryEquals}.
*
* @param {Rac.Color} otherColor - A `Color` to compare
* @returns {Boolean}
* @see [`rac.unitaryEquals`]{@link Rac#unitaryEquals}
*/
equals(otherColor) {
return otherColor instanceof Color
&& this.rac.unitaryEquals(this.r, otherColor.r)
&& this.rac.unitaryEquals(this.g, otherColor.g)
&& this.rac.unitaryEquals(this.b, otherColor.b)
&& this.rac.unitaryEquals(this.a, otherColor.a);
}
/**
* Creates a new `Color` instance with each channel received in the
* *[0,255]* range
*
* @param {Rac} rac - Instance to use for drawing and creating other objects
* @param {Number} r - The red channel value, in the *[0,255]* range
* @param {Number} g - The green channel value, in the *[0,255]* range
* @param {Number} b - The blue channel value, in the *[0,255]* range
* @param {Number} [a=255] - The alpha channel value, in the *[0,255]* range
*
* @returns {Rac.Color}
*/
static fromRgba(rac, r, g, b, a = 255) {
return new Color(rac, r/255, g/255, b/255, a/255);
}
/**
* Creates a new `Color` instance from a hexadecimal triplet or quadruplet
* string.
*
* The `hexString` is expected to have 6 or 8 hex digits for the RGB and
* optionally alpha channels. It can start with `#`. `AABBCC` and
* `#CCDDEEFF` are both valid inputs.
*
* The three digit shorthand is not yet supported.
*
* An error is thrown if `hexString` is misformatted or cannot be parsed.
*
* @param {Rac} rac - Instance to use for drawing and creating other objects
* @param {String} hexString - The hex string to interpret
*
* @returns {Rac.Color}
*/
static fromHex(rac, hexString) {
if (hexString.charAt(0) == '#') {
hexString = hexString.substring(1);
}
if (![6, 8].includes(hexString.length)) {
throw Rac.Exception.failedAssert(
`Unexpected length for hex triplet string: ${hexString}`);
}
let rStr = hexString.substring(0, 2);
let gStr = hexString.substring(2, 4);
let bStr = hexString.substring(4, 6);
let aStr = 'ff';
if (hexString.length == 8) {
aStr = hexString.substring(6, 8);
}
let newR = parseInt(rStr, 16);
let newG = parseInt(gStr, 16);
let newB = parseInt(bStr, 16);
let newA = parseInt(aStr, 16);
if (isNaN(newR) || isNaN(newG) || isNaN(newB) || isNaN(newA)) {
throw Rac.Exception.failedAssert(
`Could not parse hex triplet string: ${hexString}`);
}
return new Color(rac, newR/255, newG/255, newB/255, newA/255);
}
/**
* Returns a new `Fill` that uses `this` as `color`.
*
* @returns {Rac.Fill}
*/
fill() {
return new Rac.Fill(this.rac, this);
}
/**
* Returns a new `Stroke` that uses `this` as `color`.
*
* @param {?Number} weight - The weight of the new `Stroke`
* @returns {Rac.Stroke}
*/
stroke(weight = null) {
return new Rac.Stroke(this.rac, weight, this);
}
/**
* Returns a new `Color` with `a` set to `newAlpha`.
*
* @param {Number} newAlpha - The alpha channel for the new `Color`, in the
* *[0,1]* range
* @returns {Rac.Color}
*/
withAlpha(newAlpha) {
return new Color(this.rac, this.r, this.g, this.b, newAlpha);
}
/**
* Returns a new `Color` with `a` set to `this.a * ratio`.
*
* @param {Number} ratio - The factor to multiply `a` by
* @returns {Rac.Color}
*/
withAlphaRatio(ratio) {
return new Color(this.rac, this.r, this.g, this.b, this.a * ratio);
}
/**
* Returns a new `Color` in the linear transition between `this` and
* `target` at a `ratio` in the range *[0,1]*.
*
* When `ratio` is `0` or less the new `Color` is equivalent to `this`,
* when `ratio` is `1` or larger the new `Color` is equivalent to
* `target`.
*
* @param {Number} ratio - The transition ratio for the new `Color`
* @param {Rac.Color} target - The transition target `Color`
* @returns {Rac.Color}
*/
linearTransition(ratio, target) {
ratio = Math.max(ratio, 0);
ratio = Math.min(ratio, 1);
let newR = this.r + (target.r - this.r) * ratio;
let newG = this.g + (target.g - this.g) * ratio;
let newB = this.b + (target.b - this.b) * ratio;
let newA = this.a + (target.a - this.a) * ratio;
return new Color(this.rac, newR, newG, newB, newA);
}
} // class Color
module.exports = Color;