From 5b01f0b7c1a284ea980b7311315a4baecb6cba64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 17 Jun 2026 19:24:32 +0200 Subject: [PATCH] secugen: Do image processing inside GTask threads Do not block the main loop to process image data --- libfprint/drivers/secugen.c | 438 ++++++++++++++++++++++++++---------- 1 file changed, 318 insertions(+), 120 deletions(-) diff --git a/libfprint/drivers/secugen.c b/libfprint/drivers/secugen.c index 1c5410a6..57c5bd9c 100644 --- a/libfprint/drivers/secugen.c +++ b/libfprint/drivers/secugen.c @@ -185,7 +185,7 @@ G_DEFINE_TYPE (FpiDeviceSecugen, * correction, middle range scales linearly. Identical to the table * stored in the device's firmware data block. */ -static const guint8 g_blend_curve[256] = { +static const guint8 SECUGEN_BLEND_CURVE[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 21, 23, 25, 27, 29, 31, 32, 34, 36, 38, 40, 42, 44, @@ -1449,6 +1449,184 @@ secugen_sharpen (const guint8 *src, } } +typedef struct +{ + guint8 *raw_frame; + gboolean has_blc_offsets; + gint16 blc_offsets[16]; + gboolean has_ref_image; + guint8 *ref_image; + guint16 cal_value; + gboolean sharpen_enabled; + guint8 sharpen_threshold; + guint8 sharpen_amount; + guint8 sharpen_limit; +} SecugenCaptureTaskData; + +typedef struct +{ + guint8 *frame; + guint8 *cal_raw; +} SecugenDetectTaskData; + +typedef struct +{ + guint mean; + gboolean finger_present; + gboolean update_calibration; + guint8 *new_cal_raw; +} SecugenDetectTaskResult; + +static void +secugen_capture_task_data_free (SecugenCaptureTaskData *data) +{ + g_clear_pointer (&data->raw_frame, g_free); + g_clear_pointer (&data->ref_image, g_free); + g_free (data); +} + +static void +secugen_detect_task_data_free (SecugenDetectTaskData *data) +{ + g_clear_pointer (&data->frame, g_free); + g_clear_pointer (&data->cal_raw, g_free); + g_free (data); +} + +static void +secugen_detect_task_result_free (SecugenDetectTaskResult *result) +{ + g_clear_pointer (&result->new_cal_raw, g_free); + g_free (result); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SecugenCaptureTaskData, secugen_capture_task_data_free) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SecugenDetectTaskData, secugen_detect_task_data_free) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SecugenDetectTaskResult, secugen_detect_task_result_free) + +static void +secugen_capture_process_thread (GTask *task, + gpointer source_object G_GNUC_UNUSED, + gpointer task_data, + GCancellable *cancellable G_GNUC_UNUSED) +{ + SecugenCaptureTaskData *data = task_data; + g_autofree guint8 *image = g_malloc (SECUGEN_IMG_SIZE); + + if (g_task_return_error_if_cancelled (task)) + return; + + /* + * Image processing pipeline: + * 1. Band compensation at 956x688 + * 2. Edge-aware unsharp mask at 956x688 + * 3. Bilinear downsample 956x688 → 300x400 + * 4. Flat-field blend at 300x400 + * 5. Directional sharpening at 300x400 (conditional) + * 6. Bitwise NOT invert + */ + + /* Step 1: BLC band compensation */ + if (data->has_blc_offsets) + secugen_blc_compensate (data->raw_frame, SECUGEN_RAW_WIDTH, + SECUGEN_RAW_HEIGHT, data->blc_offsets); + + /* Step 2: Edge-aware unsharp mask */ + secugen_unsharp_mask (data->raw_frame, SECUGEN_RAW_WIDTH, SECUGEN_RAW_HEIGHT); + + /* Step 3: Bilinear downsample to 300x400 */ + secugen_resize_bilinear (data->raw_frame, + SECUGEN_RAW_WIDTH, SECUGEN_RAW_HEIGHT, + image, + SECUGEN_IMG_WIDTH, SECUGEN_IMG_HEIGHT); + + if (g_task_return_error_if_cancelled (task)) + return; + + /* Step 4: Blend flat-field correction */ + if (data->has_ref_image) + { + secugen_blend (image, + SECUGEN_IMG_WIDTH, SECUGEN_IMG_HEIGHT, + data->ref_image, + data->cal_value, + SECUGEN_BLEND_CURVE); + } + + if (g_task_return_error_if_cancelled (task)) + return; + + /* Step 5: Directional sharpening (conditional) */ + if (data->sharpen_enabled) + { + g_autofree guint8 *sharpened = g_malloc (SECUGEN_IMG_SIZE); + + secugen_sharpen (image, sharpened, + SECUGEN_IMG_WIDTH, SECUGEN_IMG_HEIGHT, + data->sharpen_threshold, + data->sharpen_amount, + data->sharpen_limit); + memcpy (image, sharpened, SECUGEN_IMG_SIZE); + } + + /* Step 6: Invert (bitwise NOT) */ + for (int i = 0; i < SECUGEN_IMG_SIZE; i++) + image[i] = ~image[i]; + + if (g_task_return_error_if_cancelled (task)) + return; + + g_task_return_pointer (task, g_steal_pointer (&image), g_free); +} + +static void +secugen_detect_analyze_thread (GTask *task, + gpointer source_object G_GNUC_UNUSED, + gpointer task_data, + GCancellable *cancellable) +{ + SecugenDetectTaskData *data = task_data; + + g_autoptr(SecugenDetectTaskResult) result = g_new0 (SecugenDetectTaskResult, 1); + guint64 sum = 0; + int count = 0; + int start_row = SECUGEN_RAW_HEIGHT / 4; + int end_row = 3 * SECUGEN_RAW_HEIGHT / 4; + int start_col = SECUGEN_RAW_WIDTH / 4; + int end_col = 3 * SECUGEN_RAW_WIDTH / 4; + + if (g_task_return_error_if_cancelled (task)) + return; + + /* Compute mean brightness of central 50% region */ + for (int y = start_row; y < end_row; y++) + { + for (int x = start_col; x < end_col; x++) + { + int idx = y * SECUGEN_RAW_WIDTH + x; + int val = (int) data->frame[idx] - (int) data->cal_raw[idx]; + + if (val < 0) + val = 0; + sum += val; + count++; + } + + if (g_task_return_error_if_cancelled (task)) + return; + } + + result->mean = count > 0 ? (guint) (sum / count) : 0; + result->finger_present = (result->mean >= SECUGEN_FINGER_THRESHOLD); + result->update_calibration = (!result->finger_present && result->mean <= 5); + if (result->update_calibration) + result->new_cal_raw = g_memdup2 (data->frame, SECUGEN_BULK_BUF_SIZE); + + g_task_return_pointer (task, + g_steal_pointer (&result), + (GDestroyNotify) secugen_detect_task_result_free); +} + /* ================================================================ * Capture State Machine * @@ -1470,6 +1648,47 @@ enum capture_states { CAPTURE_NUM_STATES, }; +static void +capture_process_done (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(FpImage) img = NULL; + g_autoptr(GError) error = NULL; + g_autofree guint8 *processed = NULL; + FpDevice *dev = FP_DEVICE (source_object); + FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); + FpiSsm *ssm = user_data; + + processed = g_task_propagate_pointer (G_TASK (result), &error); + if (error) + { + if (self->deactivating && + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + fpi_ssm_mark_completed (ssm); + return; + } + + fpi_ssm_mark_failed (ssm, g_steal_pointer (&error)); + return; + } + + if (self->deactivating) + { + fpi_ssm_mark_completed (ssm); + return; + } + + img = fp_image_new (SECUGEN_IMG_WIDTH, SECUGEN_IMG_HEIGHT); + img->ppmm = SECUGEN_PPMM; + img->flags = FPI_IMAGE_V_FLIPPED | FPI_IMAGE_H_FLIPPED; + memcpy (img->data, processed, SECUGEN_IMG_SIZE); + + fpi_image_device_image_captured (FP_IMAGE_DEVICE (dev), g_steal_pointer (&img)); + fpi_ssm_mark_completed (ssm); +} + static void capture_status_cb (FpiUsbTransfer *transfer, FpDevice *dev, @@ -1533,75 +1752,37 @@ capture_run_state (FpiSsm *ssm, FpDevice *_dev) case CAPTURE_DONE: { - g_autoptr(FpImage) img = NULL; - g_autofree guint8 *raw = NULL; - int i; + g_autoptr(GTask) task = NULL; + g_autoptr(SecugenCaptureTaskData) task_data = NULL; - img = fp_image_new (SECUGEN_IMG_WIDTH, SECUGEN_IMG_HEIGHT); - img->ppmm = SECUGEN_PPMM; - img->flags = FPI_IMAGE_V_FLIPPED | FPI_IMAGE_H_FLIPPED; + /* Snapshot frame + calibration parameters for worker-thread processing. + * The shared driver buffers remain owned by the main thread. */ + task_data = g_new0 (SecugenCaptureTaskData, 1); + task_data->raw_frame = g_memdup2 (self->bulk_buffer, SECUGEN_RAW_SIZE); + task_data->has_blc_offsets = self->has_blc_offsets; + memcpy (task_data->blc_offsets, self->blc_offsets, sizeof (task_data->blc_offsets)); + task_data->has_ref_image = self->has_ref_image; - /* Work on a copy of the raw 956x688 sensor data */ - raw = g_malloc (SECUGEN_RAW_SIZE); - memcpy (raw, self->bulk_buffer, SECUGEN_RAW_SIZE); - - /* - * Image processing pipeline: - * 1. Band compensation at 956x688 - * 2. Edge-aware unsharp mask at 956x688 - * 3. Bilinear downsample 956x688 → 300x400 - * 4. Flat-field blend at 300x400 - * 5. Directional sharpening at 300x400 (conditional) - * 6. Bitwise NOT invert - */ - - /* Step 1: BLC band compensation */ - if (self->has_blc_offsets) - { - secugen_blc_compensate (raw, SECUGEN_RAW_WIDTH, - SECUGEN_RAW_HEIGHT, - self->blc_offsets); - } - - /* Step 2: Edge-aware unsharp mask */ - secugen_unsharp_mask (raw, SECUGEN_RAW_WIDTH, SECUGEN_RAW_HEIGHT); - - /* Step 3: Bilinear downsample to 300x400 */ - secugen_resize_bilinear (raw, - SECUGEN_RAW_WIDTH, SECUGEN_RAW_HEIGHT, - img->data, - SECUGEN_IMG_WIDTH, SECUGEN_IMG_HEIGHT); - - /* Step 4: Blend flat-field correction */ if (self->has_ref_image) - { - secugen_blend (img->data, - SECUGEN_IMG_WIDTH, SECUGEN_IMG_HEIGHT, - self->ref_image, self->cal_value, - g_blend_curve); - } + task_data->ref_image = g_memdup2 (self->ref_image, SECUGEN_IMG_SIZE); - /* Step 5: Directional sharpening (conditional) */ - if (self->sharpen_enabled) - { - g_autofree guint8 *sharpened = g_malloc (SECUGEN_IMG_SIZE); + task_data->cal_value = self->cal_value; + task_data->sharpen_enabled = self->sharpen_enabled; + task_data->sharpen_threshold = self->sharpen_threshold; + task_data->sharpen_amount = self->sharpen_amount; + task_data->sharpen_limit = self->sharpen_limit; - secugen_sharpen (img->data, sharpened, - SECUGEN_IMG_WIDTH, SECUGEN_IMG_HEIGHT, - self->sharpen_threshold, - self->sharpen_amount, - self->sharpen_limit); - memcpy (img->data, sharpened, SECUGEN_IMG_SIZE); - } - - /* Step 6: Invert (bitwise NOT) */ - for (i = 0; i < SECUGEN_IMG_SIZE; i++) - img->data[i] = ~img->data[i]; - - fpi_image_device_image_captured (dev, g_steal_pointer (&img)); - fpi_ssm_mark_completed (ssm); + task = g_task_new (FP_DEVICE (dev), + fpi_device_get_cancellable (FP_DEVICE (dev)), + capture_process_done, + ssm); + g_task_set_source_tag (task, secugen_capture_process_thread); + g_task_set_check_cancellable (task, TRUE); + g_task_set_task_data (task, g_steal_pointer (&task_data), + (GDestroyNotify) secugen_capture_task_data_free); + g_task_run_in_thread (task, secugen_capture_process_thread); + break; } - break; } } @@ -1671,6 +1852,64 @@ enum detect_states { static void detect_start (FpImageDevice *dev); static void detect_retry_timeout (FpDevice *dev, gpointer user_data G_GNUC_UNUSED); +static void detect_analyze_done (GObject *source_object, + GAsyncResult *result, + gpointer user_data); + +static void +detect_analyze_done (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + FpDevice *dev = FP_DEVICE (source_object); + FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); + FpiSsm *ssm = user_data; + + g_autoptr(GError) error = NULL; + g_autoptr(SecugenDetectTaskResult) analysis = NULL; + + analysis = g_task_propagate_pointer (G_TASK (result), &error); + if (error) + { + if (self->deactivating && + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + fpi_ssm_mark_completed (ssm); + return; + } + fpi_ssm_mark_failed (ssm, g_steal_pointer (&error)); + return; + } + + if (self->deactivating) + { + fpi_ssm_mark_completed (ssm); + return; + } + + fp_dbg ("Finger detect: mean brightness = %u (threshold %d)", + analysis->mean, SECUGEN_FINGER_THRESHOLD); + + if (analysis->finger_present) + { + fp_dbg ("Finger detected!"); + fpi_image_device_report_finger_status (FP_IMAGE_DEVICE (dev), TRUE); + } + else + { + if (analysis->update_calibration && analysis->new_cal_raw) + memcpy (self->cal_raw, analysis->new_cal_raw, SECUGEN_BULK_BUF_SIZE); + + self->finger_poll_source = + fpi_device_add_timeout (FP_DEVICE (dev), + SECUGEN_FINGER_POLL_MS, + detect_retry_timeout, + NULL, + NULL); + } + + fpi_ssm_mark_completed (ssm); +} static void detect_run_state (FpiSsm *ssm, FpDevice *_dev) @@ -1706,15 +1945,8 @@ detect_run_state (FpiSsm *ssm, FpDevice *_dev) case DETECT_ANALYZE: { - guint64 sum = 0; - /* Use central 50% of the 956x688 raw sensor area */ - int start_row = SECUGEN_RAW_HEIGHT / 4; - int end_row = 3 * SECUGEN_RAW_HEIGHT / 4; - int start_col = SECUGEN_RAW_WIDTH / 4; - int end_col = 3 * SECUGEN_RAW_WIDTH / 4; - int count = 0; - guint mean; - int y, x; + g_autoptr(GTask) task = NULL; + g_autoptr(SecugenDetectTaskData) task_data = NULL; /* If a deactivate arrived while this detect cycle ran, do not report * finger state or arm another poll - just unwind the SSM so the @@ -1725,54 +1957,20 @@ detect_run_state (FpiSsm *ssm, FpDevice *_dev) break; } - /* Compute mean brightness of central 50% region */ - for (y = start_row; y < end_row; y++) - for (x = start_col; x < end_col; x++) - { - int idx = y * SECUGEN_RAW_WIDTH + x; + /* Snapshot current frame/calibration for worker-thread analysis. */ + task_data = g_new0 (SecugenDetectTaskData, 1); + task_data->frame = g_memdup2 (self->bulk_buffer, SECUGEN_BULK_BUF_SIZE); + task_data->cal_raw = g_memdup2 (self->cal_raw, SECUGEN_BULK_BUF_SIZE); - if (idx < SECUGEN_BULK_BUF_SIZE) - { - int val = (int) self->bulk_buffer[idx] - - (int) self->cal_raw[idx]; - - if (val < 0) - val = 0; - sum += val; - count++; - } - } - - mean = count > 0 ? (guint) (sum / count) : 0; - fp_dbg ("Finger detect: mean brightness = %u (threshold %d)", - mean, SECUGEN_FINGER_THRESHOLD); - - if (mean >= SECUGEN_FINGER_THRESHOLD) - { - fp_dbg ("Finger detected!"); - fpi_image_device_report_finger_status (dev, TRUE); - } - else - { - /* - * No finger - update calibration reference from this frame - * only if truly empty (mean < 5). This avoids contaminating - * the reference with partial finger touches. - */ - if (mean <= 5) - memcpy (self->cal_raw, self->bulk_buffer, - SECUGEN_BULK_BUF_SIZE); - - /* Schedule another poll */ - self->finger_poll_source = - fpi_device_add_timeout (FP_DEVICE (dev), - SECUGEN_FINGER_POLL_MS, - detect_retry_timeout, - NULL, - NULL); - } - - fpi_ssm_mark_completed (ssm); + task = g_task_new (FP_DEVICE (dev), + fpi_device_get_cancellable (FP_DEVICE (dev)), + detect_analyze_done, + ssm); + g_task_set_source_tag (task, secugen_detect_analyze_thread); + g_task_set_check_cancellable (task, TRUE); + g_task_set_task_data (task, g_steal_pointer (&task_data), + (GDestroyNotify) secugen_detect_task_data_free); + g_task_run_in_thread (task, secugen_detect_analyze_thread); } break; }