91 lines
3.4 KiB
TypeScript
91 lines
3.4 KiB
TypeScript
import type { ErrorCode } from "./error";
|
|
|
|
export const NANOVDB_RENDER_GOLDEN_SCHEMA_VERSION = 1 as const;
|
|
export const NANOVDB_GOLDEN_MISMATCH_CODE = "NANOVDB_GOLDEN_MISMATCH" as const satisfies ErrorCode;
|
|
|
|
export interface NanoVDBRenderGoldenThresholdsIR {
|
|
maxChannelError: number;
|
|
meanAbsoluteError: number;
|
|
rmsError: number;
|
|
alphaCoverageDeltaRatio: number;
|
|
}
|
|
|
|
export interface NanoVDBRenderGoldenComparisonIR {
|
|
schemaVersion: typeof NANOVDB_RENDER_GOLDEN_SCHEMA_VERSION;
|
|
status: "READY" | "BLOCKED";
|
|
pixelCount: number;
|
|
comparedChannels: number;
|
|
maxChannelError: number;
|
|
meanAbsoluteError: number;
|
|
rmsError: number;
|
|
referenceAlphaPixels: number;
|
|
actualAlphaPixels: number;
|
|
alphaCoverageDeltaRatio: number;
|
|
thresholds: NanoVDBRenderGoldenThresholdsIR;
|
|
errorCode: typeof NANOVDB_GOLDEN_MISMATCH_CODE | null;
|
|
}
|
|
|
|
function validateThresholds(value: NanoVDBRenderGoldenThresholdsIR): NanoVDBRenderGoldenThresholdsIR {
|
|
if (
|
|
!Number.isInteger(value.maxChannelError) || value.maxChannelError < 0 || value.maxChannelError > 255 ||
|
|
!Number.isFinite(value.meanAbsoluteError) || value.meanAbsoluteError < 0 || value.meanAbsoluteError > 255 ||
|
|
!Number.isFinite(value.rmsError) || value.rmsError < 0 || value.rmsError > 255 ||
|
|
!Number.isFinite(value.alphaCoverageDeltaRatio) || value.alphaCoverageDeltaRatio < 0 || value.alphaCoverageDeltaRatio > 1
|
|
) {
|
|
throw new Error("NANOVDB_INVALID_ARGUMENT: render golden thresholds are invalid");
|
|
}
|
|
return { ...value };
|
|
}
|
|
|
|
export function compareNanoVDBRenderGolden(
|
|
reference: Uint8Array,
|
|
actual: Uint8Array,
|
|
thresholdsValue: NanoVDBRenderGoldenThresholdsIR,
|
|
): NanoVDBRenderGoldenComparisonIR {
|
|
if (
|
|
!(reference instanceof Uint8Array) || !(actual instanceof Uint8Array) ||
|
|
reference.byteLength === 0 || reference.byteLength !== actual.byteLength ||
|
|
reference.byteLength % 4 !== 0
|
|
) {
|
|
throw new Error("NANOVDB_INVALID_ARGUMENT: render golden images must be equal non-empty RGBA8 buffers");
|
|
}
|
|
const thresholds = validateThresholds(thresholdsValue);
|
|
let maximum = 0;
|
|
let absoluteTotal = 0;
|
|
let squaredTotal = 0;
|
|
let referenceAlphaPixels = 0;
|
|
let actualAlphaPixels = 0;
|
|
for (let index = 0; index < reference.byteLength; index++) {
|
|
const difference = Math.abs(reference[index] - actual[index]);
|
|
maximum = Math.max(maximum, difference);
|
|
absoluteTotal += difference;
|
|
squaredTotal += difference * difference;
|
|
if ((index & 3) === 3) {
|
|
if (reference[index] > 0) referenceAlphaPixels++;
|
|
if (actual[index] > 0) actualAlphaPixels++;
|
|
}
|
|
}
|
|
const pixelCount = reference.byteLength / 4;
|
|
const meanAbsoluteError = absoluteTotal / reference.byteLength;
|
|
const rmsError = Math.sqrt(squaredTotal / reference.byteLength);
|
|
const alphaCoverageDeltaRatio = Math.abs(referenceAlphaPixels - actualAlphaPixels) / pixelCount;
|
|
const matches = maximum <= thresholds.maxChannelError &&
|
|
meanAbsoluteError <= thresholds.meanAbsoluteError &&
|
|
rmsError <= thresholds.rmsError &&
|
|
alphaCoverageDeltaRatio <= thresholds.alphaCoverageDeltaRatio;
|
|
return {
|
|
schemaVersion: NANOVDB_RENDER_GOLDEN_SCHEMA_VERSION,
|
|
status: matches ? "READY" : "BLOCKED",
|
|
pixelCount,
|
|
comparedChannels: reference.byteLength,
|
|
maxChannelError: maximum,
|
|
meanAbsoluteError,
|
|
rmsError,
|
|
referenceAlphaPixels,
|
|
actualAlphaPixels,
|
|
alphaCoverageDeltaRatio,
|
|
thresholds,
|
|
errorCode: matches ? null : NANOVDB_GOLDEN_MISMATCH_CODE,
|
|
};
|
|
}
|