2021-06-23 12:31:12 -03:00
function resizeWithAspect (
input_width ,
input_height ,
target_width ,
2021-10-25 13:17:43 +10:00
target_height
2021-06-23 12:31:12 -03:00
) {
if ( ! target_width && ! target_height ) {
2021-10-25 13:17:43 +10:00
throw Error ( "Need to specify at least width or height when resizing" );
2021-06-23 12:31:12 -03:00
}
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 ),
};
}
2022-03-07 12:39:33 +10:00
function logIfDebug (... messages ) {
2021-06-23 12:31:12 -03:00
if ( DedicatedWorkerGlobalScope . debugMode ) {
// eslint-disable-next-line no-console
2022-03-07 12:39:33 +10:00
console . log (... messages );
2021-06-23 12:31:12 -03:00
}
}
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 {
2021-10-25 13:17:43 +10:00
const target_dimensions = resizeWithAspect (
width ,
height ,
settings . resize_target
);
2021-06-23 12:31:12 -03:00
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
);
2021-06-30 16:01:17 -03:00
if ( resizeResult [ 3 ] !== 255 ) {
2021-10-25 13:17:43 +10:00
throw "Image corrupted during resize. Falling back to the original for encode" ;
2021-06-30 16:01:17 -03:00
}
2021-06-23 12:31:12 -03:00
maybeResized = new ImageData (
resizeResult ,
target_dimensions . width ,
2021-10-25 13:17:43 +10:00
target_dimensions . height
2021-06-23 12:31:12 -03:00
). data ;
width = target_dimensions . width ;
height = target_dimensions . height ;
2021-06-28 18:21:39 -03:00
logIfDebug ( `Worker post resizing file: ${ maybeResized . byteLength } ` );
2021-06-23 12:31:12 -03:00
} 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
);
2021-10-25 13:17:43 +10:00
const finalSize = result . byteLength ;
2021-06-23 12:31:12 -03:00
logIfDebug ( `Worker post reencode file: ${ finalSize } ` );
logIfDebug ( `Reduction: ${ ( initialSize / finalSize ). toFixed ( 1 ) } x speedup` );
2021-06-30 16:01:17 -03:00
if ( finalSize < 20000 ) {
2021-10-25 13:17:43 +10:00
throw "Final size suspciously small, discarding optimizations" ;
2021-06-30 16:01:17 -03:00
}
2021-06-23 12:31:12 -03:00
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 ,
2021-08-23 12:10:33 +10:00
fileName : e . data . fileName ,
2021-10-25 13:17:43 +10:00
fileId : e . data . fileId ,
2021-06-23 12:31:12 -03:00
},
[ optimized ]
);
} catch ( error ) {
console . error ( error );
postMessage ({
type : "error" ,
2021-08-17 09:33:16 +10:00
file : e . data . file ,
2021-08-23 12:10:33 +10:00
fileName : e . data . fileName ,
2021-10-25 13:17:43 +10:00
fileId : e . data . fileId ,
2021-06-23 12:31:12 -03:00
});
}
break ;
2021-10-25 13:17:43 +10:00
case "install" :
2021-12-21 09:00:19 +10:00
try {
await loadLibs ( e . data . settings );
postMessage ({ type : "installed" });
} catch ( error ) {
postMessage ({ type : "installFailed" , errorMessage : error . message });
}
2021-10-25 13:17:43 +10:00
break ;
2021-06-23 12:31:12 -03:00
default :
logIfDebug ( `Sorry, we are out of ${ e } .` );
}
};
2021-10-25 13:17:43 +10:00
async function loadLibs ( settings ) {
2021-06-23 12:31:12 -03:00
if ( self . codecs ) return ;
2021-10-25 13:17:43 +10:00
importScripts ( settings . mozjpeg_script );
importScripts ( settings . resize_script );
2021-06-23 12:31:12 -03:00
let encoderModuleOverrides = {
2021-10-25 13:17:43 +10:00
locateFile : function ( path , prefix ) {
2021-06-23 12:31:12 -03:00
// 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 );
2021-10-25 13:17:43 +10:00
self . codecs = { mozjpeg_enc : mozjpeg_enc_module , resize : resize };
2021-08-17 09:33:16 +10:00
}