Files
cell-image/jpg.c
2025-12-11 10:37:41 -06:00

119 lines
3.9 KiB
C

#include "cell.h"
#include <string.h>
#include <stdlib.h>
#include "stb_image_common.h"
// JPEG decoding
JSValue js_jpg_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 JPEG 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 JPEG: %s", stbi_failure_reason());
if (width <= 0 || height <= 0) {
stbi_image_free(data);
return JS_ThrowReferenceError(js, "decoded JPEG 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;
}
// JPEG encoding
JSValue js_jpg_encode(JSContext *js, JSValue this_val, int argc, JSValueConst *argv)
{
if (argc < 1)
return JS_ThrowTypeError(js, "jpg.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, "jpg.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);
// Quality: default 90, can be overridden via options
int quality = 90;
if (argc > 1 && JS_IsObject(argv[1])) {
JSValue qv = JS_GetPropertyStr(js, argv[1], "quality");
if (!JS_IsNull(qv)) {
int qtmp;
if (JS_ToInt32(js, &qtmp, qv) == 0) {
if (qtmp < 1) qtmp = 1;
if (qtmp > 100) qtmp = 100;
quality = qtmp;
}
}
JS_FreeValue(js, qv);
}
stbi_mem_buf buf = {0};
stbi_write_jpg_to_func(stbi_mem_write, &buf, width, height, 4, pixel_data, quality);
if (!buf.data || buf.size == 0) {
if (buf.data) free(buf.data);
return JS_ThrowInternalError(js, "failed to encode JPEG");
}
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, "jpg"));
JS_SetPropertyStr(js, result, "pixels", js_new_blob_stoned_copy(js, buf.data, buf.size));
free(buf.data);
return result;
}
static const JSCFunctionListEntry js_jpg_funcs[] = {
MIST_FUNC_DEF(jpg, decode, 1),
MIST_FUNC_DEF(jpg, encode, 2)
};
CELL_USE_FUNCS(js_jpg_funcs)