1// 2// EMU_DITHERTIZER_converthgr.js 3// 4// Thin adapter around the bundled ConvertHGR browser worker source. The worker 5// source is loaded by the emulator as normal script data and then instantiated 6// as a Blob Worker so local file:// execution does not need runtime fetch(). 7// Backend is selected per conversion; unexpected fallback is reported. 8// 9 10function DithertizerConvertHGRAdapter(options) 11{ 12 options=options || {}; 13 14 var worker=null; 15 var workerBlobURL=null; 16 var initPromise=null; 17 var requestSeq=0; 18 var pending=null; 19 var settings=null; 20 var closed=false; 21 var backend=options.backend==="javascript" ? "javascript" : "wasm"; 22 23 function failPending(error) 24 { 25 if(!pending) return; 26 var p=pending; 27 pending=null; 28 p.reject(error instanceof Error ? error : new Error(String(error))); 29 } 30 31 function workerMessage(event) 32 { 33 var msg=event && event.data ? event.data : {}; 34 if(!pending || Number(msg.requestId)!==pending.requestId) 35 return; 36 37 if(msg.type==="progress") return; 38 39 if(msg.type==="error") 40 { 41 failPending(new Error("ConvertHGR worker: "+String(msg.message || msg.code || "conversion failed"))); 42 return; 43 } 44 45 if(msg.type!=="conversionResult") return; 46 47 var p=pending; 48 pending=null; 49 50 var metadata=msg.metadata || {}; 51 if(metadata.backendUsed!==p.backend) 52 { 53 p.reject(new Error("ConvertHGR "+(p.backend==="wasm" ? "WASM" : "JavaScript")+" backend required; worker used "+String(metadata.backendUsed || "unknown")+ 54 (metadata.fallbackReason ? " ("+metadata.fallbackReason+")" : ""))); 55 return; 56 } 57 58 var buffers=msg.buffers || {}; 59 var paletteBuffer=buffers.paletteImage; 60 if(!(paletteBuffer instanceof ArrayBuffer) || paletteBuffer.byteLength!==280*192) 61 { 62 p.reject(new Error("ConvertHGR worker returned an invalid 280x192 palette buffer")); 63 return; 64 } 65 66 p.resolve({ 67 width:280 68 ,height:192 69 ,backendUsed:p.backend 70 ,metadata:metadata 71 ,timing:msg.timing || {} 72 ,processedRGB:buffers.processedRGB instanceof ArrayBuffer ? new Uint8Array(buffers.processedRGB) : null 73 ,paletteIndex:new Uint8Array(paletteBuffer) 74 ,linearHGR:buffers.linearHgr instanceof ArrayBuffer ? new Uint8Array(buffers.linearHgr) : null 75 ,hgrPage:buffers.hgrPage instanceof ArrayBuffer ? new Uint8Array(buffers.hgrPage) : null 76 }); 77 } 78 79 this.init=function() 80 { 81 if(closed) return Promise.reject(new Error("ConvertHGR adapter is closed")); 82 if(worker) return Promise.resolve(this); 83 if(initPromise) return initPromise; 84 85 var workerSource=options.workerSource || 86 (typeof(CONVERTHGR_WORKER_SOURCE)!=="undefined" ? CONVERTHGR_WORKER_SOURCE : null); 87 var WorkerCtor=options.Worker || (typeof(Worker)!=="undefined" ? Worker : null); 88 var BlobCtor=options.Blob || (typeof(Blob)!=="undefined" ? Blob : null); 89 var URLApi=options.URL || (typeof(URL)!=="undefined" ? URL : null); 90 91 if(typeof(workerSource)!=="string" || workerSource.length===0) 92 return Promise.reject(new Error("ConvertHGR bundled worker source is unavailable")); 93 94 if(!WorkerCtor || !BlobCtor || !URLApi || typeof(URLApi.createObjectURL)!=="function") 95 return Promise.reject(new Error("ConvertHGR worker APIs are unavailable")); 96 97 var self=this; 98 initPromise=Promise.resolve().then(function() 99 { 100 var blob=new BlobCtor([workerSource],{type:"text/javascript"}); 101 workerBlobURL=URLApi.createObjectURL(blob); 102 worker=new WorkerCtor(workerBlobURL); 103 worker.onmessage=workerMessage; 104 worker.onerror=function(event) 105 { 106 failPending(new Error("ConvertHGR worker error: "+String(event && event.message || "worker failure"))); 107 }; 108 return self; 109 }).catch(function(error) 110 { 111 initPromise=null; 112 throw error; 113 }); 114 115 return initPromise; 116 }; 117 118 this.configure=function(nextSettings) 119 { 120 if(!nextSettings || typeof(nextSettings)!=="object") 121 throw new TypeError("ConvertHGR settings object required"); 122 settings=JSON.parse(JSON.stringify(nextSettings)); 123 return this; 124 }; 125 126 this.setBackend=function(value) 127 { 128 if(value!=="wasm" && value!=="javascript") throw new TypeError("Invalid ConvertHGR backend"); 129 backend=value; 130 return this; 131 }; 132 133 this.convert=function(rgb,width,height,seed) 134 { 135 if(closed) return Promise.reject(new Error("ConvertHGR adapter is closed")); 136 if(!worker) return Promise.reject(new Error("ConvertHGR adapter is not initialized")); 137 if(pending) return Promise.reject(new Error("ConvertHGR conversion already in flight")); 138 if(!settings) return Promise.reject(new Error("ConvertHGR adapter is not configured")); 139 140 width=Number(width)|0; 141 height=Number(height)|0; 142 if(width<=0 || height<=0) 143 return Promise.reject(new Error("ConvertHGR source dimensions must be positive")); 144
145 if(rgb instanceof ArrayBuffer) rgb=new Uint8Array(rgb); 146 else if(typeof(ArrayBuffer)!=="undefined" && typeof(ArrayBuffer.isView)==="function" && ArrayBuffer.isView(rgb)) 147 rgb=new Uint8Array(rgb.buffer,rgb.byteOffset,rgb.byteLength); 148 if(!rgb || rgb.byteLength!==width*height*3) 149 return Promise.reject(new Error("ConvertHGR source must be tightly packed RGB24")); 150 151 var transferBuffer; 152 if(rgb.byteOffset===0 && rgb.byteLength===rgb.buffer.byteLength) 153 transferBuffer=rgb.buffer; 154 else 155 transferBuffer=new Uint8Array(rgb).buffer; 156 157 var requestId=++requestSeq; 158 return new Promise(function(resolve,reject) 159 { 160 pending={requestId:requestId,resolve:resolve,reject:reject,backend:backend}; 161 try 162 { 163 worker.postMessage({ 164 type:"convert" 165 ,requestId:requestId 166 ,settings:settings 167 ,source:{rgbBuffer:transferBuffer,width:width,height:height} 168 ,randomSeed:Number(seed)>>>0 169 ,backend:backend 170 },[transferBuffer]); 171 } 172 catch(error) 173 { 174 pending=null; 175 reject(error); 176 } 177 }); 178 }; 179 180 this.close=function() 181 { 182 if(closed) return; 183 closed=true; 184 failPending(new Error("ConvertHGR adapter closed")); 185 if(worker && typeof(worker.terminate)==="function") worker.terminate(); 186 worker=null; 187 initPromise=null; 188 if(workerBlobURL) 189 { 190 var URLApi=options.URL || (typeof(URL)!=="undefined" ? URL : null); 191 if(URLApi && typeof(URLApi.revokeObjectURL)==="function") URLApi.revokeObjectURL(workerBlobURL); 192 } 193 workerBlobURL=null; 194 }; 195}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.