blob: c5dd260cdac29c78b3d60ea1477107032f78297e [file]
#ifdef __EMSCRIPTEN__
#include "rive/async/browser_image_decode.hpp"
#include <emscripten.h>
#include <cstdlib>
#include <unordered_map>
#include <utility>
namespace
{
std::unordered_map<uint32_t, rive::BrowserImageDecoded> s_pendingDecodes;
uint32_t s_nextDecodeId = 1;
uint32_t nextDecodeId()
{
uint32_t id = s_nextDecodeId++;
// On wraparound, skip ids that are still in flight.
while (id == 0 || s_pendingDecodes.count(id) != 0)
{
id = s_nextDecodeId++;
}
return id;
}
// Removes the entry first so done may start or cancel other decodes.
rive::BrowserImageDecoded takeDecode(uint32_t id)
{
auto it = s_pendingDecodes.find(id);
if (it == s_pendingDecodes.end())
{
return nullptr;
}
auto done = std::move(it->second);
s_pendingDecodes.erase(it);
return done;
}
} // namespace
// createImageBitmap is async; the callback calls back into C++ between frames.
// Internals go by their bare names, which the closure compiler keeps.
EM_JS(
void,
wasm_start_image_decode,
(uint32_t requestId, const uint8_t* data, int dataLen),
{
// Copy from WASM heap (SharedArrayBuffer can't be used for Blob).
var sourceView = new Uint8Array(wasmMemory.buffer, data, dataLen);
var buffer = new Uint8Array(dataLen);
buffer.set(sourceView);
// A decoder that throws as it starts fails the decode, not librive.
Promise.resolve()
.then(function() { return createImageBitmap(new Blob([buffer])); })
.then(function(bmp) {
// Draw to OffscreenCanvas to extract raw RGBA pixels.
var canvas = new OffscreenCanvas(bmp.width, bmp.height);
var ctx2d = canvas.getContext("2d");
ctx2d.drawImage(bmp, 0, 0);
var imageData = ctx2d.getImageData(0, 0, bmp.width, bmp.height);
// Allocate WASM memory and copy pixels.
var numBytes = imageData.data.length;
var ptr = _wasm_image_decode_alloc(numBytes);
if (!ptr)
{
_wasm_image_decode_error(requestId);
return;
}
// malloc may grow memory, so view the buffer after it.
new Uint8Array(wasmMemory.buffer, ptr, numBytes)
.set(imageData.data);
_wasm_image_decode_complete(
requestId,
bmp.width,
bmp.height,
ptr,
numBytes);
})
.catch(function() { _wasm_image_decode_error(requestId); });
});
extern "C"
{
// Our own export, so the glue needs no EM_JS_DEPS on malloc, which Unity's
// oldest emsdk predates.
EMSCRIPTEN_KEEPALIVE
uint8_t* wasm_image_decode_alloc(int numBytes)
{
return static_cast<uint8_t*>(malloc(numBytes));
}
EMSCRIPTEN_KEEPALIVE
void wasm_image_decode_complete(
uint32_t requestId,
int width,
int height,
uint8_t* pixels,
int numBytes)
{
auto done = takeDecode(requestId);
if (done)
{
// getImageData returns straight RGBA, and both scripting lanes
// deliver premultiplied RGBA8.
for (int i = 0; i < numBytes; i += 4)
{
uint8_t a = pixels[i + 3];
if (a < 255)
{
pixels[i + 0] = (uint16_t(pixels[i + 0]) * a + 127) / 255;
pixels[i + 1] = (uint16_t(pixels[i + 1]) * a + 127) / 255;
pixels[i + 2] = (uint16_t(pixels[i + 2]) * a + 127) / 255;
}
}
done(
requestId,
width,
height,
rive::Span<const uint8_t>(pixels, numBytes),
nullptr);
}
free(pixels);
}
// The native decoders' message, so a script sees one on every platform.
EMSCRIPTEN_KEEPALIVE
void wasm_image_decode_error(uint32_t requestId)
{
auto done = takeDecode(requestId);
if (done)
{
done(
requestId,
0,
0,
rive::Span<const uint8_t>(),
"failed to decode image data");
}
}
}
namespace rive
{
uint32_t startBrowserImageDecode(
const uint8_t* bytes,
uint32_t byteCount,
BrowserImageDecoded done)
{
uint32_t id = nextDecodeId();
s_pendingDecodes[id] = std::move(done);
wasm_start_image_decode(id, bytes, (int)byteCount);
return id;
}
void cancelBrowserImageDecode(uint32_t id) { s_pendingDecodes.erase(id); }
} // namespace rive
#endif