mirror of
https://gitlab.freedesktop.org/libfprint/libfprint.git
synced 2025-11-15 07:38:12 +00:00
Some scanners provide hardware assistance in frame assemling, i.e. horizontal and vertical offset to previous frame is provided. This commit improves code to utilise that assistance. Sensors without hardware assistance will use software algorithm, which was also improved to do search in horizontal direction.
426 lines
9.9 KiB
C
426 lines
9.9 KiB
C
/*
|
|
* Shared functions between libfprint Authentec drivers
|
|
* Copyright (C) 2007-2008 Daniel Drake <dsd@gentoo.org>
|
|
*
|
|
* 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 "aeslib"
|
|
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
|
|
#include <libusb.h>
|
|
#include <glib.h>
|
|
|
|
#include "fp_internal.h"
|
|
#include "aeslib.h"
|
|
|
|
#define MAX_REGWRITES_PER_REQUEST 16
|
|
|
|
#define BULK_TIMEOUT 4000
|
|
#define EP_IN (1 | LIBUSB_ENDPOINT_IN)
|
|
#define EP_OUT (2 | LIBUSB_ENDPOINT_OUT)
|
|
|
|
struct write_regv_data {
|
|
struct fp_img_dev *imgdev;
|
|
unsigned int num_regs;
|
|
const struct aes_regwrite *regs;
|
|
unsigned int offset;
|
|
aes_write_regv_cb callback;
|
|
void *user_data;
|
|
};
|
|
|
|
static void continue_write_regv(struct write_regv_data *wdata);
|
|
|
|
/* libusb bulk callback for regv write completion transfer. continues the
|
|
* transaction */
|
|
static void write_regv_trf_complete(struct libusb_transfer *transfer)
|
|
{
|
|
struct write_regv_data *wdata = transfer->user_data;
|
|
|
|
if (transfer->status != LIBUSB_TRANSFER_COMPLETED)
|
|
wdata->callback(wdata->imgdev, -EIO, wdata->user_data);
|
|
else if (transfer->length != transfer->actual_length)
|
|
wdata->callback(wdata->imgdev, -EPROTO, wdata->user_data);
|
|
else
|
|
continue_write_regv(wdata);
|
|
|
|
g_free(transfer->buffer);
|
|
libusb_free_transfer(transfer);
|
|
}
|
|
|
|
/* write from wdata->offset to upper_bound (inclusive) of wdata->regs */
|
|
static int do_write_regv(struct write_regv_data *wdata, int upper_bound)
|
|
{
|
|
unsigned int offset = wdata->offset;
|
|
unsigned int num = upper_bound - offset + 1;
|
|
size_t alloc_size = num * 2;
|
|
unsigned char *data = g_malloc(alloc_size);
|
|
unsigned int i;
|
|
size_t data_offset = 0;
|
|
struct libusb_transfer *transfer = libusb_alloc_transfer(0);
|
|
int r;
|
|
|
|
if (!transfer) {
|
|
g_free(data);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (i = offset; i < offset + num; i++) {
|
|
const struct aes_regwrite *regwrite = &wdata->regs[i];
|
|
data[data_offset++] = regwrite->reg;
|
|
data[data_offset++] = regwrite->value;
|
|
}
|
|
|
|
libusb_fill_bulk_transfer(transfer, wdata->imgdev->udev, EP_OUT, data,
|
|
alloc_size, write_regv_trf_complete, wdata, BULK_TIMEOUT);
|
|
r = libusb_submit_transfer(transfer);
|
|
if (r < 0) {
|
|
g_free(data);
|
|
libusb_free_transfer(transfer);
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
/* write the next batch of registers to be written, or if there are no more,
|
|
* indicate completion to the caller */
|
|
static void continue_write_regv(struct write_regv_data *wdata)
|
|
{
|
|
unsigned int offset = wdata->offset;
|
|
unsigned int regs_remaining;
|
|
unsigned int limit;
|
|
unsigned int upper_bound;
|
|
int i;
|
|
int r;
|
|
|
|
/* skip all zeros and ensure there is still work to do */
|
|
while (TRUE) {
|
|
if (offset >= wdata->num_regs) {
|
|
fp_dbg("all registers written");
|
|
wdata->callback(wdata->imgdev, 0, wdata->user_data);
|
|
return;
|
|
}
|
|
if (wdata->regs[offset].reg)
|
|
break;
|
|
offset++;
|
|
}
|
|
|
|
wdata->offset = offset;
|
|
regs_remaining = wdata->num_regs - offset;
|
|
limit = MIN(regs_remaining, MAX_REGWRITES_PER_REQUEST);
|
|
upper_bound = offset + limit - 1;
|
|
|
|
/* determine if we can write the entire of the regs at once, or if there
|
|
* is a zero dividing things up */
|
|
for (i = offset; i <= upper_bound; i++)
|
|
if (!wdata->regs[i].reg) {
|
|
upper_bound = i - 1;
|
|
break;
|
|
}
|
|
|
|
r = do_write_regv(wdata, upper_bound);
|
|
if (r < 0) {
|
|
wdata->callback(wdata->imgdev, r, wdata->user_data);
|
|
return;
|
|
}
|
|
|
|
wdata->offset = upper_bound + 1;
|
|
}
|
|
|
|
/* write a load of registers to the device, combining multiple writes in a
|
|
* single URB up to a limit. insert writes to non-existent register 0 to force
|
|
* specific groups of writes to be separated by different URBs. */
|
|
void aes_write_regv(struct fp_img_dev *dev, const struct aes_regwrite *regs,
|
|
unsigned int num_regs, aes_write_regv_cb callback, void *user_data)
|
|
{
|
|
struct write_regv_data *wdata = g_malloc(sizeof(*wdata));
|
|
fp_dbg("write %d regs", num_regs);
|
|
wdata->imgdev = dev;
|
|
wdata->num_regs = num_regs;
|
|
wdata->regs = regs;
|
|
wdata->offset = 0;
|
|
wdata->callback = callback;
|
|
wdata->user_data = user_data;
|
|
continue_write_regv(wdata);
|
|
}
|
|
|
|
static inline unsigned char aes_get_pixel(struct aes_stripe *frame,
|
|
unsigned int x,
|
|
unsigned int y,
|
|
unsigned int frame_width,
|
|
unsigned int frame_height)
|
|
{
|
|
unsigned char ret;
|
|
|
|
ret = frame->data[x * (frame_height >> 1) + (y >> 1)];
|
|
ret = y % 2 ? ret >> 4 : ret & 0xf;
|
|
ret *= 17;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned int calc_error(struct aes_stripe *first_frame,
|
|
struct aes_stripe *second_frame,
|
|
int dx,
|
|
int dy,
|
|
unsigned int frame_width,
|
|
unsigned int frame_height)
|
|
{
|
|
unsigned int width, height;
|
|
unsigned int x1, y1, x2, y2, err, i, j;
|
|
|
|
width = frame_width - (dx > 0 ? dx : -dx);
|
|
height = frame_height - dy;
|
|
|
|
y1 = 0;
|
|
y2 = dy;
|
|
i = 0;
|
|
err = 0;
|
|
do {
|
|
x1 = dx < 0 ? 0 : dx;
|
|
x2 = dx < 0 ? -dx : 0;
|
|
j = 0;
|
|
|
|
do {
|
|
unsigned char v1, v2;
|
|
|
|
|
|
v1 = aes_get_pixel(first_frame, x1, y1, frame_width, frame_height);
|
|
v2 = aes_get_pixel(second_frame, x2, y2, frame_width, frame_height);
|
|
err += v1 > v2 ? v1 - v2 : v2 - v1;
|
|
j++;
|
|
x1++;
|
|
x2++;
|
|
|
|
} while (j < width);
|
|
i++;
|
|
y1++;
|
|
y2++;
|
|
} while (i < height);
|
|
|
|
/* Normalize error */
|
|
err *= (frame_height * frame_width);
|
|
err /= (height * width);
|
|
|
|
if (err == 0)
|
|
return INT_MAX;
|
|
|
|
return err;
|
|
}
|
|
|
|
/* This function is rather CPU-intensive. It's better to use hardware
|
|
* to detect movement direction when possible.
|
|
*/
|
|
static void find_overlap(struct aes_stripe *first_frame,
|
|
struct aes_stripe *second_frame,
|
|
unsigned int *min_error,
|
|
unsigned int frame_width,
|
|
unsigned int frame_height)
|
|
{
|
|
int dx, dy;
|
|
unsigned int err;
|
|
*min_error = INT_MAX;
|
|
|
|
/* Seeking in horizontal and vertical dimensions,
|
|
* for horizontal dimension we'll check only 8 pixels
|
|
* in both directions. For vertical direction diff is
|
|
* rarely less than 2, so start with it.
|
|
*/
|
|
for (dy = 2; dy < frame_height; dy++) {
|
|
for (dx = -8; dx < 8; dx++) {
|
|
err = calc_error(first_frame, second_frame,
|
|
dx, dy, frame_width, frame_height);
|
|
if (err < *min_error) {
|
|
*min_error = err;
|
|
second_frame->delta_x = -dx;
|
|
second_frame->delta_y = dy;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
unsigned int aes_calc_delta(GSList *stripes, size_t num_stripes,
|
|
unsigned int frame_width, unsigned int frame_height,
|
|
gboolean reverse)
|
|
{
|
|
GSList *list_entry = stripes;
|
|
GTimer *timer;
|
|
int frame = 1;
|
|
int height = 0;
|
|
struct aes_stripe *prev_stripe = list_entry->data;
|
|
unsigned int min_error;
|
|
|
|
list_entry = g_slist_next(list_entry);
|
|
|
|
timer = g_timer_new();
|
|
do {
|
|
struct aes_stripe *cur_stripe = list_entry->data;
|
|
|
|
if (reverse) {
|
|
find_overlap(prev_stripe, cur_stripe, &min_error,
|
|
frame_width, frame_height);
|
|
prev_stripe->delta_y = -prev_stripe->delta_y;
|
|
prev_stripe->delta_x = -prev_stripe->delta_x;
|
|
}
|
|
else
|
|
find_overlap(cur_stripe, prev_stripe, &min_error,
|
|
frame_width, frame_height);
|
|
|
|
frame++;
|
|
height += prev_stripe->delta_y;
|
|
prev_stripe = cur_stripe;
|
|
list_entry = g_slist_next(list_entry);
|
|
|
|
} while (frame < num_stripes);
|
|
|
|
if (height < 0)
|
|
height = -height;
|
|
height += frame_height;
|
|
g_timer_stop(timer);
|
|
fp_dbg("calc delta completed in %f secs", g_timer_elapsed(timer, NULL));
|
|
g_timer_destroy(timer);
|
|
|
|
return height;
|
|
}
|
|
|
|
static inline void aes_blit_stripe(struct fp_img *img,
|
|
struct aes_stripe *stripe,
|
|
int x, int y, unsigned int frame_width,
|
|
unsigned int frame_height)
|
|
{
|
|
unsigned int ix, iy;
|
|
unsigned int fx, fy;
|
|
unsigned int width, height;
|
|
|
|
/* Find intersection */
|
|
if (x < 0) {
|
|
width = frame_width + x;
|
|
ix = 0;
|
|
fx = -x;
|
|
} else {
|
|
ix = x;
|
|
fx = 0;
|
|
width = frame_width;
|
|
}
|
|
if ((ix + width) > img->width)
|
|
width = img->width - ix;
|
|
|
|
if (y < 0) {
|
|
iy = 0;
|
|
fy = -y;
|
|
height = frame_height + y;
|
|
} else {
|
|
iy = y;
|
|
fy = 0;
|
|
height = frame_height;
|
|
}
|
|
|
|
if (fx > frame_width)
|
|
return;
|
|
|
|
if (fy > frame_height)
|
|
return;
|
|
|
|
if (ix > img->width)
|
|
return;
|
|
|
|
if (iy > img->height)
|
|
return;
|
|
|
|
if ((iy + height) > img->height)
|
|
height = img->height - iy;
|
|
|
|
for (; fy < height; fy++, iy++) {
|
|
if (x < 0) {
|
|
ix = 0;
|
|
fx = -x;
|
|
} else {
|
|
ix = x;
|
|
fx = 0;
|
|
}
|
|
for (; fx < width; fx++, ix++) {
|
|
img->data[ix + (iy * img->width)] = aes_get_pixel(stripe, fx, fy, frame_width, frame_height);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct fp_img *aes_assemble(GSList *stripes, size_t stripes_len,
|
|
unsigned int frame_width, unsigned int frame_height, unsigned int img_width)
|
|
{
|
|
GSList *stripe;
|
|
struct fp_img *img;
|
|
int height = 0;
|
|
int i, y, x;
|
|
gboolean reverse = FALSE;
|
|
struct aes_stripe *aes_stripe;
|
|
|
|
BUG_ON(stripes_len == 0);
|
|
BUG_ON(img_width < frame_width);
|
|
|
|
/* Calculate height */
|
|
i = 0;
|
|
stripe = stripes;
|
|
|
|
/* No offset for 1st image */
|
|
aes_stripe = stripe->data;
|
|
aes_stripe->delta_x = 0;
|
|
aes_stripe->delta_y = 0;
|
|
do {
|
|
aes_stripe = stripe->data;
|
|
|
|
height += aes_stripe->delta_y;
|
|
i++;
|
|
stripe = g_slist_next(stripe);
|
|
} while (i < stripes_len);
|
|
|
|
fp_dbg("height is %d", height);
|
|
|
|
if (height < 0) {
|
|
reverse = TRUE;
|
|
height = -height;
|
|
}
|
|
|
|
/* For last frame */
|
|
height += frame_height;
|
|
|
|
/* Create buffer big enough for max image */
|
|
img = fpi_img_new(img_width * height);
|
|
img->flags = FP_IMG_COLORS_INVERTED;
|
|
img->width = img_width;
|
|
img->height = height;
|
|
|
|
/* Assemble stripes */
|
|
i = 0;
|
|
stripe = stripes;
|
|
y = reverse ? (height - frame_height) : 0;
|
|
x = (img_width - frame_width) / 2;
|
|
|
|
do {
|
|
aes_stripe = stripe->data;
|
|
|
|
y += aes_stripe->delta_y;
|
|
x += aes_stripe->delta_x;
|
|
|
|
aes_blit_stripe(img, aes_stripe, x, y, frame_width, frame_height);
|
|
|
|
stripe = g_slist_next(stripe);
|
|
i++;
|
|
} while (i < stripes_len);
|
|
|
|
return img;
|
|
}
|