diff --git a/codecs/wp2/Makefile b/codecs/wp2/Makefile new file mode 100644 index 00000000..8321f6e5 --- /dev/null +++ b/codecs/wp2/Makefile @@ -0,0 +1,47 @@ +CODEC_URL = https://chromium.googlesource.com/codecs/libwebp2/+archive/c90b5b476004c9a98731ae1c175cebab5de50fbf.tar.gz +CODEC_DIR = node_modules/wp2 +CODEC_BUILD_DIR := $(CODEC_DIR)/.build +CODEC_OUT := $(CODEC_BUILD_DIR)/libwebp2.a + +OUT_JS = enc/wp2_enc.js dec/wp2_dec.js +OUT_WASM = $(OUT_JS:.js=.wasm) + +.PHONY: all clean + +all: $(OUT_JS) + +%.js: %.cpp $(CODEC_OUT) + $(CXX) \ + -I $(CODEC_DIR) \ + ${CXXFLAGS} \ + ${LDFLAGS} \ + --bind \ + --closure 1 \ + -s ALLOW_MEMORY_GROWTH=1 \ + -s MODULARIZE=1 \ + -s 'EXPORT_NAME="$(basename $(@F))"' \ + -o $@ \ + $+ + +$(CODEC_OUT): $(CODEC_BUILD_DIR)/Makefile + cd $(CODEC_BUILD_DIR) && \ + $(MAKE) + +$(CODEC_BUILD_DIR)/Makefile: $(CODEC_DIR)/CMakeLists.txt + mkdir -p $(CODEC_BUILD_DIR) + cd $(CODEC_BUILD_DIR) && \ + emcmake cmake \ + -DWP2_ENABLE_SIMD=0 \ + -DWP2_BUILD_TESTS=0 \ + -DWP2_ENABLE_TESTS=0 \ + -DWP2_BUILD_EXAMPLES=0 \ + -DWP2_BUILD_EXTRAS=0 \ + ../ + +$(CODEC_DIR)/CMakeLists.txt: + mkdir -p $(CODEC_DIR) + curl -sL $(CODEC_URL) | tar xz -C $(CODEC_DIR) + +clean: + $(RM) $(OUT_JS) $(OUT_WASM) + $(MAKE) -C $(CODEC_BUILD_DIR) clean diff --git a/codecs/wp2/dec/README.md b/codecs/wp2/dec/README.md new file mode 100644 index 00000000..e3e0b0bc --- /dev/null +++ b/codecs/wp2/dec/README.md @@ -0,0 +1,17 @@ +# WebP2 decoder + +The source for the library is not open-source _yet_. The second it is we will publicize the build steps. + +## Dependencies + +N/A + +## Example + +N/A + +## API + +### `RawImage decode(uint8_t* image_buffer, int image_width, int image_height)` + +Decodes the given WP2 buffer into raw RGBA. `RawImage` is a class with 3 fields: `buffer`, `width`, and `height`. diff --git a/codecs/wp2/dec/wp2_dec.cpp b/codecs/wp2/dec/wp2_dec.cpp new file mode 100644 index 00000000..d661ad9c --- /dev/null +++ b/codecs/wp2/dec/wp2_dec.cpp @@ -0,0 +1,40 @@ +#include +#include +#include + +#include "src/wp2/decode.h" +using namespace emscripten; + +class RawImage { +public: + val buffer; + int width; + int height; + + RawImage(val b, int w, int h) : buffer(b), width(w), height(h) {} +}; + +WP2::ArgbBuffer *buffer = new WP2::ArgbBuffer(WP2_rgbA_32); + +RawImage decode(std::string image_in) { + WP2Status status; + status = WP2::Decode(image_in, buffer); + if (status != WP2_STATUS_OK) { + return RawImage(val::null(), -1, -1); + } + return RawImage(val(typed_memory_view(buffer->width * buffer->height * 4, + (uint8_t *)buffer->GetRow(0))), + buffer->width, buffer->height); +} + +void free_result() { delete buffer; } + +EMSCRIPTEN_BINDINGS(my_module) { + class_("RawImage") + .property("buffer", &RawImage::buffer) + .property("width", &RawImage::width) + .property("height", &RawImage::height); + + function("decode", &decode); + function("free_result", &free_result); +} diff --git a/codecs/wp2/dec/wp2_dec.d.ts b/codecs/wp2/dec/wp2_dec.d.ts new file mode 100644 index 00000000..128c95a2 --- /dev/null +++ b/codecs/wp2/dec/wp2_dec.d.ts @@ -0,0 +1,13 @@ +interface RawImage { + buffer: Uint8Array; + width: number; + height: number; +} + +interface WP2Module extends EmscriptenWasm.Module { + decode(data: BufferSource): RawImage; + free_result(): void; +} + +export default function(opts: EmscriptenWasm.ModuleOpts): Promise; + diff --git a/codecs/wp2/dec/wp2_dec.js b/codecs/wp2/dec/wp2_dec.js new file mode 100644 index 00000000..469a5464 --- /dev/null +++ b/codecs/wp2/dec/wp2_dec.js @@ -0,0 +1,83 @@ + +var wp2_dec = (function() { + var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined; + if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename; + return ( +function(wp2_dec) { + wp2_dec = wp2_dec || {}; + + +var d;d||(d=typeof wp2_dec !== 'undefined' ? wp2_dec : {});var aa,ba;d.ready=new Promise(function(a,b){aa=a;ba=b});var r={},w;for(w in d)d.hasOwnProperty(w)&&(r[w]=d[w]);var ca=!1,y=!1,da=!1,ea=!1;ca="object"===typeof window;y="function"===typeof importScripts;da="object"===typeof process&&"object"===typeof process.versions&&"string"===typeof process.versions.node;ea=!ca&&!da&&!y;var z="",ha,A,ia,ja; +if(da)z=y?require("path").dirname(z)+"/":__dirname+"/",ha=function(a,b){ia||(ia=require("fs"));ja||(ja=require("path"));a=ja.normalize(a);return ia.readFileSync(a,b?null:"utf8")},A=function(a){a=ha(a,!0);a.buffer||(a=new Uint8Array(a));a.buffer||B("Assertion failed: undefined");return a},1=e);)++c;if(16f?e+=String.fromCharCode(f):(f-=65536,e+=String.fromCharCode(55296|f>>10,56320|f&1023))}}else e+=String.fromCharCode(f)}return e} +function pa(a,b,c){var e=H;if(0=g){var l=a.charCodeAt(++f);g=65536+((g&1023)<<10)|l&1023}if(127>=g){if(b>=c)break;e[b++]=g}else{if(2047>=g){if(b+1>=c)break;e[b++]=192|g>>6}else{if(65535>=g){if(b+2>=c)break;e[b++]=224|g>>12}else{if(b+3>=c)break;e[b++]=240|g>>18;e[b++]=128|g>>12&63}e[b++]=128|g>>6&63}e[b++]=128|g&63}}e[b]=0}}var qa="undefined"!==typeof TextDecoder?new TextDecoder("utf-16le"):void 0; +function ra(a,b){var c=a>>1;for(var e=c+b/2;!(c>=e)&&sa[c];)++c;c<<=1;if(32>1];if(0==f||c==b/2)return e;++c;e+=String.fromCharCode(f)}}function ta(a,b,c){void 0===c&&(c=2147483647);if(2>c)return 0;c-=2;var e=b;c=c<2*a.length?c/2:a.length;for(var f=0;f>1]=a.charCodeAt(f),b+=2;I[b>>1]=0;return b-e}function ua(a){return 2*a.length} +function va(a,b){for(var c=0,e="";!(c>=b/4);){var f=J[a+4*c>>2];if(0==f)break;++c;65536<=f?(f-=65536,e+=String.fromCharCode(55296|f>>10,56320|f&1023)):e+=String.fromCharCode(f)}return e}function wa(a,b,c){void 0===c&&(c=2147483647);if(4>c)return 0;var e=b;c=e+c-4;for(var f=0;f=g){var l=a.charCodeAt(++f);g=65536+((g&1023)<<10)|l&1023}J[b>>2]=g;b+=4;if(b+4>c)break}J[b>>2]=0;return b-e} +function xa(a){for(var b=0,c=0;c=e&&++c;b+=4}return b}var K,ya,H,I,sa,J,L,za,Aa;function Ba(a){K=a;d.HEAP8=ya=new Int8Array(a);d.HEAP16=I=new Int16Array(a);d.HEAP32=J=new Int32Array(a);d.HEAPU8=H=new Uint8Array(a);d.HEAPU16=sa=new Uint16Array(a);d.HEAPU32=L=new Uint32Array(a);d.HEAPF32=za=new Float32Array(a);d.HEAPF64=Aa=new Float64Array(a)}var Ca=d.INITIAL_MEMORY||16777216; +d.wasmMemory?G=d.wasmMemory:G=new WebAssembly.Memory({initial:Ca/65536,maximum:32768});G&&(K=G.buffer);Ca=K.byteLength;Ba(K);J[33292]=5376208;function Da(a){for(;0=b?"_"+a:a} +function Xa(a,b){a=Wa(a);return(new Function("body","return function "+a+'() {\n "use strict"; return body.apply(this, arguments);\n};\n'))(b)}function Ya(a){var b=Error,c=Xa(a,function(e){this.name=a;this.message=e;e=Error(e).stack;void 0!==e&&(this.stack=this.toString()+"\n"+e.replace(/^Error(:[^\n]*)?\n/,""))});c.prototype=Object.create(b.prototype);c.prototype.constructor=c;c.prototype.toString=function(){return void 0===this.message?this.name:this.name+": "+this.message};return c} +var Za=void 0;function S(a){throw new Za(a);}var $a=void 0;function ab(a){throw new $a(a);}function bb(a,b,c){function e(k){k=c(k);k.length!==a.length&&ab("Mismatched type converter count");for(var h=0;h>2])}function wb(a,b,c){if(b===c)return a;if(void 0===c.ga)return null;a=wb(a,b,c.ga);return null===a?null:c.ya(a)}var xb={}; +function yb(a,b){for(void 0===b&&S("ptr should not be undefined");a.ga;)b=a.oa(b),a=a.ga;return xb[b]}function zb(a,b){b.da&&b.aa||ab("makeClassHandle requires ptr and ptrType");!!b.fa!==!!b.ea&&ab("Both smartPtrType and smartPtr must be specified");b.count={value:1};return hb(Object.create(a,{$:{value:b}}))} +function W(a,b,c,e){this.name=a;this.ba=b;this.sa=c;this.pa=e;this.qa=!1;this.ka=this.Fa=this.Ea=this.va=this.Ga=this.Da=void 0;void 0!==b.ga?this.toWireType=rb:(this.toWireType=e?qb:tb,this.ia=null)}function Ab(a,b,c){d.hasOwnProperty(a)||ab("Replacing nonexistant public symbol");void 0!==d[a].ha&&void 0!==c?d[a].ha[c]=b:(d[a]=b,d[a].wa=c)} +function X(a,b){a=P(a);var c=d["dynCall_"+a];for(var e=[],f=1;f>2])};case 3:return function(c){return this.fromWireType(Aa[c>>3])};default:throw new TypeError("Unknown float type: "+a);}}function Kb(a){var b=Function;if(!(b instanceof Function))throw new TypeError("new_ called with constructor type "+typeof b+" which is not a function");var c=Xa(b.name||"unknownFunctionName",function(){});c.prototype=b.prototype;c=new c;a=b.apply(c,a);return a instanceof Object?a:c} +function Lb(a,b){for(var c=[],e=0;e>2)+e]);return c}function Mb(a,b,c){switch(b){case 0:return c?function(e){return ya[e]}:function(e){return H[e]};case 1:return c?function(e){return I[e>>1]}:function(e){return sa[e>>1]};case 2:return c?function(e){return J[e>>2]}:function(e){return L[e>>2]};default:throw new TypeError("Unknown integer type: "+a);}}for(var Nb=[null,[],[]],Ob=Array(256),Pb=0;256>Pb;++Pb)Ob[Pb]=String.fromCharCode(Pb);Ua=Ob;Za=d.BindingError=Ya("BindingError"); +$a=d.InternalError=Ya("InternalError");U.prototype.isAliasOf=function(a){if(!(this instanceof U&&a instanceof U))return!1;var b=this.$.da.ba,c=this.$.aa,e=a.$.da.ba;for(a=a.$.aa;b.ga;)c=b.oa(c),b=b.ga;for(;e.ga;)a=e.oa(a),e=e.ga;return b===e&&c===a};U.prototype.clone=function(){this.$.aa||db(this);if(this.$.na)return this.$.count.value+=1,this;var a=hb(Object.create(Object.getPrototypeOf(this),{$:{value:cb(this.$)}}));a.$.count.value+=1;a.$.la=!1;return a}; +U.prototype["delete"]=function(){this.$.aa||db(this);this.$.la&&!this.$.na&&S("Object already scheduled for deletion");fb(this);gb(this.$);this.$.na||(this.$.ea=void 0,this.$.aa=void 0)};U.prototype.isDeleted=function(){return!this.$.aa};U.prototype.deleteLater=function(){this.$.aa||db(this);this.$.la&&!this.$.na&&S("Object already scheduled for deletion");jb.push(this);1===jb.length&&ib&&ib(kb);this.$.la=!0;return this};W.prototype.Ba=function(a){this.va&&(a=this.va(a));return a}; +W.prototype.ua=function(a){this.ka&&this.ka(a)};W.prototype.argPackAdvance=8;W.prototype.readValueFromPointer=vb;W.prototype.deleteObject=function(a){if(null!==a)a["delete"]()}; +W.prototype.fromWireType=function(a){function b(){return this.qa?zb(this.ba.ja,{da:this.Da,aa:c,fa:this,ea:a}):zb(this.ba.ja,{da:this,aa:a})}var c=this.Ba(a);if(!c)return this.ua(a),null;var e=yb(this.ba,c);if(void 0!==e){if(0===e.$.count.value)return e.$.aa=c,e.$.ea=a,e.clone();e=e.clone();this.ua(a);return e}e=this.ba.Aa(c);e=lb[e];if(!e)return b.call(this);e=this.pa?e.xa:e.pointerType;var f=wb(c,this.ba,e.ba);return null===f?b.call(this):this.qa?zb(e.ba.ja,{da:e,aa:f,fa:this,ea:a}):zb(e.ba.ja, +{da:e,aa:f})};d.getInheritedInstanceCount=function(){return Object.keys(xb).length};d.getLiveInheritedInstances=function(){var a=[],b;for(b in xb)xb.hasOwnProperty(b)&&a.push(xb[b]);return a};d.flushPendingDeletes=kb;d.setDelayFunction=function(a){ib=a;jb.length&&ib&&ib(kb)};Bb=d.UnboundTypeError=Ya("UnboundTypeError");d.count_emval_handles=function(){for(var a=0,b=5;b>g])},ia:null})},t:function(a,b,c,e,f,g,l,k,h,p,m,q,t){m=P(m);g=X(f,g);k&&(k=X(l, +k));p&&(p=X(h,p));t=X(q,t);var u=Wa(m);nb(u,function(){Eb("Cannot construct "+m+" due to unbound types",[e])});bb([a,b,c],e?[e]:[],function(n){n=n[0];if(e){var D=n.ba;var v=D.ja}else v=U.prototype;n=Xa(u,function(){if(Object.getPrototypeOf(this)!==x)throw new Za("Use 'new' to construct "+m);if(void 0===C.ma)throw new Za(m+" has no accessible constructor");var ub=C.ma[arguments.length];if(void 0===ub)throw new Za("Tried to invoke ctor of "+m+" with invalid number of parameters ("+arguments.length+ +") - expected ("+Object.keys(C.ma).toString()+") parameters instead!");return ub.apply(this,arguments)});var x=Object.create(v,{constructor:{value:n}});n.prototype=x;var C=new ob(m,n,x,t,D,g,k,p);D=new W(m,C,!0,!1);v=new W(m+"*",C,!1,!1);var fa=new W(m+" const*",C,!1,!0);lb[a]={pointerType:v,xa:fa};Ab(u,n);return[D,v,fa]})},e:function(a,b,c,e,f,g,l,k,h,p){b=P(b);f=X(e,f);bb([],[a],function(m){m=m[0];var q=m.name+"."+b,t={get:function(){Eb("Cannot access "+q+" due to unbound types",[c,l])},enumerable:!0, +configurable:!0};h?t.set=function(){Eb("Cannot access "+q+" due to unbound types",[c,l])}:t.set=function(){S(q+" is a read-only property")};Object.defineProperty(m.ba.ja,b,t);bb([],h?[c,l]:[c],function(u){var n=u[0],D={get:function(){var x=Gb(this,m,q+" getter");return n.fromWireType(f(g,x))},enumerable:!0};if(h){h=X(k,h);var v=u[1];D.set=function(x){var C=Gb(this,m,q+" setter"),fa=[];h(p,C,v.toWireType(fa,x));Fb(fa)}}Object.defineProperty(m.ba.ja,b,D);return[]});return[]})},w:function(a,b){b=P(b); +T(a,{name:b,fromWireType:function(c){var e=Z[c].value;Ib(c);return e},toWireType:function(c,e){return sb(e)},argPackAdvance:8,readValueFromPointer:vb,ia:null})},l:function(a,b,c){c=Ta(c);b=P(b);T(a,{name:b,fromWireType:function(e){return e},toWireType:function(e,f){if("number"!==typeof f&&"boolean"!==typeof f)throw new TypeError('Cannot convert "'+V(f)+'" to '+this.name);return f},argPackAdvance:8,readValueFromPointer:Jb(b,c),ia:null})},g:function(a,b,c,e,f,g){var l=Lb(b,c);a=P(a);f=X(e,f);nb(a,function(){Eb("Cannot call "+ +a+" due to unbound types",l)},b-1);bb([],l,function(k){var h=[k[0],null].concat(k.slice(1)),p=k=a,m=f,q=h.length;2>q&&S("argTypes array size mismatch! Must at least get return value and 'this' types!");for(var t=null!==h[1]&&!1,u=!1,n=1;n>>k}}var h=-1!=b.indexOf("unsigned");T(a,{name:b,fromWireType:g,toWireType:function(p,m){if("number"!==typeof m&&"boolean"!==typeof m)throw new TypeError('Cannot convert "'+V(m)+'" to '+this.name);if(mf)throw new TypeError('Passing a number "'+V(m)+'" from JS side to C/C++ side to an argument of type "'+b+'", which is outside the valid range ['+e+", "+f+"]!");return h?m>>>0:m|0},argPackAdvance:8,readValueFromPointer:Mb(b,l,0!==e),ia:null})},a:function(a,b,c){function e(g){g>>=2;var l=L;return new f(K, +l[g+1],l[g])}var f=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array][b];c=P(c);T(a,{name:c,fromWireType:e,argPackAdvance:8,readValueFromPointer:e},{Ca:!0})},m:function(a,b){b=P(b);var c="std::string"===b;T(a,{name:b,fromWireType:function(e){var f=L[e>>2];if(c)for(var g=e+4,l=0;l<=f;++l){var k=e+4+l;if(l==f||0==H[k]){g=g?oa(H,g,k-g):"";if(void 0===h)var h=g;else h+=String.fromCharCode(0),h+=g;g=k+1}}else{h=Array(f);for(l=0;l=q&&(q=65536+((q&1023)<<10)|f.charCodeAt(++m)&1023);127>=q?++p:p=2047>=q?p+2:65535>=q?p+3:p+4}return p}:function(){return f.length})(),k=Qb(4+l+1);L[k>> +2]=l;if(c&&g)pa(f,k+4,l+1);else if(g)for(g=0;g>2], +m=l(),q,t=h+4,u=0;u<=p;++u){var n=h+4+u*b;if(u==p||0==m[n>>k])t=e(t,n-t),void 0===q?q=t:(q+=String.fromCharCode(0),q+=t),t=n+b}Y(h);return q},toWireType:function(h,p){"string"!==typeof p&&S("Cannot pass non-string to C++ string type "+c);var m=g(p),q=Qb(4+m+b);L[q>>2]=m>>k;f(p,q+4,m+b);null!==h&&h.push(Y,q);return q},argPackAdvance:8,readValueFromPointer:vb,ia:function(h){Y(h)}})},y:function(a,b){b=P(b);T(a,{Ha:!0,name:b,argPackAdvance:0,fromWireType:function(){},toWireType:function(){}})},k:Ib,B:function(a){4< +a&&(Z[a].ta+=1)},o:function(a,b){var c=R[a];void 0===c&&S("_emval_take_value has unknown type "+Cb(a));a=c.readValueFromPointer(b);return sb(a)},i:function(){B()},v:function(a,b,c){H.copyWithin(a,b,b+c)},c:function(a){a>>>=0;var b=H.length;if(2147483648=c;c*=2){var e=b*(1+.2/c);e=Math.min(e,a+100663296);e=Math.max(16777216,a,e);0>>16);Ba(G.buffer);var f=1;break a}catch(g){}f=void 0}if(f)return!0}return!1}, +j:function(a,b,c,e){for(var f=0,g=0;g>2],k=J[b+(8*g+4)>>2],h=0;h>2]=f;return 0},memory:G,n:function(){return 0},q:function(){return 0},p:function(){},A:function(){return 6},z:function(){},h:function(a){a=+a;return 0<=a?+Ka(a+.5):+Ja(a-.5)},d:function(a){a=+a;return 0<=a?+Ka(a+.5):+Ja(a-.5)},u:function(){},table:la}; +(function(){function a(f){d.asm=f.exports;M--;d.monitorRunDependencies&&d.monitorRunDependencies(M);0==M&&(null!==La&&(clearInterval(La),La=null),N&&(f=N,N=null,f()))}function b(f){a(f.instance)}function c(f){return Qa().then(function(g){return WebAssembly.instantiate(g,e)}).then(f,function(g){E("failed to asynchronously prepare wasm: "+g);B(g)})}var e={a:Rb};M++;d.monitorRunDependencies&&d.monitorRunDependencies(M);if(d.instantiateWasm)try{return d.instantiateWasm(e,a)}catch(f){return E("Module.instantiateWasm callback failed with error: "+ +f),!1}(function(){if(F||"function"!==typeof WebAssembly.instantiateStreaming||Na()||Ma("file://")||"function"!==typeof fetch)return c(b);fetch(O,{credentials:"same-origin"}).then(function(f){return WebAssembly.instantiateStreaming(f,e).then(b,function(g){E("wasm streaming compile failed: "+g);E("falling back to ArrayBuffer instantiation");return c(b)})})})();return{}})(); +var Ra=d.___wasm_call_ctors=function(){return(Ra=d.___wasm_call_ctors=d.asm.C).apply(null,arguments)},Qb=d._malloc=function(){return(Qb=d._malloc=d.asm.D).apply(null,arguments)},Y=d._free=function(){return(Y=d._free=d.asm.E).apply(null,arguments)},Db=d.___getTypeName=function(){return(Db=d.___getTypeName=d.asm.F).apply(null,arguments)};d.___embind_register_native_and_builtin_types=function(){return(d.___embind_register_native_and_builtin_types=d.asm.G).apply(null,arguments)}; +d.dynCall_ii=function(){return(d.dynCall_ii=d.asm.H).apply(null,arguments)};d.dynCall_vi=function(){return(d.dynCall_vi=d.asm.I).apply(null,arguments)};d.dynCall_iii=function(){return(d.dynCall_iii=d.asm.J).apply(null,arguments)};d.dynCall_viii=function(){return(d.dynCall_viii=d.asm.K).apply(null,arguments)};d.dynCall_vii=function(){return(d.dynCall_vii=d.asm.L).apply(null,arguments)};d.dynCall_v=function(){return(d.dynCall_v=d.asm.M).apply(null,arguments)}; +d.dynCall_viiiiiii=function(){return(d.dynCall_viiiiiii=d.asm.N).apply(null,arguments)};d.dynCall_viiiiiiiii=function(){return(d.dynCall_viiiiiiiii=d.asm.O).apply(null,arguments)};d.dynCall_viiiiiiii=function(){return(d.dynCall_viiiiiiii=d.asm.P).apply(null,arguments)};d.dynCall_viiii=function(){return(d.dynCall_viiii=d.asm.Q).apply(null,arguments)};d.dynCall_viiiiii=function(){return(d.dynCall_viiiiii=d.asm.R).apply(null,arguments)}; +d.dynCall_iidiiii=function(){return(d.dynCall_iidiiii=d.asm.S).apply(null,arguments)};d.dynCall_iiiii=function(){return(d.dynCall_iiiii=d.asm.T).apply(null,arguments)};d.dynCall_iiiiii=function(){return(d.dynCall_iiiiii=d.asm.U).apply(null,arguments)};d.dynCall_fi=function(){return(d.dynCall_fi=d.asm.V).apply(null,arguments)};d.dynCall_fii=function(){return(d.dynCall_fii=d.asm.W).apply(null,arguments)};d.dynCall_viiiii=function(){return(d.dynCall_viiiii=d.asm.X).apply(null,arguments)}; +d.dynCall_iiii=function(){return(d.dynCall_iiii=d.asm.Y).apply(null,arguments)};d.dynCall_jiji=function(){return(d.dynCall_jiji=d.asm.Z).apply(null,arguments)};var Sb;N=function Tb(){Sb||Ub();Sb||(N=Tb)}; +function Ub(){function a(){if(!Sb&&(Sb=!0,d.calledRun=!0,!ma)){Da(Fa);Da(Ga);aa(d);if(d.onRuntimeInitialized)d.onRuntimeInitialized();if(d.postRun)for("function"==typeof d.postRun&&(d.postRun=[d.postRun]);d.postRun.length;){var b=d.postRun.shift();Ha.unshift(b)}Da(Ha)}}if(!(0 +#include +#include + +#include "src/wp2/encode.h" +using namespace emscripten; + +// enum PartitionMethod { +// // For each block size starting from biggest, take all blocks matching +// some: THRESH_VARIANCE_PARTITIONING, // uniform variance score +// // For each block size starting from biggest, find the best pos depending +// on: BIG_BLOCK_VARIANCE_RNG_PARTITIONING, // uniform variance score +// BIG_BLOCK_VARIANCE_MAX_PARTITIONING, // low variance score +// BIG_BLOCK_TRANS_AMPL_PARTITIONING, // DCT coeffs amplitude +// BIG_BLOCK_TRANS_ZEROS_PARTITIONING, // number of near zeros in DCT coeffs +// BIG_BLOCK_LUMA_RNG_PARTITIONING, // luma range score +// BIG_BLOCK_RECONS_PARTITIONING, // reconstruction score +// BIG_BLOCK_PRED_PARTITIONING, // prediction score +// BIG_BLOCK_QUANT_PARTITIONING, // pred+recons+quant score +// // For each pos starting from top left, take the best block size depending +// on: TOP_LEFT_RECONS_PARTITIONING, // reconstruction score +// TOP_LEFT_PRED_PARTITIONING, // prediction score +// TOP_LEFT_BLOCK_ENCODE_PARTITIONING, // block encoding score +// TOP_LEFT_TILE_ENCODE_PARTITIONING, // tile encoding score (slow) +// TOP_LEFT_TILE_ENCODE_DECODE_PARTITIONING, // tile enc+dec score (slow) +// // Combine several metrics in successive block size passes: +// MULTIPASS_PARTITIONING, +// // For each possible block layout, take the best one (extremely slow): +// EXHAUSTIVE_PARTITIONING, // based on tile enc+dec score +// // Split variants of methods above: +// SPLIT_BIG_BLOCK_VARIANCE_MAX_PARTITIONING, +// SPLIT_BIG_BLOCK_PRED_PARTITIONING, +// SPLIT_BIG_BLOCK_QUANT_PARTITIONING, +// SPLIT_TOP_LEFT_BLOCK_ENCODE_PARTITIONING, +// // Fixed block size (except on edges). Also depends on the partition set. +// ALL_4X4_PARTITIONING, +// ALL_8X8_PARTITIONING, +// ALL_16X16_PARTITIONING, +// ALL_32X32_PARTITIONING, +// NUM_PARTITION_METHODS +// }; + +// enum PartitionSet { // The smallest block size is 4x4. +// SMALL_SQUARES, // Up to 8x8 +// SMALL_RECTS, // Up to 16x16 +// ALL_RECTS, // Up to 32x32, ratio at most 4:1 +// THICK_RECTS, // Up to 32x32, ratio at most 2:1 +// MEDIUM_SQUARES, // Up to 16x16 +// ALL_SQUARES, // Up to 32x32 +// SOME_RECTS, // Up to 32x32, subset of frequently used rects +// NUM_PARTITION_SETS +// }; + +// enum class PredictorSet { +// CUSTOM_PREDICTORS = 0, // this predictor should stay first +// DC_PREDICTORS, +// ANGLE_PREDICTORS, +// VP8_PREDICTORS, +// SMOOTH_PREDICTORS, +// PAETH_PREDICTOR, +// AV1_FILTER, +// FUSE_PREDICTORS, +// ALL, // includes all of the above starting at 1 +// LARGE_PREDICTORS, // fixed set for some Y blocks +// UV_PREDICTORS, // fixed set for U/V +// ALPHA_PREDICTORS, // fixed set for alpha +// LAST_PREDICTORS, // end-of-list marker +// }; + +// enum class Csp { kYCoCg, kYCbCr, kCustom, kYIQ }; + +// typedef enum { +// UVModeAdapt = 0, +// UVMode420, +// UVModeSharp, +// UVMode444, +// NumUVMode // End-of-list marker +// } UVMode; + +struct WP2Options { + float quality = 75.0f; // Range: [0 = smallest file .. 100 = lossless] + // Quality in [95-100) range will use near-lossless. + // Quality 100 is strictly lossless. + // int target_size = 0; // If non-zero, set the desired target size in + // bytes. Takes precedence over the 'quality' parameter. + // float target_psnr = 0.f; // If non-zero, specifies the minimal distortion + // to try to achieve. Precedence over 'target_size'. + + float alpha_quality = 100.f; // Range: [0 = smallest size .. 100 = lossless] + int speed = 5; // Quality/speed trade-off. Range: [0=fast .. 9=slower-better] + + // Side parameters: + // Set whether the image will be rotated during decoding. + // Orientation decoding_orientation = Orientation::kOriginal; + // Add a heavily compressed preview to be decoded and displayed before final + // pixels (small size overhead up to kMaxPreviewSize). + // bool create_preview = false; + + // TransferFunction transfer_function = WP2_TF_ITU_R_BT2020_10BIT; + + // Parameters related to lossy compression only: + int pass = 1; // Number of entropy-analysis passes. Range: [1..10] + + // Spatial noise shaping strength in [0(=off), 100] + // Affects how we spread noise between 'risky' areas (where noise is easily + // visible) and easier areas (where it's less visible). A high SNS + // value leads to skewing noise more towards areas where it should be less + // visible. In general this improves SSIM but worsens PSNR. + float sns = 50.f; + int error_diffusion = 0; // error diffusion strength [0=off, 100=max] + + int segments = 4; // Max number of segments. Range: [1..kMaxNumSegments] + int segment_threshold = 0; // Segmentation threshold. Range: [0..100] + // selector for explicit or implicit segment-id + // typedef enum { + // SEGMENT_ID_AUTO, // use ID_EXPLICIT above a quality threshold + // SEGMENT_ID_EXPLICIT, + // SEGMENT_ID_IMPLICIT + // } SegmentIdMode; + // SegmentIdMode segment_id_mode = SEGMENT_ID_AUTO; + + // Size of tiles (width/height) in pixels. Tiles are always square. Each + // tile is compressed independently, possibly in parallel. + // Valid values: 64, 128, 256, 512 (see format_constants.h) + // 0 means size is chosen automatically. + int tile_size = 0; + + // Algorithm for dividing the image into blocks. + // PartitionMethod partition_method = MULTIPASS_PARTITIONING; + // The set of allowed block sizes for image partitioning. + // PartitionSet partition_set = SOME_RECTS; + // If true, use binary space partitioning instead of floating partition. + bool partition_snapping = true; + + // The set of predictors that can be used for reconstruction. + // PredictorSet predictor_set = PredictorSet::VP8_PREDICTORS; + + WP2::Csp csp_type = WP2::Csp::kYCoCg; // Colorspace. + + WP2::EncoderConfig::UVMode uv_mode = + WP2::EncoderConfig::UVMode::UVMode420; // Default sub-sampling mode for + // U/V planes. + + // int preprocessing = 0; // Preprocessing filter. + // int preprocessing_strength = 0; // Range: [0 .. 100] + + // bool use_random_matrix = false; // Experimental. + // bool store_grain = false; // Experimental: store grain info + + // // Parameters related to lossless compression only: + // bool use_delta_palette = false; // Reserved for future lossless feature. + + // // Performance parameters (no impact on encoded bytes): + // int thread_level = 0; // If non-zero, try and use multi-threaded + // encoding. bool low_memory = false; // Memory usage reduction (but CPU use + // increase). + + // // Neural compression: + // int use_neural_compression = 0; // Neural network compression. + // const char* graphdef_path = nullptr; // Directory holding encoder / + // decoder + // // graphdefs structure: + // // base/qq/[en|de]coder.pbbin + + // EncoderInfo* info = nullptr; // If not +}; + +val encode(std::string image_in, int image_width, int image_height, + WP2Options options) { + WP2Status status; + uint8_t *image_buffer = (uint8_t *)image_in.c_str(); + WP2::ArgbBuffer src = WP2::ArgbBuffer(); + status = src.Import(WP2_rgbA_32, // Format. WP2_RGBA_32 is the same but NOT + // premultiplied alpha + image_width, image_height, image_buffer, 4 * image_width); + if (status != WP2_STATUS_OK) { + return val(1); + } + + WP2::MemoryWriter memory_writer; + WP2::EncoderConfig config; + config.quality = options.quality; + config.alpha_quality = options.alpha_quality; + config.speed = options.speed; + config.pass = options.pass; + config.sns = options.sns; + status = WP2::Encode(src, &memory_writer, config); + if (status != WP2_STATUS_OK) { + return val(2); + } + + return val(typed_memory_view(memory_writer.size_, memory_writer.mem_)); + // Lol I forgot to add the free here. +} + +EMSCRIPTEN_BINDINGS(my_module) { + value_object("WP2Options") + .field("quality", &WP2Options::quality) + .field("alpha_quality", &WP2Options::alpha_quality) + .field("speed", &WP2Options::speed) + .field("pass", &WP2Options::pass) + .field("sns", &WP2Options::sns); + + function("encode", &encode); +} diff --git a/codecs/wp2/enc/wp2_enc.d.ts b/codecs/wp2/enc/wp2_enc.d.ts new file mode 100644 index 00000000..04949538 --- /dev/null +++ b/codecs/wp2/enc/wp2_enc.d.ts @@ -0,0 +1,8 @@ +import { EncodeOptions } from '../../src/codecs/wp2/encoder-meta'; + +interface WP2Module extends EmscriptenWasm.Module { + encode(data: BufferSource, width: number, height: number, options: EncodeOptions): Uint8Array; + free_result(): void; +} + +export default function(opts: EmscriptenWasm.ModuleOpts): Promise; diff --git a/codecs/wp2/enc/wp2_enc.js b/codecs/wp2/enc/wp2_enc.js new file mode 100644 index 00000000..9667e085 --- /dev/null +++ b/codecs/wp2/enc/wp2_enc.js @@ -0,0 +1,71 @@ + +var wp2_enc = (function() { + var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined; + if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename; + return ( +function(wp2_enc) { + wp2_enc = wp2_enc || {}; + + +var c;c||(c=typeof wp2_enc !== 'undefined' ? wp2_enc : {});var aa,ba;c.ready=new Promise(function(a,b){aa=a;ba=b});var r={},t;for(t in c)c.hasOwnProperty(t)&&(r[t]=c[t]);var u=!1,v=!1,ca=!1,da=!1;u="object"===typeof window;v="function"===typeof importScripts;ca="object"===typeof process&&"object"===typeof process.versions&&"string"===typeof process.versions.node;da=!u&&!ca&&!v;var w="",y,B,ea,fa; +if(ca)w=v?require("path").dirname(w)+"/":__dirname+"/",y=function(a,b){ea||(ea=require("fs"));fa||(fa=require("path"));a=fa.normalize(a);return ea.readFileSync(a,b?null:"utf8")},B=function(a){a=y(a,!0);a.buffer||(a=new Uint8Array(a));a.buffer||C("Assertion failed: undefined");return a},1=e);)++d;if(16f?e+=String.fromCharCode(f):(f-=65536,e+=String.fromCharCode(55296|f>>10,56320|f&1023))}}else e+=String.fromCharCode(f)}return e} +function ma(a,b,d){var e=I;if(0=g){var l=a.charCodeAt(++f);g=65536+((g&1023)<<10)|l&1023}if(127>=g){if(b>=d)break;e[b++]=g}else{if(2047>=g){if(b+1>=d)break;e[b++]=192|g>>6}else{if(65535>=g){if(b+2>=d)break;e[b++]=224|g>>12}else{if(b+3>=d)break;e[b++]=240|g>>18;e[b++]=128|g>>12&63}e[b++]=128|g>>6&63}e[b++]=128|g&63}}e[b]=0}}var na="undefined"!==typeof TextDecoder?new TextDecoder("utf-16le"):void 0; +function oa(a,b){var d=a>>1;for(var e=d+b/2;!(d>=e)&&J[d];)++d;d<<=1;if(32>1];if(0==f||d==b/2)return e;++d;e+=String.fromCharCode(f)}}function pa(a,b,d){void 0===d&&(d=2147483647);if(2>d)return 0;d-=2;var e=b;d=d<2*a.length?d/2:a.length;for(var f=0;f>1]=a.charCodeAt(f),b+=2;K[b>>1]=0;return b-e}function qa(a){return 2*a.length} +function ra(a,b){for(var d=0,e="";!(d>=b/4);){var f=L[a+4*d>>2];if(0==f)break;++d;65536<=f?(f-=65536,e+=String.fromCharCode(55296|f>>10,56320|f&1023)):e+=String.fromCharCode(f)}return e}function sa(a,b,d){void 0===d&&(d=2147483647);if(4>d)return 0;var e=b;d=e+d-4;for(var f=0;f=g){var l=a.charCodeAt(++f);g=65536+((g&1023)<<10)|l&1023}L[b>>2]=g;b+=4;if(b+4>d)break}L[b>>2]=0;return b-e} +function ta(a){for(var b=0,d=0;d=e&&++d;b+=4}return b}var M,ua,I,K,J,L,N,va,wa;function xa(a){M=a;c.HEAP8=ua=new Int8Array(a);c.HEAP16=K=new Int16Array(a);c.HEAP32=L=new Int32Array(a);c.HEAPU8=I=new Uint8Array(a);c.HEAPU16=J=new Uint16Array(a);c.HEAPU32=N=new Uint32Array(a);c.HEAPF32=va=new Float32Array(a);c.HEAPF64=wa=new Float64Array(a)}var ya=c.INITIAL_MEMORY||16777216;c.wasmMemory?F=c.wasmMemory:F=new WebAssembly.Memory({initial:ya/65536,maximum:32768}); +F&&(M=F.buffer);ya=M.byteLength;xa(M);L[35276]=5384144;function za(a){for(;0>2])}var R={},S={},Sa={};function Ta(a){if(void 0===a)return"_unknown";a=a.replace(/[^a-zA-Z0-9_]/g,"$");var b=a.charCodeAt(0);return 48<=b&&57>=b?"_"+a:a}function Ua(a,b){a=Ta(a);return(new Function("body","return function "+a+'() {\n "use strict"; return body.apply(this, arguments);\n};\n'))(b)} +function Va(a){var b=Error,d=Ua(a,function(e){this.name=a;this.message=e;e=Error(e).stack;void 0!==e&&(this.stack=this.toString()+"\n"+e.replace(/^Error(:[^\n]*)?\n/,""))});d.prototype=Object.create(b.prototype);d.prototype.constructor=d;d.prototype.toString=function(){return void 0===this.message?this.name:this.name+": "+this.message};return d}var Wa=void 0; +function Xa(a,b,d){function e(k){k=d(k);if(k.length!==a.length)throw new Wa("Mismatched type converter count");for(var h=0;h>2])};case 3:return function(d){return this.fromWireType(wa[d>>3])};default:throw new TypeError("Unknown float type: "+a);}}function fb(a){var b=Function;if(!(b instanceof Function))throw new TypeError("new_ called with constructor type "+typeof b+" which is not a function");var d=Ua(b.name||"unknownFunctionName",function(){});d.prototype=b.prototype;d=new d;a=b.apply(d,a);return a instanceof Object?a:d} +function gb(a,b){var d=c;if(void 0===d[a].fa){var e=d[a];d[a]=function(){d[a].fa.hasOwnProperty(arguments.length)||V("Function '"+b+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+d[a].fa+")!");return d[a].fa[arguments.length].apply(this,arguments)};d[a].fa=[];d[a].fa[e.la]=e}} +function hb(a,b,d){c.hasOwnProperty(a)?((void 0===d||void 0!==c[a].fa&&void 0!==c[a].fa[d])&&V("Cannot register public name '"+a+"' twice"),gb(a,a),c.hasOwnProperty(d)&&V("Cannot register multiple overloads of a function with the same number of arguments ("+d+")!"),c[a].fa[d]=b):(c[a]=b,void 0!==d&&(c[a].ya=d))}function ib(a,b){for(var d=[],e=0;e>2)+e]);return d} +function Y(a,b){a=U(a);var d=c["dynCall_"+a];for(var e=[],f=1;f>1]}:function(e){return J[e>>1]};case 2:return d?function(e){return L[e>>2]}:function(e){return N[e>>2]};default:throw new TypeError("Unknown integer type: "+a);}}var ob=[null,[],[]];Wa=c.InternalError=Va("InternalError"); +for(var pb=Array(256),qb=0;256>qb;++qb)pb[qb]=String.fromCharCode(qb);Za=pb;$a=c.BindingError=Va("BindingError");c.count_emval_handles=function(){for(var a=0,b=5;b>g])},ga:null})},y:function(a,b){b=U(b);T(a,{name:b,fromWireType:function(d){var e=X[d].value;bb(d);return e},toWireType:function(d,e){return cb(e)},argPackAdvance:8,readValueFromPointer:Ra,ga:null})},j:function(a,b,d){d=Ya(d);b=U(b);T(a, +{name:b,fromWireType:function(e){return e},toWireType:function(e,f){if("number"!==typeof f&&"boolean"!==typeof f)throw new TypeError('Cannot convert "'+db(f)+'" to '+this.name);return f},argPackAdvance:8,readValueFromPointer:eb(b,d),ga:null})},r:function(a,b,d,e,f,g){var l=ib(b,d);a=U(a);f=Y(e,f);hb(a,function(){mb("Cannot call "+a+" due to unbound types",l)},b-1);Xa([],l,function(k){var h=[k[0],null].concat(k.slice(1)),m=k=a,n=f,q=h.length;2>q&&V("argTypes array size mismatch! Must at least get return value and 'this' types!"); +for(var x=null!==h[1]&&!1,z=!1,p=1;p>>k}}var h=-1!=b.indexOf("unsigned");T(a,{name:b,fromWireType:g,toWireType:function(m, +n){if("number"!==typeof n&&"boolean"!==typeof n)throw new TypeError('Cannot convert "'+db(n)+'" to '+this.name);if(nf)throw new TypeError('Passing a number "'+db(n)+'" from JS side to C/C++ side to an argument of type "'+b+'", which is outside the valid range ['+e+", "+f+"]!");return h?n>>>0:n|0},argPackAdvance:8,readValueFromPointer:nb(b,l,0!==e),ga:null})},a:function(a,b,d){function e(g){g>>=2;var l=N;return new f(M,l[g+1],l[g])}var f=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array, +Uint32Array,Float32Array,Float64Array][b];d=U(d);T(a,{name:d,fromWireType:e,argPackAdvance:8,readValueFromPointer:e},{ra:!0})},k:function(a,b){b=U(b);var d="std::string"===b;T(a,{name:b,fromWireType:function(e){var f=N[e>>2];if(d)for(var g=e+4,l=0;l<=f;++l){var k=e+4+l;if(l==f||0==I[k]){g=g?H(I,g,k-g):"";if(void 0===h)var h=g;else h+=String.fromCharCode(0),h+=g;g=k+1}}else{h=Array(f);for(l=0;l=q&&(q=65536+((q&1023)<<10)|f.charCodeAt(++n)&1023);127>=q?++m:m=2047>=q?m+2:65535>=q?m+3:m+4}return m}:function(){return f.length})(),k=rb(4+l+1);N[k>>2]=l;if(d&&g)ma(f,k+4,l+1);else if(g)for(g=0;g>2],n=l(),q,x=h+4,z=0;z<=m;++z){var p=h+4+z*b;if(z==m||0==n[p>>k])x=e(x,p-x),void 0=== +q?q=x:(q+=String.fromCharCode(0),q+=x),x=p+b}Z(h);return q},toWireType:function(h,m){"string"!==typeof m&&V("Cannot pass non-string to C++ string type "+d);var n=g(m),q=rb(4+n+b);N[q>>2]=n>>k;f(m,q+4,n+b);null!==h&&h.push(Z,q);return q},argPackAdvance:8,readValueFromPointer:Ra,ga:function(h){Z(h)}})},x:function(a,b,d,e,f,g){Pa[a]={name:U(b),sa:Y(d,e),ta:Y(f,g),ka:[]}},f:function(a,b,d,e,f,g,l,k,h,m){Pa[a].ka.push({ma:U(b),qa:d,oa:Y(e,f),pa:g,va:l,ua:Y(k,h),wa:m})},A:function(a,b){b=U(b);T(a,{xa:!0, +name:b,argPackAdvance:0,fromWireType:function(){},toWireType:function(){}})},n:bb,q:function(a){4>>=0;var b=I.length;if(2147483648=d;d*=2){var e=b*(1+.2/d);e=Math.min(e,a+100663296);e=Math.max(16777216,a,e);0>>16);xa(F.buffer);var f=1;break a}catch(g){}f=void 0}if(f)return!0}return!1},w:function(){return 0},s:function(){},i:function(a,b,d,e){for(var f=0,g=0;g>2],k=L[b+(8*g+4)>>2],h=0;h>2]=f;return 0},memory:F,l:function(){return 0},E:function(){return 0},D:function(){},C:function(){return 6},B:function(){},g:function(a){a=+a;return 0<=a?+Ga(a+.5):+Fa(a-.5)},d:function(a){a= ++a;return 0<=a?+Ga(a+.5):+Fa(a-.5)},t:function(){},table:ja}; +(function(){function a(f){c.asm=f.exports;O--;c.monitorRunDependencies&&c.monitorRunDependencies(O);0==O&&(null!==Ha&&(clearInterval(Ha),Ha=null),P&&(f=P,P=null,f()))}function b(f){a(f.instance)}function d(f){return Ma().then(function(g){return WebAssembly.instantiate(g,e)}).then(f,function(g){D("failed to asynchronously prepare wasm: "+g);C(g)})}var e={a:sb};O++;c.monitorRunDependencies&&c.monitorRunDependencies(O);if(c.instantiateWasm)try{return c.instantiateWasm(e,a)}catch(f){return D("Module.instantiateWasm callback failed with error: "+ +f),!1}(function(){if(E||"function"!==typeof WebAssembly.instantiateStreaming||Ja()||Ia("file://")||"function"!==typeof fetch)return d(b);fetch(Q,{credentials:"same-origin"}).then(function(f){return WebAssembly.instantiateStreaming(f,e).then(b,function(g){D("wasm streaming compile failed: "+g);D("falling back to ArrayBuffer instantiation");return d(b)})})})();return{}})(); +var Na=c.___wasm_call_ctors=function(){return(Na=c.___wasm_call_ctors=c.asm.G).apply(null,arguments)},rb=c._malloc=function(){return(rb=c._malloc=c.asm.H).apply(null,arguments)},Z=c._free=function(){return(Z=c._free=c.asm.I).apply(null,arguments)},lb=c.___getTypeName=function(){return(lb=c.___getTypeName=c.asm.J).apply(null,arguments)};c.___embind_register_native_and_builtin_types=function(){return(c.___embind_register_native_and_builtin_types=c.asm.K).apply(null,arguments)}; +c.dynCall_i=function(){return(c.dynCall_i=c.asm.L).apply(null,arguments)};c.dynCall_vi=function(){return(c.dynCall_vi=c.asm.M).apply(null,arguments)};c.dynCall_fii=function(){return(c.dynCall_fii=c.asm.N).apply(null,arguments)};c.dynCall_viif=function(){return(c.dynCall_viif=c.asm.O).apply(null,arguments)};c.dynCall_iii=function(){return(c.dynCall_iii=c.asm.P).apply(null,arguments)};c.dynCall_viii=function(){return(c.dynCall_viii=c.asm.Q).apply(null,arguments)}; +c.dynCall_iiiiii=function(){return(c.dynCall_iiiiii=c.asm.R).apply(null,arguments)};c.dynCall_viiiii=function(){return(c.dynCall_viiiii=c.asm.S).apply(null,arguments)};c.dynCall_ii=function(){return(c.dynCall_ii=c.asm.T).apply(null,arguments)};c.dynCall_vii=function(){return(c.dynCall_vii=c.asm.U).apply(null,arguments)};c.dynCall_viiiiiii=function(){return(c.dynCall_viiiiiii=c.asm.V).apply(null,arguments)};c.dynCall_viiiiiiiii=function(){return(c.dynCall_viiiiiiiii=c.asm.W).apply(null,arguments)}; +c.dynCall_viiiiiiii=function(){return(c.dynCall_viiiiiiii=c.asm.X).apply(null,arguments)};c.dynCall_viiii=function(){return(c.dynCall_viiii=c.asm.Y).apply(null,arguments)};c.dynCall_viiiiii=function(){return(c.dynCall_viiiiii=c.asm.Z).apply(null,arguments)};c.dynCall_iidiiii=function(){return(c.dynCall_iidiiii=c.asm._).apply(null,arguments)};c.dynCall_v=function(){return(c.dynCall_v=c.asm.$).apply(null,arguments)};c.dynCall_iiiii=function(){return(c.dynCall_iiiii=c.asm.aa).apply(null,arguments)}; +c.dynCall_fi=function(){return(c.dynCall_fi=c.asm.ba).apply(null,arguments)};c.dynCall_iiiiiiiiii=function(){return(c.dynCall_iiiiiiiiii=c.asm.ca).apply(null,arguments)};c.dynCall_iiii=function(){return(c.dynCall_iiii=c.asm.da).apply(null,arguments)};c.dynCall_jiji=function(){return(c.dynCall_jiji=c.asm.ea).apply(null,arguments)};var tb;P=function ub(){tb||vb();tb||(P=ub)}; +function vb(){function a(){if(!tb&&(tb=!0,c.calledRun=!0,!ka)){za(Ba);za(Ca);aa(c);if(c.onRuntimeInitialized)c.onRuntimeInitialized();if(c.postRun)for("function"==typeof c.postRun&&(c.postRun=[c.postRun]);c.postRun.length;){var b=c.postRun.shift();Da.unshift(b)}za(Da)}}if(!(0 { +export async function decodeImage( + blob: Blob, + processor: Processor, +): Promise { const mimeType = await sniffMimeType(blob); const canDecode = await canDecodeImageType(mimeType); try { if (!canDecode) { + if (mimeType === 'image/webp2') return await processor.wp2Decode(blob); if (mimeType === 'image/avif') return await processor.avifDecode(blob); if (mimeType === 'image/webp') return await processor.webpDecode(blob); if (mimeType === 'image/jpegxl') return await processor.jxlDecode(blob); @@ -14,6 +18,6 @@ export async function decodeImage(blob: Blob, processor: Processor): Promise { return timed('webpDecode', () => decode(data)); } +async function wp2Encode( + data: ImageData, options: import('../wp2/encoder-meta').EncodeOptions, +): Promise { + const { encode } = await import( + /* webpackChunkName: "process-wp2 -enc" */ + '../wp2/encoder'); + return encode(data, options); +} + +async function wp2Decode(data: ArrayBuffer): Promise { + const { decode } = await import( + /* webpackChunkName: "process-wp2-dec" */ + '../wp2/decoder'); + return decode(data); +} + async function avifEncode( data: ImageData, options: import('../avif/encoder-meta').EncodeOptions, ): Promise { @@ -122,6 +138,8 @@ const exports = { oxiPngEncode, webpEncode, webpDecode, + wp2Encode, + wp2Decode, avifEncode, avifDecode, jxlEncode, diff --git a/src/codecs/processor.ts b/src/codecs/processor.ts index 35729268..48d64a40 100644 --- a/src/codecs/processor.ts +++ b/src/codecs/processor.ts @@ -4,6 +4,7 @@ import { canvasEncode, blobToArrayBuffer } from '../lib/util'; import { EncodeOptions as MozJPEGEncoderOptions } from './mozjpeg/encoder-meta'; import { EncodeOptions as OxiPNGEncoderOptions } from './oxipng/encoder-meta'; import { EncodeOptions as WebPEncoderOptions } from './webp/encoder-meta'; +import { EncodeOptions as WP2EncoderOptions } from './wp2/encoder-meta'; import { EncodeOptions as AvifEncoderOptions } from './avif/encoder-meta'; import { EncodeOptions as JXLEncoderOptions } from './jxl/encoder-meta'; import { EncodeOptions as BrowserJPEGOptions } from './browser-jpeg/encoder-meta'; @@ -157,12 +158,23 @@ export default class Processor { return this._workerApi!.webpEncode(data, opts); } + @Processor._processingJob({ needsWorker: true }) + wp2Encode(data: ImageData, opts: WP2EncoderOptions): Promise { + return this._workerApi!.wp2Encode(data, opts); + } + @Processor._processingJob({ needsWorker: true }) async webpDecode(blob: Blob): Promise { const data = await blobToArrayBuffer(blob); return this._workerApi!.webpDecode(data); } + @Processor._processingJob({ needsWorker: true }) + async wp2Decode(blob: Blob): Promise { + const data = await blobToArrayBuffer(blob); + return this._workerApi!.wp2Decode(data); + } + @Processor._processingJob({ needsWorker: true }) async avifDecode(blob: Blob): Promise { const data = await blobToArrayBuffer(blob); diff --git a/src/codecs/wp2/decoder-meta.ts b/src/codecs/wp2/decoder-meta.ts new file mode 100644 index 00000000..92c52cab --- /dev/null +++ b/src/codecs/wp2/decoder-meta.ts @@ -0,0 +1,7 @@ +export const name = 'WASM WP2 Decoder'; + +const supportedMimeTypes = ['image/webp2']; + +export function canHandleMimeType(mimeType: string): boolean { + return supportedMimeTypes.includes(mimeType); +} diff --git a/src/codecs/wp2/decoder.ts b/src/codecs/wp2/decoder.ts new file mode 100644 index 00000000..a6d7ea59 --- /dev/null +++ b/src/codecs/wp2/decoder.ts @@ -0,0 +1,20 @@ +import wp2_dec, { WP2Module } from '../../../codecs/wp2/dec/wp2_dec'; +import wasmUrl from '../../../codecs/wp2/dec/wp2_dec.wasm'; +import { initEmscriptenModule } from '../util'; + +let emscriptenModule: Promise; + +export async function decode(data: ArrayBuffer): Promise { + if (!emscriptenModule) emscriptenModule = initEmscriptenModule(wp2_dec, wasmUrl); + + const module = await emscriptenModule; + const rawImage = module.decode(data); + const result = new ImageData( + new Uint8ClampedArray(rawImage.buffer), + rawImage.width, + rawImage.height, + ); + + module.free_result(); + return result; +} diff --git a/src/codecs/wp2/encoder-meta.ts b/src/codecs/wp2/encoder-meta.ts new file mode 100644 index 00000000..34c26e7a --- /dev/null +++ b/src/codecs/wp2/encoder-meta.ts @@ -0,0 +1,20 @@ +export interface EncodeOptions { + quality: number; + alpha_quality: number; + speed: number; + pass: number; + sns: number; +} +export interface EncoderState { type: typeof type; options: EncodeOptions; } + +export const type = 'wp2'; +export const label = 'WP2 (unstable)'; +export const mimeType = 'image/webp2'; +export const extension = 'wp2'; +export const defaultOptions: EncodeOptions = { + quality: 75, + alpha_quality: 100, + speed: 5, + pass: 1, + sns: 50, +}; diff --git a/src/codecs/wp2/encoder.ts b/src/codecs/wp2/encoder.ts new file mode 100644 index 00000000..4e8538a3 --- /dev/null +++ b/src/codecs/wp2/encoder.ts @@ -0,0 +1,17 @@ +import wp2_enc, { WP2Module } from '../../../codecs/wp2/enc/wp2_enc'; +import wasmUrl from '../../../codecs/wp2/enc/wp2_enc.wasm'; +import { EncodeOptions } from './encoder-meta'; +import { initEmscriptenModule } from '../util'; + +let emscriptenModule: Promise; + +export async function encode(data: ImageData, options: EncodeOptions): Promise { + if (!emscriptenModule) emscriptenModule = initEmscriptenModule(wp2_enc, wasmUrl); + + const module = await emscriptenModule; + const resultView = module.encode(data.data, data.width, data.height, options); + const result = new Uint8Array(resultView); + + // wasm can’t run on SharedArrayBuffers, so we hard-cast to ArrayBuffer. + return result.buffer as ArrayBuffer; +} diff --git a/src/codecs/wp2/options.tsx b/src/codecs/wp2/options.tsx new file mode 100644 index 00000000..4af93e33 --- /dev/null +++ b/src/codecs/wp2/options.tsx @@ -0,0 +1,109 @@ +import { h, Component } from 'preact'; +import { bind } from '../../lib/initial-util'; +import { inputFieldValueAsNumber, preventDefault } from '../../lib/util'; +import { EncodeOptions } from './encoder-meta'; +import * as style from '../../components/Options/style.scss'; +// import Checkbox from '../../components/checkbox'; +// import Expander from '../../components/expander'; +// import Select from '../../components/select'; +import Range from '../../components/range'; +// import linkState from 'linkstate'; + +interface Props { + options: EncodeOptions; + onChange(newOptions: EncodeOptions): void; +} + +interface State { + showAdvanced: boolean; +} + +export default class WP2EncoderOptions extends Component { + state: State = { + showAdvanced: false, + }; + + @bind + onChange(event: Event) { + const form = (event.currentTarget as HTMLInputElement).closest( + 'form', + ) as HTMLFormElement; + const { options } = this.props; + const newOptions: EncodeOptions = { + quality: inputFieldValueAsNumber(form.quality, options.quality), + alpha_quality: inputFieldValueAsNumber(form.alpha_quality, options.alpha_quality), + speed: inputFieldValueAsNumber(form.speed, options.speed), + pass: inputFieldValueAsNumber(form.pass, options.pass), + sns: inputFieldValueAsNumber(form.sns, options.sns), + + }; + this.props.onChange(newOptions); + } + + render({ options }: Props) { + return ( +
+
+ + Quality: + +
+
+ + Alpha Quality: + +
+
+ + Speed: + +
+
+ + Pass: + +
+
+ + Spatial noise shaping: + +
+
+ ); + } +} diff --git a/src/components/Options/index.tsx b/src/components/Options/index.tsx index cb63498b..820e2dc4 100644 --- a/src/components/Options/index.tsx +++ b/src/components/Options/index.tsx @@ -7,6 +7,7 @@ import OxiPNGEncoderOptions from '../../codecs/oxipng/options'; import MozJpegEncoderOptions from '../../codecs/mozjpeg/options'; import BrowserJPEGEncoderOptions from '../../codecs/browser-jpeg/options'; import WebPEncoderOptions from '../../codecs/webp/options'; +import WP2EncoderOptions from '../../codecs/wp2/options'; import AvifEncoderOptions from '../../codecs/avif/options'; import JXLEncoderOptions from '../../codecs/jxl/options'; import BrowserWebPEncoderOptions from '../../codecs/browser-webp/options'; @@ -18,6 +19,7 @@ import * as identity from '../../codecs/identity/encoder-meta'; import * as oxiPNG from '../../codecs/oxipng/encoder-meta'; import * as mozJPEG from '../../codecs/mozjpeg/encoder-meta'; import * as webP from '../../codecs/webp/encoder-meta'; +import * as wp2 from '../../codecs/wp2/encoder-meta'; import * as avif from '../../codecs/avif/encoder-meta'; import * as jxl from '../../codecs/jxl/encoder-meta'; import * as browserPNG from '../../codecs/browser-png/encoder-meta'; @@ -51,6 +53,7 @@ const encoderOptionsComponentMap: { [oxiPNG.type]: OxiPNGEncoderOptions, [mozJPEG.type]: MozJpegEncoderOptions, [webP.type]: WebPEncoderOptions, + [wp2.type]: WP2EncoderOptions, [avif.type]: AvifEncoderOptions, [jxl.type]: JXLEncoderOptions, [browserPNG.type]: undefined, diff --git a/src/components/compress/index.tsx b/src/components/compress/index.tsx index 27c64cb0..304673ab 100644 --- a/src/components/compress/index.tsx +++ b/src/components/compress/index.tsx @@ -10,6 +10,7 @@ import * as identity from '../../codecs/identity/encoder-meta'; import * as oxiPNG from '../../codecs/oxipng/encoder-meta'; import * as mozJPEG from '../../codecs/mozjpeg/encoder-meta'; import * as webP from '../../codecs/webp/encoder-meta'; +import * as wp2 from '../../codecs/wp2/encoder-meta'; import * as avif from '../../codecs/avif/encoder-meta'; import * as jxl from '../../codecs/jxl/encoder-meta'; import * as browserPNG from '../../codecs/browser-png/encoder-meta'; @@ -138,6 +139,7 @@ async function compressImage( case oxiPNG.type: return processor.oxiPngEncode(image, encodeData.options); case mozJPEG.type: return processor.mozjpegEncode(image, encodeData.options); case webP.type: return processor.webpEncode(image, encodeData.options); + case wp2.type: return processor.wp2Encode(image, encodeData.options); case avif.type: return processor.avifEncode(image, encodeData.options); case jxl.type: return processor.jxlEncode(image, encodeData.options); case browserPNG.type: return processor.browserPngEncode(image); diff --git a/src/lib/util.ts b/src/lib/util.ts index 552d7277..e278247c 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -129,6 +129,7 @@ const magicNumberToMimeType = new Map([ [/^II*/, 'image/tiff'], [/^MM\x00*/, 'image/tiff'], [/^RIFF....WEBPVP8[LX ]/, 'image/webp'], + [/\xF4\xFF\x6F/, 'image/webp2'], // Not sure about this one tho lol [/^\x00\x00\x00 ftypavif\x00\x00\x00\x00/, 'image/avif'], [/^\xff\x0a/, 'image/jpegxl'], ]);