Source: index.js

/**
 * @description Amount of decimal places round coordinates to
 */
const decimalRoundCoordinate = 3;
/**
 * @description Amount of decimal places round angles in radians to
 */
const decimalRoundAngle = 15;
/**
 * @description Class representing a canvas
 */
export class Canvas {
    /**
     * 
     * @param {Object} options Options for the canvas creation
     * @param {Number} [options.width] The width of the canvas
     * @param {Number} [options.height] The height of the canvas
     * @param {HTMLElement} [options.container] The parent element of the canvas
     * @param {?"fullscreen" | "small"} [options.preset] If preset is present, it will bypass all other options and initialize the canvas with the preset
     */
    constructor(options = { width: window.innerWidth / 2, height: window.innerHeight / 2, container: document.body }) {
        /**
         * @description The canvas element object
         * @type {HTMLCanvasElement}
         */
        this.canvas = document.createElement("canvas");
        if (options.preset) {
            switch (options.preset) {
                case "fullscreen":
                    document.body.appendChild(this.canvas);
                    document.body.style.margin = 0;
                    document.body.style.overflow = "hidden";
                    this.canvas.style.margin = 0;
                    this.canvas.width = window.innerWidth
                    this.canvas.height = window.innerHeight;
                    this.canvas.style.position = "absolute";
                    this.canvas.style.top = 0;
                    this.canvas.style.left = 0;
                    break;
                case "small":
                    document.body.appendChild(this.canvas);
                    document.body.style.margin = 0;
                    document.body.style.overflow = "hidden";
                    this.canvas.style.margin = 0;
                    this.canvas.width = window.innerWidt / 4;
                    this.canvas.height = window.innerHeight / 4;
                    break;
                default:
                    throw new Error("Unknown preset: " + options.preset);
            }
        } else {
            options.container.appendChild(this.canvas);
            this.canvas.height = options.height + "px";
            this.canvas.width = options.width + "px";
        }
        /**
         * @description Context of the canvas
         * @type {CanvasRenderingContext2D}
         */
        this.context = this.canvas.getContext("2d");
        /**
         * @description Filters for the canvas
         * @type {FilterManager}
         */
        this.filters = new FilterManager();
    }
    /**
      * @param {Number} A 
      * @param {Number} B 
      * @param {Number} C
      * @param {Number} D
      * @returns {void}
      */
    /**
     * @param {Object} A Point A
     * @param {Number} [A.x] The x coordinate
     * @param {Number} [A.y] The y coordinate
     * @param {Object} B Point B
     * @param {Number} [B.x] The x coordinate
     * @param {Number} [B.y] The y coordinate
     * @returns {void}
     */
    drawLine(A, B, C, D) {
        if (A === undefined || B === undefined) throw new Error("At least two arguments need to be provided.")
        if (typeof C === "number") {
            A = new Point(A, B);
            B = new Point(C, D);
        }
        try {
            this.context.beginPath();
            this.context.moveTo(A.x, A.y);
            this.context.lineTo(B.x, B.y);
            this.context.closePath();
            this.context.stroke();
        } catch (e) {
            throw new Error(`Unable to draw line from [${A.x}, ${A.y}] to [${B.x}, ${B.y}]`);
        }
    }
    /**
     * @description Transforms the canvas according to the provided properties. If no options argument provided, this function will reset the transformation.
     * @param {?Object} options
     * @param {?Number} [options.x] The x coordinate of the [0, 0] point
     * @param {?Number} [options.y] The y coordinate of the [0, 0] point
     * @param {?Number} [options.xScale] The scaling factor of the x axis
     * @param {?Number} [options.yScale] The scaling factor of the y axis
     * @param {?Number} [options.rotation] The rotation of the plane in radians
     * @returns {void}
     */
    transform(options) {
        if (!options) {
            this.context.setTransform(1, 0, 0, 1, 0, 0);
            return;
        }
        if (typeof options.x === "number" || typeof options.y === "number") {
            this.context.translate(round(options.x || 0, "coordinate"), round(options.y || 0, "coordinate"));
        }
        if (typeof options.xScale === "number" || typeof options.yScale === "number") {
            this.context.scale(round(options.xScale || 1, "coordinate"), round(options.yScale || 1, "coordinate"));
        }
        if (typeof options.rotation === "number") {
            this.context.rotate(round(options.rotation, "angle"));
        }
    }
    /**
     * @description Moves the zero point of the canvas
     * @param {Number} x X axis move 
     * @param {Number} y Y axis move
     * @returns {void}
     */
    translate(x = 0, y = 0) {
        this.context.translate(x, y);
    }
    /**
     * @description Scales the canvas
     * @param {Number} x X axis scale
     * @param {Number} y Y axis scale
     * @return {void}
     */
    scale(x = 1, y = 1) {
        this.context.scale(x, y);
    }
    /**
     * @description Rotates the canvas
     * @param {Number} angle Angle of rotation in radians
     * @return {void}
     */
    rotate(angle) {
        this.context.rotate(angle);
    }
    /**
     * @description Gets the transform matrix
     * @return {DOMMatrix2DInit}
     */
    getTransform() {
        return this.context.getTransform();
    }
    /**
     * @description Sets the transform matrix
     * @param {DOMMatrix2DInit} transform 
     * @returns {void}
     */
    setTransform(transform) {
        this.context.setTransform(transform);
    }
    /**
     * @description Clears the canvas
     * @returns {void}
     */
    clear() {
        var s = this.getTransform();
        this.transform();
        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.setTransform(s);
    }
    /**
     * @description Draws a grid
     * @param {Number} width How far apart should the lines of the grid be
     */
    drawGrid(width = 50) {
        var lw = this.context.lineWidth;
        this.context.lineWidth = 2;
        this.drawLine(-this.canvas.width, 0, this.canvas.width, 0);
        this.drawLine(0, -this.canvas.height, 0, this.canvas.height);
        this.context.lineWidth = 0.5;
        for (var i = width; i < this.canvas.height; i += width) {
            this.drawLine(-this.canvas.width, i, this.canvas.width, i);
        }
        for (var i = -width; i > -this.canvas.height; i -= width) {
            this.drawLine(-this.canvas.width, i, this.canvas.width, i);
        }
        for (var i = width; i < this.canvas.width; i += width) {
            this.drawLine(i, -this.canvas.height, i, this.canvas.height);
        }
        for (var i = -width; i > -this.canvas.width; i -= width) {
            this.drawLine(i, -this.canvas.height, i, this.canvas.height);
        }
        this.context.lineWidth = lw;
    }
    /**
     * @description Draws a rectangle outline
     * @param {Number} x The x coordinate of the upper left corner of the rectangle
     * @param {Number} y The y coordinate of the upper left corner of the rectangle
     * @param {Number} width The width of the rectangle 
     * @param {Number} height The height of the rectangle
     * @returns {void}
     */
    rect(x, y, width, height) {
        this.context.strokeRect(x, y, width, height);
    }
    /**
     * @description Fills a rectangle
     * @param {Number} x The x coordinate of the upper left corner of the rectangle
     * @param {Number} y The y coordinate of the upper left corner of the rectangle
     * @param {Number} width The width of the rectangle 
     * @param {Number} height The height of the rectangle
     * @returns {void}
     */
    fillRect(x, y, width, height) {
        this.context.fillRect(x, y, width, height);
    }
    /**
     * @description Clears a rectangle
     * @param {Number} x The x coordinate of the upper left corner of the rectangle
     * @param {Number} y The y coordinate of the upper left corner of the rectangle
     * @param {Number} width The width of the rectangle 
     * @param {Number} height The height of the rectangle
     * @returns {void}
     */
    clearRect(x, y, width, height) {
        this.context.clearRect(x, y, width, height);
    }
    /**
     * @returns {void}
     * @description Saves the currect state of the canvas to stack. Can be restored using Canvas.load()
     */
    save() {
        this.context.save();
    }
    /**
     * @returns {void}
     * @description Loads the last saved canvas from the stack
     */
    load() {
        this.context.restore();
    }
    /**
     * @description Draws a text on the canvas
     * @param {String} text Text to be drawn on canvas
     * @param {Number} x The x coordinate of the upper left corner of the text
     * @param {Number} y The y coordinate of the upper left corner of the text
     * @param {?Number} maxWidth Max width of the text
     * @returns {void}
     */
    text(text, x, y, maxWidth) {
        this.context.fillText(text, x, y, maxWidth);
    }
    /**
     * @description Draws a text outline on the canvas
     * @param {String} text Text to be drawn on canvas
     * @param {Number} x The x coordinate of the upper left corner of the text
     * @param {Number} y The y coordinate of the upper left corner of the text
     * @param {?Number} maxWidth Max width of the text
     * @returns {void}
     */
    textOutline(text, x, y, maxWidth) {
        this.context.strokeText(text, x, y, maxWidth);
    }
    /**
     * @description Sets line width 
     * @param {Number} width 
     * @returns {void}
     */
    setLineWidth(width) {
        this.context.lineWidth = width;
    }
    /**
     * @description Sets the type of line endings 
     * @param {"butt" | "round" | "square"} cap Type of cap
     * @returns {void}
     */
    setLineCap(cap = "butt") {
        this.context.lineCap = cap;
    }
    /**
     * @description Sets the type of corners where two lines meet
     * @param {"round" | "bevel" | "miter"} join Type of line join
     * @returns {void}
     */
    setLineJoin(join = "miter") {
        this.lineJoin = join;
    }
    /**
     * @description Sets the miter limit
     * @param {Number} limit 
     * @returns {void}
     */
    setMiterLimit(limit) {
        this.context.miterLimit = limit;
    }
    /**
     * @description Sets the spacing of a line
     * @param {Number} lineWidth The width of the line
     * @param {Number} spacing Spacing of the lines 
     * @returns {void}
     */
    /**
     * @description Sets the spacing of a line
     * @param {Array<Number>} lineWidth 
     * @returns {void}
     */
    setLineDash(lineWidth, spacing) {
        if (typeof lineWidth === "object") this.context.setLineDash(lineWidth); else this.context.setLineDash([lineWidth, spacing]);
    }
    /**
     * @description Gets the line dashing pattern
     * @return {LineDashPattern}
     */
    getLineDash() {
        return new LineDashPattern(this.context.getLineDash());
    }
    /**
     * @description Sets the line dash offset
     * @param {Number} offset The offset of the line dash
     * @returns {void}
     */
    setLineDashOffset(offset) {
        this.context.lineDashOffset = offset;
    }
    /**
     * @description Sets the font style
     * @param {String} font String represnting the css style font
     * @returns {void}
     */
    setFont(font) {
        this.context.font = font;
    }
    /**
     * @description Sets the text align
     * @param {"start" | "end" | "left" | "right" | "center"} textAlign Text align type
     * @returns {void}
     */
    setTextAlign(textAlign = "start") {
        this.context.textAlign = textAlign;
    }
    /**
     * @description Sets the test baseline
     * @param {"top" | "hanging" | "middle" | "alphabetic" | "ideographic" | "bottom"} textBaseline Text baseline style
     * @returns {void} 
     */
    setTextBaseline(textBaseline = "alphabetic") {
        this.context.textBaseline = textBaseline;
    }
    /**
     * @description Sets text direction
     * @param {"ltr" | "rtl" | "inherit"} direction Direction of the text
     * @returns {void}
     */
    setDirection(direction = "inherit") {
        this.context.direction = direction;
    }
    /**
     * @description Sets the fill style
     * @param {String | CanvasGradient | CanvasPattern} style Syle of fill (color | gradient | image)
     * @returns {void}
     */
    setFill(style) {
        this.context.fillStyle = style;
    }
    /**
     * @description Sets the stroke style
     * @param {String | CanvasGradient | CanvasPattern} style Syle of stroke (color | gradient | image)
     * @returns {void}
     */
    setStroke(style) {
        this.context.strokeStyle = style;
    }
    /**
     * @description Sets the shadow blur. Default is 0, higher level is more blur
     * @param {Number} level Blur level
     * @returns {void}
     */
    setShadowBlur(level) {
        this.context.shadowBlur = level;
    }
    /**
     * @description Sets the shadow color
     * @param {String} color CSS style color
     * @returns {void}
     */
    setShadowColor(color) {
        this.context.shadowColor = color;
    }
    /**
     * @description Sets shadow offset
     * @param {?Number} x X shadow offset
     * @param {?Number} y Y shadow offset
     * @returns {void}
     */
    setShadowOffset(x, y) {
        if (typeof x === "number") this.context.shadowOffsetX = x;
        if (typeof y === "number") this.context.shadowOffsetY = y;
    }
    /**
     * @description Sets global alpha for all drawn objects
     * @param {Number} alpha Number between 0 and 1 representing the global alpha value
     * @returns {void}
     */
    setGlobalAlpha(alpha) {
        this.context.globalAlpha = alpha;
    }
    /**
     * @description Sets the global composite operation. For more information about operations visit offical MDN documentation
     * @param {"source-over" | "source-in" | "source-out" | "source-atop" | "destination-over" | "destination-in" | "destination-out" | "destination-atop" | "lighter" | "copy" | "xor" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity"} operation The operation
     * @link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
     */
    setGlobalCompositeOperation(operation) {
        this.context.globalCompositeOperation = operation;
    }
    /**
     * @description Draws an image to the canvas
     * @param {Image} image The image to be drawn
     * @param {Number} x The X coordinate of the upper left corner
     * @param {Number} y The Y coordinate of the upper left corner
     * @returns {void}
     */
    drawImage(image, x, y) {
        if (image.constructor.name !== 'Image') throw new TypeError("Please provide a valid Image object.");
        switch (image.getDrawType()) {
            case "normal": return (this.context.drawImage(image.image, x, y), [][0]);
            case "resize": return (this.context.drawImage(image.image, x, y, image.w, image.h), [][0]);
            case "crop": return (this.context.drawImage(image.image, image.cropRectangle.x, image.cropRectangle.y, image.cropRectangle.width, image.cropRectangle.height, x, y, image.w, image.h), [][0]);
        }
    }
    /**
     * @description Returns ImageData object with pixels from the rectangle from the canvas
     * @param {Object} rectangle Object represnting the rectangle
     * @param {Number} rectangle.x The x coordinate of the upper left corner of the rectangle
     * @param {Number} rectangle.y The y coordinate of the upper left corner of the rectangle
     * @param {Number} rectangle.width The width of the rectangle
     * @param {Number} rectangle.height The height of the rectangle
     * @returns {ImageData}
     */
    getImageData(rectangle) {
        return this.context.getImageData(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
    }
    /**
     * @description Applies a filter to the canvas
     * @param {Filter} filter Filter to be applied
     * @returns {void}
     */
    applyFilter(filter) {
        this.filters.add(filter);
        this.context.filter = this.filters.toString();
    }
    /**
     * @description Returns all the filters
     * @returns {FilterManager}
     */
    getAllFilters() {
        return this.filters;
    }
    /**
     * @description Removes a filter
     * @param {Number | Filter} filter 
     */
    removeFilter(filter) {
        this.filters.remove(filter);
        this.context.filter = this.filters.toString();
    }
    /**
     * @description Clears all filters
     * @returns {void}
     */
    clearFilters() {
        this.filters.clear();
        this.context.filter = this.filters.toString();
    }
    /**
     * @description Sets the image smoothing
     * @param {"disabled" | "low" | "medium" | "high"} imageSmoothingMode 
     * @returns {void}
     */
    setImageSmooth(imageSmoothingMode) {
        if (imageSmoothingMode === "disabled") return (this.context.imageSmoothingEnabled = false, [][0]);
        this.context.imageSmoothingEnabled = true;
        this.context.imageSmoothingQuality = imageSmoothingMode;
    }
    /**
     * @description Creates a path
     * @returns {Path}
     */
    createPath() {
        return new Path(this);
    }
}
/**
 * @description Simple Path class that inherits all functionalities from Path2D and adds fill() and draw() methods for direct draw on the canvas
 * @extends {Path2D}
 * @link https://developer.mozilla.org/en-US/docs/Web/API/Path2D
 */
export class Path extends Path2D {
    /**
     * @param {Canvas} canvas
     */
    constructor(canvas) {
        super();
        this.canvas = canvas;
    }
    /**
     * @description Fills the path
     * @returns {void}
     */
    fill() {
        this.canvas.context.fill(this);
    }
    /**
     * @description Draws the path
     * @returns {void}
     */
    draw() {
        this.canvas.context.stroke(this);
    }
}
/**
 * @description Class representing a line dash pattern
 * @extends {Array}
 */
export class LineDashPattern extends Array {
    constructor(pattern) {
        super();
        this.push(...pattern);
    }
}
/**
 * @description Class representing an Image, Graphics or a Video
 */
export class Image {
    /**
     * 
     * @param {HTMLImageElement | SVGImageElement | HTMLVideoElement | HTMLCanvasElement | ImageBitmap | OffscreenCanvas} image 
     */
    constructor(image) {
        /**
         * @description Image
         * @type {HTMLImageElement | SVGImageElement | HTMLVideoElement | HTMLCanvasElement | ImageBitmap | OffscreenCanvas}
         */
        this.image = image;
    }
    /**
     * @description Creates Image object from url
     * @param {String} url 
     * @returns {Image}
     */
    static fromUrl(url) {
        return new Image(`url(${url})`);
    }
    /**
     * @description Creates Image object from ImageData object
     * @param {ImageData} imageData 
     * @returns {Promise<Image>}
     */
    static fromImageData(imageData) {
        return new Promise((resolve, reject) => {
            createImageBitmap(imageData).then(e => {
                resolve(new Image(e));
            })
        })

    }
    /**
     * @description Resizes the image for drawing
     * @param {Number} width Image width
     * @param {Number} height Image height
     * @returns {void}
     */
    resize(width, height) {
        /**
         * @description Image width used when image is drawn 
         * @type {Number}
         */
        this.w = width;
        /**
         * @description Image height used when image is drawn
         * @type {Number}
         */
        this.h = height;
    }
    /**
     * @description Crops the original image to a rectangle
     * @param {Object} rectangle Object represnting the rectangle to crop out of the original image
     * @param {Number} [rectangle.x] The x coordinate of the upper left corner of the rectangle
     * @param {Number} [rectangle.y] The y coordinate of the upper left corner of the rectangle
     * @param {Number} [rectangle.width] The width of the rectangle
     * @param {Number} [rectangle.height] The height of the rectangle
     * @returns {void}
     */
    crop(rectangle) {
        this.cropRectangle = rectangle;
    }
    /**
     * @description Returns the method that will be used when drawing the rectangle
     * @ignore
     * @returns {"normal" | "resize" | "crop"}
     */
    getDrawType() {
        if (typeof this.w === "number" && typeof this.h === "number") {
            return (typeof this.cropRectangle === "object" && typeof this.cropRectangle.height === "number" && typeof this.cropRectangle.width === "number" && typeof this.cropRectangle.x === "number" && typeof this.cropRectangle.y === "number") ? "crop" : "resize";
        } else return "normal";
    }
}

/**
 * @description Manages filters for a canvas
 * @extends {Array<Filter>}
 */
class FilterManager extends Array {
    constructor() {
        super();
    }
    /**
     * @description Adds a filter
     * @param {Filter} filter 
     */
    add(filter) {
        this.push(filter);
    }
    /**
     * @description Removes an existing filter
     * @param {Number | Filter} f 
     * @returns {void}
     */
    remove(f) {
        if (typeof f === "number") {
            this.splice(f, 1);
        } else if (typeof f === "object") {
            if (this.findIndex(v => v === f) == -1) throw new Error("Filter not found.");
            this.splice(this.findIndex(v => v === f), 1);
        } else throw new TypeError("Parameter must be and index or a filter.");
    }
    /**
     * @description Clears all filters
     * @returns {void}
     */
    clear() {
        this.splice(0, this.length);
    }
    /**
     * @description Returns a string representation of the filters
     * @returns {String}
     */
    toString() {
        return this.join(" ");
    }
}
/**
 * @description Represents a filter that can be applied to the canvas
 */
export class Filter {
    constructor(type, value) {
        /**
         * @description The type of the filter
         * @type {"url" | "blur" | "brightness" | "contrast" | "dropShadow" | "Grayscale" | "hue-rotate" | "invert" | "opacity" | "saturate" | "sepia"}
         */
        this.type = type;
        /**
         * @description The value of the filter
         * @type {Number}
         */
        this.value = value;
        /**
         * @description Used to construct the filter string
         * @type {String}
         */
        this.unit;
    }
    /**
     * @description The toString function
     * @returns {String}
     */
    toString() {
        return `${this.type}(${this.value}${this.unit})`;
    }
    /**
     * @description All possible filters
     * @constant
     */
    static filters = {
        url: Filter.Url,
        blur: Filter.Blur,
        brightness: Filter.Brightness,
        contrast: Filter.Contrast,
        dropShadow: Filter.DropShadow,
        grayscale: Filter.Grayscale,
        hueRotate: Filter.HueRotate,
        invert: Filter.Invert,
        opacity: Filter.Opacity,
        saturation: Filter.Saturation,
        sepia: Filter.Sepia
    }
}
/**
 * @description Blur filter
 * @extends {Filter}
 */
Filter.Blur = class BlurFilter extends Filter {
    /**
     * 
     * @param {Number} radius A number representing the radius of the blur
     */
    constructor(radius) {
        if (typeof radius !== 'number') throw new TypeError("Radius must be a number.");
        super("blur", radius);
        this.unit = "px";
    }
}
/**
 * @description External SVG filter
 * @extends {Filter}
 */
Filter.Url = class UrlFilter extends Filter {
    /**
     * 
     * @param {String} url The url to the filter
     */
    constructor(url) {
        if (typeof url !== "string") throw new TypeError("Url must be a string.");
        super("url", url);
        this.unit = "";
    }
}
/**
 * @description Brightness filter
 * @extends {Filter}
 */
Filter.Brightness = class BrightnessFilter extends Filter {
    /**
     * 
     * @param {Number} intensity A number between 0 and 1 representing the intensity
     */
    constructor(intensity) {
        if (typeof intensity !== "number") throw new TypeError("Intensity must be a number.");
        if (intensity < 0 || intensity > 1) throw new Error("Intensity must be between 0 and 1.");
        super("brightness", intensity * 100);
        this.unit = "%";
    }
}
/**
 * @description Contrast filter
 * @extends {Filter}
 */
Filter.Contrast = class ContrastFilter extends Filter {
    /**
     * 
     * @param {Number} intensity A number between 0 and 1 representing the intensity
     */
    constructor(intensity) {
        if (typeof intensity !== "number") throw new TypeError("Intensity must be a number.");
        if (intensity < 0 || intensity > 1) throw new Error("Intensity must be between 0 and 1.");
        super("contrast", intensity * 100);
        this.unit = "%";
    }
}
/**
 * @description DropShadow filter 
 * @extends {Filter}
 */
Filter.DropShadow = class DropShadowFilter extends Filter {
    /**
     * 
     * @param {Number} xOffset X axis offset
     * @param {Number} yOffset Y axis offset
     * @param {Number} blurRadius Blur radius
     * @param {String} color Color of the shadow
     */
    constructor(xOffset, yOffset, blurRadius, color) {
        if (typeof xOffset !== "number") throw new TypeError("X Offset must be a number.");
        if (typeof yOffset !== "number") throw new TypeError("Y Offset must be a number.");
        if (typeof blurRadius !== "number") throw new TypeError("Blur radius must be a number.");
        if (blurRadius < 0) throw new Error("Blur radius must be greater than or equal to zero.");
        if (!color || (typeof color !== "string" && typeof color.toString() !== "string")) throw new TypeError("Color must be representable by a string.");
        if (typeof color !== "string") color = color.toString();
        this.type = "drop-shadow";
        /**
         * @description Values to the function
         * @type {Number | String}
         */
        this.values = [xOffset, yOffset, blurRadius, color];
        /**
         * @description Units to the values
         * @type {String}
         */
        this.units = ["px", "px", "", ""];
    }
    /**
     * @description The toString function
     * @returns {String}
     */
    toString() {
        return `${this.type}(${this.values[0]}${this.units[0]} ${this.values[1]}${this.units[1]} ${this.values[2]}${this.units[2]} ${this.values[3]}${this.units[3]})`;
    }
}
/**
 * @description Grayscale filter 
 * @extends {Filter}
 */
Filter.Grayscale = class GrayscaleFilter extends Filter {
    /**
     * 
     * @param {Number} intensity A number between 0 and 1 representing the intensity
     */
    constructor(intensity) {
        if (typeof intensity !== "number") throw new TypeError("Intensity must be a number.");
        if (intensity < 0 || intensity > 1) throw new Error("Intensity must be between 0 and 1.");
        super("grayscale", intensity * 100);
        this.unit = "%";
    }
}
/**
 * @description Filter that rotates all colors hue by an angle
 * @extends {Filter}
 */
Filter.HueRotate = class HueRotateFilter extends Filter {
    /**
     * 
     * @param {Number} angle Angle to rotate in radians
     */
    constructor(angle) {
        if (typeof angle !== "number") throw new TypeError("Angle must be a number.");
        super("hue-rotate", Math.round(angle / Math.PI * 180 * 1e2) * 1e2);
        this.unit = "deg";
    }
}
/**
 * @description Invert filter 
 * @extends {Filter}
 */
Filter.Invert = class InvertFilter extends Filter {
    /**
     * 
     * @param {Number} intensity A number between 0 and 1 representing the intensity
     */
    constructor(intensity) {
        if (typeof intensity !== "number") throw new TypeError("Intensity must be a number.");
        if (intensity < 0 || intensity > 1) throw new Error("Intensity must be between 0 and 1.");
        super("invert", intensity * 100);
        this.unit = "%";
    }
}
/**
 * @description Opacity filter 
 * @extends {Filter}
 */
Filter.Opacity = class OpacityFilter extends Filter {
    /**
     * 
     * @param {Number} intensity A number between 0 and 1 representing the intensity
     */
    constructor(intensity) {
        if (typeof intensity !== "number") throw new TypeError("Intensity must be a number.");
        if (intensity < 0 || intensity > 1) throw new Error("Intensity must be between 0 and 1.");
        super("opacity", intensity * 100);
        this.unit = "%";
    }
}
/**
 * @description Saturation filter 
 * @extends {Filter}
 */
Filter.Saturation = class SaturationFilter extends Filter {
    /**
     * 
     * @param {Number} intensity A number between 0 and 1 representing the intensity
     */
    constructor(intensity) {
        if (typeof intensity !== "number") throw new TypeError("Intensity must be a number.");
        if (intensity < 0 || intensity > 1) throw new Error("Intensity must be between 0 and 1.");
        super("saturate", intensity * 100);
        this.unit = "%";
    }
}
/**
 * @description Sepia filter 
 * @extends {Filter}
 */
Filter.Sepia = class SepiaFilter extends Filter {
    /**
     * 
     * @param {Number} intensity A number between 0 and 1 representing the intensity
     */
    constructor(intensity) {
        if (typeof intensity !== "number") throw new TypeError("Intensity must be a number.");
        if (intensity < 0 || intensity > 1) throw new Error("Intensity must be between 0 and 1.");
        super("sepia", intensity * 100);
        this.unit = "%";
    }
}


// GEOMETRY
/**
 * @description Class representing a point on the canvas
 */
export class Point {
    /**
     * 
     * @param {Number} x The x coordinate of the point
     * @param {Number} y The y coordinate of the point 
     */
    constructor(x, y) {
        if (typeof x !== 'number') throw new TypeError("Point x must be a number.");
        if (typeof y !== 'number') throw new TypeError("Point y must be a number.");
        /**
         * @description The x coordinate
         * @type {Number}
         */
        this.x = round(x);
        /**
         * @description The y coordinate
         * @type {Number}
         */
        this.y = round(y);
    }
    /**
     * 
     * @param {Number} x The x coordinate of the point
     * @param {Number} y The y coordinate of the point
     * @returns {Number} The distance
     */
    /**
     * @description Calculates the distance between two points using the pythagorian theorem
     * @param {Point} x The point to calculate the distance to
     * @returns {Number} The distance
     */
    distance(x, y) {
        if (x.constructor.name === "Point") {
            y = x.y;
            x = x.x
        } else if (typeof x !== 'number') throw new TypeError("Point x must be a number.");
        else if (typeof y !== 'number') throw new TypeError("Point y must be a number.");
        return Math.pow(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2), 1 / 2);
    }
}
/**
 * @description Rounds number with given constants 
 * @param {Number} x The number to round
 * @param {"coordinate" | "angle"}
 * @returns {Number}
 */
function round(x, type = "coordinate") {
    switch (type) {
        case "coordinate": return Math.round(x * Math.pow(10, decimalRoundCoordinate)) / Math.pow(10, decimalRoundCoordinate);
        case "angle": return Math.round(x * Math.pow(10, decimalRoundAngle)) / Math.pow(10, decimalRoundAngle);
        default: throw new Error("Unsuported rounding type.")
    }
}