123 lines
4.2 KiB
C
123 lines
4.2 KiB
C
#ifndef STB_IMAGE_COMMON_H
|
|
#define STB_IMAGE_COMMON_H
|
|
|
|
#include "stb_image.h"
|
|
#include "stb_image_write.h"
|
|
|
|
// Simple growable memory buffer for stbi write callbacks
|
|
typedef struct {
|
|
unsigned char *data;
|
|
size_t size;
|
|
size_t cap;
|
|
} stbi_mem_buf;
|
|
|
|
static void stbi_mem_write(void *context, void *data, int size)
|
|
{
|
|
stbi_mem_buf *buf = (stbi_mem_buf*)context;
|
|
size_t needed = buf->size + (size_t)size;
|
|
if (needed > buf->cap) {
|
|
size_t new_cap = buf->cap ? buf->cap * 2 : 4096;
|
|
while (new_cap < needed) new_cap *= 2;
|
|
unsigned char *new_data = realloc(buf->data, new_cap);
|
|
if (!new_data) return;
|
|
buf->data = new_data;
|
|
buf->cap = new_cap;
|
|
}
|
|
memcpy(buf->data + buf->size, data, (size_t)size);
|
|
buf->size += (size_t)size;
|
|
}
|
|
|
|
#define IMAGE_DECODER(fmt) \
|
|
JSValue js_##fmt##_decode(JSContext *js, JSValue this_val, int argc, JSValueConst *argv) \
|
|
{ \
|
|
size_t len; \
|
|
void *raw = js_get_blob_data(js, &len, argv[0]); \
|
|
if (raw == NULL) return JS_EXCEPTION; \
|
|
if (!raw) return JS_ThrowReferenceError(js, "could not get " #fmt " data from blob"); \
|
|
\
|
|
int n, width, height; \
|
|
void *data = stbi_load_from_memory(raw, len, &width, &height, &n, 4); \
|
|
\
|
|
if (!data) \
|
|
return JS_ThrowReferenceError(js, "failed to decode " #fmt ": %s", stbi_failure_reason()); \
|
|
\
|
|
if (width <= 0 || height <= 0) { \
|
|
stbi_image_free(data); \
|
|
return JS_ThrowReferenceError(js, "decoded " #fmt " has invalid size: %dx%d", width, height); \
|
|
} \
|
|
\
|
|
int pitch = width * 4; \
|
|
size_t pixels_size = pitch * height; \
|
|
\
|
|
JSValue obj = JS_NewObject(js); \
|
|
JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, width)); \
|
|
JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, height)); \
|
|
JS_SetPropertyStr(js, obj, "format", JS_NewString(js, "rgba32")); \
|
|
JS_SetPropertyStr(js, obj, "pitch", JS_NewInt32(js, pitch)); \
|
|
JS_SetPropertyStr(js, obj, "pixels", js_new_blob_stoned_copy(js, data, pixels_size)); \
|
|
JS_SetPropertyStr(js, obj, "depth", JS_NewInt32(js, 8)); \
|
|
JS_SetPropertyStr(js, obj, "hdr", JS_NewBool(js, 0)); \
|
|
\
|
|
stbi_image_free(data); \
|
|
return obj; \
|
|
}
|
|
|
|
#define IMAGE_ENCODER(fmt) \
|
|
JSValue js_##fmt##_encode(JSContext *js, JSValue this_val, int argc, JSValueConst *argv) \
|
|
{ \
|
|
if (argc < 1) \
|
|
return JS_ThrowTypeError(js, #fmt ".encode requires an image object"); \
|
|
\
|
|
JSValue width_val = JS_GetPropertyStr(js, argv[0], "width"); \
|
|
JSValue height_val = JS_GetPropertyStr(js, argv[0], "height"); \
|
|
\
|
|
if (JS_IsNull(width_val) || JS_IsNull(height_val)) { \
|
|
JS_FreeValue(js, width_val); \
|
|
JS_FreeValue(js, height_val); \
|
|
return JS_ThrowTypeError(js, #fmt ".encode requires width and height properties"); \
|
|
} \
|
|
\
|
|
int width, height; \
|
|
if (JS_ToInt32(js, &width, width_val) < 0 || JS_ToInt32(js, &height, height_val) < 0) { \
|
|
JS_FreeValue(js, width_val); \
|
|
JS_FreeValue(js, height_val); \
|
|
return JS_ThrowTypeError(js, "width and height must be numbers"); \
|
|
} \
|
|
JS_FreeValue(js, width_val); \
|
|
JS_FreeValue(js, height_val); \
|
|
\
|
|
if (width < 1 || height < 1) \
|
|
return JS_ThrowRangeError(js, "width and height must be at least 1"); \
|
|
\
|
|
JSValue pixels_val = JS_GetPropertyStr(js, argv[0], "pixels"); \
|
|
size_t pixel_len; \
|
|
void *pixel_data = js_get_blob_data(js, &pixel_len, pixels_val); \
|
|
JS_FreeValue(js, pixels_val); \
|
|
\
|
|
if (pixel_data == NULL) return JS_EXCEPTION; \
|
|
if (!pixel_data) return JS_ThrowTypeError(js, "pixels blob has no data"); \
|
|
\
|
|
size_t required_size = width * height * 4; \
|
|
if (pixel_len < required_size) \
|
|
return JS_ThrowRangeError(js, "pixels buffer too small (need %zu, got %zu)", required_size, pixel_len); \
|
|
\
|
|
stbi_mem_buf buf = {0}; \
|
|
stbi_write_##fmt##_to_func(stbi_mem_write, &buf, width, height, 4, pixel_data); \
|
|
\
|
|
if (!buf.data || buf.size == 0) { \
|
|
if (buf.data) free(buf.data); \
|
|
return JS_ThrowInternalError(js, "failed to encode " #fmt); \
|
|
} \
|
|
\
|
|
JSValue result = JS_NewObject(js); \
|
|
JS_SetPropertyStr(js, result, "width", JS_NewInt32(js, width)); \
|
|
JS_SetPropertyStr(js, result, "height", JS_NewInt32(js, height)); \
|
|
JS_SetPropertyStr(js, result, "format", JS_NewString(js, #fmt)); \
|
|
JS_SetPropertyStr(js, result, "pixels", js_new_blob_stoned_copy(js, buf.data, buf.size)); \
|
|
\
|
|
free(buf.data); \
|
|
return result; \
|
|
}
|
|
|
|
#endif
|