@alttiri/image-hash
Version:
Alt-Image-Hash. An alternative image hashing library.
99 lines (98 loc) • 3.45 kB
JavaScript
import { GrayImageData } from "./mono-image-data.js";
function isString(value) {
return typeof value === "string";
}
/**
* Using of 1 `getUint32` is faster than 3 accesses by index (`array[N]`).
*
* `getCalculateBT601` is faster than:
* `array[i / 4] = data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114;`
* or `getCalculateAverage` is faster than:
* `array[i / 4] = (data[i] + data[i + 1] + data[i + 2]) / 3;`
*
* Using `dw.getUint32` inside the function is faster,
* than using `dw.getUint32` in a loop and passing `uint` directly to the function.
*
* Do not change the get functions' signature.
*/
export const getCalculateBT601 = getCalculateBT601_Optimized;
/** Note: It's without rounding. It works as with Math.trunc/Math.floor. */
export function getCalculateBT601_Original(dw) {
return function calculateBT601(i) {
const uint = dw.getUint32(i, true);
return (uint & 0xFF) * 0.299
+ ((uint >> 8) & 0xFF) * 0.587
+ ((uint >> 16) & 0xFF) * 0.114;
// + 0.5 << 0; // (Math.round) // Add it for rounding.
};
}
/** Faster version of BT601 with very similar results
* (only 0.05 % difference no more than by 1 in comparison with the rounded version). */
export function getCalculateBT601_Optimized(dw) {
return function calculateBT601(i) {
const uint = dw.getUint32(i, true);
return ((uint & 0xFF) * 19595
+ ((uint >> 8) & 0xFF) * 38470
+ ((uint >> 16) & 0xFF) * 7471
+ 0x8000) >> 16; // "0x8000" === "1 << 15" === 32768
};
}
export function getCalculateAverage(dw) {
return function calculateAverage(i) {
const uint = dw.getUint32(i, true);
return ((uint & 0xFF)
+ ((uint >> 8) & 0xFF)
+ ((uint >> 16) & 0xFF)) / 3;
};
}
export function getCalculateBT709(dw) {
return function calculateBT709(i) {
const uint = dw.getUint32(i, true);
return (uint & 0xFF) * 0.2126
+ ((uint >> 8) & 0xFF) * 0.7152
+ ((uint >> 16) & 0xFF) * 0.0722;
};
}
const grayScalerMap = {
"bt601": getCalculateBT601,
"average": getCalculateAverage,
"bt709": getCalculateBT709,
};
function getGrayScalerGetter(grayScaler) {
if (isString(grayScaler)) {
return grayScalerMap[grayScaler];
}
return grayScaler;
}
/**
* `getFunc`:
* - `getCalculateBT601`
* - `getCalculateAverage`
* - `getCalculateBT709`
*/
export function getGrayData(imageData, getFunc = getCalculateBT601) {
if (isString(getFunc)) {
getFunc = getGrayScalerGetter(getFunc);
}
const { data, width, height, data: { length } } = imageData;
const array = new Uint8Array(length / 4);
const dw = new DataView(data.buffer);
const calculateLuminance = getFunc(dw);
// It runs 25_000_000 iterations for 5000x5000 image,
// so, don't use `i < data.length`, but `i < length`.
for (let i = 0; i < length; i += 4) {
array[i / 4] = calculateLuminance(i);
}
return new GrayImageData(array, width, height);
}
// todo: use Uint32Array
//
// const u32 = new Uint32Array(data.buffer);
// const l32 = u32.length;
// for (let i = 0; i < l32; i += 1) {
// const uint = u32[i];
// array[i] = ((uint & 0xFF) * 19595
// + ((uint >> 8) & 0xFF) * 38470
// + ((uint >> 16) & 0xFF) * 7471
// + 0x8000) >> 16;
// }