1
0
Fork 0
mirror of https://github.com/codedread/bitjs synced 2025-10-05 18:34:17 +02:00

Add webp-shim to bitjs

This commit is contained in:
codedread 2020-03-14 08:54:31 -07:00
parent 58ac1aed25
commit b6dcda89ff
10 changed files with 284 additions and 0 deletions

View file

@ -0,0 +1,24 @@
FROM ubuntu:18.04
RUN apt-get update && apt-get install -y \
cmake \
git-core \
python \
wget
# Put more deps here ...
# RUN apt-get update && apt-get install -y \
RUN git clone https://github.com/webmproject/libwebp
RUN git clone https://github.com/emscripten-core/emsdk.git && \
cd emsdk && \
./emsdk install latest && \
./emsdk activate latest
RUN git clone https://github.com/nothings/stb.git
# Figure out how to invoke emsdk/emsdk_env.sh and have it affect PATH.
ENV PATH="$PATH:/emsdk:/emsdk/node/12.9.1_64bit/bin:/emsdk/upstream/emscripten"
COPY . .

25
build/webp-shim/Makefile Normal file
View file

@ -0,0 +1,25 @@
OUT_PATH=out
OUT_NAME=webp-shim-module
OUT_TARGET=${OUT_PATH}/${OUT_NAME}.js
LIBWEBP_PATH=libwebp/src
LIBWEBP_SRC=$(shell find ${LIBWEBP_PATH}/ -name '*.c')
SHIM_PATH=src
SHIM_SRC=${SHIM_PATH}/webp.c
debug: ${OUT_TARGET}
clean:
rm -rf ${OUT_PATH}/${OUT_NAME}.*
${OUT_TARGET}: ${SHIM_SRC}
emcc -O3 \
-s WASM=1 \
-s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]' \
-s ALLOW_MEMORY_GROWTH=1 \
-s ASSERTIONS=1 \
-I stb \
-I libwebp \
${LIBWEBP_SRC} \
${SHIM_SRC} \
-o ${OUT_TARGET}

15
build/webp-shim/README.md Normal file
View file

@ -0,0 +1,15 @@
# WebP Shim
## Prerequisites
* Install Docker
* git clone this repository
## Build Instructions
Assuming you have cloned the repository in /path/to/bitjs:
* cd /path/to/bitjs/build/webp-shim/
* Build the Docker image: `docker build -f Dockerfile -t wps/0.1 .`
* Run the Docker image: `docker run -it --mount type=bind,source=/path/to/bitjs/image/webp-shim,target=/out wps/0.1`
* Build the WASM module: `make`
* Exit the Docker image: `exit`
The /path/to/webp-shim/lib/ directory will now contain webp-shim.js and webp-shim.wasm.

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -0,0 +1,97 @@
#include <stdio.h>
#include <stdlib.h>
#include "emscripten.h"
#include "src/webp/decode.h"
#define STBI_WRITE_NO_STDIO
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
#include "stretchy_buffer.h"
// Method to create buffers to transfer data into wasm.
EMSCRIPTEN_KEEPALIVE uint8_t* create_buffer(int size) { return malloc(size * sizeof(uint8_t)); }
EMSCRIPTEN_KEEPALIVE void destroy_buffer(uint8_t* p) { free(p); }
// ImageBuffer is a struct representing information about the image data.
// TODO: Use embind to make this a C++ class?
typedef struct {
size_t byte_count;
uint8_t* bytes; // A stretchy buffer.
} ImageBuffer;
// Methods to get ImageBuffer fields.
EMSCRIPTEN_KEEPALIVE uint8_t* get_image_bytes_from_handle(ImageBuffer* p) { return p->bytes; }
EMSCRIPTEN_KEEPALIVE size_t get_num_bytes_from_handle(ImageBuffer* p) { return p->byte_count; }
EMSCRIPTEN_KEEPALIVE void release_image_handle(ImageBuffer* image_buffer) {
if (image_buffer) {
sb_free(image_buffer->bytes);
free(image_buffer);
}
}
ImageBuffer* createImageBuffer() {
ImageBuffer* image_buffer = (ImageBuffer*) malloc(sizeof(ImageBuffer));
image_buffer->byte_count = 0;
image_buffer->bytes = NULL;
return image_buffer;
}
void write_image_to_mem(void* context, void* data, int size) {
ImageBuffer* image_buffer = (ImageBuffer*) context;
// If the buffer is not big enough for the new data, grow it.
int size_of_buffer = sb_count(image_buffer->bytes);
if (size_of_buffer < image_buffer->byte_count + size) {
sb_add(image_buffer->bytes, size < 128 ? 128 : size);
}
uint8_t* start_ptr = image_buffer->bytes + image_buffer->byte_count;
memcpy(start_ptr, data, size);
image_buffer->byte_count += size;
}
uint8_t* decode_webp_to_rgba(uint8_t* webp_ptr, size_t size, int* width, int* height) {
if (!webp_ptr) {
printf("webp_ptr is NULL");
}
if (!WebPGetInfo(webp_ptr, size, width, height)) {
fprintf(stderr, "WebPGetInfo() returned an error\n");
return NULL;
}
uint8_t* rgba_ptr = WebPDecodeRGBA(webp_ptr, size, width, height);
return rgba_ptr;
}
EMSCRIPTEN_KEEPALIVE ImageBuffer* get_png_handle_from_webp(uint8_t* webp_ptr, size_t size) {
int width, height;
uint8_t* rgba_ptr = decode_webp_to_rgba(webp_ptr, size, &width, &height);
if (!rgba_ptr) {
return NULL;
}
ImageBuffer* image_buffer = createImageBuffer();
int result = stbi_write_png_to_func(write_image_to_mem, image_buffer, width, height, 4,
rgba_ptr, width * 4);
WebPFree(rgba_ptr);
if (!result) {
return NULL;
}
return image_buffer;
}
EMSCRIPTEN_KEEPALIVE ImageBuffer* get_jpg_handle_from_webp(uint8_t* webp_ptr, size_t size) {
int width, height;
uint8_t* rgba_ptr = decode_webp_to_rgba(webp_ptr, size, &width, &height);
if (!rgba_ptr) {
return NULL;
}
ImageBuffer* image_buffer = createImageBuffer();
int result = stbi_write_jpg_to_func(write_image_to_mem, image_buffer, width, height, 4,
rgba_ptr, 100);
WebPFree(rgba_ptr);
if (!result) {
return NULL;
}
return image_buffer;
}

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<body>
<img id="webp" src="../samples/norway.webp">
<img id="png">
<img id="jpg">
</body>
<!--script src="../lib/webp-shim.js"></script-->
<script type="module" src="harness.js"></script>
</html>

View file

@ -0,0 +1,20 @@
import {convertWebPtoPNG, convertWebPtoJPG} from '../../../image/webp-shim/webp-shim.js';
new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', '../samples/norway.webp', true);
xhr.responseType = 'arraybuffer'
xhr.onload = evt => resolve(evt.target.response);
xhr.onerror = err => reject(err);
xhr.send(null);
}).then(webpBuffer => {
convertWebPtoPNG(webpBuffer).then(pngArr => {
document.getElementById('png').setAttribute('src',
URL.createObjectURL(new Blob([pngArr], {type: 'image/png'})));
});
convertWebPtoJPG(webpBuffer).then(jpgArr => {
document.getElementById('jpg').setAttribute('src',
URL.createObjectURL(new Blob([jpgArr], {type: 'image/jpeg'})));
});
});

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -0,0 +1,92 @@
/**
*
*/
const url = import.meta.url;
if (!url.endsWith('/webp-shim.js')) {
throw 'webp-shim must be loaded as webp-shim.js';
}
const thisModulePath = url.substring(0, url.indexOf('/webp-shim.js'));
let loadingPromise = undefined;
let api = undefined;
/**
* @return {Promise<Object>} Returns the API object.
*/
function loadWebPShimApi() {
if (api) { return Promise.resolve(api); }
else if (loadingPromise) { return loadingPromise; }
return loadingPromise = new Promise((resolve, reject) => {
const scriptEl = document.createElement('script');
scriptEl.onload = () => {
Module.print = str => console.log(str);
Module.printErr = str => console.error(str);
Module.onRuntimeInitialized = () => {
api = {
createWASMBuffer: Module.cwrap('create_buffer', 'number', ['number', 'number']),
destroyWASMBuffer: Module.cwrap('destroy_buffer', '', ['number']),
getJPGHandle: Module.cwrap('get_jpg_handle_from_webp', 'number', ['number', 'number']),
getPNGHandle: Module.cwrap('get_png_handle_from_webp', 'number', ['number', 'number']),
getImageBytesFromHandle: Module.cwrap('get_image_bytes_from_handle', 'number', ['number']),
getNumBytesFromHandle: Module.cwrap('get_num_bytes_from_handle', 'number', ['number']),
heap: Module.HEAPU8,
releaseImageHandle: Module.cwrap('release_image_handle', '', ['number']),
};
resolve(api);
};
};
scriptEl.onerror = err => reject(err);
scriptEl.src = `${thisModulePath}/webp-shim-module.js`;
document.body.appendChild(scriptEl);
});
}
/**
* @param {ArrayBuffer|TypedArray} webpBuffer The byte array containing the WebP image bytes.
* @returns {Promise<ArrayBuffer>} A Promise resolving to a byte array containing the PNG bytes.
*/
export function convertWebPtoPNG(webpBuffer) {
return loadWebPShimApi().then((api) => {
// Create a buffer of the WebP bytes that we can send into WASM-land.
const webpArray = new Uint8Array(webpBuffer);
const size = webpArray.byteLength;
const webpWASMBuffer = api.createWASMBuffer(size);
api.heap.set(webpArray, webpWASMBuffer);
// Convert to PNG.
const pngHandle = api.getPNGHandle(webpWASMBuffer, size);
const numBytes = api.getNumBytesFromHandle(pngHandle);
const pngBufPtr = api.getImageBytesFromHandle(pngHandle);
const pngBuffer = api.heap.slice(pngBufPtr, pngBufPtr + numBytes - 1);
// Cleanup.
api.releaseImageHandle(pngHandle);
api.destroyWASMBuffer(webpWASMBuffer);
return pngBuffer;
});
}
/**
* @param {ArrayBuffer|TypedArray} webpBuffer The byte array containing the WebP image bytes.
* @returns {Promise<ArrayBuffer>} A Promise resolving to a byte array containing the JPG bytes.
*/
export function convertWebPtoJPG(webpBuffer) {
return loadWebPShimApi().then((api) => {
// Create a buffer of the WebP bytes that we can send into WASM-land.
const size = webpBuffer.byteLength;
const webpWASMBuffer = api.createWASMBuffer(size);
api.heap.set(webpBuffer, webpWASMBuffer);
// Convert to JPG.
const jpgHandle = api.getJPGHandle(webpWASMBuffer, size);
const numJPGBytes = api.getNumBytesFromHandle(jpgHandle);
const jpgBufPtr = api.getImageBytesFromHandle(jpgHandle);
const jpgBuffer = api.heap.slice(jpgBufPtr, jpgBufPtr + numJPGBytes - 1);
// Cleanup.
api.releaseImageHandle(jpgHandle);
api.destroyWASMBuffer(webpWASMBuffer);
return jpgBuffer;
});
}