LensSerious 0.1
Lens-correction mathematics as data, not as a library of callbacks
Loading...
Searching...
No Matches
lensserious_db.c
Go to the documentation of this file.
1/*
2 LensSerious — reading the lens database.
3
4 Copyright (C) 2026 Aurélien PIERRE. License: LGPL-3.0-or-later.
5
6 See include/lensserious_db.h for the contract. The short version: the file is opened
7 read-only and immutable, so SQLite takes no file locks and keeps no shared state; the
8 handle is opened NOMUTEX, so SQLite takes no mutex either. Neither does this file.
9 What makes that safe is that a database is replaced by rename and never edited.
10*/
11
12#include "lensserious_db.h"
13
14#include <sqlite3.h>
15#include <stdio.h>
16#include <stdlib.h>
17#include <string.h>
18
19#define LS_DB_SCHEMA_VERSION 4
20
21struct ls_db_t
22{
23 sqlite3 *sql;
25 char error[256];
26};
27
28static void _db_err(ls_db_t *db, const char *what)
29{
30 if(!db) return;
31 const char *detail = db->sql ? sqlite3_errmsg(db->sql) : "no connection";
32 snprintf(db->error, sizeof(db->error), "%s: %s", what, detail);
33}
34
46static char *_db_build_uri(const char *path)
47{
48 if(!path || !*path) return NULL;
49
50 const int is_uri = strncmp(path, "file:", 5) == 0;
51 const size_t len = strlen(path);
52 /* Worst case: every byte percent-encoded, plus the scheme and our parameters. */
53 char *uri = (char *)malloc(len * 3 + 64);
54 if(!uri) return NULL;
55
56 size_t w = 0;
57 if(is_uri)
58 {
59 memcpy(uri, path, len);
60 w = len;
61 }
62 else
63 {
64 memcpy(uri, "file:", 5);
65 w = 5;
66 for(size_t i = 0; i < len; i++)
67 {
68 const unsigned char c = (unsigned char)path[i];
69 if(c == '?' || c == '#' || c == '%')
70 {
71 static const char hex[] = "0123456789ABCDEF";
72 uri[w++] = '%';
73 uri[w++] = hex[c >> 4];
74 uri[w++] = hex[c & 0xF];
75 }
76 else
77 uri[w++] = (char)c;
78 }
79 }
80
81 const char *sep = strchr(uri, '?') ? "&" : "?";
82 w += (size_t)snprintf(uri + w, 64, "%smode=ro&immutable=1", sep);
83 uri[w] = '\0';
84 return uri;
85}
86
87ls_db_t *ls_db_open(const char *path)
88{
89 char *uri = _db_build_uri(path);
90 if(!uri) return NULL;
91
92 ls_db_t *db = (ls_db_t *)calloc(1, sizeof(ls_db_t));
93 if(!db)
94 {
95 free(uri);
96 return NULL;
97 }
98 db->schema_version = -1;
99
100 /* NOMUTEX: this handle belongs to one thread and SQLite need not defend it. READONLY
101 * and the immutable URI parameter between them mean no locking at all. */
102 const int rc = sqlite3_open_v2(uri, &db->sql,
103 SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_URI,
104 NULL);
105 free(uri);
106 if(rc != SQLITE_OK)
107 {
108 ls_db_close(db);
109 return NULL;
110 }
111
112 sqlite3_stmt *st = NULL;
113 if(sqlite3_prepare_v2(db->sql, "PRAGMA user_version", -1, &st, NULL) == SQLITE_OK
114 && sqlite3_step(st) == SQLITE_ROW)
115 db->schema_version = sqlite3_column_int(st, 0);
116 sqlite3_finalize(st);
117
119 {
120 ls_db_close(db);
121 return NULL;
122 }
123 return db;
124}
125
127{
128 if(!db) return;
129 if(db->sql) sqlite3_close(db->sql);
130 free(db);
131}
132
133const char *ls_db_error(const ls_db_t *db)
134{
135 return (db && db->error[0]) ? db->error : NULL;
136}
137
139{
140 return db ? db->schema_version : -1;
141}
142
143/* ------------------------------------------------------------------------- */
144/* Name normalisation. Must match tools/import_lensfun_xml.c byte for byte, */
145/* since that is what filled the `norm` columns being compared against. */
146/* ------------------------------------------------------------------------- */
147
156size_t ls_db_normalize(const char *in, char *out, size_t out_size)
157{
158 if(!out || out_size == 0) return 0;
159 size_t w = 0;
160 int pending_space = 0;
161
162 for(const unsigned char *p = (const unsigned char *)(in ? in : ""); *p; p++)
163 {
164 unsigned char c = *p;
165 if(c <= ' ' || c == '_' || c == '-' || c == '/' || c == '\\' || c == ',' || c == '.'
166 || c == '(' || c == ')' || c == '[' || c == ']' || c == '\'' || c == '"' || c == ':'
167 || c == ';' || c == '+' || c == '*')
168 {
169 if(w > 0) pending_space = 1;
170 continue;
171 }
172 if(c >= 'A' && c <= 'Z') c = (unsigned char)(c - 'A' + 'a');
173
174 if(pending_space)
175 {
176 if(w + 1 >= out_size) break;
177 out[w++] = ' ';
178 pending_space = 0;
179 }
180 if(w + 1 >= out_size) break;
181 out[w++] = (char)c;
182 }
183 out[w] = '\0';
184 return w;
185}
186
187/* ------------------------------------------------------------------------- */
188
189static int _fill_calibrations(ls_db_t *db, long long lens_id, ls_lens_t *out)
190{
191 static const struct
192 {
193 const char *sql;
194 int terms;
195 } q[3] = {
196 { "SELECT model, focal, t0, t1, t2 FROM calib_distortion WHERE lens_id = ?1 ORDER BY ord", 3 },
197 { "SELECT model, focal, t0, t1, t2, t3, t4, t5 FROM calib_tca WHERE lens_id = ?1 ORDER BY ord", 6 },
198 { "SELECT model, focal, aperture, distance, t0, t1, t2 FROM calib_vignetting WHERE lens_id = ?1 ORDER BY ord", 3 },
199 };
200
201 for(int kind = 0; kind < 3; kind++)
202 {
203 sqlite3_stmt *st = NULL;
204 if(sqlite3_prepare_v2(db->sql, q[kind].sql, -1, &st, NULL) != SQLITE_OK)
205 {
206 _db_err(db, "prepare calibration");
207 return -1;
208 }
209 sqlite3_bind_int64(st, 1, lens_id);
210
211 int n = 0;
212 int overflow = 0;
213 while(sqlite3_step(st) == SQLITE_ROW)
214 {
215 if(n >= LS_MAX_CALIB)
216 {
217 overflow = 1;
218 break;
219 }
220 const int model = sqlite3_column_int(st, 0);
221 const float focal = (float)sqlite3_column_double(st, 1);
222
223 if(kind == 0)
224 {
225 ls_calib_dist_t *c = &out->dist[n];
226 c->model = (ls_dist_model_t)model;
227 c->focal = focal;
228 for(int t = 0; t < 3; t++) c->terms[t] = (float)sqlite3_column_double(st, 2 + t);
229 }
230 else if(kind == 1)
231 {
232 ls_calib_tca_t *c = &out->tca[n];
233 c->model = (ls_tca_model_t)model;
234 c->focal = focal;
235 for(int t = 0; t < 6; t++) c->terms[t] = (float)sqlite3_column_double(st, 2 + t);
236 }
237 else
238 {
239 ls_calib_vig_t *c = &out->vig[n];
240 c->model = (ls_vig_model_t)model;
241 c->focal = focal;
242 c->aperture = (float)sqlite3_column_double(st, 2);
243 c->distance = (float)sqlite3_column_double(st, 3);
244 for(int t = 0; t < 3; t++) c->terms[t] = (float)sqlite3_column_double(st, 4 + t);
245 }
246 n++;
247 }
248 sqlite3_finalize(st);
249
250 if(overflow)
251 {
252 /* Truncating would corrupt both the IDW weighting and the spline neighbours, so this
253 * is an error rather than a partial answer. LS_MAX_CALIB is 512 against a densest
254 * real lens of 440 vignetting points; a database that trips this needs the constant
255 * raised, not the rows dropped. */
256 snprintf(db->error, sizeof(db->error),
257 "lens %lld has more than %d calibration entries of one kind",
258 lens_id, (int)LS_MAX_CALIB);
259 return -1;
260 }
261
262 if(kind == 0) out->n_dist = n;
263 else if(kind == 1) out->n_tca = n;
264 else out->n_vig = n;
265 }
266
267 /* <real-focal-length>. Its own query rather than a fourth entry in the table above: it
268 * has no model and no terms, and bending the shared loop around that would cost more than
269 * the six lines it saves. It is also ordered by focal rather than by ord, because the
270 * spline picks neighbours by focal distance and a file written in any other order would
271 * still have to be sorted somewhere. */
272 {
273 sqlite3_stmt *st = NULL;
274 if(sqlite3_prepare_v2(db->sql,
275 "SELECT focal, real_focal FROM lens_real_focal WHERE lens_id = ?1"
276 " ORDER BY focal", -1, &st, NULL) != SQLITE_OK)
277 {
278 _db_err(db, "prepare real focal");
279 return -1;
280 }
281 sqlite3_bind_int64(st, 1, lens_id);
282 int n = 0;
283 while(sqlite3_step(st) == SQLITE_ROW && n < LS_MAX_CALIB)
284 {
285 out->real_focal[n].focal = (float)sqlite3_column_double(st, 0);
286 out->real_focal[n].real_focal = (float)sqlite3_column_double(st, 1);
287 n++;
288 }
289 sqlite3_finalize(st);
290 out->n_real_focal = n;
291 }
292 return 0;
293}
294
295int ls_db_lens_by_id(ls_db_t *db, long long lens_id, ls_lens_t *out)
296{
297 if(!db || !db->sql || !out) return -1;
298 memset(out, 0, sizeof(*out));
299
300 sqlite3_stmt *st = NULL;
301 if(sqlite3_prepare_v2(db->sql,
302 "SELECT type, crop_factor, aspect_ratio, center_x, center_y,"
303 " min_focal, max_focal FROM lens WHERE id = ?1",
304 -1, &st, NULL) != SQLITE_OK)
305 {
306 _db_err(db, "prepare lens");
307 return -1;
308 }
309 sqlite3_bind_int64(st, 1, lens_id);
310
311 if(sqlite3_step(st) != SQLITE_ROW)
312 {
313 sqlite3_finalize(st);
314 return 0;
315 }
316 out->type = (ls_lens_type_t)sqlite3_column_int(st, 0);
317 out->crop_factor = (float)sqlite3_column_double(st, 1);
318 out->aspect_ratio = (float)sqlite3_column_double(st, 2);
319 out->center_x = (float)sqlite3_column_double(st, 3);
320 out->center_y = (float)sqlite3_column_double(st, 4);
321 out->min_focal = (float)sqlite3_column_double(st, 5);
322 out->max_focal = (float)sqlite3_column_double(st, 6);
323 sqlite3_finalize(st);
324
325 return (_fill_calibrations(db, lens_id, out) == 0) ? 1 : -1;
326}
327
332static long long _find_lens_id(ls_db_t *db, const char *maker, const char *model, float crop)
333{
334 char nmodel[512], nmaker[512];
335 ls_db_normalize(model, nmodel, sizeof(nmodel));
336 ls_db_normalize(maker, nmaker, sizeof(nmaker));
337
338 /* One statement whether or not a maker was given: ?3 < 0 disables the maker test, so
339 * there is a single query plan to reason about rather than two that can drift. */
340 sqlite3_stmt *st = NULL;
341 if(sqlite3_prepare_v2(db->sql,
342 "SELECT l.id FROM lens l"
343 " JOIN lens_name m ON m.lens_id = l.id AND m.kind = 'model' AND m.norm = ?1"
344 " WHERE (?3 = 0 OR EXISTS (SELECT 1 FROM lens_name k"
345 " WHERE k.lens_id = l.id AND k.kind = 'maker' AND k.norm = ?2))"
346 " ORDER BY abs(l.crop_factor - ?4) ASC, l.id ASC LIMIT 1",
347 -1, &st, NULL) != SQLITE_OK)
348 {
349 _db_err(db, "prepare lens lookup");
350 return -1;
351 }
352 sqlite3_bind_text(st, 1, nmodel, -1, SQLITE_STATIC);
353 sqlite3_bind_text(st, 2, nmaker, -1, SQLITE_STATIC);
354 sqlite3_bind_int(st, 3, (maker && *nmaker) ? 1 : 0);
355 sqlite3_bind_double(st, 4, (crop > 0.f) ? (double)crop : 0.0);
356
357 long long id = 0;
358 if(sqlite3_step(st) == SQLITE_ROW) id = sqlite3_column_int64(st, 0);
359 sqlite3_finalize(st);
360 return id;
361}
362
363int ls_db_find_lens(ls_db_t *db, const char *maker, const char *model, float crop,
364 ls_lens_t *out)
365{
366 if(!db || !db->sql || !model || !out) return -1;
367
368 const long long id = _find_lens_id(db, maker, model, crop);
369 if(id < 0) return -1;
370 if(id == 0) return 0;
371 return ls_db_lens_by_id(db, id, out);
372}
373
374int ls_db_find_camera(ls_db_t *db, const char *maker, const char *model, ls_camera_t *out)
375{
376 if(!db || !db->sql || !model || !out) return -1;
377 memset(out, 0, sizeof(*out));
378
379 char nmodel[512], nmaker[512];
380 ls_db_normalize(model, nmodel, sizeof(nmodel));
381 ls_db_normalize(maker, nmaker, sizeof(nmaker));
382
383 sqlite3_stmt *st = NULL;
384 if(sqlite3_prepare_v2(db->sql,
385 "SELECT c.crop_factor, ifnull(c.mount_id, 0), c.id FROM camera c"
386 " JOIN camera_name m ON m.camera_id = c.id AND m.kind = 'model' AND m.norm = ?1"
387 " WHERE (?3 = 0 OR EXISTS (SELECT 1 FROM camera_name k"
388 " WHERE k.camera_id = c.id AND k.kind = 'maker' AND k.norm = ?2))"
389 " ORDER BY c.id ASC LIMIT 1",
390 -1, &st, NULL) != SQLITE_OK)
391 {
392 _db_err(db, "prepare camera lookup");
393 return -1;
394 }
395 sqlite3_bind_text(st, 1, nmodel, -1, SQLITE_STATIC);
396 sqlite3_bind_text(st, 2, nmaker, -1, SQLITE_STATIC);
397 sqlite3_bind_int(st, 3, (maker && *nmaker) ? 1 : 0);
398
399 int found = 0;
400 if(sqlite3_step(st) == SQLITE_ROW)
401 {
402 out->crop_factor = (float)sqlite3_column_double(st, 0);
403 out->mount_id = sqlite3_column_int64(st, 1);
404 out->id = sqlite3_column_int64(st, 2);
405 found = 1;
406 }
407 sqlite3_finalize(st);
408 return found;
409}
410
411int ls_db_lens_fits_mount(ls_db_t *db, long long lens_id, long long mount_id)
412{
413 if(!db || !db->sql) return -1;
414 if(mount_id <= 0) return 0;
415
416 sqlite3_stmt *st = NULL;
417 /* Direct fit, or the camera's mount declares the lens's mount compatible. */
418 if(sqlite3_prepare_v2(db->sql,
419 "SELECT 1 FROM lens_mount lm WHERE lm.lens_id = ?1 AND ("
420 " lm.mount_id = ?2"
421 " OR EXISTS (SELECT 1 FROM mount_compat mc"
422 " WHERE mc.mount_id = ?2 AND mc.compat_id = lm.mount_id))"
423 " LIMIT 1",
424 -1, &st, NULL) != SQLITE_OK)
425 {
426 _db_err(db, "prepare mount test");
427 return -1;
428 }
429 sqlite3_bind_int64(st, 1, lens_id);
430 sqlite3_bind_int64(st, 2, mount_id);
431 const int fits = (sqlite3_step(st) == SQLITE_ROW) ? 1 : 0;
432 sqlite3_finalize(st);
433 return fits;
434}
435
436int ls_db_list_lenses(ls_db_t *db, long long *out_ids, int max)
437{
438 if(!db || !db->sql) return -1;
439
440 sqlite3_stmt *st = NULL;
441 if(sqlite3_prepare_v2(db->sql, "SELECT id FROM lens ORDER BY id ASC", -1, &st, NULL) != SQLITE_OK)
442 {
443 _db_err(db, "prepare lens list");
444 return -1;
445 }
446 int n = 0;
447 while(sqlite3_step(st) == SQLITE_ROW)
448 {
449 if(out_ids)
450 {
451 if(n >= max) break;
452 out_ids[n] = sqlite3_column_int64(st, 0);
453 }
454 n++;
455 }
456 sqlite3_finalize(st);
457 return n;
458}
459
460int ls_db_lens_name(ls_db_t *db, long long lens_id, char *maker, size_t maker_size,
461 char *model, size_t model_size)
462{
463 if(!db || !db->sql) return -1;
464 if(maker && maker_size) maker[0] = '\0';
465 if(model && model_size) model[0] = '\0';
466
467 sqlite3_stmt *st = NULL;
468 if(sqlite3_prepare_v2(db->sql, "SELECT maker, model FROM lens WHERE id = ?1", -1, &st, NULL)
469 != SQLITE_OK)
470 {
471 _db_err(db, "prepare lens name");
472 return -1;
473 }
474 sqlite3_bind_int64(st, 1, lens_id);
475
476 int written = 0;
477 if(sqlite3_step(st) == SQLITE_ROW)
478 {
479 const char *a = (const char *)sqlite3_column_text(st, 0);
480 const char *b = (const char *)sqlite3_column_text(st, 1);
481 if(maker && maker_size && a)
482 {
483 snprintf(maker, maker_size, "%s", a);
484 written += (int)strlen(maker);
485 }
486 if(model && model_size && b)
487 {
488 snprintf(model, model_size, "%s", b);
489 written += (int)strlen(model);
490 }
491 }
492 sqlite3_finalize(st);
493 return written;
494}
495
496int ls_db_lens_range(ls_db_t *db, long long lens_id, float *min_focal, float *max_focal,
497 float *min_aperture, float *max_aperture)
498{
499 if(!db || !db->sql) return -1;
500 if(min_focal) *min_focal = 0.f;
501 if(max_focal) *max_focal = 0.f;
502 if(min_aperture) *min_aperture = 0.f;
503 if(max_aperture) *max_aperture = 0.f;
504
505 sqlite3_stmt *st = NULL;
506 if(sqlite3_prepare_v2(db->sql,
507 "SELECT min_focal, max_focal, min_aperture, max_aperture"
508 " FROM lens WHERE id = ?1", -1, &st, NULL) != SQLITE_OK)
509 {
510 _db_err(db, "prepare lens range");
511 return -1;
512 }
513 sqlite3_bind_int64(st, 1, lens_id);
514 int found = 0;
515 if(sqlite3_step(st) == SQLITE_ROW)
516 {
517 found = 1;
518 if(min_focal) *min_focal = (float)sqlite3_column_double(st, 0);
519 if(max_focal) *max_focal = (float)sqlite3_column_double(st, 1);
520 if(min_aperture) *min_aperture = (float)sqlite3_column_double(st, 2);
521 if(max_aperture) *max_aperture = (float)sqlite3_column_double(st, 3);
522 }
523 sqlite3_finalize(st);
524 return found;
525}
526
527int ls_db_lens_mounts(ls_db_t *db, long long lens_id, char *out, size_t out_size)
528{
529 if(!db || !db->sql) return -1;
530 if(out && out_size) out[0] = '\0';
531
532 sqlite3_stmt *st = NULL;
533 if(sqlite3_prepare_v2(db->sql,
534 "SELECT m.name FROM lens_mount lm JOIN mount m ON m.id = lm.mount_id"
535 " WHERE lm.lens_id = ?1 ORDER BY m.name", -1, &st, NULL) != SQLITE_OK)
536 {
537 _db_err(db, "prepare lens mounts");
538 return -1;
539 }
540 sqlite3_bind_int64(st, 1, lens_id);
541
542 int n = 0;
543 size_t used = 0;
544 while(sqlite3_step(st) == SQLITE_ROW)
545 {
546 const char *name = (const char *)sqlite3_column_text(st, 0);
547 if(!name) continue;
548 n++;
549 if(!out || out_size == 0) continue;
550 /* Truncate rather than fail: this string is shown to a person, and a picker that says
551 * nothing because one lens has more mounts than the caller budgeted for is worse than
552 * one that says most of them. The count returned is still the true count. */
553 const size_t need = strlen(name) + (used ? 2 : 0);
554 if(used + need >= out_size) continue;
555 if(used) { memcpy(out + used, ", ", 2); used += 2; }
556 memcpy(out + used, name, strlen(name));
557 used += strlen(name);
558 out[used] = '\0';
559 }
560 sqlite3_finalize(st);
561 return n;
562}
563
564int ls_db_list_cameras(ls_db_t *db, long long *out_ids, int max)
565{
566 if(!db || !db->sql) return -1;
567
568 sqlite3_stmt *st = NULL;
569 if(sqlite3_prepare_v2(db->sql, "SELECT id FROM camera ORDER BY id", -1, &st, NULL)
570 != SQLITE_OK)
571 {
572 _db_err(db, "prepare camera list");
573 return -1;
574 }
575 int n = 0;
576 while(sqlite3_step(st) == SQLITE_ROW)
577 {
578 if(out_ids && n < max) out_ids[n] = sqlite3_column_int64(st, 0);
579 n++;
580 }
581 sqlite3_finalize(st);
582 return (out_ids && n > max) ? max : n;
583}
584
585int ls_db_camera_name(ls_db_t *db, long long camera_id, char *maker, size_t maker_size,
586 char *model, size_t model_size, char *variant, size_t variant_size)
587{
588 if(!db || !db->sql) return -1;
589 if(maker && maker_size) maker[0] = '\0';
590 if(model && model_size) model[0] = '\0';
591 if(variant && variant_size) variant[0] = '\0';
592
593 sqlite3_stmt *st = NULL;
594 if(sqlite3_prepare_v2(db->sql, "SELECT maker, model, variant FROM camera WHERE id = ?1",
595 -1, &st, NULL) != SQLITE_OK)
596 {
597 _db_err(db, "prepare camera name");
598 return -1;
599 }
600 sqlite3_bind_int64(st, 1, camera_id);
601 int found = 0;
602 if(sqlite3_step(st) == SQLITE_ROW)
603 {
604 found = 1;
605 const char *a = (const char *)sqlite3_column_text(st, 0);
606 const char *b = (const char *)sqlite3_column_text(st, 1);
607 const char *c = (const char *)sqlite3_column_text(st, 2);
608 if(maker && maker_size && a) snprintf(maker, maker_size, "%s", a);
609 if(model && model_size && b) snprintf(model, model_size, "%s", b);
610 if(variant && variant_size && c) snprintf(variant, variant_size, "%s", c);
611 }
612 sqlite3_finalize(st);
613 return found;
614}
615
616int ls_db_camera_by_id(ls_db_t *db, long long camera_id, ls_camera_t *out)
617{
618 if(!db || !db->sql || !out) return -1;
619 memset(out, 0, sizeof(*out));
620
621 sqlite3_stmt *st = NULL;
622 if(sqlite3_prepare_v2(db->sql, "SELECT crop_factor, mount_id FROM camera WHERE id = ?1",
623 -1, &st, NULL) != SQLITE_OK)
624 {
625 _db_err(db, "prepare camera by id");
626 return -1;
627 }
628 sqlite3_bind_int64(st, 1, camera_id);
629 int found = 0;
630 if(sqlite3_step(st) == SQLITE_ROW)
631 {
632 found = 1;
633 out->crop_factor = (float)sqlite3_column_double(st, 0);
634 out->mount_id = sqlite3_column_int64(st, 1);
635 out->id = camera_id;
636 }
637 sqlite3_finalize(st);
638 return found;
639}
640
641int ls_db_mount_name(ls_db_t *db, long long mount_id, char *out, size_t out_size)
642{
643 if(!db || !db->sql || !out || out_size == 0) return -1;
644 out[0] = '\0';
645
646 sqlite3_stmt *st = NULL;
647 if(sqlite3_prepare_v2(db->sql, "SELECT name FROM mount WHERE id = ?1", -1, &st, NULL)
648 != SQLITE_OK)
649 {
650 _db_err(db, "prepare mount name");
651 return -1;
652 }
653 sqlite3_bind_int64(st, 1, mount_id);
654 int found = 0;
655 if(sqlite3_step(st) == SQLITE_ROW)
656 {
657 const char *n = (const char *)sqlite3_column_text(st, 0);
658 if(n)
659 {
660 snprintf(out, out_size, "%s", n);
661 found = 1;
662 }
663 }
664 sqlite3_finalize(st);
665 return found;
666}
667
668int ls_db_lenses_for_mount(ls_db_t *db, long long mount_id, long long *out_ids, int max)
669{
670 if(!db || !db->sql) return -1;
671
672 sqlite3_stmt *st = NULL;
673 if(sqlite3_prepare_v2(db->sql,
674 "SELECT lens_id FROM lens_mount WHERE mount_id = ?1 ORDER BY lens_id",
675 -1, &st, NULL) != SQLITE_OK)
676 {
677 _db_err(db, "prepare lenses for mount");
678 return -1;
679 }
680 sqlite3_bind_int64(st, 1, mount_id);
681 int n = 0;
682 while(sqlite3_step(st) == SQLITE_ROW)
683 {
684 if(out_ids && n < max) out_ids[n] = sqlite3_column_int64(st, 0);
685 n++;
686 }
687 sqlite3_finalize(st);
688 return (out_ids && n > max) ? max : n;
689}
690
691int ls_db_meta(ls_db_t *db, const char *key, char *out, size_t out_size)
692{
693 if(!db || !db->sql || !key || !out || out_size == 0) return -1;
694 out[0] = '\0';
695
696 sqlite3_stmt *st = NULL;
697 if(sqlite3_prepare_v2(db->sql, "SELECT value FROM meta WHERE key = ?1", -1, &st, NULL) != SQLITE_OK)
698 {
699 _db_err(db, "prepare meta");
700 return -1;
701 }
702 sqlite3_bind_text(st, 1, key, -1, SQLITE_STATIC);
703
704 int n = -1;
705 if(sqlite3_step(st) == SQLITE_ROW)
706 {
707 const char *v = (const char *)sqlite3_column_text(st, 0);
708 if(v)
709 {
710 snprintf(out, out_size, "%s", v);
711 n = (int)strlen(out);
712 }
713 }
714 sqlite3_finalize(st);
715 return n;
716}
717
718/* ------------------------------------------------------------------------- */
719/* Fuzzy matching. */
720/* */
721/* A raw file names a lens the way its vendor abbreviates it, and upstream */
722/* names it the way upstream chose. "16-35mm f/4G ED VR" has to reach "Nikon */
723/* AF-S Nikkor 16-35mm f/4G ED VR" with most of the tokens missing. */
724/* */
725/* The weights below are not derived from anything: they were calibrated */
726/* against liblensfun's own decisions, which tests/match_lensfun.c re-checks */
727/* over the whole database. Change one and run that test. */
728/* ------------------------------------------------------------------------- */
729
730enum { LS_MAX_TOKENS = 32, LS_TOKEN_LEN = 48 };
731
743typedef struct
744{
746 unsigned h[LS_MAX_TOKENS];
747 unsigned char len[LS_MAX_TOKENS];
748 unsigned long long bloom;
749 int n;
751
758int ls_db_tokenize(const char *norm, char *out_tokens, int max, int stride)
759{
760 int n = 0;
761 const char *p = norm ? norm : "";
762 while(*p && n < max)
763 {
764 while(*p == ' ') p++;
765 if(!*p) break;
766
767 char *w = out_tokens + (size_t)n * stride;
768 int len = 0;
769 int prev_digit = -1;
770 while(*p && *p != ' ' && len < stride - 1)
771 {
772 const int is_digit = (*p >= '0' && *p <= '9');
773 if(prev_digit >= 0 && is_digit != prev_digit) break; /* letter<->digit boundary */
774 w[len++] = *p++;
775 prev_digit = is_digit;
776 }
777 w[len] = '\0';
778 if(len) n++;
779 }
780 return n;
781}
782
783unsigned ls_db_token_hash(const char *token)
784{
785 unsigned hash = 2166136261u; /* FNV-1a */
786 for(const char *c = token; *c; c++)
787 {
788 hash ^= (unsigned char)*c;
789 hash *= 16777619u;
790 }
791 return hash;
792}
793
794size_t ls_db_token_digest(const char *norm, unsigned char *out, size_t out_size)
795{
796 char toks[LS_MAX_TOKENS][LS_TOKEN_LEN];
797 const int n = ls_db_tokenize(norm, &toks[0][0], LS_MAX_TOKENS, LS_TOKEN_LEN);
798 const size_t need = 2 + (size_t)n * 5;
799 if(out_size < need) return 0;
800
801 out[0] = (unsigned char)(n & 0xFF);
802 out[1] = (unsigned char)((n >> 8) & 0xFF);
803 for(int i = 0; i < n; i++)
804 {
805 const unsigned h = ls_db_token_hash(toks[i]);
806 unsigned char *p = out + 2 + (size_t)i * 4;
807 p[0] = (unsigned char)(h & 0xFF);
808 p[1] = (unsigned char)((h >> 8) & 0xFF);
809 p[2] = (unsigned char)((h >> 16) & 0xFF);
810 p[3] = (unsigned char)((h >> 24) & 0xFF);
811 out[2 + (size_t)n * 4 + i] = (unsigned char)strlen(toks[i]);
812 }
813 return need;
814}
815
817static int _digest_load(const unsigned char *blob, int bytes, ls_tokens_t *out)
818{
819 out->n = 0;
820 out->bloom = 0;
821 if(!blob || bytes < 2) return 0;
822
823 int n = blob[0] | (blob[1] << 8);
824 if(n > LS_MAX_TOKENS) n = LS_MAX_TOKENS;
825 if(bytes < 2 + n * 5) return 0;
826
827 for(int i = 0; i < n; i++)
828 {
829 const unsigned char *p = blob + 2 + (size_t)i * 4;
830 const unsigned h = (unsigned)p[0] | ((unsigned)p[1] << 8)
831 | ((unsigned)p[2] << 16) | ((unsigned)p[3] << 24);
832 out->h[i] = h;
833 out->len[i] = blob[2 + (size_t)n * 4 + i];
834 out->bloom |= 1ULL << (h & 63u);
835 }
836 out->n = n;
837 return n;
838}
839
840static void _tokenize(const char *norm, ls_tokens_t *out)
841{
842 out->n = ls_db_tokenize(norm, &out->t[0][0], LS_MAX_TOKENS, LS_TOKEN_LEN);
843 out->bloom = 0;
844 for(int i = 0; i < out->n; i++)
845 {
846 const unsigned hash = ls_db_token_hash(out->t[i]);
847 out->h[i] = hash;
848 out->len[i] = (unsigned char)strlen(out->t[i]);
849 out->bloom |= 1ULL << (hash & 63u);
850 }
851}
852
865static float _score_tokens(const ls_tokens_t *pat, const ls_tokens_t *cand)
866{
867 if(pat->n == 0 || cand->n == 0) return 0.f;
868
869 unsigned char used[LS_MAX_TOKENS] = { 0 };
870 float got = 0.f;
871
872 for(int i = 0; i < pat->n; i++)
873 {
874 const unsigned hi = pat->h[i];
875 const int li = pat->len[i];
876 float best = 0.f;
877 int best_j = -1;
878 for(int j = 0; j < cand->n; j++)
879 {
880 if(used[j]) continue;
881 float s = 0.f;
882 /* Hash and length, and nothing else: the candidate's token TEXT is not read from the
883 * database at all -- only this digest is -- so there is no string to compare against.
884 * A 32-bit FNV plus the length is ~40 bits of discrimination over ~47000 tokens; the
885 * agreement test is what says that is enough, and it reports the same 99.0% as the
886 * version that compared the bytes. */
887 if(hi == cand->h[j] && li == cand->len[j])
888 s = 1.f;
889 if(s > best)
890 {
891 best = s;
892 best_j = j;
893 }
894 }
895 if(best_j >= 0 && best > 0.f)
896 {
897 used[best_j] = 1;
898 got += best;
899 }
900 }
901
902 const float forward = got / (float)pat->n; /* pattern covered */
903 int unmatched = 0;
904 for(int j = 0; j < cand->n; j++) if(!used[j]) unmatched++;
905 const float verbosity = (float)unmatched / (float)cand->n; /* candidate's extra words */
906
907 float score = 100.f * (forward - 0.15f * verbosity * forward);
908 if(score < 0.f) score = 0.f;
909 return score;
910}
911
934static void _parse_focal_range(const char *name, float *minf, float *maxf)
935{
936 *minf = 0.f;
937 *maxf = 0.f;
938 if(!name) return;
939
940 /* Upstream refuses to read numbers out of these (GuessParameters): a teleconverter or an
941 * adapter carries a magnification, not a focal length, and reading "1.4x" as a focal
942 * rejects every lens it could pair with. Same list, same reason. */
943 static const char *const not_a_lens[]
944 = { "adapter", "reducer", "booster", "extender", "converter", "ext.", "ext ", NULL };
945 for(int i = 0; not_a_lens[i]; i++)
946 {
947 /* case-insensitively, since this sees raw names */
948 for(const char *h = name; *h; h++)
949 {
950 const char *a = h, *b = not_a_lens[i];
951 while(*a && *b && ((*a | 32) == (*b | 32))) { a++; b++; }
952 if(!*b) return;
953 }
954 }
955
956 for(const char *c = name; *c; c++)
957 {
958 if(*c < '0' || *c > '9') continue;
959 /* Whitespace or start of string, and NOTHING else -- upstream's regex begins
960 * ([[:space:]]+|^) and this must not be more eager than that.
961 *
962 * Accepting '-' as a boundary too, which is what this did first, half-parses the names
963 * vendors actually write: "XF18-55mmF2.8-4" has no space before the 18, so the 18 is
964 * skipped, the 55 after the dash is taken instead, and an 18-55 zoom is read as a 55mm
965 * prime -- which then rejects the very lens being searched for. Upstream cannot parse
966 * that name either, and answers by leaving the range UNSET, which makes the filter
967 * neutral and the lens findable. Failing to parse is the safe outcome here; parsing
968 * wrongly is not. Measured: six lenses across ten images went from resolved to not
969 * found at all. */
970 if(c != name && c[-1] != ' ') continue;
971
972 char *end = NULL;
973 const float a = strtof(c, &end);
974 if(!end || end == c) continue;
975
976 float b = a;
977 const char *p = end;
978 if(*p == '-')
979 {
980 char *end2 = NULL;
981 const float t = strtof(p + 1, &end2);
982 if(end2 && end2 != p + 1) { b = t; p = end2; }
983 }
984 while(*p == ' ') p++;
985 if((p[0] == 'm' || p[0] == 'M') && (p[1] == 'm' || p[1] == 'M') && a > 0.f && b >= a)
986 {
987 *minf = a;
988 *maxf = b;
989 return; /* upstream takes the first match too */
990 }
991 c = end - 1;
992 }
993}
994
999static int _compare_num(const float a, const float b)
1000{
1001 if(a == 0.f || b == 0.f) return 0;
1002 const float r = a / b;
1003 return (r <= 0.99f || r >= 1.01f) ? -1 : +1;
1004}
1005
1028static float _crop_score(const float cam, const float calib)
1029{
1030 if(cam <= 0.01f || calib <= 0.f) return 0.f; /* unknown: the rule cannot be applied */
1031 if(cam < calib * 0.96f) return -1.f; /* the calibration does not cover the frame */
1032
1033 if(cam >= calib * 1.41f) return 2.f;
1034 if(cam >= calib * 1.31f) return 4.f;
1035 if(cam >= calib * 1.21f) return 6.f;
1036 if(cam >= calib * 1.11f) return 8.f;
1037 if(cam >= calib * 1.01f) return 10.f;
1038 if(cam >= calib) return 5.f;
1039 return 3.f; /* within the 4% tolerance, but smaller */
1040}
1041
1042int ls_db_match_lens(ls_db_t *db, const char *maker, const char *model, long long mount_id,
1043 float crop, ls_db_match_t *out, int max)
1044{
1045 if(!db || !db->sql || !model || !out || max <= 0) return -1;
1046
1047 char nmodel[512], nmaker[512];
1048 ls_db_normalize(model, nmodel, sizeof(nmodel));
1049 ls_db_normalize(maker, nmaker, sizeof(nmaker));
1050
1051 ls_tokens_t pat, pat_maker;
1052 _tokenize(nmodel, &pat);
1053 _tokenize(nmaker, &pat_maker);
1054 if(pat.n == 0) return 0;
1055
1056 /* Two phases, and the first one is what makes this cheap.
1057 *
1058 * Scoring every name in the catalogue costs 610 ns each -- 88% of a lookup, measured --
1059 * and there are ~4700 of them. The fix is not to score faster (hashing the tokens first
1060 * was tried and moved nothing: the per-comparison cost was already ~5 ns, there were
1061 * simply half a million comparisons). The fix is to score far fewer names.
1062 *
1063 * So: ask lens_token which of the QUERY's tokens is rarest, and gather only the lenses
1064 * carrying it. "16" or "nikkor" reaches tens of lenses where "mm" or "f" reaches
1065 * thousands, which is exactly why gathering on ALL the query's tokens -- also tried, also
1066 * measured -- is no better than the full scan it replaces.
1067 *
1068 * The frequencies come from token_df, precomputed at import. Deriving them here with a
1069 * GROUP BY over lens_token was the first attempt and cost 0.6 ms on its own: counting how
1070 * often "mm" occurs means walking every one of its index rows.
1071 *
1072 * If that yields nothing (a query whose rarest token no catalogue name shares), the scan
1073 * runs after all, so the answer never depends on the pruning. */
1074 char in_list[LS_MAX_TOKENS * (LS_TOKEN_LEN + 4) + 8];
1075 {
1076 size_t w = 0;
1077 in_list[w++] = '(';
1078 for(int i = 0; i < pat.n; i++)
1079 {
1080 if(i) in_list[w++] = ',';
1081 in_list[w++] = '\'';
1082 for(const char *c = pat.t[i]; *c; c++)
1083 if(*c != '\'') in_list[w++] = *c; /* tokens are [a-z0-9] after normalisation */
1084 in_list[w++] = '\'';
1085 }
1086 in_list[w++] = ')';
1087 in_list[w] = '\0';
1088 }
1089
1090 char rarest[LS_TOKEN_LEN] = { 0 };
1091 {
1092 char sqlbuf[sizeof(in_list) + 256];
1093 snprintf(sqlbuf, sizeof(sqlbuf),
1094 "SELECT token FROM token_df WHERE kind = 'model' AND token IN %s"
1095 " ORDER BY df ASC LIMIT 1", in_list);
1096 sqlite3_stmt *rq = NULL;
1097 if(sqlite3_prepare_v2(db->sql, sqlbuf, -1, &rq, NULL) == SQLITE_OK)
1098 {
1099 if(sqlite3_step(rq) == SQLITE_ROW)
1100 {
1101 const char *t = (const char *)sqlite3_column_text(rq, 0);
1102 if(t) snprintf(rarest, sizeof(rarest), "%s", t);
1103 }
1104 sqlite3_finalize(rq);
1105 }
1106 }
1107
1108 sqlite3_stmt *st = NULL;
1109 char sqlbuf[1024];
1110 const char *mount_clause =
1111 (mount_id > 0)
1112 ? " AND EXISTS (SELECT 1 FROM lens_mount lm WHERE lm.lens_id = n.lens_id AND ("
1113 " lm.mount_id = ?1 OR EXISTS (SELECT 1 FROM mount_compat mc"
1114 " WHERE mc.mount_id = ?1 AND mc.compat_id = lm.mount_id)))"
1115 : "";
1116 if(rarest[0])
1117 snprintf(sqlbuf, sizeof(sqlbuf),
1118 "SELECT n.lens_id, n.tokens, n.kind FROM lens_name n"
1119 " WHERE n.lens_id IN (SELECT lens_id FROM lens_token"
1120 " WHERE kind = 'model' AND token = ?2)%s", mount_clause);
1121 else
1122 snprintf(sqlbuf, sizeof(sqlbuf),
1123 "SELECT n.lens_id, n.tokens, n.kind FROM lens_name n WHERE 1%s", mount_clause);
1124
1125 if(sqlite3_prepare_v2(db->sql, sqlbuf, -1, &st, NULL) != SQLITE_OK)
1126 {
1127 _db_err(db, "prepare match");
1128 return -1;
1129 }
1130 if(rarest[0]) sqlite3_bind_text(st, 2, rarest, -1, SQLITE_STATIC);
1131 if(mount_id > 0) sqlite3_bind_int64(st, 1, mount_id);
1132
1133 /* Best score per lens, kept in a small open-addressed table: a lens has several names
1134 * and several of them may score, but only its best counts. */
1135 enum { SLOTS = 4096 };
1136 long long *ids = (long long *)calloc(SLOTS, sizeof(long long));
1137 float *best = (float *)calloc(SLOTS, sizeof(float));
1138 float *maker_bonus = (float *)calloc(SLOTS, sizeof(float));
1139 if(!ids || !best || !maker_bonus)
1140 {
1141 free(ids); free(best); free(maker_bonus);
1142 sqlite3_finalize(st);
1143 return -1;
1144 }
1145
1146 while(sqlite3_step(st) == SQLITE_ROW)
1147 {
1148 const long long id = sqlite3_column_int64(st, 0);
1149 const unsigned char *blob = (const unsigned char *)sqlite3_column_blob(st, 1);
1150 const int blob_bytes = sqlite3_column_bytes(st, 1);
1151 const char *kind = (const char *)sqlite3_column_text(st, 2);
1152 if(!blob || !kind) continue;
1153
1154 /* Decoded, not parsed. The tokens were split and hashed once, at import; this reads
1155 * fixed-width fields out of a blob that came straight from a covering index. */
1156 ls_tokens_t cand;
1157 if(!_digest_load(blob, blob_bytes, &cand)) continue;
1158
1159 /* No shared token means no score, and the quadratic comparison below would only
1160 * discover that the expensive way. One AND settles it for most of the catalogue. */
1161 if(!(cand.bloom & ((kind[0] == 'm' && kind[1] == 'o') ? pat.bloom : pat_maker.bloom)))
1162 continue;
1163
1164 size_t slot = (size_t)id % SLOTS;
1165 while(ids[slot] && ids[slot] != id) slot = (slot + 1) % SLOTS;
1166 ids[slot] = id;
1167
1168 if(kind[0] == 'm' && kind[1] == 'o') /* "model" */
1169 {
1170 const float s = _score_tokens(&pat, &cand);
1171 if(s > best[slot]) best[slot] = s;
1172 }
1173 else if(pat_maker.n) /* "maker" */
1174 {
1175 /* The maker is corroboration, not a filter: vendors and upstream disagree about
1176 * their own names often enough ("Nikon Corporation" vs "Nikon") that requiring it
1177 * loses more matches than the false positives it prevents. */
1178 const float s = _score_tokens(&pat_maker, &cand);
1179 if(s > maker_bonus[slot]) maker_bonus[slot] = s;
1180 }
1181 }
1182 sqlite3_finalize(st);
1183
1184 /* The calibration crop factor of every candidate, in one query rather than one per
1185 * candidate: the set is small by now, but a round trip each would undo the work the
1186 * rarest-token pruning just did. */
1187 /* The exact terms -- crop factor and focal range -- are tie-breaks, NOT score terms, and
1188 * this constant is what makes them so. Their REJECTS stay absolute (a calibration that
1189 * cannot cover the sensor, or a 20mm answering a 50mm query, is out whatever its name);
1190 * their positive weights are scaled far below the resolution of a name score so that they
1191 * can only order candidates whose names are otherwise equal.
1192 *
1193 * The distinction is not cosmetic, and both halves of it were measured. Upstream ships
1194 * "Nikon AF-S Nikkor 50mm f/1.4G" twice, one row carrying a stray "160" in its name and
1195 * no vignetting or TCA data; the names differ by 1.36 and the crop term, added at full
1196 * weight, was worth 3 -- so the emptier row won on a D7200 and the render silently lost
1197 * two corrections. Conversely the Sigma 70-200mm and Tokina 11-20mm have rows whose names
1198 * are IDENTICAL and differ only in calibration sensor, and there the crop term is the only
1199 * thing that can choose; weakening it uniformly (which was the first attempt) got the
1200 * Nikon right and those two wrong.
1201 *
1202 * Name first, exact facts to break ties, is what satisfies both. */
1203 const float EXACT_TIE = 1e-4f;
1204
1205 float q_minf = 0.f, q_maxf = 0.f;
1206 /* The RAW name, not the normalised one. Normalisation collapses punctuation, so
1207 * "16-35mm" arrives as "16 35mm" and a zoom reads as a 35mm prime -- which then rejects
1208 * the very lens being searched for. Measured: 99.0% agreement fell to 68.9%. */
1209 _parse_focal_range(model, &q_minf, &q_maxf);
1210
1211 sqlite3_stmt *cq = NULL;
1212 if(crop > 0.01f || q_minf > 0.f)
1213 sqlite3_prepare_v2(db->sql,
1214 "SELECT crop_factor, min_focal, max_focal FROM lens WHERE id = ?1",
1215 -1, &cq, NULL);
1216
1217 int n = 0;
1218 for(size_t slot = 0; slot < SLOTS; slot++)
1219 {
1220 if(!ids[slot] || best[slot] <= 0.f) continue;
1221 float score = best[slot] + 0.10f * maker_bonus[slot];
1222
1223 if(cq)
1224 {
1225 sqlite3_reset(cq);
1226 sqlite3_bind_int64(cq, 1, ids[slot]);
1227 if(sqlite3_step(cq) != SQLITE_ROW) continue;
1228 const float calib = (float)sqlite3_column_double(cq, 0);
1229 const float c_minf = (float)sqlite3_column_double(cq, 1);
1230 const float c_maxf = (float)sqlite3_column_double(cq, 2);
1231
1232 const float w = _crop_score(crop, calib);
1233 if(w < 0.f) continue; /* rejected: this calibration cannot serve this frame */
1234 /* A TIE-BREAK, scaled so it cannot outweigh a name difference -- see EXACT_TIE. */
1235 score += w * EXACT_TIE;
1236
1237 /* The focal range the NAME claims, as upstream's hard filter. A 50mm query must not
1238 * resolve to a 20mm lens however well the rest of the tokens line up. */
1239 const int fmin = _compare_num(q_minf, c_minf);
1240 const int fmax = _compare_num(q_maxf, c_maxf);
1241 if(fmin < 0 || fmax < 0) continue;
1242 if(fmin > 0) score += 10.f * EXACT_TIE;
1243 if(fmax > 0) score += 10.f * EXACT_TIE;
1244 }
1245
1246 /* Insertion sort into the caller's top-N: max is small (a GUI shows a handful). */
1247 int at = n;
1248 while(at > 0 && out[at - 1].score < score)
1249 {
1250 if(at < max) out[at] = out[at - 1];
1251 at--;
1252 }
1253 if(at < max)
1254 {
1255 out[at].lens_id = ids[slot];
1256 out[at].score = score;
1257 if(n < max) n++;
1258 }
1259 }
1260
1261 if(cq) sqlite3_finalize(cq);
1262 free(ids);
1263 free(best);
1264 free(maker_bonus);
1265 return n;
1266}
@ LS_MAX_CALIB
Definition lensserious.h:92
ls_tca_model_t
Definition lensserious.h:71
ls_vig_model_t
Definition lensserious.h:78
ls_lens_type_t
Definition lensserious.h:95
ls_dist_model_t
Definition lensserious.h:62
int ls_db_lenses_for_mount(ls_db_t *db, long long mount_id, long long *out_ids, int max)
The lenses made for one mount.
int ls_db_find_lens(ls_db_t *db, const char *maker, const char *model, float crop, ls_lens_t *out)
Find a lens by maker and model, and fill out with its coefficients.
int ls_db_match_lens(ls_db_t *db, const char *maker, const char *model, long long mount_id, float crop, ls_db_match_t *out, int max)
Find the lenses a free-text name most likely refers to.
int ls_db_meta(ls_db_t *db, const char *key, char *out, size_t out_size)
A meta value by key (built_utc, source, lensfun_db_version, ...).
void ls_db_close(ls_db_t *db)
Release a handle. Safe on NULL.
int ls_db_list_cameras(ls_db_t *db, long long *out_ids, int max)
Enumerate camera ids, for a GUI's camera picker.
int ls_db_list_lenses(ls_db_t *db, long long *out_ids, int max)
Enumerate lens ids, oldest-inserted first, for tests and for a GUI's lens picker.
static float _score_tokens(const ls_tokens_t *pat, const ls_tokens_t *cand)
How well pat's tokens are covered by cand's, 0..100.
int ls_db_tokenize(const char *norm, char *out_tokens, int max, int stride)
Split a normalised name on spaces, and split letter/digit runs apart.
int ls_db_lens_fits_mount(ls_db_t *db, long long lens_id, long long mount_id)
Does a lens fit a camera's mount, upstream's compatibility table included?
int ls_db_lens_by_id(ls_db_t *db, long long lens_id, ls_lens_t *out)
Load a lens by its database id, for a caller that already resolved one.
int ls_db_mount_name(ls_db_t *db, long long mount_id, char *out, size_t out_size)
A mount's name.
static int _digest_load(const unsigned char *blob, int bytes, ls_tokens_t *out)
Read a digest back into the arrays _score_tokens() compares.
static float _crop_score(const float cam, const float calib)
Upstream's crop-factor rule, as a bonus ADDED to a candidate's score.
int ls_db_lens_mounts(ls_db_t *db, long long lens_id, char *out, size_t out_size)
The mounts a lens is made for, joined with ", ".
int ls_db_lens_range(ls_db_t *db, long long lens_id, float *min_focal, float *max_focal, float *min_aperture, float *max_aperture)
The lens's focal and aperture range, for a picker that lists it.
static void _tokenize(const char *norm, ls_tokens_t *out)
int ls_db_schema_version(const ls_db_t *db)
Schema version of the open file, or -1. Bumped when the layout changes.
int ls_db_lens_name(ls_db_t *db, long long lens_id, char *maker, size_t maker_size, char *model, size_t model_size)
The lens's maker/model, as stored.
int ls_db_camera_by_id(ls_db_t *db, long long camera_id, ls_camera_t *out)
Load a camera by its database id, for a caller that already resolved one.
@ LS_MAX_TOKENS
@ LS_TOKEN_LEN
static int _compare_num(const float a, const float b)
Upstream's _lf_compare_num(): a numeric field as a filter, not a score.
size_t ls_db_token_digest(const char *norm, unsigned char *out, size_t out_size)
Pack a normalised name's tokens into the digest stored in lens_name.tokens.
unsigned ls_db_token_hash(const char *token)
FNV-1a of a token, the hash the stored digest and the matcher both use.
ls_db_t * ls_db_open(const char *path)
Open a database for reading.
int ls_db_find_camera(ls_db_t *db, const char *maker, const char *model, ls_camera_t *out)
Find a camera by maker and model.
static int _fill_calibrations(ls_db_t *db, long long lens_id, ls_lens_t *out)
#define LS_DB_SCHEMA_VERSION
static void _db_err(ls_db_t *db, const char *what)
int ls_db_camera_name(ls_db_t *db, long long camera_id, char *maker, size_t maker_size, char *model, size_t model_size, char *variant, size_t variant_size)
A camera's maker, model and variant, as stored.
size_t ls_db_normalize(const char *in, char *out, size_t out_size)
Case-fold ASCII, drop punctuation, collapse whitespace.
static long long _find_lens_id(ls_db_t *db, const char *maker, const char *model, float crop)
The lens id whose names match, preferring the calibration crop nearest crop.
static void _parse_focal_range(const char *name, float *minf, float *maxf)
Pull the focal range out of a lens NAME, the way upstream does.
const char * ls_db_error(const ls_db_t *db)
The last error on db, as a string owned by db, or NULL.
static char * _db_build_uri(const char *path)
Build the URI this library insists on, whatever the caller passed.
A read-only database, and an API with nothing behind it.
ls_dist_model_t model
Definition lensserious.h:85
ls_tca_model_t model
Definition lensserious.h:86
float terms[6]
Definition lensserious.h:86
ls_vig_model_t model
Definition lensserious.h:87
float terms[3]
Definition lensserious.h:87
long long mount_id
long long lens_id
char error[256]
sqlite3 * sql
int schema_version
float crop_factor
ls_lens_type_t type
ls_calib_vig_t vig[LS_MAX_CALIB]
float center_x
ls_calib_real_focal_t real_focal[LS_MAX_CALIB]
ls_calib_tca_t tca[LS_MAX_CALIB]
float min_focal
float center_y
ls_calib_dist_t dist[LS_MAX_CALIB]
float aspect_ratio
float max_focal
int n_real_focal
A tokenised name, with everything the comparison needs precomputed.
unsigned h[LS_MAX_TOKENS]
unsigned char len[LS_MAX_TOKENS]
char t[LS_MAX_TOKENS][LS_TOKEN_LEN]
unsigned long long bloom