PageSourceSearch

https://retroapplejs.github.io/res/EMU_DITHERTIZER_converthgr.js

js retroapplejs.github.io collected 2026-10-03 09:50:56 UTC 7,233 bytes, 195 lines download raw bytes

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.