import { instantiateEmscriptenWasm } from './emscripten-utils.js'; import visdif from '../../codecs/visdif/visdif.js'; import visdifWasm from 'asset-url:../../codecs/visdif/visdif.wasm'; // `measure` is a (async) function that takes exactly one numeric parameter and // returns a value. The function is assumed to be monotonic (an increase in `parameter` // will result in an increase in the return value. The function uses binary search // to find `parameter` such that `measure` returns `measureGoal`, within an error // of `epsilon`. It will use at most `maxRounds` attempts. export async function binarySearch( measureGoal, measure, { min = 0, max = 100, epsilon = 0.1, maxRounds = 6 } = {}, ) { let parameter = (max - min) / 2 + min; let delta = (max - min) / 4; let value; let round = 1; while (true) { value = await measure(parameter); if (Math.abs(value - measureGoal) < epsilon || round >= maxRounds) { return { parameter, round, value }; } if (value > measureGoal) { parameter -= delta; } else if (value < measureGoal) { parameter += delta; } delta /= 2; round++; } } export async function autoOptimize( bitmapIn, encode, decode, { butteraugliDistanceGoal = 1.4, ...otherOpts } = {}, ) { const { VisDiff } = await instantiateEmscriptenWasm(visdif, visdifWasm); const comparator = new VisDiff( bitmapIn.data, bitmapIn.width, bitmapIn.height, ); let bitmapOut; let binaryOut; // Increasing quality means _decrease_ in Butteraugli distance. // `binarySearch` assumes that increasing `parameter` will // increase the metric value. So multipliy Butteraugli values by -1. const { parameter } = await binarySearch( -1 * butteraugliDistanceGoal, async (quality) => { binaryOut = await encode(bitmapIn, quality); bitmapOut = await decode(binaryOut); return -1 * comparator.distance(bitmapOut.data); }, otherOpts, ); comparator.delete(); return { bitmap: bitmapOut, binary: binaryOut, quality: parameter, }; }