Merge branch 'master' into dev

This commit is contained in:
Devesh35 2023-04-10 23:13:39 +05:30 committed by GitHub
commit a06dbb6fdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1358 additions and 2223 deletions

View File

@ -9,6 +9,7 @@
[![CI](https://github.com/trekhleb/javascript-algorithms/workflows/CI/badge.svg)](https://github.com/trekhleb/javascript-algorithms/actions?query=workflow%3ACI+branch%3Amaster)
[![codecov](https://codecov.io/gh/trekhleb/javascript-algorithms/branch/master/graph/badge.svg)](https://codecov.io/gh/trekhleb/javascript-algorithms)
![repo size](https://img.shields.io/github/repo-size/trekhleb/javascript-algorithms.svg)
This repository contains JavaScript based examples of many
popular algorithms and data structures.

3400
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -38,14 +38,14 @@
"@babel/cli": "7.20.7",
"@babel/preset-env": "7.20.2",
"@types/jest": "29.4.0",
"canvas": "2.11.0",
"eslint": "8.33.0",
"eslint-config-airbnb": "19.0.4",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-jest": "27.2.1",
"eslint-plugin-jsx-a11y": "6.7.1",
"husky": "8.0.3",
"jest": "29.4.1"
"jest": "29.4.1",
"pngjs": "^7.0.0"
},
"engines": {
"node": ">=16.15.0",

View File

@ -0,0 +1,85 @@
import fs from 'fs';
import { PNG } from 'pngjs';
import resizeImageWidth from '../resizeImageWidth';
const testImageBeforePath = './src/algorithms/image-processing/seam-carving/__tests__/test-image-before.png';
const testImageAfterPath = './src/algorithms/image-processing/seam-carving/__tests__/test-image-after.png';
/**
* Compares two images and finds the number of different pixels.
*
* @param {ImageData} imgA - ImageData for the first image.
* @param {ImageData} imgB - ImageData for the second image.
* @param {number} threshold - Color difference threshold [0..255]. Smaller - stricter.
* @returns {number} - Number of different pixels.
*/
function pixelsDiff(imgA, imgB, threshold = 0) {
if (imgA.width !== imgB.width || imgA.height !== imgB.height) {
throw new Error('Images must have the same size');
}
let differentPixels = 0;
const numColorParams = 4; // RGBA
for (let pixelIndex = 0; pixelIndex < imgA.data.length; pixelIndex += numColorParams) {
// Get pixel's color for each image.
const [aR, aG, aB] = imgA.data.subarray(pixelIndex, pixelIndex + numColorParams);
const [bR, bG, bB] = imgB.data.subarray(pixelIndex, pixelIndex + numColorParams);
// Get average pixel's color for each image (make them greyscale).
const aAvgColor = Math.floor((aR + aG + aB) / 3);
const bAvgColor = Math.floor((bR + bG + bB) / 3);
// Compare pixel colors.
if (Math.abs(aAvgColor - bAvgColor) > threshold) {
differentPixels += 1;
}
}
return differentPixels;
}
const pngLoad = (path) => new Promise((resolve) => {
fs.createReadStream(path)
.pipe(new PNG())
.on('parsed', function Parsed() {
/** @type {ImageData} */
const imageData = {
colorSpace: 'srgb',
width: this.width,
height: this.height,
data: this.data,
};
resolve(imageData);
});
});
describe('resizeImageWidth', () => {
it('should perform content-aware image width reduction', async () => {
const imgBefore = await pngLoad(testImageBeforePath);
const imgAfter = await pngLoad(testImageAfterPath);
const toWidth = Math.floor(imgBefore.width / 2);
const {
img: imgResized,
size: resizedSize,
} = resizeImageWidth({ img: imgBefore, toWidth });
expect(imgResized).toBeDefined();
expect(resizedSize).toBeDefined();
expect(resizedSize).toEqual({ w: toWidth, h: imgBefore.height });
expect(imgResized.width).toBe(imgAfter.width);
expect(imgResized.height).toBe(imgAfter.height);
const colorThreshold = 50;
const differentPixels = pixelsDiff(imgResized, imgAfter, colorThreshold);
// Allow 10% of pixels to be different
const pixelsThreshold = Math.floor((imgAfter.width * imgAfter.height) / 10);
expect(differentPixels).toBeLessThanOrEqual(pixelsThreshold);
});
});

View File

@ -1,91 +0,0 @@
import { createCanvas, loadImage } from 'canvas';
import resizeImageWidth from '../resizeImageWidth';
const testImageBeforePath = './src/algorithms/image-processing/seam-carving/__tests__/test-image-before.jpg';
const testImageAfterPath = './src/algorithms/image-processing/seam-carving/__tests__/test-image-after.jpg';
/**
* Compares two images and finds the number of different pixels.
*
* @param {ImageData} imgA - ImageData for the first image.
* @param {ImageData} imgB - ImageData for the second image.
* @param {number} threshold - Color difference threshold [0..255]. Smaller - stricter.
* @returns {number} - Number of different pixels.
*/
function pixelsDiff(imgA, imgB, threshold = 0) {
if (imgA.width !== imgB.width || imgA.height !== imgB.height) {
throw new Error('Images must have the same size');
}
let differentPixels = 0;
const numColorParams = 4; // RGBA
for (let pixelIndex = 0; pixelIndex < imgA.data.length; pixelIndex += numColorParams) {
// Get pixel's color for each image.
const [aR, aG, aB] = imgA.data.subarray(pixelIndex, pixelIndex + numColorParams);
const [bR, bG, bB] = imgB.data.subarray(pixelIndex, pixelIndex + numColorParams);
// Get average pixel's color for each image (make them greyscale).
const aAvgColor = Math.floor((aR + aG + aB) / 3);
const bAvgColor = Math.floor((bR + bG + bB) / 3);
// Compare pixel colors.
if (Math.abs(aAvgColor - bAvgColor) > threshold) {
differentPixels += 1;
}
}
return differentPixels;
}
describe('resizeImageWidth', () => {
it('should perform content-aware image width reduction', () => {
// @see: https://jestjs.io/docs/asynchronous
return Promise.all([
loadImage(testImageBeforePath),
loadImage(testImageAfterPath),
]).then(([imgBefore, imgAfter]) => {
// Original image.
const canvasBefore = createCanvas(imgBefore.width, imgBefore.height);
const ctxBefore = canvasBefore.getContext('2d');
ctxBefore.drawImage(imgBefore, 0, 0, imgBefore.width, imgBefore.height);
const imgDataBefore = ctxBefore.getImageData(0, 0, imgBefore.width, imgBefore.height);
// Resized image saved.
const canvasAfter = createCanvas(imgAfter.width, imgAfter.height);
const ctxAfter = canvasAfter.getContext('2d');
ctxAfter.drawImage(imgAfter, 0, 0, imgAfter.width, imgAfter.height);
const imgDataAfter = ctxAfter.getImageData(0, 0, imgAfter.width, imgAfter.height);
const toWidth = Math.floor(imgBefore.width / 2);
const {
img: resizedImg,
size: resizedSize,
} = resizeImageWidth({ img: imgDataBefore, toWidth });
expect(resizedImg).toBeDefined();
expect(resizedSize).toBeDefined();
// Resized image generated.
const canvasTest = createCanvas(resizedSize.w, resizedSize.h);
const ctxTest = canvasTest.getContext('2d');
ctxTest.putImageData(resizedImg, 0, 0, 0, 0, resizedSize.w, resizedSize.h);
const imgDataTest = ctxTest.getImageData(0, 0, resizedSize.w, resizedSize.h);
expect(resizedSize).toEqual({ w: toWidth, h: imgBefore.height });
expect(imgDataTest.width).toBe(toWidth);
expect(imgDataTest.height).toBe(imgBefore.height);
expect(imgDataTest.width).toBe(imgAfter.width);
expect(imgDataTest.height).toBe(imgAfter.height);
const colorThreshold = 50;
const differentPixels = pixelsDiff(imgDataTest, imgDataAfter, colorThreshold);
// Allow 10% of pixels to be different
const pixelsThreshold = Math.floor((imgAfter.width * imgAfter.height) / 10);
expect(differentPixels).toBeLessThanOrEqual(pixelsThreshold);
});
});
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB