image-js
Version:
Image processing and manipulation in JavaScript
179 lines (167 loc) • 4.67 kB
text/typescript
import { xMedian } from 'ml-spectra-processing';
import type { Image } from '../Image.js';
import type { Point } from '../utils/geometry/points.js';
import { getOutputImage } from '../utils/getOutputImage.js';
import { assertUnreachable } from '../utils/validators/assert.js';
export interface PixelateOptions {
/**
* Range of pixelated area.
*/
cellSize: number;
/**
* algorithm to use.
* @default `'center'`
*/
algorithm?: 'center' | 'median' | 'mean';
/**
* Image to which to output.
*/
out?: Image;
}
interface GetValueOptions {
/*
* width of a region to look for center
*/
width: number;
/*
* height of a region to look for center
*/
height: number;
/*
* top left point of a region which serves as point of origin
*/
origin: Point;
}
/**
* Function to pixelate an image.
* @param image - Image to be pixelated.
* @param options - PixelateOptions.
* @returns Pixelated Image.
*/
export function pixelate(image: Image, options: PixelateOptions): Image {
const { cellSize, algorithm = 'center' } = options;
if (!Number.isInteger(cellSize)) {
throw new TypeError('cellSize must be an integer');
}
if (cellSize < 2) {
throw new RangeError('cellSize must be greater than 1');
}
const newImage = getOutputImage(image, options);
const getCellValue = getCellValueFunction(algorithm);
for (let channel = 0; channel < image.channels; channel++) {
for (let column = 0; column < image.width; column += cellSize) {
for (let row = 0; row < image.height; row += cellSize) {
const currentCellWidth = Math.min(cellSize, image.width - column);
const currentCellHeight = Math.min(cellSize, image.height - row);
const value = getCellValue(image, channel, {
width: currentCellWidth,
height: currentCellHeight,
origin: { column, row },
});
for (
let newColumn = column;
newColumn < column + currentCellWidth;
newColumn++
) {
for (let newRow = row; newRow < row + currentCellHeight; newRow++) {
newImage.setValue(newColumn, newRow, channel, value);
}
}
}
}
}
return newImage;
}
/**
* Computes the center value for the current sector
* @param image - image used for the algorithm
* @param channel - image channel toto find center value of
* @param options - GetValueOptions
* @returns center value
*/
function getCellCenter(
image: Image,
channel: number,
options: GetValueOptions,
): number {
const center = {
column: Math.floor(
(options.origin.column + options.origin.column + options.width - 1) / 2,
),
row: Math.floor(
(options.origin.row + options.origin.row + options.height - 1) / 2,
),
};
const value = image.getValue(center.column, center.row, channel);
return value;
}
/**
* Computes mean value for the current sector
* @param image - image used for algorithm
* @param channel - current channel of an image
* @param options - GetValueOptions
* @returns mean value
*/
function getCellMean(image: Image, channel: number, options: GetValueOptions) {
let sum = 0;
for (
let column = options.origin.column;
column < options.origin.column + options.width;
column++
) {
for (
let row = options.origin.row;
row < options.origin.row + options.height;
row++
) {
sum += image.getValue(column, row, channel);
}
}
return Math.round(sum / (options.width * options.height));
}
/**
* Computes a median value for the current sector
* @param image - image used algorithm
* @param channel - current channel of an image
* @param options - GetValueOptions
* @returns median value
*/
function getCellMedian(
image: Image,
channel: number,
options: GetValueOptions,
) {
const array = [];
for (
let column = options.origin.column;
column < options.origin.column + options.width;
column++
) {
for (
let row = options.origin.row;
row < options.origin.row + options.height;
row++
) {
array.push(image.getValue(column, row, channel));
}
}
return xMedian(array);
}
/**
* Chooses which algorithm to use for pixelization and returns a function to use for computation
* @param algorithm - string with the name of an algorithm
* @returns function
*/
function getCellValueFunction(algorithm: 'center' | 'mean' | 'median') {
switch (algorithm) {
case 'mean':
return getCellMean;
case 'median':
return getCellMedian;
case 'center':
return getCellCenter;
default:
assertUnreachable(algorithm);
break;
}
}