diff --git a/camera.css b/camera.css
deleted file mode 100644
index e897a85..0000000
--- a/camera.css
+++ /dev/null
@@ -1,12 +0,0 @@
-.black {
- background: #000000;
-}
-
-.errorbox {
- background: #dd0000;
- color: #ffffff;
-}
-
-.controlbox {
- background: rgba(0,0,0,0.2);
-}
diff --git a/camera.glade b/camera.glade
deleted file mode 100644
index 8fb7e67..0000000
--- a/camera.glade
+++ /dev/null
@@ -1,592 +0,0 @@
-
-
-
-
-
-
-
diff --git a/config/pine64,pinephone-1.0.ini b/config/pine64,pinephone-1.0.ini
index cafbb77..901ccc7 100644
--- a/config/pine64,pinephone-1.0.ini
+++ b/config/pine64,pinephone-1.0.ini
@@ -11,7 +11,7 @@ capture-rate=10
capture-fmt=BGGR8
preview-width=1280
preview-height=720
-preview-rate=20
+preview-rate=30
preview-fmt=BGGR8
rotate=270
colormatrix=1.384,-0.3203,-0.0124,-0.2728,1.049,0.1556,-0.0506,0.2577,0.8050
@@ -29,11 +29,11 @@ driver=gc2145
media-driver=sun6i-csi
capture-width=1280
capture-height=960
-capture-rate=30
+capture-rate=60
capture-fmt=BGGR8
preview-width=1280
preview-height=960
-preview-rate=30
+preview-rate=60
preview-fmt=BGGR8
rotate=90
mirrored=true
diff --git a/config/pine64,pinephone-1.1.ini b/config/pine64,pinephone-1.1.ini
index cafbb77..901ccc7 100644
--- a/config/pine64,pinephone-1.1.ini
+++ b/config/pine64,pinephone-1.1.ini
@@ -11,7 +11,7 @@ capture-rate=10
capture-fmt=BGGR8
preview-width=1280
preview-height=720
-preview-rate=20
+preview-rate=30
preview-fmt=BGGR8
rotate=270
colormatrix=1.384,-0.3203,-0.0124,-0.2728,1.049,0.1556,-0.0506,0.2577,0.8050
@@ -29,11 +29,11 @@ driver=gc2145
media-driver=sun6i-csi
capture-width=1280
capture-height=960
-capture-rate=30
+capture-rate=60
capture-fmt=BGGR8
preview-width=1280
preview-height=960
-preview-rate=30
+preview-rate=60
preview-fmt=BGGR8
rotate=90
mirrored=true
diff --git a/config/pine64,pinephone-1.2.ini b/config/pine64,pinephone-1.2.ini
index 74ce9d2..e295f87 100644
--- a/config/pine64,pinephone-1.2.ini
+++ b/config/pine64,pinephone-1.2.ini
@@ -11,7 +11,7 @@ capture-rate=10
capture-fmt=BGGR8
preview-width=1280
preview-height=720
-preview-rate=20
+preview-rate=30
preview-fmt=BGGR8
rotate=270
mirrored=false
@@ -30,11 +30,11 @@ driver=gc2145
media-driver=sun6i-csi
capture-width=1280
capture-height=960
-capture-rate=30
+capture-rate=60
capture-fmt=BGGR8
preview-width=1280
preview-height=960
-preview-rate=30
+preview-rate=60
preview-fmt=BGGR8
rotate=90
mirrored=true
diff --git a/data/blit.frag b/data/blit.frag
new file mode 100644
index 0000000..58df011
--- /dev/null
+++ b/data/blit.frag
@@ -0,0 +1,11 @@
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+uniform sampler2D texture;
+
+varying vec2 uv;
+
+void main() {
+ gl_FragColor = vec4(texture2D(texture, uv).rgb, 1);
+}
diff --git a/data/blit.vert b/data/blit.vert
new file mode 100644
index 0000000..e6e75d4
--- /dev/null
+++ b/data/blit.vert
@@ -0,0 +1,16 @@
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+attribute vec2 vert;
+attribute vec2 tex_coord;
+
+uniform mat3 transform;
+
+varying vec2 uv;
+
+void main() {
+ uv = tex_coord;
+
+ gl_Position = vec4(transform * vec3(vert, 1), 1);
+}
diff --git a/data/camera.css b/data/camera.css
new file mode 100644
index 0000000..85c65e3
--- /dev/null
+++ b/data/camera.css
@@ -0,0 +1,13 @@
+.errorbox {
+ background: #dd0000;
+ color: #ffffff;
+}
+
+.controlbox {
+ background: rgba(0, 0, 0, 0.2);
+}
+
+.button-overlay {
+ opacity: 0.9;
+ background-color: rgba(0, 0, 0, 0.2);
+}
diff --git a/data/camera.ui b/data/camera.ui
new file mode 100644
index 0000000..23dd058
--- /dev/null
+++ b/data/camera.ui
@@ -0,0 +1,250 @@
+
+
+
+
+ 0
+ 360
+ 640
+ 0
+
+
+ 0
+
+
+ main
+
+
+ 0
+
+
+ 1
+ 0
+ 1
+
+
+
+
+ vertical
+ fill
+ start
+ 5
+ 0
+
+
+
+ 1
+ 1
+ start
+ 5
+ 5
+ 5
+ 5
+ 0
+ 5
+
+
+ start
+ ISO
+
+
+
+
+
+ start
+ shutter-symbolic
+
+
+
+
+
+
+
+ 0
+ start
+ 5
+ 5
+ 5
+ 5
+
+
+ horizontal
+ 10
+
+
+ Error
+
+
+
+
+ dialog-warning-symbolic
+
+
+
+
+
+
+
+
+
+
+
+
+ horizontal
+ fill
+ end
+
+
+
+ 1
+ 1
+ center
+ center
+ 5
+ 5
+ 5
+ 5
+ 5
+
+
+ app.open-settings
+ settings-symbolic
+
+
+
+
+ app.switch-camera
+ switch-camera-symbolic
+
+
+
+
+
+
+ 5
+ 5
+ app.capture
+
+
+ 60
+ 0
+ shutter-button-symbolic
+
+
+
+
+
+
+
+
+ 1
+ 1
+ center
+ center
+ 5
+ 5
+ 5
+ 5
+ 5
+
+
+ app.open-last
+
+
+ 0
+
+
+ 24
+ 24
+ 0
+
+
+
+
+ 24
+ 24
+ 0
+
+
+
+
+
+
+
+
+ app.open-photos
+ folder-symbolic
+
+
+
+
+
+
+
+
+
+
+
+
+
+ settings
+ page1
+
+
+
+
+ 0
+
+
+ 0
+ 10
+ 10
+ 10
+ 10
+ vertical
+ 10
+
+
+ 0
+
+
+ Back
+ 10
+ 10
+ app.close-settings
+
+
+
+
+
+ 0
+ Settings aren't functional yet
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/data/controls-popover.ui b/data/controls-popover.ui
new file mode 100644
index 0000000..140ef1a
--- /dev/null
+++ b/data/controls-popover.ui
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+ vertical
+ 1
+ 5
+
+
+
+
+
+
+ horizontal
+ 1
+ 5
+
+
+ horizontal
+ 0
+ 150
+
+
+
+
+
+
+
+
+ center
+ auto
+ 5
+
+
+
+
+
+
+
+
diff --git a/data/debayer.frag b/data/debayer.frag
new file mode 100644
index 0000000..9a98c10
--- /dev/null
+++ b/data/debayer.frag
@@ -0,0 +1,34 @@
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+uniform sampler2D texture;
+uniform mat3 color_matrix;
+
+varying vec2 top_left_uv;
+varying vec2 top_right_uv;
+varying vec2 bottom_left_uv;
+varying vec2 bottom_right_uv;
+
+void main() {
+ // Note the coordinates for texture samples need to be a varying, as the
+ // Mali-400 has this as a fast path allowing 32-bit floats. Otherwise
+ // they end up as 16-bit floats and that's not accurate enough.
+ vec4 samples = vec4(
+ texture2D(texture, top_left_uv).r,
+ texture2D(texture, top_right_uv).r,
+ texture2D(texture, bottom_left_uv).r,
+ texture2D(texture, bottom_right_uv).r);
+
+ // Assume BGGR for now. Currently this just takes 3 of the four samples
+ // for each pixel, there's room here to do some better debayering.
+ vec3 color = vec3(samples.w, (samples.y + samples.w) / 2.0, samples.x);
+
+ // Fast SRGB estimate. See https://mimosa-pudica.net/fast-gamma/
+ vec3 srgb_color = (vec3(1.138) * inversesqrt(color) - vec3(0.138)) * color;
+
+ // Slow SRGB estimate
+ // vec3 srgb_color = pow(color, vec3(1.0 / 2.2));
+
+ gl_FragColor = vec4(color_matrix * srgb_color, 1);
+}
diff --git a/data/debayer.vert b/data/debayer.vert
new file mode 100644
index 0000000..49af8c1
--- /dev/null
+++ b/data/debayer.vert
@@ -0,0 +1,23 @@
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+attribute vec2 vert;
+attribute vec2 tex_coord;
+
+uniform mat3 transform;
+uniform vec2 pixel_size;
+
+varying vec2 top_left_uv;
+varying vec2 top_right_uv;
+varying vec2 bottom_left_uv;
+varying vec2 bottom_right_uv;
+
+void main() {
+ top_left_uv = tex_coord - pixel_size / 2.0;
+ bottom_right_uv = tex_coord + pixel_size / 2.0;
+ top_right_uv = vec2(top_left_uv.x, bottom_right_uv.y);
+ bottom_left_uv = vec2(bottom_right_uv.x, top_left_uv.y);
+
+ gl_Position = vec4(transform * vec3(vert, 1), 1);
+}
diff --git a/folder-symbolic.svg b/data/folder-symbolic.svg
similarity index 100%
rename from folder-symbolic.svg
rename to data/folder-symbolic.svg
diff --git a/data/meson.build b/data/meson.build
new file mode 100644
index 0000000..c0de055
--- /dev/null
+++ b/data/meson.build
@@ -0,0 +1,15 @@
+resources = gnome.compile_resources('megapixels-resources',
+ 'org.postmarketos.Megapixels.gresource.xml')
+
+install_data(['org.postmarketos.Megapixels.desktop'],
+ install_dir: get_option('datadir') / 'applications')
+
+install_data(['org.postmarketos.Megapixels.metainfo.xml'],
+ install_dir: get_option('datadir') / 'metainfo')
+
+install_data('org.postmarketos.Megapixels.svg',
+ install_dir: join_paths(get_option('datadir'), 'icons/hicolor/scalable/apps'))
+
+install_data(['postprocess.sh'],
+ install_dir: get_option('datadir') / 'megapixels/',
+ install_mode: 'rwxr-xr-x')
diff --git a/data/org.postmarketos.Megapixels.gresource.xml b/data/org.postmarketos.Megapixels.gresource.xml
new file mode 100644
index 0000000..0243024
--- /dev/null
+++ b/data/org.postmarketos.Megapixels.gresource.xml
@@ -0,0 +1,19 @@
+
+
+
+ camera.ui
+ controls-popover.ui
+ camera.css
+ switch-camera-symbolic.svg
+ shutter-button-symbolic.svg
+ folder-symbolic.svg
+ settings-symbolic.svg
+ shutter-symbolic.svg
+ blit.vert
+ blit.frag
+ solid.vert
+ solid.frag
+ debayer.vert
+ debayer.frag
+
+
diff --git a/postprocess.sh b/data/postprocess.sh
similarity index 100%
rename from postprocess.sh
rename to data/postprocess.sh
diff --git a/settings-symbolic.svg b/data/settings-symbolic.svg
similarity index 100%
rename from settings-symbolic.svg
rename to data/settings-symbolic.svg
diff --git a/shutter-button.svg b/data/shutter-button-symbolic.svg
similarity index 100%
rename from shutter-button.svg
rename to data/shutter-button-symbolic.svg
diff --git a/data/shutter-symbolic.svg b/data/shutter-symbolic.svg
new file mode 100644
index 0000000..bcfec02
--- /dev/null
+++ b/data/shutter-symbolic.svg
@@ -0,0 +1,32 @@
+
+
diff --git a/data/solid.frag b/data/solid.frag
new file mode 100644
index 0000000..bb167f7
--- /dev/null
+++ b/data/solid.frag
@@ -0,0 +1,9 @@
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+uniform vec4 color;
+
+void main() {
+ gl_FragColor = color;
+}
diff --git a/data/solid.vert b/data/solid.vert
new file mode 100644
index 0000000..f578403
--- /dev/null
+++ b/data/solid.vert
@@ -0,0 +1,9 @@
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+attribute vec2 vert;
+
+void main() {
+ gl_Position = vec4(vert, 0, 1);
+}
diff --git a/switch-camera.svg b/data/switch-camera-symbolic.svg
similarity index 100%
rename from switch-camera.svg
rename to data/switch-camera-symbolic.svg
diff --git a/main.c b/main.c
deleted file mode 100644
index 63471bc..0000000
--- a/main.c
+++ /dev/null
@@ -1,792 +0,0 @@
-#include "main.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include "camera_config.h"
-#include "quickpreview.h"
-#include "io_pipeline.h"
-
-enum user_control { USER_CONTROL_ISO, USER_CONTROL_SHUTTER };
-
-static bool camera_is_initialized = false;
-static const struct mp_camera_config *camera = NULL;
-static MPCameraMode mode;
-
-static int preview_width = -1;
-static int preview_height = -1;
-
-static bool gain_is_manual = false;
-static int gain;
-static int gain_max;
-
-static bool exposure_is_manual = false;
-static int exposure;
-
-static bool has_auto_focus_continuous;
-static bool has_auto_focus_start;
-
-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;
-
-// Widgets
-GtkWidget *preview;
-GtkWidget *error_box;
-GtkWidget *error_message;
-GtkWidget *main_stack;
-GtkWidget *open_last_stack;
-GtkWidget *thumb_last;
-GtkWidget *process_spinner;
-GtkWidget *control_box;
-GtkWidget *control_name;
-GtkAdjustment *control_slider;
-GtkWidget *control_auto;
-
-int
-remap(int value, int input_min, int input_max, int output_min, int output_max)
-{
- const long long factor = 1000000000;
- long long output_spread = output_max - output_min;
- long long input_spread = input_max - input_min;
-
- long long zero_value = value - input_min;
- zero_value *= factor;
- long long percentage = zero_value / input_spread;
-
- long long zero_output = percentage * output_spread / factor;
-
- long long result = output_min + zero_output;
- return (int)result;
-}
-
-static void
-update_io_pipeline()
-{
- struct mp_io_pipeline_state io_state = {
- .camera = camera,
- .burst_length = burst_length,
- .preview_width = preview_width,
- .preview_height = preview_height,
- .gain_is_manual = gain_is_manual,
- .gain = gain,
- .exposure_is_manual = exposure_is_manual,
- .exposure = exposure,
- };
- mp_io_pipeline_update_state(&io_state);
-}
-
-static bool
-update_state(const struct mp_main_state *state)
-{
- if (!camera_is_initialized) {
- camera_is_initialized = true;
- }
-
- if (camera == state->camera) {
- mode = state->mode;
-
- if (!gain_is_manual) {
- gain = state->gain;
- }
- gain_max = state->gain_max;
-
- if (!exposure_is_manual) {
- exposure = state->exposure;
- }
-
- has_auto_focus_continuous = state->has_auto_focus_continuous;
- has_auto_focus_start = state->has_auto_focus_start;
- }
-
- return false;
-}
-
-void
-mp_main_update_state(const struct mp_main_state *state)
-{
- struct mp_main_state *state_copy = malloc(sizeof(struct mp_main_state));
- *state_copy = *state;
-
- g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
- (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)
-{
- if (surface) {
- cairo_surface_destroy(surface);
- }
- surface = image;
- gtk_widget_queue_draw(preview);
- return false;
-}
-
-void
-mp_main_set_preview(cairo_surface_t *image)
-{
- g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
- (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);
-
- int width = cairo_image_surface_get_width(surface);
- int height = cairo_image_surface_get_height(surface);
- transform_centered(cr, dst_width, dst_height, width, height);
-
- cairo_set_source_surface(cr, surface, 0, 0);
- cairo_paint(cr);
- cairo_restore(cr);
-}
-
-struct capture_completed_args {
- cairo_surface_t *thumb;
- char *fname;
-};
-
-static bool
-capture_completed(struct capture_completed_args *args)
-{
- strncpy(last_path, args->fname, 259);
-
- gtk_image_set_from_surface(GTK_IMAGE(thumb_last), args->thumb);
-
- gtk_spinner_stop(GTK_SPINNER(process_spinner));
- gtk_stack_set_visible_child(GTK_STACK(open_last_stack), thumb_last);
-
- cairo_surface_destroy(args->thumb);
- g_free(args->fname);
-
- return false;
-}
-
-void
-mp_main_capture_completed(cairo_surface_t *thumb, const char *fname)
-{
- struct capture_completed_args *args = malloc(sizeof(struct capture_completed_args));
- args->thumb = thumb;
- args->fname = g_strdup(fname);
- g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
- (GSourceFunc)capture_completed, args, free);
-}
-
-static void
-draw_controls()
-{
- cairo_t *cr;
- char iso[6];
- int temp;
- char shutterangle[6];
-
- if (exposure_is_manual) {
- temp = (int)((float)exposure / (float)camera->capture_mode.height *
- 360);
- sprintf(shutterangle, "%d\u00b0", temp);
- } else {
- sprintf(shutterangle, "auto");
- }
-
- if (gain_is_manual) {
- temp = remap(gain - 1, 0, gain_max, camera->iso_min,
- camera->iso_max);
- sprintf(iso, "%d", temp);
- } else {
- sprintf(iso, "auto");
- }
-
- if (status_surface)
- cairo_surface_destroy(status_surface);
-
- // Make a service to show status of controls, 32px high
- if (gtk_widget_get_window(preview) == NULL) {
- return;
- }
- status_surface =
- gdk_window_create_similar_surface(gtk_widget_get_window(preview),
- CAIRO_CONTENT_COLOR_ALPHA,
- preview_width, 32);
-
- cr = cairo_create(status_surface);
- cairo_set_source_rgba(cr, 0, 0, 0, 0.0);
- cairo_paint(cr);
-
- // Draw the outlines for the headings
- cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL,
- CAIRO_FONT_WEIGHT_BOLD);
- cairo_set_font_size(cr, 9);
- cairo_set_source_rgba(cr, 0, 0, 0, 1);
-
- cairo_move_to(cr, 16, 16);
- cairo_text_path(cr, "ISO");
- cairo_stroke(cr);
-
- cairo_move_to(cr, 60, 16);
- cairo_text_path(cr, "Shutter");
- cairo_stroke(cr);
-
- // Draw the fill for the headings
- cairo_set_source_rgba(cr, 1, 1, 1, 1);
- cairo_move_to(cr, 16, 16);
- cairo_show_text(cr, "ISO");
- cairo_move_to(cr, 60, 16);
- cairo_show_text(cr, "Shutter");
-
- // Draw the outlines for the values
- cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL,
- CAIRO_FONT_WEIGHT_NORMAL);
- cairo_set_font_size(cr, 11);
- cairo_set_source_rgba(cr, 0, 0, 0, 1);
-
- cairo_move_to(cr, 16, 26);
- cairo_text_path(cr, iso);
- cairo_stroke(cr);
-
- cairo_move_to(cr, 60, 26);
- cairo_text_path(cr, shutterangle);
- cairo_stroke(cr);
-
- // Draw the fill for the values
- cairo_set_source_rgba(cr, 1, 1, 1, 1);
- cairo_move_to(cr, 16, 26);
- cairo_show_text(cr, iso);
- cairo_move_to(cr, 60, 26);
- cairo_show_text(cr, shutterangle);
-
- cairo_destroy(cr);
-
- gtk_widget_queue_draw_area(preview, 0, 0, preview_width, 32);
-}
-
-static gboolean
-preview_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
-{
- if (!camera_is_initialized) {
- return FALSE;
- }
-
- // Clear preview area with black
- cairo_paint(cr);
-
- if (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_line_width(cr, 3.0);
- cairo_set_source_rgba(cr, 0, 0.5, 1, 0.75);
- 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
- cairo_set_source_surface(cr, status_surface, 0, 0);
- cairo_paint(cr);
- return FALSE;
-}
-
-static gboolean
-preview_configure(GtkWidget *widget, GdkEventConfigure *event)
-{
- int new_preview_width = gtk_widget_get_allocated_width(widget);
- int new_preview_height = gtk_widget_get_allocated_height(widget);
-
- if (preview_width != new_preview_width ||
- preview_height != new_preview_height) {
- preview_width = new_preview_width;
- preview_height = new_preview_height;
- update_io_pipeline();
- }
-
- draw_controls();
-
- return TRUE;
-}
-
-void
-on_open_last_clicked(GtkWidget *widget, gpointer user_data)
-{
- char uri[275];
- GError *error = NULL;
-
- if (strlen(last_path) == 0) {
- return;
- }
- sprintf(uri, "file://%s", last_path);
- if (!g_app_info_launch_default_for_uri(uri, NULL, &error)) {
- g_printerr("Could not launch image viewer for '%s': %s\n", uri, error->message);
- }
-}
-
-void
-on_open_directory_clicked(GtkWidget *widget, gpointer user_data)
-{
- char uri[270];
- GError *error = NULL;
- sprintf(uri, "file://%s", g_get_user_special_dir(G_USER_DIRECTORY_PICTURES));
- if (!g_app_info_launch_default_for_uri(uri, NULL, &error)) {
- g_printerr("Could not launch image viewer: %s\n", error->message);
- }
-}
-
-void
-on_shutter_clicked(GtkWidget *widget, gpointer user_data)
-{
- gtk_spinner_start(GTK_SPINNER(process_spinner));
- gtk_stack_set_visible_child(GTK_STACK(open_last_stack), process_spinner);
- mp_io_pipeline_capture();
-}
-
-void
-on_capture_shortcut(void)
-{
- on_shutter_clicked(NULL, NULL);
-}
-
-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 = g_uri_is_valid(
- code->data, G_URI_FLAGS_PARSE_RELAXED, NULL);
-
- char* data = strdup(code->data);
-
- 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(data,
- NULL, &error)) {
- g_printerr("Could not launch application: %s\n",
- error->message);
- }
- case GTK_RESPONSE_ACCEPT:
- gtk_clipboard_set_text(
- gtk_clipboard_get(GDK_SELECTION_PRIMARY),
- data, -1);
- case GTK_RESPONSE_CANCEL:
- break;
- default:
- g_printerr("Wrong dialog result: %d\n", result);
- }
- gtk_widget_destroy(dialog);
-}
-
-void
-on_preview_tap(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
-{
- if (event->type != GDK_BUTTON_PRESS)
- return;
-
- // Handle taps on the controls
- if (event->y < 32) {
- if (gtk_widget_is_visible(control_box)) {
- gtk_widget_hide(control_box);
- return;
- } else {
- gtk_widget_show(control_box);
- }
-
- if (event->x < 60) {
- // ISO
- current_control = USER_CONTROL_ISO;
- gtk_label_set_text(GTK_LABEL(control_name), "ISO");
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(control_auto),
- !gain_is_manual);
- gtk_adjustment_set_lower(control_slider, 0.0);
- gtk_adjustment_set_upper(control_slider, (float)gain_max);
- gtk_adjustment_set_value(control_slider, (double)gain);
-
- } else if (event->x > 60 && event->x < 120) {
- // Shutter angle
- current_control = USER_CONTROL_SHUTTER;
- gtk_label_set_text(GTK_LABEL(control_name), "Shutter");
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(control_auto),
- !exposure_is_manual);
- gtk_adjustment_set_lower(control_slider, 1.0);
- gtk_adjustment_set_upper(control_slider, 360.0);
- gtk_adjustment_set_value(control_slider, (double)exposure);
- }
-
- 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();
- }
-}
-
-void
-on_error_close_clicked(GtkWidget *widget, gpointer user_data)
-{
- gtk_widget_hide(error_box);
-}
-
-void
-on_camera_switch_clicked(GtkWidget *widget, gpointer user_data)
-{
- size_t next_index = camera->index + 1;
- const struct mp_camera_config *next_camera =
- mp_get_camera_config(next_index);
-
- if (!next_camera) {
- next_index = 0;
- next_camera = mp_get_camera_config(next_index);
- }
-
- camera = next_camera;
- update_io_pipeline();
-}
-
-void
-on_settings_btn_clicked(GtkWidget *widget, gpointer user_data)
-{
- gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "settings");
-}
-
-void
-on_back_clicked(GtkWidget *widget, gpointer user_data)
-{
- gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "main");
-}
-
-void
-on_control_auto_toggled(GtkToggleButton *widget, gpointer user_data)
-{
- bool is_manual = gtk_toggle_button_get_active(widget) ? false : true;
- bool has_changed;
-
- switch (current_control) {
- case USER_CONTROL_ISO:
- if (gain_is_manual != is_manual) {
- gain_is_manual = is_manual;
- has_changed = true;
- }
- break;
- case USER_CONTROL_SHUTTER:
- if (exposure_is_manual != is_manual) {
- exposure_is_manual = is_manual;
- has_changed = true;
- }
- break;
- }
-
- if (has_changed) {
- // The slider might have been moved while Auto mode is active. When entering
- // Manual mode, first read the slider value to sync with those changes.
- double value = gtk_adjustment_get_value(control_slider);
- switch (current_control) {
- case USER_CONTROL_ISO:
- if (value != gain) {
- gain = (int)value;
- }
- break;
- case USER_CONTROL_SHUTTER: {
- // So far all sensors use exposure time in number of sensor rows
- int new_exposure =
- (int)(value / 360.0 * camera->capture_mode.height);
- if (new_exposure != exposure) {
- exposure = new_exposure;
- }
- break;
- }
- }
-
- update_io_pipeline();
- draw_controls();
- }
-}
-
-void
-on_control_slider_changed(GtkAdjustment *widget, gpointer user_data)
-{
- double value = gtk_adjustment_get_value(widget);
-
- bool has_changed = false;
- switch (current_control) {
- case USER_CONTROL_ISO:
- if (value != gain) {
- gain = (int)value;
- has_changed = true;
- }
- break;
- case USER_CONTROL_SHUTTER: {
- // So far all sensors use exposure time in number of sensor rows
- int new_exposure =
- (int)(value / 360.0 * camera->capture_mode.height);
- if (new_exposure != exposure) {
- exposure = new_exposure;
- has_changed = true;
- }
- break;
- }
- }
-
- if (has_changed) {
- update_io_pipeline();
- draw_controls();
- }
-}
-
-int
-main(int argc, char *argv[])
-{
- if (!mp_load_config())
- return 1;
-
- setenv("LC_NUMERIC", "C", 1);
-
- gtk_init(&argc, &argv);
- g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme",
- TRUE, NULL);
- GtkBuilder *builder = gtk_builder_new_from_resource(
- "/org/postmarketos/Megapixels/camera.glade");
-
- GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "window"));
- GtkWidget *shutter = GTK_WIDGET(gtk_builder_get_object(builder, "shutter"));
- GtkWidget *switch_btn =
- GTK_WIDGET(gtk_builder_get_object(builder, "switch_camera"));
- GtkWidget *settings_btn =
- GTK_WIDGET(gtk_builder_get_object(builder, "settings"));
- GtkWidget *settings_back =
- GTK_WIDGET(gtk_builder_get_object(builder, "settings_back"));
- GtkWidget *error_close =
- GTK_WIDGET(gtk_builder_get_object(builder, "error_close"));
- GtkWidget *open_last =
- GTK_WIDGET(gtk_builder_get_object(builder, "open_last"));
- GtkWidget *open_directory =
- GTK_WIDGET(gtk_builder_get_object(builder, "open_directory"));
- preview = GTK_WIDGET(gtk_builder_get_object(builder, "preview"));
- error_box = GTK_WIDGET(gtk_builder_get_object(builder, "error_box"));
- error_message = GTK_WIDGET(gtk_builder_get_object(builder, "error_message"));
- main_stack = GTK_WIDGET(gtk_builder_get_object(builder, "main_stack"));
- open_last_stack = GTK_WIDGET(gtk_builder_get_object(builder, "open_last_stack"));
- thumb_last = GTK_WIDGET(gtk_builder_get_object(builder, "thumb_last"));
- process_spinner = GTK_WIDGET(gtk_builder_get_object(builder, "process_spinner"));
- control_box = GTK_WIDGET(gtk_builder_get_object(builder, "control_box"));
- control_name = GTK_WIDGET(gtk_builder_get_object(builder, "control_name"));
- control_slider =
- GTK_ADJUSTMENT(gtk_builder_get_object(builder, "control_adj"));
- control_auto = GTK_WIDGET(gtk_builder_get_object(builder, "control_auto"));
- g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
- g_signal_connect(shutter, "clicked", G_CALLBACK(on_shutter_clicked), NULL);
- g_signal_connect(error_close, "clicked", G_CALLBACK(on_error_close_clicked),
- NULL);
- g_signal_connect(switch_btn, "clicked", G_CALLBACK(on_camera_switch_clicked),
- NULL);
- g_signal_connect(settings_btn, "clicked",
- G_CALLBACK(on_settings_btn_clicked), NULL);
- g_signal_connect(settings_back, "clicked", G_CALLBACK(on_back_clicked),
- NULL);
- g_signal_connect(open_last, "clicked", G_CALLBACK(on_open_last_clicked),
- NULL);
- g_signal_connect(open_directory, "clicked",
- G_CALLBACK(on_open_directory_clicked), NULL);
- g_signal_connect(preview, "draw", G_CALLBACK(preview_draw), NULL);
- g_signal_connect(preview, "configure-event", G_CALLBACK(preview_configure),
- NULL);
- gtk_widget_set_events(preview, gtk_widget_get_events(preview) |
- GDK_BUTTON_PRESS_MASK |
- GDK_POINTER_MOTION_MASK);
- g_signal_connect(preview, "button-press-event", G_CALLBACK(on_preview_tap),
- NULL);
- g_signal_connect(control_auto, "toggled",
- G_CALLBACK(on_control_auto_toggled), NULL);
- g_signal_connect(control_slider, "value-changed",
- G_CALLBACK(on_control_slider_changed), NULL);
-
- GtkCssProvider *provider = gtk_css_provider_new();
- if (access("camera.css", F_OK) != -1) {
- gtk_css_provider_load_from_path(provider, "camera.css", NULL);
- } else {
- gtk_css_provider_load_from_resource(
- provider, "/org/postmarketos/Megapixels/camera.css");
- }
- GtkStyleContext *context = gtk_widget_get_style_context(error_box);
- gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider),
- GTK_STYLE_PROVIDER_PRIORITY_USER);
- context = gtk_widget_get_style_context(control_box);
- gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider),
- GTK_STYLE_PROVIDER_PRIORITY_USER);
-
- GClosure* capture_shortcut = g_cclosure_new(on_capture_shortcut, 0, 0);
-
- GtkAccelGroup* accel_group = gtk_accel_group_new();
- gtk_accel_group_connect(accel_group,
- GDK_KEY_space,
- 0,
- 0,
- capture_shortcut);
-
- gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
-
- mp_io_pipeline_start();
-
- camera = mp_get_camera_config(0);
- update_io_pipeline();
-
- gtk_widget_show(window);
- gtk_main();
-
- mp_io_pipeline_stop();
-
- return 0;
-}
diff --git a/meson.build b/meson.build
index 56df6fe..163fc20 100644
--- a/meson.build
+++ b/meson.build
@@ -1,73 +1,76 @@
project('megapixels', 'c')
+
gnome = import('gnome')
-gtkdep = dependency('gtk+-3.0')
+gtkdep = dependency('gtk4')
tiff = dependency('libtiff-4')
zbar = dependency('zbar')
threads = dependency('threads')
+# gl = dependency('gl')
+epoxy = dependency('epoxy')
cc = meson.get_compiler('c')
libm = cc.find_library('m', required: false)
-resources = gnome.compile_resources('megapixels-resources', 'org.postmarketos.Megapixels.gresource.xml')
+subdir('data')
conf = configuration_data()
conf.set_quoted('DATADIR', join_paths(get_option('prefix'), get_option('datadir')))
conf.set_quoted('SYSCONFDIR', get_option('sysconfdir'))
configure_file(
output: 'config.h',
- configuration: conf )
+ configuration: conf)
# Define DEBUG for debug builds only (debugoptimized is not included on this one)
if get_option('buildtype') == 'debug'
add_global_arguments('-DDEBUG', language: 'c')
endif
-# Workaround for libtiff having ABI changes but not changing the internal version number
+# Workaround for libtiff having ABI changes but not changing the internal
+# version number
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',
- 'zbar_pipeline.c',
- 'matrix.c',
- resources,
- dependencies : [gtkdep, libm, tiff, zbar, threads],
- install : true)
+ 'src/main.c',
+ 'src/ini.c',
+ 'src/gles2_debayer.c',
+ 'src/gl_util.c',
+ 'src/camera.c',
+ 'src/device.c',
+ 'src/pipeline.c',
+ 'src/camera_config.c',
+ 'src/io_pipeline.c',
+ 'src/process_pipeline.c',
+ 'src/zbar_pipeline.c',
+ 'src/matrix.c',
+ resources,
+ include_directories: 'src/',
+ dependencies: [gtkdep, libm, tiff, zbar, threads, epoxy],
+ install: true,
+ link_args: '-Wl,-ldl')
-install_data(['data/org.postmarketos.Megapixels.desktop'],
- install_dir : get_option('datadir') / 'applications')
-
-install_data(['data/org.postmarketos.Megapixels.metainfo.xml'],
- install_dir : get_option('datadir') / 'metainfo')
-
-install_data('data/org.postmarketos.Megapixels.svg',
- install_dir: join_paths(get_option('datadir'), 'icons/hicolor/scalable/apps')
-)
-
-install_data([
- 'config/pine64,pinephone-1.0.ini',
- 'config/pine64,pinephone-1.1.ini',
- 'config/pine64,pinephone-1.2.ini',
- 'config/pine64,pinetab.ini',
+install_data(
+ [
+ 'config/pine64,pinephone-1.0.ini',
+ 'config/pine64,pinephone-1.1.ini',
+ 'config/pine64,pinephone-1.2.ini',
+ 'config/pine64,pinetab.ini',
],
- install_dir : get_option('datadir') / 'megapixels/config/')
-
-install_data(['postprocess.sh'],
- install_dir : get_option('datadir') / 'megapixels/',
- install_mode: 'rwxr-xr-x')
+ install_dir: get_option('datadir') / 'megapixels/config/')
# Tools
-executable('megapixels-list-devices', 'tools/list_devices.c', 'device.c', dependencies: [gtkdep], install: true)
-executable('megapixels-camera-test', 'tools/camera_test.c', 'camera.c', 'device.c', dependencies: [gtkdep], install: true)
+executable('megapixels-list-devices',
+ 'tools/list_devices.c',
+ 'src/device.c',
+ include_directories: 'src/',
+ dependencies: [gtkdep],
+ install: true)
-test_quickpreview = executable('test_quickpreview', 'tests/test_quickpreview.c', 'quickpreview.c', 'camera.c', dependencies: [gtkdep])
-test('quickpreview', test_quickpreview)
+executable('megapixels-camera-test',
+ 'tools/camera_test.c',
+ 'src/camera.c',
+ 'src/device.c',
+ include_directories: 'src/',
+ dependencies: [gtkdep],
+ install: true)
diff --git a/org.postmarketos.Megapixels.gresource.xml b/org.postmarketos.Megapixels.gresource.xml
deleted file mode 100644
index d9938c1..0000000
--- a/org.postmarketos.Megapixels.gresource.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
- camera.glade
- camera.css
- switch-camera.svg
- shutter-button.svg
- folder-symbolic.svg
- settings-symbolic.svg
-
-
diff --git a/quickpreview.c b/quickpreview.c
deleted file mode 100644
index f8aed4f..0000000
--- a/quickpreview.c
+++ /dev/null
@@ -1,351 +0,0 @@
-/*
- * Fast but bad debayer method that scales and rotates by skipping source
- * pixels and doesn't interpolate any values at all
- */
-
-#include "quickpreview.h"
-#include
-#include
-
-/* Linear -> sRGB lookup table */
-static const int srgb[] = {
- 0, 12, 21, 28, 33, 38, 42, 46, 49, 52, 55, 58, 61, 63, 66, 68, 70,
- 73, 75, 77, 79, 81, 82, 84, 86, 88, 89, 91, 93, 94, 96, 97, 99, 100,
- 102, 103, 104, 106, 107, 109, 110, 111, 112, 114, 115, 116, 117, 118,
- 120, 121, 122, 123, 124, 125, 126, 127, 129, 130, 131, 132, 133, 134,
- 135, 136, 137, 138, 139, 140, 141, 142, 142, 143, 144, 145, 146, 147,
- 148, 149, 150, 151, 151, 152, 153, 154, 155, 156, 157, 157, 158, 159,
- 160, 161, 161, 162, 163, 164, 165, 165, 166, 167, 168, 168, 169, 170,
- 171, 171, 172, 173, 174, 174, 175, 176, 176, 177, 178, 179, 179, 180,
- 181, 181, 182, 183, 183, 184, 185, 185, 186, 187, 187, 188, 189, 189,
- 190, 191, 191, 192, 193, 193, 194, 194, 195, 196, 196, 197, 197, 198,
- 199, 199, 200, 201, 201, 202, 202, 203, 204, 204, 205, 205, 206, 206,
- 207, 208, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, 214,
- 215, 215, 216, 217, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222,
- 222, 223, 223, 224, 224, 225, 226, 226, 227, 227, 228, 228, 229, 229,
- 230, 230, 231, 231, 232, 232, 233, 233, 234, 234, 235, 235, 236, 236,
- 237, 237, 237, 238, 238, 239, 239, 240, 240, 241, 241, 242, 242, 243,
- 243, 244, 244, 245, 245, 245, 246, 246, 247, 247, 248, 248, 249, 249,
- 250, 250, 251, 251, 251, 252, 252, 253, 253, 254, 254, 255
-};
-
-static inline uint32_t
-pack_rgb(uint8_t r, uint8_t g, uint8_t b)
-{
- return (r << 16) | (g << 8) | b;
-}
-
-static inline uint32_t
-convert_yuv_to_srgb(uint8_t y, uint8_t u, uint8_t v)
-{
- uint32_t r = 1.164f * y + 1.596f * (v - 128);
- uint32_t g = 1.164f * y - 0.813f * (v - 128) - 0.391f * (u - 128);
- uint32_t b = 1.164f * y + 2.018f * (u - 128);
- return pack_rgb(r, g, b);
-}
-
-static inline uint32_t
-apply_colormatrix(uint32_t color, const float *colormatrix)
-{
- if (!colormatrix) {
- return color;
- }
-
- uint32_t r = (color >> 16) * colormatrix[0] +
- ((color >> 8) & 0xFF) * colormatrix[1] +
- (color & 0xFF) * colormatrix[2];
- uint32_t g = (color >> 16) * colormatrix[3] +
- ((color >> 8) & 0xFF) * colormatrix[4] +
- (color & 0xFF) * colormatrix[5];
- uint32_t b = (color >> 16) * colormatrix[6] +
- ((color >> 8) & 0xFF) * colormatrix[7] +
- (color & 0xFF) * colormatrix[8];
-
- // Clip colors
- if (r > 0xFF)
- r = 0xFF;
- if (g > 0xFF)
- g = 0xFF;
- if (b > 0xFF)
- b = 0xFF;
- return pack_rgb(r, g, b);
-}
-
-static inline uint32_t
-coord_map(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int rotation,
- bool mirrored)
-{
- uint32_t x_r, y_r;
- if (rotation == 0) {
- x_r = x;
- y_r = y;
- } else if (rotation == 90) {
- x_r = y;
- y_r = height - x - 1;
- } else if (rotation == 270) {
- x_r = width - y - 1;
- y_r = x;
- } else {
- x_r = width - x - 1;
- y_r = height - y - 1;
- }
-
- if (mirrored) {
- x_r = width - x_r - 1;
- }
-
- uint32_t index = y_r * width + x_r;
-#ifdef DEBUG
- assert(index < width * height);
-#endif
- return index;
-}
-
-static void
-quick_preview_rggb8(uint32_t *dst, const uint32_t dst_width,
- const uint32_t dst_height, const uint8_t *src,
- const uint32_t src_width, const uint32_t src_height,
- const MPPixelFormat format, const uint32_t rotation,
- const bool mirrored, const float *colormatrix,
- const uint8_t blacklevel, const uint32_t skip)
-{
- uint32_t src_y = 0, dst_y = 0;
- while (src_y < src_height) {
- uint32_t src_x = 0, dst_x = 0;
- while (src_x < src_width) {
- uint32_t src_i = src_y * src_width + src_x;
-
- uint8_t b0 = srgb[src[src_i] - blacklevel];
- uint8_t b1 = srgb[src[src_i + 1] - blacklevel];
- uint8_t b2 = srgb[src[src_i + src_width + 1] - blacklevel];
-
- uint32_t color;
- switch (format) {
- case MP_PIXEL_FMT_BGGR8:
- color = pack_rgb(b2, b1, b0);
- break;
- case MP_PIXEL_FMT_GBRG8:
- color = pack_rgb(b2, b0, b1);
- break;
- case MP_PIXEL_FMT_GRBG8:
- color = pack_rgb(b1, b0, b2);
- break;
- case MP_PIXEL_FMT_RGGB8:
- color = pack_rgb(b0, b1, b2);
- break;
- default:
- assert(false);
- }
-
- color = apply_colormatrix(color, colormatrix);
-
- dst[coord_map(dst_x, dst_y, dst_width, dst_height, rotation,
- mirrored)] = color;
-
- src_x += 2 + 2 * skip;
- ++dst_x;
- }
-
- src_y += 2 + 2 * skip;
- ++dst_y;
- }
-}
-
-static void
-quick_preview_rggb10(uint32_t *dst, const uint32_t dst_width,
- const uint32_t dst_height, const uint8_t *src,
- const uint32_t src_width, const uint32_t src_height,
- const MPPixelFormat format, const uint32_t rotation,
- const bool mirrored, const float *colormatrix,
- const uint8_t blacklevel, const uint32_t skip)
-{
- assert(src_width % 2 == 0);
-
- uint32_t width_bytes = mp_pixel_format_width_to_bytes(format, src_width);
-
- uint32_t src_y = 0, dst_y = 0;
- while (src_y < src_height) {
- uint32_t src_x = 0, dst_x = 0;
- while (src_x < width_bytes) {
- uint32_t src_i = src_y * width_bytes + src_x;
-
- uint8_t b0 = srgb[src[src_i] - blacklevel];
- uint8_t b1 = srgb[src[src_i + 1] - blacklevel];
- uint8_t b2 = srgb[src[src_i + width_bytes + 1] - blacklevel];
-
- uint32_t color;
- switch (format) {
- case MP_PIXEL_FMT_BGGR10P:
- color = pack_rgb(b2, b1, b0);
- break;
- case MP_PIXEL_FMT_GBRG10P:
- color = pack_rgb(b2, b0, b1);
- break;
- case MP_PIXEL_FMT_GRBG10P:
- color = pack_rgb(b1, b0, b2);
- break;
- case MP_PIXEL_FMT_RGGB10P:
- color = pack_rgb(b0, b1, b2);
- break;
- default:
- assert(false);
- }
-
- color = apply_colormatrix(color, colormatrix);
-
- dst[coord_map(dst_x, dst_y, dst_width, dst_height, rotation,
- mirrored)] = color;
-
- uint32_t advance = 1 + skip;
- if (src_x % 5 == 0) {
- src_x += 2 * (advance % 2) + 5 * (advance / 2);
- } else {
- src_x += 3 * (advance % 2) + 5 * (advance / 2);
- }
- ++dst_x;
- }
-
- src_y += 2 + 2 * skip;
- ++dst_y;
- }
-}
-
-static void
-quick_preview_yuv(uint32_t *dst, const uint32_t dst_width, const uint32_t dst_height,
- const uint8_t *src, const uint32_t src_width,
- const uint32_t src_height, const MPPixelFormat format,
- const uint32_t rotation, const bool mirrored,
- const float *colormatrix, const uint32_t skip)
-{
- assert(src_width % 2 == 0);
-
- uint32_t width_bytes = src_width * 2;
-
- uint32_t unrot_dst_width = dst_width;
- if (rotation != 0 && rotation != 180) {
- unrot_dst_width = dst_height;
- }
-
- uint32_t src_y = 0, dst_y = 0;
- while (src_y < src_height) {
- uint32_t src_x = 0, dst_x = 0;
- while (src_x < width_bytes) {
- uint32_t src_i = src_y * width_bytes + src_x;
-
- uint8_t b0 = src[src_i];
- uint8_t b1 = src[src_i + 1];
- uint8_t b2 = src[src_i + 2];
- uint8_t b3 = src[src_i + 3];
-
- uint32_t color1, color2;
- switch (format) {
- case MP_PIXEL_FMT_UYVY:
- color1 = convert_yuv_to_srgb(b1, b0, b2);
- color2 = convert_yuv_to_srgb(b3, b0, b2);
- break;
- case MP_PIXEL_FMT_YUYV:
- color1 = convert_yuv_to_srgb(b0, b1, b3);
- color2 = convert_yuv_to_srgb(b2, b1, b3);
- break;
- default:
- assert(false);
- }
-
- color1 = apply_colormatrix(color1, colormatrix);
- color2 = apply_colormatrix(color2, colormatrix);
-
- uint32_t dst_i1 = coord_map(dst_x, dst_y, dst_width,
- dst_height, rotation, mirrored);
- dst[dst_i1] = color1;
- ++dst_x;
-
- // The last pixel needs to be skipped if we have an odd un-rotated width
- if (dst_x < unrot_dst_width) {
- uint32_t dst_i2 =
- coord_map(dst_x, dst_y, dst_width,
- dst_height, rotation, mirrored);
- dst[dst_i2] = color2;
- ++dst_x;
- }
-
- src_x += 4 + 4 * skip;
- }
-
- src_y += 1 + skip;
- ++dst_y;
- }
-}
-
-void
-quick_preview(uint32_t *dst, const uint32_t dst_width, const uint32_t dst_height,
- const uint8_t *src, const uint32_t src_width,
- const uint32_t src_height, const MPPixelFormat format,
- const uint32_t rotation, const bool mirrored, const float *colormatrix,
- const uint8_t blacklevel, const uint32_t skip)
-{
- switch (format) {
- case MP_PIXEL_FMT_BGGR8:
- case MP_PIXEL_FMT_GBRG8:
- case MP_PIXEL_FMT_GRBG8:
- case MP_PIXEL_FMT_RGGB8:
- quick_preview_rggb8(dst, dst_width, dst_height, src, src_width,
- src_height, format, rotation, mirrored,
- colormatrix, blacklevel, skip);
- break;
- case MP_PIXEL_FMT_BGGR10P:
- case MP_PIXEL_FMT_GBRG10P:
- case MP_PIXEL_FMT_GRBG10P:
- case MP_PIXEL_FMT_RGGB10P:
- quick_preview_rggb10(dst, dst_width, dst_height, src, src_width,
- src_height, format, rotation, mirrored,
- colormatrix, blacklevel, skip);
- break;
- case MP_PIXEL_FMT_UYVY:
- case MP_PIXEL_FMT_YUYV:
- quick_preview_yuv(dst, dst_width, dst_height, src, src_width,
- src_height, format, rotation, mirrored,
- colormatrix, skip);
- break;
- default:
- assert(false);
- }
-}
-
-static uint32_t
-div_ceil(uint32_t x, uint32_t y)
-{
- return x / y + !!(x % y);
-}
-
-void
-quick_preview_size(uint32_t *dst_width, uint32_t *dst_height, uint32_t *skip,
- const uint32_t preview_width, const uint32_t preview_height,
- const uint32_t src_width, const uint32_t src_height,
- const MPPixelFormat format, const int rotation)
-{
- uint32_t colors_x = mp_pixel_format_width_to_colors(format, src_width);
- uint32_t colors_y = mp_pixel_format_height_to_colors(format, src_height);
-
- if (rotation != 0 && rotation != 180) {
- uint32_t tmp = colors_x;
- colors_x = colors_y;
- colors_y = tmp;
- }
-
- uint32_t scale_x = colors_x / preview_width;
- uint32_t scale_y = colors_y / preview_height;
-
- if (scale_x > 0)
- --scale_x;
- if (scale_y > 0)
- --scale_y;
- *skip = scale_x > scale_y ? scale_x : scale_y;
-
- *dst_width = div_ceil(colors_x, (1 + *skip));
- if (*dst_width <= 0)
- *dst_width = 1;
-
- *dst_height = div_ceil(colors_y, (1 + *skip));
- if (*dst_height <= 0)
- *dst_height = 1;
-}
diff --git a/quickpreview.h b/quickpreview.h
deleted file mode 100644
index 1815215..0000000
--- a/quickpreview.h
+++ /dev/null
@@ -1,14 +0,0 @@
-#include "camera.h"
-#include
-
-void quick_preview(uint32_t *dst, const uint32_t dst_width,
- const uint32_t dst_height, const uint8_t *src,
- const uint32_t src_width, const uint32_t src_height,
- const MPPixelFormat format, const uint32_t rotation,
- const bool mirrored, const float *colormatrix,
- const uint8_t blacklevel, const uint32_t skip);
-
-void quick_preview_size(uint32_t *dst_width, uint32_t *dst_height, uint32_t *skip,
- const uint32_t preview_width, const uint32_t preview_height,
- const uint32_t src_width, const uint32_t src_height,
- const MPPixelFormat format, const int rotation);
diff --git a/camera.c b/src/camera.c
similarity index 96%
rename from camera.c
rename to src/camera.c
index 6e80781..6bc3e36 100644
--- a/camera.c
+++ b/src/camera.c
@@ -6,6 +6,7 @@
#include
#include
#include
+#include
#define MAX_VIDEO_BUFFERS 20
@@ -205,6 +206,7 @@ xioctl(int fd, int request, void *arg)
struct video_buffer {
uint32_t length;
uint8_t *data;
+ int fd;
};
struct _MPCamera {
@@ -465,6 +467,17 @@ mp_camera_start_capture(MPCamera *camera)
break;
}
+ struct v4l2_exportbuffer expbuf = {
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+ .index = i,
+ };
+ if (xioctl(camera->video_fd, VIDIOC_EXPBUF, &expbuf) == -1) {
+ errno_printerr("VIDIOC_EXPBUF");
+ break;
+ }
+
+ camera->buffers[i].fd = expbuf.fd;
+
++camera->num_buffers;
}
@@ -510,6 +523,10 @@ error:
-1) {
errno_printerr("munmap");
}
+
+ if (close(camera->buffers[i].fd) == -1) {
+ errno_printerr("close");
+ }
}
// Reset allocated buffers
@@ -543,6 +560,10 @@ mp_camera_stop_capture(MPCamera *camera)
-1) {
errno_printerr("munmap");
}
+
+ if (close(camera->buffers[i].fd) == -1) {
+ errno_printerr("close");
+ }
}
camera->num_buffers = 0;
@@ -565,8 +586,7 @@ mp_camera_is_capturing(MPCamera *camera)
}
bool
-mp_camera_capture_image(MPCamera *camera, void (*callback)(MPImage, void *),
- void *user_data)
+mp_camera_capture_buffer(MPCamera *camera, MPBuffer *buffer)
{
struct v4l2_buffer buf = {};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
@@ -587,6 +607,7 @@ mp_camera_capture_image(MPCamera *camera, void (*callback)(MPImage, void *),
/* fallthrough */
default:
errno_printerr("VIDIOC_DQBUF");
+ exit(1);
return false;
}
}
@@ -606,24 +627,23 @@ mp_camera_capture_image(MPCamera *camera, void (*callback)(MPImage, void *),
mp_pixel_format_width_to_bytes(pixel_format, width) * height);
assert(bytesused == camera->buffers[buf.index].length);
- MPImage image = {
- .pixel_format = pixel_format,
- .width = width,
- .height = height,
- .data = camera->buffers[buf.index].data,
- };
+ buffer->index = buf.index;
+ buffer->data = camera->buffers[buf.index].data;
+ buffer->fd = camera->buffers[buf.index].fd;
- callback(image, user_data);
+ return true;
+}
- // The callback may have stopped the capture, only queue the buffer if we're
- // still capturing.
- if (mp_camera_is_capturing(camera)) {
- if (xioctl(camera->video_fd, VIDIOC_QBUF, &buf) == -1) {
- errno_printerr("VIDIOC_QBUF");
- return false;
- }
+bool mp_camera_release_buffer(MPCamera *camera, uint32_t buffer_index)
+{
+ struct v4l2_buffer buf = {};
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ buf.index = buffer_index;
+ if (xioctl(camera->video_fd, VIDIOC_QBUF, &buf) == -1) {
+ errno_printerr("VIDIOC_QBUF");
+ return false;
}
-
return true;
}
diff --git a/camera.h b/src/camera.h
similarity index 95%
rename from camera.h
rename to src/camera.h
index 3004e8e..323ac63 100644
--- a/camera.h
+++ b/src/camera.h
@@ -45,11 +45,11 @@ typedef struct {
bool mp_camera_mode_is_equivalent(const MPCameraMode *m1, const MPCameraMode *m2);
typedef struct {
- uint32_t pixel_format;
- uint32_t width;
- uint32_t height;
+ uint32_t index;
+
uint8_t *data;
-} MPImage;
+ int fd;
+} MPBuffer;
typedef struct _MPCamera MPCamera;
@@ -67,8 +67,8 @@ bool mp_camera_set_mode(MPCamera *camera, MPCameraMode *mode);
bool mp_camera_start_capture(MPCamera *camera);
bool mp_camera_stop_capture(MPCamera *camera);
bool mp_camera_is_capturing(MPCamera *camera);
-bool mp_camera_capture_image(MPCamera *camera, void (*callback)(MPImage, void *),
- void *user_data);
+bool mp_camera_capture_buffer(MPCamera *camera, MPBuffer *buffer);
+bool mp_camera_release_buffer(MPCamera *camera, uint32_t buffer_index);
typedef struct _MPCameraModeList MPCameraModeList;
diff --git a/camera_config.c b/src/camera_config.c
similarity index 95%
rename from camera_config.c
rename to src/camera_config.c
index 1e52d62..f8ecf05 100644
--- a/camera_config.c
+++ b/src/camera_config.c
@@ -3,7 +3,6 @@
#include "ini.h"
#include "config.h"
#include "matrix.h"
-#include
#include
#include
#include
@@ -21,18 +20,8 @@ static bool
find_config(char *conffile)
{
char buf[512];
- char *xdg_config_home;
- wordexp_t exp_result;
FILE *fp;
- // Resolve XDG stuff
- if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) {
- xdg_config_home = "~/.config";
- }
- wordexp(xdg_config_home, &exp_result, 0);
- xdg_config_home = strdup(exp_result.we_wordv[0]);
- wordfree(&exp_result);
-
if (access("/proc/device-tree/compatible", F_OK) != -1) {
// Reads to compatible string of the current device tree, looks like:
// pine64,pinephone-1.2\0allwinner,sun50i-a64\0
@@ -48,7 +37,7 @@ find_config(char *conffile)
}
// Check for a config file in XDG_CONFIG_HOME
- sprintf(conffile, "%s/megapixels/config/%s.ini", xdg_config_home,
+ sprintf(conffile, "%s/megapixels/config/%s.ini", g_get_user_config_dir (),
buf);
if (access(conffile, F_OK) != -1) {
printf("Found config file at %s\n", conffile);
diff --git a/camera_config.h b/src/camera_config.h
similarity index 100%
rename from camera_config.h
rename to src/camera_config.h
diff --git a/device.c b/src/device.c
similarity index 100%
rename from device.c
rename to src/device.c
diff --git a/device.h b/src/device.h
similarity index 100%
rename from device.h
rename to src/device.h
diff --git a/src/gl_util.c b/src/gl_util.c
new file mode 100644
index 0000000..c4c76ca
--- /dev/null
+++ b/src/gl_util.c
@@ -0,0 +1,220 @@
+#include "gl_util.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+void gl_util_check_error(const char *file, int line)
+{
+ GLenum error = glGetError();
+
+ const char *name;
+ switch (error) {
+ case GL_NO_ERROR:
+ return; // no error
+ case GL_INVALID_ENUM:
+ name = "GL_INVALID_ENUM";
+ break;
+ case GL_INVALID_VALUE:
+ name = "GL_INVALID_VALUE";
+ break;
+ case GL_INVALID_OPERATION:
+ name = "GL_INVALID_OPERATION";
+ break;
+ case GL_INVALID_FRAMEBUFFER_OPERATION:
+ name = "GL_INVALID_FRAMEBUFFER_OPERATION";
+ break;
+ case GL_OUT_OF_MEMORY:
+ name = "GL_OUT_OF_MEMORY";
+ break;
+ default:
+ name = "UNKNOWN ERROR!";
+ break;
+ }
+
+ printf("GL error at %s:%d - %s\n", file, line, name);
+
+ // raise(SIGTRAP);
+}
+
+GLuint
+gl_util_load_shader(const char *resource, GLenum type, const char **extra_sources, size_t num_extra)
+{
+ GdkGLContext *context = gdk_gl_context_get_current();
+ assert(context);
+
+ GLuint shader = glCreateShader(type);
+ if (shader == 0) {
+ return 0;
+ }
+
+ GBytes *bytes = g_resources_lookup_data(resource, 0, NULL);
+ if (!bytes) {
+ printf("Failed to load shader resource %s\n", resource);
+ return 0;
+ }
+
+ // Build #define for OpenGL context information
+ gboolean is_es = gdk_gl_context_get_use_es(context);
+ int major, minor;
+ gdk_gl_context_get_version(context, &major, &minor);
+ char context_info_buf[128];
+ snprintf(context_info_buf, 128, "#define %s\n#define GL_%d\n#define GL_%d_%d\n", is_es ? "GL_ES" : "GL_NO_ES", major, major, minor);
+
+ gsize glib_size = 0;
+ const GLchar *source = g_bytes_get_data(bytes, &glib_size);
+ if (glib_size == 0 || glib_size > INT_MAX) {
+ printf("Invalid size for resource\n");
+ return 0;
+ }
+
+ const GLchar **sources = malloc((num_extra + 1) * sizeof(GLchar *));
+ GLint *sizes = malloc((num_extra + 1) * sizeof(GLint));
+
+ for (size_t i = 0; i < num_extra; ++i) {
+ sources[i] = extra_sources[i];
+ sizes[i] = -1;
+ }
+ sources[num_extra] = source;
+ sizes[num_extra] = glib_size;
+
+ glShaderSource(shader, num_extra + 1, sources, sizes);
+ glCompileShader(shader);
+ check_gl();
+
+ free(sources);
+ free(sizes);
+
+ g_bytes_unref(bytes);
+
+ // Check compile status
+ GLint success;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
+ if (success == GL_FALSE) {
+ printf("Shader compilation failed for %s\n", resource);
+
+ glDeleteShader(shader);
+ return 0;
+ }
+
+ GLint log_length;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
+ if (log_length > 0) {
+ char *log = malloc(sizeof(char) * log_length);
+ glGetShaderInfoLog(shader, log_length - 1, &log_length, log);
+
+ printf("Shader %s log: %s\n", resource, log);
+ free(log);
+
+ glDeleteShader(shader);
+ return 0;
+ }
+
+ return shader;
+}
+
+GLuint
+gl_util_link_program(GLuint *shaders, size_t num_shaders)
+{
+ GLuint program = glCreateProgram();
+
+ for (size_t i = 0; i < num_shaders; ++i) {
+ glAttachShader(program, shaders[i]);
+ }
+
+ glLinkProgram(program);
+ check_gl();
+
+ GLint success;
+ glGetProgramiv(program, GL_LINK_STATUS, &success);
+ if (success == GL_FALSE) {
+ printf("Program linking failed\n");
+ }
+
+ GLint log_length;
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
+ if (log_length > 0) {
+ char *log = malloc(sizeof(char) * log_length);
+ glGetProgramInfoLog(program, log_length - 1, &log_length, log);
+
+ printf("Program log: %s\n", log);
+ free(log);
+ }
+ check_gl();
+
+ return program;
+}
+
+static const GLfloat quad_data[] = {
+ // Vertices
+ -1, -1,
+ 1, -1,
+ -1, 1,
+ 1, 1,
+ // Texcoords
+ 0, 0,
+ 1, 0,
+ 0, 1,
+ 1, 1,
+};
+
+GLuint gl_util_new_quad()
+{
+ GdkGLContext *context = gdk_gl_context_get_current();
+ assert(context);
+
+ if (gdk_gl_context_get_use_es(context)) {
+ return 0;
+ } else {
+ GLuint buffer;
+ glGenBuffers(1, &buffer);
+
+ glBindBuffer(GL_ARRAY_BUFFER, buffer);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(quad_data), quad_data, GL_STATIC_DRAW);
+ check_gl();
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ check_gl();
+
+ return buffer;
+ }
+}
+
+void gl_util_bind_quad(GLuint buffer)
+{
+ GdkGLContext *context = gdk_gl_context_get_current();
+ assert(context);
+
+ if (gdk_gl_context_get_use_es(context)) {
+ glVertexAttribPointer(GL_UTIL_VERTEX_ATTRIBUTE, 2, GL_FLOAT, 0, 0, quad_data);
+ check_gl();
+ glEnableVertexAttribArray(GL_UTIL_VERTEX_ATTRIBUTE);
+ check_gl();
+
+ glVertexAttribPointer(GL_UTIL_TEX_COORD_ATTRIBUTE, 2, GL_FLOAT, 0, 0, quad_data + 8);
+ check_gl();
+ glEnableVertexAttribArray(GL_UTIL_TEX_COORD_ATTRIBUTE);
+ check_gl();
+ } else {
+ glBindBuffer(GL_ARRAY_BUFFER, buffer);
+ check_gl();
+
+ glVertexAttribPointer(GL_UTIL_VERTEX_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0);
+ glEnableVertexAttribArray(GL_UTIL_VERTEX_ATTRIBUTE);
+ check_gl();
+
+ glVertexAttribPointer(GL_UTIL_TEX_COORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, (void*) (8 * sizeof(float)));
+ glEnableVertexAttribArray(GL_UTIL_TEX_COORD_ATTRIBUTE);
+ check_gl();
+ }
+}
+
+void gl_util_draw_quad(GLuint buffer)
+{
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+ check_gl();
+}
diff --git a/src/gl_util.h b/src/gl_util.h
new file mode 100644
index 0000000..f1e8451
--- /dev/null
+++ b/src/gl_util.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include
+#include
+
+#define GL_UTIL_VERTEX_ATTRIBUTE 0
+#define GL_UTIL_TEX_COORD_ATTRIBUTE 1
+
+#define check_gl() gl_util_check_error(__FILE__, __LINE__)
+void gl_util_check_error(const char *file, int line);
+
+GLuint gl_util_load_shader(const char *resource, GLenum type, const char **extra_sources, size_t num_extra);
+GLuint gl_util_link_program(GLuint *shaders, size_t num_shaders);
+
+GLuint gl_util_new_quad();
+void gl_util_bind_quad(GLuint buffer);
+void gl_util_draw_quad(GLuint buffer);
diff --git a/src/gles2_debayer.c b/src/gles2_debayer.c
new file mode 100644
index 0000000..df31fc4
--- /dev/null
+++ b/src/gles2_debayer.c
@@ -0,0 +1,141 @@
+#include "gles2_debayer.h"
+
+#include "camera.h"
+#include "gl_util.h"
+#include
+
+#define VERTEX_ATTRIBUTE 0
+#define TEX_COORD_ATTRIBUTE 1
+
+struct _GLES2Debayer {
+ GLuint frame_buffer;
+ GLuint program;
+ GLuint uniform_transform;
+ GLuint uniform_pixel_size;
+ GLuint uniform_texture;
+ GLuint uniform_color_matrix;
+
+ GLuint quad;
+};
+
+GLES2Debayer *
+gles2_debayer_new(MPPixelFormat format)
+{
+ if (format != MP_PIXEL_FMT_BGGR8) {
+ return NULL;
+ }
+
+ GLuint frame_buffer;
+ glGenFramebuffers(1, &frame_buffer);
+ check_gl();
+
+ GLuint shaders[] = {
+ gl_util_load_shader("/org/postmarketos/Megapixels/debayer.vert", GL_VERTEX_SHADER, NULL, 0),
+ gl_util_load_shader("/org/postmarketos/Megapixels/debayer.frag", GL_FRAGMENT_SHADER, NULL, 0),
+ };
+
+ GLuint program = gl_util_link_program(shaders, 2);
+ glBindAttribLocation(program, VERTEX_ATTRIBUTE, "vert");
+ glBindAttribLocation(program, TEX_COORD_ATTRIBUTE, "tex_coord");
+ check_gl();
+
+ GLES2Debayer *self = malloc(sizeof(GLES2Debayer));
+ self->frame_buffer = frame_buffer;
+ self->program = program;
+
+ self->uniform_transform = glGetUniformLocation(self->program, "transform");
+ self->uniform_pixel_size = glGetUniformLocation(self->program, "pixel_size");
+ self->uniform_texture = glGetUniformLocation(self->program, "texture");
+ self->uniform_color_matrix = glGetUniformLocation(self->program, "color_matrix");
+ check_gl();
+
+ self->quad = gl_util_new_quad();
+
+ return self;
+}
+
+void
+gles2_debayer_free(GLES2Debayer *self)
+{
+ glDeleteFramebuffers(1, &self->frame_buffer);
+
+ glDeleteProgram(self->program);
+
+ free(self);
+}
+
+void
+gles2_debayer_use(GLES2Debayer *self)
+{
+ glUseProgram(self->program);
+ check_gl();
+
+ gl_util_bind_quad(self->quad);
+}
+
+void
+gles2_debayer_configure(GLES2Debayer *self,
+ const uint32_t dst_width, const uint32_t dst_height,
+ const uint32_t src_width, const uint32_t src_height,
+ const uint32_t rotation,
+ const bool mirrored,
+ const float *colormatrix,
+ const uint8_t blacklevel)
+{
+ glViewport(0, 0, dst_width, dst_height);
+ check_gl();
+
+ GLfloat rotation_list[4] = { 0, -1, 0, 1 };
+ int rotation_index = 4 - rotation / 90;
+
+ GLfloat sin_rot = rotation_list[rotation_index];
+ GLfloat cos_rot = rotation_list[(rotation_index + 1) % 4];
+ GLfloat scale_x = mirrored ? 1 : -1;
+ GLfloat matrix[9] = {
+ cos_rot * scale_x, sin_rot, 0,
+ -sin_rot * scale_x, cos_rot, 0,
+ 0, 0, 1,
+ };
+ glUniformMatrix3fv(self->uniform_transform, 1, GL_FALSE, matrix);
+ check_gl();
+
+ GLfloat pixel_size_x = 1.0f / src_width;
+ GLfloat pixel_size_y = 1.0f / src_height;
+ glUniform2f(self->uniform_pixel_size, pixel_size_x, pixel_size_y);
+ check_gl();
+
+ if (colormatrix) {
+ GLfloat transposed[9];
+ for (int i = 0; i < 3; ++i)
+ for (int j = 0; j < 3; ++j)
+ transposed[i + j * 3] = colormatrix[j + i * 3];
+
+ glUniformMatrix3fv(self->uniform_color_matrix, 1, GL_FALSE, transposed);
+ } else {
+ static const GLfloat identity[9] = {
+ 1, 0, 0,
+ 0, 1, 0,
+ 0, 0, 1,
+ };
+ glUniformMatrix3fv(self->uniform_color_matrix, 1, GL_FALSE, identity);
+ }
+ check_gl();
+}
+
+void
+gles2_debayer_process(GLES2Debayer *self, GLuint dst_id, GLuint source_id)
+{
+ glBindFramebuffer(GL_FRAMEBUFFER, self->frame_buffer);
+ glBindTexture(GL_TEXTURE_2D, dst_id);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_id, 0);
+ check_gl();
+
+ assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, source_id);
+ glUniform1i(self->uniform_texture, 0);
+ check_gl();
+
+ gl_util_draw_quad(self->quad);
+}
diff --git a/src/gles2_debayer.h b/src/gles2_debayer.h
new file mode 100644
index 0000000..0028749
--- /dev/null
+++ b/src/gles2_debayer.h
@@ -0,0 +1,21 @@
+#include "camera.h"
+#include "gl_util.h"
+#include
+#include
+
+typedef struct _GLES2Debayer GLES2Debayer;
+
+GLES2Debayer* gles2_debayer_new(MPPixelFormat format);
+void gles2_debayer_free(GLES2Debayer *self);
+
+void gles2_debayer_use(GLES2Debayer *self);
+
+void gles2_debayer_configure(GLES2Debayer *self,
+ const uint32_t dst_width, const uint32_t dst_height,
+ const uint32_t src_width, const uint32_t src_height,
+ const uint32_t rotation,
+ const bool mirrored,
+ const float *colormatrix,
+ const uint8_t blacklevel);
+
+void gles2_debayer_process(GLES2Debayer *self, GLuint dst_id, GLuint source_id);
diff --git a/ini.c b/src/ini.c
similarity index 100%
rename from ini.c
rename to src/ini.c
diff --git a/ini.h b/src/ini.h
similarity index 100%
rename from ini.h
rename to src/ini.h
diff --git a/io_pipeline.c b/src/io_pipeline.c
similarity index 92%
rename from io_pipeline.c
rename to src/io_pipeline.c
index 980a722..1281df3 100644
--- a/io_pipeline.c
+++ b/src/io_pipeline.c
@@ -75,6 +75,8 @@ static int captures_remaining = 0;
static int preview_width;
static int preview_height;
+static int device_rotation;
+
struct control_state {
bool gain_is_manual;
int gain;
@@ -185,6 +187,12 @@ setup_camera(MPDeviceList **device_list, const struct mp_camera_config *config)
info->camera = mp_camera_new(dev_info->video_fd, info->fd);
+ // Start with the capture format, this works around a bug with
+ // the ov5640 driver where it won't allow setting the preview
+ // format initially.
+ MPCameraMode mode = config->capture_mode;
+ mp_camera_set_mode(info->camera, &mode);
+
// Trigger continuous auto focus if the sensor supports it
if (mp_camera_query_control(info->camera, V4L2_CID_FOCUS_AUTO,
NULL)) {
@@ -269,6 +277,7 @@ update_process_pipeline()
.burst_length = burst_length,
.preview_width = preview_width,
.preview_height = preview_height,
+ .device_rotation = device_rotation,
.gain_is_manual = current_controls.gain_is_manual,
.gain = current_controls.gain,
.gain_max = info->gain_max,
@@ -305,6 +314,7 @@ capture(MPPipeline *pipeline, const void *data)
V4L2_EXPOSURE_MANUAL);
// Change camera mode for capturing
+ mp_process_pipeline_sync();
mp_camera_stop_capture(info->camera);
mode = camera->capture_mode;
@@ -324,6 +334,19 @@ mp_io_pipeline_capture()
mp_pipeline_invoke(pipeline, capture, NULL, 0);
}
+static void
+release_buffer(MPPipeline *pipeline, const uint32_t *buffer_index)
+{
+ struct camera_info *info = &cameras[camera->index];
+
+ mp_camera_release_buffer(info->camera, *buffer_index);
+}
+
+void mp_io_pipeline_release_buffer(uint32_t buffer_index)
+{
+ mp_pipeline_invoke(pipeline, (MPPipelineCallback) release_buffer, &buffer_index, sizeof(uint32_t));
+}
+
static void
update_controls()
{
@@ -375,7 +398,7 @@ update_controls()
}
static void
-on_frame(MPImage image, void *data)
+on_frame(MPBuffer buffer, void * _data)
{
// Only update controls right after a frame was captured
update_controls();
@@ -384,13 +407,13 @@ on_frame(MPImage image, void *data)
// presumably from buffers made ready during the switch. Ignore these.
if (just_switched_mode) {
if (blank_frame_count < 20) {
- // Only check a 50x50 area
+ // Only check a 10x10 area
size_t test_size =
- MIN(50, image.width) * MIN(50, image.height);
+ MIN(10, mode.width) * MIN(10, mode.height);
bool image_is_blank = true;
for (size_t i = 0; i < test_size; ++i) {
- if (image.data[i] != 0) {
+ if (buffer.data[i] != 0) {
image_is_blank = false;
}
}
@@ -407,17 +430,8 @@ on_frame(MPImage image, void *data)
blank_frame_count = 0;
}
- // Copy from the camera buffer
- size_t size =
- mp_pixel_format_width_to_bytes(image.pixel_format, image.width) *
- image.height;
- uint8_t *buffer = malloc(size);
- memcpy(buffer, image.data, size);
-
- image.data = buffer;
-
// Send the image off for processing
- mp_process_pipeline_process_image(image);
+ mp_process_pipeline_process_image(buffer);
if (captures_remaining > 0) {
--captures_remaining;
@@ -438,6 +452,7 @@ on_frame(MPImage image, void *data)
}
// Go back to preview mode
+ mp_process_pipeline_sync();
mp_camera_stop_capture(info->camera);
mode = camera->preview_mode;
@@ -465,6 +480,7 @@ update_state(MPPipeline *pipeline, const struct mp_io_pipeline_state *state)
struct camera_info *info = &cameras[camera->index];
struct device_info *dev_info = &devices[info->device_index];
+ mp_process_pipeline_sync();
mp_camera_stop_capture(info->camera);
mp_device_setup_link(dev_info->device, info->pad_id,
dev_info->interface_pad_id, false);
@@ -492,15 +508,15 @@ update_state(MPPipeline *pipeline, const struct mp_io_pipeline_state *state)
pipeline, info->camera, on_frame, NULL);
current_controls.gain_is_manual =
- mp_camera_control_get_int32(
- info->camera, V4L2_CID_EXPOSURE_AUTO) ==
- V4L2_EXPOSURE_MANUAL;
+ mp_camera_control_get_bool(info->camera,
+ V4L2_CID_AUTOGAIN) == 0;
current_controls.gain = mp_camera_control_get_int32(
info->camera, info->gain_ctrl);
current_controls.exposure_is_manual =
- mp_camera_control_get_bool(info->camera,
- V4L2_CID_AUTOGAIN) == 0;
+ mp_camera_control_get_int32(
+ info->camera, V4L2_CID_EXPOSURE_AUTO) ==
+ V4L2_EXPOSURE_MANUAL;
current_controls.exposure = mp_camera_control_get_int32(
info->camera, V4L2_CID_EXPOSURE);
}
@@ -508,11 +524,13 @@ update_state(MPPipeline *pipeline, const struct mp_io_pipeline_state *state)
has_changed = has_changed || burst_length != state->burst_length ||
preview_width != state->preview_width ||
- preview_height != state->preview_height;
+ preview_height != state->preview_height ||
+ device_rotation != state->device_rotation;
burst_length = state->burst_length;
preview_width = state->preview_width;
preview_height = state->preview_height;
+ device_rotation = state->device_rotation;
if (camera) {
struct control_state previous_desired = desired_controls;
diff --git a/io_pipeline.h b/src/io_pipeline.h
similarity index 84%
rename from io_pipeline.h
rename to src/io_pipeline.h
index 10a6298..62b0c8b 100644
--- a/io_pipeline.h
+++ b/src/io_pipeline.h
@@ -10,6 +10,8 @@ struct mp_io_pipeline_state {
int preview_width;
int preview_height;
+ int device_rotation;
+
bool gain_is_manual;
int gain;
@@ -23,4 +25,6 @@ void mp_io_pipeline_stop();
void mp_io_pipeline_focus();
void mp_io_pipeline_capture();
+void mp_io_pipeline_release_buffer(uint32_t buffer_index);
+
void mp_io_pipeline_update_state(const struct mp_io_pipeline_state *state);
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..b096e8e
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,963 @@
+#include "main.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "gl_util.h"
+#include "camera_config.h"
+#include "io_pipeline.h"
+#include "process_pipeline.h"
+
+#define RENDERDOC
+
+#ifdef RENDERDOC
+#include
+#include
+RENDERDOC_API_1_1_2 *rdoc_api = NULL;
+#endif
+
+enum user_control { USER_CONTROL_ISO, USER_CONTROL_SHUTTER };
+
+static bool camera_is_initialized = false;
+static const struct mp_camera_config *camera = NULL;
+static MPCameraMode mode;
+
+static int preview_width = -1;
+static int preview_height = -1;
+
+static int device_rotation = 0;
+
+static bool gain_is_manual = false;
+static int gain;
+static int gain_max;
+
+static bool exposure_is_manual = false;
+static int exposure;
+
+static bool has_auto_focus_continuous;
+static bool has_auto_focus_start;
+
+static MPProcessPipelineBuffer *current_preview_buffer = NULL;
+static int preview_buffer_width = -1;
+static int preview_buffer_height = -1;
+
+static char last_path[260] = "";
+
+static MPZBarScanResult *zbar_result = NULL;
+
+static int burst_length = 3;
+
+// Widgets
+GtkWidget *preview;
+GtkWidget *main_stack;
+GtkWidget *open_last_stack;
+GtkWidget *thumb_last;
+GtkWidget *process_spinner;
+GtkWidget *scanned_codes;
+GtkWidget *preview_top_box;
+GtkWidget *preview_bottom_box;
+
+int
+remap(int value, int input_min, int input_max, int output_min, int output_max)
+{
+ const long long factor = 1000000000;
+ long long output_spread = output_max - output_min;
+ long long input_spread = input_max - input_min;
+
+ long long zero_value = value - input_min;
+ zero_value *= factor;
+ long long percentage = zero_value / input_spread;
+
+ long long zero_output = percentage * output_spread / factor;
+
+ long long result = output_min + zero_output;
+ return (int)result;
+}
+
+static void
+update_io_pipeline()
+{
+ struct mp_io_pipeline_state io_state = {
+ .camera = camera,
+ .burst_length = burst_length,
+ .preview_width = preview_width,
+ .preview_height = preview_height,
+ .device_rotation = device_rotation,
+ .gain_is_manual = gain_is_manual,
+ .gain = gain,
+ .exposure_is_manual = exposure_is_manual,
+ .exposure = exposure,
+ };
+ mp_io_pipeline_update_state(&io_state);
+}
+
+static bool
+update_state(const struct mp_main_state *state)
+{
+ if (!camera_is_initialized) {
+ camera_is_initialized = true;
+ }
+
+ if (camera == state->camera) {
+ mode = state->mode;
+
+ if (!gain_is_manual) {
+ gain = state->gain;
+ }
+ gain_max = state->gain_max;
+
+ if (!exposure_is_manual) {
+ exposure = state->exposure;
+ }
+
+ has_auto_focus_continuous = state->has_auto_focus_continuous;
+ has_auto_focus_start = state->has_auto_focus_start;
+ }
+
+ preview_buffer_width = state->image_width;
+ preview_buffer_height = state->image_height;
+
+ return false;
+}
+
+void
+mp_main_update_state(const struct mp_main_state *state)
+{
+ struct mp_main_state *state_copy = malloc(sizeof(struct mp_main_state));
+ *state_copy = *state;
+
+ g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
+ (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(MPProcessPipelineBuffer *buffer)
+{
+ if (current_preview_buffer) {
+ mp_process_pipeline_buffer_unref(current_preview_buffer);
+ }
+ current_preview_buffer = buffer;
+ gtk_widget_queue_draw(preview);
+ return false;
+}
+
+void
+mp_main_set_preview(MPProcessPipelineBuffer *buffer)
+{
+ g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
+ (GSourceFunc)set_preview, buffer, NULL);
+}
+
+struct capture_completed_args {
+ GdkTexture *thumb;
+ char *fname;
+};
+
+static bool
+capture_completed(struct capture_completed_args *args)
+{
+ strncpy(last_path, args->fname, 259);
+
+ gtk_image_set_from_paintable(GTK_IMAGE(thumb_last),
+ GDK_PAINTABLE(args->thumb));
+
+ gtk_spinner_stop(GTK_SPINNER(process_spinner));
+ gtk_stack_set_visible_child(GTK_STACK(open_last_stack), thumb_last);
+
+ g_object_unref(args->thumb);
+ g_free(args->fname);
+
+ return false;
+}
+
+void
+mp_main_capture_completed(GdkTexture *thumb, const char *fname)
+{
+ struct capture_completed_args *args = malloc(sizeof(struct capture_completed_args));
+ args->thumb = thumb;
+ args->fname = g_strdup(fname);
+ g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
+ (GSourceFunc)capture_completed, args, free);
+}
+
+static GLuint blit_program;
+static GLuint blit_uniform_transform;
+static GLuint blit_uniform_texture;
+static GLuint solid_program;
+static GLuint solid_uniform_color;
+static GLuint quad;
+
+static void
+preview_realize(GtkGLArea *area)
+{
+ gtk_gl_area_make_current(area);
+
+ if (gtk_gl_area_get_error(area) != NULL) {
+ return;
+ }
+
+ // Make a VAO for OpenGL
+ if (!gtk_gl_area_get_use_es(area)) {
+ GLuint vao;
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+ check_gl();
+ }
+
+ GLuint blit_shaders[] = {
+ gl_util_load_shader("/org/postmarketos/Megapixels/blit.vert", GL_VERTEX_SHADER, NULL, 0),
+ gl_util_load_shader("/org/postmarketos/Megapixels/blit.frag", GL_FRAGMENT_SHADER, NULL, 0),
+ };
+
+ blit_program = gl_util_link_program(blit_shaders, 2);
+ glBindAttribLocation(blit_program, GL_UTIL_VERTEX_ATTRIBUTE, "vert");
+ glBindAttribLocation(blit_program, GL_UTIL_TEX_COORD_ATTRIBUTE, "tex_coord");
+ check_gl();
+
+ blit_uniform_transform = glGetUniformLocation(blit_program, "transform");
+ blit_uniform_texture = glGetUniformLocation(blit_program, "texture");
+
+ GLuint solid_shaders[] = {
+ gl_util_load_shader("/org/postmarketos/Megapixels/solid.vert", GL_VERTEX_SHADER, NULL, 0),
+ gl_util_load_shader("/org/postmarketos/Megapixels/solid.frag", GL_FRAGMENT_SHADER, NULL, 0),
+ };
+
+ solid_program = gl_util_link_program(solid_shaders, 2);
+ glBindAttribLocation(solid_program, GL_UTIL_VERTEX_ATTRIBUTE, "vert");
+ check_gl();
+
+ solid_uniform_color = glGetUniformLocation(solid_program, "color");
+
+ quad = gl_util_new_quad();
+}
+
+static void
+position_preview(float *offset_x, float *offset_y, float *size_x, float *size_y)
+{
+ int buffer_width, buffer_height;
+ if (device_rotation == 0 || device_rotation == 180) {
+ buffer_width = preview_buffer_width;
+ buffer_height = preview_buffer_height;
+ } else {
+ buffer_width = preview_buffer_height;
+ buffer_height = preview_buffer_width;
+ }
+
+ int scale_factor = gtk_widget_get_scale_factor(preview);
+ int top_height = gtk_widget_get_allocated_height(preview_top_box) * scale_factor;
+ int bottom_height = gtk_widget_get_allocated_height(preview_bottom_box) * scale_factor;
+ int inner_height = preview_height - top_height - bottom_height;
+
+ double scale = MIN(preview_width / (float) buffer_width, preview_height / (float) buffer_height);
+
+ *size_x = scale * buffer_width;
+ *size_y = scale * buffer_height;
+
+ *offset_x = (preview_width - *size_x) / 2.0;
+
+ if (*size_y > inner_height) {
+ *offset_y = (preview_height - *size_y) / 2.0;
+ } else {
+ *offset_y = top_height + (inner_height - *size_y) / 2.0;
+ }
+
+}
+
+static gboolean
+preview_draw(GtkGLArea *area, GdkGLContext *ctx, gpointer data)
+{
+ if (gtk_gl_area_get_error(area) != NULL) {
+ return FALSE;
+ }
+
+ if (!camera_is_initialized) {
+ return FALSE;
+ }
+
+#ifdef RENDERDOC
+ if (rdoc_api) rdoc_api->StartFrameCapture(NULL, NULL);
+#endif
+
+ glClearColor(0, 0, 0, 1);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ float offset_x, offset_y, size_x, size_y;
+ position_preview(&offset_x, &offset_y, &size_x, &size_y);
+ glViewport(offset_x,
+ preview_height - size_y - offset_y,
+ size_x,
+ size_y);
+
+ if (current_preview_buffer) {
+ glUseProgram(blit_program);
+
+ GLfloat rotation_list[4] = { 0, -1, 0, 1 };
+ int rotation_index = device_rotation / 90;
+
+ GLfloat sin_rot = rotation_list[rotation_index];
+ GLfloat cos_rot = rotation_list[(4 + rotation_index - 1) % 4];
+ GLfloat matrix[9] = {
+ cos_rot, sin_rot, 0,
+ -sin_rot, cos_rot, 0,
+ 0, 0, 1,
+ };
+ glUniformMatrix3fv(blit_uniform_transform, 1, GL_FALSE, matrix);
+ check_gl();
+
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, mp_process_pipeline_buffer_get_texture_id(current_preview_buffer));
+ glUniform1i(blit_uniform_texture, 0);
+ check_gl();
+
+ gl_util_bind_quad(quad);
+ gl_util_draw_quad(quad);
+ }
+
+ if (zbar_result) {
+ GLuint buffer;
+ if (!gtk_gl_area_get_use_es(area)) {
+ glGenBuffers(1, &buffer);
+ glBindBuffer(GL_ARRAY_BUFFER, buffer);
+ check_gl();
+ }
+
+ glUseProgram(solid_program);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ glUniform4f(solid_uniform_color, 1, 0, 0, 0.5);
+
+ for (uint8_t i = 0; i < zbar_result->size; ++i) {
+ MPZBarCode *code = &zbar_result->codes[i];
+
+ GLfloat vertices[] = {
+ code->bounds_x[0], code->bounds_y[0],
+ code->bounds_x[1], code->bounds_y[1],
+ code->bounds_x[3], code->bounds_y[3],
+ code->bounds_x[2], code->bounds_y[2],
+ };
+
+ for (int i = 0; i < 4; ++i) {
+ vertices[i * 2] = 2 * vertices[i * 2] / preview_buffer_width - 1.0;
+ vertices[i * 2 + 1] = 1.0 - 2 * vertices[i * 2 + 1] / preview_buffer_height;
+ }
+
+ if (gtk_gl_area_get_use_es(area)) {
+ glVertexAttribPointer(GL_UTIL_VERTEX_ATTRIBUTE, 2, GL_FLOAT, 0, 0, vertices);
+ check_gl();
+ glEnableVertexAttribArray(GL_UTIL_VERTEX_ATTRIBUTE);
+ check_gl();
+ } else {
+ glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STREAM_DRAW);
+ check_gl();
+
+ glVertexAttribPointer(GL_UTIL_VERTEX_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0);
+ glEnableVertexAttribArray(GL_UTIL_VERTEX_ATTRIBUTE);
+ check_gl();
+ }
+
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+ check_gl();
+ }
+
+ glDisable(GL_BLEND);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ }
+
+ glFlush();
+
+#ifdef RENDERDOC
+ if(rdoc_api) rdoc_api->EndFrameCapture(NULL, NULL);
+#endif
+
+ return FALSE;
+}
+
+static gboolean
+preview_resize(GtkWidget *widget, int width, int height, gpointer data)
+{
+ if (preview_width != width || preview_height != height) {
+ preview_width = width;
+ preview_height = height;
+ update_io_pipeline();
+ }
+
+ return TRUE;
+}
+
+void
+run_open_last_action(GSimpleAction *action, GVariant *param, gpointer user_data)
+{
+ char uri[275];
+ GError *error = NULL;
+
+ if (strlen(last_path) == 0) {
+ return;
+ }
+ sprintf(uri, "file://%s", last_path);
+ if (!g_app_info_launch_default_for_uri(uri, NULL, &error)) {
+ g_printerr("Could not launch image viewer for '%s': %s\n", uri, error->message);
+ }
+}
+
+void
+run_open_photos_action(GSimpleAction *action, GVariant *param, gpointer user_data)
+{
+ char uri[270];
+ GError *error = NULL;
+ sprintf(uri, "file://%s", g_get_user_special_dir(G_USER_DIRECTORY_PICTURES));
+ if (!g_app_info_launch_default_for_uri(uri, NULL, &error)) {
+ g_printerr("Could not launch image viewer: %s\n", error->message);
+ }
+}
+
+void
+run_capture_action(GSimpleAction *action, GVariant *param, gpointer user_data)
+{
+ gtk_spinner_start(GTK_SPINNER(process_spinner));
+ gtk_stack_set_visible_child(GTK_STACK(open_last_stack), process_spinner);
+ mp_io_pipeline_capture();
+}
+
+void
+run_quit_action(GSimpleAction *action, GVariant *param, GApplication *app)
+{
+ g_application_quit(app);
+}
+
+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_dialog_response(GtkDialog *dialog, int response, char *data)
+{
+ GError *error = NULL;
+ switch (response) {
+ case GTK_RESPONSE_YES:
+ if (!g_app_info_launch_default_for_uri(data,
+ NULL, &error)) {
+ g_printerr("Could not launch application: %s\n",
+ error->message);
+ }
+ case GTK_RESPONSE_ACCEPT:
+ {
+ GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(dialog));
+ gdk_clipboard_set_text(
+ gdk_display_get_clipboard(display),
+ data);
+ }
+ case GTK_RESPONSE_CANCEL:
+ break;
+ default:
+ g_printerr("Wrong dialog response: %d\n", response);
+ }
+
+ g_free(data);
+ gtk_window_destroy(GTK_WINDOW(dialog));
+}
+
+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 = g_uri_is_valid(
+ code->data, G_URI_FLAGS_PARSE_RELAXED, NULL);
+
+ char* data = strdup(code->data);
+
+ if (data_is_url) {
+ dialog = gtk_message_dialog_new(
+ GTK_WINDOW(gtk_widget_get_root(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_root(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);
+
+ g_signal_connect(dialog, "response", G_CALLBACK(on_zbar_dialog_response), data);
+
+ gtk_widget_show(GTK_WIDGET(dialog));
+}
+
+static void
+preview_pressed(GtkGestureClick *gesture, int n_press, double x, double y)
+{
+ GtkWidget *widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture));
+ int scale_factor = gtk_widget_get_scale_factor(widget);
+
+ // Tapped zbar result
+ if (zbar_result) {
+ // Transform the event coordinates to the image
+ float offset_x, offset_y, size_x, size_y;
+ position_preview(&offset_x, &offset_y, &size_x, &size_y);
+
+ int zbar_x = (x - offset_x) * scale_factor / size_x * preview_buffer_width;
+ int zbar_y = (y - offset_y) * scale_factor / size_y * preview_buffer_height;
+
+ for (uint8_t i = 0; i < zbar_result->size; ++i) {
+ MPZBarCode *code = &zbar_result->codes[i];
+
+ if (check_point_inside_bounds(zbar_x, zbar_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();
+ }
+}
+
+static void
+run_camera_switch_action(GSimpleAction *action, GVariant *param, gpointer user_data)
+{
+ size_t next_index = camera->index + 1;
+ const struct mp_camera_config *next_camera =
+ mp_get_camera_config(next_index);
+
+ if (!next_camera) {
+ next_index = 0;
+ next_camera = mp_get_camera_config(next_index);
+ }
+
+ camera = next_camera;
+ update_io_pipeline();
+}
+
+static void
+run_open_settings_action(GSimpleAction *action, GVariant *param, gpointer user_data)
+{
+ gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "settings");
+}
+
+static void
+run_close_settings_action(GSimpleAction *action, GVariant *param, gpointer user_data)
+{
+ gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "main");
+}
+
+static void
+on_controls_scale_changed(GtkAdjustment *adjustment, void (*set_fn)(double))
+{
+ set_fn(gtk_adjustment_get_value(adjustment));
+}
+
+static void
+update_value(GtkAdjustment *adjustment, GtkLabel *label)
+{
+ char buf[12];
+ snprintf(buf, 12, "%.0f", gtk_adjustment_get_value(adjustment));
+ gtk_label_set_label(label, buf);
+}
+
+static void
+on_auto_controls_toggled(GtkToggleButton *button, void (*set_auto_fn)(bool))
+{
+ set_auto_fn(gtk_toggle_button_get_active(button));
+}
+
+static void
+update_scale(GtkToggleButton *button, GtkScale *scale)
+{
+ gtk_widget_set_sensitive(GTK_WIDGET(scale), !gtk_toggle_button_get_active(button));
+}
+
+static void
+open_controls(GtkWidget *parent, const char *title_name,
+ double min_value, double max_value, double current,
+ bool auto_enabled,
+ void (*set_fn)(double),
+ void (*set_auto_fn)(bool))
+{
+ GtkBuilder *builder = gtk_builder_new_from_resource(
+ "/org/postmarketos/Megapixels/controls-popover.ui");
+ GtkPopover *popover = GTK_POPOVER(gtk_builder_get_object(builder, "controls"));
+ GtkScale *scale = GTK_SCALE(gtk_builder_get_object(builder, "scale"));
+ GtkLabel *title = GTK_LABEL(gtk_builder_get_object(builder, "title"));
+ GtkLabel *value_label = GTK_LABEL(gtk_builder_get_object(builder, "value-label"));
+ GtkToggleButton *auto_button = GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "auto-button"));
+
+ gtk_label_set_label(title, title_name);
+
+ GtkAdjustment *adjustment = gtk_range_get_adjustment(GTK_RANGE(scale));
+ gtk_adjustment_set_lower(adjustment, min_value);
+ gtk_adjustment_set_upper(adjustment, max_value);
+ gtk_adjustment_set_value(adjustment, current);
+ update_value(adjustment, value_label);
+
+ gtk_toggle_button_set_active(auto_button, auto_enabled);
+ update_scale(auto_button, scale);
+
+ g_signal_connect(adjustment, "value-changed", G_CALLBACK(on_controls_scale_changed), set_fn);
+ g_signal_connect(adjustment, "value-changed", G_CALLBACK(update_value), value_label);
+ g_signal_connect(auto_button, "toggled", G_CALLBACK(on_auto_controls_toggled), set_auto_fn);
+ g_signal_connect(auto_button, "toggled", G_CALLBACK(update_scale), scale);
+
+ gtk_widget_set_parent(GTK_WIDGET(popover), parent);
+ gtk_popover_popup(popover);
+ // g_object_unref(popover);
+}
+
+static void
+set_gain(double value)
+{
+ if (gain != (int)value) {
+ gain = value;
+ update_io_pipeline();
+ }
+}
+
+static void
+set_gain_auto(bool is_auto)
+{
+ if (gain_is_manual != !is_auto) {
+ gain_is_manual = !is_auto;
+ update_io_pipeline();
+ }
+}
+
+static void
+open_iso_controls(GtkWidget *button, gpointer user_data)
+{
+ open_controls(button, "ISO", 0, gain_max, gain, !gain_is_manual, set_gain, set_gain_auto);
+}
+
+static void
+set_shutter(double value)
+{
+ int new_exposure =
+ (int)(value / 360.0 * camera->capture_mode.height);
+ if (new_exposure != exposure) {
+ exposure = new_exposure;
+ update_io_pipeline();
+ }
+}
+
+static void
+set_shutter_auto(bool is_auto)
+{
+ if (exposure_is_manual != !is_auto) {
+ exposure_is_manual = !is_auto;
+ update_io_pipeline();
+ }
+}
+
+static void
+open_shutter_controls(GtkWidget *button, gpointer user_data)
+{
+ open_controls(button, "Shutter", 1.0, 360.0, exposure, !exposure_is_manual, set_shutter, set_shutter_auto);
+}
+
+static void
+on_realize(GtkWidget *window, gpointer *data)
+{
+ GtkNative *native = gtk_widget_get_native(window);
+ mp_process_pipeline_init_gl(gtk_native_get_surface(native));
+
+ camera = mp_get_camera_config(0);
+ update_io_pipeline();
+}
+
+static GSimpleAction *
+create_simple_action(GtkApplication *app, const char *name, GCallback callback)
+{
+ GSimpleAction *action = g_simple_action_new(name, NULL);
+ g_signal_connect(action, "activate", callback, app);
+ g_action_map_add_action(G_ACTION_MAP(app), G_ACTION(action));
+ return action;
+}
+
+static void update_ui_rotation()
+{
+ if (device_rotation == 0 || device_rotation == 180) {
+ // Portrait
+ gtk_widget_set_halign(preview_top_box, GTK_ALIGN_FILL);
+ gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_top_box), GTK_ORIENTATION_VERTICAL);
+
+ gtk_widget_set_halign(preview_bottom_box, GTK_ALIGN_FILL);
+ gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_bottom_box), GTK_ORIENTATION_HORIZONTAL);
+
+ if (device_rotation == 0) {
+ gtk_widget_set_valign(preview_top_box, GTK_ALIGN_START);
+ gtk_widget_set_valign(preview_bottom_box, GTK_ALIGN_END);
+ } else {
+ gtk_widget_set_valign(preview_top_box, GTK_ALIGN_END);
+ gtk_widget_set_valign(preview_bottom_box, GTK_ALIGN_START);
+ }
+ } else {
+ // Landscape
+ gtk_widget_set_valign(preview_top_box, GTK_ALIGN_FILL);
+ gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_top_box), GTK_ORIENTATION_HORIZONTAL);
+
+ gtk_widget_set_valign(preview_bottom_box, GTK_ALIGN_FILL);
+ gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_bottom_box), GTK_ORIENTATION_VERTICAL);
+
+ if (device_rotation == 90) {
+ gtk_widget_set_halign(preview_top_box, GTK_ALIGN_END);
+ gtk_widget_set_halign(preview_bottom_box, GTK_ALIGN_START);
+ } else {
+ gtk_widget_set_halign(preview_top_box, GTK_ALIGN_START);
+ gtk_widget_set_halign(preview_bottom_box, GTK_ALIGN_END);
+ }
+ }
+}
+
+static void display_config_received(GDBusConnection *conn, GAsyncResult *res, gpointer user_data)
+{
+ GError *error = NULL;
+ GVariant *result = g_dbus_connection_call_finish(conn, res, &error);
+
+ if (!result) {
+ printf("Failed to get display configuration: %s\n", error->message);
+ return;
+ }
+
+ GVariant *configs = g_variant_get_child_value(result, 1);
+ if (g_variant_n_children(configs) == 0) {
+ return;
+ }
+
+ GVariant *config = g_variant_get_child_value(configs, 0);
+ GVariant *rot_config = g_variant_get_child_value(config, 7);
+ uint32_t rotation_index = g_variant_get_uint32(rot_config);
+
+ assert(rotation_index < 4);
+ int new_rotation = rotation_index * 90;
+
+ if (new_rotation != device_rotation) {
+ device_rotation = new_rotation;
+ update_io_pipeline();
+ update_ui_rotation();
+ }
+}
+
+static void update_screen_rotation(GDBusConnection *conn)
+{
+ g_dbus_connection_call(conn,
+ "org.gnome.Mutter.DisplayConfig",
+ "/org/gnome/Mutter/DisplayConfig",
+ "org.gnome.Mutter.DisplayConfig",
+ "GetResources",
+ NULL,
+ NULL,
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ -1,
+ NULL,
+ (GAsyncReadyCallback)display_config_received,
+ NULL);
+}
+
+static void on_screen_rotate(
+ GDBusConnection *conn,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ update_screen_rotation(conn);
+}
+
+static void
+activate(GtkApplication *app, gpointer data)
+{
+ g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme",
+ TRUE, NULL);
+
+ GdkDisplay *display = gdk_display_get_default();
+ GtkIconTheme *icon_theme = gtk_icon_theme_get_for_display(display);
+ gtk_icon_theme_add_resource_path(icon_theme, "/org/postmarketos/Megapixels");
+
+ GtkCssProvider *provider = gtk_css_provider_new();
+ gtk_css_provider_load_from_resource(
+ provider, "/org/postmarketos/Megapixels/camera.css");
+ gtk_style_context_add_provider_for_display(
+ display, GTK_STYLE_PROVIDER(provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+ GtkBuilder *builder = gtk_builder_new_from_resource(
+ "/org/postmarketos/Megapixels/camera.ui");
+
+ GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "window"));
+ GtkWidget *iso_button = GTK_WIDGET(gtk_builder_get_object(builder, "iso-controls-button"));
+ GtkWidget *shutter_button = GTK_WIDGET(gtk_builder_get_object(builder, "shutter-controls-button"));
+ preview = GTK_WIDGET(gtk_builder_get_object(builder, "preview"));
+ main_stack = GTK_WIDGET(gtk_builder_get_object(builder, "main_stack"));
+ open_last_stack = GTK_WIDGET(gtk_builder_get_object(builder, "open_last_stack"));
+ thumb_last = GTK_WIDGET(gtk_builder_get_object(builder, "thumb_last"));
+ process_spinner = GTK_WIDGET(gtk_builder_get_object(builder, "process_spinner"));
+ scanned_codes = GTK_WIDGET(gtk_builder_get_object(builder, "scanned-codes"));
+ preview_top_box = GTK_WIDGET(gtk_builder_get_object(builder, "top-box"));
+ preview_bottom_box = GTK_WIDGET(gtk_builder_get_object(builder, "bottom-box"));
+
+ g_signal_connect(window, "realize", G_CALLBACK(on_realize), NULL);
+
+ g_signal_connect(preview, "realize", G_CALLBACK(preview_realize), NULL);
+ g_signal_connect(preview, "render", G_CALLBACK(preview_draw), NULL);
+ g_signal_connect(preview, "resize", G_CALLBACK(preview_resize), NULL);
+ GtkGesture *click = gtk_gesture_click_new();
+ g_signal_connect(click, "pressed", G_CALLBACK(preview_pressed), NULL);
+ gtk_widget_add_controller(preview, GTK_EVENT_CONTROLLER(click));
+
+ g_signal_connect(iso_button, "clicked", G_CALLBACK(open_iso_controls), NULL);
+ g_signal_connect(shutter_button, "clicked", G_CALLBACK(open_shutter_controls), NULL);
+
+ // Setup actions
+ create_simple_action(app, "capture", G_CALLBACK(run_capture_action));
+ create_simple_action(app, "switch-camera", G_CALLBACK(run_camera_switch_action));
+ create_simple_action(app, "open-settings", G_CALLBACK(run_open_settings_action));
+ create_simple_action(app, "close-settings", G_CALLBACK(run_close_settings_action));
+ create_simple_action(app, "open-last", G_CALLBACK(run_open_last_action));
+ create_simple_action(app, "open-photos", G_CALLBACK(run_open_photos_action));
+ create_simple_action(app, "quit", G_CALLBACK(run_quit_action));
+
+ // Setup shortcuts
+ const char *capture_accels[] = { "space", NULL };
+ gtk_application_set_accels_for_action(app, "app.capture", capture_accels);
+
+ const char *quit_accels[] = { "q", "w", NULL };
+ gtk_application_set_accels_for_action(app, "app.quit", quit_accels);
+
+ // Listen for phosh rotation
+ GDBusConnection *conn = g_application_get_dbus_connection(G_APPLICATION(app));
+ g_dbus_connection_signal_subscribe(
+ conn,
+ NULL,
+ "org.gnome.Mutter.DisplayConfig",
+ "MonitorsChanged",
+ "/org/gnome/Mutter/DisplayConfig",
+ NULL,
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ &on_screen_rotate,
+ NULL,
+ NULL);
+ update_screen_rotation(conn);
+
+ mp_io_pipeline_start();
+
+ gtk_application_add_window(app, GTK_WINDOW(window));
+ gtk_widget_show(window);
+}
+
+static void
+shutdown(GApplication *app, gpointer data)
+{
+ // Only do cleanup in development, let the OS clean up otherwise
+#ifdef DEBUG
+ mp_io_pipeline_stop();
+#endif
+}
+
+int
+main(int argc, char *argv[])
+{
+#ifdef RENDERDOC
+ {
+ void *mod = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD);
+ if (mod)
+ {
+ pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)dlsym(mod, "RENDERDOC_GetAPI");
+ int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_1_2, (void **)&rdoc_api);
+ assert(ret == 1);
+ }
+ else
+ {
+ printf("Renderdoc not found\n");
+ }
+ }
+#endif
+
+ if (!mp_load_config())
+ return 1;
+
+ setenv("LC_NUMERIC", "C", 1);
+
+ GtkApplication *app = gtk_application_new("org.postmarketos.Megapixels", 0);
+
+ g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
+ g_signal_connect(app, "shutdown", G_CALLBACK(shutdown), NULL);
+
+ g_application_run(G_APPLICATION(app), argc, argv);
+
+ return 0;
+}
diff --git a/main.h b/src/main.h
similarity index 65%
rename from main.h
rename to src/main.h
index 2995993..bbebca9 100644
--- a/main.h
+++ b/src/main.h
@@ -2,14 +2,16 @@
#include "camera_config.h"
#include "zbar_pipeline.h"
+#include "process_pipeline.h"
#include "gtk/gtk.h"
-#define MP_MAIN_THUMB_SIZE 24
-
struct mp_main_state {
const struct mp_camera_config *camera;
MPCameraMode mode;
+ int image_width;
+ int image_height;
+
bool gain_is_manual;
int gain;
int gain_max;
@@ -23,12 +25,9 @@ struct mp_main_state {
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_preview(MPProcessPipelineBuffer *buffer);
+void mp_main_capture_completed(GdkTexture *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,
- cairo_surface_t *surface);
diff --git a/matrix.c b/src/matrix.c
similarity index 100%
rename from matrix.c
rename to src/matrix.c
diff --git a/matrix.h b/src/matrix.h
similarity index 100%
rename from matrix.h
rename to src/matrix.h
diff --git a/pipeline.c b/src/pipeline.c
similarity index 79%
rename from pipeline.c
rename to src/pipeline.c
index fcfa4c2..80715f0 100644
--- a/pipeline.c
+++ b/src/pipeline.c
@@ -65,6 +65,27 @@ mp_pipeline_invoke(MPPipeline *pipeline, MPPipelineCallback callback,
}
}
+static bool
+unlock_mutex(GMutex *mutex)
+{
+ g_mutex_unlock(mutex);
+ return false;
+}
+
+void
+mp_pipeline_sync(MPPipeline *pipeline)
+{
+ GMutex mutex;
+ g_mutex_init(&mutex);
+ g_mutex_lock(&mutex);
+
+ g_main_context_invoke_full(pipeline->main_context, G_PRIORITY_LOW, (GSourceFunc)unlock_mutex, &mutex, NULL);
+ g_mutex_lock(&mutex);
+ g_mutex_unlock(&mutex);
+
+ g_mutex_clear(&mutex);
+}
+
void
mp_pipeline_free(MPPipeline *pipeline)
{
@@ -80,21 +101,24 @@ mp_pipeline_free(MPPipeline *pipeline)
struct capture_source_args {
MPCamera *camera;
- void (*callback)(MPImage, void *);
+ void (*callback)(MPBuffer, void *);
void *user_data;
};
static bool
on_capture(int fd, GIOCondition condition, struct capture_source_args *args)
{
- mp_camera_capture_image(args->camera, args->callback, args->user_data);
+ MPBuffer buffer;
+ if (mp_camera_capture_buffer(args->camera, &buffer)) {
+ args->callback(buffer, args->user_data);
+ }
return true;
}
// Not thread safe
GSource *
mp_pipeline_add_capture_source(MPPipeline *pipeline, MPCamera *camera,
- void (*callback)(MPImage, void *), void *user_data)
+ void (*callback)(MPBuffer, void *), void *user_data)
{
int video_fd = mp_camera_get_video_fd(camera);
GSource *video_source = g_unix_fd_source_new(video_fd, G_IO_IN);
diff --git a/pipeline.h b/src/pipeline.h
similarity index 77%
rename from pipeline.h
rename to src/pipeline.h
index 6f71faa..67eaeff 100644
--- a/pipeline.h
+++ b/src/pipeline.h
@@ -11,8 +11,10 @@ typedef void (*MPPipelineCallback)(MPPipeline *, const void *);
MPPipeline *mp_pipeline_new();
void mp_pipeline_invoke(MPPipeline *pipeline, MPPipelineCallback callback,
const void *data, size_t size);
+// Wait until all pending tasks have completed
+void mp_pipeline_sync(MPPipeline *pipeline);
void mp_pipeline_free(MPPipeline *pipeline);
GSource *mp_pipeline_add_capture_source(MPPipeline *pipeline, MPCamera *camera,
- void (*callback)(MPImage, void *),
+ void (*callback)(MPBuffer, void *),
void *user_data);
diff --git a/process_pipeline.c b/src/process_pipeline.c
similarity index 55%
rename from process_pipeline.c
rename to src/process_pipeline.c
index 64003a2..dd5e4f4 100644
--- a/process_pipeline.c
+++ b/src/process_pipeline.c
@@ -2,15 +2,18 @@
#include "pipeline.h"
#include "zbar_pipeline.h"
+#include "io_pipeline.h"
#include "main.h"
#include "config.h"
-#include "quickpreview.h"
+#include "gles2_debayer.h"
#include
#include
#include
-#include
#include
+#include "gl_util.h"
+#include
+
#define TIFFTAG_FORWARDMATRIX1 50964
static const float colormatrix_srgb[] = { 3.2409, -1.5373, -0.4986, -0.9692, 1.8759,
@@ -26,6 +29,7 @@ static volatile int frames_processed = 0;
static volatile int frames_received = 0;
static const struct mp_camera_config *camera;
+static int camera_rotation;
static MPCameraMode mode;
@@ -35,6 +39,11 @@ static int captures_remaining = 0;
static int preview_width;
static int preview_height;
+static int device_rotation;
+
+static int output_buffer_width = -1;
+static int output_buffer_height = -1;
+
// static bool gain_is_manual;
static int gain;
static int gain_max;
@@ -60,28 +69,18 @@ register_custom_tiff_tags(TIFF *tif)
static bool
find_processor(char *script)
{
- char *xdg_config_home;
char filename[] = "postprocess.sh";
- wordexp_t exp_result;
- // Resolve XDG stuff
- if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) {
- xdg_config_home = "~/.config";
- }
- wordexp(xdg_config_home, &exp_result, 0);
- xdg_config_home = strdup(exp_result.we_wordv[0]);
- wordfree(&exp_result);
-
- // Check postprocess.h in the current working directory
- sprintf(script, "%s", filename);
+ // Check postprocess.sh in the current working directory
+ sprintf(script, "./data/%s", filename);
if (access(script, F_OK) != -1) {
- sprintf(script, "./%s", filename);
+ sprintf(script, "./data/%s", filename);
printf("Found postprocessor script at %s\n", script);
return true;
}
// Check for a script in XDG_CONFIG_HOME
- sprintf(script, "%s/megapixels/%s", xdg_config_home, filename);
+ sprintf(script, "%s/megapixels/%s", g_get_user_config_dir(), filename);
if (access(script, F_OK) != -1) {
printf("Found postprocessor script at %s\n", script);
return true;
@@ -134,47 +133,208 @@ mp_process_pipeline_stop()
mp_zbar_pipeline_stop();
}
-static cairo_surface_t *
-process_image_for_preview(const MPImage *image)
+void
+mp_process_pipeline_sync()
{
- uint32_t surface_width, surface_height, skip;
- quick_preview_size(&surface_width, &surface_height, &skip, preview_width,
- preview_height, image->width, image->height,
- image->pixel_format, camera->rotate);
+ mp_pipeline_sync(pipeline);
+}
- cairo_surface_t *surface = cairo_image_surface_create(
- CAIRO_FORMAT_RGB24, surface_width, surface_height);
+#define NUM_BUFFERS 4
- uint8_t *pixels = cairo_image_surface_get_data(surface);
+struct _MPProcessPipelineBuffer {
+ GLuint texture_id;
- quick_preview((uint32_t *)pixels, surface_width, surface_height, image->data,
- image->width, image->height, image->pixel_format,
- camera->rotate, camera->mirrored,
- camera->previewmatrix[0] == 0 ? NULL : camera->previewmatrix,
- camera->blacklevel, skip);
+ _Atomic(int) refcount;
+};
+static MPProcessPipelineBuffer output_buffers[NUM_BUFFERS];
- // Create a thumbnail from the preview for the last capture
- cairo_surface_t *thumb = NULL;
- if (captures_remaining == 1) {
- printf("Making thumbnail\n");
- thumb = cairo_image_surface_create(
- CAIRO_FORMAT_ARGB32, MP_MAIN_THUMB_SIZE, MP_MAIN_THUMB_SIZE);
+void
+mp_process_pipeline_buffer_ref(MPProcessPipelineBuffer *buf)
+{
+ ++buf->refcount;
+}
- cairo_t *cr = cairo_create(thumb);
- draw_surface_scaled_centered(
- cr, MP_MAIN_THUMB_SIZE, MP_MAIN_THUMB_SIZE, surface);
- cairo_destroy(cr);
+void
+mp_process_pipeline_buffer_unref(MPProcessPipelineBuffer *buf)
+{
+ --buf->refcount;
+}
+
+uint32_t
+mp_process_pipeline_buffer_get_texture_id(MPProcessPipelineBuffer *buf)
+{
+ return buf->texture_id;
+}
+
+static GLES2Debayer *gles2_debayer = NULL;
+
+static GdkGLContext *context;
+
+#define RENDERDOC
+
+#ifdef RENDERDOC
+#include
+extern RENDERDOC_API_1_1_2 *rdoc_api;
+#endif
+
+static void
+init_gl(MPPipeline *pipeline, GdkSurface **surface)
+{
+ GError *error = NULL;
+ context = gdk_surface_create_gl_context(*surface, &error);
+ if (context == NULL) {
+ printf("Failed to initialize OpenGL context: %s\n", error->message);
+ g_clear_error(&error);
+ return;
}
- // Pass processed preview to main and zbar
- mp_zbar_pipeline_process_image(cairo_surface_reference(surface));
- mp_main_set_preview(surface);
+ gdk_gl_context_set_use_es(context, true);
+ gdk_gl_context_set_required_version(context, 2, 0);
+ gdk_gl_context_set_forward_compatible(context, false);
+#ifdef DEBUG
+ gdk_gl_context_set_debug_enabled(context, true);
+#else
+ gdk_gl_context_set_debug_enabled(context, false);
+#endif
+
+ gdk_gl_context_realize(context, &error);
+ if (error != NULL) {
+ printf("Failed to create OpenGL context: %s\n", error->message);
+ g_clear_object(&context);
+ g_clear_error(&error);
+ return;
+ }
+
+ gdk_gl_context_make_current(context);
+ check_gl();
+
+ // Make a VAO for OpenGL
+ if (!gdk_gl_context_get_use_es(context)) {
+ GLuint vao;
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+ check_gl();
+ }
+
+ gles2_debayer = gles2_debayer_new(MP_PIXEL_FMT_BGGR8);
+ check_gl();
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ check_gl();
+
+ gles2_debayer_use(gles2_debayer);
+
+ for (size_t i = 0; i < NUM_BUFFERS; ++i) {
+ glGenTextures(1, &output_buffers[i].texture_id);
+ glBindTexture(GL_TEXTURE_2D, output_buffers[i].texture_id);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ }
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ gboolean is_es = gdk_gl_context_get_use_es(context);
+ int major, minor;
+ gdk_gl_context_get_version(context, &major, &minor);
+
+ printf("Initialized %s %d.%d\n", is_es ? "OpenGL ES" : "OpenGL", major, minor);
+}
+
+void
+mp_process_pipeline_init_gl(GdkSurface *surface)
+{
+ mp_pipeline_invoke(pipeline, (MPPipelineCallback) init_gl, &surface, sizeof(GdkSurface *));
+}
+
+static GdkTexture *
+process_image_for_preview(const uint8_t *image)
+{
+#ifdef PROFILE_DEBAYER
+ clock_t t1 = clock();
+#endif
+
+ // Pick an available buffer
+ MPProcessPipelineBuffer *output_buffer = NULL;
+ for (size_t i = 0; i < NUM_BUFFERS; ++i) {
+ if (output_buffers[i].refcount == 0) {
+ output_buffer = &output_buffers[i];
+ }
+ }
+
+ if (output_buffer == NULL) {
+ return NULL;
+ }
+ assert(output_buffer != NULL);
+
+#ifdef RENDERDOC
+ if (rdoc_api) rdoc_api->StartFrameCapture(NULL, NULL);
+#endif
+
+ // Copy image to a GL texture. TODO: This can be avoided
+ GLuint input_texture;
+ glGenTextures(1, &input_texture);
+ glBindTexture(GL_TEXTURE_2D, input_texture);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, mode.width, mode.height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image);
+ check_gl();
+
+ gles2_debayer_process(
+ gles2_debayer, output_buffer->texture_id, input_texture);
+ check_gl();
+
+ glFinish();
+
+ glDeleteTextures(1, &input_texture);
+
+#ifdef PROFILE_DEBAYER
+ clock_t t2 = clock();
+ printf("process_image_for_preview %fms\n", (float)(t2 - t1) / CLOCKS_PER_SEC * 1000);
+#endif
+
+#ifdef RENDERDOC
+ if(rdoc_api) rdoc_api->EndFrameCapture(NULL, NULL);
+#endif
+
+ mp_process_pipeline_buffer_ref(output_buffer);
+ mp_main_set_preview(output_buffer);
+
+ // Create a thumbnail from the preview for the last capture
+ GdkTexture *thumb = NULL;
+ if (captures_remaining == 1) {
+ printf("Making thumbnail\n");
+
+ size_t size = output_buffer_width * output_buffer_height * sizeof(uint32_t);
+
+ uint32_t *data = g_malloc_n(size, 1);
+
+ glReadPixels(0, 0, output_buffer_width, output_buffer_height, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ check_gl();
+
+ // Flip vertically
+ for (size_t y = 0; y < output_buffer_height / 2; ++y) {
+ for (size_t x = 0; x < output_buffer_width; ++x) {
+ uint32_t tmp = data[(output_buffer_height - y - 1) * output_buffer_width + x];
+ data[(output_buffer_height - y - 1) * output_buffer_width + x] = data[y * output_buffer_width + x];
+ data[y * output_buffer_width + x] = tmp;
+ }
+ }
+
+ thumb = gdk_memory_texture_new(
+ output_buffer_width,
+ output_buffer_height,
+ GDK_MEMORY_R8G8B8A8,
+ g_bytes_new_take(data, size),
+ output_buffer_width * sizeof(uint32_t));
+ }
return thumb;
}
static void
-process_image_for_capture(const MPImage *image, int count)
+process_image_for_capture(const uint8_t *image, int count)
{
time_t rawtime;
time(&rawtime);
@@ -193,21 +353,21 @@ process_image_for_capture(const MPImage *image, int count)
// Define TIFF thumbnail
TIFFSetField(tif, TIFFTAG_SUBFILETYPE, 1);
- TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, image->width >> 4);
- TIFFSetField(tif, TIFFTAG_IMAGELENGTH, image->height >> 4);
+ TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, mode.width >> 4);
+ TIFFSetField(tif, TIFFTAG_IMAGELENGTH, mode.height >> 4);
TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
TIFFSetField(tif, TIFFTAG_MAKE, mp_get_device_make());
TIFFSetField(tif, TIFFTAG_MODEL, mp_get_device_model());
uint16_t orientation;
- if (camera->rotate == 0) {
+ if (camera_rotation == 0) {
orientation = camera->mirrored ? ORIENTATION_TOPRIGHT :
ORIENTATION_TOPLEFT;
- } else if (camera->rotate == 90) {
+ } else if (camera_rotation == 90) {
orientation = camera->mirrored ? ORIENTATION_RIGHTBOT :
ORIENTATION_LEFTBOT;
- } else if (camera->rotate == 180) {
+ } else if (camera_rotation == 180) {
orientation = camera->mirrored ? ORIENTATION_BOTLEFT :
ORIENTATION_BOTRIGHT;
} else {
@@ -241,8 +401,8 @@ process_image_for_capture(const MPImage *image, int count)
// Write black thumbnail, only windows uses this
{
unsigned char *buf =
- (unsigned char *)calloc(1, (int)image->width >> 4);
- for (int row = 0; row < (image->height >> 4); row++) {
+ (unsigned char *)calloc(1, (int)mode.width >> 4);
+ for (int row = 0; row < (mode.height >> 4); row++) {
TIFFWriteScanline(tif, buf, row, 0);
}
free(buf);
@@ -251,8 +411,8 @@ process_image_for_capture(const MPImage *image, int count)
// Define main photo
TIFFSetField(tif, TIFFTAG_SUBFILETYPE, 0);
- TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, image->width);
- TIFFSetField(tif, TIFFTAG_IMAGELENGTH, image->height);
+ TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, mode.width);
+ TIFFSetField(tif, TIFFTAG_IMAGELENGTH, mode.height);
TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CFA);
TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1);
@@ -274,9 +434,9 @@ process_image_for_capture(const MPImage *image, int count)
TIFFCheckpointDirectory(tif);
printf("Writing frame to %s\n", fname);
- unsigned char *pLine = (unsigned char *)malloc(image->width);
- for (int row = 0; row < image->height; row++) {
- TIFFWriteScanline(tif, image->data + (row * image->width), row, 0);
+ unsigned char *pLine = (unsigned char *)malloc(mode.width);
+ for (int row = 0; row < mode.height; row++) {
+ TIFFWriteScanline(tif, (void *) image + (row * mode.width), row, 0);
}
free(pLine);
TIFFWriteDirectory(tif);
@@ -293,7 +453,7 @@ process_image_for_capture(const MPImage *image, int count)
TIFFSetField(tif, EXIFTAG_EXPOSURETIME,
(mode.frame_interval.numerator /
(float)mode.frame_interval.denominator) /
- ((float)image->height / (float)exposure));
+ ((float)mode.height / (float)exposure));
uint16_t isospeed[1];
isospeed[0] = (uint16_t)remap(gain - 1, 0, gain_max, camera->iso_min,
camera->iso_max);
@@ -325,7 +485,7 @@ process_image_for_capture(const MPImage *image, int count)
}
static void
-post_process_finished(GSubprocess *proc, GAsyncResult *res, cairo_surface_t *thumb)
+post_process_finished(GSubprocess *proc, GAsyncResult *res, GdkTexture *thumb)
{
char *stdout;
g_subprocess_communicate_utf8_finish(proc, res, &stdout, NULL, NULL);
@@ -348,7 +508,7 @@ post_process_finished(GSubprocess *proc, GAsyncResult *res, cairo_surface_t *thu
}
static void
-process_capture_burst(cairo_surface_t *thumb)
+process_capture_burst(GdkTexture *thumb)
{
time_t rawtime;
time(&rawtime);
@@ -401,11 +561,27 @@ process_capture_burst(cairo_surface_t *thumb)
}
static void
-process_image(MPPipeline *pipeline, const MPImage *image)
+process_image(MPPipeline *pipeline, const MPBuffer *buffer)
{
- assert(image->width == mode.width && image->height == mode.height);
+#ifdef PROFILE_PROCESS
+ clock_t t1 = clock();
+#endif
- cairo_surface_t *thumb = process_image_for_preview(image);
+ size_t size =
+ mp_pixel_format_width_to_bytes(mode.pixel_format, mode.width) *
+ mode.height;
+ uint8_t *image = malloc(size);
+ memcpy(image, buffer->data, size);
+ mp_io_pipeline_release_buffer(buffer->index);
+
+ MPZBarImage *zbar_image = mp_zbar_image_new(image, mode.pixel_format, mode.width, mode.height, camera_rotation, camera->mirrored);
+ mp_zbar_pipeline_process_image(mp_zbar_image_ref(zbar_image));
+
+#ifdef PROFILE_PROCESS
+ clock_t t2 = clock();
+#endif
+
+ GdkTexture *thumb = process_image_for_preview(image);
if (captures_remaining > 0) {
int count = burst_length - captures_remaining;
@@ -423,28 +599,35 @@ process_image(MPPipeline *pipeline, const MPImage *image)
assert(!thumb);
}
- free(image->data);
+ mp_zbar_image_unref(zbar_image);
++frames_processed;
if (captures_remaining == 0) {
is_capturing = false;
}
+
+#ifdef PROFILE_PROCESS
+ clock_t t3 = clock();
+ printf("process_image %fms, step 1:%fms, step 2:%fms\n",
+ (float)(t3 - t1) / CLOCKS_PER_SEC * 1000,
+ (float)(t2 - t1) / CLOCKS_PER_SEC * 1000,
+ (float)(t3 - t2) / CLOCKS_PER_SEC * 1000);
+#endif
}
void
-mp_process_pipeline_process_image(MPImage image)
+mp_process_pipeline_process_image(MPBuffer buffer)
{
// If we haven't processed the previous frame yet, drop this one
if (frames_received != frames_processed && !is_capturing) {
- printf("Dropped frame at capture\n");
- free(image.data);
+ mp_io_pipeline_release_buffer(buffer.index);
return;
}
++frames_received;
- mp_pipeline_invoke(pipeline, (MPPipelineCallback)process_image, &image,
- sizeof(MPImage));
+ mp_pipeline_invoke(pipeline, (MPPipelineCallback)process_image, &buffer,
+ sizeof(MPBuffer));
}
static void
@@ -472,17 +655,60 @@ mp_process_pipeline_capture()
mp_pipeline_invoke(pipeline, capture, NULL, 0);
}
+static void
+on_output_changed()
+{
+ output_buffer_width = mode.width / 2;
+ output_buffer_height = mode.height / 2;
+
+ if (camera->rotate != 0 || camera->rotate != 180) {
+ int tmp = output_buffer_width;
+ output_buffer_width = output_buffer_height;
+ output_buffer_height = tmp;
+ }
+
+ for (size_t i = 0; i < NUM_BUFFERS; ++i) {
+ glBindTexture(GL_TEXTURE_2D, output_buffers[i].texture_id);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, output_buffer_width, output_buffer_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+ }
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ gles2_debayer_configure(
+ gles2_debayer,
+ output_buffer_width, output_buffer_height,
+ mode.width, mode.height,
+ camera->rotate, camera->mirrored,
+ camera->previewmatrix[0] == 0 ? NULL : camera->previewmatrix,
+ camera->blacklevel);
+}
+
+static int
+mod(int a, int b)
+{
+ int r = a % b;
+ return r < 0 ? r + b : r;
+}
+
static void
update_state(MPPipeline *pipeline, const struct mp_process_pipeline_state *state)
{
+ const bool output_changed =
+ !mp_camera_mode_is_equivalent(&mode, &state->mode)
+ || preview_width != state->preview_width
+ || preview_height != state->preview_height
+ || device_rotation != state->device_rotation;
+
camera = state->camera;
mode = state->mode;
- burst_length = state->burst_length;
-
preview_width = state->preview_width;
preview_height = state->preview_height;
+ device_rotation = state->device_rotation;
+
+ burst_length = state->burst_length;
+
// gain_is_manual = state->gain_is_manual;
gain = state->gain;
gain_max = state->gain_max;
@@ -490,9 +716,17 @@ update_state(MPPipeline *pipeline, const struct mp_process_pipeline_state *state
exposure_is_manual = state->exposure_is_manual;
exposure = state->exposure;
+ if (output_changed) {
+ camera_rotation = mod(camera->rotate - device_rotation, 360);
+
+ on_output_changed();
+ }
+
struct mp_main_state main_state = {
.camera = camera,
.mode = mode,
+ .image_width = output_buffer_width,
+ .image_height = output_buffer_height,
.gain_is_manual = state->gain_is_manual,
.gain = gain,
.gain_max = gain_max,
@@ -510,3 +744,6 @@ mp_process_pipeline_update_state(const struct mp_process_pipeline_state *new_sta
mp_pipeline_invoke(pipeline, (MPPipelineCallback)update_state, new_state,
sizeof(struct mp_process_pipeline_state));
}
+
+// GTK4 seems to require this
+void pango_fc_font_get_languages() {}
diff --git a/process_pipeline.h b/src/process_pipeline.h
similarity index 52%
rename from process_pipeline.h
rename to src/process_pipeline.h
index 40cadd7..2413d35 100644
--- a/process_pipeline.h
+++ b/src/process_pipeline.h
@@ -2,6 +2,8 @@
#include "camera_config.h"
+typedef struct _GdkSurface GdkSurface;
+
struct mp_process_pipeline_state {
const struct mp_camera_config *camera;
MPCameraMode mode;
@@ -11,6 +13,8 @@ struct mp_process_pipeline_state {
int preview_width;
int preview_height;
+ int device_rotation;
+
bool gain_is_manual;
int gain;
int gain_max;
@@ -24,7 +28,16 @@ struct mp_process_pipeline_state {
void mp_process_pipeline_start();
void mp_process_pipeline_stop();
+void mp_process_pipeline_sync();
-void mp_process_pipeline_process_image(MPImage image);
+void mp_process_pipeline_init_gl(GdkSurface *window);
+
+void mp_process_pipeline_process_image(MPBuffer buffer);
void mp_process_pipeline_capture();
void mp_process_pipeline_update_state(const struct mp_process_pipeline_state *state);
+
+typedef struct _MPProcessPipelineBuffer MPProcessPipelineBuffer;
+
+void mp_process_pipeline_buffer_ref(MPProcessPipelineBuffer *buf);
+void mp_process_pipeline_buffer_unref(MPProcessPipelineBuffer *buf);
+uint32_t mp_process_pipeline_buffer_get_texture_id(MPProcessPipelineBuffer *buf);
diff --git a/src/renderdoc/app.h b/src/renderdoc/app.h
new file mode 100644
index 0000000..4e1fec2
--- /dev/null
+++ b/src/renderdoc/app.h
@@ -0,0 +1,688 @@
+/******************************************************************************
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019-2021 Baldur Karlsson
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ******************************************************************************/
+
+#pragma once
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// Documentation for the API is available at https://renderdoc.org/docs/in_application_api.html
+//
+
+#if !defined(RENDERDOC_NO_STDINT)
+#include
+#endif
+
+#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
+#define RENDERDOC_CC __cdecl
+#elif defined(__linux__)
+#define RENDERDOC_CC
+#elif defined(__APPLE__)
+#define RENDERDOC_CC
+#else
+#error "Unknown platform"
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Constants not used directly in below API
+
+// This is a GUID/magic value used for when applications pass a path where shader debug
+// information can be found to match up with a stripped shader.
+// the define can be used like so: const GUID RENDERDOC_ShaderDebugMagicValue =
+// RENDERDOC_ShaderDebugMagicValue_value
+#define RENDERDOC_ShaderDebugMagicValue_struct \
+ { \
+ 0xeab25520, 0x6670, 0x4865, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
+ }
+
+// as an alternative when you want a byte array (assuming x86 endianness):
+#define RENDERDOC_ShaderDebugMagicValue_bytearray \
+ { \
+ 0x20, 0x55, 0xb2, 0xea, 0x70, 0x66, 0x65, 0x48, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
+ }
+
+// truncated version when only a uint64_t is available (e.g. Vulkan tags):
+#define RENDERDOC_ShaderDebugMagicValue_truncated 0x48656670eab25520ULL
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// RenderDoc capture options
+//
+
+typedef enum RENDERDOC_CaptureOption {
+ // Allow the application to enable vsync
+ //
+ // Default - enabled
+ //
+ // 1 - The application can enable or disable vsync at will
+ // 0 - vsync is force disabled
+ eRENDERDOC_Option_AllowVSync = 0,
+
+ // Allow the application to enable fullscreen
+ //
+ // Default - enabled
+ //
+ // 1 - The application can enable or disable fullscreen at will
+ // 0 - fullscreen is force disabled
+ eRENDERDOC_Option_AllowFullscreen = 1,
+
+ // Record API debugging events and messages
+ //
+ // Default - disabled
+ //
+ // 1 - Enable built-in API debugging features and records the results into
+ // the capture, which is matched up with events on replay
+ // 0 - no API debugging is forcibly enabled
+ eRENDERDOC_Option_APIValidation = 2,
+ eRENDERDOC_Option_DebugDeviceMode = 2, // deprecated name of this enum
+
+ // Capture CPU callstacks for API events
+ //
+ // Default - disabled
+ //
+ // 1 - Enables capturing of callstacks
+ // 0 - no callstacks are captured
+ eRENDERDOC_Option_CaptureCallstacks = 3,
+
+ // When capturing CPU callstacks, only capture them from drawcalls.
+ // This option does nothing without the above option being enabled
+ //
+ // Default - disabled
+ //
+ // 1 - Only captures callstacks for drawcall type API events.
+ // Ignored if CaptureCallstacks is disabled
+ // 0 - Callstacks, if enabled, are captured for every event.
+ eRENDERDOC_Option_CaptureCallstacksOnlyDraws = 4,
+
+ // Specify a delay in seconds to wait for a debugger to attach, after
+ // creating or injecting into a process, before continuing to allow it to run.
+ //
+ // 0 indicates no delay, and the process will run immediately after injection
+ //
+ // Default - 0 seconds
+ //
+ eRENDERDOC_Option_DelayForDebugger = 5,
+
+ // Verify buffer access. This includes checking the memory returned by a Map() call to
+ // detect any out-of-bounds modification, as well as initialising buffers with undefined contents
+ // to a marker value to catch use of uninitialised memory.
+ //
+ // NOTE: This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do
+ // not do the same kind of interception & checking and undefined contents are really undefined.
+ //
+ // Default - disabled
+ //
+ // 1 - Verify buffer access
+ // 0 - No verification is performed, and overwriting bounds may cause crashes or corruption in
+ // RenderDoc.
+ eRENDERDOC_Option_VerifyBufferAccess = 6,
+
+ // The old name for eRENDERDOC_Option_VerifyBufferAccess was eRENDERDOC_Option_VerifyMapWrites.
+ // This option now controls the filling of uninitialised buffers with 0xdddddddd which was
+ // previously always enabled
+ eRENDERDOC_Option_VerifyMapWrites = eRENDERDOC_Option_VerifyBufferAccess,
+
+ // Hooks any system API calls that create child processes, and injects
+ // RenderDoc into them recursively with the same options.
+ //
+ // Default - disabled
+ //
+ // 1 - Hooks into spawned child processes
+ // 0 - Child processes are not hooked by RenderDoc
+ eRENDERDOC_Option_HookIntoChildren = 7,
+
+ // By default RenderDoc only includes resources in the final capture necessary
+ // for that frame, this allows you to override that behaviour.
+ //
+ // Default - disabled
+ //
+ // 1 - all live resources at the time of capture are included in the capture
+ // and available for inspection
+ // 0 - only the resources referenced by the captured frame are included
+ eRENDERDOC_Option_RefAllResources = 8,
+
+ // **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or
+ // getting it will be ignored, to allow compatibility with older versions.
+ // In v1.1 the option acts as if it's always enabled.
+ //
+ // By default RenderDoc skips saving initial states for resources where the
+ // previous contents don't appear to be used, assuming that writes before
+ // reads indicate previous contents aren't used.
+ //
+ // Default - disabled
+ //
+ // 1 - initial contents at the start of each captured frame are saved, even if
+ // they are later overwritten or cleared before being used.
+ // 0 - unless a read is detected, initial contents will not be saved and will
+ // appear as black or empty data.
+ eRENDERDOC_Option_SaveAllInitials = 9,
+
+ // In APIs that allow for the recording of command lists to be replayed later,
+ // RenderDoc may choose to not capture command lists before a frame capture is
+ // triggered, to reduce overheads. This means any command lists recorded once
+ // and replayed many times will not be available and may cause a failure to
+ // capture.
+ //
+ // NOTE: This is only true for APIs where multithreading is difficult or
+ // discouraged. Newer APIs like Vulkan and D3D12 will ignore this option
+ // and always capture all command lists since the API is heavily oriented
+ // around it and the overheads have been reduced by API design.
+ //
+ // 1 - All command lists are captured from the start of the application
+ // 0 - Command lists are only captured if their recording begins during
+ // the period when a frame capture is in progress.
+ eRENDERDOC_Option_CaptureAllCmdLists = 10,
+
+ // Mute API debugging output when the API validation mode option is enabled
+ //
+ // Default - enabled
+ //
+ // 1 - Mute any API debug messages from being displayed or passed through
+ // 0 - API debugging is displayed as normal
+ eRENDERDOC_Option_DebugOutputMute = 11,
+
+ // Option to allow vendor extensions to be used even when they may be
+ // incompatible with RenderDoc and cause corrupted replays or crashes.
+ //
+ // Default - inactive
+ //
+ // No values are documented, this option should only be used when absolutely
+ // necessary as directed by a RenderDoc developer.
+ eRENDERDOC_Option_AllowUnsupportedVendorExtensions = 12,
+
+} RENDERDOC_CaptureOption;
+
+// Sets an option that controls how RenderDoc behaves on capture.
+//
+// Returns 1 if the option and value are valid
+// Returns 0 if either is invalid and the option is unchanged
+typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionU32)(RENDERDOC_CaptureOption opt, uint32_t val);
+typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionF32)(RENDERDOC_CaptureOption opt, float val);
+
+// Gets the current value of an option as a uint32_t
+//
+// If the option is invalid, 0xffffffff is returned
+typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionU32)(RENDERDOC_CaptureOption opt);
+
+// Gets the current value of an option as a float
+//
+// If the option is invalid, -FLT_MAX is returned
+typedef float(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionF32)(RENDERDOC_CaptureOption opt);
+
+typedef enum RENDERDOC_InputButton {
+ // '0' - '9' matches ASCII values
+ eRENDERDOC_Key_0 = 0x30,
+ eRENDERDOC_Key_1 = 0x31,
+ eRENDERDOC_Key_2 = 0x32,
+ eRENDERDOC_Key_3 = 0x33,
+ eRENDERDOC_Key_4 = 0x34,
+ eRENDERDOC_Key_5 = 0x35,
+ eRENDERDOC_Key_6 = 0x36,
+ eRENDERDOC_Key_7 = 0x37,
+ eRENDERDOC_Key_8 = 0x38,
+ eRENDERDOC_Key_9 = 0x39,
+
+ // 'A' - 'Z' matches ASCII values
+ eRENDERDOC_Key_A = 0x41,
+ eRENDERDOC_Key_B = 0x42,
+ eRENDERDOC_Key_C = 0x43,
+ eRENDERDOC_Key_D = 0x44,
+ eRENDERDOC_Key_E = 0x45,
+ eRENDERDOC_Key_F = 0x46,
+ eRENDERDOC_Key_G = 0x47,
+ eRENDERDOC_Key_H = 0x48,
+ eRENDERDOC_Key_I = 0x49,
+ eRENDERDOC_Key_J = 0x4A,
+ eRENDERDOC_Key_K = 0x4B,
+ eRENDERDOC_Key_L = 0x4C,
+ eRENDERDOC_Key_M = 0x4D,
+ eRENDERDOC_Key_N = 0x4E,
+ eRENDERDOC_Key_O = 0x4F,
+ eRENDERDOC_Key_P = 0x50,
+ eRENDERDOC_Key_Q = 0x51,
+ eRENDERDOC_Key_R = 0x52,
+ eRENDERDOC_Key_S = 0x53,
+ eRENDERDOC_Key_T = 0x54,
+ eRENDERDOC_Key_U = 0x55,
+ eRENDERDOC_Key_V = 0x56,
+ eRENDERDOC_Key_W = 0x57,
+ eRENDERDOC_Key_X = 0x58,
+ eRENDERDOC_Key_Y = 0x59,
+ eRENDERDOC_Key_Z = 0x5A,
+
+ // leave the rest of the ASCII range free
+ // in case we want to use it later
+ eRENDERDOC_Key_NonPrintable = 0x100,
+
+ eRENDERDOC_Key_Divide,
+ eRENDERDOC_Key_Multiply,
+ eRENDERDOC_Key_Subtract,
+ eRENDERDOC_Key_Plus,
+
+ eRENDERDOC_Key_F1,
+ eRENDERDOC_Key_F2,
+ eRENDERDOC_Key_F3,
+ eRENDERDOC_Key_F4,
+ eRENDERDOC_Key_F5,
+ eRENDERDOC_Key_F6,
+ eRENDERDOC_Key_F7,
+ eRENDERDOC_Key_F8,
+ eRENDERDOC_Key_F9,
+ eRENDERDOC_Key_F10,
+ eRENDERDOC_Key_F11,
+ eRENDERDOC_Key_F12,
+
+ eRENDERDOC_Key_Home,
+ eRENDERDOC_Key_End,
+ eRENDERDOC_Key_Insert,
+ eRENDERDOC_Key_Delete,
+ eRENDERDOC_Key_PageUp,
+ eRENDERDOC_Key_PageDn,
+
+ eRENDERDOC_Key_Backspace,
+ eRENDERDOC_Key_Tab,
+ eRENDERDOC_Key_PrtScrn,
+ eRENDERDOC_Key_Pause,
+
+ eRENDERDOC_Key_Max,
+} RENDERDOC_InputButton;
+
+// Sets which key or keys can be used to toggle focus between multiple windows
+//
+// If keys is NULL or num is 0, toggle keys will be disabled
+typedef void(RENDERDOC_CC *pRENDERDOC_SetFocusToggleKeys)(RENDERDOC_InputButton *keys, int num);
+
+// Sets which key or keys can be used to capture the next frame
+//
+// If keys is NULL or num is 0, captures keys will be disabled
+typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureKeys)(RENDERDOC_InputButton *keys, int num);
+
+typedef enum RENDERDOC_OverlayBits {
+ // This single bit controls whether the overlay is enabled or disabled globally
+ eRENDERDOC_Overlay_Enabled = 0x1,
+
+ // Show the average framerate over several seconds as well as min/max
+ eRENDERDOC_Overlay_FrameRate = 0x2,
+
+ // Show the current frame number
+ eRENDERDOC_Overlay_FrameNumber = 0x4,
+
+ // Show a list of recent captures, and how many captures have been made
+ eRENDERDOC_Overlay_CaptureList = 0x8,
+
+ // Default values for the overlay mask
+ eRENDERDOC_Overlay_Default = (eRENDERDOC_Overlay_Enabled | eRENDERDOC_Overlay_FrameRate |
+ eRENDERDOC_Overlay_FrameNumber | eRENDERDOC_Overlay_CaptureList),
+
+ // Enable all bits
+ eRENDERDOC_Overlay_All = ~0U,
+
+ // Disable all bits
+ eRENDERDOC_Overlay_None = 0,
+} RENDERDOC_OverlayBits;
+
+// returns the overlay bits that have been set
+typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetOverlayBits)();
+// sets the overlay bits with an and & or mask
+typedef void(RENDERDOC_CC *pRENDERDOC_MaskOverlayBits)(uint32_t And, uint32_t Or);
+
+// this function will attempt to remove RenderDoc's hooks in the application.
+//
+// Note: that this can only work correctly if done immediately after
+// the module is loaded, before any API work happens. RenderDoc will remove its
+// injected hooks and shut down. Behaviour is undefined if this is called
+// after any API functions have been called, and there is still no guarantee of
+// success.
+typedef void(RENDERDOC_CC *pRENDERDOC_RemoveHooks)();
+
+// DEPRECATED: compatibility for code compiled against pre-1.4.1 headers.
+typedef pRENDERDOC_RemoveHooks pRENDERDOC_Shutdown;
+
+// This function will unload RenderDoc's crash handler.
+//
+// If you use your own crash handler and don't want RenderDoc's handler to
+// intercede, you can call this function to unload it and any unhandled
+// exceptions will pass to the next handler.
+typedef void(RENDERDOC_CC *pRENDERDOC_UnloadCrashHandler)();
+
+// Sets the capture file path template
+//
+// pathtemplate is a UTF-8 string that gives a template for how captures will be named
+// and where they will be saved.
+//
+// Any extension is stripped off the path, and captures are saved in the directory
+// specified, and named with the filename and the frame number appended. If the
+// directory does not exist it will be created, including any parent directories.
+//
+// If pathtemplate is NULL, the template will remain unchanged
+//
+// Example:
+//
+// SetCaptureFilePathTemplate("my_captures/example");
+//
+// Capture #1 -> my_captures/example_frame123.rdc
+// Capture #2 -> my_captures/example_frame456.rdc
+typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFilePathTemplate)(const char *pathtemplate);
+
+// returns the current capture path template, see SetCaptureFileTemplate above, as a UTF-8 string
+typedef const char *(RENDERDOC_CC *pRENDERDOC_GetCaptureFilePathTemplate)();
+
+// DEPRECATED: compatibility for code compiled against pre-1.1.2 headers.
+typedef pRENDERDOC_SetCaptureFilePathTemplate pRENDERDOC_SetLogFilePathTemplate;
+typedef pRENDERDOC_GetCaptureFilePathTemplate pRENDERDOC_GetLogFilePathTemplate;
+
+// returns the number of captures that have been made
+typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetNumCaptures)();
+
+// This function returns the details of a capture, by index. New captures are added
+// to the end of the list.
+//
+// filename will be filled with the absolute path to the capture file, as a UTF-8 string
+// pathlength will be written with the length in bytes of the filename string
+// timestamp will be written with the time of the capture, in seconds since the Unix epoch
+//
+// Any of the parameters can be NULL and they'll be skipped.
+//
+// The function will return 1 if the capture index is valid, or 0 if the index is invalid
+// If the index is invalid, the values will be unchanged
+//
+// Note: when captures are deleted in the UI they will remain in this list, so the
+// capture path may not exist anymore.
+typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCapture)(uint32_t idx, char *filename,
+ uint32_t *pathlength, uint64_t *timestamp);
+
+// Sets the comments associated with a capture file. These comments are displayed in the
+// UI program when opening.
+//
+// filePath should be a path to the capture file to add comments to. If set to NULL or ""
+// the most recent capture file created made will be used instead.
+// comments should be a NULL-terminated UTF-8 string to add as comments.
+//
+// Any existing comments will be overwritten.
+typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFileComments)(const char *filePath,
+ const char *comments);
+
+// returns 1 if the RenderDoc UI is connected to this application, 0 otherwise
+typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsTargetControlConnected)();
+
+// DEPRECATED: compatibility for code compiled against pre-1.1.1 headers.
+// This was renamed to IsTargetControlConnected in API 1.1.1, the old typedef is kept here for
+// backwards compatibility with old code, it is castable either way since it's ABI compatible
+// as the same function pointer type.
+typedef pRENDERDOC_IsTargetControlConnected pRENDERDOC_IsRemoteAccessConnected;
+
+// This function will launch the Replay UI associated with the RenderDoc library injected
+// into the running application.
+//
+// if connectTargetControl is 1, the Replay UI will be launched with a command line parameter
+// to connect to this application
+// cmdline is the rest of the command line, as a UTF-8 string. E.g. a captures to open
+// if cmdline is NULL, the command line will be empty.
+//
+// returns the PID of the replay UI if successful, 0 if not successful.
+typedef uint32_t(RENDERDOC_CC *pRENDERDOC_LaunchReplayUI)(uint32_t connectTargetControl,
+ const char *cmdline);
+
+// RenderDoc can return a higher version than requested if it's backwards compatible,
+// this function returns the actual version returned. If a parameter is NULL, it will be
+// ignored and the others will be filled out.
+typedef void(RENDERDOC_CC *pRENDERDOC_GetAPIVersion)(int *major, int *minor, int *patch);
+
+//////////////////////////////////////////////////////////////////////////
+// Capturing functions
+//
+
+// A device pointer is a pointer to the API's root handle.
+//
+// This would be an ID3D11Device, HGLRC/GLXContext, ID3D12Device, etc
+typedef void *RENDERDOC_DevicePointer;
+
+// A window handle is the OS's native window handle
+//
+// This would be an HWND, GLXDrawable, etc
+typedef void *RENDERDOC_WindowHandle;
+
+// A helper macro for Vulkan, where the device handle cannot be used directly.
+//
+// Passing the VkInstance to this macro will return the RENDERDOC_DevicePointer to use.
+//
+// Specifically, the value needed is the dispatch table pointer, which sits as the first
+// pointer-sized object in the memory pointed to by the VkInstance. Thus we cast to a void** and
+// indirect once.
+#define RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(inst) (*((void **)(inst)))
+
+// This sets the RenderDoc in-app overlay in the API/window pair as 'active' and it will
+// respond to keypresses. Neither parameter can be NULL
+typedef void(RENDERDOC_CC *pRENDERDOC_SetActiveWindow)(RENDERDOC_DevicePointer device,
+ RENDERDOC_WindowHandle wndHandle);
+
+// capture the next frame on whichever window and API is currently considered active
+typedef void(RENDERDOC_CC *pRENDERDOC_TriggerCapture)();
+
+// capture the next N frames on whichever window and API is currently considered active
+typedef void(RENDERDOC_CC *pRENDERDOC_TriggerMultiFrameCapture)(uint32_t numFrames);
+
+// When choosing either a device pointer or a window handle to capture, you can pass NULL.
+// Passing NULL specifies a 'wildcard' match against anything. This allows you to specify
+// any API rendering to a specific window, or a specific API instance rendering to any window,
+// or in the simplest case of one window and one API, you can just pass NULL for both.
+//
+// In either case, if there are two or more possible matching (device,window) pairs it
+// is undefined which one will be captured.
+//
+// Note: for headless rendering you can pass NULL for the window handle and either specify
+// a device pointer or leave it NULL as above.
+
+// Immediately starts capturing API calls on the specified device pointer and window handle.
+//
+// If there is no matching thing to capture (e.g. no supported API has been initialised),
+// this will do nothing.
+//
+// The results are undefined (including crashes) if two captures are started overlapping,
+// even on separate devices and/oror windows.
+typedef void(RENDERDOC_CC *pRENDERDOC_StartFrameCapture)(RENDERDOC_DevicePointer device,
+ RENDERDOC_WindowHandle wndHandle);
+
+// Returns whether or not a frame capture is currently ongoing anywhere.
+//
+// This will return 1 if a capture is ongoing, and 0 if there is no capture running
+typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsFrameCapturing)();
+
+// Ends capturing immediately.
+//
+// This will return 1 if the capture succeeded, and 0 if there was an error capturing.
+typedef uint32_t(RENDERDOC_CC *pRENDERDOC_EndFrameCapture)(RENDERDOC_DevicePointer device,
+ RENDERDOC_WindowHandle wndHandle);
+
+// Ends capturing immediately and discard any data stored without saving to disk.
+//
+// This will return 1 if the capture was discarded, and 0 if there was an error or no capture
+// was in progress
+typedef uint32_t(RENDERDOC_CC *pRENDERDOC_DiscardFrameCapture)(RENDERDOC_DevicePointer device,
+ RENDERDOC_WindowHandle wndHandle);
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// RenderDoc API versions
+//
+
+// RenderDoc uses semantic versioning (http://semver.org/).
+//
+// MAJOR version is incremented when incompatible API changes happen.
+// MINOR version is incremented when functionality is added in a backwards-compatible manner.
+// PATCH version is incremented when backwards-compatible bug fixes happen.
+//
+// Note that this means the API returned can be higher than the one you might have requested.
+// e.g. if you are running against a newer RenderDoc that supports 1.0.1, it will be returned
+// instead of 1.0.0. You can check this with the GetAPIVersion entry point
+typedef enum RENDERDOC_Version {
+ eRENDERDOC_API_Version_1_0_0 = 10000, // RENDERDOC_API_1_0_0 = 1 00 00
+ eRENDERDOC_API_Version_1_0_1 = 10001, // RENDERDOC_API_1_0_1 = 1 00 01
+ eRENDERDOC_API_Version_1_0_2 = 10002, // RENDERDOC_API_1_0_2 = 1 00 02
+ eRENDERDOC_API_Version_1_1_0 = 10100, // RENDERDOC_API_1_1_0 = 1 01 00
+ eRENDERDOC_API_Version_1_1_1 = 10101, // RENDERDOC_API_1_1_1 = 1 01 01
+ eRENDERDOC_API_Version_1_1_2 = 10102, // RENDERDOC_API_1_1_2 = 1 01 02
+ eRENDERDOC_API_Version_1_2_0 = 10200, // RENDERDOC_API_1_2_0 = 1 02 00
+ eRENDERDOC_API_Version_1_3_0 = 10300, // RENDERDOC_API_1_3_0 = 1 03 00
+ eRENDERDOC_API_Version_1_4_0 = 10400, // RENDERDOC_API_1_4_0 = 1 04 00
+ eRENDERDOC_API_Version_1_4_1 = 10401, // RENDERDOC_API_1_4_1 = 1 04 01
+} RENDERDOC_Version;
+
+// API version changelog:
+//
+// 1.0.0 - initial release
+// 1.0.1 - Bugfix: IsFrameCapturing() was returning false for captures that were triggered
+// by keypress or TriggerCapture, instead of Start/EndFrameCapture.
+// 1.0.2 - Refactor: Renamed eRENDERDOC_Option_DebugDeviceMode to eRENDERDOC_Option_APIValidation
+// 1.1.0 - Add feature: TriggerMultiFrameCapture(). Backwards compatible with 1.0.x since the new
+// function pointer is added to the end of the struct, the original layout is identical
+// 1.1.1 - Refactor: Renamed remote access to target control (to better disambiguate from remote
+// replay/remote server concept in replay UI)
+// 1.1.2 - Refactor: Renamed "log file" in function names to just capture, to clarify that these
+// are captures and not debug logging files. This is the first API version in the v1.0
+// branch.
+// 1.2.0 - Added feature: SetCaptureFileComments() to add comments to a capture file that will be
+// displayed in the UI program on load.
+// 1.3.0 - Added feature: New capture option eRENDERDOC_Option_AllowUnsupportedVendorExtensions
+// which allows users to opt-in to allowing unsupported vendor extensions to function.
+// Should be used at the user's own risk.
+// Refactor: Renamed eRENDERDOC_Option_VerifyMapWrites to
+// eRENDERDOC_Option_VerifyBufferAccess, which now also controls initialisation to
+// 0xdddddddd of uninitialised buffer contents.
+// 1.4.0 - Added feature: DiscardFrameCapture() to discard a frame capture in progress and stop
+// capturing without saving anything to disk.
+// 1.4.1 - Refactor: Renamed Shutdown to RemoveHooks to better clarify what is happening
+
+typedef struct RENDERDOC_API_1_4_1
+{
+ pRENDERDOC_GetAPIVersion GetAPIVersion;
+
+ pRENDERDOC_SetCaptureOptionU32 SetCaptureOptionU32;
+ pRENDERDOC_SetCaptureOptionF32 SetCaptureOptionF32;
+
+ pRENDERDOC_GetCaptureOptionU32 GetCaptureOptionU32;
+ pRENDERDOC_GetCaptureOptionF32 GetCaptureOptionF32;
+
+ pRENDERDOC_SetFocusToggleKeys SetFocusToggleKeys;
+ pRENDERDOC_SetCaptureKeys SetCaptureKeys;
+
+ pRENDERDOC_GetOverlayBits GetOverlayBits;
+ pRENDERDOC_MaskOverlayBits MaskOverlayBits;
+
+ // Shutdown was renamed to RemoveHooks in 1.4.1.
+ // These unions allow old code to continue compiling without changes
+ union
+ {
+ pRENDERDOC_Shutdown Shutdown;
+ pRENDERDOC_RemoveHooks RemoveHooks;
+ };
+ pRENDERDOC_UnloadCrashHandler UnloadCrashHandler;
+
+ // Get/SetLogFilePathTemplate was renamed to Get/SetCaptureFilePathTemplate in 1.1.2.
+ // These unions allow old code to continue compiling without changes
+ union
+ {
+ // deprecated name
+ pRENDERDOC_SetLogFilePathTemplate SetLogFilePathTemplate;
+ // current name
+ pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate;
+ };
+ union
+ {
+ // deprecated name
+ pRENDERDOC_GetLogFilePathTemplate GetLogFilePathTemplate;
+ // current name
+ pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate;
+ };
+
+ pRENDERDOC_GetNumCaptures GetNumCaptures;
+ pRENDERDOC_GetCapture GetCapture;
+
+ pRENDERDOC_TriggerCapture TriggerCapture;
+
+ // IsRemoteAccessConnected was renamed to IsTargetControlConnected in 1.1.1.
+ // This union allows old code to continue compiling without changes
+ union
+ {
+ // deprecated name
+ pRENDERDOC_IsRemoteAccessConnected IsRemoteAccessConnected;
+ // current name
+ pRENDERDOC_IsTargetControlConnected IsTargetControlConnected;
+ };
+ pRENDERDOC_LaunchReplayUI LaunchReplayUI;
+
+ pRENDERDOC_SetActiveWindow SetActiveWindow;
+
+ pRENDERDOC_StartFrameCapture StartFrameCapture;
+ pRENDERDOC_IsFrameCapturing IsFrameCapturing;
+ pRENDERDOC_EndFrameCapture EndFrameCapture;
+
+ // new function in 1.1.0
+ pRENDERDOC_TriggerMultiFrameCapture TriggerMultiFrameCapture;
+
+ // new function in 1.2.0
+ pRENDERDOC_SetCaptureFileComments SetCaptureFileComments;
+
+ // new function in 1.4.0
+ pRENDERDOC_DiscardFrameCapture DiscardFrameCapture;
+} RENDERDOC_API_1_4_1;
+
+typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_0_0;
+typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_0_1;
+typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_0_2;
+typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_1_0;
+typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_1_1;
+typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_1_2;
+typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_2_0;
+typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_3_0;
+typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_4_0;
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// RenderDoc API entry point
+//
+// This entry point can be obtained via GetProcAddress/dlsym if RenderDoc is available.
+//
+// The name is the same as the typedef - "RENDERDOC_GetAPI"
+//
+// This function is not thread safe, and should not be called on multiple threads at once.
+// Ideally, call this once as early as possible in your application's startup, before doing
+// any API work, since some configuration functionality etc has to be done also before
+// initialising any APIs.
+//
+// Parameters:
+// version is a single value from the RENDERDOC_Version above.
+//
+// outAPIPointers will be filled out with a pointer to the corresponding struct of function
+// pointers.
+//
+// Returns:
+// 1 - if the outAPIPointers has been filled with a pointer to the API struct requested
+// 0 - if the requested version is not supported or the arguments are invalid.
+//
+typedef int(RENDERDOC_CC *pRENDERDOC_GetAPI)(RENDERDOC_Version version, void **outAPIPointers);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
diff --git a/zbar_pipeline.c b/src/zbar_pipeline.c
similarity index 57%
rename from zbar_pipeline.c
rename to src/zbar_pipeline.c
index e4c9257..0fa5223 100644
--- a/zbar_pipeline.c
+++ b/src/zbar_pipeline.c
@@ -6,6 +6,17 @@
#include
#include
+struct _MPZBarImage {
+ uint8_t *data;
+ MPPixelFormat pixel_format;
+ int width;
+ int height;
+ int rotation;
+ bool mirrored;
+
+ _Atomic int ref_count;
+};
+
static MPPipeline *pipeline;
static volatile int frames_processed = 0;
@@ -33,7 +44,8 @@ mp_zbar_pipeline_stop()
mp_pipeline_free(pipeline);
}
-static bool is_3d_code(zbar_symbol_type_t type)
+static bool
+is_3d_code(zbar_symbol_type_t type)
{
switch (type) {
case ZBAR_EAN2:
@@ -62,9 +74,41 @@ static bool is_3d_code(zbar_symbol_type_t type)
}
}
-static MPZBarCode
-process_symbol(const zbar_symbol_t *symbol)
+static inline void
+map_coords(int *x, int *y, int width, int height, int rotation, bool mirrored)
{
+ int x_r, y_r;
+ if (rotation == 0) {
+ x_r = *x;
+ y_r = *y;
+ } else if (rotation == 90) {
+ x_r = *y;
+ y_r = height - *x - 1;
+ } else if (rotation == 270) {
+ x_r = width - *y - 1;
+ y_r = *x;
+ } else {
+ x_r = width - *x - 1;
+ y_r = height - *y - 1;
+ }
+
+ if (mirrored) {
+ x_r = width - x_r - 1;
+ }
+
+ *x = x_r;
+ *y = y_r;
+}
+
+static MPZBarCode
+process_symbol(const MPZBarImage *image, int width, int height, const zbar_symbol_t *symbol)
+{
+ if (image->rotation == 90 || image->rotation == 270) {
+ int tmp = width;
+ width = height;
+ height = tmp;
+ }
+
MPZBarCode code;
unsigned loc_size = zbar_symbol_get_loc_size(symbol);
@@ -100,6 +144,10 @@ process_symbol(const zbar_symbol_t *symbol)
code.bounds_y[3] = max_y;
}
+ for (uint8_t i = 0; i < 4; ++i) {
+ map_coords(&code.bounds_x[i], &code.bounds_y[i], width, height, image->rotation, image->mirrored);
+ }
+
const char *data = zbar_symbol_get_data(symbol);
unsigned int data_size = zbar_symbol_get_data_length(symbol);
code.type = zbar_get_symbol_name(type);
@@ -110,25 +158,33 @@ process_symbol(const zbar_symbol_t *symbol)
}
static void
-process_surface(MPPipeline *pipeline, cairo_surface_t **_surface)
+process_image(MPPipeline *pipeline, MPZBarImage **_image)
{
- cairo_surface_t *surface = *_surface;
+ MPZBarImage *image = *_image;
- 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);
+ assert(image->pixel_format == MP_PIXEL_FMT_BGGR8
+ || image->pixel_format == MP_PIXEL_FMT_GBRG8
+ || image->pixel_format == MP_PIXEL_FMT_GRBG8
+ || image->pixel_format == MP_PIXEL_FMT_RGGB8);
+
+ // Create a grayscale image for scanning from the current preview.
+ // Rotate/mirror correctly.
+ int width = image->width / 2;
+ int height = image->height / 2;
- // 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;
+ size_t i = 0;
+ for (int y = 0; y < image->height; y += 2) {
+ for (int x = 0; x < image->width; x += 2) {
+ data[++i] = image->data[x + image->width * y];
+ }
}
// 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);
+ zbar_image_set_data(zbar_image, data, width * height * sizeof(uint8_t), NULL);
int res = zbar_scan_image(scanner, zbar_image);
assert(res >= 0);
@@ -140,7 +196,7 @@ process_surface(MPPipeline *pipeline, cairo_surface_t **_surface)
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);
+ result->codes[i] = process_symbol(image, width, height, symbol);
symbol = zbar_symbol_next(symbol);
}
@@ -150,22 +206,57 @@ process_surface(MPPipeline *pipeline, cairo_surface_t **_surface)
}
zbar_image_destroy(zbar_image);
- cairo_surface_destroy(surface);
+ mp_zbar_image_unref(image);
++frames_processed;
}
void
-mp_zbar_pipeline_process_image(cairo_surface_t *surface)
+mp_zbar_pipeline_process_image(MPZBarImage *image)
{
// If we haven't processed the previous frame yet, drop this one
if (frames_received != frames_processed) {
- cairo_surface_destroy(surface);
+ mp_zbar_image_unref(image);
return;
}
++frames_received;
- mp_pipeline_invoke(pipeline, (MPPipelineCallback)process_surface, &surface,
- sizeof(cairo_surface_t *));
+ mp_pipeline_invoke(pipeline, (MPPipelineCallback)process_image, &image,
+ sizeof(MPZBarImage *));
+}
+
+MPZBarImage *
+mp_zbar_image_new(uint8_t *data,
+ MPPixelFormat pixel_format,
+ int width,
+ int height,
+ int rotation,
+ bool mirrored)
+{
+ MPZBarImage *image = malloc(sizeof(MPZBarImage));
+ image->data = data;
+ image->pixel_format = pixel_format;
+ image->width = width;
+ image->height = height;
+ image->rotation = rotation;
+ image->mirrored = mirrored;
+ image->ref_count = 1;
+ return image;
+}
+
+MPZBarImage *
+mp_zbar_image_ref(MPZBarImage *image)
+{
+ ++image->ref_count;
+ return image;
+}
+
+void
+mp_zbar_image_unref(MPZBarImage *image)
+{
+ if (--image->ref_count == 0) {
+ free(image->data);
+ free(image);
+ }
}
diff --git a/src/zbar_pipeline.h b/src/zbar_pipeline.h
new file mode 100644
index 0000000..410e906
--- /dev/null
+++ b/src/zbar_pipeline.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "camera_config.h"
+
+typedef struct _MPZBarImage MPZBarImage;
+
+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(MPZBarImage *image);
+
+MPZBarImage *mp_zbar_image_new(uint8_t *data,
+ MPPixelFormat pixel_format,
+ int width,
+ int height,
+ int rotation,
+ bool mirrored);
+MPZBarImage *mp_zbar_image_ref(MPZBarImage *image);
+void mp_zbar_image_unref(MPZBarImage *image);
diff --git a/tests/test_quickpreview.c b/tests/test_quickpreview.c
deleted file mode 100644
index 9b43778..0000000
--- a/tests/test_quickpreview.c
+++ /dev/null
@@ -1,120 +0,0 @@
-#include "quickpreview.h"
-#include
-#include
-#include
-
-static void
-test_quick_preview_fuzz()
-{
- for (size_t i = 0; i < 10000; ++i) {
- uint32_t width = rand() % 127 + 1;
- uint32_t height = rand() % 127 + 1;
- uint32_t format = rand() % (MP_PIXEL_FMT_MAX - 1) + 1;
-
- assert(width > 0 && height > 0);
-
- switch (format) {
- case MP_PIXEL_FMT_BGGR8:
- case MP_PIXEL_FMT_GBRG8:
- case MP_PIXEL_FMT_GRBG8:
- case MP_PIXEL_FMT_RGGB8:
- width += width % 2;
- height += height % 2;
- break;
- case MP_PIXEL_FMT_BGGR10P:
- case MP_PIXEL_FMT_GBRG10P:
- case MP_PIXEL_FMT_GRBG10P:
- case MP_PIXEL_FMT_RGGB10P:
- width += width % 2;
- height += height % 2;
- break;
- case MP_PIXEL_FMT_UYVY:
- case MP_PIXEL_FMT_YUYV:
- width += width % 4;
- break;
- default:
- assert(false);
- }
-
- int rotation;
- switch (rand() % 3) {
- case 0:
- rotation = 0;
- break;
- case 1:
- rotation = 90;
- break;
- case 2:
- rotation = 180;
- break;
- default:
- rotation = 270;
- break;
- }
-
- bool mirrored = rand() % 2;
-
- float matbuf[9];
- float *colormatrix = NULL;
- if (rand() % 2) {
- for (int j = 0; j < 9; ++j) {
- matbuf[j] = (double)rand() / RAND_MAX * 2.0;
- }
- colormatrix = matbuf;
- }
-
- uint8_t blacklevel = rand() % 3;
-
- size_t src_size = mp_pixel_format_width_to_bytes(format, width) * height;
- uint8_t *src = malloc(src_size);
- for (int j = 0; j < src_size; ++j) {
- src[j] = rand();
- }
-
- uint32_t preview_width = mp_pixel_format_width_to_colors(format, width);
- uint32_t preview_height = mp_pixel_format_height_to_colors(format, height);
- if (preview_width > 32 && preview_height > 32) {
- preview_width /= (1 + rand() % 2);
- preview_height /= (1 + rand() % 2);
- }
-
- uint32_t dst_width, dst_height, skip;
- quick_preview_size(
- &dst_width,
- &dst_height,
- &skip,
- preview_width,
- preview_height,
- width,
- height,
- format,
- rotation);
-
- uint32_t *dst = malloc(dst_width * dst_height * sizeof(uint32_t));
-
- quick_preview(
- dst,
- dst_width,
- dst_height,
- src,
- width,
- height,
- format,
- rotation,
- mirrored,
- colormatrix,
- blacklevel,
- skip);
-
- free(dst);
- free(src);
- }
-}
-
-int
-main(int argc, char *argv[])
-{
- g_test_init(&argc, &argv, NULL);
- g_test_add_func("/quick_preview/fuzz", test_quick_preview_fuzz);
- return g_test_run();
-}
diff --git a/tools/camera_test.c b/tools/camera_test.c
index df88dbf..df7d923 100644
--- a/tools/camera_test.c
+++ b/tools/camera_test.c
@@ -15,20 +15,6 @@ get_time()
return t.tv_sec + t.tv_usec * 1e-6;
}
-void
-on_capture(MPImage image, void *user_data)
-{
- size_t num_bytes =
- mp_pixel_format_width_to_bytes(image.pixel_format, image.width) *
- image.height;
- uint8_t *data = malloc(num_bytes);
- memcpy(data, image.data, num_bytes);
-
- printf(" first byte: %d.", data[0]);
-
- free(data);
-}
-
int
main(int argc, char *argv[])
{
@@ -184,7 +170,22 @@ main(int argc, char *argv[])
(last - start_capture) * 1000);
for (int i = 0; i < 10; ++i) {
- mp_camera_capture_image(camera, on_capture, NULL);
+ MPBuffer buffer;
+ if (!mp_camera_capture_buffer(camera, &buffer)) {
+ printf(" Failed to capture buffer\n");
+ }
+
+ size_t num_bytes =
+ mp_pixel_format_width_to_bytes(m->pixel_format, m->width) *
+ m->height;
+ uint8_t *data = malloc(num_bytes);
+ memcpy(data, buffer.data, num_bytes);
+
+ printf(" first byte: %d.", data[0]);
+
+ free(data);
+
+ mp_camera_release_buffer(camera, buffer.index);
double now = get_time();
printf(" capture took %fms\n", (now - last) * 1000);
diff --git a/zbar_pipeline.h b/zbar_pipeline.h
deleted file mode 100644
index 150861a..0000000
--- a/zbar_pipeline.h
+++ /dev/null
@@ -1,22 +0,0 @@
-#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);