Files
libfprint/libfprint/fp-image.c
2023-11-24 21:02:50 +01:00

542 lines
14 KiB
C

/*
* FPrint Image
* Copyright (C) 2007 Daniel Drake <dsd@gentoo.org>
* Copyright (C) 2019 Benjamin Berg <bberg@redhat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#define FP_COMPONENT "image"
#include "fpi-compat.h"
#include "fpi-image.h"
#include "fpi-log.h"
#include <config.h>
#include <nbis.h>
/**
* SECTION: fp-image
* @title: FpImage
* @short_description: Internal Image handling routines
*
* Some devices will provide the image data corresponding to a print
* this object allows accessing this data.
*/
G_DEFINE_TYPE (FpImage, fp_image, G_TYPE_OBJECT)
enum {
PROP_0,
PROP_WIDTH,
PROP_HEIGHT,
N_PROPS
};
static GParamSpec *properties[N_PROPS];
FpImage *
fp_image_new (gint width, gint height)
{
return g_object_new (FP_TYPE_IMAGE,
"width", width,
"height", height,
NULL);
}
static void
fp_image_finalize (GObject *object)
{
FpImage *self = (FpImage *) object;
g_clear_pointer (&self->data, g_free);
g_clear_pointer (&self->binarized, g_free);
g_clear_pointer (&self->minutiae, g_ptr_array_unref);
G_OBJECT_CLASS (fp_image_parent_class)->finalize (object);
}
static void
fp_image_constructed (GObject *object)
{
FpImage *self = (FpImage *) object;
self->data = g_malloc0 (self->width * self->height);
}
static void
fp_image_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
FpImage *self = FP_IMAGE (object);
switch (prop_id)
{
case PROP_WIDTH:
g_value_set_uint (value, self->width);
break;
case PROP_HEIGHT:
g_value_set_uint (value, self->height);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
fp_image_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
FpImage *self = FP_IMAGE (object);
switch (prop_id)
{
case PROP_WIDTH:
self->width = g_value_get_uint (value);
break;
case PROP_HEIGHT:
self->height = g_value_get_uint (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
fp_image_class_init (FpImageClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = fp_image_finalize;
object_class->constructed = fp_image_constructed;
object_class->set_property = fp_image_set_property;
object_class->get_property = fp_image_get_property;
properties[PROP_WIDTH] =
g_param_spec_uint ("width",
"Width",
"The width of the image",
0,
G_MAXUINT16,
0,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
properties[PROP_HEIGHT] =
g_param_spec_uint ("height",
"Height",
"The height of the image",
0,
G_MAXUINT16,
0,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
fp_image_init (FpImage *self)
{
}
typedef struct
{
struct fp_minutiae *minutiae;
guchar *binarized;
FpiImageFlags flags;
unsigned char *image;
gboolean image_changed;
} DetectMinutiaeNbisData;
static void
fp_image_detect_minutiae_free (DetectMinutiaeNbisData *data)
{
g_clear_pointer (&data->minutiae, free_minutiae);
g_clear_pointer (&data->binarized, g_free);
if (data->image_changed)
g_clear_pointer (&data->image, g_free);
g_free (data);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (DetectMinutiaeNbisData, fp_image_detect_minutiae_free)
static gboolean
fp_image_detect_minutiae_nbis_finish (FpImage *self,
GTask *task,
GError **error)
{
g_autoptr(DetectMinutiaeNbisData) data = NULL;
data = g_task_propagate_pointer (task, error);
if (data != NULL)
{
self->flags = data->flags;
if (data->image_changed)
{
g_clear_pointer (&self->data, g_free);
self->data = g_steal_pointer (&data->image);
}
g_clear_pointer (&self->binarized, g_free);
self->binarized = g_steal_pointer (&data->binarized);
g_clear_pointer (&self->minutiae, g_ptr_array_unref);
self->minutiae = g_ptr_array_new_full (data->minutiae->num,
(GDestroyNotify) free_minutia);
for (int i = 0; i < data->minutiae->num; i++)
g_ptr_array_add (self->minutiae,
g_steal_pointer (&data->minutiae->list[i]));
/* Don't let free_minutiae delete the minutiae that we now own. */
data->minutiae->num = 0;
return TRUE;
}
return FALSE;
}
static void
vflip (guint8 *data, gint width, gint height)
{
int data_len = width * height;
unsigned char rowbuf[width];
int i;
for (i = 0; i < height / 2; i++)
{
int offset = i * width;
int swap_offset = data_len - (width * (i + 1));
/* copy top row into buffer */
memcpy (rowbuf, data + offset, width);
/* copy lower row over upper row */
memcpy (data + offset, data + swap_offset, width);
/* copy buffer over lower row */
memcpy (data + swap_offset, rowbuf, width);
}
}
static void
hflip (guint8 *data, gint width, gint height)
{
unsigned char rowbuf[width];
int i, j;
for (i = 0; i < height; i++)
{
int offset = i * width;
memcpy (rowbuf, data + offset, width);
for (j = 0; j < width; j++)
data[offset + j] = rowbuf[width - j - 1];
}
}
static void
invert_colors (guint8 *data, gint width, gint height)
{
int data_len = width * height;
int i;
for (i = 0; i < data_len; i++)
data[i] = 0xff - data[i];
}
static void
fp_image_detect_minutiae_nbis_thread_func (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
g_autoptr(GTimer) timer = NULL;
g_autoptr(DetectMinutiaeNbisData) ret_data = NULL;
g_autoptr(GTask) thread_task = g_steal_pointer (&task);
g_autofree gint *direction_map = NULL;
g_autofree gint *low_contrast_map = NULL;
g_autofree gint *low_flow_map = NULL;
g_autofree gint *high_curve_map = NULL;
g_autofree gint *quality_map = NULL;
g_autofree LFSPARMS *lfsparms = NULL;
FpImage *self = source_object;
FpiImageFlags minutiae_flags;
unsigned char *image;
gint map_w, map_h;
gint bw, bh, bd;
gint r;
image = self->data;
minutiae_flags = self->flags & ~(FPI_IMAGE_H_FLIPPED |
FPI_IMAGE_V_FLIPPED |
FPI_IMAGE_COLORS_INVERTED);
if (minutiae_flags != FPI_IMAGE_NONE)
image = g_memdup2 (self->data, self->width * self->height);
ret_data = g_new0 (DetectMinutiaeNbisData, 1);
ret_data->flags = minutiae_flags;
ret_data->image = image;
ret_data->image_changed = image != self->data;
/* Normalize the image first */
if (self->flags & FPI_IMAGE_H_FLIPPED)
hflip (image, self->width, self->height);
if (self->flags & FPI_IMAGE_V_FLIPPED)
vflip (image, self->width, self->height);
if (self->flags & FPI_IMAGE_COLORS_INVERTED)
invert_colors (image, self->width, self->height);
lfsparms = g_memdup2 (&g_lfsparms_V2, sizeof (LFSPARMS));
lfsparms->remove_perimeter_pts = minutiae_flags & FPI_IMAGE_PARTIAL ? TRUE : FALSE;
timer = g_timer_new ();
r = get_minutiae (&ret_data->minutiae, &quality_map, &direction_map,
&low_contrast_map, &low_flow_map, &high_curve_map,
&map_w, &map_h, &ret_data->binarized, &bw, &bh, &bd,
image, self->width, self->height, 8,
self->ppmm, lfsparms);
g_timer_stop (timer);
fp_dbg ("Minutiae scan completed in %f secs", g_timer_elapsed (timer, NULL));
if (g_task_had_error (thread_task))
return;
if (r)
{
fp_err ("get minutiae failed, code %d", r);
g_task_return_new_error (thread_task, G_IO_ERROR,
G_IO_ERROR_FAILED,
"Minutiae scan failed with code %d", r);
return;
}
if (!ret_data->minutiae || ret_data->minutiae->num == 0)
{
g_task_return_new_error (thread_task, G_IO_ERROR, G_IO_ERROR_FAILED,
"No minutiae found");
return;
}
g_task_return_pointer (thread_task, g_steal_pointer (&ret_data),
(GDestroyNotify) fp_image_detect_minutiae_free);
}
/**
* fp_image_get_height:
* @self: A #FpImage
*
* Gets the pixel height of an image.
*
* Returns: the height of the image
*/
guint
fp_image_get_height (FpImage *self)
{
return self->height;
}
/**
* fp_image_get_width:
* @self: A #FpImage
*
* Gets the pixel width of an image.
*
* Returns: the width of the image
*/
guint
fp_image_get_width (FpImage *self)
{
return self->width;
}
/**
* fp_image_get_ppmm:
* @self: A #FpImage
*
* Gets the resolution of the image. Note that this is assumed to
* be fixed to 500 points per inch (~19.685 p/mm) for most drivers.
*
* Returns: the resolution of the image in points per millimeter
*/
gdouble
fp_image_get_ppmm (FpImage *self)
{
return self->ppmm;
}
/**
* fp_image_get_data:
* @self: A #FpImage
* @len: (out) (optional): Return location for length or %NULL
*
* Gets the greyscale data for an image. This data must not be modified or
* freed.
*
* Returns: (transfer none) (array length=len): The image data
*/
const guchar *
fp_image_get_data (FpImage *self, gsize *len)
{
if (len)
*len = self->width * self->height;
return self->data;
}
/**
* fp_image_get_binarized:
* @self: A #FpImage
* @len: (out) (optional): Return location for length or %NULL
*
* Gets the binarized data for an image. This data must not be modified or
* freed. You need to first detect the minutiae using
* fp_image_detect_minutiae().
*
* Returns: (transfer none) (array length=len): The binarized image data
*/
const guchar *
fp_image_get_binarized (FpImage *self, gsize *len)
{
if (len && self->binarized)
*len = self->width * self->height;
return self->binarized;
}
/**
* fp_image_get_minutiae:
* @self: A #FpImage
*
* Gets the minutiae for an image. This data must not be modified or
* freed. You need to first detect the minutiae using
* fp_image_detect_minutiae().
*
* Returns: (transfer none) (element-type FpMinutia): The detected minutiae
*/
GPtrArray *
fp_image_get_minutiae (FpImage *self)
{
return self->minutiae;
}
/**
* fp_image_detect_minutiae:
* @self: A #FpImage
* @cancellable: a #GCancellable, or %NULL
* @callback: the function to call on completion
* @user_data: the data to pass to @callback
*
* Detects the minutiae found in an image.
*/
void
fp_image_detect_minutiae (FpImage *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(GTask) task = NULL;
g_return_if_fail (FP_IS_IMAGE (self));
g_return_if_fail (callback != NULL);
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_source_tag (task, fp_image_detect_minutiae);
g_task_set_check_cancellable (task, TRUE);
if (!g_atomic_int_compare_and_exchange (&self->detection_in_progress,
FALSE, TRUE))
{
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE,
"Minutiae detection is already in progress");
return;
}
g_task_run_in_thread (g_steal_pointer (&task),
fp_image_detect_minutiae_nbis_thread_func);
}
/**
* fp_image_detect_minutiae_finish:
* @self: A #FpImage
* @result: A #GAsyncResult
* @error: Return location for errors, or %NULL to ignore
*
* Finish minutiae detection in an image
*
* Returns: %TRUE on success
*/
gboolean
fp_image_detect_minutiae_finish (FpImage *self,
GAsyncResult *result,
GError **error)
{
GTask *task;
gboolean changed;
g_return_val_if_fail (FP_IS_IMAGE (self), FALSE);
g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) ==
fp_image_detect_minutiae, FALSE);
task = G_TASK (result);
changed = g_atomic_int_compare_and_exchange (&self->detection_in_progress,
TRUE, FALSE);
g_assert (changed);
if (g_task_had_error (task))
{
gpointer data = g_task_propagate_pointer (task, error);
g_assert (data == NULL);
return FALSE;
}
return fp_image_detect_minutiae_nbis_finish (self, task, error);
}
/**
* fp_minutia_get_coords:
* @min: A #FpMinutia
* @x: (out): x position in image
* @y: (out): y position in image
*
* Returns the coordinates of the found minutia. This is only useful for
* debugging purposes and the API is not considered stable for production.
*/
void
fp_minutia_get_coords (FpMinutia *min, gint *x, gint *y)
{
if (x)
*x = min->x;
if (y)
*y = min->y;
}