Upgrade AVIF decoding code
- Update to newer APIs. - Avoid manual pixel-by-pixel copy in favour of decoding directly to desired format & bit depth. - Avoid use-after-free by cloning the Uint8Array Wasm memory view into a JS-owned Uint8Array right away.
This commit is contained in:
@ -7,19 +7,12 @@ using namespace emscripten;
|
||||
class RawImage {
|
||||
public:
|
||||
val buffer;
|
||||
int width;
|
||||
int height;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
|
||||
RawImage(val b, int w, int h) : buffer(b), width(w), height(h) {}
|
||||
RawImage(val b, uint32_t w, uint32_t h) : buffer(b), width(w), height(h) {}
|
||||
};
|
||||
// NOTE: avifDecoderRead() offers the simplest means to get an avifImage that is
|
||||
// complete independent of an avifDecoder, but at the cost of additional
|
||||
// allocations and copies, and no support for image sequences. If you don't mind
|
||||
// keeping around the avifDecoder while you read in the image and/or need image
|
||||
// sequence support, skip ahead to the Advanced Decoding example. It is only one
|
||||
// additional function call, and the avifImage is owned by the avifDecoder.
|
||||
|
||||
uint8_t *result;
|
||||
RawImage decode(std::string avifimage) {
|
||||
// point raw.data and raw.size to the contents of an .avif(s)
|
||||
avifROData raw;
|
||||
@ -29,86 +22,33 @@ RawImage decode(std::string avifimage) {
|
||||
avifImage *image = avifImageCreateEmpty();
|
||||
avifDecoder *decoder = avifDecoderCreate();
|
||||
avifResult decodeResult = avifDecoderRead(decoder, image, &raw);
|
||||
// image is an independent copy of decoded data, decoder may be destroyed here
|
||||
avifDecoderDestroy(decoder);
|
||||
if (decodeResult != AVIF_RESULT_OK) {
|
||||
return RawImage(val::null(), -1, -1);
|
||||
// printf("ERROR: Failed to decode: %s\n", avifResultToString(result));
|
||||
}
|
||||
|
||||
int width = image->width;
|
||||
int height = image->height;
|
||||
int numBytes = width * height * 4;
|
||||
result = new uint8_t[numBytes];
|
||||
// Convert to interleaved RGB(A)/BGR(A) using a libavif-allocated buffer.
|
||||
avifRGBImage rgb;
|
||||
avifRGBImageSetDefaults(&rgb, image); // Defaults to AVIF_RGB_FORMAT_RGBA which is what we want.
|
||||
rgb.depth = 8; // Does not need to match image->depth. We always want 8-bit pixels.
|
||||
|
||||
// image is an independent copy of decoded data, decoder may be destroyed here
|
||||
|
||||
// ... image->width;
|
||||
// ... image->height;
|
||||
// ... image->depth; // If >8, all plane ptrs below are uint16_t*
|
||||
// ... image->yuvFormat; // U and V planes might be smaller than Y based on
|
||||
// format, use avifGetPixelFormatInfo() to find out in a generic way
|
||||
|
||||
// Option 1: Use YUV planes directly
|
||||
// ... image->yuvPlanes;
|
||||
// ... image->yuvRowBytes;
|
||||
|
||||
// Option 2: Convert to RGB and use RGB planes
|
||||
avifImageYUVToRGB(image);
|
||||
// ... image->rgbPlanes;
|
||||
// ... image->rgbRowBytes;
|
||||
|
||||
if (image->depth > 8) {
|
||||
uint16_t depthDivistor = 1 << (image->depth - 8);
|
||||
// Plane ptrs are uint16_t*
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int pixelOffset = y * image->width + x;
|
||||
result[pixelOffset * 4 + 0] = (uint8_t)(
|
||||
((uint16_t *)(image->rgbPlanes[0]))[pixelOffset] / depthDivistor);
|
||||
result[pixelOffset * 4 + 1] = (uint8_t)(
|
||||
((uint16_t *)(image->rgbPlanes[1]))[pixelOffset] / depthDivistor);
|
||||
result[pixelOffset * 4 + 2] = (uint8_t)(
|
||||
((uint16_t *)(image->rgbPlanes[2]))[pixelOffset] / depthDivistor);
|
||||
if (image->alphaPlane) {
|
||||
result[pixelOffset * 4 + 3] = (uint8_t)(
|
||||
((uint16_t *)(image->alphaPlane))[pixelOffset] / depthDivistor);
|
||||
} else {
|
||||
result[pixelOffset * 4 + 3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Plane ptrs are uint8_t*
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int pixelOffset = y * image->width + x;
|
||||
result[pixelOffset * 4 + 0] = image->rgbPlanes[0][pixelOffset];
|
||||
result[pixelOffset * 4 + 1] = image->rgbPlanes[1][pixelOffset];
|
||||
result[pixelOffset * 4 + 2] = image->rgbPlanes[2][pixelOffset];
|
||||
if (image->alphaPlane) {
|
||||
result[pixelOffset * 4 + 3] = image->alphaPlane[pixelOffset];
|
||||
} else {
|
||||
result[pixelOffset * 4 + 3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // Use alpha plane, if present
|
||||
// if (image->alphaPlane) {
|
||||
// ... image->alphaPlane;
|
||||
// ... image->alphaRowBytes;
|
||||
// }
|
||||
avifRGBImageAllocatePixels(&rgb);
|
||||
avifImageYUVToRGB(image, &rgb);
|
||||
|
||||
// Image has been converted to RGB, we don't need the original anymore.
|
||||
avifImageDestroy(image);
|
||||
avifDecoderDestroy(decoder);
|
||||
|
||||
return RawImage(val(typed_memory_view(numBytes, result)), (int)width,
|
||||
(int)height);
|
||||
// We want to create a *copy* of the decoded data to be owned by the JavaScript side.
|
||||
// For that, we perform `new Uint8Array(wasmMemBuffer, wasmPtr, wasmSize).slice()`:
|
||||
auto result = val(typed_memory_view(rgb.rowBytes * rgb.height, rgb.pixels)).call<val>("slice");
|
||||
|
||||
// Now we can safely free the RGB pixels:
|
||||
avifRGBImageFreePixels(&rgb);
|
||||
|
||||
return RawImage(result, rgb.width, rgb.height);
|
||||
}
|
||||
|
||||
void free_result() { delete result; }
|
||||
|
||||
EMSCRIPTEN_BINDINGS(my_module) {
|
||||
class_<RawImage>("RawImage")
|
||||
.property("buffer", &RawImage::buffer)
|
||||
@ -116,5 +56,4 @@ EMSCRIPTEN_BINDINGS(my_module) {
|
||||
.property("height", &RawImage::height);
|
||||
|
||||
function("decode", &decode);
|
||||
function("free_result", &free_result);
|
||||
}
|
||||
|
1
codecs/avif_dec/avif_dec.d.ts
vendored
1
codecs/avif_dec/avif_dec.d.ts
vendored
@ -6,7 +6,6 @@ interface RawImage {
|
||||
|
||||
interface AVIFModule extends EmscriptenWasm.Module {
|
||||
decode(data: BufferSource): RawImage;
|
||||
free_result(): void;
|
||||
}
|
||||
|
||||
export default function(opts: EmscriptenWasm.ModuleOpts): AVIFModule;
|
||||
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -15,6 +15,5 @@ export async function decode(data: ArrayBuffer): Promise<ImageData> {
|
||||
rawImage.height,
|
||||
);
|
||||
|
||||
module.free_result();
|
||||
return result;
|
||||
}
|
||||
|
Reference in New Issue
Block a user