imagemagick-async
Version:
An API for Image Magick commands.
977 lines (804 loc) • 24.6 kB
JavaScript
let VALIDATE = require('./validate.js');
let LOCAL_COMMAND = require('linux-commands-async').Command.LOCAL;
let ColorBaseClass = require('./colorbaseclass.js').ColorBaseClass;
//-------------------------------------
// CONSTANTS
const RGB_CHARS = ['r', 'g', 'b'];
const RGB1_LENGTH = RGB_CHARS.length; // #rgb (reduced, simplified notation)
const RGB2_LENGTH = RGB_CHARS.length * 2; // #rrggbb (8-bit per channel)
const RGBA2_LENGTH = RGB2_LENGTH + 2; // #rrggbbaa (8-bit per channel)
const RGB4_LENGTH = RGB_CHARS.length * 4; // #rrrrggggbbbb (16-bit per channel)
const RGBA4_LENGTH = RGB4_LENGTH + 4; // #rrrrggggbbbbaaaa (16-bit per channel)
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;
//-------------------------------------
// ERROR CHECKS
function FormatValidator(hexString) {
let error = VALIDATE.IsStringInput(hexString);
if (error)
return `is ${error}`;
if (!hexString.startsWith('#'))
return `is invalid. Must start with a '#' symbol.`;
let hex = hexString.substring(1);
if (
hex.length != RGB1_LENGTH &&
hex.length != RGB2_LENGTH &&
hex.length != RGBA2_LENGTH &&
hex.length != RGB4_LENGTH &&
hex.length != RGBA4_LENGTH
)
return `is invalid. Must have length ${RGB1_LENGTH}, ${RGB2_LENGTH}, ${RGBA2_LENGTH}, ${RGB4_LENGTH}, or ${RGBA4_LENGTH}.`;
return null;
}
//-------------------------------------
// 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 {
/**
* @param {string} hexStr
*/
constructor(hexStr) {
let o = ParseHextString(hexStr.toLowerCase());
this.hex = o.hex;
this.numbers = o.numbers;
this.percents = o.percents;
this.alphaChannel = o.alphaChannel;
this.bitsPerChannel = o.bitsPerChannel;
}
/**
* Create a Color object using RGB integers.
* @param {number} r Red integer value between 0 and 255.
* @param {number} g Green integer value between 0 and 255.
* @param {number} b Blue integer value between 0 and 255.
* @param {number} a Alpha float value between 0 (fully transparent) and 225 (fully opaque).
* @returns {Color} Returns a Color object. If inputs are invalid, it returns null.
*/
static CreateUsingRGBIntgers(r, g, b, a) {
// Validate rgb values
if (
VALIDATE.IsInteger(r) ||
VALIDATE.IsIntegerInRange(r, RGB_MIN, RGB_8_BIT_MAX) ||
VALIDATE.IsInteger(g) ||
VALIDATE.IsIntegerInRange(g, RGB_MIN, RGB_8_BIT_MAX) ||
VALIDATE.IsInteger(b) ||
VALIDATE.IsIntegerInRange(b, RGB_MIN, RGB_8_BIT_MAX) ||
VALIDATE.IsInteger(a) ||
VALIDATE.IsIntegerInRange(a, RGB_MIN, RGB_8_BIT_MAX)
)
return null;
// Create object
let hexStr = RGBAIntegersToHexString(r, g, b, a);
return new Color(hexStr);
}
/**
* Create a Color object using RGB percents.
* @param {number} r Red float value between 0 and 100.
* @param {number} g Green float value between 0 and 100.
* @param {number} b Blue float value between 0 and 100.
* @param {number} a Alpha float value between 0 (fully transparent) and 100 (fully opaque).
* @returns {Color} Returns a Color object. If inputs are invalid, it returns null.
*/
static CreateUsingPercents(r, g, b, a) {
// Validate percent values
if (
VALIDATE.IsNumber(r) ||
VALIDATE.IsNumberInRange(r, PERCENT_MIN, PERCENT_MAX) ||
VALIDATE.IsNumber(g) ||
VALIDATE.IsNumberInRange(g, PERCENT_MIN, PERCENT_MAX) ||
VALIDATE.IsNumber(b) ||
VALIDATE.IsNumberInRange(b, PERCENT_MIN, PERCENT_MAX) ||
VALIDATE.IsNumber(a) ||
VALIDATE.IsNumberInRange(a, PERCENT_MIN, PERCENT_MAX)
)
return null;
// Create object
let hexStr = RGBAPercentsToHexString(r, g, b, a);
return new Color(hexStr);
}
/**
* Create a Color object using RGB hex string.
* @param {string} hexStr Must follow one of the following formats: #rgb, #rrggbb, #rrggbbaa, #rrrrggggbbbb, #rrrrggggbbbbaaaa.
* @returns {Color} Returns a Color object. If inputs are invalid, it returns null.
*/
static CreateUsingRGBHexString(hexStr) {
if (FormatValidator(hexStr))
return null;
return new Color(hexStr);
}
}
//-------------------------------
// NEGATE
class Negate extends ColorBaseClass {
constructor(src) {
super();
this.src_ = src;
}
/**
* @returns {Array<string|number>} Returns an array of image magick arguments associated with this layer.
*/
Args() {
return ['-negate'];
}
/**
* @returns {Array<string|number>} Returns an array of arguments used for rendering this layer.
*/
RenderArgs() {
return [this.src_].concat(this.Args());
}
/**
* @override
*/
Name() {
return 'Negate';
}
/**
* Create a Negate object. Negates all image colors.
* @param {string} src
* @returns {Negate} Returns a Negate object. If inputs are invalid, it returns null.
*/
static Create(src) {
if (!src)
return null;
return new Negate(src);
}
}
//-------------------------------
// COLORIZE
class Colorize extends ColorBaseClass {
constructor(src, fillColor, percent) {
super();
this.src_ = src;
this.fillColor_ = fillColor;
this.percent_ = percent;
}
/**
* @returns {Array<string|number>} Returns an array of image magick arguments associated with this layer.
*/
Args() {
return ['-fill', this.fillColor_, '-colorize', `${this.percent_}%`];
}
/**
* @returns {Array<string|number>} Returns an array of arguments used for rendering this layer.
*/
RenderArgs() {
return [this.src_].concat(this.Args());
}
/**
* @override
*/
Name() {
return 'Colorize';
}
/**
* Create a Colorize object. Creates a veil of color over an image.
* @param {string} src
* @returns {Colorize} Returns a Colorize object. If inputs are invalid, it returns null.
*/
static Create(src, fillColor, percent) {
if (!src || !fillColor || !percent)
return null;
return new Colorize(src, fillColor, percent);
}
}
//-------------------------------
// GRAYSCALE
class GrayscaleFormat extends ColorBaseClass {
constructor(src) {
super();
this.src_ = src;
}
/**
* @returns {Array<string|number>} Returns an array of image magick arguments associated with this layer.
*/
Args() {
return ['-colorspace', 'Gray'];
}
/**
* @returns {Array<string|number>} Returns an array of arguments used for rendering this layer.
*/
RenderArgs() {
return [this.src_].concat(this.Args());
}
/**
* @override
*/
Name() {
return 'GrayscaleFormat';
}
/**
* Create a GrayscaleFormat object. Converts an image to grayscale format.
* @param {string} src
* @returns {GrayscaleFormat} Returns a Grayscale object. If inputs are invalid, it returns null.
*/
static Create(src) {
if (!src)
return null;
return new GrayscaleFormat(src);
}
}
//------------------------------
// RGB
/**
* Convert an image to RGB.
* @param {string} src Source
* @param {string} outputPath The path where the image will be rendered to.
* @returns {Promise} Returns a Promise that resolves if successful. Otherwise, it returns an error.
*/
class RgbFormat extends ColorBaseClass {
constructor(src) {
super();
this.src_ = src;
}
/**
* @returns {Array<string|number>} Returns an array of image magick arguments associated with this layer.
*/
Args() {
return ['-colorspace', 'RGB'];
}
/**
* @returns {Array<string|number>} Returns an array of arguments used for rendering this layer.
*/
RenderArgs() {
return [this.src_].concat(this.Args());
}
/**
* @override
*/
Name() {
return 'RgbFormat';
}
/**
* Create a RgbFormat object. Converts an image to grayscale format.
* @param {string} src
* @returns {RgbFormat} Returns a RgbFormat object. If inputs are invalid, it returns null.
*/
static Create(src) {
if (!src)
return null;
return new RgbFormat(src);
}
}
//--------------------------------------
// REPLACE
class Replace extends ColorBaseClass {
constructor(src, targetColor, desiredColor, fuzz) {
super();
this.src_ = src;
this.targetColor_ = targetColor;
this.desiredColor_ = desiredColor;
this.fuzz_ = fuzz;
}
/**
* @returns {Array<string|number>} Returns an array of image magick arguments associated with this layer.
*/
Args() {
let args = ['-alpha', 'on', '-channel', 'rgba'];
if (this.fuzz_ && this.fuzz_ > 0)
args.push('-fuzz', `${this.fuzz_}%`);
args.push('-fill', this.desiredColor_, '-opaque', this.targetColor_);
return args;
}
/**
* @returns {Array<string|number>} Returns an array of arguments used for rendering this layer.
*/
RenderArgs() {
return [this.src_].concat(this.Args());
}
/**
* @override
*/
Name() {
return 'Replace';
}
/**
* Create a Replace object. Replaces one color with another.
* @param {string} src
* @param {string} targetColor The color you want to change. (Valid color format string used in Image Magick)
* @param {string} desiredColor The color that will replace the target color. (Valid color format string used in Image Magick)
* @param {number} fuzz (Optional) A value between 0 and 100 that determines which other colors similar to the target color will be removed. (The higher the value, the more colors will disappear)
* @returns {Replace} Returns a Replace object. If inputs are invalid, it returns null.
*/
static Create(src, targetColor, desiredColor, fuzz) {
if (!src || !targetColor || !desiredColor)
return null;
return new Replace(src, targetColor, desiredColor, fuzz);
}
}
//---------------------------------------
// TRANSPARENCY
class Transparency extends ColorBaseClass {
constructor(src, percent) {
super();
this.src_ = src;
this.percent_ = percent;
}
/**
* @returns {Array<string|number>} Returns an array of image magick arguments associated with this layer.
*/
Args() {
let adjustedTransPercent = 0;
if (this.percent_ > 100)
adjustedTransPercent = 100;
else if (this.percent_ < 0)
adjustedTransPercent = 0;
else
adjustedTransPercent = this.percent_;
let opaqueValue = (100 - adjustedTransPercent) / 100;
return ['-alpha', 'on', '-channel', 'a', '-evaluate', 'multiply', `${opaqueValue}`, '+channel'];
}
/**
* @returns {Array<string|number>} Returns an array of arguments used for rendering this layer.
*/
RenderArgs() {
return [this.src_].concat(this.Args());
}
/**
* @override
*/
Name() {
return 'Transparency';
}
/**
* Create a Transparency object. Makes an image transparent.
* @param {string} src
* @param {number} percent
* @returns {Replace} Returns a Transparency object. If inputs are invalid, it returns null.
*/
static Create(src, percent) {
if (!src || !percent)
return null;
return new Transparency(src, percent);
}
}
//-------------------------------
// CHANNEL ADJUST
class ChannelAdjust extends ColorBaseClass {
constructor(src, channel, value) {
super();
this.src_ = src;
this.channel_ = channel;
this.value_ = value;
}
/**
* @returns {Array<string|number>} Returns an array of image magick arguments associated with this layer.
*/
Args() {
return ['-alpha', 'set', '-channel', this.channel_, '-evaluate', 'set', this.value_];
}
/**
* @returns {Array<string|number>} Returns an array of arguments used for rendering this layer.
*/
RenderArgs() {
return [this.src_].concat(this.Args());
}
/**
* @override
*/
Name() {
return 'ChannelAdjust';
}
/**
* Create a ChannelAdjust object. Adjusts color channel saturation.
* @param {string} src
* @param {string} channel A valid Image Magick channel.
* @param {string|number} value Can be an rgba value 0-255 or a percent string (e.g. 10%, 15%, etc).
* @returns {ChannelAdjust} Returns a ChannelAdjust object. If inputs are invalid, it returns null.
*/
static Create(src, channel, value) {
if (!src || !channel || !value)
return null;
return new ChannelAdjust(src, channel, value);
}
}
//------------------------------------
// AUTO LEVEL
class AutoLevel extends ColorBaseClass {
constructor(src) {
super();
this.src_ = src;
}
/**
* @returns {Array<string|number>} Returns an array of image magick arguments associated with this layer.
*/
Args() {
return ['-auto-level'];
}
/**
* @returns {Array<string|number>} Returns an array of arguments used for rendering this layer.
*/
RenderArgs() {
return [this.src_].concat(this.Args());
}
/**
* @override
*/
Name() {
return 'AutoLevel';
}
/**
* Create a AutoLevel object. Renders an image whose colors are normalized (brightened). Makes really dark compare/difference images easier to analyze.
* @param {string} src
* @returns {AutoLevel} Returns a AutoLevel object. If inputs are invalid, it returns null.
*/
static Create(src) {
if (!src)
return null;
return new ChannelAdjust(src);
}
}
//----------------------------
// BRIGHTNESS
class Brightness extends ColorBaseClass {
constructor(src, value) {
super();
this.src_ = src;
this.value_ = value;
}
/**
* @returns {Array<string|number>} Returns an array of image magick arguments associated with this layer.
*/
Args() {
return ['-modulate', this.value_];
}
/**
* @returns {Array<string|number>} Returns an array of arguments used for rendering this layer.
*/
RenderArgs() {
return [this.src_].concat(this.Args());
}
/**
* @override
*/
Name() {
return 'Brightness';
}
/**
* Create a Brightness object. Modifies an image's brightness.
* @param {string} src
* @param {number} value Brightness value between 0 and 200. A value of 100 will make no changes.
* @returns {Brightness} Returns a Brightness object. If inputs are invalid, it returns null.
*/
static Create(src, value) {
if (!src || !value)
return null;
return new Brightness(src, value);
}
}
//----------------------------
// SATURATION
class Saturation extends ColorBaseClass {
constructor(src, value) {
super();
this.src_ = src;
this.value_ = value;
}
/**
* @returns {Array<string|number>} Returns an array of image magick arguments associated with this layer.
*/
Args() {
return ['-modulate', `100,${this.value_}`];
}
/**
* @returns {Array<string|number>} Returns an array of arguments used for rendering this layer.
*/
RenderArgs() {
return [this.src_].concat(this.Args());
}
/**
* @override
*/
Name() {
return 'Saturation';
}
/**
* Create a Saturation object. Modifies an image's saturation levels.
* @param {string} src
* @param {number} value Saturation value between 0 and 200. A value of 100 will make no changes.
* @returns {Brightness} Returns a Brightness object. If inputs are invalid, it returns null.
*/
static Create(src, value) {
if (!src || !value)
return null;
return new Saturation(src, value);
}
}
//---------------------------
// HUE
class Hue extends ColorBaseClass {
constructor(src, value) {
super();
this.src_ = src;
this.value_ = value;
}
/**
* @returns {Array<string|number>} Returns an array of image magick arguments associated with this layer.
*/
Args() {
return ['-modulate', `100,100,${this.value_}`];
}
/**
* @returns {Array<string|number>} Returns an array of arguments used for rendering this layer.
*/
RenderArgs() {
return [this.src_].concat(this.Args());
}
/**
* @override
*/
Name() {
return 'Hue';
}
/**
* Create a Hue object. Modifies an image's hue.
* @param {string} src
* @param {number} value Hue value between 0 and 200. A value of 100 will make no changes.
* @returns {Brightness} Returns a Brightness object. If inputs are invalid, it returns null.
*/
static Create(src, value) {
if (!src || !value)
return null;
return new Hue(src, value);
}
}
//----------------------------
// CHANNELS
/**
* @returns {Promise<Array<string>>} Returns a Promise. If it resolves, it returns a list of color channel names. Otherwise, it returns an error.
*/
function Channels() {
let args = ['-list', 'channel']; // convert: prints newline delimited list of channels
LOCAL_COMMAND.Execute('convert', args).then(output => {
if (output.stderr) {
reject(`Failed to make transparent: ${output.stderr}`);
return;
}
let names = output.stdout.split('\n').filter(line => line && line != '' && line.trim() != '').map(line => line.trim());
resolve(names);
}).catch(error => `Failed to make transparent: ${error}`);
}
//------------------------------
// EXPORTS
exports.Color = Color;
exports.CreateNegateMod = Negate.Create;
exports.CreateColorizeMod = Colorize.Create;
exports.CreateGrayscaleFormatMod = GrayscaleFormat.Create;
exports.CreateRgbFormatMod = RgbFormat.Create;
exports.CreateReplaceMod = Replace.Create;
exports.CreateTransparencyMod = Transparency.Create;
exports.CreateChannelAdjustMod = ChannelAdjust.Create;
exports.CreateAutoLevelMod = AutoLevel.Create;
exports.CreateBrightnessMod = Brightness.Create;
exports.CreateSaturationMod = Saturation.Create;
exports.CreateHueMod = Hue.Create;
exports.Channels = Channels;