@alttiri/image-hash
Version:
Alt-Image-Hash. An alternative image hashing library.
151 lines (150 loc) • 5.87 kB
JavaScript
import { calculateMedian } from "./median.js";
import { GrayImageData } from "./mono-image-data.js";
const defaultSize = 8;
export function scaleDownLinear(orig, opts = {}) {
const width = opts.size || opts.width || defaultSize;
const height = opts.size || opts.height || defaultSize;
const round = opts.round || "round";
const { median = false, ignore = false } = opts;
if (width === orig.width && height === orig.height) {
return orig;
}
if (width > orig.width || height > orig.height) {
if (ignore) {
return orig;
}
throw new Error("Up-scaling is not supported");
}
const dest = new GrayImageData(new Uint8Array(width * height), width, height);
if (median) {
scaleDownLinearMedianEx(orig, dest, round);
}
else {
scaleDownLinearAverageEx(orig, dest, round);
}
return dest;
}
function scaleDownLinearAverageEx(from, to, round = "round") {
if (round === "round") {
return scaleDownLinearAverageRound(from, to);
}
const near = Math[round];
const { data: orig, width, height } = from;
const { data: dest, width: newWidth, height: newHeight } = to;
const xScale = width / newWidth;
const yScale = height / newHeight;
for (let newY = 0; newY < newHeight; newY++) {
for (let newX = 0; newX < newWidth; newX++) {
const fromY = near(yScale * newY);
const fromX = near(xScale * newX);
const toY = near(yScale * (newY + 1));
const toX = near(xScale * (newX + 1));
const count = (toY - fromY) * (toX - fromX);
let value = 0;
for (let y = fromY; y < toY; y++) {
for (let x = fromX; x < toX; x++) {
value += orig[y * width + x];
}
}
dest[newY * newWidth + newX] = value / count + 0.5 << 0;
}
}
}
function scaleDownLinearAverageRound(from, to) {
const { data: orig, width, height } = from;
const { data: dest, width: newWidth, height: newHeight } = to;
const xScale = width / newWidth;
const yScale = height / newHeight;
for (let newY = 0; newY < newHeight; newY++) {
for (let newX = 0; newX < newWidth; newX++) {
const fromY = yScale * newY + 0.5 << 0;
const fromX = xScale * newX + 0.5 << 0;
const toY = yScale * (newY + 1) + 0.5 << 0;
const toX = xScale * (newX + 1) + 0.5 << 0;
const count = (toY - fromY) * (toX - fromX);
let value = 0;
for (let y = fromY; y < toY; y++) {
for (let x = fromX; x < toX; x++) {
value += orig[y * width + x];
}
}
dest[newY * newWidth + newX] = value / count + 0.5 << 0;
}
}
}
function scaleDownLinearMedianEx(from, to, round = "round") {
const { data: orig, width, height } = from;
const { data: dest, width: newWidth, height: newHeight } = to;
const xScale = width / newWidth;
const yScale = height / newHeight;
const near = Math[round];
const cache = new Map();
for (let newY = 0; newY < newHeight; newY++) {
for (let newX = 0; newX < newWidth; newX++) {
const fromY = near(yScale * newY);
const fromX = near(xScale * newX);
const toY = near(yScale * (newY + 1));
const toX = near(xScale * (newX + 1));
const count = (toY - fromY) * (toX - fromX);
const value = cache.get(count);
const medianArray = value || new Uint8Array(count);
if (!value) {
cache.set(count, medianArray);
}
let i = 0;
let offset = fromY * width;
for (let y = fromY; y < toY; y++) {
for (let x = fromX; x < toX; x++) {
medianArray[i++] = orig[offset + x];
}
offset += width;
}
const medianValue = calculateMedian(medianArray);
dest[newY * newWidth + newX] = Math.round(medianValue);
}
}
}
export function scaleDownLinearAverage(orig, width, height, round = "round") {
const data = new Uint8Array(width * height);
scaleDownLinearAverageEx(orig, { data, width, height }, round);
return data;
}
export function scaleDownLinearMedian(orig, width, height, round = "round") {
const data = new Uint8Array(width * height);
scaleDownLinearMedianEx(orig, { data, width, height }, round);
return data;
}
function printArray(array, columns) {
console.log(getPrintedArray(array, columns));
}
export function getPrintedArray(array, columns) {
// @ts-ignore
return array.reduce((acc, cur, i) => {
if (i % columns === 0) {
acc.push([]);
}
acc[Math.trunc(i / columns)].push(cur);
return acc;
}, []).map((a) => a.map(d => d.toString().padStart(3)).join(" ")).join("\n");
}
export function scaleUpIntegerTwice(orig) {
return scaleUpNearestNeighbor(orig, orig.width * 2, orig.height * 2);
}
export function scaleUpNearestNeighbor(orig, newWidth, newHeight, integer = false) {
if (integer) {
newWidth = orig.width * (newWidth / orig.width << 0); // "Math.trunc"
newHeight = orig.height * (newHeight / orig.height << 0);
}
const { data, width, height } = orig;
const dest = new Uint8Array(newWidth * newHeight);
const xScale = width / newWidth;
const yScale = height / newHeight;
for (let newY = 0; newY < newHeight; newY++) {
for (let newX = 0; newX < newWidth; newX++) {
const x = Math.trunc(newX * xScale);
const y = Math.trunc(newY * yScale);
dest[newY * newWidth + newX] = data[y * width + x];
}
}
return orig.newInstance(dest, newWidth, newHeight);
}