summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--gui_app_window.c47
-rw-r--r--image.c202
-rw-r--r--image.h20
-rw-r--r--window.ui44
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 &&
@@ -278,6 +291,26 @@ gain_adj_value_changed_cb(GtkAdjustment *adj, gpointer user_data)
}
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)
{
GuiAppWindow *win =
@@ -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;
@@ -386,6 +421,10 @@ gui_app_window_class_init(GuiAppWindowClass *class)
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),
GuiAppWindow, save_button);
@@ -404,6 +443,10 @@ gui_app_window_class_init(GuiAppWindowClass *class)
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),
save_button_clicked_cb);
@@ -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)
{
@@ -71,6 +74,205 @@ image_unref(struct image *image)
}
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
@@ -127,6 +127,46 @@
</packing>
</child>
<child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkCheckButton" id="wb_button">
+ <property name="label" translatable="yes">White Balance</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="wb_button_toggled_cb" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="wb_cal_button">
+ <property name="label" translatable="yes">Cal.</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="wb_cal_button_clicked_cb" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ <child>
<object class="GtkCheckButton" id="rotate_button">
<property name="label" translatable="yes">Rotate</property>
<property name="visible">True</property>
@@ -138,7 +178,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
- <property name="position">6</property>
+ <property name="position">7</property>
</packing>
</child>
<child>
@@ -153,7 +193,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
- <property name="position">7</property>
+ <property name="position">8</property>
</packing>
</child>
</object>