/*
James Cryer / Huddle
URL: https://github.com/Huddle/Resemble.js
*/

(function(root, factory) {
    "use strict";
    if (typeof define === "function" && define.amd) {
        define([], factory);
    } else if (typeof module === "object" && module.exports) {
        module.exports = factory();
    } else {
        root.resemble = factory();
    }
})(this, function() {
    "use strict";

    var Img;
    var Canvas;

    if (typeof Image !== "undefined") {
        Img = Image;
    } else {
        Canvas = require("canvas-prebuilt"); // eslint-disable-line global-require
        Img = Canvas.Image;
    }

    var document =
        typeof window !== "undefined"
            ? window.document
            : {
                  createElement: function() {
                      // This will work as long as only createElement is used on window.document
                      return new Canvas();
                  }
              };

    var oldGlobalSettings = {};
    var globalOutputSettings = oldGlobalSettings;

    function setGlobalOutputSettings(settings) {
        var msg =
            "warning resemble.outputSettings mutates global state, and " +
            "will be removed in 3.0.0";
        console.warn(msg);
        globalOutputSettings = settings;
        return this;
    }

    var resemble = function(fileData) {
        var pixelTransparency = 1;

        var errorPixelColor = {
            // Color for Error Pixels. Between 0 and 255.
            red: 255,
            green: 0,
            blue: 255,
            alpha: 255
        };

        var targetPix = { r: 0, g: 0, b: 0, a: 0 }; // isAntialiased

        function colorsDistance(c1, c2) {
            return (
                (Math.abs(c1.r - c2.r) +
                    Math.abs(c1.g - c2.g) +
                    Math.abs(c1.b - c2.b)) /
                3
            );
        }

        function withinBoundingBox(x, y, width, height, box) {
            return (
                x > (box.left || 0) &&
                x < (box.right || width) &&
                y > (box.top || 0) &&
                y < (box.bottom || height)
            );
        }

        function withinComparedArea(x, y, width, height) {
            var isIncluded = true;

            if (
                boundingBox !== undefined &&
                !withinBoundingBox(x, y, width, height, boundingBox)
            ) {
                isIncluded = false;
            }

            if (
                ignoredBox !== undefined &&
                withinBoundingBox(x, y, width, height, ignoredBox)
            ) {
                isIncluded = false;
            }

            return isIncluded;
        }

        var errorPixelTransform = {
            flat: function(px, offset) {
                px[offset] = errorPixelColor.red;
                px[offset + 1] = errorPixelColor.green;
                px[offset + 2] = errorPixelColor.blue;
                px[offset + 3] = errorPixelColor.alpha;
            },
            movement: function(px, offset, d1, d2) {
                px[offset] =
                    (d2.r * (errorPixelColor.red / 255) + errorPixelColor.red) /
                    2;
                px[offset + 1] =
                    (d2.g * (errorPixelColor.green / 255) +
                        errorPixelColor.green) /
                    2;
                px[offset + 2] =
                    (d2.b * (errorPixelColor.blue / 255) +
                        errorPixelColor.blue) /
                    2;
                px[offset + 3] = d2.a;
            },
            flatDifferenceIntensity: function(px, offset, d1, d2) {
                px[offset] = errorPixelColor.red;
                px[offset + 1] = errorPixelColor.green;
                px[offset + 2] = errorPixelColor.blue;
                px[offset + 3] = colorsDistance(d1, d2);
            },
            movementDifferenceIntensity: function(px, offset, d1, d2) {
                var ratio = colorsDistance(d1, d2) / 255 * 0.8;

                px[offset] =
                    (1 - ratio) * (d2.r * (errorPixelColor.red / 255)) +
                    ratio * errorPixelColor.red;
                px[offset + 1] =
                    (1 - ratio) * (d2.g * (errorPixelColor.green / 255)) +
                    ratio * errorPixelColor.green;
                px[offset + 2] =
                    (1 - ratio) * (d2.b * (errorPixelColor.blue / 255)) +
                    ratio * errorPixelColor.blue;
                px[offset + 3] = d2.a;
            },
            diffOnly: function(px, offset, d1, d2) {
                px[offset] = d2.r;
                px[offset + 1] = d2.g;
                px[offset + 2] = d2.b;
                px[offset + 3] = d2.a;
            }
        };

        var errorPixel = errorPixelTransform.flat;
        var errorType;
        var boundingBox;
        var ignoredBox;
        var largeImageThreshold = 1200;
        var useCrossOrigin = true;
        var data = {};
        var images = [];
        var updateCallbackArray = [];

        var tolerance = {
            // between 0 and 255
            red: 16,
            green: 16,
            blue: 16,
            alpha: 16,
            minBrightness: 16,
            maxBrightness: 240
        };

        var ignoreAntialiasing = false;
        var ignoreColors = false;
        var scaleToSameSize = false;

        function triggerDataUpdate() {
            var len = updateCallbackArray.length;
            var i;
            for (i = 0; i < len; i++) {
                if (typeof updateCallbackArray[i] === "function") {
                    updateCallbackArray[i](data);
                }
            }
        }

        function loop(w, h, callback) {
            var x;
            var y;

            for (x = 0; x < w; x++) {
                for (y = 0; y < h; y++) {
                    callback(x, y);
                }
            }
        }

        function parseImage(sourceImageData, width, height) {
            var pixelCount = 0;
            var redTotal = 0;
            var greenTotal = 0;
            var blueTotal = 0;
            var alphaTotal = 0;
            var brightnessTotal = 0;
            var whiteTotal = 0;
            var blackTotal = 0;

            loop(width, height, function(horizontalPos, verticalPos) {
                var offset = (verticalPos * width + horizontalPos) * 4;
                var red = sourceImageData[offset];
                var green = sourceImageData[offset + 1];
                var blue = sourceImageData[offset + 2];
                var alpha = sourceImageData[offset + 3];
                var brightness = getBrightness(red, green, blue);

                if (red === green && red === blue && alpha) {
                    if (red === 0) {
                        blackTotal++;
                    } else if (red === 255) {
                        whiteTotal++;
                    }
                }

                pixelCount++;

                redTotal += red / 255 * 100;
                greenTotal += green / 255 * 100;
                blueTotal += blue / 255 * 100;
                alphaTotal += (255 - alpha) / 255 * 100;
                brightnessTotal += brightness / 255 * 100;
            });

            data.red = Math.floor(redTotal / pixelCount);
            data.green = Math.floor(greenTotal / pixelCount);
            data.blue = Math.floor(blueTotal / pixelCount);
            data.alpha = Math.floor(alphaTotal / pixelCount);
            data.brightness = Math.floor(brightnessTotal / pixelCount);
            data.white = Math.floor(whiteTotal / pixelCount * 100);
            data.black = Math.floor(blackTotal / pixelCount * 100);

            triggerDataUpdate();
        }

        function loadImageData(fileDataForImage, callback) {
            var fileReader;
            var hiddenImage = new Img();

            if (!hiddenImage.setAttribute) {
                hiddenImage.setAttribute = function setAttribute() {};
            }

            if (useCrossOrigin) {
                hiddenImage.setAttribute("crossorigin", "anonymous");
            }

            hiddenImage.onerror = function(err) {
                hiddenImage.onload = null;
                hiddenImage.onerror = null; // fixes pollution between calls
                images.push({ error: err ? err + "" : "Image load error." });
                callback();
            };

            hiddenImage.onload = function() {
                hiddenImage.onload = null; // fixes pollution between calls
                hiddenImage.onerror = null;

                var hiddenCanvas = document.createElement("canvas");
                var imageData;

                // don't assign to hiddenImage, see https://github.com/Huddle/Resemble.js/pull/87/commits/300d43352a2845aad289b254bfbdc7cd6a37e2d7
                var width = hiddenImage.width;
                var height = hiddenImage.height;

                if (scaleToSameSize && images.length === 1) {
                    width = images[0].width;
                    height = images[0].height;
                }

                hiddenCanvas.width = width;
                hiddenCanvas.height = height;

                hiddenCanvas
                    .getContext("2d")
                    .drawImage(hiddenImage, 0, 0, width, height);
                imageData = hiddenCanvas
                    .getContext("2d")
                    .getImageData(0, 0, width, height);

                images.push(imageData);

                callback(imageData, width, height);
            };

            if (typeof fileDataForImage === "string") {
                hiddenImage.src = fileDataForImage;
                if (hiddenImage.complete && hiddenImage.naturalWidth > 0) {
                    hiddenImage.onload();
                }
            } else if (
                typeof fileDataForImage.data !== "undefined" &&
                typeof fileDataForImage.width === "number" &&
                typeof fileDataForImage.height === "number"
            ) {
                images.push(fileDataForImage);

                callback(
                    fileDataForImage,
                    fileDataForImage.width,
                    fileDataForImage.height
                );
            } else if (
                typeof Buffer !== "undefined" &&
                fileDataForImage instanceof Buffer
            ) {
                // If we have Buffer, assume we're on Node+Canvas and its supported
                hiddenImage.src = fileDataForImage;
            } else {
                fileReader = new FileReader();
                fileReader.onload = function(event) {
                    hiddenImage.src = event.target.result;
                };
                fileReader.readAsDataURL(fileDataForImage);
            }
        }

        function isColorSimilar(a, b, color) {
            var absDiff = Math.abs(a - b);

            if (typeof a === "undefined") {
                return false;
            }
            if (typeof b === "undefined") {
                return false;
            }

            if (a === b) {
                return true;
            } else if (absDiff < tolerance[color]) {
                return true;
            }
            return false;
        }

        function isPixelBrightnessSimilar(d1, d2) {
            var alpha = isColorSimilar(d1.a, d2.a, "alpha");
            var brightness = isColorSimilar(
                d1.brightness,
                d2.brightness,
                "minBrightness"
            );
            return brightness && alpha;
        }

        function getBrightness(r, g, b) {
            return 0.3 * r + 0.59 * g + 0.11 * b;
        }

        function isRGBSame(d1, d2) {
            var red = d1.r === d2.r;
            var green = d1.g === d2.g;
            var blue = d1.b === d2.b;
            return red && green && blue;
        }

        function isRGBSimilar(d1, d2) {
            var red = isColorSimilar(d1.r, d2.r, "red");
            var green = isColorSimilar(d1.g, d2.g, "green");
            var blue = isColorSimilar(d1.b, d2.b, "blue");
            var alpha = isColorSimilar(d1.a, d2.a, "alpha");

            return red && green && blue && alpha;
        }

        function isContrasting(d1, d2) {
            return (
                Math.abs(d1.brightness - d2.brightness) >
                tolerance.maxBrightness
            );
        }

        function getHue(red, green, blue) {
            var r = red / 255;
            var g = green / 255;
            var b = blue / 255;
            var max = Math.max(r, g, b);
            var min = Math.min(r, g, b);
            var h;
            var d;

            if (max === min) {
                h = 0; // achromatic
            } else {
                d = max - min;
                switch (max) {
                    case r:
                        h = (g - b) / d + (g < b ? 6 : 0);
                        break;
                    case g:
                        h = (b - r) / d + 2;
                        break;
                    case b:
                        h = (r - g) / d + 4;
                        break;
                    default:
                        h /= 6;
                }
            }

            return h;
        }

        function isAntialiased(
            sourcePix,
            pix,
            cacheSet,
            verticalPos,
            horizontalPos,
            width
        ) {
            var offset;
            var distance = 1;
            var i;
            var j;
            var hasHighContrastSibling = 0;
            var hasSiblingWithDifferentHue = 0;
            var hasEquivalentSibling = 0;

            addHueInfo(sourcePix);

            for (i = distance * -1; i <= distance; i++) {
                for (j = distance * -1; j <= distance; j++) {
                    if (i === 0 && j === 0) {
                        // ignore source pixel
                    } else {
                        offset =
                            ((verticalPos + j) * width + (horizontalPos + i)) *
                            4;

                        if (!getPixelInfo(targetPix, pix, offset, cacheSet)) {
                            continue;
                        }

                        addBrightnessInfo(targetPix);
                        addHueInfo(targetPix);

                        if (isContrasting(sourcePix, targetPix)) {
                            hasHighContrastSibling++;
                        }

                        if (isRGBSame(sourcePix, targetPix)) {
                            hasEquivalentSibling++;
                        }

                        if (Math.abs(targetPix.h - sourcePix.h) > 0.3) {
                            hasSiblingWithDifferentHue++;
                        }

                        if (
                            hasSiblingWithDifferentHue > 1 ||
                            hasHighContrastSibling > 1
                        ) {
                            return true;
                        }
                    }
                }
            }

            if (hasEquivalentSibling < 2) {
                return true;
            }

            return false;
        }

        function copyPixel(px, offset, pix) {
            if (errorType === "diffOnly") {
                return;
            }

            px[offset] = pix.r; // r
            px[offset + 1] = pix.g; // g
            px[offset + 2] = pix.b; // b
            px[offset + 3] = pix.a * pixelTransparency; // a
        }

        function copyGrayScalePixel(px, offset, pix) {
            if (errorType === "diffOnly") {
                return;
            }

            px[offset] = pix.brightness; // r
            px[offset + 1] = pix.brightness; // g
            px[offset + 2] = pix.brightness; // b
            px[offset + 3] = pix.a * pixelTransparency; // a
        }

        function getPixelInfo(dst, pix, offset) {
            if (pix.length > offset) {
                dst.r = pix[offset];
                dst.g = pix[offset + 1];
                dst.b = pix[offset + 2];
                dst.a = pix[offset + 3];

                return true;
            }

            return false;
        }

        function addBrightnessInfo(pix) {
            pix.brightness = getBrightness(pix.r, pix.g, pix.b); // 'corrected' lightness
        }

        function addHueInfo(pix) {
            pix.h = getHue(pix.r, pix.g, pix.b);
        }

        function analyseImages(img1, img2, width, height) {
            var hiddenCanvas = document.createElement("canvas");

            var data1 = img1.data;
            var data2 = img2.data;

            hiddenCanvas.width = width;
            hiddenCanvas.height = height;

            var context = hiddenCanvas.getContext("2d");
            var imgd = context.createImageData(width, height);
            var pix = imgd.data;

            var mismatchCount = 0;
            var diffBounds = {
                top: height,
                left: width,
                bottom: 0,
                right: 0
            };
            var updateBounds = function(x, y) {
                diffBounds.left = Math.min(x, diffBounds.left);
                diffBounds.right = Math.max(x, diffBounds.right);
                diffBounds.top = Math.min(y, diffBounds.top);
                diffBounds.bottom = Math.max(y, diffBounds.bottom);
            };

            var time = Date.now();

            var skip;

            if (
                !!largeImageThreshold &&
                ignoreAntialiasing &&
                (width > largeImageThreshold || height > largeImageThreshold)
            ) {
                skip = 6;
            }

            var pixel1 = { r: 0, g: 0, b: 0, a: 0 };
            var pixel2 = { r: 0, g: 0, b: 0, a: 0 };

            loop(width, height, function(horizontalPos, verticalPos) {
                if (skip) {
                    // only skip if the image isn't small
                    if (
                        verticalPos % skip === 0 ||
                        horizontalPos % skip === 0
                    ) {
                        return;
                    }
                }

                var offset = (verticalPos * width + horizontalPos) * 4;
                var isWithinComparedArea = withinComparedArea(
                    horizontalPos,
                    verticalPos,
                    width,
                    height
                );

                if (
                    !getPixelInfo(pixel1, data1, offset, 1) ||
                    !getPixelInfo(pixel2, data2, offset, 2)
                ) {
                    return;
                }

                if (ignoreColors) {
                    addBrightnessInfo(pixel1);
                    addBrightnessInfo(pixel2);

                    if (
                        isPixelBrightnessSimilar(pixel1, pixel2) ||
                        !isWithinComparedArea
                    ) {
                        copyGrayScalePixel(pix, offset, pixel2);
                    } else {
                        errorPixel(pix, offset, pixel1, pixel2);
                        mismatchCount++;
                        updateBounds(horizontalPos, verticalPos);
                    }
                    return;
                }

                if (isRGBSimilar(pixel1, pixel2) || !isWithinComparedArea) {
                    copyPixel(pix, offset, pixel1);
                } else if (
                    ignoreAntialiasing &&
                    (addBrightnessInfo(pixel1), // jit pixel info augmentation looks a little weird, sorry.
                    addBrightnessInfo(pixel2),
                    isAntialiased(
                        pixel1,
                        data1,
                        1,
                        verticalPos,
                        horizontalPos,
                        width
                    ) ||
                        isAntialiased(
                            pixel2,
                            data2,
                            2,
                            verticalPos,
                            horizontalPos,
                            width
                        ))
                ) {
                    if (
                        isPixelBrightnessSimilar(pixel1, pixel2) ||
                        !isWithinComparedArea
                    ) {
                        copyGrayScalePixel(pix, offset, pixel2);
                    } else {
                        errorPixel(pix, offset, pixel1, pixel2);
                        mismatchCount++;
                        updateBounds(horizontalPos, verticalPos);
                    }
                } else {
                    errorPixel(pix, offset, pixel1, pixel2);
                    mismatchCount++;
                    updateBounds(horizontalPos, verticalPos);
                }
            });

            data.rawMisMatchPercentage = mismatchCount / (height * width) * 100;
            data.misMatchPercentage = data.rawMisMatchPercentage.toFixed(2);
            data.diffBounds = diffBounds;
            data.analysisTime = Date.now() - time;

            data.getImageDataUrl = function(text) {
                var barHeight = 0;

                if (text) {
                    barHeight = addLabel(text, context, hiddenCanvas);
                }

                context.putImageData(imgd, 0, barHeight);

                return hiddenCanvas.toDataURL("image/png");
            };

            if (hiddenCanvas.toBuffer) {
                data.getBuffer = function(includeOriginal) {
                    if (includeOriginal) {
                        var imageWidth = hiddenCanvas.width + 2;
                        hiddenCanvas.width = imageWidth * 3;
                        context.putImageData(img1, 0, 0);
                        context.putImageData(img2, imageWidth, 0);
                        context.putImageData(imgd, imageWidth * 2, 0);
                    } else {
                        context.putImageData(imgd, 0, 0);
                    }
                    return hiddenCanvas.toBuffer();
                };
            }
        }

        function addLabel(text, context, hiddenCanvas) {
            var textPadding = 2;

            context.font = "12px sans-serif";

            var textWidth = context.measureText(text).width + textPadding * 2;
            var barHeight = 22;

            if (textWidth > hiddenCanvas.width) {
                hiddenCanvas.width = textWidth;
            }

            hiddenCanvas.height += barHeight;

            context.fillStyle = "#666";
            context.fillRect(0, 0, hiddenCanvas.width, barHeight - 4);
            context.fillStyle = "#fff";
            context.fillRect(0, barHeight - 4, hiddenCanvas.width, 4);

            context.fillStyle = "#fff";
            context.textBaseline = "top";
            context.font = "12px sans-serif";
            context.fillText(text, textPadding, 1);

            return barHeight;
        }

        function normalise(img, w, h) {
            var c;
            var context;

            if (img.height < h || img.width < w) {
                c = document.createElement("canvas");
                c.width = w;
                c.height = h;
                context = c.getContext("2d");
                context.putImageData(img, 0, 0);
                return context.getImageData(0, 0, w, h);
            }

            return img;
        }

        function outputSettings(options) {
            var key;

            if (options.errorColor) {
                for (key in options.errorColor) {
                    if (options.errorColor.hasOwnProperty(key)) {
                        errorPixelColor[key] =
                            options.errorColor[key] === void 0
                                ? errorPixelColor[key]
                                : options.errorColor[key];
                    }
                }
            }

            if (options.errorType && errorPixelTransform[options.errorType]) {
                errorPixel = errorPixelTransform[options.errorType];
                errorType = options.errorType;
            }

            if (
                options.errorPixel &&
                typeof options.errorPixel === "function"
            ) {
                errorPixel = options.errorPixel;
            }

            pixelTransparency = isNaN(Number(options.transparency))
                ? pixelTransparency
                : options.transparency;

            if (options.largeImageThreshold !== undefined) {
                largeImageThreshold = options.largeImageThreshold;
            }

            if (options.useCrossOrigin !== undefined) {
                useCrossOrigin = options.useCrossOrigin;
            }

            if (options.boundingBox !== undefined) {
                boundingBox = options.boundingBox;
            }

            if (options.ignoredBox !== undefined) {
                ignoredBox = options.ignoredBox;
            }
        }

        function compare(one, two) {
            if (globalOutputSettings !== oldGlobalSettings) {
                outputSettings(globalOutputSettings);
            }

            function onceWeHaveBoth() {
                var width;
                var height;
                if (images.length === 2) {
                    if (images[0].error || images[1].error) {
                        data = {};
                        data.error = images[0].error
                            ? images[0].error
                            : images[1].error;
                        triggerDataUpdate();
                        return;
                    }
                    width =
                        images[0].width > images[1].width
                            ? images[0].width
                            : images[1].width;
                    height =
                        images[0].height > images[1].height
                            ? images[0].height
                            : images[1].height;

                    if (
                        images[0].width === images[1].width &&
                        images[0].height === images[1].height
                    ) {
                        data.isSameDimensions = true;
                    } else {
                        data.isSameDimensions = false;
                    }

                    data.dimensionDifference = {
                        width: images[0].width - images[1].width,
                        height: images[0].height - images[1].height
                    };

                    analyseImages(
                        normalise(images[0], width, height),
                        normalise(images[1], width, height),
                        width,
                        height
                    );

                    triggerDataUpdate();
                }
            }

            images = [];
            loadImageData(one, onceWeHaveBoth);
            loadImageData(two, onceWeHaveBoth);
        }

        function getCompareApi(param) {
            var secondFileData;
            var hasMethod = typeof param === "function";

            if (!hasMethod) {
                // assume it's file data
                secondFileData = param;
            }

            var self = {
                scaleToSameSize: function() {
                    scaleToSameSize = true;

                    if (hasMethod) {
                        param();
                    }
                    return self;
                },
                useOriginalSize: function() {
                    scaleToSameSize = false;

                    if (hasMethod) {
                        param();
                    }
                    return self;
                },
                ignoreNothing: function() {
                    tolerance.red = 0;
                    tolerance.green = 0;
                    tolerance.blue = 0;
                    tolerance.alpha = 0;
                    tolerance.minBrightness = 0;
                    tolerance.maxBrightness = 255;

                    ignoreAntialiasing = false;
                    ignoreColors = false;

                    if (hasMethod) {
                        param();
                    }
                    return self;
                },
                ignoreLess: function() {
                    tolerance.red = 16;
                    tolerance.green = 16;
                    tolerance.blue = 16;
                    tolerance.alpha = 16;
                    tolerance.minBrightness = 16;
                    tolerance.maxBrightness = 240;

                    ignoreAntialiasing = false;
                    ignoreColors = false;

                    if (hasMethod) {
                        param();
                    }
                    return self;
                },
                ignoreAntialiasing: function() {
                    tolerance.red = 32;
                    tolerance.green = 32;
                    tolerance.blue = 32;
                    tolerance.alpha = 32;
                    tolerance.minBrightness = 64;
                    tolerance.maxBrightness = 96;

                    ignoreAntialiasing = true;
                    ignoreColors = false;

                    if (hasMethod) {
                        param();
                    }
                    return self;
                },
                ignoreColors: function() {
                    tolerance.alpha = 16;
                    tolerance.minBrightness = 16;
                    tolerance.maxBrightness = 240;

                    ignoreAntialiasing = false;
                    ignoreColors = true;

                    if (hasMethod) {
                        param();
                    }
                    return self;
                },
                ignoreAlpha: function() {
                    tolerance.red = 16;
                    tolerance.green = 16;
                    tolerance.blue = 16;
                    tolerance.alpha = 255;
                    tolerance.minBrightness = 16;
                    tolerance.maxBrightness = 240;

                    ignoreAntialiasing = false;
                    ignoreColors = false;

                    if (hasMethod) {
                        param();
                    }
                    return self;
                },
                repaint: function() {
                    if (hasMethod) {
                        param();
                    }
                    return self;
                },
                outputSettings: function(options) {
                    outputSettings(options);
                    return self;
                },
                onComplete: function(callback) {
                    updateCallbackArray.push(callback);

                    var wrapper = function() {
                        compare(fileData, secondFileData);
                    };

                    wrapper();

                    return getCompareApi(wrapper);
                }
            };

            return self;
        }

        var rootSelf = {
            onComplete: function(callback) {
                updateCallbackArray.push(callback);
                loadImageData(fileData, function(imageData, width, height) {
                    parseImage(imageData.data, width, height);
                });
            },
            compareTo: function(secondFileData) {
                return getCompareApi(secondFileData);
            },
            outputSettings: function(options) {
                outputSettings(options);
                return rootSelf;
            }
        };
        return rootSelf;
    };

    function applyIgnore(api, ignore) {
        switch (ignore) {
            case "nothing":
                api.ignoreNothing();
                break;
            case "less":
                api.ignoreLess();
                break;
            case "antialiasing":
                api.ignoreAntialiasing();
                break;
            case "colors":
                api.ignoreColors();
                break;
            case "alpha":
                api.ignoreAlpha();
                break;
            default:
                throw new Error("Invalid ignore: " + ignore);
        }
    }

    resemble.compare = function(image1, image2, options, cb) {
        var callback;
        var opt;

        if (typeof options === "function") {
            callback = options;
            opt = {};
        } else {
            callback = cb;
            opt = options || {};
        }

        var res = resemble(image1);
        var compare;

        if (opt.output) {
            res.outputSettings(opt.output);
        }

        compare = res.compareTo(image2);

        if (opt.scaleToSameSize) {
            compare.scaleToSameSize();
        }

        if (typeof opt.ignore === "string") {
            applyIgnore(compare, opt.ignore);
        } else if (opt.ignore && opt.ignore.forEach) {
            opt.ignore.forEach(function(v) {
                applyIgnore(compare, v);
            });
        }

        compare.onComplete(function(data) {
            if (data.error) {
                callback(data.error);
            } else {
                callback(null, data);
            }
        });
    };

    resemble.outputSettings = setGlobalOutputSettings;
    return resemble;
});
