7ed9b0c2f9
Now that nbis is a static library it should be possible to compile it without any fprint-built dependency, although since it included fp_internal there was a compile-time dependency on the fp-enums that can be generated at later times. So: - Move nbis-helpers to nbis includes (and remove inclusion in fp_internal) - Move the Minutiae definitions inside a standalone fpi-minutiae header - Include fpi-minutiae.h in fp_internal.h - Include nbis-hepers.h and fpi-minutiae.h in nbis' lfs.h - Adapt missing definitions in libfprint
611 lines
16 KiB
C
611 lines
16 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
|
|
*/
|
|
|
|
#include "fpi-image.h"
|
|
#include "fpi-log.h"
|
|
|
|
#include <nbis.h>
|
|
|
|
#if HAVE_PIXMAN
|
|
#include <pixman.h>
|
|
#endif
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
|
|
/**
|
|
* SECTION: fpi-image
|
|
* @title: Internal FpImage
|
|
* @short_description: Internal image handling routines
|
|
*
|
|
* Internal image handling routines. Also see the public <ulink
|
|
* url="libfprint-FpImage.html">FpImage routines</ulink>.
|
|
*/
|
|
|
|
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
|
|
{
|
|
GAsyncReadyCallback user_cb;
|
|
struct fp_minutiae *minutiae;
|
|
gint width, height;
|
|
gdouble ppmm;
|
|
FpiImageFlags flags;
|
|
guchar *image;
|
|
guchar *binarized;
|
|
} DetectMinutiaeData;
|
|
|
|
static void
|
|
fp_image_detect_minutiae_free (DetectMinutiaeData *data)
|
|
{
|
|
g_clear_pointer (&data->image, g_free);
|
|
g_clear_pointer (&data->minutiae, free_minutiae);
|
|
g_clear_pointer (&data->binarized, g_free);
|
|
g_free (data);
|
|
}
|
|
|
|
static void
|
|
fp_image_detect_minutiae_cb (GObject *source_object,
|
|
GAsyncResult *res,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task = G_TASK (res);
|
|
FpImage *image;
|
|
DetectMinutiaeData *data = g_task_get_task_data (task);
|
|
GCancellable *cancellable;
|
|
|
|
cancellable = g_task_get_cancellable (task);
|
|
if (!cancellable || !g_cancellable_is_cancelled (cancellable))
|
|
{
|
|
gint i;
|
|
image = FP_IMAGE (source_object);
|
|
|
|
image->flags = data->flags;
|
|
|
|
g_clear_pointer (&image->data, g_free);
|
|
image->data = g_steal_pointer (&data->image);
|
|
|
|
g_clear_pointer (&image->binarized, g_free);
|
|
image->binarized = g_steal_pointer (&data->binarized);
|
|
|
|
g_clear_pointer (&image->minutiae, g_ptr_array_unref);
|
|
image->minutiae = g_ptr_array_new_full (data->minutiae->num,
|
|
(GDestroyNotify) free_minutia);
|
|
|
|
for (i = 0; i < data->minutiae->num; i++)
|
|
g_ptr_array_add (image->minutiae,
|
|
g_steal_pointer (&data->minutiae->list[i]));
|
|
|
|
/* Don't let it delete anything. */
|
|
data->minutiae->num = 0;
|
|
}
|
|
|
|
if (data->user_cb)
|
|
data->user_cb (source_object, res, user_data);
|
|
}
|
|
|
|
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_thread_func (GTask *task,
|
|
gpointer source_object,
|
|
gpointer task_data,
|
|
GCancellable *cancellable)
|
|
{
|
|
g_autoptr(GTimer) timer = NULL;
|
|
DetectMinutiaeData *data = task_data;
|
|
struct fp_minutiae *minutiae = NULL;
|
|
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 guchar *bdata = NULL;
|
|
gint map_w, map_h;
|
|
gint bw, bh, bd;
|
|
gint r;
|
|
|
|
/* Normalize the image first */
|
|
if (data->flags & FPI_IMAGE_H_FLIPPED)
|
|
hflip (data->image, data->width, data->height);
|
|
|
|
if (data->flags & FPI_IMAGE_V_FLIPPED)
|
|
vflip (data->image, data->width, data->height);
|
|
|
|
if (data->flags & FPI_IMAGE_COLORS_INVERTED)
|
|
invert_colors (data->image, data->width, data->height);
|
|
|
|
data->flags &= ~(FPI_IMAGE_H_FLIPPED | FPI_IMAGE_V_FLIPPED | FPI_IMAGE_COLORS_INVERTED);
|
|
|
|
timer = g_timer_new ();
|
|
r = get_minutiae (&minutiae, &quality_map, &direction_map,
|
|
&low_contrast_map, &low_flow_map, &high_curve_map,
|
|
&map_w, &map_h, &bdata, &bw, &bh, &bd,
|
|
data->image, data->width, data->height, 8,
|
|
data->ppmm, &g_lfsparms_V2);
|
|
g_timer_stop (timer);
|
|
fp_dbg ("Minutiae scan completed in %f secs", g_timer_elapsed (timer, NULL));
|
|
|
|
data->binarized = g_steal_pointer (&bdata);
|
|
data->minutiae = minutiae;
|
|
|
|
if (r)
|
|
{
|
|
fp_err ("get minutiae failed, code %d", r);
|
|
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "Minutiae scan failed with code %d", r);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
GTask *task;
|
|
DetectMinutiaeData *data = g_new0 (DetectMinutiaeData, 1);
|
|
|
|
task = g_task_new (self, cancellable, fp_image_detect_minutiae_cb, user_data);
|
|
|
|
data->image = g_malloc (self->width * self->height);
|
|
memcpy (data->image, self->data, self->width * self->height);
|
|
data->flags = self->flags;
|
|
data->width = self->width;
|
|
data->height = self->height;
|
|
data->ppmm = self->ppmm;
|
|
data->user_cb = callback;
|
|
|
|
g_task_set_task_data (task, data, (GDestroyNotify) fp_image_detect_minutiae_free);
|
|
g_task_run_in_thread (task, fp_image_detect_minutiae_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)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (result), error);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* fpi_std_sq_dev:
|
|
* @buf: buffer (usually bitmap, one byte per pixel)
|
|
* @size: size of @buffer
|
|
*
|
|
* Calculates the squared standard deviation of the individual
|
|
* pixels in the buffer, as per the following formula:
|
|
* |[<!-- -->
|
|
* mean = sum (buf[0..size]) / size
|
|
* sq_dev = sum ((buf[0.size] - mean) ^ 2)
|
|
* ]|
|
|
* This function is usually used to determine whether image
|
|
* is empty.
|
|
*
|
|
* Returns: the squared standard deviation for @buffer
|
|
*/
|
|
gint
|
|
fpi_std_sq_dev (const guint8 *buf,
|
|
gint size)
|
|
{
|
|
guint64 res = 0, mean = 0;
|
|
gint i;
|
|
|
|
for (i = 0; i < size; i++)
|
|
mean += buf[i];
|
|
|
|
mean /= size;
|
|
|
|
for (i = 0; i < size; i++)
|
|
{
|
|
int dev = (int) buf[i] - mean;
|
|
res += dev * dev;
|
|
}
|
|
|
|
return res / size;
|
|
}
|
|
|
|
/**
|
|
* fpi_mean_sq_diff_norm:
|
|
* @buf1: buffer (usually bitmap, one byte per pixel)
|
|
* @buf2: buffer (usually bitmap, one byte per pixel)
|
|
* @size: buffer size of smallest buffer
|
|
*
|
|
* This function calculates the normalized mean square difference of
|
|
* two buffers, usually two lines, as per the following formula:
|
|
* |[<!-- -->
|
|
* sq_diff = sum ((buf1[0..size] - buf2[0..size]) ^ 2) / size
|
|
* ]|
|
|
*
|
|
* This functions is usually used to get numerical difference
|
|
* between two images.
|
|
*
|
|
* Returns: the normalized mean squared difference between @buf1 and @buf2
|
|
*/
|
|
gint
|
|
fpi_mean_sq_diff_norm (const guint8 *buf1,
|
|
const guint8 *buf2,
|
|
gint size)
|
|
{
|
|
int res = 0, i;
|
|
|
|
for (i = 0; i < size; i++)
|
|
{
|
|
int dev = (int) buf1[i] - (int) buf2[i];
|
|
res += dev * dev;
|
|
}
|
|
|
|
return res / size;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
#if HAVE_PIXMAN
|
|
FpImage *
|
|
fpi_image_resize (FpImage *orig_img,
|
|
guint w_factor,
|
|
guint h_factor)
|
|
{
|
|
int new_width = orig_img->width * w_factor;
|
|
int new_height = orig_img->height * h_factor;
|
|
pixman_image_t *orig, *resized;
|
|
pixman_transform_t transform;
|
|
FpImage *newimg;
|
|
|
|
orig = pixman_image_create_bits (PIXMAN_a8, orig_img->width, orig_img->height, (uint32_t *) orig_img->data, orig_img->width);
|
|
resized = pixman_image_create_bits (PIXMAN_a8, new_width, new_height, NULL, new_width);
|
|
|
|
pixman_transform_init_identity (&transform);
|
|
pixman_transform_scale (NULL, &transform, pixman_int_to_fixed (w_factor), pixman_int_to_fixed (h_factor));
|
|
pixman_image_set_transform (orig, &transform);
|
|
pixman_image_set_filter (orig, PIXMAN_FILTER_BILINEAR, NULL, 0);
|
|
pixman_image_composite32 (PIXMAN_OP_SRC,
|
|
orig, /* src */
|
|
NULL, /* mask */
|
|
resized, /* dst */
|
|
0, 0, /* src x y */
|
|
0, 0, /* mask x y */
|
|
0, 0, /* dst x y */
|
|
new_width, new_height /* width height */
|
|
);
|
|
|
|
newimg = fp_image_new (new_width, new_height);
|
|
newimg->flags = orig_img->flags;
|
|
|
|
memcpy (newimg->data, pixman_image_get_data (resized), new_width * new_height);
|
|
|
|
pixman_image_unref (orig);
|
|
pixman_image_unref (resized);
|
|
|
|
return newimg;
|
|
}
|
|
#endif
|