@alttiri/image-hash
Version:
Alt-Image-Hash. An alternative image hashing library.
140 lines (139 loc) • 4.2 kB
JavaScript
import { BiImageData } from "./mono-image-data.js";
export class ImageHash {
constructor(mid) {
this.mono = mid;
}
static fromMono(mono) {
return new ImageHash(mono);
}
getMono(copy = false) {
if (copy) {
const { data, width, height } = this.mono;
return new BiImageData(new Uint8Array(data.buffer), width, height);
}
return this.mono;
}
static fromHex(hexLine, width, height) {
const biUi8a = hexToBiUi8a(hexLine);
return ImageHash.from(biUi8a, width, height);
}
static fromBin(binLine, width, height) {
const biUi8a = binToBiUi8a(binLine);
return ImageHash.from(biUi8a, width, height);
}
static from(biUi8a, width, height) {
if (!width || !height) {
({ width, height } = calculateSquareSize(biUi8a.length));
}
else {
const square = width * height;
if (square < biUi8a.length && square + 8 > biUi8a.length) {
biUi8a = biUi8a.slice(biUi8a.length - square);
}
}
const mono = new BiImageData(biUi8a, width, height);
return ImageHash.fromMono(mono);
}
/** Bits size */
get size() {
return this.mono.data.length;
}
get hex() {
return biUi8aToHex(this.mono.data);
}
get bin() {
return biUi8aToBin(this.mono.data);
}
diff(hash) {
return hammingDistanceBiUi8a(this.mono.data, hash.mono.data);
}
diffHex(hex) {
return hammingDistanceBiUi8a(this.mono.data, ImageHash.fromHex(hex).mono.data);
}
diffBin(bin) {
return hammingDistanceBiUi8a(this.mono.data, ImageHash.fromBin(bin).mono.data);
}
static diffHex(hex1, hex2) {
return ImageHash.fromHex(hex1).diffHex(hex2);
}
static diffBin(bin1, bin2) {
return ImageHash.fromBin(bin1).diffBin(bin2);
}
}
export function calculateSquareSize(pixelCount) {
const sqrt = Math.sqrt(pixelCount);
if (sqrt % 1 === 0) {
return { width: sqrt, height: sqrt };
}
return { width: 0, height: 0 };
}
export function hexToBiUi8a(hex) {
hex = hex.replace(/_|\s+/g, "").replace(/^0x/, "");
if (hex.length % 2) {
hex = "0" + hex;
}
const ui8a = new Uint8Array(hex.length * 4);
for (let k = 0, j = 0; k < hex.length; k += 2) {
const byte = parseInt(hex.slice(k, k + 2), 16);
for (let i = 7; i >= 0; i--) {
ui8a[j++] = ((byte >> i) & 1 ? 255 : 0);
}
}
return ui8a;
}
export function binToBiUi8a(bin) {
bin = bin.replace(/_|\s+/g, "").replace(/^0b/, "");
if (bin.length % 8) {
bin = "0".repeat(8 - bin.length % 8) + bin;
}
const ui8a = new Uint8Array(bin.length);
let i = 0;
for (const bit of bin) {
ui8a[i++] = bit === "1" ? 255 : 0;
}
return ui8a;
}
export function biUi8aToHex(biUi8a) {
const remainder = biUi8a.length % 8;
if (remainder) {
const extendedLength = biUi8a.length + 8 - remainder;
const _biUi8a = new Uint8Array(extendedLength);
_biUi8a.set(biUi8a, extendedLength - biUi8a.length);
biUi8a = _biUi8a;
}
const byteArray = [];
let currentByte = 0;
for (let i = 0; i < biUi8a.length; i++) {
const bit = biUi8a[i] === 0 ? 0 : 1;
currentByte = (currentByte << 1) | bit;
if ((i + 1) % 8 === 0) {
byteArray.push(currentByte);
currentByte = 0;
}
}
const hexes = byteArray.map(byte => byte.toString(16).padStart(2, "0"));
return hexes.join("");
}
export function biUi8aToBin(biUi8a) {
const byteArray = [];
const remainder = biUi8a.length % 8;
if (remainder) {
byteArray.push("0".repeat(8 - remainder));
}
for (const bitStr of biUi8a) {
byteArray.push(bitStr ? "1" : "0");
}
return byteArray.join("");
}
export function hammingDistanceBiUi8a(arr1, arr2) {
if (arr1.length !== arr2.length) {
return -1;
}
let count = 0;
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) {
count++;
}
}
return count;
}