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:
parent
58ac1aed25
commit
b6dcda89ff
10 changed files with 284 additions and 0 deletions
24
build/webp-shim/Dockerfile
Normal file
24
build/webp-shim/Dockerfile
Normal 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
25
build/webp-shim/Makefile
Normal 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
15
build/webp-shim/README.md
Normal 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.
|
BIN
build/webp-shim/samples/norway.webp
Normal file
BIN
build/webp-shim/samples/norway.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
97
build/webp-shim/src/webp.c
Normal file
97
build/webp-shim/src/webp.c
Normal 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;
|
||||
}
|
10
build/webp-shim/test/harness.html
Normal file
10
build/webp-shim/test/harness.html
Normal 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>
|
20
build/webp-shim/test/harness.js
Normal file
20
build/webp-shim/test/harness.js
Normal 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'})));
|
||||
});
|
||||
});
|
1
image/webp-shim/webp-shim-module.js
Normal file
1
image/webp-shim/webp-shim-module.js
Normal file
File diff suppressed because one or more lines are too long
BIN
image/webp-shim/webp-shim-module.wasm
Normal file
BIN
image/webp-shim/webp-shim-module.wasm
Normal file
Binary file not shown.
92
image/webp-shim/webp-shim.js
Normal file
92
image/webp-shim/webp-shim.js
Normal 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;
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue