diff --git a/app/assets/javascripts/discourse/app/components/composer-editor.js b/app/assets/javascripts/discourse/app/components/composer-editor.js
index d4bf5097e58..7280058f667 100644
--- a/app/assets/javascripts/discourse/app/components/composer-editor.js
+++ b/app/assets/javascripts/discourse/app/components/composer-editor.js
@@ -672,6 +672,11 @@ export default Component.extend({
filename: data.files[data.index].name,
})}]()\n`
);
+ this.setProperties({
+ uploadProgress: 0,
+ isUploading: true,
+ isCancellable: false,
+ });
})
.on("fileuploadprocessalways", (e, data) => {
this.appEvents.trigger(
@@ -681,6 +686,11 @@ export default Component.extend({
})}]()\n`,
""
);
+ this.setProperties({
+ uploadProgress: 0,
+ isUploading: false,
+ isCancellable: false,
+ });
});
$element.on("fileuploadpaste", (e) => {
diff --git a/app/assets/javascripts/discourse/app/initializers/register-media-optimization-upload-processor.js b/app/assets/javascripts/discourse/app/initializers/register-media-optimization-upload-processor.js
new file mode 100644
index 00000000000..3b6a9758260
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/initializers/register-media-optimization-upload-processor.js
@@ -0,0 +1,20 @@
+import { addComposerUploadProcessor } from "discourse/components/composer-editor";
+
+export default {
+ name: "register-media-optimization-upload-processor",
+
+ initialize(container) {
+ let siteSettings = container.lookup("site-settings:main");
+ if (siteSettings.composer_media_optimization_image_enabled) {
+ addComposerUploadProcessor(
+ { action: "optimizeJPEG" },
+ {
+ optimizeJPEG: (data) =>
+ container
+ .lookup("service:media-optimization-worker")
+ .optimizeImage(data),
+ }
+ );
+ }
+ },
+};
diff --git a/app/assets/javascripts/discourse/app/lib/media-optimization-utils.js b/app/assets/javascripts/discourse/app/lib/media-optimization-utils.js
new file mode 100644
index 00000000000..b67424f4c46
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/lib/media-optimization-utils.js
@@ -0,0 +1,63 @@
+import { Promise } from "rsvp";
+
+export async function fileToImageData(file) {
+ let drawable, err;
+
+ // Chrome and Firefox use a native method to do Image -> Bitmap Array (it happens of the main thread!)
+ // Safari uses the `` element due to https://bugs.webkit.org/show_bug.cgi?id=182424
+ if ("createImageBitmap" in self) {
+ drawable = await createImageBitmap(file);
+ } else {
+ const url = URL.createObjectURL(file);
+ const img = new Image();
+ img.decoding = "async";
+ img.src = url;
+ const loaded = new Promise((resolve, reject) => {
+ img.onload = () => resolve();
+ img.onerror = () => reject(Error("Image loading error"));
+ });
+
+ if (img.decode) {
+ // Nice off-thread way supported in Safari/Chrome.
+ // Safari throws on decode if the source is SVG.
+ // https://bugs.webkit.org/show_bug.cgi?id=188347
+ await img.decode().catch(() => null);
+ }
+
+ // Always await loaded, as we may have bailed due to the Safari bug above.
+ await loaded;
+
+ drawable = img;
+ }
+
+ const width = drawable.width,
+ height = drawable.height,
+ sx = 0,
+ sy = 0,
+ sw = width,
+ sh = height;
+ // Make canvas same size as image
+ const canvas = document.createElement("canvas");
+ canvas.width = width;
+ canvas.height = height;
+ // Draw image onto canvas
+ const ctx = canvas.getContext("2d");
+ if (!ctx) {
+ err = "Could not create canvas context";
+ }
+ ctx.drawImage(drawable, sx, sy, sw, sh, 0, 0, width, height);
+ const imageData = ctx.getImageData(0, 0, width, height);
+ canvas.remove();
+
+ // potentially transparent
+ if (/(\.|\/)(png|webp)$/i.test(file.type)) {
+ for (let i = 0; i < imageData.data.length; i += 4) {
+ if (imageData.data[i + 3] < 255) {
+ err = "Image has transparent pixels, won't convert to JPEG!";
+ break;
+ }
+ }
+ }
+
+ return { imageData, width, height, err };
+}
diff --git a/app/assets/javascripts/discourse/app/lib/plugin-api.js b/app/assets/javascripts/discourse/app/lib/plugin-api.js
index 5c25a4a0aee..994d45dfbd6 100644
--- a/app/assets/javascripts/discourse/app/lib/plugin-api.js
+++ b/app/assets/javascripts/discourse/app/lib/plugin-api.js
@@ -951,8 +951,6 @@ class PluginApi {
/**
* Registers a pre-processor for file uploads
* See https://github.com/blueimp/jQuery-File-Upload/wiki/Options#file-processing-options
- * Your theme/plugin will also need to load https://github.com/blueimp/jQuery-File-Upload/blob/v10.13.0/js/jquery.fileupload-process.js
- * for this hook to work.
*
* Useful for transforming to-be uploaded files client-side
*
diff --git a/app/assets/javascripts/discourse/app/services/media-optimization-worker.js b/app/assets/javascripts/discourse/app/services/media-optimization-worker.js
new file mode 100644
index 00000000000..24069246fab
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/services/media-optimization-worker.js
@@ -0,0 +1,128 @@
+import Service from "@ember/service";
+import { getOwner } from "@ember/application";
+import { Promise } from "rsvp";
+import { fileToImageData } from "discourse/lib/media-optimization-utils";
+import { getAbsoluteURL, getURLWithCDN } from "discourse-common/lib/get-url";
+
+export default class MediaOptimizationWorkerService extends Service {
+ appEvents = getOwner(this).lookup("service:app-events");
+ worker = null;
+ workerUrl = getAbsoluteURL("/javascripts/media-optimization-worker.js");
+ currentComposerUploadData = null;
+ currentPromiseResolver = null;
+
+ startWorker() {
+ this.worker = new Worker(this.workerUrl); // TODO come up with a workaround for FF that lacks type: module support
+ }
+
+ stopWorker() {
+ this.worker.terminate();
+ this.worker = null;
+ }
+
+ ensureAvailiableWorker() {
+ if (!this.worker) {
+ this.startWorker();
+ this.registerMessageHandler();
+ this.appEvents.on("composer:closed", this, "stopWorker");
+ }
+ }
+
+ logIfDebug(message) {
+ if (this.siteSettings.composer_media_optimization_debug_mode) {
+ // eslint-disable-next-line no-console
+ console.log(message);
+ }
+ }
+
+ optimizeImage(data) {
+ let file = data.files[data.index];
+ if (!/(\.|\/)(jpe?g|png|webp)$/i.test(file.type)) {
+ return data;
+ }
+ if (
+ file.size <
+ this.siteSettings
+ .composer_media_optimization_image_kilobytes_optimization_threshold
+ ) {
+ return data;
+ }
+ this.ensureAvailiableWorker();
+ return new Promise(async (resolve) => {
+ this.logIfDebug(`Transforming ${file.name}`);
+
+ this.currentComposerUploadData = data;
+ this.currentPromiseResolver = resolve;
+
+ const { imageData, width, height, err } = await fileToImageData(file);
+
+ if (err) {
+ this.logIfDebug(err);
+ return resolve(data);
+ }
+
+ this.worker.postMessage(
+ {
+ type: "compress",
+ file: imageData.data.buffer,
+ fileName: file.name,
+ width: width,
+ height: height,
+ settings: {
+ mozjpeg_script: getURLWithCDN(
+ "/javascripts/squoosh/mozjpeg_enc.js"
+ ),
+ mozjpeg_wasm: getURLWithCDN(
+ "/javascripts/squoosh/mozjpeg_enc.wasm"
+ ),
+ resize_script: getURLWithCDN(
+ "/javascripts/squoosh/squoosh_resize.js"
+ ),
+ resize_wasm: getURLWithCDN(
+ "/javascripts/squoosh/squoosh_resize_bg.wasm"
+ ),
+ resize_threshold: this.siteSettings
+ .composer_media_optimization_image_resize_dimensions_threshold,
+ resize_target: this.siteSettings
+ .composer_media_optimization_image_resize_width_target,
+ resize_pre_multiply: this.siteSettings
+ .composer_media_optimization_image_resize_pre_multiply,
+ resize_linear_rgb: this.siteSettings
+ .composer_media_optimization_image_resize_linear_rgb,
+ encode_quality: this.siteSettings
+ .composer_media_optimization_image_encode_quality,
+ debug_mode: this.siteSettings
+ .composer_media_optimization_debug_mode,
+ },
+ },
+ [imageData.data.buffer]
+ );
+ });
+ }
+
+ registerMessageHandler() {
+ this.worker.onmessage = (e) => {
+ this.logIfDebug("Main: Message received from worker script");
+ this.logIfDebug(e);
+ switch (e.data.type) {
+ case "file":
+ let optimizedFile = new File([e.data.file], `${e.data.fileName}`, {
+ type: "image/jpeg",
+ });
+ this.logIfDebug(
+ `Finished optimization of ${optimizedFile.name} new size: ${optimizedFile.size}.`
+ );
+ let data = this.currentComposerUploadData;
+ data.files[data.index] = optimizedFile;
+ this.currentPromiseResolver(data);
+ break;
+ case "error":
+ this.stopWorker();
+ this.currentPromiseResolver(this.currentComposerUploadData);
+ break;
+ default:
+ this.logIfDebug(`Sorry, we are out of ${e}.`);
+ }
+ };
+ }
+}
diff --git a/app/assets/javascripts/discourse/ember-cli-build.js b/app/assets/javascripts/discourse/ember-cli-build.js
index 98d33e1de81..4650fb52925 100644
--- a/app/assets/javascripts/discourse/ember-cli-build.js
+++ b/app/assets/javascripts/discourse/ember-cli-build.js
@@ -34,6 +34,7 @@ module.exports = function (defaults) {
app.import(vendorJs + "bootstrap-modal.js");
app.import(vendorJs + "jquery.ui.widget.js");
app.import(vendorJs + "jquery.fileupload.js");
+ app.import(vendorJs + "jquery.fileupload-process.js");
app.import(vendorJs + "jquery.autoellipsis-1.0.10.js");
app.import(vendorJs + "show-html.js");
diff --git a/app/assets/javascripts/discourse/tests/theme_qunit_vendor.js b/app/assets/javascripts/discourse/tests/theme_qunit_vendor.js
index f97174a43f7..a7f547667cd 100644
--- a/app/assets/javascripts/discourse/tests/theme_qunit_vendor.js
+++ b/app/assets/javascripts/discourse/tests/theme_qunit_vendor.js
@@ -21,6 +21,7 @@
//= require jquery.color.js
//= require jquery.fileupload.js
//= require jquery.iframe-transport.js
+//= require jquery.fileupload-process.js
//= require jquery.tagsinput.js
//= require jquery.sortable.js
//= require lodash.js
diff --git a/app/assets/javascripts/vendor.js b/app/assets/javascripts/vendor.js
index 2f2fe583908..f28366faa38 100644
--- a/app/assets/javascripts/vendor.js
+++ b/app/assets/javascripts/vendor.js
@@ -14,6 +14,7 @@
//= require jquery.color.js
//= require jquery.fileupload.js
//= require jquery.iframe-transport.js
+//= require jquery.fileupload-process.js
//= require jquery.tagsinput.js
//= require jquery.sortable.js
//= require lodash.js
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 0164ecba094..d7445c44482 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1824,6 +1824,12 @@ en:
strip_image_metadata: "Strip image metadata."
+ composer_media_optimization_image_enabled: "Enables client-side media optimization of uploaded image files."
+ composer_media_optimization_image_kilobytes_optimization_threshold: "Minimum image file size to trigger client-side optimization"
+ composer_media_optimization_image_resize_dimensions_threshold: "Minimum image width to trigger client-side resize"
+ composer_media_optimization_image_resize_width_target: "Images with widths larger than `composer_media_optimization_image_dimensions_resize_threshold` will be resized to this width. Must be >= than `composer_media_optimization_image_dimensions_resize_threshold`."
+ composer_media_optimization_image_encode_quality: "JPEG encode quality used in the re-encode process."
+
min_ratio_to_crop: "Ratio used to crop tall images. Enter the result of width / height."
simultaneous_uploads: "Maximum number of files that can be dragged & dropped in the composer"
diff --git a/config/nginx.sample.conf b/config/nginx.sample.conf
index 9fd6c51b907..07dcfd9d3d4 100644
--- a/config/nginx.sample.conf
+++ b/config/nginx.sample.conf
@@ -1,6 +1,7 @@
# Additional MIME types that you'd like nginx to handle go in here
types {
text/csv csv;
+ application/wasm wasm;
}
upstream discourse {
@@ -47,7 +48,7 @@ server {
gzip_vary on;
gzip_min_length 1000;
gzip_comp_level 5;
- gzip_types application/json text/css text/javascript application/x-javascript application/javascript image/svg+xml;
+ gzip_types application/json text/css text/javascript application/x-javascript application/javascript image/svg+xml application/wasm;
gzip_proxied any;
# Uncomment and configure this section for HTTPS support
diff --git a/config/site_settings.yml b/config/site_settings.yml
index a6ec120c767..12a645d55fd 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -1405,6 +1405,33 @@ files:
decompressed_backup_max_file_size_mb:
default: 100000
hidden: true
+ composer_media_optimization_image_enabled:
+ default: false
+ client: true
+ composer_media_optimization_image_kilobytes_optimization_threshold:
+ default: 1048576
+ client: true
+ composer_media_optimization_image_resize_dimensions_threshold:
+ default: 1920
+ client: true
+ composer_media_optimization_image_resize_width_target:
+ default: 1920
+ client: true
+ composer_media_optimization_image_resize_pre_multiply:
+ default: false
+ hidden: true
+ client: true
+ composer_media_optimization_image_resize_linear_rgb:
+ default: false
+ hidden: true
+ client: true
+ composer_media_optimization_image_encode_quality:
+ default: 75
+ client: true
+ composer_media_optimization_debug_mode:
+ default: false
+ client: true
+ hidden: true
trust:
default_trust_level:
diff --git a/lib/tasks/javascript.rake b/lib/tasks/javascript.rake
index a40bd98cff4..834a3804c31 100644
--- a/lib/tasks/javascript.rake
+++ b/lib/tasks/javascript.rake
@@ -112,6 +112,8 @@ def dependencies
source: 'blueimp-file-upload/js/jquery.fileupload.js',
}, {
source: 'blueimp-file-upload/js/jquery.iframe-transport.js',
+ }, {
+ source: 'blueimp-file-upload/js/jquery.fileupload-process.js',
}, {
source: 'blueimp-file-upload/js/vendor/jquery.ui.widget.js',
}, {
@@ -191,6 +193,30 @@ def dependencies
{
source: 'sinon/pkg/sinon.js'
},
+ {
+ source: 'squoosh/codecs/mozjpeg/enc/mozjpeg_enc.js',
+ destination: 'squoosh',
+ public: true,
+ skip_versioning: true
+ },
+ {
+ source: 'squoosh/codecs/mozjpeg/enc/mozjpeg_enc.wasm',
+ destination: 'squoosh',
+ public: true,
+ skip_versioning: true
+ },
+ {
+ source: 'squoosh/codecs/resize/pkg/squoosh_resize.js',
+ destination: 'squoosh',
+ public: true,
+ skip_versioning: true
+ },
+ {
+ source: 'squoosh/codecs/resize/pkg/squoosh_resize_bg.wasm',
+ destination: 'squoosh',
+ public: true,
+ skip_versioning: true
+ },
]
end
diff --git a/package.json b/package.json
index 9d013590a10..0b8fddf98df 100644
--- a/package.json
+++ b/package.json
@@ -49,7 +49,8 @@
"puppeteer": "1.20",
"qunit": "2.8.0",
"route-recognizer": "^0.3.3",
- "sinon": "^9.0.2"
+ "sinon": "^9.0.2",
+ "squoosh": "discourse/squoosh#dc9649d"
},
"resolutions": {
"lodash": "4.17.21"
diff --git a/public/javascripts/media-optimization-worker.js b/public/javascripts/media-optimization-worker.js
new file mode 100644
index 00000000000..7bbdf137bd5
--- /dev/null
+++ b/public/javascripts/media-optimization-worker.js
@@ -0,0 +1,166 @@
+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};
+}
\ No newline at end of file
diff --git a/public/javascripts/squoosh/mozjpeg_enc.js b/public/javascripts/squoosh/mozjpeg_enc.js
new file mode 100644
index 00000000000..e52ad7a6d0d
--- /dev/null
+++ b/public/javascripts/squoosh/mozjpeg_enc.js
@@ -0,0 +1,21 @@
+
+var mozjpeg_enc = (function() {
+ var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;
+
+ return (
+function(mozjpeg_enc) {
+ mozjpeg_enc = mozjpeg_enc || {};
+
+var Module=typeof mozjpeg_enc!=="undefined"?mozjpeg_enc:{};var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var arguments_=[];var thisProgram="./this.program";var quit_=function(status,toThrow){throw toThrow};var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=true;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!=="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=function(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=function(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=function(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=function(title){document.title=title}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);for(key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var tempRet0=0;var setTempRet0=function(value){tempRet0=value};var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!=="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;var UTF8Decoder=new TextDecoder("utf8");function UTF8ArrayToString(heap,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heap[endPtr]&&!(endPtr>=endIdx))++endPtr;return UTF8Decoder.decode(heap.subarray?heap.subarray(idx,endPtr):new Uint8Array(heap.slice(idx,endPtr)))}function UTF8ToString(ptr,maxBytesToRead){if(!ptr)return"";var maxPtr=ptr+maxBytesToRead;for(var end=ptr;!(end>=maxPtr)&&HEAPU8[end];)++end;return UTF8Decoder.decode(HEAPU8.subarray(ptr,end))}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127)++len;else if(u<=2047)len+=2;else if(u<=65535)len+=3;else len+=4}return len}var UTF16Decoder=new TextDecoder("utf-16le");function UTF16ToString(ptr,maxBytesToRead){var endPtr=ptr;var idx=endPtr>>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr));var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str}function stringToUTF16(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr}function lengthBytesUTF16(str){return str.length*2}function UTF32ToString(ptr,maxBytesToRead){var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str}function stringToUTF32(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr}function lengthBytesUTF32(str){var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}function alignUp(x,multiple){if(x%multiple>0){x+=multiple-x%multiple}return x}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeExited=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function exitRuntime(){runtimeExited=true}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["preloadedImages"]={};Module["preloadedAudios"]={};function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what+="";err(what);ABORT=true;EXITSTATUS=1;what="abort("+what+"). Build with -s ASSERTIONS=1 for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}var wasmBinaryFile="mozjpeg_enc.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}else{throw"both async and sync fetching of the wasm failed"}}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch==="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary(wasmBinaryFile)})}}return Promise.resolve().then(function(){return getBinary(wasmBinaryFile)})}function createWasm(){var info={"a":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["C"];updateGlobalBufferAndViews(wasmMemory.buffer);wasmTable=Module["asm"]["I"];addOnInit(Module["asm"]["D"]);removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){var result=WebAssembly.instantiate(binary,info);return result}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming==="function"&&!isDataURI(wasmBinaryFile)&&typeof fetch==="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiationResult,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiationResult)})})}else{return instantiateArrayBuffer(receiveInstantiationResult)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync().catch(readyPromiseReject);return{}}function callRuntimeCallbacks(callbacks){while(callbacks.length>0){var callback=callbacks.shift();if(typeof callback=="function"){callback(Module);continue}var func=callback.func;if(typeof func==="number"){if(callback.arg===undefined){wasmTable.get(func)()}else{wasmTable.get(func)(callback.arg)}}else{func(callback.arg===undefined?null:callback.arg)}}}var runtimeKeepaliveCounter=0;function keepRuntimeAlive(){return noExitRuntime||runtimeKeepaliveCounter>0}function _atexit(func,arg){}function ___cxa_thread_atexit(a0,a1){return _atexit(a0,a1)}var structRegistrations={};function runDestructors(destructors){while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}}function simpleReadValueFromPointer(pointer){return this["fromWireType"](HEAPU32[pointer>>2])}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var char_0=48;var char_9=57;function makeLegalFunctionName(name){if(undefined===name){return"_unknown"}name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return"_"+name}else{return name}}function createNamedFunction(name,body){name=makeLegalFunctionName(name);return function(){null;return body.apply(this,arguments)}}function extendError(baseErrorType,errorName){var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return this.name+": "+this.message}};return errorClass}var InternalError=undefined;function throwInternalError(message){throw new InternalError(message)}function whenDependentTypesAreResolved(myTypes,dependentTypes,getTypeConverters){myTypes.forEach(function(type){typeDependencies[type]=dependentTypes});function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i>shift])},destructorFunction:null})}var emval_free_list=[];var emval_handle_array=[{},{value:undefined},{value:null},{value:true},{value:false}];function __emval_decref(handle){if(handle>4&&0===--emval_handle_array[handle].refcount){emval_handle_array[handle]=undefined;emval_free_list.push(handle)}}function count_emval_handles(){var count=0;for(var i=5;i>2])};case 3:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError("Unknown float type: "+name)}}function __embind_register_float(rawType,name,size){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(value){return value},"toWireType":function(destructors,value){if(typeof value!=="number"&&typeof value!=="boolean"){throw new TypeError('Cannot convert "'+_embind_repr(value)+'" to '+this.name)}return value},"argPackAdvance":8,"readValueFromPointer":floatReadValueFromPointer(name,shift),destructorFunction:null})}function craftInvokerFunction(humanName,argTypes,classType,cppInvokerFunc,cppTargetFunc){var argCount=argTypes.length;if(argCount<2){throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!")}var isClassMethodFunc=argTypes[1]!==null&&classType!==null;var needsDestructorStack=false;for(var i=1;i>2)+i])}return array}function replacePublicSymbol(name,value,numArguments){if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistant public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}}function dynCallLegacy(sig,ptr,args){var f=Module["dynCall_"+sig];return args&&args.length?f.apply(null,[ptr].concat(args)):f.call(null,ptr)}function dynCall(sig,ptr,args){if(sig.includes("j")){return dynCallLegacy(sig,ptr,args)}return wasmTable.get(ptr).apply(null,args)}function getDynCaller(sig,ptr){var argCache=[];return function(){argCache.length=arguments.length;for(var i=0;i>1]}:function readU16FromPointer(pointer){return HEAPU16[pointer>>1]};case 2:return signed?function readS32FromPointer(pointer){return HEAP32[pointer>>2]}:function readU32FromPointer(pointer){return HEAPU32[pointer>>2]};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_integer(primitiveType,name,size,minRange,maxRange){name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var shift=getShiftFromSize(size);var fromWireType=function(value){return value};if(minRange===0){var bitshift=32-8*size;fromWireType=function(value){return value<>>bitshift}}var isUnsignedType=name.includes("unsigned");registerType(primitiveType,{name:name,"fromWireType":fromWireType,"toWireType":function(destructors,value){if(typeof value!=="number"&&typeof value!=="boolean"){throw new TypeError('Cannot convert "'+_embind_repr(value)+'" to '+this.name)}if(valuemaxRange){throw new TypeError('Passing a number "'+_embind_repr(value)+'" from JS side to C/C++ side to an argument of type "'+name+'", which is outside the valid range ['+minRange+", "+maxRange+"]!")}return isUnsignedType?value>>>0:value|0},"argPackAdvance":8,"readValueFromPointer":integerReadValueFromPointer(name,shift,minRange!==0),destructorFunction:null})}function __embind_register_memory_view(rawType,dataTypeIndex,name){var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){handle=handle>>2;var heap=HEAPU32;var size=heap[handle];var data=heap[handle+1];return new TA(buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":decodeMemoryView,"argPackAdvance":8,"readValueFromPointer":decodeMemoryView},{ignoreDuplicateRegistrations:true})}function __embind_register_std_string(rawType,name){name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var str;if(stdStringIsUTF8){var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i;if(i==length||HEAPU8[currentBytePtr]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;i>2]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr+4,length+1)}else{if(valueIsOfTypeString){for(var i=0;i255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+4+i]=charCode}}else{for(var i=0;i>2];var HEAP=getHeap();var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||HEAP[currentBytePtr>>shift]==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},"toWireType":function(destructors,value){if(!(typeof value==="string")){throwBindingError("Cannot pass non-string to C++ string type "+name)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length>>shift;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:function(ptr){_free(ptr)}})}function __embind_register_value_object(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){structRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}}function __embind_register_value_object_field(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){structRegistrations[structType].fields.push({fieldName:readLatin1String(fieldName),getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_void(rawType,name){name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,"argPackAdvance":0,"fromWireType":function(){return undefined},"toWireType":function(destructors,o){return undefined}})}var emval_symbols={};function getStringOrSymbol(address){var symbol=emval_symbols[address];if(symbol===undefined){return readLatin1String(address)}else{return symbol}}function emval_get_global(){if(typeof globalThis==="object"){return globalThis}function testGlobal(obj){obj["$$$embind_global$$$"]=obj;var success=typeof $$$embind_global$$$==="object"&&obj["$$$embind_global$$$"]===obj;if(!success){delete obj["$$$embind_global$$$"]}return success}if(typeof $$$embind_global$$$==="object"){return $$$embind_global$$$}if(typeof global==="object"&&testGlobal(global)){$$$embind_global$$$=global}else if(typeof self==="object"&&testGlobal(self)){$$$embind_global$$$=self}if(typeof $$$embind_global$$$==="object"){return $$$embind_global$$$}throw Error("unable to get global object.")}function __emval_get_global(name){if(name===0){return __emval_register(emval_get_global())}else{name=getStringOrSymbol(name);return __emval_register(emval_get_global()[name])}}function __emval_incref(handle){if(handle>4){emval_handle_array[handle].refcount+=1}}function requireRegisteredType(rawType,humanName){var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(humanName+" has unknown type "+getTypeName(rawType))}return impl}function craftEmvalAllocator(argCount){var argsList=new Array(argCount+1);return function(constructor,argTypes,args){argsList[0]=constructor;for(var i=0;i>2)+i],"parameter "+i);argsList[i+1]=argType.readValueFromPointer(args);args+=argType.argPackAdvance}var obj=new(constructor.bind.apply(constructor,argsList));return __emval_register(obj)}}var emval_newers={};function requireHandle(handle){if(!handle){throwBindingError("Cannot use deleted val. handle = "+handle)}return emval_handle_array[handle].value}function __emval_new(handle,argCount,argTypes,args){handle=requireHandle(handle);var newer=emval_newers[argCount];if(!newer){newer=craftEmvalAllocator(argCount);emval_newers[argCount]=newer}return newer(handle,argTypes,args)}function _abort(){abort()}function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function emscripten_realloc_buffer(size){try{wasmMemory.grow(size-buffer.byteLength+65535>>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;var maxHeapSize=2147483648;if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var ENV={};function getExecutableName(){return thisProgram||"./this.program"}function getEnvStrings(){if(!getEnvStrings.strings){var lang=(typeof navigator==="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={"USER":"web_user","LOGNAME":"web_user","PATH":"/","PWD":"/","HOME":"/home/web_user","LANG":lang,"_":getExecutableName()};for(var x in ENV){env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(x+"="+env[x])}getEnvStrings.strings=strings}return getEnvStrings.strings}var SYSCALLS={mappings:{},buffers:[null,[],[]],printChar:function(stream,curr){var buffer=SYSCALLS.buffers[stream];if(curr===0||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer,0));buffer.length=0}else{buffer.push(curr)}},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},get64:function(low,high){return low}};function _environ_get(__environ,environ_buf){var bufSize=0;getEnvStrings().forEach(function(string,i){var ptr=environ_buf+bufSize;HEAP32[__environ+i*4>>2]=ptr;writeAsciiToMemory(string,ptr);bufSize+=string.length+1});return 0}function _environ_sizes_get(penviron_count,penviron_buf_size){var strings=getEnvStrings();HEAP32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAP32[penviron_buf_size>>2]=bufSize;return 0}function _exit(status){exit(status)}function _fd_close(fd){return 0}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){}function _fd_write(fd,iov,iovcnt,pnum){var num=0;for(var i=0;i>2];var len=HEAP32[iov+(i*8+4)>>2];for(var j=0;j>2]=num;return 0}function _setTempRet0(val){setTempRet0(val)}InternalError=Module["InternalError"]=extendError(Error,"InternalError");embind_init_charCodes();BindingError=Module["BindingError"]=extendError(Error,"BindingError");init_emval();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");var asmLibraryArg={"B":___cxa_thread_atexit,"l":__embind_finalize_value_object,"p":__embind_register_bigint,"y":__embind_register_bool,"x":__embind_register_emval,"i":__embind_register_float,"f":__embind_register_function,"c":__embind_register_integer,"b":__embind_register_memory_view,"j":__embind_register_std_string,"e":__embind_register_std_wstring,"m":__embind_register_value_object,"a":__embind_register_value_object_field,"z":__embind_register_void,"g":__emval_decref,"u":__emval_get_global,"k":__emval_incref,"n":__emval_new,"h":_abort,"r":_emscripten_memcpy_big,"d":_emscripten_resize_heap,"s":_environ_get,"t":_environ_sizes_get,"A":_exit,"w":_fd_close,"o":_fd_seek,"v":_fd_write,"q":_setTempRet0};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["D"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["E"]).apply(null,arguments)};var _free=Module["_free"]=function(){return(_free=Module["_free"]=Module["asm"]["F"]).apply(null,arguments)};var ___getTypeName=Module["___getTypeName"]=function(){return(___getTypeName=Module["___getTypeName"]=Module["asm"]["G"]).apply(null,arguments)};var ___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=function(){return(___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=Module["asm"]["H"]).apply(null,arguments)};var dynCall_jiji=Module["dynCall_jiji"]=function(){return(dynCall_jiji=Module["dynCall_jiji"]=Module["asm"]["J"]).apply(null,arguments)};var calledRun;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}Module["run"]=run;function exit(status,implicit){EXITSTATUS=status;if(implicit&&keepRuntimeAlive()&&status===0){return}if(keepRuntimeAlive()){}else{exitRuntime();if(Module["onExit"])Module["onExit"](status);ABORT=true}quit_(status,new ExitStatus(status))}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run();
+
+
+ return mozjpeg_enc.ready
+}
+);
+})();
+if (typeof exports === 'object' && typeof module === 'object')
+ module.exports = mozjpeg_enc;
+else if (typeof define === 'function' && define['amd'])
+ define([], function() { return mozjpeg_enc; });
+else if (typeof exports === 'object')
+ exports["mozjpeg_enc"] = mozjpeg_enc;
diff --git a/public/javascripts/squoosh/mozjpeg_enc.wasm b/public/javascripts/squoosh/mozjpeg_enc.wasm
new file mode 100755
index 00000000000..d3cfebf62e0
Binary files /dev/null and b/public/javascripts/squoosh/mozjpeg_enc.wasm differ
diff --git a/public/javascripts/squoosh/squoosh_resize.js b/public/javascripts/squoosh/squoosh_resize.js
new file mode 100644
index 00000000000..ef47ab4e501
--- /dev/null
+++ b/public/javascripts/squoosh/squoosh_resize.js
@@ -0,0 +1,129 @@
+let wasm_bindgen;
+(function() {
+ const __exports = {};
+ let wasm;
+
+ let cachegetUint8Memory0 = null;
+ function getUint8Memory0() {
+ if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
+ cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
+ }
+ return cachegetUint8Memory0;
+ }
+
+ let WASM_VECTOR_LEN = 0;
+
+ function passArray8ToWasm0(arg, malloc) {
+ const ptr = malloc(arg.length * 1);
+ getUint8Memory0().set(arg, ptr / 1);
+ WASM_VECTOR_LEN = arg.length;
+ return ptr;
+ }
+
+ let cachegetInt32Memory0 = null;
+ function getInt32Memory0() {
+ if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {
+ cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
+ }
+ return cachegetInt32Memory0;
+ }
+
+ let cachegetUint8ClampedMemory0 = null;
+ function getUint8ClampedMemory0() {
+ if (cachegetUint8ClampedMemory0 === null || cachegetUint8ClampedMemory0.buffer !== wasm.memory.buffer) {
+ cachegetUint8ClampedMemory0 = new Uint8ClampedArray(wasm.memory.buffer);
+ }
+ return cachegetUint8ClampedMemory0;
+ }
+
+ function getClampedArrayU8FromWasm0(ptr, len) {
+ return getUint8ClampedMemory0().subarray(ptr / 1, ptr / 1 + len);
+ }
+ /**
+ * @param {Uint8Array} input_image
+ * @param {number} input_width
+ * @param {number} input_height
+ * @param {number} output_width
+ * @param {number} output_height
+ * @param {number} typ_idx
+ * @param {boolean} premultiply
+ * @param {boolean} color_space_conversion
+ * @returns {Uint8ClampedArray}
+ */
+ __exports.resize = function(input_image, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion) {
+ try {
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+ var ptr0 = passArray8ToWasm0(input_image, wasm.__wbindgen_malloc);
+ var len0 = WASM_VECTOR_LEN;
+ wasm.resize(retptr, ptr0, len0, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion);
+ var r0 = getInt32Memory0()[retptr / 4 + 0];
+ var r1 = getInt32Memory0()[retptr / 4 + 1];
+ var v1 = getClampedArrayU8FromWasm0(r0, r1).slice();
+ wasm.__wbindgen_free(r0, r1 * 1);
+ return v1;
+ } finally {
+ wasm.__wbindgen_add_to_stack_pointer(16);
+ }
+ };
+
+ async function load(module, imports) {
+ if (typeof Response === 'function' && module instanceof Response) {
+ if (typeof WebAssembly.instantiateStreaming === 'function') {
+ try {
+ return await WebAssembly.instantiateStreaming(module, imports);
+
+ } catch (e) {
+ if (module.headers.get('Content-Type') != 'application/wasm') {
+ console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
+
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ const bytes = await module.arrayBuffer();
+ return await WebAssembly.instantiate(bytes, imports);
+
+ } else {
+ const instance = await WebAssembly.instantiate(module, imports);
+
+ if (instance instanceof WebAssembly.Instance) {
+ return { instance, module };
+
+ } else {
+ return instance;
+ }
+ }
+ }
+
+ async function init(input) {
+ if (typeof input === 'undefined') {
+ let src;
+ if (typeof document === 'undefined') {
+ src = location.href;
+ } else {
+ src = document.currentScript.src;
+ }
+ input = src.replace(/\.js$/, '_bg.wasm');
+ }
+ const imports = {};
+
+
+ if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
+ input = fetch(input);
+ }
+
+
+
+ const { instance, module } = await load(await input, imports);
+
+ wasm = instance.exports;
+ init.__wbindgen_wasm_module = module;
+
+ return wasm;
+ }
+
+ wasm_bindgen = Object.assign(init, __exports);
+
+})();
diff --git a/public/javascripts/squoosh/squoosh_resize_bg.wasm b/public/javascripts/squoosh/squoosh_resize_bg.wasm
new file mode 100644
index 00000000000..b910c97b050
Binary files /dev/null and b/public/javascripts/squoosh/squoosh_resize_bg.wasm differ
diff --git a/vendor/assets/javascripts/jquery.fileupload-process.js b/vendor/assets/javascripts/jquery.fileupload-process.js
new file mode 100644
index 00000000000..29de8309a50
--- /dev/null
+++ b/vendor/assets/javascripts/jquery.fileupload-process.js
@@ -0,0 +1,169 @@
+/*
+ * jQuery File Upload Processing Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2012, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, require */
+
+(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['jquery', './jquery.fileupload'], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(require('jquery'), require('./jquery.fileupload'));
+ } else {
+ // Browser globals:
+ factory(window.jQuery);
+ }
+})(function ($) {
+ 'use strict';
+
+ var originalAdd = $.blueimp.fileupload.prototype.options.add;
+
+ // The File Upload Processing plugin extends the fileupload widget
+ // with file processing functionality:
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+ options: {
+ // The list of processing actions:
+ processQueue: [
+ /*
+ {
+ action: 'log',
+ type: 'debug'
+ }
+ */
+ ],
+ add: function (e, data) {
+ var $this = $(this);
+ data.process(function () {
+ return $this.fileupload('process', data);
+ });
+ originalAdd.call(this, e, data);
+ }
+ },
+
+ processActions: {
+ /*
+ log: function (data, options) {
+ console[options.type](
+ 'Processing "' + data.files[data.index].name + '"'
+ );
+ }
+ */
+ },
+
+ _processFile: function (data, originalData) {
+ var that = this,
+ // eslint-disable-next-line new-cap
+ dfd = $.Deferred().resolveWith(that, [data]),
+ chain = dfd.promise();
+ this._trigger('process', null, data);
+ $.each(data.processQueue, function (i, settings) {
+ var func = function (data) {
+ if (originalData.errorThrown) {
+ // eslint-disable-next-line new-cap
+ return $.Deferred().rejectWith(that, [originalData]).promise();
+ }
+ return that.processActions[settings.action].call(
+ that,
+ data,
+ settings
+ );
+ };
+ chain = chain.then(func, settings.always && func);
+ });
+ chain
+ .done(function () {
+ that._trigger('processdone', null, data);
+ that._trigger('processalways', null, data);
+ })
+ .fail(function () {
+ that._trigger('processfail', null, data);
+ that._trigger('processalways', null, data);
+ });
+ return chain;
+ },
+
+ // Replaces the settings of each processQueue item that
+ // are strings starting with an "@", using the remaining
+ // substring as key for the option map,
+ // e.g. "@autoUpload" is replaced with options.autoUpload:
+ _transformProcessQueue: function (options) {
+ var processQueue = [];
+ $.each(options.processQueue, function () {
+ var settings = {},
+ action = this.action,
+ prefix = this.prefix === true ? action : this.prefix;
+ $.each(this, function (key, value) {
+ if ($.type(value) === 'string' && value.charAt(0) === '@') {
+ settings[key] =
+ options[
+ value.slice(1) ||
+ (prefix
+ ? prefix + key.charAt(0).toUpperCase() + key.slice(1)
+ : key)
+ ];
+ } else {
+ settings[key] = value;
+ }
+ });
+ processQueue.push(settings);
+ });
+ options.processQueue = processQueue;
+ },
+
+ // Returns the number of files currently in the processsing queue:
+ processing: function () {
+ return this._processing;
+ },
+
+ // Processes the files given as files property of the data parameter,
+ // returns a Promise object that allows to bind callbacks:
+ process: function (data) {
+ var that = this,
+ options = $.extend({}, this.options, data);
+ if (options.processQueue && options.processQueue.length) {
+ this._transformProcessQueue(options);
+ if (this._processing === 0) {
+ this._trigger('processstart');
+ }
+ $.each(data.files, function (index) {
+ var opts = index ? $.extend({}, options) : options,
+ func = function () {
+ if (data.errorThrown) {
+ // eslint-disable-next-line new-cap
+ return $.Deferred().rejectWith(that, [data]).promise();
+ }
+ return that._processFile(opts, data);
+ };
+ opts.index = index;
+ that._processing += 1;
+ that._processingQueue = that._processingQueue
+ .then(func, func)
+ .always(function () {
+ that._processing -= 1;
+ if (that._processing === 0) {
+ that._trigger('processstop');
+ }
+ });
+ });
+ }
+ return this._processingQueue;
+ },
+
+ _create: function () {
+ this._super();
+ this._processing = 0;
+ // eslint-disable-next-line new-cap
+ this._processingQueue = $.Deferred().resolveWith(this).promise();
+ }
+ });
+});
diff --git a/yarn.lock b/yarn.lock
index 26f75cc7bcf..8439082536d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2158,7 +2158,7 @@ lodash.get@^4.4.2:
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
-lodash@4.17.21, lodash@^4.17.14, lodash@^4.17.19:
+lodash@4.17.15, lodash@4.17.21, lodash@^4.17.14, lodash@^4.17.19:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -3091,6 +3091,12 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
+squoosh@discourse/squoosh#dc9649d:
+ version "2.0.0"
+ resolved "https://codeload.github.com/discourse/squoosh/tar.gz/dc9649d0a4d396d1251c22291b17d99f1716da44"
+ dependencies:
+ wasm-feature-detect "^1.2.9"
+
static-extend@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
@@ -3409,6 +3415,11 @@ walker@~1.0.5:
dependencies:
makeerror "1.0.x"
+wasm-feature-detect@^1.2.11:
+ version "1.2.11"
+ resolved "https://registry.yarnpkg.com/wasm-feature-detect/-/wasm-feature-detect-1.2.11.tgz#e21992fd1f1d41a47490e392a5893cb39d81e29e"
+ integrity sha512-HUqwaodrQGaZgz1lZaNioIkog9tkeEJjrM3eq4aUL04whXOVDRc/o2EGb/8kV0QX411iAYWEqq7fMBmJ6dKS6w==
+
wcwidth@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"