GCC Code Coverage Report


Directory: ./
File: libfprint/fp-image.c
Date: 2024-05-04 14:54:39
Exec Total Coverage
Lines: 161 197 81.7%
Functions: 20 24 83.3%
Branches: 58 103 56.3%

Line Branch Exec Source
1 /*
2 * FPrint Image
3 * Copyright (C) 2007 Daniel Drake <dsd@gentoo.org>
4 * Copyright (C) 2019 Benjamin Berg <bberg@redhat.com>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #define FP_COMPONENT "image"
22
23 #include "fpi-compat.h"
24 #include "fpi-image.h"
25 #include "fpi-log.h"
26
27 #include <config.h>
28 #include <nbis.h>
29
30 /**
31 * SECTION: fp-image
32 * @title: FpImage
33 * @short_description: Internal Image handling routines
34 *
35 * Some devices will provide the image data corresponding to a print
36 * this object allows accessing this data.
37 */
38
39
4/5
✓ Branch 0 taken 88 times.
✓ Branch 1 taken 220 times.
✓ Branch 2 taken 21 times.
✓ Branch 3 taken 88 times.
✗ Branch 4 not taken.
834 G_DEFINE_TYPE (FpImage, fp_image, G_TYPE_OBJECT)
40
41 enum {
42 PROP_0,
43 PROP_WIDTH,
44 PROP_HEIGHT,
45 N_PROPS
46 };
47
48 static GParamSpec *properties[N_PROPS];
49
50 FpImage *
51 53 fp_image_new (gint width, gint height)
52 {
53 53 return g_object_new (FP_TYPE_IMAGE,
54 "width", width,
55 "height", height,
56 NULL);
57 }
58
59 static void
60 53 fp_image_finalize (GObject *object)
61 {
62 53 FpImage *self = (FpImage *) object;
63
64
1/2
✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
53 g_clear_pointer (&self->data, g_free);
65
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 5 times.
53 g_clear_pointer (&self->binarized, g_free);
66
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 5 times.
53 g_clear_pointer (&self->minutiae, g_ptr_array_unref);
67
68 53 G_OBJECT_CLASS (fp_image_parent_class)->finalize (object);
69 53 }
70
71 static void
72 53 fp_image_constructed (GObject *object)
73 {
74 53 FpImage *self = (FpImage *) object;
75
76 53 self->data = g_malloc0 (self->width * self->height);
77 53 }
78
79 static void
80 fp_image_get_property (GObject *object,
81 guint prop_id,
82 GValue *value,
83 GParamSpec *pspec)
84 {
85 FpImage *self = FP_IMAGE (object);
86
87 switch (prop_id)
88 {
89 case PROP_WIDTH:
90 g_value_set_uint (value, self->width);
91 break;
92
93 case PROP_HEIGHT:
94 g_value_set_uint (value, self->height);
95 break;
96
97 default:
98 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
99 }
100 }
101
102 static void
103 106 fp_image_set_property (GObject *object,
104 guint prop_id,
105 const GValue *value,
106 GParamSpec *pspec)
107 {
108 106 FpImage *self = FP_IMAGE (object);
109
110
2/3
✓ Branch 0 taken 53 times.
✓ Branch 1 taken 53 times.
✗ Branch 2 not taken.
106 switch (prop_id)
111 {
112 53 case PROP_WIDTH:
113 53 self->width = g_value_get_uint (value);
114 53 break;
115
116 53 case PROP_HEIGHT:
117 53 self->height = g_value_get_uint (value);
118 53 break;
119
120 default:
121 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
122 }
123 106 }
124
125 static void
126 21 fp_image_class_init (FpImageClass *klass)
127 {
128 21 GObjectClass *object_class = G_OBJECT_CLASS (klass);
129
130 21 object_class->finalize = fp_image_finalize;
131 21 object_class->constructed = fp_image_constructed;
132 21 object_class->set_property = fp_image_set_property;
133 21 object_class->get_property = fp_image_get_property;
134
135 42 properties[PROP_WIDTH] =
136 21 g_param_spec_uint ("width",
137 "Width",
138 "The width of the image",
139 0,
140 G_MAXUINT16,
141 0,
142 G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
143
144 42 properties[PROP_HEIGHT] =
145 21 g_param_spec_uint ("height",
146 "Height",
147 "The height of the image",
148 0,
149 G_MAXUINT16,
150 0,
151 G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
152
153 21 g_object_class_install_properties (object_class, N_PROPS, properties);
154 21 }
155
156 static void
157 53 fp_image_init (FpImage *self)
158 {
159 53 }
160
161 typedef struct
162 {
163 struct fp_minutiae *minutiae;
164 guchar *binarized;
165 FpiImageFlags flags;
166 unsigned char *image;
167 gboolean image_changed;
168 } DetectMinutiaeNbisData;
169
170 static void
171 48 fp_image_detect_minutiae_free (DetectMinutiaeNbisData *data)
172 {
173
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
48 g_clear_pointer (&data->minutiae, free_minutiae);
174
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 g_clear_pointer (&data->binarized, g_free);
175
176
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 41 times.
48 if (data->image_changed)
177
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 g_clear_pointer (&data->image, g_free);
178
179 48 g_free (data);
180 48 }
181
182 144 G_DEFINE_AUTOPTR_CLEANUP_FUNC (DetectMinutiaeNbisData, fp_image_detect_minutiae_free)
183
184
185 static gboolean
186 48 fp_image_detect_minutiae_nbis_finish (FpImage *self,
187 GTask *task,
188 GError **error)
189 {
190 96 g_autoptr(DetectMinutiaeNbisData) data = NULL;
191
192 48 data = g_task_propagate_pointer (task, error);
193
194
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
48 if (data != NULL)
195 {
196 48 self->flags = data->flags;
197
198
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 41 times.
48 if (data->image_changed)
199 {
200
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 g_clear_pointer (&self->data, g_free);
201 7 self->data = g_steal_pointer (&data->image);
202 }
203
204
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 g_clear_pointer (&self->binarized, g_free);
205
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 self->binarized = g_steal_pointer (&data->binarized);
206
207
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 g_clear_pointer (&self->minutiae, g_ptr_array_unref);
208 48 self->minutiae = g_ptr_array_new_full (data->minutiae->num,
209 (GDestroyNotify) free_minutia);
210
211
2/2
✓ Branch 0 taken 3495 times.
✓ Branch 1 taken 48 times.
3543 for (int i = 0; i < data->minutiae->num; i++)
212 3495 g_ptr_array_add (self->minutiae,
213 3495 g_steal_pointer (&data->minutiae->list[i]));
214
215 /* Don't let free_minutiae delete the minutiae that we now own. */
216 48 data->minutiae->num = 0;
217
218 48 return TRUE;
219 }
220
221 return FALSE;
222 }
223
224 static void
225 9 vflip (guint8 *data, gint width, gint height)
226 9 {
227 9 int data_len = width * height;
228 9 unsigned char rowbuf[width];
229 9 int i;
230
231
2/2
✓ Branch 0 taken 1916 times.
✓ Branch 1 taken 9 times.
1925 for (i = 0; i < height / 2; i++)
232 {
233 1916 int offset = i * width;
234 1916 int swap_offset = data_len - (width * (i + 1));
235
236 /* copy top row into buffer */
237 1916 memcpy (rowbuf, data + offset, width);
238
239 /* copy lower row over upper row */
240 1916 memcpy (data + offset, data + swap_offset, width);
241
242 /* copy buffer over lower row */
243 1916 memcpy (data + swap_offset, rowbuf, width);
244 }
245 9 }
246
247 static void
248 6 hflip (guint8 *data, gint width, gint height)
249 6 {
250 6 unsigned char rowbuf[width];
251 6 int i, j;
252
253
2/2
✓ Branch 0 taken 2135 times.
✓ Branch 1 taken 6 times.
2141 for (i = 0; i < height; i++)
254 {
255 2135 int offset = i * width;
256
257 2135 memcpy (rowbuf, data + offset, width);
258
2/2
✓ Branch 0 taken 624616 times.
✓ Branch 1 taken 2135 times.
626751 for (j = 0; j < width; j++)
259 624616 data[offset + j] = rowbuf[width - j - 1];
260 }
261 6 }
262
263 static void
264 9 invert_colors (guint8 *data, gint width, gint height)
265 {
266 9 int data_len = width * height;
267 9 int i;
268
269
2/2
✓ Branch 0 taken 829536 times.
✓ Branch 1 taken 9 times.
829545 for (i = 0; i < data_len; i++)
270 829536 data[i] = 0xff - data[i];
271 }
272
273 static void
274 48 fp_image_detect_minutiae_nbis_thread_func (GTask *task,
275 gpointer source_object,
276 gpointer task_data,
277 GCancellable *cancellable)
278 {
279 48 g_autoptr(GTimer) timer = NULL;
280 g_autoptr(DetectMinutiaeNbisData) ret_data = NULL;
281
3/4
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 41 times.
✓ Branch 2 taken 48 times.
✗ Branch 3 not taken.
96 g_autoptr(GTask) thread_task = g_steal_pointer (&task);
282
1/4
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
48 g_autofree gint *direction_map = NULL;
283 48 g_autofree gint *low_contrast_map = NULL;
284 48 g_autofree gint *low_flow_map = NULL;
285 48 g_autofree gint *high_curve_map = NULL;
286 48 g_autofree gint *quality_map = NULL;
287 48 g_autofree LFSPARMS *lfsparms = NULL;
288 48 FpImage *self = source_object;
289 48 FpiImageFlags minutiae_flags;
290 48 unsigned char *image;
291 48 gint map_w, map_h;
292 48 gint bw, bh, bd;
293 48 gint r;
294
295 48 image = self->data;
296 48 minutiae_flags = self->flags & ~(FPI_IMAGE_H_FLIPPED |
297 FPI_IMAGE_V_FLIPPED |
298 FPI_IMAGE_COLORS_INVERTED);
299
300
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 41 times.
48 if (minutiae_flags != FPI_IMAGE_NONE)
301 7 image = g_memdup2 (self->data, self->width * self->height);
302
303 48 ret_data = g_new0 (DetectMinutiaeNbisData, 1);
304 48 ret_data->flags = minutiae_flags;
305 48 ret_data->image = image;
306 48 ret_data->image_changed = image != self->data;
307
308 /* Normalize the image first */
309
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 42 times.
48 if (self->flags & FPI_IMAGE_H_FLIPPED)
310 6 hflip (image, self->width, self->height);
311
312
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 39 times.
48 if (self->flags & FPI_IMAGE_V_FLIPPED)
313 9 vflip (image, self->width, self->height);
314
315
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 39 times.
48 if (self->flags & FPI_IMAGE_COLORS_INVERTED)
316 9 invert_colors (image, self->width, self->height);
317
318 48 lfsparms = g_memdup2 (&g_lfsparms_V2, sizeof (LFSPARMS));
319 48 lfsparms->remove_perimeter_pts = minutiae_flags & FPI_IMAGE_PARTIAL ? TRUE : FALSE;
320
321 48 timer = g_timer_new ();
322 96 r = get_minutiae (&ret_data->minutiae, &quality_map, &direction_map,
323 &low_contrast_map, &low_flow_map, &high_curve_map,
324 48 &map_w, &map_h, &ret_data->binarized, &bw, &bh, &bd,
325 48 image, self->width, self->height, 8,
326 self->ppmm, lfsparms);
327 48 g_timer_stop (timer);
328 48 fp_dbg ("Minutiae scan completed in %f secs", g_timer_elapsed (timer, NULL));
329
330
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 if (g_task_had_error (thread_task))
331 return;
332
333
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 if (r)
334 {
335 fp_err ("get minutiae failed, code %d", r);
336 g_task_return_new_error (thread_task, G_IO_ERROR,
337 G_IO_ERROR_FAILED,
338 "Minutiae scan failed with code %d", r);
339 return;
340 }
341
342
2/4
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 48 times.
48 if (!ret_data->minutiae || ret_data->minutiae->num == 0)
343 {
344 g_task_return_new_error (thread_task, G_IO_ERROR, G_IO_ERROR_FAILED,
345 "No minutiae found");
346 return;
347 }
348
349 48 g_task_return_pointer (thread_task, g_steal_pointer (&ret_data),
350 (GDestroyNotify) fp_image_detect_minutiae_free);
351 }
352
353 /**
354 * fp_image_get_height:
355 * @self: A #FpImage
356 *
357 * Gets the pixel height of an image.
358 *
359 * Returns: the height of the image
360 */
361 guint
362 15 fp_image_get_height (FpImage *self)
363 {
364 15 return self->height;
365 }
366
367 /**
368 * fp_image_get_width:
369 * @self: A #FpImage
370 *
371 * Gets the pixel width of an image.
372 *
373 * Returns: the width of the image
374 */
375 guint
376 15 fp_image_get_width (FpImage *self)
377 {
378 15 return self->width;
379 }
380
381 /**
382 * fp_image_get_ppmm:
383 * @self: A #FpImage
384 *
385 * Gets the resolution of the image. Note that this is assumed to
386 * be fixed to 500 points per inch (~19.685 p/mm) for most drivers.
387 *
388 * Returns: the resolution of the image in points per millimeter
389 */
390 gdouble
391 fp_image_get_ppmm (FpImage *self)
392 {
393 return self->ppmm;
394 }
395
396 /**
397 * fp_image_get_data:
398 * @self: A #FpImage
399 * @len: (out) (optional): Return location for length or %NULL
400 *
401 * Gets the greyscale data for an image. This data must not be modified or
402 * freed.
403 *
404 * Returns: (transfer none) (array length=len): The image data
405 */
406 const guchar *
407 15 fp_image_get_data (FpImage *self, gsize *len)
408 {
409
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 if (len)
410 15 *len = self->width * self->height;
411
412 15 return self->data;
413 }
414
415 /**
416 * fp_image_get_binarized:
417 * @self: A #FpImage
418 * @len: (out) (optional): Return location for length or %NULL
419 *
420 * Gets the binarized data for an image. This data must not be modified or
421 * freed. You need to first detect the minutiae using
422 * fp_image_detect_minutiae().
423 *
424 * Returns: (transfer none) (array length=len): The binarized image data
425 */
426 const guchar *
427 fp_image_get_binarized (FpImage *self, gsize *len)
428 {
429 if (len && self->binarized)
430 *len = self->width * self->height;
431
432 return self->binarized;
433 }
434
435 /**
436 * fp_image_get_minutiae:
437 * @self: A #FpImage
438 *
439 * Gets the minutiae for an image. This data must not be modified or
440 * freed. You need to first detect the minutiae using
441 * fp_image_detect_minutiae().
442 *
443 * Returns: (transfer none) (element-type FpMinutia): The detected minutiae
444 */
445 GPtrArray *
446 33 fp_image_get_minutiae (FpImage *self)
447 {
448 33 return self->minutiae;
449 }
450
451 /**
452 * fp_image_detect_minutiae:
453 * @self: A #FpImage
454 * @cancellable: a #GCancellable, or %NULL
455 * @callback: the function to call on completion
456 * @user_data: the data to pass to @callback
457 *
458 * Detects the minutiae found in an image.
459 */
460 void
461 48 fp_image_detect_minutiae (FpImage *self,
462 GCancellable *cancellable,
463 GAsyncReadyCallback callback,
464 gpointer user_data)
465 {
466 48 g_autoptr(GTask) task = NULL;
467
468
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 g_return_if_fail (FP_IS_IMAGE (self));
469
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 g_return_if_fail (callback != NULL);
470
471 48 task = g_task_new (self, cancellable, callback, user_data);
472 48 g_task_set_source_tag (task, fp_image_detect_minutiae);
473 48 g_task_set_check_cancellable (task, TRUE);
474
475
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 48 times.
48 if (!g_atomic_int_compare_and_exchange (&self->detection_in_progress,
476 FALSE, TRUE))
477 {
478 g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE,
479 "Minutiae detection is already in progress");
480 return;
481 }
482
483 48 g_task_run_in_thread (g_steal_pointer (&task),
484 fp_image_detect_minutiae_nbis_thread_func);
485 }
486
487 /**
488 * fp_image_detect_minutiae_finish:
489 * @self: A #FpImage
490 * @result: A #GAsyncResult
491 * @error: Return location for errors, or %NULL to ignore
492 *
493 * Finish minutiae detection in an image
494 *
495 * Returns: %TRUE on success
496 */
497 gboolean
498 48 fp_image_detect_minutiae_finish (FpImage *self,
499 GAsyncResult *result,
500 GError **error)
501 {
502 48 GTask *task;
503 48 gboolean changed;
504
505
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 g_return_val_if_fail (FP_IS_IMAGE (self), FALSE);
506
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 48 times.
48 g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
507
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 48 times.
48 g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) ==
508 fp_image_detect_minutiae, FALSE);
509
510 48 task = G_TASK (result);
511 48 changed = g_atomic_int_compare_and_exchange (&self->detection_in_progress,
512 TRUE, FALSE);
513
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 g_assert (changed);
514
515
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 48 times.
48 if (g_task_had_error (task))
516 {
517 gpointer data = g_task_propagate_pointer (task, error);
518 g_assert (data == NULL);
519 return FALSE;
520 }
521
522 48 return fp_image_detect_minutiae_nbis_finish (self, task, error);
523 }
524
525 /**
526 * fp_minutia_get_coords:
527 * @min: A #FpMinutia
528 * @x: (out): x position in image
529 * @y: (out): y position in image
530 *
531 * Returns the coordinates of the found minutia. This is only useful for
532 * debugging purposes and the API is not considered stable for production.
533 */
534 void
535 fp_minutia_get_coords (FpMinutia *min, gint *x, gint *y)
536 {
537 if (x)
538 *x = min->x;
539 if (y)
540 *y = min->y;
541 }
542