Automatically rotate image based on accelerometer values

Device orientation is provided by net.hadess.SensorProxy
iio-proxy-service. If there is no accelerometer on target platform,
then window orientation from X11/wayland is used as before.
This commit is contained in:
Andrey Skvortsov
2025-05-04 00:33:51 +03:00
parent b4b0a61230
commit 4db24ddc5e
8 changed files with 233 additions and 3 deletions

View File

@@ -27,6 +27,9 @@ finish-args:
# camera shutter feedback # camera shutter feedback
- --talk-name=org.sigxcpu.Feedback - --talk-name=org.sigxcpu.Feedback
# accelerometer sensor for orientation
- --system-talk-name=net.hadess.SensorProxy
modules: modules:
- name: megapixels - name: megapixels
buildsystem: meson buildsystem: meson

View File

@@ -54,6 +54,7 @@ executable('megapixels',
'src/matrix.c', 'src/matrix.c',
'src/pipeline.c', 'src/pipeline.c',
'src/process_pipeline.c', 'src/process_pipeline.c',
'src/rotation.c',
'src/zbar_pipeline.c', 'src/zbar_pipeline.c',
'src/dcp.c', 'src/dcp.c',
resources, resources,
@@ -126,6 +127,8 @@ if clang_format.found()
'src/pipeline.h', 'src/pipeline.h',
'src/process_pipeline.c', 'src/process_pipeline.c',
'src/process_pipeline.h', 'src/process_pipeline.h',
'src/rotation.c',
'src/rotation.h',
'src/zbar_pipeline.c', 'src/zbar_pipeline.c',
'src/zbar_pipeline.h', 'src/zbar_pipeline.h',
'tools/camera_test.c', 'tools/camera_test.c',

View File

@@ -96,6 +96,8 @@ update_process_pipeline()
.preview_width = state_io.preview_width, .preview_width = state_io.preview_width,
.preview_height = state_io.preview_height, .preview_height = state_io.preview_height,
.device_rotation = state_io.device_rotation, .device_rotation = state_io.device_rotation,
.device_accel_rotation = state_io.device_accel_rotation,
.device_accel_rotation_good = state_io.device_accel_rotation_good,
.gain.control = state_io.gain.control, .gain.control = state_io.gain.control,
.gain.auto_control = state_io.gain.auto_control, .gain.auto_control = state_io.gain.auto_control,
@@ -669,6 +671,8 @@ update_state(MPPipeline *pipeline, const mp_state_io *new_state)
state_io.preview_width = new_state->preview_width; state_io.preview_width = new_state->preview_width;
state_io.preview_height = new_state->preview_height; state_io.preview_height = new_state->preview_height;
state_io.device_rotation = new_state->device_rotation; state_io.device_rotation = new_state->device_rotation;
state_io.device_accel_rotation = new_state->device_accel_rotation;
state_io.device_accel_rotation_good = new_state->device_accel_rotation_good;
if (state_io.camera) { if (state_io.camera) {
state_io.gain.value = new_state->gain.value; state_io.gain.value = new_state->gain.value;

View File

@@ -4,6 +4,7 @@
#include "gl_util.h" #include "gl_util.h"
#include "io_pipeline.h" #include "io_pipeline.h"
#include "process_pipeline.h" #include "process_pipeline.h"
#include "rotation.h"
#include "state.h" #include "state.h"
#include <asm/errno.h> #include <asm/errno.h>
#include <assert.h> #include <assert.h>
@@ -120,6 +121,8 @@ update_io_pipeline()
.preview_width = state.preview_width, .preview_width = state.preview_width,
.preview_height = state.preview_height, .preview_height = state.preview_height,
.device_rotation = state.device_rotation, .device_rotation = state.device_rotation,
.device_accel_rotation = state.device_accel_rotation,
.device_accel_rotation_good = state.device_accel_rotation_good,
.gain.control = state.gain.control, .gain.control = state.gain.control,
.gain.auto_control = state.gain.auto_control, .gain.auto_control = state.gain.auto_control,
@@ -1291,6 +1294,17 @@ xevent_handler(GdkDisplay *display, XEvent *xevent, gpointer data)
} }
#endif // GDK_WINDOWING_X11 #endif // GDK_WINDOWING_X11
static void
rotation_sensor_handler(int rotation, bool good)
{
if (rotation != state.device_accel_rotation ||
good != state.device_accel_rotation_good) {
state.device_accel_rotation = rotation;
state.device_accel_rotation_good = good;
update_io_pipeline();
}
}
static void static void
activate(GtkApplication *app, gpointer data) activate(GtkApplication *app, gpointer data)
{ {
@@ -1472,6 +1486,7 @@ activate(GtkApplication *app, gpointer data)
GDBusConnection *conn = GDBusConnection *conn =
g_application_get_dbus_connection(G_APPLICATION(app)); g_application_get_dbus_connection(G_APPLICATION(app));
mp_flash_gtk_init(conn); mp_flash_gtk_init(conn);
mp_device_rotation_init(rotation_sensor_handler);
if (state.configuration->count > 0) { if (state.configuration->count > 0) {
mp_io_pipeline_start(); mp_io_pipeline_start();
@@ -1501,6 +1516,7 @@ shutdown(GApplication *app, gpointer data)
#ifdef DEBUG #ifdef DEBUG
mp_io_pipeline_stop(); mp_io_pipeline_stop();
mp_flash_gtk_clean(); mp_flash_gtk_clean();
mp_device_rotation_clean();
g_clear_object(&fb_settings); g_clear_object(&fb_settings);
g_clear_object(&capture_event); g_clear_object(&capture_event);

View File

@@ -1564,7 +1564,9 @@ update_state(MPPipeline *pipeline, const mp_state_proc *new_state)
new_state->camera->current_mode) || new_state->camera->current_mode) ||
state_proc.preview_width != new_state->preview_width || state_proc.preview_width != new_state->preview_width ||
state_proc.preview_height != new_state->preview_height || state_proc.preview_height != new_state->preview_height ||
state_proc.device_rotation != new_state->device_rotation; state_proc.device_rotation != new_state->device_rotation ||
state_proc.device_accel_rotation != new_state->device_accel_rotation ||
state_proc.device_accel_rotation_good != new_state->device_accel_rotation_good;
bool format_changed = state_proc.mode == NULL; bool format_changed = state_proc.mode == NULL;
@@ -1579,6 +1581,8 @@ update_state(MPPipeline *pipeline, const mp_state_proc *new_state)
state_proc.preview_height = new_state->preview_height; state_proc.preview_height = new_state->preview_height;
state_proc.device_rotation = new_state->device_rotation; state_proc.device_rotation = new_state->device_rotation;
state_proc.device_accel_rotation = new_state->device_accel_rotation;
state_proc.device_accel_rotation_good = new_state->device_accel_rotation_good;
state_proc.burst_length = new_state->burst_length; state_proc.burst_length = new_state->burst_length;
state_proc.balance[0] = new_state->balance[0]; state_proc.balance[0] = new_state->balance[0];
@@ -1602,9 +1606,12 @@ update_state(MPPipeline *pipeline, const mp_state_proc *new_state)
} }
if (output_changed || camera_changed) { if (output_changed || camera_changed) {
int rotation;
rotation = state_proc.device_accel_rotation_good ?
state_proc.device_accel_rotation :
state_proc.device_rotation;
state_proc.camera_rotation = mod( state_proc.camera_rotation = mod(
state_proc.mode->rotation - state_proc.device_rotation, 360); state_proc.mode->rotation - rotation, 360);
on_output_changed(format_changed); on_output_changed(format_changed);
} }

182
src/rotation.c Normal file
View File

@@ -0,0 +1,182 @@
#include "gtk/gtk.h"
#include <stdbool.h>
#include "rotation.h"
struct sensor_cb_data {
sensor_cb rotation_update;
};
static GDBusProxy *iio_proxy;
static bool rotation_valid = false;
static int rotation;
struct sensor_cb_data cb_data;
static int
rotation_string_to_degree(const gchar* val)
{
int rot = -1;
if (!g_ascii_strcasecmp(val, "normal"))
rot = 0;
if (!g_ascii_strcasecmp(val, "right-up"))
rot = 270;
if (!g_ascii_strcasecmp(val, "left-up"))
rot = 90;
if (!g_ascii_strcasecmp(val, "bottom-up"))
rot = 180;
return rot;
}
static void
rotation_properties_changed (GDBusProxy *proxy,
GVariant *changed_properties,
GStrv invalidated_properties,
gpointer user_data)
{
GVariant *v;
GVariantDict dict;
const gchar *val;
struct sensor_cb_data *cb_data = (struct sensor_cb_data*)user_data;
g_variant_dict_init (&dict, changed_properties);
if (g_variant_dict_contains (&dict, "HasAccelerometer")) {
v = g_dbus_proxy_get_cached_property (iio_proxy, "HasAccelerometer");
rotation_valid = g_variant_get_boolean (v);
g_debug ("accelerometer changed: %d\n", rotation_valid);
g_variant_unref (v);
}
if (g_variant_dict_contains (&dict, "AccelerometerOrientation")) {
v = g_dbus_proxy_get_cached_property (iio_proxy, "AccelerometerOrientation");
val = g_variant_get_string (v, NULL);
rotation = rotation_string_to_degree(val);
g_variant_unref (v);
}
g_variant_dict_clear (&dict);
g_debug ("sensor rotation changed: %d %d\n",
rotation, rotation_valid);
cb_data->rotation_update(rotation, rotation_valid);
}
static void
rotation_get_initial_values (struct sensor_cb_data *cb_data)
{
GVariant *v;
const gchar *val;
v = g_dbus_proxy_get_cached_property (iio_proxy, "HasAccelerometer");
rotation_valid = g_variant_get_boolean (v);
if (rotation_valid) {
g_variant_unref (v);
v = g_dbus_proxy_get_cached_property (iio_proxy, "AccelerometerOrientation");
val = g_variant_get_string (v, NULL);
rotation = rotation_string_to_degree(val);
g_debug ("rotation: %d\n", rotation);
} else
g_debug ("no accelerometer\n");
g_variant_unref (v);
cb_data->rotation_update(rotation, rotation_valid);
}
static void
rotation_accelerometer_claimed(GDBusProxy *proxy,
GAsyncResult *res,
gpointer user_data)
{
struct sensor_cb_data *cb_data = (struct sensor_cb_data*)user_data;
g_autoptr(GError) error = NULL;
g_autoptr(GVariant) result = g_dbus_proxy_call_finish(proxy, res, &error);
if (!result) {
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Failed to claim accelerometer: %s",
error->message);
return;
}
rotation_get_initial_values (cb_data);
}
static void
rotation_iio_proxy_init (GObject *src,
GAsyncResult *res,
gpointer user_data)
{
struct sensor_cb_data *cb_data = (struct sensor_cb_data*)user_data;
g_autoptr(GError) err = NULL;
iio_proxy = g_dbus_proxy_new_finish(res, &err);
if (!iio_proxy || err) {
g_warning ("Failed to connect to sensor proxy service %s\n",
err->message);
return;
}
g_signal_connect (G_OBJECT (iio_proxy), "g-properties-changed",
G_CALLBACK (rotation_properties_changed),
cb_data);
g_dbus_proxy_call (iio_proxy,
"ClaimAccelerometer",
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
(GAsyncReadyCallback)rotation_accelerometer_claimed,
cb_data);
}
static void
rotation_iio_proxy_appeared_cb (GDBusConnection *connection,
const gchar *name,
const gchar *name_owner,
gpointer user_data)
{
g_debug ("iio-sensor-proxy appeared\n");
g_dbus_proxy_new (connection,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"net.hadess.SensorProxy",
"/net/hadess/SensorProxy",
"net.hadess.SensorProxy",
NULL,
(GAsyncReadyCallback)rotation_iio_proxy_init,
user_data);
}
static void
rotation_iio_proxy_vanished_cb (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
struct sensor_cb_data *cb_data = (struct sensor_cb_data*)user_data;
if (iio_proxy) {
g_clear_object (&iio_proxy);
g_debug ("iio-sensor-proxy vanished\n");
rotation_valid = false;
rotation = 0;
cb_data->rotation_update(rotation, rotation_valid);
}
}
void
mp_device_rotation_init(sensor_cb cb)
{
g_assert(cb != NULL);
cb_data.rotation_update = cb;
g_bus_watch_name (G_BUS_TYPE_SYSTEM,
"net.hadess.SensorProxy",
G_BUS_NAME_WATCHER_FLAGS_NONE,
rotation_iio_proxy_appeared_cb,
rotation_iio_proxy_vanished_cb,
&cb_data,
NULL);
}
void
mp_device_rotation_clean(void)
{
g_object_unref(iio_proxy);
}

9
src/rotation.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef _MP_ROTATION_H
#define _MP_ROTATION_H
typedef void(*sensor_cb)(int, bool);
void mp_device_rotation_init(sensor_cb cb);
void mp_device_rotation_clean(void);
#endif

View File

@@ -29,6 +29,8 @@ typedef struct state_main {
int preview_buffer_height; int preview_buffer_height;
int device_rotation; int device_rotation;
int device_accel_rotation;
bool device_accel_rotation_good;
int burst_length; int burst_length;
@@ -70,6 +72,8 @@ typedef struct state_io {
int preview_width; int preview_width;
int preview_height; int preview_height;
int device_rotation; int device_rotation;
int device_accel_rotation;
bool device_accel_rotation_good;
} mp_state_io; } mp_state_io;
typedef enum aaa_mode { typedef enum aaa_mode {
@@ -93,6 +97,8 @@ typedef struct state_proc {
// Device orientation in degrees // Device orientation in degrees
int device_rotation; int device_rotation;
int device_accel_rotation;
bool device_accel_rotation_good;
// Camera orientation as number // Camera orientation as number
int camera_rotation; int camera_rotation;