mirror of
https://github.com/thangisme/notes.git
synced 2024-09-26 14:06:00 -04:00
255 lines
10 KiB
JavaScript
255 lines
10 KiB
JavaScript
|
var installedColorSpaces = [],
|
||
|
undef = function (obj) {
|
||
|
return typeof obj === 'undefined';
|
||
|
},
|
||
|
channelRegExp = /\s*(\.\d+|\d+(?:\.\d+)?)(%)?\s*/,
|
||
|
percentageChannelRegExp = /\s*(\.\d+|100|\d?\d(?:\.\d+)?)%\s*/,
|
||
|
alphaChannelRegExp = /\s*(\.\d+|\d+(?:\.\d+)?)\s*/,
|
||
|
cssColorRegExp = new RegExp(
|
||
|
'^(rgb|hsl|hsv)a?' +
|
||
|
'\\(' +
|
||
|
channelRegExp.source + ',' +
|
||
|
channelRegExp.source + ',' +
|
||
|
channelRegExp.source +
|
||
|
'(?:,' + alphaChannelRegExp.source + ')?' +
|
||
|
'\\)$', 'i');
|
||
|
|
||
|
function color(obj) {
|
||
|
if (Array.isArray(obj)) {
|
||
|
if (typeof obj[0] === 'string' && typeof color[obj[0]] === 'function') {
|
||
|
// Assumed array from .toJSON()
|
||
|
return new color[obj[0]](obj.slice(1, obj.length));
|
||
|
} else if (obj.length === 4) {
|
||
|
// Assumed 4 element int RGB array from canvas with all channels [0;255]
|
||
|
return new color.RGB(obj[0] / 255, obj[1] / 255, obj[2] / 255, obj[3] / 255);
|
||
|
}
|
||
|
} else if (typeof obj === 'string') {
|
||
|
var lowerCased = obj.toLowerCase();
|
||
|
if (color.namedColors[lowerCased]) {
|
||
|
obj = '#' + color.namedColors[lowerCased];
|
||
|
}
|
||
|
if (lowerCased === 'transparent') {
|
||
|
obj = 'rgba(0,0,0,0)';
|
||
|
}
|
||
|
// Test for CSS rgb(....) string
|
||
|
var matchCssSyntax = obj.match(cssColorRegExp);
|
||
|
if (matchCssSyntax) {
|
||
|
var colorSpaceName = matchCssSyntax[1].toUpperCase(),
|
||
|
alpha = undef(matchCssSyntax[8]) ? matchCssSyntax[8] : parseFloat(matchCssSyntax[8]),
|
||
|
hasHue = colorSpaceName[0] === 'H',
|
||
|
firstChannelDivisor = matchCssSyntax[3] ? 100 : (hasHue ? 360 : 255),
|
||
|
secondChannelDivisor = (matchCssSyntax[5] || hasHue) ? 100 : 255,
|
||
|
thirdChannelDivisor = (matchCssSyntax[7] || hasHue) ? 100 : 255;
|
||
|
if (undef(color[colorSpaceName])) {
|
||
|
throw new Error('color.' + colorSpaceName + ' is not installed.');
|
||
|
}
|
||
|
return new color[colorSpaceName](
|
||
|
parseFloat(matchCssSyntax[2]) / firstChannelDivisor,
|
||
|
parseFloat(matchCssSyntax[4]) / secondChannelDivisor,
|
||
|
parseFloat(matchCssSyntax[6]) / thirdChannelDivisor,
|
||
|
alpha
|
||
|
);
|
||
|
}
|
||
|
// Assume hex syntax
|
||
|
if (obj.length < 6) {
|
||
|
// Allow CSS shorthand
|
||
|
obj = obj.replace(/^#?([0-9a-f])([0-9a-f])([0-9a-f])$/i, '$1$1$2$2$3$3');
|
||
|
}
|
||
|
// Split obj into red, green, and blue components
|
||
|
var hexMatch = obj.match(/^#?([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])$/i);
|
||
|
if (hexMatch) {
|
||
|
return new color.RGB(
|
||
|
parseInt(hexMatch[1], 16) / 255,
|
||
|
parseInt(hexMatch[2], 16) / 255,
|
||
|
parseInt(hexMatch[3], 16) / 255
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// No match so far. Lets try the less likely ones
|
||
|
if (color.CMYK) {
|
||
|
var cmykMatch = obj.match(new RegExp(
|
||
|
'^cmyk' +
|
||
|
'\\(' +
|
||
|
percentageChannelRegExp.source + ',' +
|
||
|
percentageChannelRegExp.source + ',' +
|
||
|
percentageChannelRegExp.source + ',' +
|
||
|
percentageChannelRegExp.source +
|
||
|
'\\)$', 'i'));
|
||
|
if (cmykMatch) {
|
||
|
return new color.CMYK(
|
||
|
parseFloat(cmykMatch[1]) / 100,
|
||
|
parseFloat(cmykMatch[2]) / 100,
|
||
|
parseFloat(cmykMatch[3]) / 100,
|
||
|
parseFloat(cmykMatch[4]) / 100
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
} else if (typeof obj === 'object' && obj.isColor) {
|
||
|
return obj;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
color.namedColors = {};
|
||
|
|
||
|
color.installColorSpace = function (colorSpaceName, propertyNames, config) {
|
||
|
color[colorSpaceName] = function (a1) { // ...
|
||
|
var args = Array.isArray(a1) ? a1 : arguments;
|
||
|
propertyNames.forEach(function (propertyName, i) {
|
||
|
var propertyValue = args[i];
|
||
|
if (propertyName === 'alpha') {
|
||
|
this._alpha = (isNaN(propertyValue) || propertyValue > 1) ? 1 : (propertyValue < 0 ? 0 : propertyValue);
|
||
|
} else {
|
||
|
if (isNaN(propertyValue)) {
|
||
|
throw new Error('[' + colorSpaceName + ']: Invalid color: (' + propertyNames.join(',') + ')');
|
||
|
}
|
||
|
if (propertyName === 'hue') {
|
||
|
this._hue = propertyValue < 0 ? propertyValue - Math.floor(propertyValue) : propertyValue % 1;
|
||
|
} else {
|
||
|
this['_' + propertyName] = propertyValue < 0 ? 0 : (propertyValue > 1 ? 1 : propertyValue);
|
||
|
}
|
||
|
}
|
||
|
}, this);
|
||
|
};
|
||
|
color[colorSpaceName].propertyNames = propertyNames;
|
||
|
|
||
|
var prototype = color[colorSpaceName].prototype;
|
||
|
|
||
|
['valueOf', 'hex', 'hexa', 'css', 'cssa'].forEach(function (methodName) {
|
||
|
prototype[methodName] = prototype[methodName] || (colorSpaceName === 'RGB' ? prototype.hex : function () {
|
||
|
return this.rgb()[methodName]();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
prototype.isColor = true;
|
||
|
|
||
|
prototype.equals = function (otherColor, epsilon) {
|
||
|
if (undef(epsilon)) {
|
||
|
epsilon = 1e-10;
|
||
|
}
|
||
|
|
||
|
otherColor = otherColor[colorSpaceName.toLowerCase()]();
|
||
|
|
||
|
for (var i = 0; i < propertyNames.length; i = i + 1) {
|
||
|
if (Math.abs(this['_' + propertyNames[i]] - otherColor['_' + propertyNames[i]]) > epsilon) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
};
|
||
|
|
||
|
prototype.toJSON = function () {
|
||
|
return [colorSpaceName].concat(propertyNames.map(function (propertyName) {
|
||
|
return this['_' + propertyName];
|
||
|
}, this));
|
||
|
};
|
||
|
|
||
|
for (var propertyName in config) {
|
||
|
if (config.hasOwnProperty(propertyName)) {
|
||
|
var matchFromColorSpace = propertyName.match(/^from(.*)$/);
|
||
|
if (matchFromColorSpace) {
|
||
|
color[matchFromColorSpace[1].toUpperCase()].prototype[colorSpaceName.toLowerCase()] = config[propertyName];
|
||
|
} else {
|
||
|
prototype[propertyName] = config[propertyName];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// It is pretty easy to implement the conversion to the same color space:
|
||
|
prototype[colorSpaceName.toLowerCase()] = function () {
|
||
|
return this;
|
||
|
};
|
||
|
prototype.toString = function () {
|
||
|
return '[' + colorSpaceName + ' ' + propertyNames.map(function (propertyName) {
|
||
|
return this['_' + propertyName];
|
||
|
}).join(', ') + ']';
|
||
|
};
|
||
|
|
||
|
// Generate getters and setters
|
||
|
propertyNames.forEach(function (propertyName) {
|
||
|
var shortName = propertyName === 'black' ? 'k' : propertyName.charAt(0);
|
||
|
prototype[propertyName] = prototype[shortName] = function (value, isDelta) {
|
||
|
// Simple getter mode: color.red()
|
||
|
if (typeof value === 'undefined') {
|
||
|
return this['_' + propertyName];
|
||
|
} else if (isDelta) {
|
||
|
// Adjuster: color.red(+.2, true)
|
||
|
return new this.constructor(propertyNames.map(function (otherPropertyName) {
|
||
|
return this['_' + otherPropertyName] + (propertyName === otherPropertyName ? value : 0);
|
||
|
}, this));
|
||
|
} else {
|
||
|
// Setter: color.red(.2);
|
||
|
return new this.constructor(propertyNames.map(function (otherPropertyName) {
|
||
|
return (propertyName === otherPropertyName) ? value : this['_' + otherPropertyName];
|
||
|
}, this));
|
||
|
}
|
||
|
};
|
||
|
});
|
||
|
|
||
|
function installForeignMethods(targetColorSpaceName, sourceColorSpaceName) {
|
||
|
var obj = {};
|
||
|
obj[sourceColorSpaceName.toLowerCase()] = function () {
|
||
|
return this.rgb()[sourceColorSpaceName.toLowerCase()]();
|
||
|
};
|
||
|
color[sourceColorSpaceName].propertyNames.forEach(function (propertyName) {
|
||
|
var shortName = propertyName === 'black' ? 'k' : propertyName.charAt(0);
|
||
|
obj[propertyName] = obj[shortName] = function (value, isDelta) {
|
||
|
return this[sourceColorSpaceName.toLowerCase()]()[propertyName](value, isDelta);
|
||
|
};
|
||
|
});
|
||
|
for (var prop in obj) {
|
||
|
if (obj.hasOwnProperty(prop) && color[targetColorSpaceName].prototype[prop] === undefined) {
|
||
|
color[targetColorSpaceName].prototype[prop] = obj[prop];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
installedColorSpaces.forEach(function (otherColorSpaceName) {
|
||
|
installForeignMethods(colorSpaceName, otherColorSpaceName);
|
||
|
installForeignMethods(otherColorSpaceName, colorSpaceName);
|
||
|
});
|
||
|
|
||
|
installedColorSpaces.push(colorSpaceName);
|
||
|
return color;
|
||
|
};
|
||
|
|
||
|
color.pluginList = [];
|
||
|
|
||
|
color.use = function (plugin) {
|
||
|
if (color.pluginList.indexOf(plugin) === -1) {
|
||
|
this.pluginList.push(plugin);
|
||
|
plugin(color);
|
||
|
}
|
||
|
return color;
|
||
|
};
|
||
|
|
||
|
color.installMethod = function (name, fn) {
|
||
|
installedColorSpaces.forEach(function (colorSpace) {
|
||
|
color[colorSpace].prototype[name] = fn;
|
||
|
});
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
color.installColorSpace('RGB', ['red', 'green', 'blue', 'alpha'], {
|
||
|
hex: function () {
|
||
|
var hexString = (Math.round(255 * this._red) * 0x10000 + Math.round(255 * this._green) * 0x100 + Math.round(255 * this._blue)).toString(16);
|
||
|
return '#' + ('00000'.substr(0, 6 - hexString.length)) + hexString;
|
||
|
},
|
||
|
|
||
|
hexa: function () {
|
||
|
var alphaString = Math.round(this._alpha * 255).toString(16);
|
||
|
return '#' + '00'.substr(0, 2 - alphaString.length) + alphaString + this.hex().substr(1, 6);
|
||
|
},
|
||
|
|
||
|
css: function () {
|
||
|
return 'rgb(' + Math.round(255 * this._red) + ',' + Math.round(255 * this._green) + ',' + Math.round(255 * this._blue) + ')';
|
||
|
},
|
||
|
|
||
|
cssa: function () {
|
||
|
return 'rgba(' + Math.round(255 * this._red) + ',' + Math.round(255 * this._green) + ',' + Math.round(255 * this._blue) + ',' + this._alpha + ')';
|
||
|
}
|
||
|
});
|
||
|
|
||
|
module.exports = color;
|