diff --git a/packages/gif/codec/Makefile b/packages/gif/codec/Makefile new file mode 100644 index 0000000..974fa5e --- /dev/null +++ b/packages/gif/codec/Makefile @@ -0,0 +1,62 @@ +# using giflib from https://sourceforge.net/projects/giflib/ +GIFLIB_URL = https://sourceforge.net/projects/giflib/files/giflib-5.2.2.tar.gz/download +GIFLIB_PACKAGE = node_modules/giflib.tar.gz + +GIFLIB_DIR = node_modules/giflib +BUILD_DIR = node_modules/build + +OUT_DEC_JS = dec/gif_dec.js +OUT_DEC_CPP = dec/gif_dec.cpp +OUT_DEC_WASM = dec/gif_dec.wasm +ENVIRONMENT = web,worker + +PRE_JS = pre.js + +GIFLIB_OUT = $(BUILD_DIR)/libgif.a + +STACK_SIZE = 5242880 +INITIAL_MEMORY_SIZE = 16777216 + +.PHONY: all clean + +all: $(OUT_DEC_JS) + +# Build giflib into a static archive +$(GIFLIB_OUT): $(GIFLIB_DIR)/gif_lib.h + mkdir -p $(BUILD_DIR) + $(CC) $(CFLAGS) -I $(GIFLIB_DIR) -c $(GIFLIB_DIR)/dgif_lib.c -o $(BUILD_DIR)/dgif_lib.o + $(CC) $(CFLAGS) -I $(GIFLIB_DIR) -c $(GIFLIB_DIR)/gifalloc.c -o $(BUILD_DIR)/gifalloc.o + $(CC) $(CFLAGS) -I $(GIFLIB_DIR) -c $(GIFLIB_DIR)/gif_err.c -o $(BUILD_DIR)/gif_err.o + $(CC) $(CFLAGS) -I $(GIFLIB_DIR) -c $(GIFLIB_DIR)/openbsd-reallocarray.c -o $(BUILD_DIR)/openbsd-reallocarray.o + $(AR) rcs $@ $(BUILD_DIR)/dgif_lib.o $(BUILD_DIR)/gifalloc.o $(BUILD_DIR)/gif_err.o $(BUILD_DIR)/openbsd-reallocarray.o + +# Build the final decoder JS+WASM +$(OUT_DEC_JS): $(OUT_DEC_CPP) $(GIFLIB_OUT) + $(CXX) \ + -I $(GIFLIB_DIR) \ + $(CXXFLAGS) \ + $(LDFLAGS) \ + --pre-js $(PRE_JS) \ + --bind \ + -s ERROR_ON_UNDEFINED_SYMBOLS=0 \ + -s ENVIRONMENT=$(ENVIRONMENT) \ + -s EXPORT_ES6=1 \ + -s DYNAMIC_EXECUTION=0 \ + -s MODULARIZE=1 \ + -s STACK_SIZE=$(STACK_SIZE) \ + -s INITIAL_MEMORY=$(INITIAL_MEMORY_SIZE) \ + -o $@ \ + $+ + +# Download and extract giflib +$(GIFLIB_PACKAGE): + mkdir -p $(@D) + curl -sL $(GIFLIB_URL) -o $@ + +$(GIFLIB_DIR)/gif_lib.h: $(GIFLIB_PACKAGE) + mkdir -p $(@D) + tar xzm --strip 1 -C $(@D) -f $(GIFLIB_PACKAGE) + +clean: + $(RM) $(OUT_DEC_JS) $(OUT_DEC_WASM) + $(RM) -r $(BUILD_DIR) diff --git a/packages/gif/codec/dec/gif_dec.cpp b/packages/gif/codec/dec/gif_dec.cpp new file mode 100644 index 0000000..9ec5f9d --- /dev/null +++ b/packages/gif/codec/dec/gif_dec.cpp @@ -0,0 +1,254 @@ +#include +#include +#include +#include +#include "gif_lib.h" + +using namespace emscripten; + +thread_local const val Uint8ClampedArray = val::global("Uint8ClampedArray"); +thread_local const val ImageData = val::global("ImageData"); +thread_local const val Array = val::global("Array"); +thread_local const val Object = val::global("Object"); + +struct MemoryReader { + const uint8_t* data; + size_t size; + size_t pos; +}; + +int readFromMemory(GifFileType* gif, GifByteType* buf, int len) { + MemoryReader* reader = static_cast(gif->UserData); + size_t remaining = reader->size - reader->pos; + size_t to_read = (size_t)len < remaining ? (size_t)len : remaining; + memcpy(buf, reader->data + reader->pos, to_read); + reader->pos += to_read; + return (int)to_read; +} + +GifFileType* openGif(MemoryReader* reader, int* error) { + GifFileType* gif = DGifOpen(reader, readFromMemory, error); + if (!gif) return nullptr; + if (DGifSlurp(gif) != GIF_OK) { + DGifCloseFile(gif, error); + return nullptr; + } + return gif; +} + +// Get frame disposal method and delay from Graphics Control Extension +struct FrameInfo { + int transparentIndex; + int disposalMethod; + int delay; // in centiseconds (1/100th of a second) +}; + +FrameInfo getFrameInfo(SavedImage* frame) { + FrameInfo info; + info.transparentIndex = -1; + info.disposalMethod = 0; + info.delay = 0; + + for (int i = 0; i < frame->ExtensionBlockCount; i++) { + ExtensionBlock* eb = &frame->ExtensionBlocks[i]; + if (eb->Function == GRAPHICS_EXT_FUNC_CODE && eb->ByteCount >= 4) { + info.disposalMethod = (eb->Bytes[0] >> 2) & 0x07; + info.delay = (unsigned char)eb->Bytes[1] | ((unsigned char)eb->Bytes[2] << 8); + if (eb->Bytes[0] & 0x01) { + info.transparentIndex = (unsigned char)eb->Bytes[3]; + } + } + } + return info; +} + +void renderFrame(uint8_t* rgba, int canvasWidth, int canvasHeight, + SavedImage* frame, ColorMapObject* globalColorMap, + int transparentIndex) { + GifImageDesc* desc = &frame->ImageDesc; + ColorMapObject* colorMap = desc->ColorMap ? desc->ColorMap : globalColorMap; + if (!colorMap) return; + + for (int y = 0; y < desc->Height; y++) { + int destY = desc->Top + y; + if (destY < 0 || destY >= canvasHeight) continue; + for (int x = 0; x < desc->Width; x++) { + int destX = desc->Left + x; + if (destX < 0 || destX >= canvasWidth) continue; + + int colorIndex = frame->RasterBits[y * desc->Width + x]; + if (colorIndex == transparentIndex) continue; + if (colorIndex >= colorMap->ColorCount) continue; + + GifColorType color = colorMap->Colors[colorIndex]; + size_t offset = ((size_t)destY * canvasWidth + destX) * 4; + rgba[offset] = color.Red; + rgba[offset + 1] = color.Green; + rgba[offset + 2] = color.Blue; + rgba[offset + 3] = 255; + } + } +} + +void initCanvas(uint8_t* rgba, int width, int height, GifFileType* gif) { + size_t total = (size_t)width * height * 4; + ColorMapObject* colorMap = gif->SColorMap; + if (colorMap && gif->SBackGroundColor < colorMap->ColorCount) { + GifColorType bg = colorMap->Colors[gif->SBackGroundColor]; + for (size_t i = 0; i < total; i += 4) { + rgba[i] = bg.Red; + rgba[i + 1] = bg.Green; + rgba[i + 2] = bg.Blue; + rgba[i + 3] = 255; + } + } else { + memset(rgba, 0, total); + } +} + +void clearFrameArea(uint8_t* rgba, int canvasWidth, int canvasHeight, + SavedImage* frame, GifFileType* gif) { + GifImageDesc* desc = &frame->ImageDesc; + ColorMapObject* colorMap = gif->SColorMap; + for (int y = 0; y < desc->Height; y++) { + int destY = desc->Top + y; + if (destY < 0 || destY >= canvasHeight) continue; + for (int x = 0; x < desc->Width; x++) { + int destX = desc->Left + x; + if (destX < 0 || destX >= canvasWidth) continue; + size_t offset = ((size_t)destY * canvasWidth + destX) * 4; + if (colorMap && gif->SBackGroundColor < colorMap->ColorCount) { + GifColorType bg = colorMap->Colors[gif->SBackGroundColor]; + rgba[offset] = bg.Red; + rgba[offset + 1] = bg.Green; + rgba[offset + 2] = bg.Blue; + rgba[offset + 3] = 255; + } else { + rgba[offset] = 0; + rgba[offset + 1] = 0; + rgba[offset + 2] = 0; + rgba[offset + 3] = 0; + } + } + } +} + +val decode(std::string gifimage) { + MemoryReader reader = { + reinterpret_cast(gifimage.c_str()), + gifimage.length(), 0 + }; + + int error = 0; + GifFileType* gif = openGif(&reader, &error); + if (!gif || gif->ImageCount < 1) return val::null(); + + int width = gif->SWidth; + int height = gif->SHeight; + size_t total_bytes = (size_t)width * height * 4; + + uint8_t* rgba = static_cast(malloc(total_bytes)); + if (!rgba) { DGifCloseFile(gif, &error); return val::null(); } + + initCanvas(rgba, width, height, gif); + + FrameInfo info = getFrameInfo(&gif->SavedImages[0]); + renderFrame(rgba, width, height, &gif->SavedImages[0], + gif->SColorMap, info.transparentIndex); + + val result = ImageData.new_( + Uint8ClampedArray.new_(typed_memory_view(total_bytes, rgba)), + width, height); + + free(rgba); + DGifCloseFile(gif, &error); + return result; +} + +val decodeAnimated(std::string gifimage) { + MemoryReader reader = { + reinterpret_cast(gifimage.c_str()), + gifimage.length(), 0 + }; + + int error = 0; + GifFileType* gif = openGif(&reader, &error); + if (!gif || gif->ImageCount < 1) return val::null(); + + int width = gif->SWidth; + int height = gif->SHeight; + size_t total_bytes = (size_t)width * height * 4; + + uint8_t* canvas = static_cast(malloc(total_bytes)); + uint8_t* prevCanvas = static_cast(malloc(total_bytes)); + if (!canvas || !prevCanvas) { + free(canvas); + free(prevCanvas); + DGifCloseFile(gif, &error); + return val::null(); + } + + initCanvas(canvas, width, height, gif); + + val frames = Array.new_(); + + for (int i = 0; i < gif->ImageCount; i++) { + SavedImage* frame = &gif->SavedImages[i]; + FrameInfo info = getFrameInfo(frame); + + // Save canvas state before rendering (for restore-to-previous disposal) + memcpy(prevCanvas, canvas, total_bytes); + + renderFrame(canvas, width, height, frame, gif->SColorMap, info.transparentIndex); + + // Default delay of 100ms if not specified or zero + int delayMs = info.delay > 0 ? info.delay * 10 : 100; + + val imageData = ImageData.new_( + Uint8ClampedArray.new_(typed_memory_view(total_bytes, canvas)), + width, height); + + val frameObj = Object.new_(); + frameObj.set("imageData", imageData); + frameObj.set("duration", delayMs); + + frames.call("push", frameObj); + + // Apply disposal method for next frame + switch (info.disposalMethod) { + case 2: // Restore to background + clearFrameArea(canvas, width, height, frame, gif); + break; + case 3: // Restore to previous + memcpy(canvas, prevCanvas, total_bytes); + break; + // 0, 1: no disposal / do not dispose — leave canvas as-is + } + } + + free(canvas); + free(prevCanvas); + DGifCloseFile(gif, &error); + return frames; +} + +bool isAnimated(std::string gifimage) { + MemoryReader reader = { + reinterpret_cast(gifimage.c_str()), + gifimage.length(), 0 + }; + + int error = 0; + GifFileType* gif = openGif(&reader, &error); + if (!gif) return false; + + bool animated = gif->ImageCount > 1; + DGifCloseFile(gif, &error); + return animated; +} + +EMSCRIPTEN_BINDINGS(my_module) { + function("decode", &decode); + function("decodeAnimated", &decodeAnimated); + function("isAnimated", &isAnimated); +} diff --git a/packages/gif/codec/dec/gif_dec.d.ts b/packages/gif/codec/dec/gif_dec.d.ts new file mode 100644 index 0000000..8f6b8a2 --- /dev/null +++ b/packages/gif/codec/dec/gif_dec.d.ts @@ -0,0 +1,14 @@ +export interface GIFFrame { + imageData: ImageData; + duration: number; +} + +export interface GIFModule extends EmscriptenWasm.Module { + decode(data: BufferSource): ImageData | null; + decodeAnimated(data: BufferSource): GIFFrame[] | null; + isAnimated(data: BufferSource): boolean; +} + +declare var moduleFactory: EmscriptenWasm.ModuleFactory; + +export default moduleFactory; diff --git a/packages/gif/codec/dec/gif_dec.js b/packages/gif/codec/dec/gif_dec.js new file mode 100644 index 0000000..912246d --- /dev/null +++ b/packages/gif/codec/dec/gif_dec.js @@ -0,0 +1,15 @@ + +var Module = (() => { + var _scriptDir = import.meta.url; + + return ( +function(moduleArg = {}) { + +var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;var readyPromise=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject});const isServiceWorker=globalThis.ServiceWorkerGlobalScope!==undefined;const isRunningInCloudFlareWorkers=isServiceWorker&&typeof self!=="undefined"&&globalThis.caches&&globalThis.caches.default!==undefined;const isRunningInNode=typeof process==="object"&&process.release&&process.release.name==="node";if(isRunningInCloudFlareWorkers||isRunningInNode){if(!globalThis.ImageData){globalThis.ImageData=class ImageData{constructor(data,width,height){this.data=data;this.width=width;this.height=height}}}if(import.meta.url===undefined){import.meta.url="https://localhost"}if(typeof self!=="undefined"&&self.location===undefined){self.location={href:""}}}var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary;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.startsWith("blob:")){scriptDirectory=""}else{scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var wasmMemory;var ABORT=false;var EXITSTATUS;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b)}var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=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 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++;Module["monitorRunDependencies"]?.(runDependencies)}function removeRunDependency(id){runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";var isDataURI=filename=>filename.startsWith(dataURIPrefix);var wasmBinaryFile;if(Module["locateFile"]){wasmBinaryFile="gif_dec.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}}else{wasmBinaryFile=new URL("gif_dec.wasm",import.meta.url).href}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}function getBinaryPromise(binaryFile){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch=="function"){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{if(!response["ok"]){throw`failed to load wasm binary file at '${binaryFile}'`}return response["arrayBuffer"]()}).catch(()=>getBinarySync(binaryFile))}}return Promise.resolve().then(()=>getBinarySync(binaryFile))}function instantiateArrayBuffer(binaryFile,imports,receiver){return getBinaryPromise(binaryFile).then(binary=>WebAssembly.instantiate(binary,imports)).then(receiver,reason=>{err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)})}function instantiateAsync(binary,binaryFile,imports,callback){if(!binary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(binaryFile)&&typeof fetch=="function"){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{var result=WebAssembly.instantiateStreaming(response,imports);return result.then(callback,function(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(binaryFile,imports,callback)})})}return instantiateArrayBuffer(binaryFile,imports,callback)}function createWasm(){var info={"a":wasmImports};function receiveInstance(instance,module){wasmExports=instance.exports;wasmMemory=wasmExports["w"];updateMemoryViews();wasmTable=wasmExports["B"];addOnInit(wasmExports["x"]);removeRunDependency("wasm-instantiate");return wasmExports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}if(Module["instantiateWasm"]){try{return Module["instantiateWasm"](info,receiveInstance)}catch(e){err(`Module.instantiateWasm callback failed with error: ${e}`);readyPromiseReject(e)}}instantiateAsync(wasmBinary,wasmBinaryFile,info,receiveInstantiationResult).catch(readyPromiseReject);return{}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var noExitRuntime=Module["noExitRuntime"]||true;var __embind_register_bigint=(primitiveType,name,size,minRange,maxRange)=>{};var embind_init_charCodes=()=>{var codes=new Array(256);for(var i=0;i<256;++i){codes[i]=String.fromCharCode(i)}embind_charCodes=codes};var embind_charCodes;var readLatin1String=ptr=>{var ret="";var c=ptr;while(HEAPU8[c]){ret+=embind_charCodes[HEAPU8[c++]]}return ret};var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var BindingError;var throwBindingError=message=>{throw new BindingError(message)};var InternalError;var throwInternalError=message=>{throw new InternalError(message)};var 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{if(registeredTypes.hasOwnProperty(dt)){typeConverters[i]=registeredTypes[dt]}else{unregisteredTypes.push(dt);if(!awaitingDependencies.hasOwnProperty(dt)){awaitingDependencies[dt]=[]}awaitingDependencies[dt].push(()=>{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}});if(0===unregisteredTypes.length){onComplete(typeConverters)}};function sharedRegisterType(rawType,registeredInstance,options={}){var name=registeredInstance.name;if(!rawType){throwBindingError(`type "${name}" must have a positive integer typeid pointer`)}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError(`Cannot register type '${name}' twice`)}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function registerType(rawType,registeredInstance,options={}){if(!("argPackAdvance"in registeredInstance)){throw new TypeError("registerType registeredInstance requires argPackAdvance")}return sharedRegisterType(rawType,registeredInstance,options)}var GenericWireTypeSize=8;var __embind_register_bool=(rawType,name,trueValue,falseValue)=>{name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(wt){return!!wt},"toWireType":function(destructors,o){return o?trueValue:falseValue},"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":function(pointer){return this["fromWireType"](HEAPU8[pointer])},destructorFunction:null})};var emval_freelist=[];var emval_handles=[];var __emval_decref=handle=>{if(handle>9&&0===--emval_handles[handle+1]){emval_handles[handle]=undefined;emval_freelist.push(handle)}};var count_emval_handles=()=>emval_handles.length/2-5-emval_freelist.length;var init_emval=()=>{emval_handles.push(0,1,undefined,1,null,1,true,1,false,1);Module["count_emval_handles"]=count_emval_handles};var Emval={toValue:handle=>{if(!handle){throwBindingError("Cannot use deleted val. handle = "+handle)}return emval_handles[handle]},toHandle:value=>{switch(value){case undefined:return 2;case null:return 4;case true:return 6;case false:return 8;default:{const handle=emval_freelist.pop()||emval_handles.length;emval_handles[handle]=value;emval_handles[handle+1]=1;return handle}}}};function readPointer(pointer){return this["fromWireType"](HEAPU32[pointer>>2])}var EmValType={name:"emscripten::val","fromWireType":handle=>{var rv=Emval.toValue(handle);__emval_decref(handle);return rv},"toWireType":(destructors,value)=>Emval.toHandle(value),"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":readPointer,destructorFunction:null};var __embind_register_emval=rawType=>registerType(rawType,EmValType);var floatReadValueFromPointer=(name,width)=>{switch(width){case 4:return function(pointer){return this["fromWireType"](HEAPF32[pointer>>2])};case 8:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError(`invalid float width (${width}): ${name}`)}};var __embind_register_float=(rawType,name,size)=>{name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":value=>value,"toWireType":(destructors,value)=>value,"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":floatReadValueFromPointer(name,size),destructorFunction:null})};var createNamedFunction=(name,body)=>Object.defineProperty(body,"name",{value:name});var runDestructors=destructors=>{while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}};function usesDestructorStack(argTypes){for(var i=1;i{if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(...args){if(!proto[methodName].overloadTable.hasOwnProperty(args.length)){throwBindingError(`Function '${humanName}' called with an invalid number of arguments (${args.length}) - expects one of (${proto[methodName].overloadTable})!`)}return proto[methodName].overloadTable[args.length].apply(this,args)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}};var exposePublicSymbol=(name,value,numArguments)=>{if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError(`Cannot register public name '${name}' twice`)}ensureOverloadTable(Module,name,name);if(Module.hasOwnProperty(numArguments)){throwBindingError(`Cannot register multiple overloads of a function with the same number of arguments (${numArguments})!`)}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;if(undefined!==numArguments){Module[name].numArguments=numArguments}}};var heap32VectorToArray=(count,firstElement)=>{var array=[];for(var i=0;i>2])}return array};var replacePublicSymbol=(name,value,numArguments)=>{if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistent public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var dynCallLegacy=(sig,ptr,args)=>{sig=sig.replace(/p/g,"i");var f=Module["dynCall_"+sig];return f(ptr,...args)};var wasmTable;var getWasmTableEntry=funcPtr=>wasmTable.get(funcPtr);var dynCall=(sig,ptr,args=[])=>{if(sig.includes("j")){return dynCallLegacy(sig,ptr,args)}var rtn=getWasmTableEntry(ptr)(...args);return rtn};var getDynCaller=(sig,ptr)=>(...args)=>dynCall(sig,ptr,args);var embind__requireFunction=(signature,rawFunction)=>{signature=readLatin1String(signature);function makeDynCaller(){if(signature.includes("j")){return getDynCaller(signature,rawFunction)}return getWasmTableEntry(rawFunction)}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError(`unknown function pointer with signature ${signature}: ${rawFunction}`)}return fp};var 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 UnboundTypeError;var getTypeName=type=>{var ptr=___getTypeName(type);var rv=readLatin1String(ptr);_free(ptr);return rv};var throwUnboundTypeError=(message,types)=>{var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(`${message}: `+unboundTypes.map(getTypeName).join([", "]))};var getFunctionName=signature=>{signature=signature.trim();const argsIndex=signature.indexOf("(");if(argsIndex!==-1){return signature.substr(0,argsIndex)}else{return signature}};var __embind_register_function=(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn,isAsync)=>{var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=readLatin1String(name);name=getFunctionName(name);rawInvoker=embind__requireFunction(signature,rawInvoker);exposePublicSymbol(name,function(){throwUnboundTypeError(`Cannot call ${name} due to unbound types`,argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn,isAsync),argCount-1);return[]})};var integerReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?pointer=>HEAP8[pointer]:pointer=>HEAPU8[pointer];case 2:return signed?pointer=>HEAP16[pointer>>1]:pointer=>HEAPU16[pointer>>1];case 4:return signed?pointer=>HEAP32[pointer>>2]:pointer=>HEAPU32[pointer>>2];default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};var __embind_register_integer=(primitiveType,name,size,minRange,maxRange)=>{name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var fromWireType=value=>value;if(minRange===0){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift}var isUnsignedType=name.includes("unsigned");var checkAssertions=(value,toTypeName)=>{};var toWireType;if(isUnsignedType){toWireType=function(destructors,value){checkAssertions(value,this.name);return value>>>0}}else{toWireType=function(destructors,value){checkAssertions(value,this.name);return value}}registerType(primitiveType,{name:name,"fromWireType":fromWireType,"toWireType":toWireType,"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":integerReadValueFromPointer(name,size,minRange!==0),destructorFunction:null})};var __embind_register_memory_view=(rawType,dataTypeIndex,name)=>{var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){var size=HEAPU32[handle>>2];var data=HEAPU32[handle+4>>2];return new TA(HEAP8.buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":decodeMemoryView,"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":decodeMemoryView},{ignoreDuplicateRegistrations:true})};var 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};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var UTF8ArrayToString=(heapOrArray,idx,maxBytesToRead)=>{var endIdx=idx+maxBytesToRead;var str="";while(!(idx>=endIdx)){var u0=heapOrArray[idx++];if(!u0)return str;if(!(u0&128)){str+=String.fromCharCode(u0);continue}var u1=heapOrArray[idx++]&63;if((u0&224)==192){str+=String.fromCharCode((u0&31)<<6|u1);continue}var u2=heapOrArray[idx++]&63;if((u0&240)==224){u0=(u0&15)<<12|u1<<6|u2}else{u0=(u0&7)<<18|u1<<12|u2<<6|heapOrArray[idx++]&63}if(u0<65536){str+=String.fromCharCode(u0)}else{var ch=u0-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):"";var __embind_register_std_string=(rawType,name)=>{name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,"fromWireType"(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){var decodeStartPtr=payload;for(var i=0;i<=length;++i){var currentBytePtr=payload+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,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+i]=charCode}}else{for(var i=0;i{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};var stringToUTF16=(str,outPtr,maxBytesToWrite)=>{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};var lengthBytesUTF16=str=>str.length*2;var 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};var stringToUTF32=(str,outPtr,maxBytesToWrite)=>{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};var lengthBytesUTF32=str=>{var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len};var __embind_register_std_wstring=(rawType,charSize,name)=>{name=readLatin1String(name);var decodeString,encodeString,readCharAt,lengthBytesUTF;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16;readCharAt=pointer=>HEAPU16[pointer>>1]}else if(charSize===4){decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32;readCharAt=pointer=>HEAPU32[pointer>>2]}registerType(rawType,{name:name,"fromWireType":value=>{var length=HEAPU32[value>>2];var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||readCharAt(currentBytePtr)==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":(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/charSize;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":readPointer,destructorFunction(ptr){_free(ptr)}})};var __embind_register_void=(rawType,name)=>{name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,"argPackAdvance":0,"fromWireType":()=>undefined,"toWireType":(destructors,o)=>undefined})};var emval_methodCallers=[];var __emval_call=(caller,handle,destructorsRef,args)=>{caller=emval_methodCallers[caller];handle=Emval.toValue(handle);return caller(null,handle,destructorsRef,args)};var emval_symbols={};var getStringOrSymbol=address=>{var symbol=emval_symbols[address];if(symbol===undefined){return readLatin1String(address)}return symbol};var __emval_call_method=(caller,objHandle,methodName,destructorsRef,args)=>{caller=emval_methodCallers[caller];objHandle=Emval.toValue(objHandle);methodName=getStringOrSymbol(methodName);return caller(objHandle,objHandle[methodName],destructorsRef,args)};var 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.")};var __emval_get_global=name=>{if(name===0){return Emval.toHandle(emval_get_global())}else{name=getStringOrSymbol(name);return Emval.toHandle(emval_get_global()[name])}};var emval_addMethodCaller=caller=>{var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id};var requireRegisteredType=(rawType,humanName)=>{var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(`${humanName} has unknown type ${getTypeName(rawType)}`)}return impl};var emval_lookupTypes=(argCount,argTypes)=>{var a=new Array(argCount);for(var i=0;i>2],"parameter "+i)}return a};var reflectConstruct=Reflect.construct;var emval_returnValue=(returnType,destructorsRef,handle)=>{var destructors=[];var result=returnType["toWireType"](destructors,handle);if(destructors.length){HEAPU32[destructorsRef>>2]=Emval.toHandle(destructors)}return result};var __emval_get_method_caller=(argCount,argTypes,kind)=>{var types=emval_lookupTypes(argCount,argTypes);var retType=types.shift();argCount--;var argN=new Array(argCount);var invokerFunction=(obj,func,destructorsRef,args)=>{var offset=0;for(var i=0;it.name).join(", ")}) => ${retType.name}>`;return emval_addMethodCaller(createNamedFunction(functionName,invokerFunction))};var __emval_incref=handle=>{if(handle>9){emval_handles[handle+1]+=1}};var __emval_new_cstring=v=>Emval.toHandle(getStringOrSymbol(v));var __emval_run_destructors=handle=>{var destructors=Emval.toValue(handle);runDestructors(destructors);__emval_decref(handle)};var __emval_set_property=(handle,key,value)=>{handle=Emval.toValue(handle);key=Emval.toValue(key);value=Emval.toValue(value);handle[key]=value};var __emval_take_value=(type,arg)=>{type=requireRegisteredType(type,"_emval_take_value");var v=type["readValueFromPointer"](arg);return Emval.toHandle(v)};var _abort=()=>{abort("")};var getHeapMax=()=>2147483648;var growMemory=size=>{var b=wasmMemory.buffer;var pages=(size-b.byteLength+65535)/65536;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}var alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;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=growMemory(newSize);if(replacement){return true}}return false};embind_init_charCodes();BindingError=Module["BindingError"]=class BindingError extends Error{constructor(message){super(message);this.name="BindingError"}};InternalError=Module["InternalError"]=class InternalError extends Error{constructor(message){super(message);this.name="InternalError"}};init_emval();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");var wasmImports={m:__embind_register_bigint,k:__embind_register_bool,j:__embind_register_emval,f:__embind_register_float,h:__embind_register_function,b:__embind_register_integer,a:__embind_register_memory_view,g:__embind_register_std_string,d:__embind_register_std_wstring,l:__embind_register_void,e:__emval_call,p:__emval_call_method,u:__emval_decref,v:__emval_get_global,c:__emval_get_method_caller,q:__emval_incref,s:__emval_new_cstring,t:__emval_run_destructors,i:__emval_set_property,r:__emval_take_value,n:_abort,o:_emscripten_resize_heap};var wasmExports=createWasm();var ___wasm_call_ctors=()=>(___wasm_call_ctors=wasmExports["x"])();var ___getTypeName=a0=>(___getTypeName=wasmExports["y"])(a0);var _malloc=a0=>(_malloc=wasmExports["z"])(a0);var _free=a0=>(_free=wasmExports["A"])(a0);var __emscripten_stack_restore=a0=>(__emscripten_stack_restore=wasmExports["_emscripten_stack_restore"])(a0);var __emscripten_stack_alloc=a0=>(__emscripten_stack_alloc=wasmExports["_emscripten_stack_alloc"])(a0);var _emscripten_stack_get_current=()=>(_emscripten_stack_get_current=wasmExports["emscripten_stack_get_current"])();var ___cxa_increment_exception_refcount=a0=>(___cxa_increment_exception_refcount=wasmExports["__cxa_increment_exception_refcount"])(a0);var ___cxa_is_pointer_type=a0=>(___cxa_is_pointer_type=wasmExports["__cxa_is_pointer_type"])(a0);var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(){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()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run(); + + + return readyPromise +} +); +})(); +export default Module; \ No newline at end of file diff --git a/packages/gif/codec/dec/gif_dec.wasm b/packages/gif/codec/dec/gif_dec.wasm new file mode 100755 index 0000000..e8e05c9 Binary files /dev/null and b/packages/gif/codec/dec/gif_dec.wasm differ diff --git a/packages/gif/codec/package.json b/packages/gif/codec/package.json new file mode 100644 index 0000000..4aa1529 --- /dev/null +++ b/packages/gif/codec/package.json @@ -0,0 +1,6 @@ +{ + "scripts": { + "build": "EMSDK_VERSION=3.1.57 DEFAULT_CFLAGS='-Oz -flto' ../../../tools/build-cpp.sh" + }, + "type": "module" +} diff --git a/packages/gif/codec/pre.js b/packages/gif/codec/pre.js new file mode 100644 index 0000000..073dd63 --- /dev/null +++ b/packages/gif/codec/pre.js @@ -0,0 +1,24 @@ +const isServiceWorker = globalThis.ServiceWorkerGlobalScope !== undefined; +const isRunningInCloudFlareWorkers = isServiceWorker && typeof self !== 'undefined' && globalThis.caches && globalThis.caches.default !== undefined; +const isRunningInNode = typeof process === 'object' && process.release && process.release.name === 'node'; + +if (isRunningInCloudFlareWorkers || isRunningInNode) { + if (!globalThis.ImageData) { + // Simple Polyfill for ImageData Object + globalThis.ImageData = class ImageData { + constructor(data, width, height) { + this.data = data; + this.width = width; + this.height = height; + } + }; + } + + if (import.meta.url === undefined) { + import.meta.url = 'https://localhost'; + } + + if (typeof self !== 'undefined' && self.location === undefined) { + self.location = { href: '' }; + } +} diff --git a/packages/gif/decode.ts b/packages/gif/decode.ts new file mode 100644 index 0000000..cac9449 --- /dev/null +++ b/packages/gif/decode.ts @@ -0,0 +1,82 @@ +import type { GIFModule, GIFFrame } from './codec/dec/gif_dec.js'; +import { initEmscriptenModule } from './utils.js'; + +import gif_dec from './codec/dec/gif_dec.js'; + +export type { GIFFrame }; + +function validateGif(buffer: ArrayBuffer): void { + const header = new Uint8Array(buffer, 0, 6); + const sig = String.fromCharCode(...header); + if (sig !== 'GIF87a' && sig !== 'GIF89a') { + throw new Error( + `Not a valid GIF file (expected GIF87a/GIF89a header, got "${sig.replace(/[^\x20-\x7E]/g, '?')}")`, + ); + } +} + +let emscriptenModule: Promise; + +export async function init( + moduleOptionOverrides?: Partial, +): Promise; +export async function init( + module?: WebAssembly.Module, + moduleOptionOverrides?: Partial, +): Promise { + let actualModule: WebAssembly.Module | undefined = module; + let actualOptions: Partial | undefined = + moduleOptionOverrides; + + // If only one argument is provided and it's not a WebAssembly.Module + if (arguments.length === 1 && !(module instanceof WebAssembly.Module)) { + actualModule = undefined; + actualOptions = module as unknown as Partial; + } + + emscriptenModule = initEmscriptenModule( + gif_dec, + actualModule, + actualOptions, + ); +} + +export default async function decode( + buffer: ArrayBuffer, +): Promise { + validateGif(buffer); + if (!emscriptenModule) { + init(); + } + + const module = await emscriptenModule; + const result = module.decode(buffer); + if (!result) throw new Error('Decoding error'); + return result; +} + +export async function decodeAnimated( + buffer: ArrayBuffer, +): Promise { + validateGif(buffer); + if (!emscriptenModule) { + init(); + } + + const module = await emscriptenModule; + const result = module.decodeAnimated(buffer); + if (!result) throw new Error('Decoding error'); + return result; +} + +export async function isAnimated( + buffer: ArrayBuffer, +): Promise { + validateGif(buffer); + if (!emscriptenModule) { + init(); + } + + const module = await emscriptenModule; + return module.isAnimated(buffer); +} diff --git a/packages/gif/emscripten-types.d.ts b/packages/gif/emscripten-types.d.ts new file mode 100644 index 0000000..690ce36 --- /dev/null +++ b/packages/gif/emscripten-types.d.ts @@ -0,0 +1,137 @@ +// These types roughly model the object that the JS files generated by Emscripten define. Copied from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/emscripten/index.d.ts and turned into a type definition rather than a global to support our way of using Emscripten. +// TODO(@surma): Upstream this? +declare namespace EmscriptenWasm { + type ModuleFactory = ( + moduleOverrides?: ModuleOpts, + ) => Promise; + + type EnvironmentType = 'WEB' | 'NODE' | 'SHELL' | 'WORKER'; + + // Options object for modularized Emscripten files. Shoe-horned by @surma. + // FIXME: This an incomplete definition! + interface ModuleOpts { + mainScriptUrlOrBlob?: string; + noInitialRun?: boolean; + locateFile?: + | ((path: string) => string) + | ((path: string, prefix: string) => string); + onRuntimeInitialized?: () => void; + instantiateWasm?: ( + imports: WebAssembly.Imports, + successCallback: (module: WebAssembly.Module) => void, + ) => WebAssembly.Exports; + } + + interface Module { + print(str: string): void; + printErr(str: string): void; + arguments: string[]; + environment: EnvironmentType; + preInit: { (): void }[]; + preRun: { (): void }[]; + postRun: { (): void }[]; + preinitializedWebGLContext: WebGLRenderingContext; + noInitialRun: boolean; + noExitRuntime: boolean; + logReadFiles: boolean; + filePackagePrefixURL: string; + wasmBinary: ArrayBuffer; + + destroy(object: object): void; + getPreloadedPackage( + remotePackageName: string, + remotePackageSize: number, + ): ArrayBuffer; + instantiateWasm( + imports: WebAssembly.Imports, + successCallback: (module: WebAssembly.Module) => void, + ): WebAssembly.Exports; + locateFile(url: string): string; + onCustomMessage(event: MessageEvent): void; + + Runtime: any; + + ccall( + ident: string, + returnType: string | null, + argTypes: string[], + args: any[], + ): any; + cwrap(ident: string, returnType: string | null, argTypes: string[]): any; + + setValue(ptr: number, value: any, type: string, noSafe?: boolean): void; + getValue(ptr: number, type: string, noSafe?: boolean): number; + + ALLOC_NORMAL: number; + ALLOC_STACK: number; + ALLOC_STATIC: number; + ALLOC_DYNAMIC: number; + ALLOC_NONE: number; + + allocate(slab: any, types: string, allocator: number, ptr: number): number; + allocate( + slab: any, + types: string[], + allocator: number, + ptr: number, + ): number; + + Pointer_stringify(ptr: number, length?: number): string; + UTF16ToString(ptr: number): string; + stringToUTF16(str: string, outPtr: number): void; + UTF32ToString(ptr: number): string; + stringToUTF32(str: string, outPtr: number): void; + + // USE_TYPED_ARRAYS == 1 + HEAP: Int32Array; + IHEAP: Int32Array; + FHEAP: Float64Array; + + // USE_TYPED_ARRAYS == 2 + HEAP8: Int8Array; + HEAP16: Int16Array; + HEAP32: Int32Array; + HEAPU8: Uint8Array; + HEAPU16: Uint16Array; + HEAPU32: Uint32Array; + HEAPF32: Float32Array; + HEAPF64: Float64Array; + + TOTAL_STACK: number; + TOTAL_MEMORY: number; + FAST_MEMORY: number; + + addOnPreRun(cb: () => any): void; + addOnInit(cb: () => any): void; + addOnPreMain(cb: () => any): void; + addOnExit(cb: () => any): void; + addOnPostRun(cb: () => any): void; + + // Tools + intArrayFromString( + stringy: string, + dontAddNull?: boolean, + length?: number, + ): number[]; + intArrayToString(array: number[]): string; + writeStringToMemory( + str: string, + buffer: number, + dontAddNull: boolean, + ): void; + writeArrayToMemory(array: number[], buffer: number): void; + writeAsciiToMemory(str: string, buffer: number, dontAddNull: boolean): void; + + addRunDependency(id: any): void; + removeRunDependency(id: any): void; + + preloadedImages: any; + preloadedAudios: any; + + _malloc(size: number): number; + _free(ptr: number): void; + + // Augmentations below by @surma. + onRuntimeInitialized: () => void | null; + } +} diff --git a/packages/gif/index.ts b/packages/gif/index.ts new file mode 100644 index 0000000..7cecf23 --- /dev/null +++ b/packages/gif/index.ts @@ -0,0 +1,2 @@ +export { default as decode, decodeAnimated, isAnimated } from './decode.js'; +export type { GIFFrame } from './decode.js'; diff --git a/packages/gif/meta.ts b/packages/gif/meta.ts new file mode 100644 index 0000000..a2c903c --- /dev/null +++ b/packages/gif/meta.ts @@ -0,0 +1,3 @@ +export const label = 'GIF'; +export const mimeType = 'image/gif'; +export const extension = 'gif'; diff --git a/packages/gif/package.json b/packages/gif/package.json new file mode 100644 index 0000000..0561cac --- /dev/null +++ b/packages/gif/package.json @@ -0,0 +1,33 @@ +{ + "name": "@jsquash/gif", + "version": "1.0.0", + "main": "index.js", + "description": "Wasm GIF decoder supporting the browser. Decode GIF images to ImageData.", + "repository": "jamsinclair/jSquash", + "author": { + "name": "Jamie Sinclair", + "email": "jamsinclairnz+npm@gmail.com" + }, + "keywords": [ + "image", + "optimisation", + "optimization", + "squoosh", + "wasm", + "webassembly", + "gif" + ], + "license": "Apache-2.0", + "scripts": { + "clean": "rm -rf dist", + "build:codec": "cd codec && npm run build", + "build": "npm run clean && tsc && cp -r codec package.json README.md *.d.ts .npmignore ../../LICENSE dist && cd dist/codec", + "prepublishOnly": "[[ \"$PWD\" == *'/dist' ]] && exit 0 || (echo 'Please run npm publish from the dist directory' && exit 1)" + }, + "devDependencies": { + "@types/node": "^20.9.2", + "typescript": "^4.4.4" + }, + "type": "module", + "sideEffects": false +} diff --git a/packages/gif/tsconfig.json b/packages/gif/tsconfig.json new file mode 100644 index 0000000..7de4869 --- /dev/null +++ b/packages/gif/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2019", + "downlevelIteration": true, + "module": "esnext", + "jsx": "react", + "jsxFactory": "h", + "strict": true, + "moduleResolution": "node", + "composite": true, + "declarationMap": true, + "baseUrl": "./", + "rootDir": "./", + "outDir": "dist", + "allowSyntheticDefaultImports": true + } +} diff --git a/packages/gif/utils.ts b/packages/gif/utils.ts new file mode 100644 index 0000000..a56d4cd --- /dev/null +++ b/packages/gif/utils.ts @@ -0,0 +1,42 @@ +/** + * Copyright 2020 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Notice: I (Jamie Sinclair) have modified this file to allow manual instantiation of the Wasm Module. + */ + +export function initEmscriptenModule( + moduleFactory: EmscriptenWasm.ModuleFactory, + wasmModule?: WebAssembly.Module, + moduleOptionOverrides: Partial = {}, +): Promise { + let instantiateWasm; + + if (wasmModule) { + instantiateWasm = ( + imports: WebAssembly.Imports, + callback: (instance: WebAssembly.Instance) => void, + ) => { + const instance = new WebAssembly.Instance(wasmModule, imports); + callback(instance); + return instance.exports; + }; + } + + return moduleFactory({ + // Just to be safe, don't automatically invoke any wasm functions + noInitialRun: true, + instantiateWasm, + ...moduleOptionOverrides, + }); +}