(function exportCardPreviewMath(root)
{
/** Find inclusive nontransparent bounds in bottom-origin RGBA pixels. */
function findAlphaBounds(pixels, width, height)
{
let min_x = width;
let min_y = height;
let max_x = -1;
let max_y = -1;
for(let y = 0; y < height; ++y)
{
for(let x = 0; x < width; ++x)
{
if(pixels[(y * width + x) * 4 + 3] != 0)
{
min_x = Math.min(min_x, x);
min_y = Math.min(min_y, y);
max_x = Math.max(max_x, x);
max_y = Math.max(max_y, y);
}
}
}
return max_x < 0 ? null : {min_x, min_y, max_x, max_y};
}
/** Crop and flip bottom-origin WebGL pixels into top-origin RGBA data. */
function cropWebGLPixels(pixels, source_width, bounds)
{
const width = bounds.max_x - bounds.min_x + 1;
const height = bounds.max_y - bounds.min_y + 1;
const data = new Uint8ClampedArray(width * height * 4);
for(let output_y = 0; output_y < height; ++output_y)
{
const source_y = bounds.max_y - output_y;
for(let output_x = 0; output_x < width; ++output_x)
{
const source_x = bounds.min_x + output_x;
const source_offset =
(source_y * source_width + source_x) * 4;
const output_offset = (output_y * width + output_x) * 4;
data.set(
pixels.subarray(source_offset, source_offset + 4),
output_offset);
}
}
return {width, height, data};
}
/** Scale dimensions proportionally so their long side is exact. */
function scaleDimensions(width, height, long_side)
{
if(!Number.isInteger(long_side) || long_side < 1)
{
throw(new Error("The thumbnail size is invalid."));
}
const scale = long_side / Math.max(width, height);
return {
width: Math.max(1, Math.round(width * scale)),
height: Math.max(1, Math.round(height * scale)),
};
}
const api = {findAlphaBounds, cropWebGLPixels, scaleDimensions};
if(typeof module != "undefined" && module.exports != null)
{
module.exports = api;
}
else
{
root.cardPreviewMath = api;
}
})(globalThis);