UNPKG

image-js

Version:

Image processing and manipulation in JavaScript

567 lines 17.1 kB
import { subtract } from './compare/index.js'; import { drawLineOnMask, drawPoints, drawPolygonOnMask, drawPolylineOnMask, drawRectangle, } from './draw/index.js'; import { and, invert, or } from './filters/index.js'; import { getBorderPoints } from './maskAnalysis/getBorderPoints.js'; import { getConvexHull } from './maskAnalysis/getConvexHull.js'; import { getExternalContour } from "./maskAnalysis/getExternalContour.js"; import { getFeret } from './maskAnalysis/getFeret.js'; import { getMbr } from './maskAnalysis/getMbr.js'; import { bottomHat, clearBorder, close, dilate, erode, floodFill, morphologicalGradient, open, solidFill, topHat, } from './morphology/index.js'; import { convertColor, copyTo, paintMaskOnMask } from './operations/index.js'; import { boolToNumber } from './utils/boolToNumber.js'; import { colorModels } from './utils/constants/colorModels.js'; export class Mask { /** * The number of columns of the mask. */ width; /** * The number of rows of the mask. */ height; /** * The total number of bits in the mask (width × height). */ size; /** * The number of bits per value in each channel (always 1). */ bitDepth; /** * The color model of the mask (always BINARY). */ colorModel; /** * The number of color channels in the image, excluding the alpha channel (always 1). */ components; /** * The number of channels in the mask, including the alpha channel (always 1). */ channels; /** * Specifying that the mask has no an alpha channel. */ alpha; /** * The maximum value that a pixel channel can have. */ maxValue; /** * Origin of the image relative to a the parent image. */ origin; /** * Typed array holding the mask data. */ data; /** * Construct a new Mask knowing its dimensions. * @param width - Image width. * @param height - Image height. * @param options - Image options. */ constructor(width, height, options = {}) { const { data, origin = { row: 0, column: 0 } } = options; if (width < 1 || !Number.isInteger(width)) { throw new RangeError(`width must be an integer and at least 1. Received ${width}`); } if (height < 1 || !Number.isInteger(height)) { throw new RangeError(`height must be an integer and at least 1. Received ${height}`); } this.width = width; this.height = height; this.size = width * height; this.bitDepth = 1; this.colorModel = 'BINARY'; this.origin = origin; const colorModelDef = colorModels[this.colorModel]; this.components = colorModelDef.components; this.alpha = colorModelDef.alpha; this.channels = colorModelDef.channels; this.maxValue = 1; if (data === undefined) { this.data = new Uint8Array(this.size); } else { const expectedLength = this.size * this.channels; if (data.length !== expectedLength) { throw new RangeError(`incorrect data size: ${data.length}. Expected ${expectedLength}`); } this.data = data; } } /** * Create a new Mask based on the properties of an existing one. * @param other - Reference Mask. * @param options - Mask options. * @returns New mask. */ static createFrom(other, options = {}) { const { width = other.width, height = other.height, origin = other.origin, } = options; return new Mask(width, height, { origin, ...options }); } /** * Get a pixel of the mask. * @param column - Column index. * @param row - Row index. * @returns The pixel. */ getPixel(column, row) { const result = []; const index = row * this.width + column; result.push(this.data[index]); return result; } /** * Set a pixel. * @param column - Column index. * @param row - Row index. * @param value - The pixel value. */ setPixel(column, row, value) { const index = row * this.width + column; this.data[index] = value[0]; } /** * Set a pixel to a given value if the coordinates are inside the mask. * @param column - Column index. * @param row - Row index. * @param value - New color of the pixel to set. */ setVisiblePixel(column, row, value) { if (column >= 0 && column < this.width && row >= 0 && row < this.height) { this.setPixel(column, row, value); } } /** * Get a pixel using its index. * @param index - Index of the pixel. * @returns The pixel. */ getPixelByIndex(index) { return [this.data[index]]; } /** * Set a pixel using its index. * @param index - Index of the pixel. * @param value - New value of the pixel to set. */ setPixelByIndex(index, value) { this.data[index] = value[0]; } /** * Create a mask from an array of points. * @param width - Width of the mask. * @param height - Height of the mask. * @param points - Reference Mask. * @returns New mask. */ static fromPoints(width, height, points) { const mask = new Mask(width, height); for (const point of points) { mask.setBit(point.column, point.row, 1); } return mask; } /** * Create a copy of this mask. * @returns The mask clone. */ clone() { return Mask.createFrom(this, { data: this.data.slice() }); } /** * Get the value of a bit. * @param column - Column index. * @param row - Row index. * @returns The bit value. */ getBit(column, row) { const index = row * this.width + column; return this.data[index]; } /** * Set the value of a bit. * @param column - Column index. * @param row - Row index. * @param value - New bit value. */ setBit(column, row, value) { const index = row * this.width + column; // @ts-expect-error: we know that value is a boolean this.data[index] = value; } /** * Get the value of a bit using index. * @param index - Index of the pixel. * @returns Value of the bit. */ getBitByIndex(index) { return this.data[index * this.channels]; } /** * Set the value of a bit using index. * @param index - Index of the pixel. * @param value - Value to set. */ setBitByIndex(index, value) { this.data[index * this.channels] = boolToNumber(value); } /** * Get the number of pixels that do not have the value 0. * @returns The number of non-zero pixels. */ getNbNonZeroPixels() { let count = 0; for (const datum of this.data) { if (datum) { count++; } } return count; } /** * Get the value of a bit. Function exists for compatibility with Image. * @param column - Column index. * @param row - Row index. * @param channel - Index of the channel, must be zero. * @returns The bit value. */ getValue(column, row, channel) { checkChannel(channel); return this.getBit(column, row); } /** * Set the value of a bit. Function exists for compatibility with Image. * @param column - Column index. * @param row - Row index. * @param channel - Index of the channel, must be zero. * @param value - New bit value. */ setValue(column, row, channel, value) { checkChannel(channel); this.setBit(column, row, value); } /** * Get the value of a bit using index. Function exists for compatibility with Image. * @param index - Index of the pixel. * @param channel - Index of the channel, must be zero. * @returns Value of the bit. */ getValueByIndex(index, channel) { checkChannel(channel); return this.getBitByIndex(index); } /** * Set the value of a bit using index. Function exists for compatibility with Image. * @param index - Index of the pixel. * @param channel - Index of the channel, must be zero. * @param value - Value to set. */ setValueByIndex(index, channel, value) { checkChannel(channel); this.setBitByIndex(index, value); } /** * Get the value of a specific bit. Select bit using a point. * @param point - Coordinates of the desired biz. * @returns Value of the bit. */ getValueByPoint(point) { return this.getValue(point.column, point.row, 0); } /** * Set the value of a specific bit. Select bit using a point. * @param point - Coordinates of the bit. * @param value - Value to set. */ setValueByPoint(point, value) { this.setValue(point.column, point.row, 0, value); } /** * Return the raw mask data. * @returns The raw data. */ getRawImage() { return { width: this.width, height: this.height, data: this.data, }; } [Symbol.for('nodejs.util.inspect.custom')]() { let dataString; if (this.height > 20 || this.width > 20) { dataString = '[...]'; } else { dataString = printData(this); } return `Mask { width: ${this.width} height: ${this.height} data: ${dataString} }`; } /** * Fill the mask with a value. * @param value - Value of the bit. * @returns The mask instance. */ fill(value) { const result = boolToNumber(value); this.data.fill(result); return this; } convertColor(colorModel) { return convertColor(this, colorModel); } // FILTERS /** * Invert the colors of the mask. * @param options - Inversion options. * @returns The inverted mask. */ invert(options) { return invert(this, options); } /** * Subtract other from a mask. * @param other - Image to subtract. * @param options - Inversion options. * @returns The subtracted mask. */ subtract(other, options) { return subtract(this, other, options); } /** * Perform an AND operation on two masks. * @param other - Second mask. * @param options - And options. * @returns AND of the two masks. */ and(other, options) { return and(this, other, options); } /** * Perform an OR operation on two masks. * @param other - Second mask. * @param options - And options. * @returns OR of the two masks. */ or(other, options) { return or(this, other, options); } // MASK ANALYSIS /** * Get the coordinates of the points on the border of a shape defined in a mask. * @param options - Get border points options. * @returns Array of border points. */ getBorderPoints(options) { return getBorderPoints(this, options); } /** * Returns external contour of the mask. Unlike border points, returned points * follow the shape of the mask. * @param options - Get external contour options. * @returns Array of contour points. */ getExternalContour(options) { return getExternalContour(this, options); } /** * Get the vertices of the convex Hull polygon of a mask. * @returns Array of the vertices of the convex Hull in clockwise order. */ getConvexHull() { return getConvexHull(this); } /** * Get the corners of the minimum bounding rectangle of a shape defined in a mask. * @returns Array of border points. */ getMbr() { return getMbr(this); } /** * Computes the Feret data. * @returns The Feret diameters. */ getFeret() { return getFeret(this); } // MORPHOLOGY /** * Erode a Mask. * @param options - Erode options. * @returns The eroded mask. */ erode(options) { return erode(this, options); } /** * Dilate an image. * @param options - Dilate options. * @returns The dilated image. */ dilate(options) { return dilate(this, options); } /** * Open an image. * @param options - Open options. * @returns The opened image. */ open(options) { return open(this, options); } /** * Close an image. * @param options - Close options. * @returns The closed image. */ close(options) { return close(this, options); } /** * Top hat of an image. * @param options - Top hat options. * @returns The top-hatted image. */ topHat(options) { return topHat(this, options); } /** * Bottom hat of an image. * @param options - Bottom hat options. * @returns The bottom-hatted image. */ bottomHat(options) { return bottomHat(this, options); } /** * Apply morphological gradient to an image. * @param options - Morphological gradient options. * @returns The processed image. */ morphologicalGradient(options) { return morphologicalGradient(this, options); } /** * Remove elements connected to the borders of an image. * @param options - Clear border options. * @returns The processed image. */ clearBorder(options) { return clearBorder(this, options); } /** * Apply flood fill algorithm from a given starting point. * @param options - Flood fill options. * @returns The filled mask. */ floodFill(options) { return floodFill(this, options); } /** * Fill holes in regions of interest. * @param options - Flood fill options. * @returns The filled mask. */ solidFill(options) { return solidFill(this, options); } // DRAW /** * Draw a set of points on a mask. * @param points - Array of points. * @param options - Draw points on Image options. * @returns New mask. */ drawPoints(points, options = {}) { return drawPoints(this, points, options); } /** * Draw a line defined by two points onto a mask. * @param from - Line starting point. * @param to - Line ending point. * @param options - Draw Line options. * @returns The mask with the line drawing. */ drawLine(from, to, options = {}) { return drawLineOnMask(this, from, to, options); } /** * Draw a polyline defined by an array of points on a mask. * @param points - Polyline array of points. * @param options - Draw polyline options. * @returns The mask with the polyline drawing. */ drawPolyline(points, options = {}) { return drawPolylineOnMask(this, points, options); } /** * Draw a polygon defined by an array of points onto an mask. * @param points - Polygon vertices. * @param options - Draw Line options. * @returns The mask with the polygon drawing. */ drawPolygon(points, options = {}) { return drawPolygonOnMask(this, points, options); } /** * Draw a rectangle defined by position of the top-left corner, width and height. * @param options - Draw rectangle options. * @returns The image with the rectangle drawing. */ drawRectangle(options = {}) { return drawRectangle(this, options); } // OPERATIONS /** * Copy the mask to another one by specifying the location in the target mask. * @param target - The target mask. * @param options - Options. * @returns The target with the source copied to it. */ copyTo(target, options = {}) { return copyTo(this, target, options); } /** * Paint a mask onto another mask and the given position and with the given value. * @param mask - Mask to paint. * @param options - Paint mask options. * @returns The painted mask. */ paintMask(mask, options) { return paintMaskOnMask(this, mask, options); } } /** * Returns all values of a mask as a string. * @param mask - Input mask. * @returns Formatted string with all values of a mask. */ function printData(mask) { const result = []; for (let row = 0; row < mask.height; row++) { const line = []; for (let column = 0; column < mask.width; column++) { line.push(String(mask.getBit(column, row))); } result.push(`[${line.join(' ')}]`); } return result.join('\n '); } /** * Verify the channel value of a mask. * @param channel - The channel value. */ function checkChannel(channel) { if (channel !== 0) { throw new RangeError(`channel value must be 0 on type Mask. Received ${channel}`); } } //# sourceMappingURL=Mask.js.map