mirror of
https://github.com/discourse/discourse.git
synced 2024-11-30 10:15:13 +08:00
fa4a462517
Integrates [mozJPEG](https://github.com/mozilla/mozjpeg) and [Resize](https://github.com/PistonDevelopers/resize) using WebAssembly to optimize user uploads in the composer on the client-side. NPM libraries are sourced from our [Squoosh fork](https://github.com/discourse/squoosh/tree/discourse), which was needed because we have an older asset pipeline.
166 lines
4.2 KiB
JavaScript
166 lines
4.2 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) {
|
|
|
|
await loadLibs(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
|
|
);
|
|
maybeResized = new ImageData(
|
|
resizeResult,
|
|
target_dimensions.width,
|
|
target_dimensions.height,
|
|
).data;
|
|
width = target_dimensions.width;
|
|
height = target_dimensions.height;
|
|
} 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`);
|
|
|
|
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
|
|
},
|
|
[optimized]
|
|
);
|
|
} catch (error) {
|
|
console.error(error);
|
|
postMessage({
|
|
type: "error",
|
|
});
|
|
}
|
|
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};
|
|
} |