/* * FPrint Image * Copyright (C) 2007 Daniel Drake * Copyright (C) 2019 Benjamin Berg * * 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 #include /** * 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; }