mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-11-14 06:52:59 +08:00
Merge branch 'master' into dev
This commit is contained in:
commit
a06dbb6fdd
@ -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)
|
[![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)
|
[![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
|
This repository contains JavaScript based examples of many
|
||||||
popular algorithms and data structures.
|
popular algorithms and data structures.
|
||||||
|
3400
package-lock.json
generated
3400
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -38,14 +38,14 @@
|
|||||||
"@babel/cli": "7.20.7",
|
"@babel/cli": "7.20.7",
|
||||||
"@babel/preset-env": "7.20.2",
|
"@babel/preset-env": "7.20.2",
|
||||||
"@types/jest": "29.4.0",
|
"@types/jest": "29.4.0",
|
||||||
"canvas": "2.11.0",
|
|
||||||
"eslint": "8.33.0",
|
"eslint": "8.33.0",
|
||||||
"eslint-config-airbnb": "19.0.4",
|
"eslint-config-airbnb": "19.0.4",
|
||||||
"eslint-plugin-import": "2.27.5",
|
"eslint-plugin-import": "2.27.5",
|
||||||
"eslint-plugin-jest": "27.2.1",
|
"eslint-plugin-jest": "27.2.1",
|
||||||
"eslint-plugin-jsx-a11y": "6.7.1",
|
"eslint-plugin-jsx-a11y": "6.7.1",
|
||||||
"husky": "8.0.3",
|
"husky": "8.0.3",
|
||||||
"jest": "29.4.1"
|
"jest": "29.4.1",
|
||||||
|
"pngjs": "^7.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.15.0",
|
"node": ">=16.15.0",
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
@ -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 |
Loading…
Reference in New Issue
Block a user