mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-11-10 11:09:43 +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)
|
||||
[![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
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/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",
|
||||
|
@ -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