فهرست منبع

Add bar code scanning using zbar

Fixes #41
Benjamin Schaaf 4 سال پیش
والد
کامیت
a9d01640bd
6فایلهای تغییر یافته به همراه385 افزوده شده و 11 حذف شده
  1. 167 9
      main.c
  2. 3 0
      main.h
  3. 16 1
      meson.build
  4. 8 1
      process_pipeline.c
  5. 169 0
      zbar_pipeline.c
  6. 22 0
      zbar_pipeline.h

+ 167 - 9
main.c

@@ -17,6 +17,7 @@
 #include <wordexp.h>
 #include <gtk/gtk.h>
 #include <locale.h>
+#include <zbar.h>
 #include "camera_config.h"
 #include "quickpreview.h"
 #include "io_pipeline.h"
@@ -44,6 +45,8 @@ static cairo_surface_t *surface = NULL;
 static cairo_surface_t *status_surface = NULL;
 static char last_path[260] = "";
 
+static MPZBarScanResult *zbar_result = NULL;
+
 static int burst_length = 3;
 
 static enum user_control current_control;
@@ -130,6 +133,28 @@ mp_main_update_state(const struct mp_main_state *state)
 				   (GSourceFunc)update_state, state_copy, free);
 }
 
+static bool set_zbar_result(MPZBarScanResult *result)
+{
+	if (zbar_result) {
+		for (uint8_t i = 0; i < zbar_result->size; ++i) {
+			free(zbar_result->codes[i].data);
+		}
+
+		free(zbar_result);
+	}
+
+	zbar_result = result;
+	gtk_widget_queue_draw(preview);
+
+	return false;
+}
+
+void mp_main_set_zbar_result(MPZBarScanResult *result)
+{
+	g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
+				   (GSourceFunc)set_zbar_result, result, NULL);
+}
+
 static bool
 set_preview(cairo_surface_t *image)
 {
@@ -148,20 +173,26 @@ mp_main_set_preview(cairo_surface_t *image)
 				   (GSourceFunc)set_preview, image, NULL);
 }
 
+static void transform_centered(cairo_t *cr, uint32_t dst_width, uint32_t dst_height,
+	                       int src_width, int src_height)
+{
+	cairo_translate(cr, dst_width / 2, dst_height / 2);
+
+	double scale = MIN(dst_width / (double)src_width, dst_height / (double)src_height);
+	cairo_scale(cr, scale, scale);
+
+	cairo_translate(cr, -src_width / 2, -src_height / 2);
+}
+
 void
 draw_surface_scaled_centered(cairo_t *cr, uint32_t dst_width, uint32_t dst_height,
 			     cairo_surface_t *surface)
 {
 	cairo_save(cr);
 
-	cairo_translate(cr, dst_width / 2, dst_height / 2);
-
 	int width = cairo_image_surface_get_width(surface);
 	int height = cairo_image_surface_get_height(surface);
-	double scale = MIN(dst_width / (double)width, dst_height / (double)height);
-	cairo_scale(cr, scale, scale);
-
-	cairo_translate(cr, -width / 2, -height / 2);
+	transform_centered(cr, dst_width, dst_height, width, height);
 
 	cairo_set_source_surface(cr, surface, 0, 0);
 	cairo_paint(cr);
@@ -296,10 +327,39 @@ preview_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
 	// Clear preview area with black
 	cairo_paint(cr);
 
-	// Draw camera preview
 	if (surface) {
-		draw_surface_scaled_centered(cr, preview_width, preview_height,
-					     surface);
+		// Draw camera preview
+		cairo_save(cr);
+
+		int width = cairo_image_surface_get_width(surface);
+		int height = cairo_image_surface_get_height(surface);
+		transform_centered(cr, preview_width, preview_height, width, height);
+
+		cairo_set_source_surface(cr, surface, 0, 0);
+		cairo_paint(cr);
+
+		// Draw zbar image
+		if (zbar_result) {
+			for (uint8_t i = 0; i < zbar_result->size; ++i) {
+				MPZBarCode *code = &zbar_result->codes[i];
+
+				cairo_set_source_rgba(cr, 1, 1, 1, 0.5);
+				cairo_new_path(cr);
+				cairo_move_to(cr, code->bounds_x[0], code->bounds_y[0]);
+				for (uint8_t i = 0; i < 4; ++i) {
+					cairo_line_to(cr, code->bounds_x[i], code->bounds_y[i]);
+				}
+				cairo_close_path(cr);
+				cairo_stroke(cr);
+
+				cairo_save(cr);
+				cairo_translate(cr, code->bounds_x[0], code->bounds_y[0]);
+				cairo_show_text(cr, code->data);
+				cairo_restore(cr);
+			}
+		}
+
+		cairo_restore(cr);
 	}
 
 	// Draw control overlay
@@ -360,6 +420,85 @@ on_shutter_clicked(GtkWidget *widget, gpointer user_data)
 	mp_io_pipeline_capture();
 }
 
+static bool
+check_point_inside_bounds(int x, int y, int *bounds_x, int *bounds_y)
+{
+	bool right = false, left = false, top = false, bottom = false;
+
+	for (int i = 0; i < 4; ++i) {
+		if (x <= bounds_x[i])
+			left = true;
+		if (x >= bounds_x[i])
+			right = true;
+		if (y <= bounds_y[i])
+			top = true;
+		if (y >= bounds_y[i])
+			bottom = true;
+	}
+
+	return right && left && top && bottom;
+}
+
+static void
+on_zbar_code_tapped(GtkWidget *widget, const MPZBarCode *code)
+{
+	GtkWidget *dialog;
+	GtkDialogFlags flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT;
+	bool data_is_url = strncmp(code->data, "http://", 7) == 0
+			|| strncmp(code->data, "https://", 8) == 0;
+
+	if (data_is_url) {
+		dialog = gtk_message_dialog_new(
+			GTK_WINDOW(gtk_widget_get_toplevel(widget)),
+			flags,
+			GTK_MESSAGE_QUESTION,
+			GTK_BUTTONS_NONE,
+			"Found a URL '%s' encoded in a %s code.",
+			code->data,
+			code->type);
+		gtk_dialog_add_buttons(
+			GTK_DIALOG(dialog),
+			"_Open URL",
+			GTK_RESPONSE_YES,
+			NULL);
+	} else {
+		dialog = gtk_message_dialog_new(
+			GTK_WINDOW(gtk_widget_get_toplevel(widget)),
+			flags,
+			GTK_MESSAGE_QUESTION,
+			GTK_BUTTONS_NONE,
+			"Found '%s' encoded in a %s code.",
+			code->data,
+			code->type);
+	}
+	gtk_dialog_add_buttons(
+		GTK_DIALOG(dialog),
+		"_Copy",
+		GTK_RESPONSE_ACCEPT,
+		"_Cancel",
+		GTK_RESPONSE_CANCEL,
+		NULL);
+
+	int result = gtk_dialog_run(GTK_DIALOG(dialog));
+
+	GError *error = NULL;
+	switch (result) {
+		case GTK_RESPONSE_YES:
+			if (!g_app_info_launch_default_for_uri(code->data,
+							       NULL, &error)) {
+				g_printerr("Could not launch browser: %s\n",
+					   error->message);
+			}
+		case GTK_RESPONSE_ACCEPT:
+			gtk_clipboard_set_text(
+				gtk_clipboard_get(GDK_SELECTION_PRIMARY),
+				code->data, -1);
+		case GTK_RESPONSE_CANCEL:
+			break;
+	}
+	gtk_widget_destroy(dialog);
+}
+
 void
 on_preview_tap(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
 {
@@ -399,6 +538,25 @@ on_preview_tap(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
 		return;
 	}
 
+	// Tapped zbar result
+	if (zbar_result) {
+		// Transform the event coordinates to the image
+		int width = cairo_image_surface_get_width(surface);
+		int height = cairo_image_surface_get_height(surface);
+		double scale = MIN(preview_width / (double)width, preview_height / (double)height);
+		int x = (event->x - preview_width / 2) / scale + width / 2;
+		int y = (event->y - preview_height / 2) / scale + height / 2;
+
+		for (uint8_t i = 0; i < zbar_result->size; ++i) {
+			MPZBarCode *code = &zbar_result->codes[i];
+
+			if (check_point_inside_bounds(x, y, code->bounds_x, code->bounds_y)) {
+				on_zbar_code_tapped(widget, code);
+				return;
+			}
+		}
+	}
+
 	// Tapped preview image itself, try focussing
 	if (has_auto_focus_start) {
 		mp_io_pipeline_focus();

+ 3 - 0
main.h

@@ -1,6 +1,7 @@
 #pragma once
 
 #include "camera_config.h"
+#include "zbar_pipeline.h"
 #include "gtk/gtk.h"
 
 #define MP_MAIN_THUMB_SIZE 24
@@ -25,6 +26,8 @@ void mp_main_update_state(const struct mp_main_state *state);
 void mp_main_set_preview(cairo_surface_t *image);
 void mp_main_capture_completed(cairo_surface_t *thumb, const char *fname);
 
+void mp_main_set_zbar_result(MPZBarScanResult *result);
+
 int remap(int value, int input_min, int input_max, int output_min, int output_max);
 
 void draw_surface_scaled_centered(cairo_t *cr, uint32_t dst_width, uint32_t dst_height,

+ 16 - 1
meson.build

@@ -2,6 +2,7 @@ project('megapixels', 'c')
 gnome = import('gnome')
 gtkdep = dependency('gtk+-3.0')
 tiff = dependency('libtiff-4')
+zbar = dependency('zbar')
 threads = dependency('threads')
 
 cc = meson.get_compiler('c')
@@ -26,7 +27,21 @@ if get_option('tiffcfapattern')
   add_global_arguments('-DLIBTIFF_CFA_PATTERN', language: 'c')
 endif
 
-executable('megapixels', 'main.c', 'ini.c', 'quickpreview.c', 'camera.c', 'device.c', 'pipeline.c', 'camera_config.c', 'io_pipeline.c', 'process_pipeline.c', 'matrix.c', resources, dependencies : [gtkdep, libm, tiff, threads], install : true)
+executable('megapixels',
+           'main.c',
+           'ini.c',
+           'quickpreview.c',
+           'camera.c',
+           'device.c',
+           'pipeline.c',
+           'camera_config.c',
+           'io_pipeline.c',
+           'process_pipeline.c',
+           'zbar_pipeline.c',
+           'matrix.c',
+           resources,
+           dependencies : [gtkdep, libm, tiff, zbar, threads],
+           install : true)
 
 install_data(['data/org.postmarketos.Megapixels.desktop'],
              install_dir : get_option('datadir') / 'applications')

+ 8 - 1
process_pipeline.c

@@ -1,6 +1,7 @@
 #include "process_pipeline.h"
 
 #include "pipeline.h"
+#include "zbar_pipeline.h"
 #include "main.h"
 #include "config.h"
 #include "quickpreview.h"
@@ -120,12 +121,17 @@ mp_process_pipeline_start()
 	pipeline = mp_pipeline_new();
 
 	mp_pipeline_invoke(pipeline, setup, NULL, 0);
+
+
+	mp_zbar_pipeline_start();
 }
 
 void
 mp_process_pipeline_stop()
 {
 	mp_pipeline_free(pipeline);
+
+	mp_zbar_pipeline_stop();
 }
 
 static cairo_surface_t *
@@ -160,7 +166,8 @@ process_image_for_preview(const MPImage *image)
 		cairo_destroy(cr);
 	}
 
-	// Pass processed preview to main
+	// Pass processed preview to main and zbar
+	mp_zbar_pipeline_process_image(cairo_surface_reference(surface));
 	mp_main_set_preview(surface);
 
 	return thumb;

+ 169 - 0
zbar_pipeline.c

@@ -0,0 +1,169 @@
+#include "zbar_pipeline.h"
+
+#include "pipeline.h"
+#include "main.h"
+#include "io_pipeline.h"
+#include <zbar.h>
+#include <assert.h>
+
+static MPPipeline *pipeline;
+
+static volatile int frames_processed = 0;
+static volatile int frames_received = 0;
+
+static zbar_image_scanner_t *scanner;
+
+static void setup(MPPipeline *pipeline, const void *data)
+{
+	scanner = zbar_image_scanner_create();
+	zbar_image_scanner_set_config(scanner, 0, ZBAR_CFG_ENABLE, 1);
+}
+
+void
+mp_zbar_pipeline_start()
+{
+	pipeline = mp_pipeline_new();
+
+	mp_pipeline_invoke(pipeline, setup, NULL, 0);
+}
+
+void
+mp_zbar_pipeline_stop()
+{
+	mp_pipeline_free(pipeline);
+}
+
+static bool is_3d_code(zbar_symbol_type_t type)
+{
+	switch (type) {
+		case ZBAR_EAN2:
+		case ZBAR_EAN5:
+		case ZBAR_EAN8:
+		case ZBAR_UPCE:
+		case ZBAR_ISBN10:
+		case ZBAR_UPCA:
+		case ZBAR_EAN13:
+		case ZBAR_ISBN13:
+		case ZBAR_I25:
+		case ZBAR_DATABAR:
+		case ZBAR_DATABAR_EXP:
+		case ZBAR_CODABAR:
+		case ZBAR_CODE39:
+		case ZBAR_CODE93:
+		case ZBAR_CODE128:
+			return false;
+		case ZBAR_COMPOSITE:
+		case ZBAR_PDF417:
+		case ZBAR_QRCODE:
+		case ZBAR_SQCODE:
+			return true;
+		default:
+			return false;
+	}
+}
+
+static MPZBarCode
+process_symbol(const zbar_symbol_t *symbol)
+{
+	MPZBarCode code;
+
+	unsigned loc_size = zbar_symbol_get_loc_size(symbol);
+	assert(loc_size > 0);
+
+	zbar_symbol_type_t type = zbar_symbol_get_type(symbol);
+
+	if (is_3d_code(type) && loc_size == 4) {
+		for (unsigned i = 0; i < loc_size; ++i) {
+			code.bounds_x[i] = zbar_symbol_get_loc_x(symbol, i);
+			code.bounds_y[i] = zbar_symbol_get_loc_y(symbol, i);
+		}
+	} else {
+		int min_x = zbar_symbol_get_loc_x(symbol, 0);
+		int min_y = zbar_symbol_get_loc_y(symbol, 0);
+		int max_x = min_x, max_y = min_y;
+		for (unsigned i = 1; i < loc_size; ++i) {
+			int x = zbar_symbol_get_loc_x(symbol, i);
+			int y = zbar_symbol_get_loc_y(symbol, i);
+			min_x = MIN(min_x, x);
+			min_y = MIN(min_y, y);
+			max_x = MAX(max_x, x);
+			max_y = MAX(max_y, y);
+		}
+
+		code.bounds_x[0] = min_x;
+		code.bounds_y[0] = min_y;
+		code.bounds_x[1] = max_x;
+		code.bounds_y[1] = min_y;
+		code.bounds_x[2] = max_x;
+		code.bounds_y[2] = max_y;
+		code.bounds_x[3] = min_x;
+		code.bounds_y[3] = max_y;
+	}
+
+	const char *data = zbar_symbol_get_data(symbol);
+	unsigned int data_size = zbar_symbol_get_data_length(symbol);
+	code.data = strndup(data, data_size);
+	code.type = zbar_get_symbol_name(type);
+
+	return code;
+}
+
+static void
+process_surface(MPPipeline *pipeline, cairo_surface_t **_surface)
+{
+	cairo_surface_t *surface = *_surface;
+
+	int width = cairo_image_surface_get_width(surface);
+	int height = cairo_image_surface_get_height(surface);
+	const uint32_t *surface_data = (const uint32_t *)cairo_image_surface_get_data(surface);
+
+	// Create a grayscale image for scanning from the current preview
+	uint8_t *data = malloc(width * height * sizeof(uint8_t));
+	for (size_t i = 0; i < width * height; ++i) {
+		data[i] = (surface_data[i] >> 16) & 0xff;
+	}
+
+	// Create image for zbar
+	zbar_image_t *zbar_image = zbar_image_create();
+	zbar_image_set_format(zbar_image, zbar_fourcc('Y', '8', '0', '0'));
+	zbar_image_set_size(zbar_image, width, height);
+	zbar_image_set_data(zbar_image, data, width * height * sizeof(uint8_t), zbar_image_free_data);
+
+	int res = zbar_scan_image(scanner, zbar_image);
+	assert(res >= 0);
+
+	if (res > 0) {
+		MPZBarScanResult *result = malloc(sizeof(MPZBarScanResult));
+		result->size = res;
+
+		const zbar_symbol_t *symbol = zbar_image_first_symbol(zbar_image);
+		for (int i = 0; i < MIN(res, 8); ++i) {
+			assert(symbol != NULL);
+			result->codes[i] = process_symbol(symbol);
+			symbol = zbar_symbol_next(symbol);
+		}
+
+		mp_main_set_zbar_result(result);
+	} else {
+		mp_main_set_zbar_result(NULL);
+	}
+
+	zbar_image_destroy(zbar_image);
+	cairo_surface_destroy(surface);
+
+	++frames_processed;
+}
+
+void
+mp_zbar_pipeline_process_image(cairo_surface_t *surface)
+{
+	// If we haven't processed the previous frame yet, drop this one
+	if (frames_received != frames_processed) {
+		return;
+	}
+
+	++frames_received;
+
+	mp_pipeline_invoke(pipeline, (MPPipelineCallback)process_surface, &surface,
+			   sizeof(cairo_surface_t *));
+}

+ 22 - 0
zbar_pipeline.h

@@ -0,0 +1,22 @@
+#pragma once
+
+#include "camera_config.h"
+
+typedef struct _cairo_surface cairo_surface_t;
+
+typedef struct {
+	int bounds_x[4];
+	int bounds_y[4];
+	char *data;
+	const char *type;
+} MPZBarCode;
+
+typedef struct {
+	MPZBarCode codes[8];
+	uint8_t size;
+} MPZBarScanResult;
+
+void mp_zbar_pipeline_start();
+void mp_zbar_pipeline_stop();
+
+void mp_zbar_pipeline_process_image(cairo_surface_t *surface);