diff --git a/config/pine64,pinephone-1.0.ini b/config/pine64,pinephone-1.0.ini
index 901ccc7..3b87aa7 100644
--- a/config/pine64,pinephone-1.0.ini
+++ b/config/pine64,pinephone-1.0.ini
@@ -23,6 +23,7 @@ cropfactor=10.81
fnumber=3.0
iso-min=100
iso-max=64000
+flash-path=/sys/class/leds/white:flash
[front]
driver=gc2145
@@ -40,3 +41,4 @@ mirrored=true
focallength=2.6
cropfactor=12.7
fnumber=2.8
+flash-display=true
diff --git a/config/pine64,pinephone-1.1.ini b/config/pine64,pinephone-1.1.ini
index 901ccc7..3b87aa7 100644
--- a/config/pine64,pinephone-1.1.ini
+++ b/config/pine64,pinephone-1.1.ini
@@ -23,6 +23,7 @@ cropfactor=10.81
fnumber=3.0
iso-min=100
iso-max=64000
+flash-path=/sys/class/leds/white:flash
[front]
driver=gc2145
@@ -40,3 +41,4 @@ mirrored=true
focallength=2.6
cropfactor=12.7
fnumber=2.8
+flash-display=true
diff --git a/config/pine64,pinephone-1.2.ini b/config/pine64,pinephone-1.2.ini
index e295f87..d3c1d68 100644
--- a/config/pine64,pinephone-1.2.ini
+++ b/config/pine64,pinephone-1.2.ini
@@ -24,6 +24,7 @@ cropfactor=10.81
fnumber=3.0
iso-min=100
iso-max=64000
+flash-path=/sys/class/leds/white:flash
[front]
driver=gc2145
@@ -41,3 +42,4 @@ mirrored=true
focallength=2.6
cropfactor=12.7
fnumber=2.8
+flash-display=true
diff --git a/data/camera.css b/data/camera.css
index 85c65e3..68dd90d 100644
--- a/data/camera.css
+++ b/data/camera.css
@@ -11,3 +11,7 @@
opacity: 0.9;
background-color: rgba(0, 0, 0, 0.2);
}
+
+.flash {
+ background-color: #ffffff;
+}
diff --git a/data/camera.ui b/data/camera.ui
index 83adf8e..13140cf 100644
--- a/data/camera.ui
+++ b/data/camera.ui
@@ -59,6 +59,15 @@
+
+
+
diff --git a/data/flash-disabled-symbolic.svg b/data/flash-disabled-symbolic.svg
new file mode 100644
index 0000000..e32ee04
--- /dev/null
+++ b/data/flash-disabled-symbolic.svg
@@ -0,0 +1,32 @@
+
+
diff --git a/data/flash-enabled-symbolic.svg b/data/flash-enabled-symbolic.svg
new file mode 100644
index 0000000..39b53ab
--- /dev/null
+++ b/data/flash-enabled-symbolic.svg
@@ -0,0 +1,31 @@
+
+
diff --git a/data/org.postmarketos.Megapixels.gresource.xml b/data/org.postmarketos.Megapixels.gresource.xml
index 0243024..f4a3c8f 100644
--- a/data/org.postmarketos.Megapixels.gresource.xml
+++ b/data/org.postmarketos.Megapixels.gresource.xml
@@ -4,11 +4,13 @@
camera.ui
controls-popover.ui
camera.css
- switch-camera-symbolic.svg
- shutter-button-symbolic.svg
+ flash-disabled-symbolic.svg
+ flash-enabled-symbolic.svg
folder-symbolic.svg
settings-symbolic.svg
+ shutter-button-symbolic.svg
shutter-symbolic.svg
+ switch-camera-symbolic.svg
blit.vert
blit.frag
solid.vert
diff --git a/meson.build b/meson.build
index 0e8ff55..727b2fa 100644
--- a/meson.build
+++ b/meson.build
@@ -34,18 +34,19 @@ if get_option('tiffcfapattern')
endif
executable('megapixels',
- '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/device.c',
+ 'src/flash.c',
+ 'src/gl_util.c',
+ 'src/gles2_debayer.c',
+ 'src/ini.c',
'src/io_pipeline.c',
+ 'src/main.c',
+ 'src/matrix.c',
+ 'src/pipeline.c',
'src/process_pipeline.c',
'src/zbar_pipeline.c',
- 'src/matrix.c',
resources,
include_directories: 'src/',
dependencies: [gtkdep, libm, tiff, zbar, threads, epoxy],
diff --git a/src/camera_config.c b/src/camera_config.c
index f8ecf05..84d9e05 100644
--- a/src/camera_config.c
+++ b/src/camera_config.c
@@ -206,6 +206,10 @@ config_ini_handler(void *user, const char *section, const char *name,
cc->iso_min = strtod(value, NULL);
} else if (strcmp(name, "iso-max") == 0) {
cc->iso_max = strtod(value, NULL);
+ } else if (strcmp(name, "flash-path") == 0) {
+ strcpy(cc->flash_path, value);
+ } else if (strcmp(name, "flash-display") == 0) {
+ cc->flash_display = strcmp(value, "true") == 0;
} else {
g_printerr("Unknown key '%s' in [%s]\n", name, section);
exit(1);
diff --git a/src/camera_config.h b/src/camera_config.h
index f506af8..55a0fcb 100644
--- a/src/camera_config.h
+++ b/src/camera_config.h
@@ -41,6 +41,9 @@ struct mp_camera_config {
double fnumber;
int iso_min;
int iso_max;
+
+ char flash_path[260];
+ bool flash_display;
};
bool mp_load_config();
diff --git a/src/flash.c b/src/flash.c
new file mode 100644
index 0000000..930bcd8
--- /dev/null
+++ b/src/flash.c
@@ -0,0 +1,141 @@
+#include "flash.h"
+
+#include "gtk/gtk.h"
+#include
+#include
+#include
+#include
+
+typedef enum {
+ FLASH_TYPE_LED,
+ FLASH_TYPE_DISPLAY,
+} FlashType;
+
+typedef struct {
+ char path[260];
+ int fd;
+} MPLEDFlash;
+
+typedef struct {
+ GtkWidget *window;
+} MPDisplayFlash;
+
+struct _MPFlash {
+ FlashType type;
+
+ union {
+ MPLEDFlash led;
+ MPDisplayFlash display;
+ };
+};
+
+MPFlash *
+mp_led_flash_from_path(const char * path)
+{
+ MPFlash *flash = malloc(sizeof(MPFlash));
+ flash->type = FLASH_TYPE_LED;
+
+ strncpy(flash->led.path, path, 259);
+
+ char mpath[275];
+ snprintf(mpath, 275, "%s/flash_strobe", path);
+ flash->led.fd = open(mpath, O_WRONLY);
+ if (flash->led.fd == -1) {
+ g_printerr("Failed to open %s\n", mpath);
+ free(flash);
+ return NULL;
+ }
+
+ return flash;
+}
+
+static bool create_display(MPFlash *flash)
+{
+ // Create a full screen full white window as a flash
+ GtkWidget *window = gtk_window_new();
+ // gtk_window_set_accept_focus(GTK_WINDOW(flash->display.window), FALSE);
+ gtk_window_set_decorated(GTK_WINDOW(window), FALSE);
+ gtk_window_fullscreen(GTK_WINDOW(window));
+
+ GtkStyleContext *context;
+ context = gtk_widget_get_style_context(window);
+ gtk_style_context_add_class(context, "flash");
+
+ flash->display.window = window;
+
+ return false;
+}
+
+MPFlash *
+mp_create_display_flash()
+{
+ MPFlash *flash = malloc(sizeof(MPFlash));
+ flash->type = FLASH_TYPE_DISPLAY;
+ flash->display.window = NULL;
+
+ // All GTK functions must be called on the main thread
+ g_main_context_invoke(NULL, (GSourceFunc)create_display, flash);
+
+ return flash;
+}
+
+static bool flash_free(MPFlash *flash)
+{
+ gtk_window_destroy(GTK_WINDOW(flash->display.window));
+ free(flash);
+ return false;
+}
+
+void
+mp_flash_free(MPFlash *flash)
+{
+ switch (flash->type) {
+ case FLASH_TYPE_LED:
+ close(flash->led.fd);
+ free(flash);
+ break;
+ case FLASH_TYPE_DISPLAY:
+ g_main_context_invoke(NULL, (GSourceFunc)flash_free, flash);
+ break;
+ }
+}
+
+static bool show_flash_window(MPFlash *flash)
+{
+ gtk_widget_show(flash->display.window);
+ return false;
+}
+
+void
+mp_flash_enable(MPFlash *flash)
+{
+ switch (flash->type) {
+ case FLASH_TYPE_LED:
+ lseek(flash->led.fd, 0, SEEK_SET);
+ dprintf(flash->led.fd, "1\n");
+ break;
+ case FLASH_TYPE_DISPLAY:
+ g_main_context_invoke(NULL, (GSourceFunc)show_flash_window, flash);
+ break;
+ }
+}
+
+static bool hide_flash_window(MPFlash *flash)
+{
+ gtk_widget_hide(flash->display.window);
+ return false;
+}
+
+void
+mp_flash_disable(MPFlash *flash)
+{
+ switch (flash->type) {
+ case FLASH_TYPE_LED:
+ // Flash gets reset automatically
+ break;
+ case FLASH_TYPE_DISPLAY:
+ g_main_context_invoke(NULL, (GSourceFunc)hide_flash_window, flash);
+ break;
+ }
+}
+
diff --git a/src/flash.h b/src/flash.h
new file mode 100644
index 0000000..9ff8813
--- /dev/null
+++ b/src/flash.h
@@ -0,0 +1,8 @@
+typedef struct _MPFlash MPFlash;
+
+MPFlash *mp_led_flash_from_path(const char *path);
+MPFlash *mp_create_display_flash();
+void mp_flash_free(MPFlash *flash);
+
+void mp_flash_enable(MPFlash *flash);
+void mp_flash_disable(MPFlash *flash);
diff --git a/src/io_pipeline.c b/src/io_pipeline.c
index 2e0331a..68bdefb 100644
--- a/src/io_pipeline.c
+++ b/src/io_pipeline.c
@@ -1,16 +1,17 @@
#include "io_pipeline.h"
-#include "device.h"
#include "camera.h"
+#include "device.h"
+#include "flash.h"
#include "pipeline.h"
#include "process_pipeline.h"
-#include
-#include
-#include
-#include
-#include
#include
+#include
+#include
+#include
#include
+#include
+#include
struct media_link_info {
unsigned int source_entity_id;
@@ -29,6 +30,8 @@ struct camera_info {
MPCamera *camera;
+ MPFlash *flash;
+
int gain_ctrl;
int gain_max;
@@ -90,6 +93,8 @@ struct control_state {
static struct control_state desired_controls = {};
static struct control_state current_controls = {};
+static bool flash_enabled = false;
+
static bool want_focus = false;
static MPPipeline *pipeline;
@@ -216,6 +221,15 @@ setup_camera(MPDeviceList **device_list, const struct mp_camera_config *config)
info->gain_ctrl = V4L2_CID_ANALOGUE_GAIN;
info->gain_max = control.max;
}
+
+ // Setup flash
+ if (config->flash_path[0]) {
+ info->flash = mp_led_flash_from_path(config->flash_path);
+ } else if (config->flash_display) {
+ info->flash = mp_create_display_flash();
+ } else {
+ info->flash = NULL;
+ }
}
}
@@ -340,6 +354,11 @@ capture(MPPipeline *pipeline, const void *data)
mp_camera_start_capture(info->camera);
+ // Enable flash
+ if (info->flash && flash_enabled) {
+ mp_flash_enable(info->flash);
+ }
+
update_process_pipeline();
mp_process_pipeline_capture();
@@ -490,6 +509,11 @@ on_frame(MPBuffer buffer, void * _data)
mp_camera_start_capture(info->camera);
+ // Disable flash
+ if (info->flash) {
+ mp_flash_disable(info->flash);
+ }
+
update_process_pipeline();
}
}
@@ -571,8 +595,12 @@ update_state(MPPipeline *pipeline, const struct mp_io_pipeline_state *state)
desired_controls.exposure = state->exposure;
has_changed =
- has_changed || memcmp(&previous_desired, &desired_controls,
- sizeof(struct control_state)) != 0;
+ has_changed
+ || memcmp(&previous_desired, &desired_controls,
+ sizeof(struct control_state)) != 0
+ || flash_enabled != state->flash_enabled;
+
+ flash_enabled = state->flash_enabled;
}
assert(has_changed);
diff --git a/src/io_pipeline.h b/src/io_pipeline.h
index a8a0f2c..6e5ff28 100644
--- a/src/io_pipeline.h
+++ b/src/io_pipeline.h
@@ -19,6 +19,7 @@ struct mp_io_pipeline_state {
int exposure;
bool save_dng;
+ bool flash_enabled;
};
void mp_io_pipeline_start();
diff --git a/src/main.c b/src/main.c
index d0c66fb..cf653a2 100644
--- a/src/main.c
+++ b/src/main.c
@@ -52,6 +52,8 @@ static int exposure;
static bool has_auto_focus_continuous;
static bool has_auto_focus_start;
+static bool flash_enabled = true;
+
static bool setting_save_dng;
static MPProcessPipelineBuffer *current_preview_buffer = NULL;
@@ -107,6 +109,7 @@ update_io_pipeline()
.exposure_is_manual = exposure_is_manual,
.exposure = exposure,
.save_dng = setting_save_dng,
+ .flash_enabled = flash_enabled,
};
mp_io_pipeline_update_state(&io_state);
}
@@ -758,6 +761,16 @@ 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
+flash_button_clicked(GtkWidget *button, gpointer user_data)
+{
+ flash_enabled = !flash_enabled;
+ update_io_pipeline();
+
+ const char * icon_name = flash_enabled ? "flash-enabled-symbolic" : "flash-disabled-symbolic";
+ gtk_button_set_icon_name(GTK_BUTTON(button), icon_name);
+}
+
static void
on_realize(GtkWidget *window, gpointer *data)
{
@@ -895,6 +908,7 @@ activate(GtkApplication *app, gpointer data)
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"));
+ GtkWidget *flash_button = GTK_WIDGET(gtk_builder_get_object(builder, "flash-controls-button"));
GtkWidget *setting_dng_button = GTK_WIDGET(gtk_builder_get_object(builder, "setting-raw"));
preview = GTK_WIDGET(gtk_builder_get_object(builder, "preview"));
main_stack = GTK_WIDGET(gtk_builder_get_object(builder, "main_stack"));
@@ -916,6 +930,7 @@ activate(GtkApplication *app, gpointer data)
g_signal_connect(iso_button, "clicked", G_CALLBACK(open_iso_controls), NULL);
g_signal_connect(shutter_button, "clicked", G_CALLBACK(open_shutter_controls), NULL);
+ g_signal_connect(flash_button, "clicked", G_CALLBACK(flash_button_clicked), NULL);
// Setup actions
create_simple_action(app, "capture", G_CALLBACK(run_capture_action));