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"