From 9c081c7ddef2571e608ac194a8b150fdec450f0c Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Sun, 17 Nov 2019 00:58:31 +0100 Subject: Add white balance correction --- Makefile | 4 +- gui_app_window.c | 47 +++++++++++++ image.c | 202 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ image.h | 20 ++++++ window.ui | 44 +++++++++++- 5 files changed, 314 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 6329a94..49edf25 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ libs := libusb-1.0 libpng16 gtk+-3.0 -CFLAGS := -O3 -g -Wall $(shell pkg-config $(libs) --cflags) +CFLAGS := -O3 -g -Wall \ + -flax-vector-conversions \ + $(shell pkg-config $(libs) --cflags) CPPFLAGS := -MMD LDLIBS := $(shell pkg-config $(libs) --libs) GLIB_COMPILE_RESOURCES = \ diff --git a/gui_app_window.c b/gui_app_window.c index 56a42f6..7c2bedd 100644 --- a/gui_app_window.c +++ b/gui_app_window.c @@ -44,6 +44,8 @@ struct _GuiAppWindowPrivate { GtkComboBoxText *resolution_combo_box; GtkAdjustment *exposure_adj; GtkAdjustment *gain_adj; + GtkCheckButton *wb_button; + GtkButton *wb_cal_button; GtkCheckButton *rotate_button; GtkButton *save_button; GdkRectangle allocation; @@ -51,6 +53,9 @@ struct _GuiAppWindowPrivate { cairo_surface_t *surface; struct image *image; struct image *image_converted; + bool white_balance; + bool white_balance_calibrated; + struct image_white_balance_reference white_balance_reference; bool rotate; int width; int height; @@ -113,6 +118,14 @@ video_ready_cb(GuiApp *app, gpointer user_data) image_unref(priv->image); priv->image = NULL; } + /* White balance. */ + if (priv->white_balance && !priv->white_balance_calibrated) { + image_white_balance_calibrate(image, + &priv->white_balance_reference); + priv->white_balance_calibrated = true; + } + if (priv->white_balance) + image_white_balance(image, &priv->white_balance_reference); /* Different format? Convert. */ if (image->format != IMAGE_FORMAT_XBGR32) { if (priv->image_converted && @@ -277,6 +290,26 @@ gain_adj_value_changed_cb(GtkAdjustment *adj, gpointer user_data) device_set_gain(priv->device, val); } +static void +wb_button_toggled_cb(GtkToggleButton *button, gpointer user_data) +{ + GuiAppWindow *win = + GUI_APP_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button))); + GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); + priv->white_balance = gtk_toggle_button_get_active(button); + gtk_widget_set_sensitive(GTK_WIDGET(priv->wb_cal_button), + priv->white_balance); +} + +static void +wb_cal_button_clicked_cb(GtkButton *button, gpointer user_data) +{ + GuiAppWindow *win = + GUI_APP_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button))); + GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); + priv->white_balance_calibrated = false; +} + static void rotate_button_toggled_cb(GtkToggleButton *button, gpointer user_data) { @@ -362,6 +395,8 @@ gui_app_window_init(GuiAppWindow *win) priv->surface = NULL; priv->image = NULL; priv->image_converted = NULL; + priv->white_balance = false; + priv->white_balance_calibrated = false; priv->rotate = true; priv->width = -1; priv->height = -1; @@ -385,6 +420,10 @@ gui_app_window_class_init(GuiAppWindowClass *class) GuiAppWindow, exposure_adj); gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), GuiAppWindow, gain_adj); + gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), + GuiAppWindow, wb_button); + gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), + GuiAppWindow, wb_cal_button); gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), GuiAppWindow, rotate_button); gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), @@ -403,6 +442,10 @@ gui_app_window_class_init(GuiAppWindowClass *class) exposure_adj_value_changed_cb); gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), gain_adj_value_changed_cb); + gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), + wb_button_toggled_cb); + gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), + wb_cal_button_clicked_cb); gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), rotate_button_toggled_cb); gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), @@ -431,6 +474,10 @@ gui_app_window_open(GuiAppWindow *win, struct device *device) gtk_adjustment_set_lower(priv->gain_adj, info->gain_min); gtk_adjustment_set_upper(priv->gain_adj, info->gain_max); gtk_adjustment_set_value(priv->gain_adj, info->gain_default); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->wb_button), + priv->white_balance); + gtk_widget_set_sensitive(GTK_WIDGET(priv->wb_cal_button), + priv->white_balance); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->rotate_button), priv->rotate); gtk_combo_box_text_remove_all(priv->resolution_combo_box); diff --git a/image.c b/image.c index 9ab12c9..ac5387a 100644 --- a/image.c +++ b/image.c @@ -28,6 +28,9 @@ #include "image.h" #include "utils.h" +typedef uint8_t u8x16 __attribute__((vector_size(16))); +typedef uint16_t u16x8 __attribute__((vector_size(16))); + static void image_free(struct image *image, void *user_data) { @@ -70,6 +73,205 @@ image_unref(struct image *image) image->release(image, image->release_user_data); } +static void +image_histogram_sgrbg8(const struct image *image, uint32_t histogram[3][256]) +{ + assert(image->width % 2 == 0); + assert(image->height % 2 == 0); + assert(image->stride == image->width); + memset(histogram, 0, 3 * 256 * sizeof(uint32_t)); + const uint8_t *p = image->pixels; + for (int i = 0; i < image->height; i += 2) { + for (int j = 0; j < image->width; j += 2) { + uint8_t g = *p++; + histogram[1][g] += 2; + uint8_t r = *p++; + histogram[0][r] += 4; + } + for (int j = 0; j < image->width; j += 2) { + uint8_t b = *p++; + histogram[2][b] += 4; + uint8_t g = *p++; + histogram[1][g] += 2; + } + } +} + +static void +image_histogram_xrgb32(const struct image *image, uint32_t histogram[3][256]) +{ + assert(image->stride == image->width * 4); + memset(histogram, 0, 3 * 256 * sizeof(uint32_t)); + const uint8_t *p = image->pixels; + const uint8_t *pend = image->pixels + image->height * image->stride; + while (p != pend) { + uint8_t b = *p++; + uint8_t g = *p++; + uint8_t r = *p++; + p++; + histogram[0][r]++; + histogram[1][g]++; + histogram[2][b]++; + } +} + +void +image_histogram(const struct image *image, uint32_t histogram[3][256]) +{ + switch (image->format) { + case IMAGE_FORMAT_SGRBG8: + image_histogram_sgrbg8(image, histogram); + break; + case IMAGE_FORMAT_XBGR32: + image_histogram_xrgb32(image, histogram); + break; + default: + assert(0); + } +} + +void +image_white_balance_calibrate(const struct image *image, + struct image_white_balance_reference *reference) +{ + uint32_t histogram[3][256]; + image_histogram(image, histogram); + const uint32_t top_ignore = image->width * image->height / 1000; + uint8_t top[3]; + for (int i = 0; i < 3; i++) { + uint32_t acc = 0; + uint8_t j; + for (j = 255; acc < top_ignore && j > 0; j--) { + acc += histogram[i][j]; + } + top[i] = j; + } + reference->r = top[0]; + reference->g = top[1]; + reference->b = top[2]; +} + +static void +image_white_balance_factors( + const struct image_white_balance_reference *reference, + uint16_t *fr, uint16_t *fg, uint16_t *fb) +{ + uint8_t wr = reference->r; + uint8_t wg = reference->g; + uint8_t wb = reference->b; + if (wr > wg && wr > wb) { + *fr = 256; + *fg = 256 * wr / wg; + *fb = 256 * wr / wb; + } else if (wg > wr && wg > wb) { + *fr = 256 * wg / wr; + *fg = 256; + *fb = 256 * wg / wb; + } else { + *fr = 256 * wb / wr; + *fg = 256 * wb / wg; + *fb = 256; + } +} + +static void +image_white_balance_sgrbg8(struct image *image, + const struct image_white_balance_reference *reference) +{ + assert(image->width % 16 == 0); + assert(image->height % 2 == 0); + assert(image->stride == image->width); + uint16_t fr, fg, fb; + image_white_balance_factors(reference, &fr, &fg, &fb); + u8x16 *p = (u8x16 *) image->pixels; + const u8x16 zero = { 0 }; + const u8x16 ex0 = { 0, 16, 1, 17, 2, 18, 3, 19, + 4, 20, 5, 21, 6, 22, 7, 23 }; + const u8x16 ex1 = { 8, 24, 9, 25, 10, 26, 11, 27, + 12, 28, 13, 29, 14, 30, 15, 31 }; + const u16x8 mulo = { fg, fr, fg, fr, fg, fr, fg, fr }; + const u16x8 mule = { fb, fg, fb, fg, fb, fg, fb, fg }; + const u8x16 imp = { 0, 2, 4, 6, 8, 10, 12, 14, + 16, 18, 20, 22, 24, 26, 28, 30 }; + for (int i = 0; i < image->height; i += 2) { + for (int j = 0; j < image->width; j += 16) { + u8x16 pix4 = *p; + u16x8 pix2_0 = (u16x8) __builtin_shuffle(pix4, zero, ex0); + u16x8 pix2_0_ovf = __builtin_ia32_pmulhuw128(pix2_0, mulo) + != (u16x8) zero; + pix2_0 = ((pix2_0 * mulo) | pix2_0_ovf) / 256; + u16x8 pix2_1 = (u16x8) __builtin_shuffle(pix4, zero, ex1); + u16x8 pix2_1_ovf = __builtin_ia32_pmulhuw128(pix2_1, mulo) + != (u16x8) zero; + pix2_1 = ((pix2_1 * mulo) | pix2_1_ovf) / 256; + pix4 = __builtin_shuffle((u8x16) pix2_0, (u8x16) pix2_1, imp); + *p++ = pix4; + } + for (int j = 0; j < image->width; j += 16) { + u8x16 pix4 = *p; + u16x8 pix2_0 = (u16x8) __builtin_shuffle(pix4, zero, ex0); + u16x8 pix2_0_ovf = __builtin_ia32_pmulhuw128(pix2_0, mule) + != (u16x8) zero; + pix2_0 = ((pix2_0 * mule) | pix2_0_ovf) / 256; + u16x8 pix2_1 = (u16x8) __builtin_shuffle(pix4, zero, ex1); + u16x8 pix2_1_ovf = __builtin_ia32_pmulhuw128(pix2_1, mule) + != (u16x8) zero; + pix2_1 = ((pix2_1 * mule) | pix2_1_ovf) / 256; + pix4 = __builtin_shuffle((u8x16) pix2_0, (u8x16) pix2_1, imp); + *p++ = pix4; + } + } +} + +static void +image_white_balance_xbgr32(struct image *image, + const struct image_white_balance_reference *reference) +{ + assert(image->width % 4 == 0); + assert(image->stride == image->width * 4); + uint16_t fr, fg, fb; + image_white_balance_factors(reference, &fr, &fg, &fb); + u8x16 *p = (u8x16 *) image->pixels; + u8x16 *pend = (u8x16 *) (image->pixels + image->height * image->stride); + const u8x16 zero = { 0 }; + const u8x16 ex0 = { 0, 16, 1, 17, 2, 18, 3, 19, + 4, 20, 5, 21, 6, 22, 7, 23 }; + const u8x16 ex1 = { 8, 24, 9, 25, 10, 26, 11, 27, + 12, 28, 13, 29, 14, 30, 15, 31 }; + const u16x8 mul = { fb, fg, fr, 255, fb, fg, fr, 255 }; + const u8x16 imp = { 0, 2, 4, 6, 8, 10, 12, 14, + 16, 18, 20, 22, 24, 26, 28, 30 }; + while (p != pend) { + u8x16 pix4 = *p; + u16x8 pix2_0 = (u16x8) __builtin_shuffle(pix4, zero, ex0); + u16x8 pix2_0_ovf = __builtin_ia32_pmulhuw128(pix2_0, mul) + != (u16x8) zero; + pix2_0 = ((pix2_0 * mul) | pix2_0_ovf) / 256; + u16x8 pix2_1 = (u16x8) __builtin_shuffle(pix4, zero, ex1); + u16x8 pix2_1_ovf = __builtin_ia32_pmulhuw128(pix2_1, mul) + != (u16x8) zero; + pix2_1 = ((pix2_1 * mul) | pix2_1_ovf) / 256; + pix4 = __builtin_shuffle((u8x16) pix2_0, (u8x16) pix2_1, imp); + *p++ = pix4; + } +} + +void +image_white_balance(struct image *image, + const struct image_white_balance_reference *reference) +{ + switch (image->format) { + case IMAGE_FORMAT_SGRBG8: + image_white_balance_sgrbg8(image, reference); + break; + case IMAGE_FORMAT_XBGR32: + image_white_balance_xbgr32(image, reference); + break; + default: + assert(0); + } +} + static void image_convert_to_xbgr32_from_sgrbg8(struct image *dst, const struct image *src) diff --git a/image.h b/image.h index ae33ca7..d854fcc 100644 --- a/image.h +++ b/image.h @@ -63,6 +63,12 @@ struct image { void *release_user_data; }; +/* Reference for white balance. */ +struct image_white_balance_reference { + /* White (maximum of each component). */ + uint8_t r, g, b; +}; + /* Allocate an image using system allocator. */ struct image * image_new(int width, int height, int stride, enum image_format format); @@ -75,6 +81,20 @@ image_ref(struct image *image); void image_unref(struct image *image); +/* Compute histogram(s), should provide an uint32_t[3][256] array. */ +void +image_histogram(const struct image *image, uint32_t histogram[3][256]); + +/* Calibrate for white balance. */ +void +image_white_balance_calibrate(const struct image *image, + struct image_white_balance_reference *reference); + +/* Apply the given white balance. */ +void +image_white_balance(struct image *image, + const struct image_white_balance_reference *reference); + /* Copy an image to another one, converting format. */ void image_convert(struct image *dst, const struct image *src); diff --git a/window.ui b/window.ui index 767ef34..9eefa36 100644 --- a/window.ui +++ b/window.ui @@ -126,6 +126,46 @@ 5 + + + True + False + + + White Balance + True + True + False + True + + + + False + True + 0 + + + + + Cal. + True + True + True + + + + False + True + 1 + + + + + False + True + 6 + + Rotate @@ -138,7 +178,7 @@ False True - 6 + 7 @@ -153,7 +193,7 @@ False True - 7 + 8 -- cgit v1.2.3