UNPKG

imagemagick-async

Version:
732 lines (594 loc) 17.6 kB
let Path = require('path'); let PathParts = __dirname.split(Path.sep); let index = PathParts.indexOf('imagemagick-async'); let RootDir = PathParts.slice(0, index + 1).join(Path.sep); let Err = require(Path.join(RootDir, 'error.js')); let Filepath = require(Path.join(RootDir, 'filepath.js')).Filepath; let InputsBaseClass = require(Path.join(Filepath.InputsDir(), 'inputsbaseclass.js')).InputsBaseClass; //------------------------------------- // CONSTANTS const HEX_CHARS = Array.from('0123456789abcdef'); const RGB_CHARS = ['r', 'g', 'b']; const RGBA_CHARS = RGB_CHARS.concat('a'); const RGB1_LENGTH = RGB_CHARS.length; // #rgb (reduced, simplified notation) const RGB1_FORMAT = '#rgb'; const RGB2_LENGTH = RGB_CHARS.length * 2; // #rrggbb (8-bit per channel) const RGB2_FORMAT = '#rrggbb'; const RGBA2_LENGTH = RGB2_LENGTH + 2; // #rrggbbaa (8-bit per channel) const RGBA2_FORMAT = '#rrggbbaa'; const RGB4_LENGTH = RGB_CHARS.length * 4; // #rrrrggggbbbb (16-bit per channel) const RGB4_FORMAT = '#rrrrggggbbbb'; const RGBA4_LENGTH = RGB4_LENGTH + 4; // #rrrrggggbbbbaaaa (16-bit per channel) const RGBA4_FORMAT = '#rrrrggggbbbbaaaa'; const RGB_MIN = 0; const RGB_8_BIT_MAX = 255; const RGB_16_BIT_MAX = Math.pow(RGB_8_BIT_MAX, 2); const PERCENT_MIN = 0; const PERCENT_MAX = 100; const FORMATS = ['string', 'integers', 'percents']; //------------------------------------- // HELPERS function IntToHexString(i) { return i.toString(16); } function HexStringToInt(hexStr) { return parseInt(hexStr, 16); } function RGBAIntegersToHexString(r, g, b, a) { let hexStr = ''; let rHex = IntToHexString(r); if (rHex.length == 1) rHex = `0${rHex}`; hexStr += rHex; let gHex = IntToHexString(g); if (gHex.length == 1) gHex = `0${gHex}`; hexStr += gHex; let bHex = IntToHexString(b); if (bHex.length == 1) bHex = `0${bHex}`; hexStr += bHex; if (a != RGB_8_BIT_MAX) { let aHex = IntToHexString(a); if (aHex.length == 1) aHex = `0${aHex}`; hexStr += aHex; } return `#${hexStr}`; } function RGBAPercentsToHexString(r, g, b, a) { let hexStr = ''; let rHex = IntToHexString(Math.floor(255 * (r / 100))); if (rHex.length == 1) rHex = `0${rHex}`; hexStr += rHex; let gHex = IntToHexString(Math.floor(255 * (g / 100))); if (gHex.length == 1) gHex = `0${gHex}`; hexStr += gHex; let bHex = IntToHexString(Math.floor(255 * (b / 100))); if (bHex.length == 1) bHex = `0${bHex}`; hexStr += bHex; if (a != PERCENT_MAX) { let aHex = IntToHexString(Math.floor(255 * (a / 100))); if (aHex.length == 1) aHex = `0${aHex}`; hexStr += aHex; } return `#${hexStr}`; } function ChannelType(hexStr) { let hex = hexStr.substring(1); let hexLength = hex.length; if (hexLength == RGB2_LENGTH) return '8'; else if (hexLength == RGBA2_LENGTH) return '8A'; else if (hexLength == RGB4_LENGTH) return '16'; else if (hexLength == RGBA4_LENGTH) return '16A'; else return null; } function SimplifiedHexString(hexStr) { let channelType = ChannelType(hexStr); let hex = hexStr.substring(1); if (channelType.includes('8')) { let pairs = []; for (let i = 0; i < hex.length / 2; ++i) { let start = i * 2; let end = start + 2; pairs.push(hex.substring(start, end)); } let simplifiedHexStr = ''; for (let i = 0; i < pairs.length; ++i) { let currPair = pairs[i]; if (currPair.charAt(0) != currPair.charAt(1)) return hexStr; else simplifiedHexStr += currPair.charAt(0); } if (simplifiedHexStr.endsWith('0')) simplifiedHexStr = simplifiedHexStr.substring(0, simplifiedHexStr.length - 1); return `#${simplifiedHexStr}`; } else if (channelType.includes('16')) { let tuples = []; for (let i = 0; i < hex.length / 4; ++i) { let start = i * 4; let end = start + 4; tuples.push(hex.substring(start, end)); } let simplifiedHexStr = ''; for (let i = 0; i < tuples.length; ++i) { let currTuple = tuples[0]; if ( currTuple.charAt(0) != currTuple.charAt(1) && currTuple.charAt(0) != currTuple.charAt(2) && currTuple.charAt(0) != currTuple.charAt(3) ) return hexStr; else simplifiedHexStr += currTuple.charAt(0); } if (simplifiedHexStr.endsWith('0')) simplifiedHexStr = simplifiedHexStr.substring(0, -1); return `#${simplifiedHexStr}`; } else return null; } function ParseHextString(hexStr) { let object = {}; let channelType = ChannelType(hexStr); let hexStrNoHash = hexStr.substring(1); if (channelType.includes('8')) { object.bitsPerChannel = 8; object.alphaChannel = false; // Hex strings let hex = {}; hex.r = hexStrNoHash.substring(0, 2); hex.g = hexStrNoHash.substring(2, 4); hex.b = hexStrNoHash.substring(4, 6); hex.a = '00'; if (channelType.includes('A')) { object.alphaChannel = true; hex.a = hexStrNoHash.substring(6, 8); } hex.simplifiedString = SimplifiedHexString(hexStr); hex.string = hexStr; object.hex = hex; // Get numbers: rgba(r, g, b, a) let numbers = {}; numbers.r = HexStringToInt(hex.r); numbers.g = HexStringToInt(hex.g); numbers.b = HexStringToInt(hex.b); let args = [numbers.r, numbers.g, numbers.b]; numbers.a = 0.0; if (channelType.includes('A')) { numbers.a = HexStringToInt(hex.a) / RGB_8_BIT_MAX; numbers.a = parseFloat(numbers.a.toFixed(2)); args.push(numbers.a); } numbers.string = `rgb(${args.join(', ')})`; object.numbers = numbers; // Get percents: rgba(r%, g%, b%, a) let percents = {}; let pArgs = []; let rPercent = numbers.r / RGB_8_BIT_MAX; rPercent = parseFloat(rPercent.toFixed(1)) * 100; percents.r = rPercent; pArgs.push(`${percents.r}%`); let gPercent = numbers.g / RGB_8_BIT_MAX; gPercent = parseFloat(gPercent.toFixed(1)) * 100; percents.g = gPercent; pArgs.push(`${percents.g}%`); let bPercent = numbers.b / RGB_8_BIT_MAX; bPercent = parseFloat(bPercent.toFixed(1)) * 100; percents.b = bPercent; pArgs.push(`${percents.b}%`); percents.a = 0.0; if (channelType.includes('A')) { percents.a = numbers.a; pArgs.push(percents.a); } percents.string = `rgb(${pArgs.join(', ')})`; object.percents = percents; return object; } else if (channelType.includes('16')) { object.bitsPerChannel = 16; object.alphaChannel = false; // Hex strings let hex = {}; hex.r = hex.substring(0, 4); hex.g = hex.substring(4, 8); hex.b = hex.substring(8, 12); hex.a = '0000'; if (channelType.includes('A')) { object.alphaChannel = true; hex.a = hex.substring(12, 16); } hex.simplifiedString = SimplifiedHexString(hexStr); hex.string = hexStr; object.hex = hex; // Get numbers: rgba(r, g, b, a) let numbers = {}; numbers.r = HexStringToInt(hex.r); numbers.g = HexStringToInt(hex.g); numbers.b = HexStringToInt(hex.b); let args = [numbers.r, numbers.g, numbers.b]; numbers.a = 0.0; if (channelType.includes('A')) { numbers.a = HexStringToInt(hex.a) / RGB_16_BIT_MAX; numbers.a = parseFloat(numbers.a.toFixed(2)); args.push(numbers.a); } numbers.string = `rgb(${args.join(', ')})`; object.numbers = numbers; // Get percents: rgba(r%, g%, b%, a) let percents = {}; let pArgs = []; let rPercent = numbers.r / RGB_16_BIT_MAX; rPercent = parseFloat(rPercent.toFixed(1)); percents.r = rPercent; pArgs.push(`${percents.r}%`); let gPercent = numbers.g / RGB_16_BIT_MAX; gPercent = parseFloat(gPercent.toFixed(1)); percents.g = gPercent; pArgs.push(`${percents.g}%`); let bPercent = numbers.b / RGB_16_BIT_MAX; bPercent = parseFloat(bPercent.toFixed(1)); percents.b = bPercent; pArgs.push(`${percents.b}%`); percents.a = 0.0; if (channelType.includes('A')) { percents.a = numbers.a; pArgs.push(percents.a); } percents.string = `rgb(${pArgs.join(', ')})`; object.percents = percents; return object; } else return null; } //------------------------------------ // COLOR class Color extends InputsBaseClass { constructor(builder) { super(builder); } /** * @override */ static get Builder() { class Builder { constructor() { this.type = 'Color'; this.name = 'Color'; this.args = {}; } /** * @param {string} str Hex string representation of the desired color. */ hexString(str) { this.args.hexString = str; return this; } /** * @param {number} n Integer value of red */ red(n) { this.args.red = n; return this; } /** * @param {number} n Integer value of green */ green(n) { this.args.green = n; return this; } /** * @param {number} n Integer value of blue */ blue(n) { this.args.blue = n; return this; } /** * @param {number} n Integer value of alpha */ alpha(n) { this.args.alpha = n; return this; } /** * @param {string} str The input format that determines how the color is constructed. The formats are: 'string' (for using a hex string), 'integers' (for using rgb values), or 'percents' (for using percent values). */ format(str) { this.args.format = str; return this; } build() { return new Color(this); } } return new Builder(); } /** * @returns {{hex: {r: string, g: string, b:string, a: string, string: string, simplifiedString: string}, numbers: {r: number, g: number, b: number, a: number, string: string}, percents: {r: number, g: number, b: number, a: number, string: string}, alphaChannel: boolean, bitsPerChannel: number}} Returns an object with info pertaining to this color. */ Info() { let parsedInfo = null; let format = this.args.format; if (format == 'string') { parsedInfo = ParseHextString(this.args.hexString.toLowerCase()); } else if (format == 'integers') { let hexStr = RGBAIntegersToHexString(r, g, b, a); parsedInfo = ParseHextString(hexStr.toLowerCase()); } else if (format == 'percents') { let hexStr = RGBAPercentsToHexString(r, g, b, a); parsedInfo = ParseHextString(hexStr.toLowerCase()); } return parsedInfo; } /** * @returns {string} Returns the string representation of this color according to its format. */ String() { let info = this.Info(); let str = ''; let format = this.args.format; if (format == 'string') str = info.hex.string; else if (format == 'integers') str = info.numbers.string; else if (format == 'percents') str = info.percents.string; return str; } /** * @returns {string} Returns the hex string representation of this color in the form of '#rrggbb'. */ HexString() { return this.Info().hex.string; } /** * @override */ Errors() { let params = Color.Parameters(); let errors = []; let prefix = 'COLOR_ERROR'; let formatErr = Err.ErrorMessage.Builder .prefix(prefix) .varName('Format') .condition( new Err.StringCondition.Builder(this.args.format) .isEmpty(false) .IsWhitespace(false) .include(params.format.options) .build() ) .build() .String(); if (formatErr) errors.push(formatErr); if (this.args.format == 'string') { // Check if hex string is non-empty and starts with '#'. let hexErr = Err.ErrorMessage.Builder .prefix(prefix) .varName('Hex string') .condition( new Err.StringCondition.Builder(this.args.hexString) .isEmpty(false) .isWhitespace(false) .startsWith('#') .build() ) .build() .String(); if (hexErr) errors.push(hexErr); // Check if hex string length is valid. hexErr = Err.ErrorMessage.Builder .prefix(prefix) .varName('Hex length') .condition( new Err.StringCondition.Builder(this.args.hexString.substring(1)) .minLength(RGB1_LENGTH) .maxLength(RGBA4_LENGTH) .build() ) .build() .String(); if (hexErr) errors.push(hexErr); // Check if hex string contains invalid chars let invalidChars = Array.from(this.args.hex.substring(1)).filter(x => !HEX_CHARS.includes(x)); if (invalidChars.length > 0) errors.push('COLOR_ERROR: Hex string is invalid. Contains invalid characters.'); } else if (this.args.format == 'integers') { // Check red value let redErr = Err.ErrorMessage.Builder .prefix(prefix) .varName('Red value') .condition( new Err.NumberCondition.Builder(this.args.red) .isInteger(true) .min(params.red.min) .build() ) .build() .String(); if (redErr) errors.push(redErr); // Check green value let greenErr = Err.ErrorMessage.Builder .prefix(prefix) .varName('Green value') .condition( new Err.NumberCondition.Builder(this.args.green) .isInteger(true) .min(params.green.min) .build() ) .build() .String(); if (greenErr) errors.push(greenErr); // Check blue value let blueErr = Err.ErrorMessage.Builder .prefix(prefix) .varName('Blue value') .condition( new Err.NumberCondition.Builder(this.args.blue) .isInteger(true) .min(params.blue.min) .build() ) .build() .String(); if (blueErr) errors.push(blueErr); // Check alpha value let alphaErr = Err.ErrorMessage.Builder .prefix(prefix) .varName('Alpha value') .condition( new Err.NumberCondition.Builder(this.args.alpha) .isInteger(true) .min(params.alpha.min) .build() ) .build() .String(); if (alphaErr) errors.push(alphaErr); } else if (this.args.format == 'percents') { // Check red value let redErr = Err.ErrorMessage.Builder .prefix(prefix) .varName('Red value') .condition( new Err.NumberCondition.Builder(this.args.red) .min(params.red.min) .build() ) .build() .String(); if (redErr) errors.push(redErr); // Check green value let greenErr = Err.ErrorMessage.Builder .prefix(prefix) .varName('Green value') .condition( new Err.NumberCondition.Builder(this.args.green) .min(params.green.min) .build() ) .build() .String(); if (greenErr) errors.push(greenErr); // Check blue value let blueErr = Err.ErrorMessage.Builder .prefix(prefix) .varName('Blue value') .condition( new Err.NumberCondition.Builder(this.args.blue) .min(params.blue.min) .build() ) .build() .String(); if (blueErr) errors.push(blueErr); // Check alpha value let alphaErr = Err.ErrorMessage.Builder .prefix(prefix) .varName('Alpha value') .condition( new Err.NumberCondition.Builder(this.args.alpha) .min(params.alpha.min) .build() ) .build() .String(); if (alphaErr) errors.push(alphaErr); } return errors; } /** * @override */ static Parameters() { return { format: { type: 'string', options: ['string', 'integers', 'percents'], required: true }, red: { type: 'number', min: RGB_MIN, default: 0, required: false }, green: { type: 'number', min: RGB_MIN, default: 0, required: false }, blue: { type: 'number', min: RGB_MIN, default: 0, required: false }, alpha: { type: 'number', min: RGB_MIN, default: 0, required: false }, hextString: { type: 'string', required: false } }; } } //---------------------------- // EXPORTS exports.HEX_CHARS = HEX_CHARS; exports.RGB_CHARS = RGB_CHARS; exports.RGBA_CHARS = RGBA_CHARS; exports.RGB1_LENGTH = RGB1_LENGTH; exports.RGB1_FORMAT = RGB1_FORMAT; exports.RGB2_LENGTH = RGB2_LENGTH exports.RGB2_FORMAT = RGB2_FORMAT; exports.RGBA2_LENGTH = RGBA2_LENGTH; exports.RGBA2_FORMAT = RGBA2_FORMAT; exports.RGB4_LENGTH = RGB4_LENGTH exports.RGB4_FORMAT = RGB4_FORMAT; exports.RGBA4_LENGTH = RGBA4_LENGTH; exports.RGBA4_FORMAT = RGBA4_FORMAT; exports.RGB_MIN = RGB_MIN; exports.RGB_8_BIT_MAX = RGB_8_BIT_MAX; exports.RGB_16_BIT_MAX = RGB_16_BIT_MAX; exports.PERCENT_MIN = PERCENT_MIN; exports.PERCENT_MAX = PERCENT_MAX; exports.FORMATS = FORMATS; exports.Color = Color;