fork of https://bellard.org/bpg/ Better Portable Graphics format including working WASM (WebAssembly) build.
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
222 lines
6.9 KiB
222 lines
6.9 KiB
|
|
/* |
|
* BPG Javascript decoder |
|
* |
|
* Copyright (c) 2014 Fabrice Bellard |
|
* |
|
* Permission is hereby granted, free of charge, to any person obtaining a copy |
|
* of this software and associated documentation files (the "Software"), to deal |
|
* in the Software without restriction, including without limitation the rights |
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
* copies of the Software, and to permit persons to whom the Software is |
|
* furnished to do so, subject to the following conditions: |
|
* |
|
* The above copyright notice and this permission notice shall be included in |
|
* all copies or substantial portions of the Software. |
|
* |
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
* THE SOFTWARE. |
|
*/ |
|
|
|
|
|
window['BPGDecoder'] = function(ctx) { |
|
this.ctx = ctx; |
|
this['imageData'] = null; |
|
this['onload'] = null; |
|
this['frames'] = null; |
|
this['loop_count'] = 0; |
|
} |
|
|
|
window['BPGDecoder'].prototype = { |
|
|
|
malloc: Module['cwrap']('malloc', 'number', [ 'number' ]), |
|
|
|
free: Module['cwrap']('free', 'void', [ 'number' ]), |
|
|
|
bpg_decoder_open: Module['cwrap']('bpg_decoder_open', 'number', [ ]), |
|
|
|
bpg_decoder_decode: Module['cwrap']('bpg_decoder_decode', 'number', [ 'number', 'array', 'number' ]), |
|
|
|
bpg_decoder_get_info: Module['cwrap']('bpg_decoder_get_info', 'number', [ 'number', 'number' ]), |
|
|
|
bpg_decoder_start: Module['cwrap']('bpg_decoder_start', 'number', [ 'number', 'number' ]), |
|
|
|
bpg_decoder_get_frame_duration: Module['cwrap']('bpg_decoder_get_frame_duration', 'void', [ 'number', 'number', 'number' ]), |
|
|
|
bpg_decoder_get_line: Module['cwrap']('bpg_decoder_get_line', 'number', [ 'number', 'number' ]), |
|
|
|
bpg_decoder_close: Module['cwrap']('bpg_decoder_close', 'void', [ 'number' ] ), |
|
|
|
isReady: function() {return wasmModuleIsReady}, |
|
|
|
load: function(url) { |
|
var request = new XMLHttpRequest(); |
|
var this1 = this; |
|
|
|
request.open("get", url, true); |
|
request.responseType = "arraybuffer"; |
|
request.onload = function() { |
|
wasmModuleIsReady.then(() => this1.receiveData(new Uint8Array(request.response))); |
|
}; |
|
request.send(); |
|
}, |
|
|
|
receiveData: function(array) { |
|
|
|
var img, w, h, img_info_buf, cimg, p0, rgba_line, w4, frame_count; |
|
var heap8, heap16, heap32, dst, v, i, y, func, duration, frames, loop_count; |
|
|
|
// console.log("loaded " + data.byteLength + " bytes"); |
|
|
|
img = this.bpg_decoder_open(); |
|
|
|
if (this.bpg_decoder_decode(img, array, array.length) < 0) { |
|
console.log("could not decode image"); |
|
return; |
|
} |
|
|
|
img_info_buf = this.malloc(5 * 4); |
|
this.bpg_decoder_get_info(img, img_info_buf); |
|
/* extract the image info */ |
|
heap8 = Module['HEAPU8']; |
|
heap16 = Module['HEAPU16']; |
|
heap32 = Module['HEAPU32']; |
|
w = heap32[img_info_buf >> 2]; |
|
h = heap32[(img_info_buf + 4) >> 2]; |
|
loop_count = heap16[(img_info_buf + 16) >> 1]; |
|
// console.log("image: w=" + w + " h=" + h + " loop_count=" + loop_count); |
|
|
|
w4 = w * 4; |
|
rgba_line = this.malloc(w4); |
|
|
|
frame_count = 0; |
|
frames = []; |
|
for(;;) { |
|
/* select RGBA32 output */ |
|
if (this.bpg_decoder_start(img, 1) < 0) |
|
break; |
|
this.bpg_decoder_get_frame_duration(img, img_info_buf, |
|
img_info_buf + 4); |
|
duration = (heap32[img_info_buf >> 2] * 1000) / heap32[(img_info_buf + 4) >> 2]; |
|
|
|
cimg = this.ctx.createImageData(w, h); |
|
dst = cimg.data; |
|
p0 = 0; |
|
for(y = 0; y < h; y++) { |
|
this.bpg_decoder_get_line(img, rgba_line); |
|
for(i = 0; i < w4; i = (i + 1) | 0) { |
|
dst[p0] = heap8[(rgba_line + i) | 0] | 0; |
|
p0 = (p0 + 1) | 0; |
|
} |
|
} |
|
frames[frame_count++] = { 'img': cimg, 'duration': duration }; |
|
} |
|
|
|
this.free(rgba_line); |
|
this.free(img_info_buf); |
|
|
|
this.bpg_decoder_close(img); |
|
|
|
this['loop_count'] = loop_count; |
|
this['frames'] = frames; |
|
this['imageData'] = frames[0]['img']; |
|
|
|
if (this['onload']) |
|
this['onload'](); |
|
} |
|
|
|
}; |
|
|
|
window.createBPGElement = function(parent, url, className, elementWidth, elementHeight) { |
|
|
|
const base64ToUint8Array = (string) => { |
|
var raw = window.atob(string); |
|
var rawLength = raw.length; |
|
var array = new Uint8Array(new ArrayBuffer(rawLength)); |
|
for(i = 0; i < rawLength; i++) { |
|
array[i] = raw.charCodeAt(i); |
|
} |
|
return array; |
|
}; |
|
|
|
const canvas = document.createElement("canvas"); |
|
//canvas.style.display = "none"; |
|
parent.appendChild(canvas); |
|
|
|
const promise = new Promise((resolve) => { |
|
|
|
if(elementWidth) { |
|
canvas.style.width = elementWidth; |
|
} |
|
if(elementHeight) { |
|
canvas.style.height = elementHeight; |
|
} |
|
if(className) { |
|
canvas.className = className; |
|
} |
|
|
|
const ctx = canvas.getContext("2d"); |
|
const dec = new BPGDecoder(ctx); |
|
dec.onload = (function(canvas, ctx) { |
|
var dec = this; |
|
var frames = this['frames']; |
|
var imageData = frames[0]['img']; |
|
function next_frame() { |
|
var frame_index = dec.frame_index; |
|
|
|
/* compute next frame index */ |
|
if (++frame_index >= frames.length) { |
|
if (dec['loop_count'] == 0 || |
|
dec.loop_counter < dec['loop_count']) { |
|
frame_index = 0; |
|
dec.loop_counter++; |
|
} else { |
|
frame_index = -1; |
|
} |
|
} |
|
if (frame_index >= 0) { |
|
dec.frame_index = frame_index; |
|
ctx.putImageData(frames[frame_index]['img'], 0, 0); |
|
setTimeout(next_frame, frames[frame_index]['duration']); |
|
} |
|
}; |
|
|
|
/* resize the canvas to the image size */ |
|
canvas.width = imageData.width; |
|
canvas.height = imageData.height; |
|
|
|
/* draw the image */ |
|
ctx.putImageData(imageData, 0, 0); |
|
|
|
//canvas.style.removeProperty('display'); |
|
|
|
/* if it is an animation, add a timer to display the next frame */ |
|
if (frames.length > 1) { |
|
dec.frame_index = 0; |
|
dec.loop_counter = 0; |
|
setTimeout(next_frame, frames[0]['duration']); |
|
} |
|
|
|
resolve(canvas); |
|
}).bind(dec, canvas, ctx); |
|
|
|
if(url.indexOf('data:image/bpg;base64,') == 0) { |
|
wasmModuleIsReady.then(() => { |
|
dec.receiveData( base64ToUint8Array(url.replace('data:image/bpg;base64,', '')) ); |
|
}); |
|
} else { |
|
dec.load(url); |
|
} |
|
|
|
}); |
|
|
|
return {canvas,promise}; |
|
}; |
|
|
|
/* end of dummy function enclosing all the emscripten code */ |
|
})();
|
|
|