Install
npm install --save img-mage
Features
- Chained operations
- Support frequency domain processing
- Provide built-in complex number library Complex.js for you to manipulate complex numbers in frequency domain
- Provide highly flexible map function for pixel-wise manipulation
- Support channel-wise operations to reduce execution time
- Support node.js and browser environments
- Currently only support jpeg format!! (Contribution is welcome)
Table of content
How to use
Example 1: Chained operations
const { Image } = require('img-mage');
const { GAUSSIAN_1D, LAPLACIAN_90 } = Image.CONSTANT;
const Gaussian1D = Image.filter(GAUSSIAN_1D, 2);
const Laplacian90 = Image.filter(LAPLACIAN_90);
const img = new Image().load('example.jpg');
img
.convolve1D(Gaussian1D, 'x')
.convolve1D(Gaussian1D, 'y')
.convolve2D(Laplacian90)
.add(img)
.clip()
.save('sharpen.jpg');
Example 2: Harris corner detection algorithm
const corners = img.detectCorners(2, 1000000);
img
.plot(corners)
.save('corners.jpg');
Example 3: Channel-wise Map
We introduce a robust method called map
, which enables pixel-wise manipulation. This method is designed for channel-wise processing, i.e. you can specify the index of the channels that you want to process to reduce execution time.
const height = img.getDimensions()[1];
const cb = (pixel, i, j, k, channel) => channel[height - 1 - i][j];
img.map(cb);
img.map(cb, 0);
img.map(cb, 0, 2);
img.reflectX();
img.reflectX(0);
img.reflectX(0, 2);
Example 4: Frequency domain manipulation
const GLPF = Image.filter(Image.CONSTANT.GLPF);
img
.fourier()
.fourierMap(GLPF)
.inverseFourier()
.clip()
.save('blur.jpg');
Example 5: Custom filters
It is extremely easy to implement a custom filter. If the filter is linear, you can implement it as an 2D array. If the filter is non-linear, e.g. Median filter, you can implement it as a map callback. If the filter is for frequency domain, implement it as a fourierMap callback Example.
const derivativeFilter2D = [
[1, 0, -1],
[2, 0, -2],
[1, 0, -1],
];
img.convolve2D(derivativeFilter);
const derivativeFilter1D = [1, 0, -1];
img.convolve1D(derivativeFilter1D, 'x');
const maxFilter = (pixel, i, j, k, channel) => {
const h = channel.length;
const w = channel[0].length;
let max = Number.NEGATIVE_INFINITY;
for (let x = -1; x <= 1; x++) {
for (let y = -1; y <= 1; y++) {
const posX = i - x;
const posY = j - y;
if (posX < 0 || posX >= h || posY < 0 || posY >= w) {
continue;
}
max = Math.max(max, channel[posX][posY]);
}
}
return max;
}
img.map(maxFilter);
API
Image
const { Image } = require('img-mage');
const img = new Image().load('rgb.jpg');
const [width, height, depth] = img.getDimensions();
const bitDepth = img.getBitDepth();
const R = img.getChannel(0);
const [r, g, b] = img.getPixel(10, 10);
img.save('rgb2.jpg');
map(cb, ...channels)
Map is a robust method, it provides you a flexible way to implement most of the spatial transformations. Map applies the callback to each pixel and produce a new pixel. You can specify the channels you want to apply the map function to reduce execution time. The callback takes current pixel, pixel coordinates (i, j, k), and current channel as input.
Example 1. Invert an image
const maxIntensity = 2 ** img.getBitDepth() - 1;
const cb = (pixel) => maxIntensity - pixel;
img.map(cb);
img.map(cb, 0);
img.map(cb, 1, 2);
Example 2: Add an image
const img2 = new Image().load('img2.jpg');
const cb = (pixel, i, j, k) => pixel + img2.getChannel(k)[i][j];
img.map(cb);
img.map(cb, 0);
img.map(cb, 1, 2);
Example 3: Reflect an image
const height = img.getDimensions()[1];
const cb = (pixel, i, j, k, channel) => channel[height - 1 - i][j];
img.map(cb);
img.map(cb, 0);
img.map(cb, 0, 2);
fourier(...channels)
inverseFourier()
Apply fast fourier transform to the channels of an image and convert it to frequency domain. Apply fast inverse fourier transform to all the fourier channels of an image and convert back to the spatial domain. Note that the fourier transformation is centered.
img
.fourier()
.inverseFourier()
.clip()
fourierMap(cb, ...channels)
Similar to map in spatial domain, fourierMap is the map in frequency domain. The only different is that the callback takes centerX and centerY as additional arguments, which are the center coordinate of the transformation. Note that all pixels in frequency domain are complex number. We provide a library Complex.js for you to manipulate complex numbers
Example: Ideal Low-Pass Filter
const { Complex } = require('img-mage');
const cb = (pixel, i, j, k, centerX, centerY, channel) => {
const distance = Math.sqrt((i - centerX) ** 2 + (j - centerY) ** 2);
if (distance <= 100) {
return pixel;
}
return new Complex(0);
}
img
.fourier()
.fourierMap(cb)
.inverseFourier()
.clip()
.save('blur.jpg');
fourierSpectrum()
fourierPhase()
Get the fourier spectrum (or fourier phase) of an image.
img
.fourier()
.fourierSpectrum()
.rescale()
.logTransform()
.save('fourier-spectrum.jpg');
filter(type, ...options)
We provide some common linear, non-linear, and frequency domain filters. Linear filters are in the form of 1D and 2D arrays, non-linear filters are in the form of map callback, frequency domain filters are in the form of fourierMap callback. List of the filters:
Name | Argument(s) | Type | Remark |
---|
BOX_FILTER | size | Linear | |
LAPLACIAN_45 | No | Linear | |
LAPLACIAN_90 | No | Linear | |
GAUSSIAN_1D | sigma | Linear | |
GAUSSIAN_2D | sigma | Linear | |
MAX_FILTER | size | Non-linear | |
MIN_FILTER | size | Non-linear | |
MEDIAN_FILTER | size | Non-linear | |
ILPF | Cut-off | Frequency domain | Ideal low-pass |
GLPF | Cut-off | Frequency domain | Gaussian low-pass |
BLPF | Cut-off, order | Frequency domain | Butterworth low-pass |
IHPF | Cut-off | Frequency domain | Ideal high-pass |
GHPF | Cut-off | Frequency domain | Gaussian high-pass filter |
ILPF | Cut-off | Frequency domain | Ideal low-pass filter |
BHPF | Cut-off, order | Frequency domain | Butterworth high-pass filter |
const { BOX_FILTER, MEDIAN_FILTER, GHPF } = Image.CONSTANT;
const boxFilter = Image.filter(BOX_FILTER);
const medianFilter = Image.filter(MEDIAN_FILTER, 3);
const gaussianHighPass = Image.filter(GHPF, 100);
img.convolve2D(BOX_FILTER);
img.map(MEDIAN_FILTER);
img.fourier().fourierMap(gaussianHighPass);
convolve1D(filter, direction, ...channels)
convolve2D(filter, ...channels)
Apply 1D and 2D convolution to the channels of an image. For 1D convolution, you should specify the direction of the convolution. It allows you to utilize the advantages of separating 2D filters.
const gaussian1D = Image.filter(Image.CONSTANT.GAUSSIAN_1D, 2);
const gaussian2D = Image.filter(Image.CONSTANT.GAUSSIAN_2D, 2);
const custom1D = [-1, 0, 1];
const custom2D = [
[-1, -2, -1],
[0, 0, 0],
[1, 2, 1],
];
img
.convolve1D(gaussian1D, 'x')
.convolve1D(gaussian1D, 'y');
img.convolve2D(gaussian2D);
img
.convolve1D(custom1D, 'x')
.convolve1D(custom1D, 'y');
img.convolve2D(custom2D);
Apply Harris corner detection algorithm to your image.
const checkerboard = new Image().load('checkboard.jpg');
const corners = checkerboard.detectCorners(2, 1000000);
checkerboard.plot(corners);
crop(x, y, w, h)
Crop an image with width w and height h at (x, y)
img.crop(0, 0, 200, 200);
img.crop(0, 0, 10000, 10000);
rotate(rotation)
Rotate an image by specifying the rotation. 1 and -3 refer to clockwise 90 degrees, 2 and -2 refer to clockwise 180 degrees, 3 and -1 refer to clockwise 270 degrees.
img.rotate(1);
img.rotate(-3);
pad(x, y)
Add zero-padding to an image. The height and width of the resulting image are h + 2x and w + 2y respectively.
img.pad(10);
img.pad(10, 20);
reflectX(...channels)
reflectY(...channels)
Reflect the channels of an image vertically (x-direction) and horizontally (y-direction).
img.reflectX();
img.reflectX(0);
img.reflectX(1, 2);
negative(...channels)
Invert the channels of an image.
img.negative();
img.negative(2);
img.negavie(0, 1);
logTransform(...channels)
Apply log transform to the channels of an image. It enlarges pixel intensity.
img.logTransform();
img.logTransform(0, 1);
powerLawTransform(gamma, ...channels)
Apply power law transform to the channels of an image. gamma > 1 compresses the intensity while gamma < 1 enlarge the intensity.
img.powerLawTransform(0.5);
img.powerLawTransform(2);
img.powerLawTransform(2, 0);
clip(...channels)
Clip the overflow and underflow pixels to max intensity and 0 respectively.
img.clip();
img.clip(0);
rescale(...channels)
Rescale the pixels to the range [0, maxIntensity].
img.rescale();
img.rescale(1);
blur(sigma, ...channels)
Blur the channels of an image using Gaussian filter. Sigma controls the standard deviation of the distribution, larger sigma produces blurrier image.
img.blur(2);
img.blur(2, 1, 2);
sharpen(sigma, ...channels)
Sharpen the channels of an image using Laplacian filter. Sigma controls the sharp level, smaller sharper.
img.sharpen(0.5);
img.sharpen(2);
abs(...channels)
Calculate the absolute value of each pixel in the channels of an image.
add(Image, ...channels)
Apply pixel-wise addition to the channels of an image.
const Laplacian2D = Image.filter(Image.CONSTANT.LAPLACIAN_90);
img
.convolve2D(Laplacian2D)
.add(img)
subtract(Image, ...channels)
Apply pixel-wise subtraction to the channels of an image.
const sharpen = new Image().load('sharpen.jpg');
img
.subtract(sharpen)
.abs()
.rescale()
.logTransform()
.save('difference.jpg');
multiply(Image, ...channels)
Apply pixel-wise multiplication to the channels of an image.
RGBtoYIQ()
YIQtoRGB()
Convert RGB to YIQ and YIQ back to RGB.
const YIQ = img.RGBtoYIQ();
YIQ.getChannel(0);
YIQ.getChannel(1);
YIQ.getChannel(2);
const RGB = YIQ.YIQtoRGB();
Future Plans (Contribution is welcome)
- Support more image format (Currently only support jpeg)
- Create a playground website to experience the library
- Add more algorithm, such as scaling
- Optimize implementations
License
MIT