UNPKG

@alttiri/image-hash

Version:

Alt-Image-Hash. An alternative image hashing library.

151 lines (150 loc) 5.87 kB
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); }