@neoanaloglabkk/lensfun-wasm 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,487 @@
1
+ #include "lensfun.h"
2
+
3
+ #include <stdint.h>
4
+ #include <stdio.h>
5
+ #include <stdlib.h>
6
+ #include <string.h>
7
+
8
+ #include <algorithm>
9
+ #include <sstream>
10
+ #include <string>
11
+
12
+ #if defined(__EMSCRIPTEN__)
13
+ #include <emscripten/emscripten.h>
14
+ #define LFW_EXPORT EMSCRIPTEN_KEEPALIVE
15
+ #else
16
+ #define LFW_EXPORT
17
+ #endif
18
+
19
+ namespace
20
+ {
21
+ lfDatabase *g_db = nullptr;
22
+
23
+ void append_json_escaped(std::ostringstream &out, const char *value)
24
+ {
25
+ out << '"';
26
+ if (value)
27
+ {
28
+ for (const char *p = value; *p; ++p)
29
+ {
30
+ const unsigned char c = static_cast<unsigned char>(*p);
31
+ switch (c)
32
+ {
33
+ case '\\':
34
+ out << "\\\\";
35
+ break;
36
+ case '"':
37
+ out << "\\\"";
38
+ break;
39
+ case '\n':
40
+ out << "\\n";
41
+ break;
42
+ case '\r':
43
+ out << "\\r";
44
+ break;
45
+ case '\t':
46
+ out << "\\t";
47
+ break;
48
+ default:
49
+ if (c < 0x20)
50
+ {
51
+ out << "\\u00";
52
+ const char hex[] = "0123456789abcdef";
53
+ out << hex[(c >> 4) & 0x0F] << hex[c & 0x0F];
54
+ }
55
+ else
56
+ {
57
+ out << *p;
58
+ }
59
+ break;
60
+ }
61
+ }
62
+ }
63
+ out << '"';
64
+ }
65
+
66
+ char *dup_cstr(const std::string &s)
67
+ {
68
+ auto *buf = static_cast<char *>(malloc(s.size() + 1));
69
+ if (!buf)
70
+ {
71
+ return nullptr;
72
+ }
73
+ memcpy(buf, s.data(), s.size());
74
+ buf[s.size()] = '\0';
75
+ return buf;
76
+ }
77
+
78
+ const lfLens *resolve_lens(uint32_t lens_handle)
79
+ {
80
+ if (!g_db || lens_handle == 0)
81
+ {
82
+ return nullptr;
83
+ }
84
+
85
+ const auto *target = reinterpret_cast<const lfLens *>(static_cast<uintptr_t>(lens_handle));
86
+ const lfLens *const *all_lenses = lf_db_get_lenses(g_db);
87
+ if (!all_lenses)
88
+ {
89
+ return nullptr;
90
+ }
91
+
92
+ for (size_t i = 0; all_lenses[i] != nullptr; ++i)
93
+ {
94
+ if (all_lenses[i] == target)
95
+ {
96
+ return target;
97
+ }
98
+ }
99
+
100
+ return nullptr;
101
+ }
102
+
103
+ int grid_points(int size, int step)
104
+ {
105
+ if (size <= 0 || step <= 0)
106
+ {
107
+ return 0;
108
+ }
109
+ return ((size - 1) / step) + 1;
110
+ }
111
+
112
+ float sample_coord(int index, int step, int bound)
113
+ {
114
+ const int value = index * step;
115
+ const int clamped = std::min(value, std::max(bound - 1, 0));
116
+ return static_cast<float>(clamped);
117
+ }
118
+ } // namespace
119
+
120
+ extern "C" {
121
+
122
+ LFW_EXPORT int32_t lfw_init(const char *db_dir)
123
+ {
124
+ if (g_db)
125
+ {
126
+ lf_db_destroy(g_db);
127
+ g_db = nullptr;
128
+ }
129
+
130
+ g_db = lf_db_create();
131
+ if (!g_db)
132
+ {
133
+ return -1;
134
+ }
135
+
136
+ const char *path = db_dir ? db_dir : "/lensfun-db";
137
+ const lfError err = lf_db_load_path(g_db, path);
138
+ return static_cast<int32_t>(err);
139
+ }
140
+
141
+ LFW_EXPORT void lfw_dispose(void)
142
+ {
143
+ if (g_db)
144
+ {
145
+ lf_db_destroy(g_db);
146
+ g_db = nullptr;
147
+ }
148
+ }
149
+
150
+ LFW_EXPORT char *lfw_find_lenses_json(
151
+ const char *camera_maker,
152
+ const char *camera_model,
153
+ const char *lens_maker,
154
+ const char *lens_model,
155
+ int32_t search_flags)
156
+ {
157
+ if (!g_db)
158
+ {
159
+ return dup_cstr("[]");
160
+ }
161
+
162
+ const lfCamera *camera = nullptr;
163
+ const lfCamera **cameras_to_free = nullptr;
164
+ if (camera_maker || camera_model)
165
+ {
166
+ cameras_to_free = lf_db_find_cameras(g_db, camera_maker, camera_model);
167
+ if (cameras_to_free)
168
+ {
169
+ camera = cameras_to_free[0];
170
+ }
171
+ }
172
+
173
+ const lfLens **lenses = lf_db_find_lenses(g_db, camera, lens_maker, lens_model, search_flags);
174
+
175
+ std::ostringstream out;
176
+ out << '[';
177
+ bool first = true;
178
+ if (lenses)
179
+ {
180
+ for (size_t i = 0; lenses[i] != nullptr; ++i)
181
+ {
182
+ const lfLens *lens = lenses[i];
183
+ if (!first)
184
+ {
185
+ out << ',';
186
+ }
187
+ first = false;
188
+
189
+ const uint32_t handle = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(lens));
190
+ out << "{\"handle\":" << handle << ",\"maker\":";
191
+ append_json_escaped(out, lens->Maker ? lf_mlstr_get(lens->Maker) : "");
192
+ out << ",\"model\":";
193
+ append_json_escaped(out, lens->Model ? lf_mlstr_get(lens->Model) : "");
194
+ out << ",\"score\":" << lens->Score;
195
+ out << ",\"minFocal\":" << lens->MinFocal;
196
+ out << ",\"maxFocal\":" << lens->MaxFocal;
197
+ out << ",\"minAperture\":" << lens->MinAperture;
198
+ out << ",\"maxAperture\":" << lens->MaxAperture;
199
+ out << ",\"cropFactor\":" << lens->CropFactor;
200
+ out << '}';
201
+ }
202
+ lf_free(lenses);
203
+ }
204
+
205
+ if (cameras_to_free)
206
+ {
207
+ lf_free(cameras_to_free);
208
+ }
209
+
210
+ out << ']';
211
+ return dup_cstr(out.str());
212
+ }
213
+
214
+ LFW_EXPORT char *lfw_find_cameras_json(const char *maker, const char *model, int32_t search_flags)
215
+ {
216
+ (void)search_flags;
217
+
218
+ if (!g_db)
219
+ {
220
+ return dup_cstr("[]");
221
+ }
222
+
223
+ const lfCamera **cameras = lf_db_find_cameras_ext(g_db, maker, model, search_flags);
224
+
225
+ std::ostringstream out;
226
+ out << '[';
227
+ bool first = true;
228
+
229
+ if (cameras)
230
+ {
231
+ for (size_t i = 0; cameras[i] != nullptr; ++i)
232
+ {
233
+ const lfCamera *camera = cameras[i];
234
+ if (!first)
235
+ {
236
+ out << ',';
237
+ }
238
+ first = false;
239
+
240
+ out << "{\"maker\":";
241
+ append_json_escaped(out, camera->Maker ? lf_mlstr_get(camera->Maker) : "");
242
+ out << ",\"model\":";
243
+ append_json_escaped(out, camera->Model ? lf_mlstr_get(camera->Model) : "");
244
+ out << ",\"variant\":";
245
+ append_json_escaped(out, camera->Variant ? lf_mlstr_get(camera->Variant) : "");
246
+ out << ",\"mount\":";
247
+ append_json_escaped(out, camera->Mount ? camera->Mount : "");
248
+ out << ",\"cropFactor\":" << camera->CropFactor;
249
+ out << ",\"score\":" << camera->Score;
250
+ out << '}';
251
+ }
252
+
253
+ lf_free(cameras);
254
+ }
255
+
256
+ out << ']';
257
+ return dup_cstr(out.str());
258
+ }
259
+
260
+ LFW_EXPORT int32_t lfw_available_mods(uint32_t lens_handle, float crop)
261
+ {
262
+ const lfLens *lens = resolve_lens(lens_handle);
263
+ if (!lens)
264
+ {
265
+ return 0;
266
+ }
267
+
268
+ return lf_lens_available_modifications(const_cast<lfLens *>(lens), crop);
269
+ }
270
+
271
+ LFW_EXPORT int32_t lfw_build_geometry_map(
272
+ uint32_t lens_handle,
273
+ float focal,
274
+ float crop,
275
+ int32_t width,
276
+ int32_t height,
277
+ int32_t reverse,
278
+ int32_t step,
279
+ float *out_xy,
280
+ int32_t out_len)
281
+ {
282
+ const lfLens *lens = resolve_lens(lens_handle);
283
+ if (!lens || !out_xy || width <= 0 || height <= 0 || step <= 0)
284
+ {
285
+ return -1;
286
+ }
287
+
288
+ const int gx = grid_points(width, step);
289
+ const int gy = grid_points(height, step);
290
+ const int needed = gx * gy * 2;
291
+ if (out_len < needed)
292
+ {
293
+ return -2;
294
+ }
295
+
296
+ lfModifier *modifier = lf_modifier_create(
297
+ lens,
298
+ focal,
299
+ crop,
300
+ width,
301
+ height,
302
+ LF_PF_F32,
303
+ reverse != 0);
304
+
305
+ if (!modifier)
306
+ {
307
+ return -3;
308
+ }
309
+
310
+ lf_modifier_enable_distortion_correction(modifier);
311
+
312
+ int cursor = 0;
313
+ float result[2] = {0.0f, 0.0f};
314
+ for (int y = 0; y < gy; ++y)
315
+ {
316
+ const float py = sample_coord(y, step, height);
317
+ for (int x = 0; x < gx; ++x)
318
+ {
319
+ const float px = sample_coord(x, step, width);
320
+ if (!lf_modifier_apply_geometry_distortion(modifier, px, py, 1, 1, result))
321
+ {
322
+ lf_modifier_destroy(modifier);
323
+ return -4;
324
+ }
325
+ out_xy[cursor++] = result[0];
326
+ out_xy[cursor++] = result[1];
327
+ }
328
+ }
329
+
330
+ lf_modifier_destroy(modifier);
331
+ return 0;
332
+ }
333
+
334
+ LFW_EXPORT int32_t lfw_build_tca_map(
335
+ uint32_t lens_handle,
336
+ float focal,
337
+ float crop,
338
+ int32_t width,
339
+ int32_t height,
340
+ int32_t reverse,
341
+ int32_t step,
342
+ float *out_rgbxy,
343
+ int32_t out_len)
344
+ {
345
+ const lfLens *lens = resolve_lens(lens_handle);
346
+ if (!lens || !out_rgbxy || width <= 0 || height <= 0 || step <= 0)
347
+ {
348
+ return -1;
349
+ }
350
+
351
+ const int gx = grid_points(width, step);
352
+ const int gy = grid_points(height, step);
353
+ const int needed = gx * gy * 6;
354
+ if (out_len < needed)
355
+ {
356
+ return -2;
357
+ }
358
+
359
+ lfModifier *modifier = lf_modifier_create(
360
+ lens,
361
+ focal,
362
+ crop,
363
+ width,
364
+ height,
365
+ LF_PF_F32,
366
+ reverse != 0);
367
+
368
+ if (!modifier)
369
+ {
370
+ return -3;
371
+ }
372
+
373
+ lf_modifier_enable_tca_correction(modifier);
374
+
375
+ int cursor = 0;
376
+ float result[6] = {0};
377
+ for (int y = 0; y < gy; ++y)
378
+ {
379
+ const float py = sample_coord(y, step, height);
380
+ for (int x = 0; x < gx; ++x)
381
+ {
382
+ const float px = sample_coord(x, step, width);
383
+ if (!lf_modifier_apply_subpixel_distortion(modifier, px, py, 1, 1, result))
384
+ {
385
+ lf_modifier_destroy(modifier);
386
+ return -4;
387
+ }
388
+ for (int k = 0; k < 6; ++k)
389
+ {
390
+ out_rgbxy[cursor++] = result[k];
391
+ }
392
+ }
393
+ }
394
+
395
+ lf_modifier_destroy(modifier);
396
+ return 0;
397
+ }
398
+
399
+ LFW_EXPORT int32_t lfw_build_vignetting_map(
400
+ uint32_t lens_handle,
401
+ float focal,
402
+ float crop,
403
+ float aperture,
404
+ float distance,
405
+ int32_t width,
406
+ int32_t height,
407
+ int32_t reverse,
408
+ int32_t step,
409
+ float *out_rgb_gain,
410
+ int32_t out_len)
411
+ {
412
+ const lfLens *lens = resolve_lens(lens_handle);
413
+ if (!lens || !out_rgb_gain || width <= 0 || height <= 0 || step <= 0)
414
+ {
415
+ return -1;
416
+ }
417
+
418
+ const int gx = grid_points(width, step);
419
+ const int gy = grid_points(height, step);
420
+ const int needed = gx * gy * 3;
421
+ if (out_len < needed)
422
+ {
423
+ return -2;
424
+ }
425
+
426
+ lfModifier *modifier = lf_modifier_create(
427
+ lens,
428
+ focal,
429
+ crop,
430
+ width,
431
+ height,
432
+ LF_PF_F32,
433
+ reverse != 0);
434
+
435
+ if (!modifier)
436
+ {
437
+ return -3;
438
+ }
439
+
440
+ if (!lf_modifier_enable_vignetting_correction(modifier, aperture, distance))
441
+ {
442
+ lf_modifier_destroy(modifier);
443
+ return -4;
444
+ }
445
+
446
+ int cursor = 0;
447
+ float pixel[3] = {1.0f, 1.0f, 1.0f};
448
+ for (int y = 0; y < gy; ++y)
449
+ {
450
+ const float py = sample_coord(y, step, height);
451
+ for (int x = 0; x < gx; ++x)
452
+ {
453
+ const float px = sample_coord(x, step, width);
454
+ pixel[0] = 1.0f;
455
+ pixel[1] = 1.0f;
456
+ pixel[2] = 1.0f;
457
+
458
+ if (!lf_modifier_apply_color_modification(
459
+ modifier,
460
+ pixel,
461
+ px,
462
+ py,
463
+ 1,
464
+ 1,
465
+ LF_CR_3(RED, GREEN, BLUE),
466
+ 0))
467
+ {
468
+ lf_modifier_destroy(modifier);
469
+ return -5;
470
+ }
471
+
472
+ out_rgb_gain[cursor++] = pixel[0];
473
+ out_rgb_gain[cursor++] = pixel[1];
474
+ out_rgb_gain[cursor++] = pixel[2];
475
+ }
476
+ }
477
+
478
+ lf_modifier_destroy(modifier);
479
+ return 0;
480
+ }
481
+
482
+ LFW_EXPORT void lfw_free(void *p)
483
+ {
484
+ free(p);
485
+ }
486
+
487
+ } // extern "C"
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@neoanaloglabkk/lensfun-wasm",
3
+ "version": "0.1.1",
4
+ "description": "Lensfun compiled to WebAssembly with a high-level JS API for browser frontends.",
5
+ "license": "LGPL-3.0-or-later",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/lexluthor0304/LensfunWasm.git"
9
+ },
10
+ "type": "module",
11
+ "main": "./dist/umd/index.iife.js",
12
+ "module": "./dist/esm/index.js",
13
+ "types": "./dist/types/index.d.ts",
14
+ "files": [
15
+ "dist",
16
+ "native",
17
+ "scripts",
18
+ "UPSTREAM.md",
19
+ "NOTICE.md",
20
+ "THIRD_PARTY_LICENSES.md"
21
+ ],
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/types/index.d.ts",
25
+ "import": "./dist/esm/index.js",
26
+ "default": "./dist/umd/index.iife.js"
27
+ },
28
+ "./core": {
29
+ "default": "./dist/assets/lensfun-core.js"
30
+ },
31
+ "./core-wasm": {
32
+ "default": "./dist/assets/lensfun-core.wasm"
33
+ },
34
+ "./core-data": {
35
+ "default": "./dist/assets/lensfun-core.data"
36
+ }
37
+ },
38
+ "scripts": {
39
+ "build:wasm": "bash scripts/build-wasm.sh",
40
+ "build:js": "tsup",
41
+ "build:types": "tsc --emitDeclarationOnly",
42
+ "build": "npm run build:wasm && npm run build:js && npm run build:types",
43
+ "test": "vitest run",
44
+ "check": "npm run build:js && npm run build:types && npm run test",
45
+ "pack:dry-run": "npm pack --dry-run",
46
+ "release:verify-tag": "node scripts/verify-release-tag.mjs"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^22.13.10",
50
+ "tsup": "^8.3.0",
51
+ "typescript": "^5.8.2",
52
+ "vitest": "^2.1.9"
53
+ }
54
+ }
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ BUILD_DIR="${ROOT_DIR}/wasm-build"
6
+ DIST_ASSETS_DIR="${ROOT_DIR}/dist/assets"
7
+
8
+ if ! command -v emcc >/dev/null 2>&1; then
9
+ cat >&2 <<'MSG'
10
+ [build-wasm] emcc not found.
11
+ Install and activate Emscripten first, for example:
12
+ git clone https://github.com/emscripten-core/emsdk.git
13
+ cd emsdk
14
+ ./emsdk install latest
15
+ ./emsdk activate latest
16
+ source ./emsdk_env.sh
17
+ MSG
18
+ exit 1
19
+ fi
20
+
21
+ rm -rf "${BUILD_DIR}"
22
+ mkdir -p "${BUILD_DIR}" "${DIST_ASSETS_DIR}"
23
+
24
+ emcmake cmake -S "${ROOT_DIR}/native" -B "${BUILD_DIR}" -DCMAKE_BUILD_TYPE=Release
25
+ cmake --build "${BUILD_DIR}" --target lensfun-core -j
26
+
27
+ cp "${BUILD_DIR}/lensfun-core.js" "${DIST_ASSETS_DIR}/lensfun-core.js"
28
+ cp "${BUILD_DIR}/lensfun-core.wasm" "${DIST_ASSETS_DIR}/lensfun-core.wasm"
29
+ cp "${BUILD_DIR}/lensfun-core.data" "${DIST_ASSETS_DIR}/lensfun-core.data"
30
+
31
+ echo "[build-wasm] built assets in ${DIST_ASSETS_DIR}"
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ SUBMODULE_DIR="${ROOT_DIR}/third_party/lensfun"
6
+
7
+ if [[ ! -d "${SUBMODULE_DIR}" ]]; then
8
+ echo "[sync-upstream] lensfun submodule not found at ${SUBMODULE_DIR}" >&2
9
+ exit 1
10
+ fi
11
+
12
+ pushd "${SUBMODULE_DIR}" >/dev/null
13
+
14
+ git fetch origin
15
+ TARGET="${1:-origin/master}"
16
+ git checkout "${TARGET}"
17
+
18
+ NEW_COMMIT="$(git rev-parse HEAD)"
19
+ NEW_SUBJECT="$(git log -1 --format=%s)"
20
+
21
+ popd >/dev/null
22
+
23
+ echo "[sync-upstream] lensfun updated to ${NEW_COMMIT}"
24
+ echo "[sync-upstream] ${NEW_SUBJECT}"
25
+ echo "[sync-upstream] run tests and update UPSTREAM.md before committing"
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import process from 'node:process';
6
+
7
+ const root = process.cwd();
8
+ const packageJsonPath = path.join(root, 'package.json');
9
+
10
+ if (!fs.existsSync(packageJsonPath)) {
11
+ console.error('[release] package.json not found in current directory');
12
+ process.exit(1);
13
+ }
14
+
15
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
16
+ const version = pkg.version;
17
+ const tagArg = process.argv[2] || process.env.GITHUB_REF_NAME || '';
18
+
19
+ if (!version || typeof version !== 'string') {
20
+ console.error('[release] package.json version is missing or invalid');
21
+ process.exit(1);
22
+ }
23
+
24
+ if (!tagArg) {
25
+ console.error('[release] missing tag argument. Usage: node scripts/verify-release-tag.mjs vX.Y.Z');
26
+ process.exit(1);
27
+ }
28
+
29
+ if (!tagArg.startsWith('v')) {
30
+ console.error(`[release] invalid tag "${tagArg}". Tag must start with "v".`);
31
+ process.exit(1);
32
+ }
33
+
34
+ const tagVersion = tagArg.slice(1);
35
+
36
+ if (tagVersion !== version) {
37
+ console.error(`[release] tag/version mismatch: tag=${tagArg}, package.json=${version}`);
38
+ process.exit(1);
39
+ }
40
+
41
+ console.log(`[release] tag/version check passed: ${tagArg} == ${version}`);