discourse/public/javascripts/media-optimization-worker.js
Martin Brennan 2461ed303c
DEV: Move loadLibs to explicit install message in media-optimization-worker (#14707)
Previously, `loadLibs` was called inside the `optimize` function of
the media-optimization-worker, which meant that it could be hit
multiple times causing load errors (as seen in b69c2f7311)

This commit moves that call to a specific message handler (the `install` message)
for the service worker, and refactors the service for the media-optimization-worker
to wait for this installation to complete before continuing with processing
image optimizations.

This way, we know for sure based on promises and worker messages
that the worker is installed and has all required libraries
loaded before we continue on with attempting any processing. The
change made in b69c2f7311 is no
longer needed with this commit.
2021-10-25 13:17:43 +10:00

183 lines
4.8 KiB
JavaScript

function resizeWithAspect(
input_width,
input_height,
target_width,
target_height
) {
if (!target_width && !target_height) {
throw Error("Need to specify at least width or height when resizing");
}
if (target_width && target_height) {
return { width: target_width, height: target_height };
}
if (!target_width) {
return {
width: Math.round((input_width / input_height) * target_height),
height: target_height,
};
}
return {
width: target_width,
height: Math.round((input_height / input_width) * target_width),
};
}
function logIfDebug(message) {
if (DedicatedWorkerGlobalScope.debugMode) {
// eslint-disable-next-line no-console
console.log(message);
}
}
async function optimize(imageData, fileName, width, height, settings) {
const mozJpegDefaultOptions = {
quality: settings.encode_quality,
baseline: false,
arithmetic: false,
progressive: true,
optimize_coding: true,
smoothing: 0,
color_space: 3 /*YCbCr*/,
quant_table: 3,
trellis_multipass: false,
trellis_opt_zero: false,
trellis_opt_table: false,
trellis_loops: 1,
auto_subsample: true,
chroma_subsample: 2,
separate_chroma_quality: false,
chroma_quality: 75,
};
const initialSize = imageData.byteLength;
logIfDebug(`Worker received imageData: ${initialSize}`);
let maybeResized;
// resize
if (width > settings.resize_threshold) {
try {
const target_dimensions = resizeWithAspect(
width,
height,
settings.resize_target
);
const resizeResult = self.codecs.resize(
new Uint8ClampedArray(imageData),
width, //in
height, //in
target_dimensions.width, //out
target_dimensions.height, //out
3, // 3 is lanczos
settings.resize_pre_multiply,
settings.resize_linear_rgb
);
if (resizeResult[3] !== 255) {
throw "Image corrupted during resize. Falling back to the original for encode";
}
maybeResized = new ImageData(
resizeResult,
target_dimensions.width,
target_dimensions.height
).data;
width = target_dimensions.width;
height = target_dimensions.height;
logIfDebug(`Worker post resizing file: ${maybeResized.byteLength}`);
} catch (error) {
console.error(`Resize failed: ${error}`);
maybeResized = imageData;
}
} else {
logIfDebug(`Skipped resize: ${width} < ${settings.resize_threshold}`);
maybeResized = imageData;
}
// mozJPEG re-encode
const result = self.codecs.mozjpeg_enc.encode(
maybeResized,
width,
height,
mozJpegDefaultOptions
);
const finalSize = result.byteLength;
logIfDebug(`Worker post reencode file: ${finalSize}`);
logIfDebug(`Reduction: ${(initialSize / finalSize).toFixed(1)}x speedup`);
if (finalSize < 20000) {
throw "Final size suspciously small, discarding optimizations";
}
let transferrable = Uint8Array.from(result).buffer; // decoded was allocated inside WASM so it **cannot** be transfered to another context, need to copy by value
return transferrable;
}
onmessage = async function (e) {
switch (e.data.type) {
case "compress":
try {
DedicatedWorkerGlobalScope.debugMode = e.data.settings.debug_mode;
let optimized = await optimize(
e.data.file,
e.data.fileName,
e.data.width,
e.data.height,
e.data.settings
);
postMessage(
{
type: "file",
file: optimized,
fileName: e.data.fileName,
fileId: e.data.fileId,
},
[optimized]
);
} catch (error) {
console.error(error);
postMessage({
type: "error",
file: e.data.file,
fileName: e.data.fileName,
fileId: e.data.fileId,
});
}
break;
case "install":
await loadLibs(e.data.settings);
postMessage({ type: "installed" });
break;
default:
logIfDebug(`Sorry, we are out of ${e}.`);
}
};
async function loadLibs(settings) {
if (self.codecs) return;
importScripts(settings.mozjpeg_script);
importScripts(settings.resize_script);
let encoderModuleOverrides = {
locateFile: function (path, prefix) {
// if it's a mem init file, use a custom dir
if (path.endsWith(".wasm")) return settings.mozjpeg_wasm;
// otherwise, use the default, the prefix (JS file's dir) + the path
return prefix + path;
},
onRuntimeInitialized: function () {
return this;
},
};
const mozjpeg_enc_module = await mozjpeg_enc(encoderModuleOverrides);
const { resize } = wasm_bindgen;
await wasm_bindgen(settings.resize_wasm);
self.codecs = { mozjpeg_enc: mozjpeg_enc_module, resize: resize };
}