qop
This commit is contained in:
17
meson.build
17
meson.build
@@ -255,22 +255,6 @@ else
|
||||
deps += soloud_dep
|
||||
endif
|
||||
|
||||
# QR code support (optional)
|
||||
qrencode_enabled = get_option('qrencode')
|
||||
if qrencode_enabled
|
||||
qr_dep = dependency('qrencode', static: true, required: false)
|
||||
if not qr_dep.found()
|
||||
message('⚙ System qrencode not found, building subproject...')
|
||||
deps += dependency('libqrencode', static:true)
|
||||
else
|
||||
deps += qr_dep
|
||||
endif
|
||||
add_project_arguments('-DHAVE_QRENCODE', language: ['c', 'cpp'])
|
||||
src += 'qjs_qr.c'
|
||||
else
|
||||
message('⚙ QR code support disabled')
|
||||
endif
|
||||
|
||||
link_args = link
|
||||
sources = []
|
||||
src += [
|
||||
@@ -294,6 +278,7 @@ src += [
|
||||
'wildmatch.c',
|
||||
'qjs_io.c',
|
||||
'qjs_fd.c',
|
||||
'qjs_qop.c',
|
||||
'qjs_os.c',
|
||||
'qjs_actor.c',
|
||||
'qjs_wota.c',
|
||||
|
||||
@@ -954,26 +954,16 @@ int main(int argc, char **argv)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Search for .cell directory up the tree */
|
||||
char *search_dir = SDL_GetCurrentDirectory();
|
||||
/* Check for .cell directory in the current directory */
|
||||
char *cell_parent_dir = NULL;
|
||||
struct stat st;
|
||||
|
||||
while (search_dir && strlen(search_dir) > 1) {
|
||||
char *current_dir = SDL_GetCurrentDirectory();
|
||||
char test_path[PATH_MAX];
|
||||
snprintf(test_path, sizeof(test_path), "%s/.cell", search_dir);
|
||||
snprintf(test_path, sizeof(test_path), "%s/.cell", current_dir);
|
||||
if (stat(test_path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||
cell_parent_dir = strdup(search_dir);
|
||||
break;
|
||||
}
|
||||
char *last_sep = strrchr(search_dir, '/');
|
||||
if (!last_sep || last_sep == search_dir) {
|
||||
if (stat("/.cell", &st) == 0 && S_ISDIR(st.st_mode))
|
||||
cell_parent_dir = strdup("/");
|
||||
break;
|
||||
}
|
||||
*last_sep = '\0';
|
||||
cell_parent_dir = strdup(current_dir);
|
||||
}
|
||||
SDL_free(current_dir);
|
||||
|
||||
if (cell_parent_dir) {
|
||||
/* 1) Strip any trailing slash from cell_parent_dir (except if it's just "/") */
|
||||
@@ -981,27 +971,19 @@ int main(int argc, char **argv)
|
||||
if (proj_len > 1 && cell_parent_dir[proj_len - 1] == '/')
|
||||
cell_parent_dir[proj_len - 1] = '\0';
|
||||
|
||||
char scriptpath[PATH_MAX];
|
||||
snprintf(scriptpath, sizeof(scriptpath), "%s/scripts", cell_parent_dir);
|
||||
|
||||
char cellpath[PATH_MAX];
|
||||
snprintf(cellpath, sizeof(cellpath), "%s/.cell/modules", cell_parent_dir);
|
||||
|
||||
PHYSFS_mount(scriptpath, NULL, 1);
|
||||
PHYSFS_mount(cellpath, NULL, 0);
|
||||
PHYSFS_mount(cell_parent_dir, NULL, 0);
|
||||
PHYSFS_setWriteDir(cell_parent_dir);
|
||||
|
||||
free(cell_parent_dir);
|
||||
} else {
|
||||
// Not in a project - use CELLPATH after confirming ..
|
||||
// TODO: implement
|
||||
printf("Could not find project! Exiting!\n");
|
||||
printf("Cell requires a .cell folder to run.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
SDL_free(search_dir);
|
||||
|
||||
/* Create the initial actor from the command line */
|
||||
int actor_argc = argc - script_start;
|
||||
char **actor_argv = argv + script_start;
|
||||
|
||||
@@ -48,6 +48,3 @@
|
||||
|
||||
#define QOI_IMPLEMENTATION
|
||||
#include "qoi.h"
|
||||
|
||||
#define QOP_IMPLEMENTATION
|
||||
#include "qop.h"
|
||||
|
||||
@@ -29,9 +29,6 @@
|
||||
#include "qjs_nota.h"
|
||||
#include "qjs_wota.h"
|
||||
#include "qjs_soloud.h"
|
||||
#ifdef HAVE_QRENCODE
|
||||
#include "qjs_qr.h"
|
||||
#endif
|
||||
#include "qjs_sdl.h"
|
||||
#include "qjs_sdl_video.h"
|
||||
#include "qjs_math.h"
|
||||
@@ -55,6 +52,8 @@
|
||||
#include "qjs_io.h"
|
||||
#include "qjs_fd.h"
|
||||
|
||||
#include "qjs_qop.h"
|
||||
|
||||
// External transform function declarations
|
||||
extern JSClassID js_transform_id;
|
||||
JSValue transform2js(JSContext *js, transform *t);
|
||||
@@ -1123,11 +1122,8 @@ void ffi_load(JSContext *js)
|
||||
// extra
|
||||
arrput(rt->module_registry, ((ModuleEntry){"io", js_io_use}));
|
||||
arrput(rt->module_registry, ((ModuleEntry){"fd", js_fd_use}));
|
||||
arrput(rt->module_registry, MISTLINE(socket));
|
||||
arrput(rt->module_registry, ((ModuleEntry){"qop", js_qop_use}));
|
||||
arrput(rt->module_registry, ((ModuleEntry){"os", js_os_use}));
|
||||
#ifdef HAVE_QRENCODE
|
||||
arrput(rt->module_registry, MISTLINE(qr));
|
||||
#endif
|
||||
arrput(rt->module_registry, MISTLINE(http));
|
||||
arrput(rt->module_registry, MISTLINE(crypto));
|
||||
arrput(rt->module_registry, MISTLINE(miniz));
|
||||
|
||||
205
source/qjs_qop.c
Normal file
205
source/qjs_qop.c
Normal file
@@ -0,0 +1,205 @@
|
||||
#include "qjs_qop.h"
|
||||
#define QOP_IMPLEMENTATION
|
||||
#include "qop.h"
|
||||
#include "qjs_blob.h"
|
||||
#include "jsffi.h"
|
||||
|
||||
static JSClassID js_qop_archive_class_id;
|
||||
|
||||
static void js_qop_archive_finalizer(JSRuntime *rt, JSValue val) {
|
||||
qop_desc *qop = JS_GetOpaque(val, js_qop_archive_class_id);
|
||||
if (qop) {
|
||||
if (qop->hashmap) {
|
||||
js_free_rt(rt, qop->hashmap);
|
||||
}
|
||||
qop_close(qop);
|
||||
js_free_rt(rt, qop);
|
||||
}
|
||||
}
|
||||
|
||||
static JSClassDef js_qop_archive_class = {
|
||||
"qop archive",
|
||||
.finalizer = js_qop_archive_finalizer,
|
||||
};
|
||||
|
||||
static qop_desc *js2qop(JSContext *js, JSValue v) {
|
||||
return JS_GetOpaque(v, js_qop_archive_class_id);
|
||||
}
|
||||
|
||||
static int js_qop_ensure_index(JSContext *js, qop_desc *qop) {
|
||||
if (qop->hashmap != NULL) return 1;
|
||||
void *buffer = js_malloc(js, qop->hashmap_size);
|
||||
if (!buffer) return 0;
|
||||
int num = qop_read_index(qop, buffer);
|
||||
if (num == 0) {
|
||||
js_free(js, buffer);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
JSC_SCALL(qop_open,
|
||||
qop_desc *qop = js_malloc(js, sizeof(qop_desc));
|
||||
if (!qop)
|
||||
ret = JS_ThrowOutOfMemory(js);
|
||||
|
||||
int size = qop_open(str, qop);
|
||||
if (size == 0) {
|
||||
js_free(js, qop);
|
||||
ret = JS_ThrowReferenceError(js, "Failed to open QOP archive: %s", str);
|
||||
} else {
|
||||
JSValue obj = JS_NewObjectClass(js, js_qop_archive_class_id);
|
||||
JS_SetOpaque(obj, qop);
|
||||
ret = obj;
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
static JSValue js_qop_close(JSContext *js, JSValue self, int argc, JSValue *argv) {
|
||||
qop_desc *qop = js2qop(js, self);
|
||||
if (!qop)
|
||||
return JS_ThrowInternalError(js, "Invalid QOP archive");
|
||||
|
||||
qop_close(qop);
|
||||
JS_SetOpaque(self, NULL); // Prevent double free
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static JSValue js_qop_read(JSContext *js, JSValue self, int argc, JSValue *argv) {
|
||||
qop_desc *qop = js2qop(js, self);
|
||||
if (!qop)
|
||||
return JS_ThrowInternalError(js, "Invalid QOP archive");
|
||||
|
||||
const char *path = JS_ToCString(js, argv[0]);
|
||||
if (!path)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
if (!js_qop_ensure_index(js, qop)) {
|
||||
JS_FreeCString(js, path);
|
||||
return JS_ThrowReferenceError(js, "Failed to read QOP index");
|
||||
}
|
||||
|
||||
qop_file *file = qop_find(qop, path);
|
||||
JS_FreeCString(js, path);
|
||||
|
||||
if (!file)
|
||||
return JS_NULL;
|
||||
|
||||
unsigned char *dest = js_malloc(js, file->size);
|
||||
if (!dest)
|
||||
return JS_ThrowOutOfMemory(js);
|
||||
|
||||
int bytes = qop_read(qop, file, dest);
|
||||
if (bytes == 0) {
|
||||
js_free(js, dest);
|
||||
return JS_ThrowReferenceError(js, "Failed to read file");
|
||||
}
|
||||
|
||||
JSValue blob = js_new_blob_stoned_copy(js, dest, bytes);
|
||||
js_free(js, dest);
|
||||
return blob;
|
||||
}
|
||||
|
||||
static JSValue js_qop_read_ex(JSContext *js, JSValue self, int argc, JSValue *argv) {
|
||||
qop_desc *qop = js2qop(js, self);
|
||||
if (!qop)
|
||||
return JS_ThrowInternalError(js, "Invalid QOP archive");
|
||||
|
||||
const char *path = JS_ToCString(js, argv[0]);
|
||||
if (!path)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
if (!js_qop_ensure_index(js, qop)) {
|
||||
JS_FreeCString(js, path);
|
||||
return JS_ThrowReferenceError(js, "Failed to read QOP index");
|
||||
}
|
||||
|
||||
qop_file *file = qop_find(qop, path);
|
||||
JS_FreeCString(js, path);
|
||||
|
||||
if (!file)
|
||||
return JS_NULL;
|
||||
|
||||
unsigned int start;
|
||||
unsigned int len;
|
||||
if (JS_ToUint32(js, &start, argv[1]) < 0 || JS_ToUint32(js, &len, argv[2]) < 0)
|
||||
return JS_ThrowTypeError(js, "Invalid start or len");
|
||||
|
||||
unsigned char *dest = js_malloc(js, len);
|
||||
if (!dest)
|
||||
return JS_ThrowOutOfMemory(js);
|
||||
|
||||
int bytes = qop_read_ex(qop, file, dest, start, len);
|
||||
if (bytes == 0) {
|
||||
js_free(js, dest);
|
||||
return JS_ThrowReferenceError(js, "Failed to read file part");
|
||||
}
|
||||
|
||||
JSValue blob = js_new_blob_stoned_copy(js, dest, bytes);
|
||||
js_free(js, dest);
|
||||
return blob;
|
||||
}
|
||||
|
||||
static JSValue js_qop_list(JSContext *js, JSValue self, int argc, JSValue *argv) {
|
||||
qop_desc *qop = js2qop(js, self);
|
||||
if (!qop)
|
||||
return JS_ThrowInternalError(js, "Invalid QOP archive");
|
||||
|
||||
if (!js_qop_ensure_index(js, qop)) {
|
||||
return JS_ThrowReferenceError(js, "Failed to read QOP index");
|
||||
}
|
||||
|
||||
JSValue arr = JS_NewArray(js);
|
||||
int count = 0;
|
||||
|
||||
for (unsigned int i = 0; i < qop->hashmap_len; i++) {
|
||||
qop_file *file = &qop->hashmap[i];
|
||||
if (file->size == 0) continue; // empty slot
|
||||
|
||||
char *path = js_malloc(js, file->path_len);
|
||||
if (!path) {
|
||||
return JS_ThrowOutOfMemory(js);
|
||||
}
|
||||
|
||||
int len = qop_read_path(qop, file, path);
|
||||
if (len == 0) {
|
||||
js_free(js, path);
|
||||
continue; // skip on error
|
||||
}
|
||||
|
||||
JSValue str = JS_NewStringLen(js, path, len - 1); // -1 for null terminator
|
||||
js_free(js, path);
|
||||
JS_SetPropertyUint32(js, arr, count++, str);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
static const JSCFunctionListEntry js_qop_archive_funcs[] = {
|
||||
JS_CFUNC_DEF("close", 0, js_qop_close),
|
||||
JS_CFUNC_DEF("list", 0, js_qop_list),
|
||||
JS_CFUNC_DEF("read", 1, js_qop_read),
|
||||
JS_CFUNC_DEF("read_ex", 3, js_qop_read_ex),
|
||||
};
|
||||
|
||||
static const JSCFunctionListEntry js_qop_funcs[] = {
|
||||
MIST_FUNC_DEF(qop, open, 1),
|
||||
JS_PROP_INT32_DEF("FLAG_NONE", QOP_FLAG_NONE, JS_PROP_ENUMERABLE),
|
||||
JS_PROP_INT32_DEF("FLAG_COMPRESSED_ZSTD", QOP_FLAG_COMPRESSED_ZSTD, JS_PROP_ENUMERABLE),
|
||||
JS_PROP_INT32_DEF("FLAG_COMPRESSED_DEFLATE", QOP_FLAG_COMPRESSED_DEFLATE, JS_PROP_ENUMERABLE),
|
||||
JS_PROP_INT32_DEF("FLAG_ENCRYPTED", QOP_FLAG_ENCRYPTED, JS_PROP_ENUMERABLE),
|
||||
};
|
||||
|
||||
JSValue js_qop_use(JSContext *js) {
|
||||
JS_NewClassID(&js_qop_archive_class_id);
|
||||
JS_NewClass(JS_GetRuntime(js), js_qop_archive_class_id, &js_qop_archive_class);
|
||||
JSValue archive_proto = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, archive_proto, js_qop_archive_funcs, countof(js_qop_archive_funcs));
|
||||
JS_SetClassProto(js, js_qop_archive_class_id, archive_proto);
|
||||
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod, js_qop_funcs, countof(js_qop_funcs));
|
||||
return mod;
|
||||
}
|
||||
8
source/qjs_qop.h
Normal file
8
source/qjs_qop.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef QJS_QOP_H
|
||||
#define QJS_QOP_H
|
||||
|
||||
#include "cell.h"
|
||||
|
||||
JSValue js_qop_use(JSContext *js);
|
||||
|
||||
#endif // QJS_QOP_H
|
||||
302
source/qjs_qr.c
302
source/qjs_qr.c
@@ -1,302 +0,0 @@
|
||||
#include "cell.h"
|
||||
#include "qjs_blob.h"
|
||||
|
||||
#include "quirc.h"
|
||||
#include "qrencode.h"
|
||||
|
||||
#include <math.h> // for sqrt
|
||||
#include <stdlib.h> // for size_t, etc.
|
||||
#include <string.h> // for memcpy, strcmp
|
||||
#include <stdint.h>
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
// QR encode function
|
||||
static JSValue js_qr_encode(JSContext *js, JSValueConst this_val, int argc, JSValueConst *argv) {
|
||||
if (argc < 1) {
|
||||
return JS_ThrowTypeError(js, "encode expects a string or ArrayBuffer as first argument");
|
||||
}
|
||||
|
||||
// Default options
|
||||
int version = 0; // 0 means auto-select in qrencode
|
||||
int errc = QR_ECLEVEL_L; // Default to "L"
|
||||
int casesensitive = 1; // Default to true (qrencode treats 1 as case-sensitive)
|
||||
int use_byte_mode = 0;
|
||||
|
||||
// Handle options object (argv[1])
|
||||
if (argc > 1 && !JS_IsNull(argv[1]) && JS_IsObject(argv[1])) {
|
||||
JSValue opt = argv[1];
|
||||
|
||||
JSValue m = JS_GetPropertyStr(js, opt, "mode");
|
||||
if (!JS_IsNull(m)) {
|
||||
const char *smode = JS_ToCString(js, m);
|
||||
if (!smode) { JS_FreeValue(js, m); return JS_EXCEPTION; }
|
||||
if (!strcasecmp(smode, "byte") || !strcasecmp(smode, "binary"))
|
||||
use_byte_mode = 1;
|
||||
else if (!strcasecmp(smode, "text"))
|
||||
use_byte_mode = 0;
|
||||
else {
|
||||
JS_FreeCString(js, smode); JS_FreeValue(js, m);
|
||||
return JS_ThrowRangeError(js, "mode must be 'byte' or 'text'");
|
||||
}
|
||||
JS_FreeCString(js, smode);
|
||||
JS_FreeValue(js, m);
|
||||
}
|
||||
|
||||
// Version (1-40)
|
||||
JSValue v = JS_GetPropertyStr(js, opt, "version");
|
||||
if (!JS_IsNull(v)) {
|
||||
int32_t ver;
|
||||
if (JS_ToInt32(js, &ver, v) || ver < 0 || ver > 40) {
|
||||
JS_FreeValue(js, v);
|
||||
return JS_ThrowRangeError(js, "version must be between 0 and 40");
|
||||
}
|
||||
version = ver;
|
||||
JS_FreeValue(js, v);
|
||||
}
|
||||
|
||||
// Error correction level ("l", "m", "q", "h")
|
||||
JSValue l = JS_GetPropertyStr(js, opt, "level");
|
||||
if (!JS_IsNull(l)) {
|
||||
const char *level = JS_ToCString(js, l);
|
||||
if (!level) {
|
||||
JS_FreeValue(js, l);
|
||||
return JS_ThrowTypeError(js, "level must be a string");
|
||||
}
|
||||
if (strcasecmp(level, "l") == 0) errc = QR_ECLEVEL_L;
|
||||
else if (strcasecmp(level, "m") == 0) errc = QR_ECLEVEL_M;
|
||||
else if (strcasecmp(level, "q") == 0) errc = QR_ECLEVEL_Q;
|
||||
else if (strcasecmp(level, "h") == 0) errc = QR_ECLEVEL_H;
|
||||
else {
|
||||
JS_FreeCString(js, level);
|
||||
JS_FreeValue(js, l);
|
||||
return JS_ThrowRangeError(js, "level must be 'l', 'm', 'q', or 'h'");
|
||||
}
|
||||
JS_FreeCString(js, level);
|
||||
JS_FreeValue(js, l);
|
||||
}
|
||||
|
||||
// Case sensitivity (true/false)
|
||||
JSValue ci = JS_GetPropertyStr(js, opt, "caseinsensitive");
|
||||
if (!JS_IsNull(ci)) {
|
||||
int bool_val;
|
||||
if (JS_ToBool(js, ci)) {
|
||||
bool_val = 0; // qrencode: 0 = case-insensitive
|
||||
} else {
|
||||
bool_val = 1; // qrencode: 1 = case-sensitive
|
||||
}
|
||||
casesensitive = bool_val;
|
||||
JS_FreeValue(js, ci);
|
||||
}
|
||||
}
|
||||
|
||||
const char *data = NULL;
|
||||
size_t data_len = 0;
|
||||
int must_free_data = 0;
|
||||
|
||||
// Handle string input
|
||||
if (!use_byte_mode && JS_IsString(argv[0])) {
|
||||
data = JS_ToCStringLen(js, &data_len, argv[0]);
|
||||
if (!data) {
|
||||
return JS_ThrowTypeError(js, "Invalid string input");
|
||||
}
|
||||
must_free_data = 1;
|
||||
} else {
|
||||
/* treat everything else as raw bytes */
|
||||
use_byte_mode = 1;
|
||||
uint8_t *buf = js_get_blob_data(js, &data_len, argv[0]);
|
||||
if (!buf) {
|
||||
return JS_ThrowTypeError(js, "encode expects a string or ArrayBuffer");
|
||||
}
|
||||
data = (const char *)buf; // Cast to char* for qrencode
|
||||
}
|
||||
|
||||
// Encode using qrencode
|
||||
QRcode *qrcode;
|
||||
if (use_byte_mode)
|
||||
qrcode = QRcode_encodeData(data_len, data, version, errc);
|
||||
else
|
||||
qrcode = QRcode_encodeString(data, version, errc, QR_MODE_8, casesensitive);
|
||||
|
||||
if (!qrcode) {
|
||||
if (must_free_data)
|
||||
JS_FreeCString(js, data);
|
||||
return JS_ThrowInternalError(js, "Failed to encode QR code: %s", strerror(errno));
|
||||
}
|
||||
|
||||
// qrcode->width is the size of one side
|
||||
// qrcode->data is width * width bytes
|
||||
printf("Encoded %d of data into a QR of width %d\n", data_len, qrcode->width);
|
||||
int width = qrcode->width;
|
||||
size_t size = (size_t)width * width; // total modules
|
||||
|
||||
// Create an array of booleans for the module data
|
||||
JSValue dataArr = JS_NewArray(js);
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
int is_black = (qrcode->data[i] & 1);
|
||||
JSValue val = JS_NewBool(js, is_black);
|
||||
JS_SetPropertyUint32(js, dataArr, (uint32_t)i, val);
|
||||
}
|
||||
|
||||
// Build a JS object to hold { width, data }
|
||||
JSValue result = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, result, "width", JS_NewInt32(js, width));
|
||||
JS_SetPropertyStr(js, result, "data", dataArr);
|
||||
|
||||
// Clean up
|
||||
QRcode_free(qrcode);
|
||||
if (must_free_data) {
|
||||
JS_FreeCString(js, data);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint8_t rgba_to_gray(uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
return (uint8_t)(( 299 * r + 587 * g + 114 * b + 500) / 1000);
|
||||
}
|
||||
|
||||
// QR decode function (unchanged)
|
||||
static JSValue js_qr_decode(JSContext *js, JSValueConst this_val, int argc, JSValueConst *argv) {
|
||||
size_t data_len;
|
||||
uint8_t *src;
|
||||
int w, h, pitch;
|
||||
src = js_get_blob_data(js, &data_len, argv[0]);
|
||||
|
||||
if (!src)
|
||||
return JS_ThrowTypeError(js, "decode expects an ArrayBuffer");
|
||||
|
||||
JS_ToInt32(js, &w, argv[1]);
|
||||
JS_ToInt32(js, &h, argv[2]);
|
||||
JS_ToInt32(js, &pitch, argv[3]);
|
||||
|
||||
if (w <= 0 || h <= 0)
|
||||
return JS_ThrowInternalError(js, "Bad width or height: %dx%d", w, h);
|
||||
|
||||
struct quirc *qr = quirc_new();
|
||||
if (!qr)
|
||||
return JS_ThrowInternalError(js, "Failed to initialize QR decoder");
|
||||
|
||||
if (quirc_resize(qr, w, h) < 0) {
|
||||
quirc_destroy(qr);
|
||||
return JS_ThrowInternalError(js, "quirc_resize failed");
|
||||
}
|
||||
|
||||
uint8_t *dst = quirc_begin(qr, NULL, NULL);
|
||||
|
||||
printf("decoding image size %dx%d, pitch %d and size %d\n", w, h, pitch, data_len);
|
||||
|
||||
for (int y = 0; y < h; ++y) {
|
||||
uint8_t *row = src + y * pitch;
|
||||
uint8_t *out = dst + y * w;
|
||||
for (int x = 0; x < w; ++x) {
|
||||
uint8_t a = row[x*4 + 3]; /* alpha */
|
||||
if (a < 128) { /* mostly transparent */
|
||||
out[x] = 255;
|
||||
} else {
|
||||
uint8_t r = row[x*4+0];
|
||||
uint8_t g = row[x*4+1];
|
||||
uint8_t b = row[x*4+2];
|
||||
out[x] = rgba_to_gray(r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
quirc_end(qr);
|
||||
|
||||
int count = quirc_count(qr);
|
||||
JSValue result = JS_NewArray(js);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
struct quirc_code code;
|
||||
struct quirc_data qdata;
|
||||
|
||||
quirc_extract(qr, i, &code);
|
||||
int err = quirc_decode(&code, &qdata);
|
||||
if (err == QUIRC_SUCCESS) {
|
||||
JSValue item = js_new_blob_stoned_copy(js, qdata.payload, qdata.payload_len);
|
||||
JS_SetPropertyUint32(js, result, i, item);
|
||||
} else {
|
||||
printf("QR error: %s\n", quirc_strerror(err));
|
||||
}
|
||||
}
|
||||
|
||||
quirc_destroy(qr);
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue js_qr_rgba(JSContext *js, JSValueConst self, int argc, JSValueConst *argv)
|
||||
{
|
||||
if (argc < 1)
|
||||
return JS_ThrowTypeError(js, "qr.rgba(mods, ...) needs a modules object");
|
||||
|
||||
JSValue mods = argv[0];
|
||||
|
||||
uint32_t on = 0xFF000000u; /* black opaque */
|
||||
uint32_t off = 0xFFFFFFFFu; /* white opaque */
|
||||
if (argc > 1) JS_ToUint32(js, &on, argv[1]);
|
||||
if (argc > 2) JS_ToUint32(js, &off, argv[2]);
|
||||
|
||||
JSValue js_w = JS_GetPropertyStr(js, mods, "width");
|
||||
if (JS_IsNull(js_w)) {
|
||||
JS_FreeValue(js, js_w);
|
||||
return JS_ThrowTypeError(js, "mods.width missing");
|
||||
}
|
||||
int32_t m = 0;
|
||||
JS_ToInt32(js, &m, js_w);
|
||||
JS_FreeValue(js, js_w);
|
||||
|
||||
if (m <= 0)
|
||||
return JS_ThrowRangeError(js, "mods.width must be > 0");
|
||||
|
||||
JSValue js_bits = JS_GetPropertyStr(js, mods, "data");
|
||||
size_t len = JS_ArrayLength(js, js_bits);
|
||||
|
||||
if (len < (size_t)m * (size_t)m) {
|
||||
JS_FreeValue(js, js_bits);
|
||||
return JS_ThrowRangeError(js, "mods.data too small for width²");
|
||||
}
|
||||
|
||||
const int w = m, h = m, pitch = w * 4;
|
||||
const size_t bytes = (size_t)h * (size_t)pitch;
|
||||
uint8_t *tmp = js_malloc(js, bytes);
|
||||
if (!tmp) { JS_FreeValue(js, js_bits); return JS_EXCEPTION; }
|
||||
|
||||
for (int y = 0; y < h; ++y) {
|
||||
uint32_t *row = (uint32_t *)(tmp + y * pitch);
|
||||
for (int x = 0; x < w; x++) {
|
||||
uint32_t idx = y * m + x;
|
||||
JSValue vbit = JS_GetPropertyUint32(js, js_bits, idx);
|
||||
int on_bit = JS_ToBool(js, vbit);
|
||||
JS_FreeValue(js, vbit);
|
||||
row[x] = on_bit ? on : off;
|
||||
}
|
||||
}
|
||||
|
||||
JSValue ab = js_new_blob_stoned_copy(js, tmp, bytes); /* GC owns copy */
|
||||
js_free(js, tmp);
|
||||
JS_FreeValue(js, js_bits); /* done with src */
|
||||
|
||||
JSValue obj = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, w));
|
||||
JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, h));
|
||||
JS_SetPropertyStr(js, obj, "pitch", JS_NewInt32(js, pitch));
|
||||
JS_SetPropertyStr(js, obj, "buffer", ab);
|
||||
JS_SetPropertyStr(js, obj, "format", JS_NewString(js, "rgba32"));
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Exported functions for the module
|
||||
static const JSCFunctionListEntry js_qr_funcs[] = {
|
||||
JS_CFUNC_DEF("encode", 2, js_qr_encode), // Updated to expect 2 args
|
||||
JS_CFUNC_DEF("decode", 4, js_qr_decode),
|
||||
JS_CFUNC_DEF("rgba", 3, js_qr_rgba),
|
||||
};
|
||||
|
||||
// Helper to return the default export object
|
||||
JSValue js_qr_use(JSContext *js) {
|
||||
JSValue exports = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, exports, js_qr_funcs, sizeof(js_qr_funcs) / sizeof(JSCFunctionListEntry));
|
||||
return exports;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
#ifndef QJS_QR_H
|
||||
#define QJS_QR_H
|
||||
|
||||
#include "cell.h"
|
||||
|
||||
JSValue js_qr_use(JSContext*);
|
||||
|
||||
#endif
|
||||
@@ -84,7 +84,7 @@ typedef struct {
|
||||
|
||||
// Open an archive at path. The supplied qop_desc will be filled with the
|
||||
// information from the file header. Returns the size of the archvie or 0 on
|
||||
// failure
|
||||
// failure.
|
||||
int qop_open(const char *path, qop_desc *qop);
|
||||
|
||||
// Read the index from an opened archive. The supplied buffer will be filled
|
||||
@@ -94,10 +94,10 @@ int qop_open(const char *path, qop_desc *qop);
|
||||
// Returns the number of files in the archive or 0 on error.
|
||||
int qop_read_index(qop_desc *qop, void *buffer);
|
||||
|
||||
// Close the archive
|
||||
// Close the archive.
|
||||
void qop_close(qop_desc *qop);
|
||||
|
||||
// Find a file with the supplied path. Returns NULL if the file is not found
|
||||
// Find a file with the supplied path. Returns NULL if the file is not found.
|
||||
qop_file *qop_find(qop_desc *qop, const char *path);
|
||||
|
||||
// Copy the path of the file into dest. The dest buffer must be at least
|
||||
@@ -107,7 +107,7 @@ int qop_read_path(qop_desc *qop, qop_file *file, char *dest);
|
||||
|
||||
// Read the whole file into dest. The dest buffer must be at least file->size
|
||||
// bytes long.
|
||||
// Returns the number of bytes read
|
||||
// Returns the number of bytes read.
|
||||
int qop_read(qop_desc *qop, qop_file *file, unsigned char *dest);
|
||||
|
||||
// Read part of a file into dest. The dest buffer must be at least len bytes
|
||||
|
||||
Reference in New Issue
Block a user