'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var fs = require('fs');
var Path = require('path');
var EventEmitter = require('events');
var index$1 = require('../../utils/es/index.js');
var index = require('../../../any-base/index.js');
var index$3 = require('../../../pixelmatch/index.js');
var tinycolor = require('../../../tinycolor2/esm/tinycolor.js');
var phash = require('./modules/phash.js');
var request = require('./request.js');
var index$2 = require('./composite/index.js');
var promisify = require('./utils/promisify.js');
var mime = require('./utils/mime.js');
var imageBitmap = require('./utils/image-bitmap.js');
var constants = require('./constants.js');

function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
const alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_";

// an array storing the maximum string length of hashes at various bases
// 0 and 1 do not exist as possible hash lengths
const maxHashLength = [NaN, NaN];
for (let i = 2; i < 65; i++) {
  const maxHash = index(index.BIN, alphabet.slice(0, i))(new Array(64 + 1).join("1"));
  maxHashLength.push(maxHash.length);
}

// no operation
function noop() {}

// error checking methods

function isArrayBuffer(test) {
  return Object.prototype.toString.call(test).toLowerCase().indexOf("arraybuffer") > -1;
}

// Prepare a Buffer object from the arrayBuffer. Necessary in the browser > node conversion,
// But this function is not useful when running in node directly
function bufferFromArrayBuffer(arrayBuffer) {
  const buffer = Buffer.alloc(arrayBuffer.byteLength);
  const view = new Uint8Array(arrayBuffer);
  for (let i = 0; i < buffer.length; ++i) {
    buffer[i] = view[i];
  }
  return buffer;
}
function loadFromURL(options, cb) {
  request(options, (err, data) => {
    if (err) {
      return cb(err);
    }
    if (typeof data === "object" && Buffer.isBuffer(data)) {
      return cb(null, data);
    }
    if (typeof data === "object" && isArrayBuffer(data)) {
      return cb(null, bufferFromArrayBuffer(data));
    }
    return new Error(`Could not load Buffer from <${options.url}>`);
  });
}
function loadBufferFromPath(src, cb) {
  if (fs && typeof fs.readFile === "function" && !src.match(/^(http|ftp)s?:\/\/./)) {
    fs.readFile(src, cb);
  } else {
    loadFromURL({
      url: src
    }, cb);
  }
}
function isRawRGBAData(obj) {
  return obj && typeof obj === "object" && typeof obj.width === "number" && typeof obj.height === "number" && (Buffer.isBuffer(obj.data) || obj.data instanceof Uint8Array || typeof Uint8ClampedArray === "function" && obj.data instanceof Uint8ClampedArray) && (obj.data.length === obj.width * obj.height * 4 || obj.data.length === obj.width * obj.height * 3);
}
function makeRGBABufferFromRGB(buffer) {
  if (buffer.length % 3 !== 0) {
    throw new Error("Buffer length is incorrect");
  }
  const rgbaBuffer = Buffer.allocUnsafe(buffer.length / 3 * 4);
  let j = 0;
  for (let i = 0; i < buffer.length; i++) {
    rgbaBuffer[j] = buffer[i];
    if ((i + 1) % 3 === 0) {
      rgbaBuffer[++j] = 255;
    }
    j++;
  }
  return rgbaBuffer;
}
const emptyBitmap = {
  data: null,
  width: null,
  height: null
};

/**
 * Jimp constructor (from a file)
 * @param path a path to the image
 * @param {function(Error, Jimp)} cb (optional) a function to call when the image is parsed to a bitmap
 */

/**
 * Jimp constructor (from a url with options)
 * @param options { url, otherOptions}
 * @param {function(Error, Jimp)} cb (optional) a function to call when the image is parsed to a bitmap
 */

/**
 * Jimp constructor (from another Jimp image or raw image data)
 * @param image a Jimp image to clone
 * @param {function(Error, Jimp)} cb a function to call when the image is parsed to a bitmap
 */

/**
 * Jimp constructor (from a Buffer)
 * @param data a Buffer containing the image data
 * @param {function(Error, Jimp)} cb a function to call when the image is parsed to a bitmap
 */

/**
 * Jimp constructor (to generate a new image)
 * @param w the width of the image
 * @param h the height of the image
 * @param {function(Error, Jimp)} cb (optional) a function to call when the image is parsed to a bitmap
 */

/**
 * Jimp constructor (to generate a new image)
 * @param w the width of the image
 * @param h the height of the image
 * @param background color to fill the image with
 * @param {function(Error, Jimp)} cb (optional) a function to call when the image is parsed to a bitmap
 */

class Jimp extends EventEmitter {
  // An object representing a bitmap in memory, comprising:
  //  - data: a buffer of the bitmap data
  //  - width: the width of the image in pixels
  //  - height: the height of the image in pixels

  // Default colour to use for new pixels

  // Default MIME is PNG

  // Exif data for the image

  // Whether Transparency supporting formats will be exported as RGB or RGBA

  constructor() {
    for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
      args[_key] = arguments[_key];
    }
    super();
    _defineProperty(this, "bitmap", emptyBitmap);
    _defineProperty(this, "_background", 0x00000000);
    _defineProperty(this, "_originalMime", Jimp.MIME_PNG);
    _defineProperty(this, "_exif", null);
    _defineProperty(this, "_rgba", true);
    _defineProperty(this, "writeAsync", path => promisify(this.write, this, path));
    _defineProperty(this, "getBase64Async", mime => promisify(this.getBase64, this, mime));
    _defineProperty(this, "getBuffer", imageBitmap.getBuffer);
    _defineProperty(this, "getBufferAsync", imageBitmap.getBufferAsync);
    _defineProperty(this, "getPixelColour", this.getPixelColor);
    _defineProperty(this, "setPixelColour", this.setPixelColor);
    const jimpInstance = this;
    let cb = noop;
    if (isArrayBuffer(args[0])) {
      args[0] = bufferFromArrayBuffer(args[0]);
    }
    function finish() {
      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
        args[_key2] = arguments[_key2];
      }
      const [err] = args;
      const evData = err || {};
      evData.methodName = "constructor";
      setTimeout(() => {
        // run on next tick.
        if (err && cb === noop) {
          jimpInstance.emitError("constructor", err);
        } else if (!err) {
          jimpInstance.emitMulti("constructor", "initialized");
        }
        cb.call(jimpInstance, ...args);
      }, 1);
    }
    if (typeof args[0] === "number" && typeof args[1] === "number" || parseInt(args[0], 10) && parseInt(args[1], 10)) {
      // create a new image
      const w = parseInt(args[0], 10);
      const h = parseInt(args[1], 10);
      cb = args[2];

      // with a hex color
      if (typeof args[2] === "number") {
        this._background = args[2];
        cb = args[3];
      }

      // with a css color
      if (typeof args[2] === "string") {
        this._background = Jimp.cssColorToHex(args[2]);
        cb = args[3];
      }
      if (typeof cb === "undefined") {
        cb = noop;
      }
      if (typeof cb !== "function") {
        return index$1.throwError.call(this, "cb must be a function", finish);
      }
      this.bitmap = {
        data: Buffer.alloc(w * h * 4),
        width: w,
        height: h
      };
      for (let i = 0; i < this.bitmap.data.length; i += 4) {
        this.bitmap.data.writeUInt32BE(this._background, i);
      }
      finish(null, this);
    } else if (typeof args[0] === "object" && args[0].url) {
      cb = args[1] || noop;
      if (typeof cb !== "function") {
        return index$1.throwError.call(this, "cb must be a function", finish);
      }
      loadFromURL(args[0], (err, data) => {
        if (err) {
          return index$1.throwError.call(this, err, finish);
        }
        this.parseBitmap(data, args[0].url, finish);
      });
    } else if (args[0] instanceof Jimp) {
      // clone an existing Jimp
      const [original] = args;
      cb = args[1];
      if (typeof cb === "undefined") {
        cb = noop;
      }
      if (typeof cb !== "function") {
        return index$1.throwError.call(this, "cb must be a function", finish);
      }
      this.bitmap = {
        data: Buffer.from(original.bitmap.data),
        width: original.bitmap.width,
        height: original.bitmap.height
      };
      this._quality = original._quality;
      this._deflateLevel = original._deflateLevel;
      this._deflateStrategy = original._deflateStrategy;
      this._filterType = original._filterType;
      this._rgba = original._rgba;
      this._background = original._background;
      this._originalMime = original._originalMime;
      finish(null, this);
    } else if (isRawRGBAData(args[0])) {
      const [imageData] = args;
      cb = args[1] || noop;
      const isRGBA = imageData.width * imageData.height * 4 === imageData.data.length;
      const buffer = isRGBA ? Buffer.from(imageData.data) : makeRGBABufferFromRGB(imageData.data);
      this.bitmap = {
        data: buffer,
        width: imageData.width,
        height: imageData.height
      };
      finish(null, this);
    } else if (typeof args[0] === "string") {
      // read from a path
      const path = args[0];
      cb = args[1];
      if (typeof cb === "undefined") {
        cb = noop;
      }
      if (typeof cb !== "function") {
        return index$1.throwError.call(this, "cb must be a function", finish);
      }
      loadBufferFromPath(path, (err, data) => {
        if (err) {
          return index$1.throwError.call(this, err, finish);
        }
        this.parseBitmap(data, path, finish);
      });
    } else if (typeof args[0] === "object" && Buffer.isBuffer(args[0])) {
      // read from a buffer
      const data = args[0];
      cb = args[1];
      if (typeof cb !== "function") {
        return index$1.throwError.call(this, "cb must be a function", finish);
      }
      this.parseBitmap(data, null, finish);
    } else {
      // Allow client libs to add new ways to build a Jimp object.
      // Extra constructors must be added by `Jimp.appendConstructorOption()`
      cb = args[args.length - 1];
      if (typeof cb !== "function") {
        // TODO: try to solve the args after cb problem.
        cb = args[args.length - 2];
        if (typeof cb !== "function") {
          cb = noop;
        }
      }
      const extraConstructor = Jimp.__extraConstructors.find(c => c.test(...args));
      if (extraConstructor) {
        new Promise((resolve, reject) => {
          extraConstructor.run.call(this, resolve, reject, ...args);
        }).then(() => finish(null, this)).catch(finish);
      } else {
        return index$1.throwError.call(this, "No matching constructor overloading was found. " + "Please see the docs for how to call the Jimp constructor.", finish);
      }
    }
  }

  /**
   * Parse a bitmap with the loaded image types.
   *
   * @param {Buffer} data raw image data
   * @param {string} path optional path to file
   * @param {function(Error, Jimp)} finish (optional) a callback for when complete
   * @memberof Jimp
   */
  parseBitmap(data, path, finish) {
    imageBitmap.parseBitmap.call(this, data, null, finish);
  }

  /**
   * Sets the type of the image (RGB or RGBA) when saving in a format that supports transparency (default is RGBA)
   * @param {boolean} bool A Boolean, true to use RGBA or false to use RGB
   * @param {function(Error, Jimp)} cb (optional) a callback for when complete
   * @returns {Jimp} this for chaining of methods
   */
  rgba(bool, cb) {
    if (typeof bool !== "boolean") {
      return index$1.throwError.call(this, "bool must be a boolean, true for RGBA or false for RGB", cb);
    }
    this._rgba = bool;
    if (index$1.isNodePattern(cb)) {
      cb.call(this, null, this);
    }
    return this;
  }

  /**
   * Emit for multiple listeners
   * @param {string} methodName name of the method to emit an error for
   * @param {string} eventName name of the eventName to emit an error for
   * @param {object} data to emit
   */
  emitMulti(methodName, eventName) {
    let data = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
    data = Object.assign(data, {
      methodName,
      eventName
    });
    this.emit("any", data);
    if (methodName) {
      this.emit(methodName, data);
    }
    this.emit(eventName, data);
  }
  emitError(methodName, err) {
    this.emitMulti(methodName, "error", err);
  }

  /**
   * Get the current height of the image
   * @return {number} height of the image
   */
  getHeight() {
    return this.bitmap.height;
  }

  /**
   * Get the current width of the image
   * @return {number} width of the image
   */
  getWidth() {
    return this.bitmap.width;
  }

  /**
   * Nicely format Jimp object when sent to the console e.g. console.log(image)
   * @returns {string} pretty printed
   */
  inspect() {
    return "<Jimp " + (this.bitmap === emptyBitmap ? "pending..." : this.bitmap.width + "x" + this.bitmap.height) + ">";
  }

  /**
   * Nicely format Jimp object when converted to a string
   * @returns {string} pretty printed
   */
  toString() {
    return "[object Jimp]";
  }

  /**
   * Returns the original MIME of the image (default: "image/png")
   * @returns {string} the MIME
   */
  getMIME() {
    const mime = this._originalMime || Jimp.MIME_PNG;
    return mime;
  }

  /**
   * Returns the appropriate file extension for the original MIME of the image (default: "png")
   * @returns {string} the file extension
   */
  getExtension() {
    const mime$1 = this.getMIME();
    return mime.getExtension(mime$1);
  }

  /**
   * Writes the image to a file
   * @param {string} path a path to the destination file
   * @param {function(Error, Jimp)} cb (optional) a function to call when the image is saved to disk
   * @returns {Jimp} this for chaining of methods
   */
  write(path, cb) {
    if (!fs || !fs.createWriteStream) {
      throw new Error("Cant access the filesystem. You can use the getBase64 method.");
    }
    if (typeof path !== "string") {
      return index$1.throwError.call(this, "path must be a string", cb);
    }
    if (typeof cb === "undefined") {
      cb = noop;
    }
    if (typeof cb !== "function") {
      return index$1.throwError.call(this, "cb must be a function", cb);
    }
    const mime$1 = mime.getType(path) || this.getMIME();
    const pathObj = Path.parse(path);
    if (pathObj.dir) {
      fs.mkdirSync(pathObj.dir, {
        recursive: true
      });
    }
    this.getBuffer(mime$1, (err, buffer) => {
      if (err) {
        return index$1.throwError.call(this, err, cb);
      }
      const stream = fs.createWriteStream(path);
      stream.on("open", () => {
        stream.write(buffer);
        stream.end();
      }).on("error", err => {
        return index$1.throwError.call(this, err, cb);
      });
      stream.on("finish", () => {
        cb.call(this, null, this);
      });
    });
    return this;
  }
  /**
   * Converts the image to a base 64 string
   * @param {string} mime the mime type of the image data to be created
   * @param {function(Error, Jimp)} cb a Node-style function to call with the buffer as the second argument
   * @returns {Jimp} this for chaining of methods
   */
  getBase64(mime, cb) {
    if (mime === Jimp.AUTO) {
      // allow auto MIME detection
      mime = this.getMIME();
    }
    if (typeof mime !== "string") {
      return index$1.throwError.call(this, "mime must be a string", cb);
    }
    if (typeof cb !== "function") {
      return index$1.throwError.call(this, "cb must be a function", cb);
    }
    this.getBuffer(mime, function (err, data) {
      if (err) {
        return index$1.throwError.call(this, err, cb);
      }
      const src = "data:" + mime + ";base64," + data.toString("base64");
      cb.call(this, null, src);
    });
    return this;
  }
  /**
   * Generates a perceptual hash of the image <https://en.wikipedia.org/wiki/Perceptual_hashing>. And pads the string. Can configure base.
   * @param {number} base (optional) a number between 2 and 64 representing the base for the hash (e.g. 2 is binary, 10 is decimal, 16 is hex, 64 is base 64). Defaults to 64.
   * @param {function(Error, Jimp)} cb (optional) a callback for when complete
   * @returns {string} a string representing the hash
   */
  hash(base, cb) {
    base = base || 64;
    if (typeof base === "function") {
      cb = base;
      base = 64;
    }
    if (typeof base !== "number") {
      return index$1.throwError.call(this, "base must be a number", cb);
    }
    if (base < 2 || base > 64) {
      return index$1.throwError.call(this, "base must be a number between 2 and 64", cb);
    }
    let hash = this.pHash();
    hash = index(index.BIN, alphabet.slice(0, base))(hash);
    while (hash.length < maxHashLength[base]) {
      hash = "0" + hash; // pad out with leading zeros
    }

    if (index$1.isNodePattern(cb)) {
      cb.call(this, null, hash);
    }
    return hash;
  }

  /**
   * Calculates the perceptual hash
   * @returns {number} the perceptual hash
   */
  pHash() {
    const pHash = new phash();
    return pHash.getHash(this);
  }

  /**
   * Calculates the hamming distance of the current image and a hash based on their perceptual hash
   * @param {hash} compareHash hash to compare to
   * @returns {number} a number ranging from 0 to 1, 0 means they are believed to be identical
   */
  distanceFromHash(compareHash) {
    const pHash = new phash();
    const currentHash = pHash.getHash(this);
    return pHash.distance(currentHash, compareHash);
  }

  /**
   * Converts the image to a buffer
   * @param {string} mime the mime type of the image buffer to be created
   * @param {function(Error, Jimp)} cb a Node-style function to call with the buffer as the second argument
   * @returns {Jimp} this for chaining of methods
   */

  /**
   * Returns the offset of a pixel in the bitmap buffer
   * @param {number} x the x coordinate
   * @param {number} y the y coordinate
   * @param {number} edgeHandling (optional) define how to sum pixels from outside the border
   * @param {number} cb (optional) a callback for when complete
   * @returns {number} the index of the pixel or -1 if not found
   */
  getPixelIndex(x, y, edgeHandling, cb) {
    let xi;
    let yi;
    if (typeof edgeHandling === "function" && typeof cb === "undefined") {
      cb = edgeHandling;
      edgeHandling = null;
    }
    if (!edgeHandling) {
      edgeHandling = Jimp.EDGE_EXTEND;
    }
    if (typeof x !== "number" || typeof y !== "number") {
      return index$1.throwError.call(this, "x and y must be numbers", cb);
    }

    // round input
    x = Math.round(x);
    y = Math.round(y);
    xi = x;
    yi = y;
    if (edgeHandling === Jimp.EDGE_EXTEND) {
      if (x < 0) xi = 0;
      if (x >= this.bitmap.width) xi = this.bitmap.width - 1;
      if (y < 0) yi = 0;
      if (y >= this.bitmap.height) yi = this.bitmap.height - 1;
    }
    if (edgeHandling === Jimp.EDGE_WRAP) {
      if (x < 0) {
        xi = this.bitmap.width + x;
      }
      if (x >= this.bitmap.width) {
        xi = x % this.bitmap.width;
      }
      if (y < 0) {
        yi = this.bitmap.height + y;
      }
      if (y >= this.bitmap.height) {
        yi = y % this.bitmap.height;
      }
    }
    let i = this.bitmap.width * yi + xi << 2;

    // if out of bounds index is -1
    if (xi < 0 || xi >= this.bitmap.width) {
      i = -1;
    }
    if (yi < 0 || yi >= this.bitmap.height) {
      i = -1;
    }
    if (index$1.isNodePattern(cb)) {
      cb.call(this, null, i);
    }
    return i;
  }

  /**
   * Returns the hex colour value of a pixel
   * @param {number} x the x coordinate
   * @param {number} y the y coordinate
   * @param {function(Error, Jimp)} cb (optional) a callback for when complete
   * @returns {number} the color of the pixel
   */
  getPixelColor(x, y, cb) {
    if (typeof x !== "number" || typeof y !== "number") return index$1.throwError.call(this, "x and y must be numbers", cb);
    const idx = this.getPixelIndex(x, y);
    const hex = this.bitmap.data.readUInt32BE(idx);
    if (index$1.isNodePattern(cb)) {
      cb.call(this, null, hex);
    }
    return hex;
  }
  /**
   * Returns the hex colour value of a pixel
   * @param {number} hex color to set
   * @param {number} x the x coordinate
   * @param {number} y the y coordinate
   * @param {function(Error, Jimp)} cb (optional) a callback for when complete
   * @returns {number} the index of the pixel or -1 if not found
   */
  setPixelColor(hex, x, y, cb) {
    if (typeof hex !== "number" || typeof x !== "number" || typeof y !== "number") return index$1.throwError.call(this, "hex, x and y must be numbers", cb);
    const idx = this.getPixelIndex(x, y);
    this.bitmap.data.writeUInt32BE(hex, idx);
    if (index$1.isNodePattern(cb)) {
      cb.call(this, null, this);
    }
    return this;
  }
  /**
   * Determine if the image contains opaque pixels.
   * @return {boolean} hasAlpha whether the image contains opaque pixels
   */
  hasAlpha() {
    const {
      width,
      height,
      data
    } = this.bitmap;
    const byteLen = width * height << 2;
    for (let idx = 3; idx < byteLen; idx += 4) {
      if (data[idx] !== 0xff) {
        return true;
      }
    }
    return false;
  }

  /**
   * Iterate scan through a region of the bitmap
   * @param {number} x the x coordinate to begin the scan at
   * @param {number} y the y coordinate to begin the scan at
   * @param w the width of the scan region
   * @param h the height of the scan region
   * @returns {IterableIterator<{x: number, y: number, idx: number, image: Jimp}>}
   */
  scanIterator(x, y, w, h) {
    if (typeof x !== "number" || typeof y !== "number") {
      return index$1.throwError.call(this, "x and y must be numbers");
    }
    if (typeof w !== "number" || typeof h !== "number") {
      return index$1.throwError.call(this, "w and h must be numbers");
    }
    return index$1.scanIterator(this, x, y, w, h);
  }
}
function addConstants(constants) {
  let jimpInstance = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Jimp;
  Object.entries(constants).forEach(_ref => {
    let [name, value] = _ref;
    jimpInstance[name] = value;
  });
}
function addJimpMethods(methods) {
  let jimpInstance = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Jimp;
  Object.entries(methods).forEach(_ref2 => {
    let [name, value] = _ref2;
    jimpInstance.prototype[name] = value;
  });
}
addConstants(constants);
addJimpMethods({
  composite: index$2
});
Jimp.__extraConstructors = [];

/**
 * Allow client libs to add new ways to build a Jimp object.
 * @param {string} name identify the extra constructor.
 * @param {function} test a function that returns true when it accepts the arguments passed to the main constructor.
 * @param {function} run where the magic happens.
 */
Jimp.appendConstructorOption = function (name, test, run) {
  Jimp.__extraConstructors.push({
    name,
    test,
    run
  });
};

/**
 * Read an image from a file or a Buffer. Takes the same args as the constructor
 * @returns {Promise} a promise
 */
Jimp.read = function () {
  for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
    args[_key3] = arguments[_key3];
  }
  return new Promise((resolve, reject) => {
    // eslint-disable-next-line no-new
    new Jimp(...args, (err, image) => {
      if (err) reject(err);else resolve(image);
    });
  });
};
Jimp.create = Jimp.read;

/**
 * A static helper method that converts RGBA values to a single integer value
 * @param {number} r the red value (0-255)
 * @param {number} g the green value (0-255)
 * @param {number} b the blue value (0-255)
 * @param {number} a the alpha value (0-255)
 * @param {function(Error, Jimp)} cb (optional) A callback for when complete
 * @returns {number} an single integer colour value
 */
Jimp.rgbaToInt = function (r, g, b, a, cb) {
  if (typeof r !== "number" || typeof g !== "number" || typeof b !== "number" || typeof a !== "number") {
    return index$1.throwError.call(this, "r, g, b and a must be numbers", cb);
  }
  if (r < 0 || r > 255) {
    return index$1.throwError.call(this, "r must be between 0 and 255", cb);
  }
  if (g < 0 || g > 255) {
    index$1.throwError.call(this, "g must be between 0 and 255", cb);
  }
  if (b < 0 || b > 255) {
    return index$1.throwError.call(this, "b must be between 0 and 255", cb);
  }
  if (a < 0 || a > 255) {
    return index$1.throwError.call(this, "a must be between 0 and 255", cb);
  }
  let i = r & 0xff;
  i <<= 8;
  i |= g & 0xff;
  i <<= 8;
  i |= b & 0xff;
  i <<= 8;
  i |= a & 0xff;

  // Ensure sign is correct
  i >>>= 0;
  if (index$1.isNodePattern(cb)) {
    cb.call(this, null, i);
  }
  return i;
};

/**
 * A static helper method that converts RGBA values to a single integer value
 * @param {number} i a single integer value representing an RGBA colour (e.g. 0xFF0000FF for red)
 * @param {function(Error, Jimp)} cb (optional) A callback for when complete
 * @returns {object} an object with the properties r, g, b and a representing RGBA values
 */
Jimp.intToRGBA = function (i, cb) {
  if (typeof i !== "number") {
    return index$1.throwError.call(this, "i must be a number", cb);
  }
  const rgba = {};
  rgba.r = Math.floor(i / Math.pow(256, 3));
  rgba.g = Math.floor((i - rgba.r * Math.pow(256, 3)) / Math.pow(256, 2));
  rgba.b = Math.floor((i - rgba.r * Math.pow(256, 3) - rgba.g * Math.pow(256, 2)) / Math.pow(256, 1));
  rgba.a = Math.floor((i - rgba.r * Math.pow(256, 3) - rgba.g * Math.pow(256, 2) - rgba.b * Math.pow(256, 1)) / Math.pow(256, 0));
  if (index$1.isNodePattern(cb)) {
    cb.call(this, null, rgba);
  }
  return rgba;
};

/**
 * Converts a css color (Hex, 8-digit (RGBA) Hex, RGB, RGBA, HSL, HSLA, HSV, HSVA, Named) to a hex number
 * @param {string} cssColor a number
 * @returns {number} a hex number representing a color
 */
Jimp.cssColorToHex = function (cssColor) {
  cssColor = cssColor || 0; // 0, null, undefined, NaN

  if (typeof cssColor === "number") return Number(cssColor);
  return parseInt(tinycolor(cssColor).toHex8(), 16);
};

/**
 * Limits a number to between 0 or 255
 * @param {number} n a number
 * @returns {number} the number limited to between 0 or 255
 */
Jimp.limit255 = function (n) {
  n = Math.max(n, 0);
  n = Math.min(n, 255);
  return n;
};

/**
 * Diffs two images and returns
 * @param {Jimp} img1 a Jimp image to compare
 * @param {Jimp} img2 a Jimp image to compare
 * @param {number} threshold (optional) a number, 0 to 1, the smaller the value the more sensitive the comparison (default: 0.1)
 * @returns {object} an object { percent: percent similar, diff: a Jimp image highlighting differences }
 */
Jimp.diff = function (img1, img2) {
  let threshold = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0.1;
  if (!(img1 instanceof Jimp) || !(img2 instanceof Jimp)) return index$1.throwError.call(this, "img1 and img2 must be an Jimp images");
  const bmp1 = img1.bitmap;
  const bmp2 = img2.bitmap;
  if (bmp1.width !== bmp2.width || bmp1.height !== bmp2.height) {
    if (bmp1.width * bmp1.height > bmp2.width * bmp2.height) {
      // img1 is bigger
      img1 = img1.cloneQuiet().resize(bmp2.width, bmp2.height);
    } else {
      // img2 is bigger (or they are the same in area)
      img2 = img2.cloneQuiet().resize(bmp1.width, bmp1.height);
    }
  }
  if (typeof threshold !== "number" || threshold < 0 || threshold > 1) {
    return index$1.throwError.call(this, "threshold must be a number between 0 and 1");
  }
  const diff = new Jimp(bmp1.width, bmp1.height, 0xffffffff);
  const numDiffPixels = index$3(bmp1.data, bmp2.data, diff.bitmap.data, diff.bitmap.width, diff.bitmap.height, {
    threshold
  });
  return {
    percent: numDiffPixels / (diff.bitmap.width * diff.bitmap.height),
    image: diff
  };
};

/**
 * Calculates the hamming distance of two images based on their perceptual hash
 * @param {Jimp} img1 a Jimp image to compare
 * @param {Jimp} img2 a Jimp image to compare
 * @returns {number} a number ranging from 0 to 1, 0 means they are believed to be identical
 */
Jimp.distance = function (img1, img2) {
  const phash$1 = new phash();
  const hash1 = phash$1.getHash(img1);
  const hash2 = phash$1.getHash(img2);
  return phash$1.distance(hash1, hash2);
};

/**
 * Calculates the hamming distance of two images based on their perceptual hash
 * @param {hash} hash1 a pHash
 * @param {hash} hash2 a pHash
 * @returns {number} a number ranging from 0 to 1, 0 means they are believed to be identical
 */
Jimp.compareHashes = function (hash1, hash2) {
  const phash$1 = new phash();
  return phash$1.distance(hash1, hash2);
};

/**
 * Compute color difference
 * 0 means no difference, 1 means maximum difference.
 * @param {number} rgba1:    first color to compare.
 * @param {number} rgba2:    second color to compare.
 * Both parameters must be an color object {r:val, g:val, b:val, a:val}
 * Where `a` is optional and `val` is an integer between 0 and 255.
 * @returns {number} float between 0 and 1.
 */
Jimp.colorDiff = function (rgba1, rgba2) {
  const pow = n => Math.pow(n, 2);
  const {
    max
  } = Math;
  const maxVal = 255 * 255 * 3;
  if (rgba1.a !== 0 && !rgba1.a) {
    rgba1.a = 255;
  }
  if (rgba2.a !== 0 && !rgba2.a) {
    rgba2.a = 255;
  }
  return (max(pow(rgba1.r - rgba2.r), pow(rgba1.r - rgba2.r - rgba1.a + rgba2.a)) + max(pow(rgba1.g - rgba2.g), pow(rgba1.g - rgba2.g - rgba1.a + rgba2.a)) + max(pow(rgba1.b - rgba2.b), pow(rgba1.b - rgba2.b - rgba1.a + rgba2.a))) / maxVal;
};

/**
 * Helper to create Jimp methods that emit events before and after its execution.
 * @param {string} methodName   The name to be appended to Jimp prototype.
 * @param {string} evName       The event name to be called.
 *                     It will be prefixed by `before-` and emitted when on method call.
 *                     It will be appended by `ed` and emitted after the method run.
 * @param {function} method       A function implementing the method itself.
 * It will also create a quiet version that will not emit events, to not
 * mess the user code with many `changed` event calls. You can call with
 * `methodName + "Quiet"`.
 *
 * The emitted event comes with a object parameter to the listener with the
 * `methodName` as one attribute.
 */
function jimpEvMethod(methodName, evName, method) {
  const evNameBefore = "before-" + evName;
  const evNameAfter = evName.replace(/e$/, "") + "ed";
  Jimp.prototype[methodName] = function () {
    let wrappedCb;
    for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
      args[_key4] = arguments[_key4];
    }
    const cb = args[method.length - 1];
    const jimpInstance = this;
    if (typeof cb === "function") {
      wrappedCb = function () {
        for (var _len5 = arguments.length, args = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
          args[_key5] = arguments[_key5];
        }
        const [err, data] = args;
        if (err) {
          jimpInstance.emitError(methodName, err);
        } else {
          jimpInstance.emitMulti(methodName, evNameAfter, {
            [methodName]: data
          });
        }
        cb.apply(this, args);
      };
      args[args.length - 1] = wrappedCb;
    } else {
      wrappedCb = false;
    }
    this.emitMulti(methodName, evNameBefore);
    let result;
    try {
      result = method.apply(this, args);
      if (!wrappedCb) {
        this.emitMulti(methodName, evNameAfter, {
          [methodName]: result
        });
      }
    } catch (error) {
      error.methodName = methodName;
      this.emitError(methodName, error);
    }
    return result;
  };
  Jimp.prototype[methodName + "Quiet"] = method;
}

/**
 * Creates a new image that is a clone of this one.
 * @param {function(Error, Jimp)} cb (optional) A callback for when complete
 * @returns the new image
 */
jimpEvMethod("clone", "clone", function (cb) {
  const clone = new Jimp(this);
  if (index$1.isNodePattern(cb)) {
    cb.call(clone, null, clone);
  }
  return clone;
});

/**
 * Simplify jimpEvMethod call for the common `change` evName.
 * @param {string} methodName name of the method
 * @param {function} method to watch changes for
 */
function jimpEvChange(methodName, method) {
  jimpEvMethod(methodName, "change", method);
}

/**
 * Sets the type of the image (RGB or RGBA) when saving as PNG format (default is RGBA)
 * @param b A Boolean, true to use RGBA or false to use RGB
 * @param {function(Error, Jimp)} cb (optional) a callback for when complete
 * @returns {Jimp} this for chaining of methods
 */
jimpEvChange("background", function (hex, cb) {
  if (typeof hex !== "number") {
    return index$1.throwError.call(this, "hex must be a hexadecimal rgba value", cb);
  }
  this._background = hex;
  if (index$1.isNodePattern(cb)) {
    cb.call(this, null, this);
  }
  return this;
});

/**
 * Scans through a region of the bitmap, calling a function for each pixel.
 * @param {number} x the x coordinate to begin the scan at
 * @param {number} y the y coordinate to begin the scan at
 * @param w the width of the scan region
 * @param h the height of the scan region
 * @param f a function to call on even pixel; the (x, y) position of the pixel
 * and the index of the pixel in the bitmap buffer are passed to the function
 * @param {function(Error, Jimp)} cb (optional) a callback for when complete
 * @returns {Jimp} this for chaining of methods
 */
jimpEvChange("scan", function (x, y, w, h, f, cb) {
  if (typeof x !== "number" || typeof y !== "number") {
    return index$1.throwError.call(this, "x and y must be numbers", cb);
  }
  if (typeof w !== "number" || typeof h !== "number") {
    return index$1.throwError.call(this, "w and h must be numbers", cb);
  }
  if (typeof f !== "function") {
    return index$1.throwError.call(this, "f must be a function", cb);
  }
  const result = index$1.scan(this, x, y, w, h, f);
  if (index$1.isNodePattern(cb)) {
    cb.call(this, null, result);
  }
  return result;
});
if (process.env.ENVIRONMENT === "BROWSER") {
  // For use in a web browser or web worker
  /* global self */
  let gl;
  if (typeof window !== "undefined" && typeof window === "object") {
    gl = window;
  }
  if (typeof self !== "undefined" && typeof self === "object") {
    gl = self;
  }
  gl.Jimp = Jimp;
  gl.Buffer = Buffer;
}

exports.addType = mime.addType;
exports.addConstants = addConstants;
exports.addJimpMethods = addJimpMethods;
exports.default = Jimp;
exports.jimpEvChange = jimpEvChange;
exports.jimpEvMethod = jimpEvMethod;
