diff --git a/ImageScript.d.ts b/ImageScript.d.ts index 34573e2..c613baa 100644 --- a/ImageScript.d.ts +++ b/ImageScript.d.ts @@ -8,16 +8,20 @@ export class Image { constructor(width: number, height: number); - private toString(): string; + private toString(): `Image<${number}x${number}>`; get width(): number; get height(): number; - * [Symbol.iterator](): void; + *[Symbol.iterator](): void; + + *iterateWithColors(): Generator< + [x: number, y: number, color: number], + void, + unknown + >; - * iterateWithColors(): Generator<[x: number, y: number, color: number], void, unknown>; - static rgbaToColor(r: number, g: number, b: number, a: number): number; static rgbToColor(r: number, g: number, b: number): number; @@ -36,7 +40,7 @@ export class Image { getRGBAAt(x: number, y: number): Uint8ClampedArray; - setPixelAt(x: number, y: number, pixelColor: number): Image; + setPixelAt(x: number, y: number, pixelColor: number): this; private __set_pixel__(x: number, y: number, pixelColor: number): void; @@ -44,167 +48,543 @@ export class Image { private static get __out_of_bounds__(): string; - fill(color: number | colorFunction): Image; + /** + * Fills the entire image with the supplied color. + * + * @param color + */ + fill(color: number | ColorFunction): this; clone(): Image; - static get RESIZE_NEAREST_NEIGHBOR(): string; - - static get RESIZE_AUTO(): number; - - scale(factor: number, mode?: string): Image; - - private __scale__(factor: number, mode?: string); - - resize(width: number, height: number, mode?: string): Image; - - contain(width: number, height: number, mode?: string): Image; - - fit(width: number, height: number, mode?: string): Image; - - cover(width: number, height: number, mode?: string): Image; - - private __resize__(width: number, height: number, mode?: string): Image; - - private __resize_nearest_neighbor__(width: number, height: number): Image; - - crop(x: number, y: number, width: number, height: number): Image; - - private __crop__(x: number, y: number, width: number, height: number): Image; - - drawBox(x: number, y: number, width: number, height: number, color: number | colorFunction): Image; - - private __fast_box__(x: number, y: number, width: number, height: number, color: number): Image; - - drawCircle(x: number, y: number, radius: number, color: number | colorFunction): Image; - - cropCircle(max?: boolean, feathering?: number): Image; - - opacity(opacity: number, absolute?: boolean): Image; - - red(saturation: number, absolute?: boolean): Image; - - green(saturation: number, absolute?: boolean): Image; - - blue(saturation: number, absolute?: boolean): Image; - - private __set_channel_value__(value: number, absolute: boolean, offset: number): void; - - lightness(value: number, absolute?: boolean): Image; - - saturation(value: number, absolute?: boolean): Image; - - composite(source: Image, x?: number, y?: number): Image; - - invert(): Image; - - invertValue(): Image; - - invertSaturation(): Image; - - invertHue(): Image; - - hueShift(degrees: number): Image; - + /** + * Use + * {@link https://en.wikipedia.org/wiki/Image_scaling#Nearest-neighbor_interpolation Nearest-neighbor} + * resizing. + */ + static get RESIZE_NEAREST_NEIGHBOR(): "RESIZE_NEAREST_NEIGHBOR"; + + /** + * Used for automatically preserving an image's aspect ratio when resizing. + */ + static get RESIZE_AUTO(): -1; + + /** + * Resizes the image by the given factor. + * + * @param factor Fraction, where: + * - `0.5` is "50%" (half) + * - `1.0` is "100%" (same size) + * - `2.0` is "200%" (double) + * @param mode Default: {@link Image.RESIZE_NEAREST_NEIGHBOR} + */ + scale(factor: number, mode?: ResizeMode): this; + + private __scale__(factor: number, mode?: ResizeMode); + + /** + * Resizes the image to the given dimensions. + * Use {@link Image.RESIZE_AUTO} as either width or height to automatically + * preserve the aspect ratio. + * + * @param width The new width. + * @param height The new height. + * @param mode Default: {@link Image.RESIZE_NEAREST_NEIGHBOR} + */ + resize(width: number, height: number, mode?: ResizeMode): this; + + /** + * Resizes the image so it is contained in the given bounding box. + * Can return an image with one axis smaller than the given bounding box. + * + * @param width The width of the bounding box + * @param height The height of the bounding box + * @param mode Default: {@link Image.RESIZE_NEAREST_NEIGHBOR} + */ + contain(width: number, height: number, mode?: ResizeMode): this; + + /** + * Resizes the image so it is contained in the given bounding box, placing it in the center of the given bounding box. + * Always returns the exact dimensions of the bounding box. + * + * @param width The width of the bounding box + * @param height The height of the bounding box + * @param mode Default: {@link Image.RESIZE_NEAREST_NEIGHBOR} + */ + fit(width: number, height: number, mode?: ResizeMode): this; + + /** + * Resizes the image so it covers the given bounding box, cropping the overflowing edges. + * Always returns the exact dimensions of the bounding box. + * + * @param width The width of the bounding box + * @param height The height of the bounding box + * @param mode Default: {@link Image.RESIZE_NEAREST_NEIGHBOR} + */ + cover(width: number, height: number, mode?: ResizeMode): this; + + private __resize__(width: number, height: number, mode?: ResizeMode): this; + + private __resize_nearest_neighbor__(width: number, height: number): this; + + crop(x: number, y: number, width: number, height: number): this; + + private __crop__(x: number, y: number, width: number, height: number): this; + + /** + * Draws a box at the specified coordinates. + */ + drawBox( + x: number, + y: number, + width: number, + height: number, + color: number | ColorFunction + ): this; + + private __fast_box__( + x: number, + y: number, + width: number, + height: number, + color: number + ): this; + + /** + * Draws a circle at the specified coordinates with the specified radius. + */ + drawCircle( + x: number, + y: number, + radius: number, + color: number | ColorFunction + ): this; + + /** + * Crops the image into a circle. + * + * @param max Whether to use the larger dimension for the size. Default: `false` + * @param feathering How much feathering to apply to the edges. Default: `0` + */ + cropCircle(max?: boolean, feathering?: number): this; + + /** + * Sets the image's opacity. + * + * @param opacity `0`-`1`, where `0` is completely transparent and + * `1` is completely opaque. + * @param absolute Whether to scale the current opacity (`false`) or + * just set the new opacity (`true`). Default: `false` + */ + opacity(opacity: number, absolute?: boolean): this; + + /** + * Set the red channel's saturation value. + * + * @param saturation `0`-`1` + * @param absolute Whether to scale the current saturation (`false`) or + * just set the new saturation (`true`). Default: `false` + */ + red(saturation: number, absolute?: boolean): this; + + /** + * Set the green channel's saturation value. + * + * @param saturation `0`-`1` + * @param absolute Whether to scale the current saturation (`false`) or + * just set the new saturation (`true`). Default: `false` + */ + green(saturation: number, absolute?: boolean): this; + + /** + * Set the blue channel's saturation value. + * + * @param saturation `0`-`1` + * @param absolute Whether to scale the current saturation (`false`) or + * just set the new saturation (`true`). Default: `false` + */ + blue(saturation: number, absolute?: boolean): this; + + private __set_channel_value__( + value: number, + absolute: boolean, + offset: number + ): void; + + /** + * Sets the brightness of the image. + * + * @param value `0`-`1` + * @param absolute Whether to scale the current lightness (`false`) or + * just set the new lightness (`true`). Default: `false` + */ + lightness(value: number, absolute?: boolean): this; + + /** + * Sets the saturation of the image. + * + * @param value `0`-`1` + * @param absolute Whether to scale the current saturation (`false`) or + * just set the new saturation (`true`). Default: `false` + */ + saturation(value: number, absolute?: boolean): this; + + /** + * Composites (overlays) the {@link source} onto this image at the + * specified coordinates. + */ + composite(source: this, x?: number, y?: number): this; + + /** + * Inverts the image's colors. + */ + invert(): this; + + /** + * Inverts the image's value (lightness). + */ + invertValue(): this; + + /** + * Inverts the image's saturation. + */ + invertSaturation(): this; + + /** + * Inverts the image's hue. + */ + invertHue(): this; + + /** + * Shifts the image's hue. + */ + hueShift(degrees: number): this; + + /** + * Gets the average color of the image. + */ averageColor(): number; - dominantColor(ignoreBlack?: boolean, ignoreWhite?: boolean, bwThreshold?: number): number; - - rotate(angle: number, resize?: boolean): Image; - - private __apply__(image: Image | Frame): Image | Frame; - - static gradient(colors: { [position: number]: number; }): ((position: number) => number); - - roundCorners(radius?: number): Image; + /** + * Gets the image's dominant color. + * + * @param ignoreBlack Whether to ignore dark colors below the threshold. + * Default: `true` + * @param ignoreWhite Whether to ignore light colors above the threshold. + * Default: `true` + * @param bwThreshold The black/white threshold (`0`-`64`). + * Default: `0xf` (`15`) + */ + dominantColor( + ignoreBlack?: boolean, + ignoreWhite?: boolean, + bwThreshold?: number + ): number; + + /** + * Rotates the image the given amount of degrees. + * + * @param angle The angle to rotate the image for (in degrees) + * @param resize Whether to resize the image so it fits all pixels (`true`) or + * just ignore outlying pixels (`false`). Default: `true` + */ + rotate(angle: number, resize?: boolean): this; + + /** + * Flips / mirrors the image horizontally or vertically. + */ + flip(direction: "horizontal" | "vertical"): this; + + private __apply__(image: this | Frame): this | Frame; + + /** + * Creates a multi-point gradient generator. + * + * @param colors The gradient points to use + * (e.g. `{0: 0xff0000ff, 1: 0x00ff00ff}`). + * @returns The gradient generator. The function argument is the position + * in the gradient (`0`-`1`). + */ + static gradient(colors: { + [position: number]: number; + }): (position: number) => number; + + /** + * Rounds the image's corners. + * + * @param radius Default: `min(width, height) / 4` + */ + roundCorners(radius?: number): this; private static __gradient__(startColor: number, endColor: number): number; - fisheye(radius?: number): Image; - - async encode(compression?: number, metadata?: PNGMetadata): Promise; + /** + * @param radius Default: `2` + */ + fisheye(radius?: number): this; + + /** + * Encodes the image into a PNG. + * + * @param compression `0`-`9`, where `0` is no compression and `9` is highest + * compression (default: `1`) + * @param metadata + */ + async encode( + compression?: PNGCompressionLevel, + metadata?: PNGMetadata + ): Promise; async encode(metadata?: PNGMetadata): Promise; - async encodeJPEG(quality?: number): Promise; - - async encodeWEBP(quality?: null | number): Promise; - - static async decode(data: Buffer | Uint8Array): Promise - - static get SVG_MODE_SCALE(): number; - - static get SVG_MODE_WIDTH(): number; - - static get SVG_MODE_HEIGHT(): number; + /** + * Encodes the image into a JPEG. + * + * @param quality `1`-`100`, where `1` is lowest quality (highest compression) + * and `100` is highest quality (lowest compression). Default: `90` + */ + async encodeJPEG(quality?: JPEGQuality): Promise; + + /** + * Encodes the image into a WEBP. + * + * @param quality `0`-`100`, or `null` for lossless. `0` is lowest quality + * (highest compression) and `100` is highest quality (lowest compression). + * Default: `null` + */ + async encodeWEBP(quality?: null | WEBPQuality): Promise; + + /** + * Decodes an image (PNG, JPEG or TIFF). + * + * @param data The binary data to decode + * @returns The decoded image + */ + static async decode(data: Buffer | Uint8Array): Promise; + + /** + * Scale the SVG by the given amount. For use with {@link Image.renderSVG}. + */ + static get SVG_MODE_SCALE(): 1; + + /** + * Scale the SVG to fit the given width. For use with {@link Image.renderSVG}. + */ + static get SVG_MODE_WIDTH(): 2; + + /** + * Scale the SVG to fit the given height. For use with {@link Image.renderSVG}. + */ + static get SVG_MODE_HEIGHT(): 3; + + /** + * Creates a new image from the given SVG. + * + * @param svg + * @param size + * @param mode {@link Image.SVG_MODE_SCALE}, {@link Image.SVG_MODE_WIDTH}, or + * {@link Image.SVG_MODE_HEIGHT}. + * + * @returns New bitmap image with the rendered {@link svg}. + */ + static async renderSVG( + svg: string, + size?: number, + mode?: SVGScaleMode + ): Promise; + + /** + * Creates a new image containing the rendered text. + * + * @param font TrueType (ttf/ttc) or OpenType (otf) font buffer to use. + * @param scale + * @param text + * @param color + * @param layout + * + * @returns New image with the rendered {@link text}. + */ + static async renderText( + font: Uint8Array, + scale: number, + text: string, + color?: number, + layout?: TextLayout + ): Promise; +} - static async renderSVG(svg: string, size?: number, mode?: number): Promise; +export type FrameDisposalModeName = "any" | "keep" | "previous" | "background"; - static async renderText(font: Uint8Array, scale: number, text: string, color?: number, layout?: TextLayout): Promise; -}; +export type FrameDisposalModeId = 0 | 1 | 2 | 3; +/** + * Represents a frame in a GIF. + */ export class Frame extends Image { - static get DISPOSAL_KEEP(): string; - - static get DISPOSAL_PREVIOUS(): string; - - static get DISPOSAL_BACKGROUND(): string - - private static __convert_disposal_mode__(mode: string | number): any; - - constructor(width: number, height: number, duration?: number, xOffset?: number, yOffset?: number, disposalMode?: typeof Frame.DISPOSAL_KEEP | string); - + static get DISPOSAL_KEEP(): "keep"; + + static get DISPOSAL_PREVIOUS(): "previous"; + + static get DISPOSAL_BACKGROUND(): "background"; + + private static __convert_disposal_mode__( + mode: FrameDisposalModeName | FrameDisposalModeId + ): FrameDisposalModeId; + + /** + * Creates a new, blank frame. + * + * @param width + * @param height + * @param duration Milliseconds (default: `100`) + * @param xOffset Offset on the X-axis (default: `0`) + * @param yOffset Offset on the y-axis (default: `0`) + * @param disposalMode The frame's disposal mode (default: `'keep'`) + */ + constructor( + width: number, + height: number, + duration: number, + xOffset?: number, + yOffset?: number, + disposalMode?: FrameDisposalModeName | FrameDisposalModeId + ); + + /** + * Milliseconds. + */ duration: number; xOffset: number; yOffset: number; - get disposalMode(): number; - - set disposalMode(disposalMode: string|number); - - toString(): string; - - static from(image: Image, duration?: number, xOffset?: number, yOffset?: number, disposalMode?: typeof Frame.DISPOSAL_KEEP | string): Frame; - - resize(width: number, height: number, mode?: typeof Image.RESIZE_NEAREST_NEIGHBOR | string): Image; -}; + get disposalMode(): FrameDisposalModeId; + + set disposalMode(disposalMode: FrameDisposalModeName | FrameDisposalModeId); + + toString(): `Frame<${number}x${number}x${number}ms>`; + + /** + * Converts an Image instance to a Frame, cloning it in the process + * @param image The image to create the frame from + * @param duration Milliseconds (default: `100`) + * @param xOffset Offset on the X-axis (default: `0`) + * @param yOffset Offset on the y-axis (default: `0`) + * @param disposalMode The frame's disposal mode (default: `'keep'`) + */ + static from( + image: Image, + duration?: number, + xOffset?: number, + yOffset?: number, + disposalMode?: FrameDisposalModeName | FrameDisposalModeId + ): Frame; + + /** + * @param width + * @param height + * @param mode Default: {@link Frame.DISPOSAL_KEEP} + */ + resize( + width: number, + height: number, + mode?: typeof Image.RESIZE_NEAREST_NEIGHBOR | string + ): Image; +} -export class GIF extends Array { +/** + * Represents a GIF image as an array of frames. + */ +export class GIF extends Array { + /** + * @param frames + * @param loopCount How many times to loop the GIF for (`-1` = unlimited). + */ constructor(frames: Frame[], loopCount?: number); get width(): number; get height(): number; - toString(): string; + toString(): `GIF<${number}x${number}x${number}ms>`; - * [Symbol.iterator](): Generator + *[Symbol.iterator](): Generator; slice(start: number, end: number): GIF; + /** + * Milliseconds. + */ get duration(): number; - async encode(quality?: number): Promise + /** + * @param quality GIF quality `0`-`100` (default: `95`) + */ + async encode(quality?: GIFQuality): Promise; + + /** + * @param data + * @param onlyExtractFirstFrame Whether to end GIF decoding after the first + * frame (default: `false`) + */ + static async decode( + data: Buffer | Uint8Array, + onlyExtractFirstFrame?: boolean + ): Promise; + + /** + * @param width + * @param height + * @param mode Default: {@link Image.RESIZE_NEAREST_NEIGHBOR} + */ + resize(width: number, height: number, mode?: ResizeMode): void; +} - static async decode(data: Buffer | Uint8Array, onlyExtractFirstFrame?: boolean): Promise; +export type WrapStyle = "word" | "char"; - resize(width: number, height: number, mode?: typeof Image.RESIZE_NEAREST_NEIGHBOR | string): void; -} +export type VerticalAlign = "left" | "center" | "right"; + +export type HorizontalAlign = "top" | "middle" | "bottom"; export class TextLayout { + /** + * @param options Defaults: + * ```js + * { + * maxWidth: Infinity, + * maxHeight: Infinity, + * wrapStyle: 'word', + * verticalAlign: 'left', + * horizontalAlign: 'top', + * wrapHardBreaks: true, + * } + * ``` + */ constructor(options?: { - maxWidth?: number, - maxHeight?: number, - wrapStyle?: string, - verticalAlign?: string, - horizontalAlign?: string, - wrapHardBreaks?: boolean + /** @default Infinity */ + maxWidth?: number; + + /** @default Infinity */ + maxHeight?: number; + + /** @default 'word' */ + wrapStyle?: WrapStyle; + + /** @default 'left' */ + verticalAlign?: VerticalAlign; + + /** @default 'top' */ + horizontalAlign?: HorizontalAlign; + + /** @default true */ + wrapHardBreaks?: boolean; }); -}; +} + +export type ImageTypeName = "png" | "jpeg" | "tiff" | "gif"; export class ImageType { - static getType(data: Buffer | Uint8Array): string | null; + static getType(data: Buffer | Uint8Array): ImageTypeName | null; static isPNG(view: DataView): boolean; @@ -213,21 +593,363 @@ export class ImageType { static isTIFF(view: DataView): boolean; static isGIF(view: DataView): boolean; -}; +} -export function decode(data: Uint8Array | Buffer, onlyExtractFirstFrame?: boolean): Promise; - -type colorFunction = (x: number, y: number) => number; - -type PNGMetadata = { - title?: string, - author?: string, - description?: string, - copyright?: string, - creationTime?: string | number | Date, - software?: string, - disclaimer?: string, - warning?: string, - source?: string, - comment?: string +/** + * @param data + * @param onlyExtractFirstFrame Whether to end GIF decoding after the first + * frame (default: `false`) + */ +export function decode( + data: Uint8Array | Buffer, + onlyExtractFirstFrame?: boolean +): Promise; + +export type PNGMetadata = { + title?: string; + author?: string; + description?: string; + copyright?: string; + creationTime?: string | number | Date; + software?: string; + disclaimer?: string; + warning?: string; + source?: string; + comment?: string; }; + +export type ColorFunction = (x: number, y: number) => number; + +export type ResizeMode = "RESIZE_NEAREST_NEIGHBOR" | -1; + +/** + * - `0` = no compression + * - `9` = highest compression + */ +export type PNGCompressionLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; + +/** + * {@link Image.SVG_MODE_SCALE}, {@link Image.SVG_MODE_WIDTH}, or + * {@link Image.SVG_MODE_HEIGHT}. + */ +export type SVGScaleMode = 1 | 2 | 3; + +/** + * - `0` = **lowest** quality (smallest file size) + * - `100` = **highest** quality (largest file size) + */ +export type WEBPQuality = + | 0 + | 1 + | 2 + | 3 + | 4 + | 5 + | 6 + | 7 + | 8 + | 9 + | 10 + | 11 + | 12 + | 13 + | 14 + | 15 + | 16 + | 17 + | 18 + | 19 + | 20 + | 21 + | 22 + | 23 + | 24 + | 25 + | 26 + | 27 + | 28 + | 29 + | 30 + | 31 + | 32 + | 33 + | 34 + | 35 + | 36 + | 37 + | 38 + | 39 + | 40 + | 41 + | 42 + | 43 + | 44 + | 45 + | 46 + | 47 + | 48 + | 49 + | 50 + | 51 + | 52 + | 53 + | 54 + | 55 + | 56 + | 57 + | 58 + | 59 + | 60 + | 61 + | 62 + | 63 + | 64 + | 65 + | 66 + | 67 + | 68 + | 69 + | 70 + | 71 + | 72 + | 73 + | 74 + | 75 + | 76 + | 77 + | 78 + | 79 + | 80 + | 81 + | 82 + | 83 + | 84 + | 85 + | 86 + | 87 + | 88 + | 89 + | 90 + | 91 + | 92 + | 93 + | 94 + | 95 + | 96 + | 97 + | 98 + | 99 + | 100; + +/** + * - `0` = **lowest** quality (smallest file size) + * - `100` = **highest** quality (largest file size) + */ +export type JPEGQuality = + | 1 + | 2 + | 3 + | 4 + | 5 + | 6 + | 7 + | 8 + | 9 + | 10 + | 11 + | 12 + | 13 + | 14 + | 15 + | 16 + | 17 + | 18 + | 19 + | 20 + | 21 + | 22 + | 23 + | 24 + | 25 + | 26 + | 27 + | 28 + | 29 + | 30 + | 31 + | 32 + | 33 + | 34 + | 35 + | 36 + | 37 + | 38 + | 39 + | 40 + | 41 + | 42 + | 43 + | 44 + | 45 + | 46 + | 47 + | 48 + | 49 + | 50 + | 51 + | 52 + | 53 + | 54 + | 55 + | 56 + | 57 + | 58 + | 59 + | 60 + | 61 + | 62 + | 63 + | 64 + | 65 + | 66 + | 67 + | 68 + | 69 + | 70 + | 71 + | 72 + | 73 + | 74 + | 75 + | 76 + | 77 + | 78 + | 79 + | 80 + | 81 + | 82 + | 83 + | 84 + | 85 + | 86 + | 87 + | 88 + | 89 + | 90 + | 91 + | 92 + | 93 + | 94 + | 95 + | 96 + | 97 + | 98 + | 99 + | 100; + +/** + * - `0` = **lowest** quality (smallest file size) + * - `100` = **highest** quality (largest file size) + */ +export type GIFQuality = + | 0 + | 1 + | 2 + | 3 + | 4 + | 5 + | 6 + | 7 + | 8 + | 9 + | 10 + | 11 + | 12 + | 13 + | 14 + | 15 + | 16 + | 17 + | 18 + | 19 + | 20 + | 21 + | 22 + | 23 + | 24 + | 25 + | 26 + | 27 + | 28 + | 29 + | 30 + | 31 + | 32 + | 33 + | 34 + | 35 + | 36 + | 37 + | 38 + | 39 + | 40 + | 41 + | 42 + | 43 + | 44 + | 45 + | 46 + | 47 + | 48 + | 49 + | 50 + | 51 + | 52 + | 53 + | 54 + | 55 + | 56 + | 57 + | 58 + | 59 + | 60 + | 61 + | 62 + | 63 + | 64 + | 65 + | 66 + | 67 + | 68 + | 69 + | 70 + | 71 + | 72 + | 73 + | 74 + | 75 + | 76 + | 77 + | 78 + | 79 + | 80 + | 81 + | 82 + | 83 + | 84 + | 85 + | 86 + | 87 + | 88 + | 89 + | 90 + | 91 + | 92 + | 93 + | 94 + | 95 + | 96 + | 97 + | 98 + | 99 + | 100;