resize
This commit is contained in:
195
resize.c
Normal file
195
resize.c
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
#include "cell.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||||
|
#include "stb_image_resize2.h"
|
||||||
|
|
||||||
|
// Resize an image to new dimensions
|
||||||
|
// Input: image object with {width, height, pixels (blob)}
|
||||||
|
// Optional: opts object with {filter: "nearest"|"linear"|"cubic"|"mitchell"}
|
||||||
|
// Returns: new image object with resized pixels
|
||||||
|
JSValue js_resize_resize(JSContext *js, JSValue this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 3)
|
||||||
|
return JS_ThrowTypeError(js, "resize.resize requires (image, new_width, new_height, [opts])");
|
||||||
|
|
||||||
|
// Get source dimensions
|
||||||
|
JSValue src_width_val = JS_GetPropertyStr(js, argv[0], "width");
|
||||||
|
JSValue src_height_val = JS_GetPropertyStr(js, argv[0], "height");
|
||||||
|
|
||||||
|
if (JS_IsNull(src_width_val) || JS_IsNull(src_height_val)) {
|
||||||
|
JS_FreeValue(js, src_width_val);
|
||||||
|
JS_FreeValue(js, src_height_val);
|
||||||
|
return JS_ThrowTypeError(js, "resize.resize requires image with width and height properties");
|
||||||
|
}
|
||||||
|
|
||||||
|
int src_width, src_height;
|
||||||
|
if (JS_ToInt32(js, &src_width, src_width_val) < 0 || JS_ToInt32(js, &src_height, src_height_val) < 0) {
|
||||||
|
JS_FreeValue(js, src_width_val);
|
||||||
|
JS_FreeValue(js, src_height_val);
|
||||||
|
return JS_ThrowTypeError(js, "width and height must be numbers");
|
||||||
|
}
|
||||||
|
JS_FreeValue(js, src_width_val);
|
||||||
|
JS_FreeValue(js, src_height_val);
|
||||||
|
|
||||||
|
// Get destination dimensions
|
||||||
|
int dst_width, dst_height;
|
||||||
|
if (JS_ToInt32(js, &dst_width, argv[1]) < 0 || JS_ToInt32(js, &dst_height, argv[2]) < 0)
|
||||||
|
return JS_ThrowTypeError(js, "new_width and new_height must be numbers");
|
||||||
|
|
||||||
|
if (dst_width < 1 || dst_height < 1)
|
||||||
|
return JS_ThrowRangeError(js, "destination dimensions must be at least 1x1");
|
||||||
|
|
||||||
|
if (src_width < 1 || src_height < 1)
|
||||||
|
return JS_ThrowRangeError(js, "source dimensions must be at least 1x1");
|
||||||
|
|
||||||
|
// Get source pixels
|
||||||
|
JSValue pixels_val = JS_GetPropertyStr(js, argv[0], "pixels");
|
||||||
|
size_t pixel_len;
|
||||||
|
void *src_pixels = js_get_blob_data(js, &pixel_len, pixels_val);
|
||||||
|
JS_FreeValue(js, pixels_val);
|
||||||
|
|
||||||
|
if (src_pixels == NULL) return JS_EXCEPTION;
|
||||||
|
if (!src_pixels) return JS_ThrowTypeError(js, "pixels blob has no data");
|
||||||
|
|
||||||
|
size_t required_size = src_width * src_height * 4;
|
||||||
|
if (pixel_len < required_size)
|
||||||
|
return JS_ThrowRangeError(js, "source pixels buffer too small (need %zu, got %zu)", required_size, pixel_len);
|
||||||
|
|
||||||
|
// Determine filter from options
|
||||||
|
stbir_filter filter = STBIR_FILTER_DEFAULT;
|
||||||
|
if (argc >= 4 && !JS_IsNull(argv[3])) {
|
||||||
|
JSValue filter_val = JS_GetPropertyStr(js, argv[3], "filter");
|
||||||
|
if (!JS_IsNull(filter_val)) {
|
||||||
|
const char *filter_str = JS_ToCString(js, filter_val);
|
||||||
|
if (filter_str) {
|
||||||
|
if (strcmp(filter_str, "nearest") == 0 || strcmp(filter_str, "point") == 0)
|
||||||
|
filter = STBIR_FILTER_POINT_SAMPLE;
|
||||||
|
else if (strcmp(filter_str, "linear") == 0 || strcmp(filter_str, "box") == 0)
|
||||||
|
filter = STBIR_FILTER_BOX;
|
||||||
|
else if (strcmp(filter_str, "cubic") == 0 || strcmp(filter_str, "catmullrom") == 0)
|
||||||
|
filter = STBIR_FILTER_CATMULLROM;
|
||||||
|
else if (strcmp(filter_str, "mitchell") == 0)
|
||||||
|
filter = STBIR_FILTER_MITCHELL;
|
||||||
|
JS_FreeCString(js, filter_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JS_FreeValue(js, filter_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate destination buffer
|
||||||
|
size_t dst_size = dst_width * dst_height * 4;
|
||||||
|
unsigned char *dst_pixels = malloc(dst_size);
|
||||||
|
if (!dst_pixels)
|
||||||
|
return JS_ThrowInternalError(js, "failed to allocate destination buffer");
|
||||||
|
|
||||||
|
// Perform resize using stbir
|
||||||
|
STBIR_RESIZE resize;
|
||||||
|
stbir_resize_init(&resize,
|
||||||
|
src_pixels, src_width, src_height, src_width * 4,
|
||||||
|
dst_pixels, dst_width, dst_height, dst_width * 4,
|
||||||
|
STBIR_RGBA, STBIR_TYPE_UINT8);
|
||||||
|
|
||||||
|
stbir_set_filters(&resize, filter, filter);
|
||||||
|
stbir_set_pixel_subrect(&resize, 0, 0, dst_width, dst_height);
|
||||||
|
|
||||||
|
int result = stbir_resize_extended(&resize);
|
||||||
|
if (!result) {
|
||||||
|
free(dst_pixels);
|
||||||
|
return JS_ThrowInternalError(js, "stbir resize failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build result object
|
||||||
|
JSValue obj = JS_NewObject(js);
|
||||||
|
JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, dst_width));
|
||||||
|
JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, dst_height));
|
||||||
|
JS_SetPropertyStr(js, obj, "format", JS_NewString(js, "rgba32"));
|
||||||
|
JS_SetPropertyStr(js, obj, "pitch", JS_NewInt32(js, dst_width * 4));
|
||||||
|
JS_SetPropertyStr(js, obj, "pixels", js_new_blob_stoned_copy(js, dst_pixels, dst_size));
|
||||||
|
JS_SetPropertyStr(js, obj, "depth", JS_NewInt32(js, 8));
|
||||||
|
|
||||||
|
free(dst_pixels);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience function to resize to fit within max dimensions while preserving aspect ratio
|
||||||
|
// resize.fit(image, max_width, max_height, [opts])
|
||||||
|
JSValue js_resize_fit(JSContext *js, JSValue this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 3)
|
||||||
|
return JS_ThrowTypeError(js, "resize.fit requires (image, max_width, max_height, [opts])");
|
||||||
|
|
||||||
|
JSValue src_width_val = JS_GetPropertyStr(js, argv[0], "width");
|
||||||
|
JSValue src_height_val = JS_GetPropertyStr(js, argv[0], "height");
|
||||||
|
|
||||||
|
int src_width, src_height;
|
||||||
|
if (JS_ToInt32(js, &src_width, src_width_val) < 0 || JS_ToInt32(js, &src_height, src_height_val) < 0) {
|
||||||
|
JS_FreeValue(js, src_width_val);
|
||||||
|
JS_FreeValue(js, src_height_val);
|
||||||
|
return JS_ThrowTypeError(js, "image must have width and height");
|
||||||
|
}
|
||||||
|
JS_FreeValue(js, src_width_val);
|
||||||
|
JS_FreeValue(js, src_height_val);
|
||||||
|
|
||||||
|
int max_width, max_height;
|
||||||
|
if (JS_ToInt32(js, &max_width, argv[1]) < 0 || JS_ToInt32(js, &max_height, argv[2]) < 0)
|
||||||
|
return JS_ThrowTypeError(js, "max dimensions must be numbers");
|
||||||
|
|
||||||
|
// Calculate scaled dimensions preserving aspect ratio
|
||||||
|
double scale_x = (double)max_width / src_width;
|
||||||
|
double scale_y = (double)max_height / src_height;
|
||||||
|
double scale = scale_x < scale_y ? scale_x : scale_y;
|
||||||
|
|
||||||
|
int dst_width = (int)(src_width * scale);
|
||||||
|
int dst_height = (int)(src_height * scale);
|
||||||
|
if (dst_width < 1) dst_width = 1;
|
||||||
|
if (dst_height < 1) dst_height = 1;
|
||||||
|
|
||||||
|
// Build args for resize
|
||||||
|
JSValue new_args[4];
|
||||||
|
new_args[0] = argv[0];
|
||||||
|
new_args[1] = JS_NewInt32(js, dst_width);
|
||||||
|
new_args[2] = JS_NewInt32(js, dst_height);
|
||||||
|
new_args[3] = argc >= 4 ? argv[3] : JS_NULL;
|
||||||
|
|
||||||
|
JSValue result = js_resize_resize(js, this_val, 4, new_args);
|
||||||
|
|
||||||
|
JS_FreeValue(js, new_args[1]);
|
||||||
|
JS_FreeValue(js, new_args[2]);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize to exact square dimensions (useful for texture atlases)
|
||||||
|
// resize.square(image, size, [opts])
|
||||||
|
JSValue js_resize_square(JSContext *js, JSValue this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 2)
|
||||||
|
return JS_ThrowTypeError(js, "resize.square requires (image, size, [opts])");
|
||||||
|
|
||||||
|
int size;
|
||||||
|
if (JS_ToInt32(js, &size, argv[1]) < 0)
|
||||||
|
return JS_ThrowTypeError(js, "size must be a number");
|
||||||
|
|
||||||
|
JSValue new_args[4];
|
||||||
|
new_args[0] = argv[0];
|
||||||
|
new_args[1] = JS_NewInt32(js, size);
|
||||||
|
new_args[2] = JS_NewInt32(js, size);
|
||||||
|
new_args[3] = argc >= 3 ? argv[2] : JS_NULL;
|
||||||
|
|
||||||
|
JSValue result = js_resize_resize(js, this_val, 4, new_args);
|
||||||
|
|
||||||
|
JS_FreeValue(js, new_args[1]);
|
||||||
|
JS_FreeValue(js, new_args[2]);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const JSCFunctionListEntry js_resize_funcs[] = {
|
||||||
|
MIST_FUNC_DEF(resize, resize, 4),
|
||||||
|
MIST_FUNC_DEF(resize, fit, 4),
|
||||||
|
MIST_FUNC_DEF(resize, square, 3)
|
||||||
|
};
|
||||||
|
|
||||||
|
CELL_USE_FUNCS(js_resize_funcs)
|
||||||
10651
stb_image_resize2.h
Normal file
10651
stb_image_resize2.h
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user