2021-06-23 11:31:12 -04:00
function resizeWithAspect (
input _width ,
input _height ,
target _width ,
2021-10-24 23:17:43 -04:00
target _height
2021-06-23 11:31:12 -04:00
) {
if ( ! target _width && ! target _height ) {
2021-10-24 23:17:43 -04:00
throw Error ( "Need to specify at least width or height when resizing" ) ;
2021-06-23 11:31:12 -04: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 ) ,
} ;
}
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 {
2021-10-24 23:17:43 -04:00
const target _dimensions = resizeWithAspect (
width ,
height ,
settings . resize _target
) ;
2021-06-23 11:31:12 -04: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 15:01:17 -04:00
if ( resizeResult [ 3 ] !== 255 ) {
2021-10-24 23:17:43 -04:00
throw "Image corrupted during resize. Falling back to the original for encode" ;
2021-06-30 15:01:17 -04:00
}
2021-06-23 11:31:12 -04:00
maybeResized = new ImageData (
resizeResult ,
target _dimensions . width ,
2021-10-24 23:17:43 -04:00
target _dimensions . height
2021-06-23 11:31:12 -04:00
) . data ;
width = target _dimensions . width ;
height = target _dimensions . height ;
2021-06-28 17:21:39 -04:00
logIfDebug ( ` Worker post resizing file: ${ maybeResized . byteLength } ` ) ;
2021-06-23 11:31:12 -04: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-24 23:17:43 -04:00
const finalSize = result . byteLength ;
2021-06-23 11:31:12 -04:00
logIfDebug ( ` Worker post reencode file: ${ finalSize } ` ) ;
logIfDebug ( ` Reduction: ${ ( initialSize / finalSize ) . toFixed ( 1 ) } x speedup ` ) ;
2021-06-30 15:01:17 -04:00
if ( finalSize < 20000 ) {
2021-10-24 23:17:43 -04:00
throw "Final size suspciously small, discarding optimizations" ;
2021-06-30 15:01:17 -04:00
}
2021-06-23 11:31:12 -04: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-22 22:10:33 -04:00
fileName : e . data . fileName ,
2021-10-24 23:17:43 -04:00
fileId : e . data . fileId ,
2021-06-23 11:31:12 -04:00
} ,
[ optimized ]
) ;
} catch ( error ) {
console . error ( error ) ;
postMessage ( {
type : "error" ,
2021-08-16 19:33:16 -04:00
file : e . data . file ,
2021-08-22 22:10:33 -04:00
fileName : e . data . fileName ,
2021-10-24 23:17:43 -04:00
fileId : e . data . fileId ,
2021-06-23 11:31:12 -04:00
} ) ;
}
break ;
2021-10-24 23:17:43 -04:00
case "install" :
2021-12-20 18:00:19 -05:00
try {
await loadLibs ( e . data . settings ) ;
postMessage ( { type : "installed" } ) ;
} catch ( error ) {
postMessage ( { type : "installFailed" , errorMessage : error . message } ) ;
}
2021-10-24 23:17:43 -04:00
break ;
2021-06-23 11:31:12 -04:00
default :
logIfDebug ( ` Sorry, we are out of ${ e } . ` ) ;
}
} ;
2021-10-24 23:17:43 -04:00
async function loadLibs ( settings ) {
2021-06-23 11:31:12 -04:00
if ( self . codecs ) return ;
2021-10-24 23:17:43 -04:00
importScripts ( settings . mozjpeg _script ) ;
importScripts ( settings . resize _script ) ;
2021-06-23 11:31:12 -04:00
let encoderModuleOverrides = {
2021-10-24 23:17:43 -04:00
locateFile : function ( path , prefix ) {
2021-06-23 11:31:12 -04: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-24 23:17:43 -04:00
self . codecs = { mozjpeg _enc : mozjpeg _enc _module , resize : resize } ;
2021-08-16 19:33:16 -04:00
}