main.cc
C++ source, Unicode text, UTF-8 text
1
/*
2
Camera+ for Halium-based GNU/Linux phones
3
Copyright 2026, roundabout-host.com
4
5
This program is free software: you can redistribute it and/or modify
6
it under the terms of the GNU General Public License as published by
7
the Free Software Foundation, either version 3 of the License, or
8
(at your option) any later version.
9
10
This program is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
GNU General Public License for more details.
14
15
You should have received a copy of the GNU General Public License
16
along with this program. If not, see <https://www.gnu.org/licenses/>.
17
*/
18
19
#define LIBFEEDBACK_USE_UNSTABLE_API 1
20
21
#include <droidmedia/droidmediacamera.h>
22
#include <cstdio>
23
#include <cstdlib>
24
#include <cstring>
25
#include <gtkmm.h>
26
#include <glibmm.h>
27
#include <memory>
28
#include <vector>
29
#include <libfeedback.h>
30
#include <iostream>
31
#include <thread>
32
#include <chrono>
33
#include <ctime>
34
#include <sstream>
35
#include <iomanip>
36
#include <unordered_map>
37
38
static inline uint8_t clamp(int v) {
39
return v < 0 ? 0 : (v > 255 ? 255 : v);
40
}
41
42
inline void set_expand_in_direction(Gtk::Widget &widget, Gtk::Orientation orientation) {
43
if(orientation == Gtk::Orientation::HORIZONTAL) {
44
widget.set_vexpand(false);
45
widget.set_hexpand(true);
46
} else {
47
widget.set_vexpand(true);
48
widget.set_hexpand(false);
49
}
50
}
51
52
std::string write_parameters(std::unordered_map<std::string, std::string> const parameters) {
53
std::stringstream str;
54
for(auto const ¶m: parameters) {
55
str << param.first << '=' << param.second << ';';
56
}
57
return str.str();
58
}
59
60
std::map<std::string, int> const ORIENTATIONS = {
61
{"", 0},
62
{"normal", 0},
63
{"left-up", 90},
64
{"bottom-up", 180},
65
{"right-up", 270}
66
};
67
68
std::vector<std::pair<int, int>> parse_size_list(std::string const &sizes) {
69
std::vector<std::pair<int, int>> result;
70
std::stringstream str(sizes);
71
while(!str.eof()) {
72
int w, h;
73
str >> w; str.get(); str >> h; str.get();
74
result.emplace_back(w, h);
75
}
76
return result;
77
}
78
79
std::vector<int> parse_tuple(std::string const &value) {
80
std::vector<int> result;
81
std::stringstream str(value);
82
if(str.get() != '(') {
83
return result; // which is currently empty
84
}
85
while(str.peek() != ')') {
86
int a;
87
str >> a;
88
if(str.peek() == ',') {
89
str.get();
90
}
91
result.push_back(a);
92
}
93
return result;
94
}
95
96
class AndroCamera : public Glib::Object {
97
private:
98
int camera_id;
99
DroidMediaCameraCallbacks cbs{};
100
DroidMediaBufferQueueCallbacks buffer_cbs{};
101
DroidMediaBufferQueue *bq = nullptr;
102
103
static void buffers_released_cb(void *self) {
104
fprintf(stderr, "buffers_released_cb\n");
105
}
106
107
static bool frame_available_cb(void *self, DroidMediaBuffer *buffer) {
108
auto cam = static_cast<AndroCamera*>(self);
109
110
DroidMediaBufferInfo info{};
111
droid_media_buffer_get_info(buffer, &info);
112
113
DroidMediaBufferYCbCr ycbcr{};
114
if(!droid_media_buffer_lock_ycbcr(buffer, DROID_MEDIA_BUFFER_LOCK_READ, &ycbcr)) {
115
droid_media_buffer_release(buffer, nullptr, nullptr);
116
return true;
117
}
118
auto rgb = std::make_shared<std::vector<uint8_t>>(info.width * info.height * 4);
119
uint8_t *dst = rgb->data();
120
for(int y = 0; y < info.height; ++y) {
121
uint8_t *Yrow = static_cast<uint8_t*>(ycbcr.y) + y * ycbcr.ystride;
122
uint8_t *VUrow = static_cast<uint8_t*>(ycbcr.cb) + (y / 2) * ycbcr.cstride;
123
uint8_t *UVrow = static_cast<uint8_t*>(ycbcr.cr) + (y / 2) * ycbcr.cstride;
124
125
for(int x = 0; x < info.width; ++x) {
126
int yy = Yrow[x];
127
128
int cr = UVrow[(x / 2) * ycbcr.chroma_step] - 128;
129
int cb = VUrow[(x / 2) * ycbcr.chroma_step] - 128;
130
131
int r = yy + ((91881 * cr) >> 16);
132
int g = yy - ((22554 * cb + 46802 * cr) >> 16);
133
int b = yy + ((116130 * cb) >> 16);
134
135
*dst++ = clamp(b);
136
*dst++ = clamp(g);
137
*dst++ = clamp(r);
138
*dst++ = 0xFF;
139
}
140
}
141
142
droid_media_buffer_unlock(buffer);
143
droid_media_buffer_release(buffer, nullptr, nullptr);
144
145
// Send to GTK main thread safely
146
Glib::signal_idle().connect_once([cam, rgb, w = info.width, h = info.height] {
147
cam->signal_frame_available.emit(rgb, w, h);
148
});
149
150
return true;
151
}
152
153
static bool buffer_created_cb(void *self, DroidMediaBuffer *) {
154
return true;
155
}
156
157
static void shutter_cb(void *self) {
158
fprintf(stderr, "shutter_cb\n");
159
}
160
161
static void compressed_image_cb(void *self, DroidMediaData *mem) {
162
if(!mem || !mem->data || mem->size == 0) {
163
fprintf(stderr, "compressed_image_cb: empty image\n");
164
return;
165
}
166
if(auto self_casted = static_cast<AndroCamera*>(self)) {
167
self_casted->signal_compressed_image.emit(mem);
168
}
169
}
170
171
public:
172
DroidMediaCamera *camera = nullptr;
173
sigc::signal<void(DroidMediaData*)> signal_compressed_image;
174
sigc::signal<void(std::shared_ptr<std::vector<uint8_t>>, int, int)> signal_frame_available;
175
std::unordered_map<std::string, std::string> parameters;
176
DroidMediaCameraInfo physical_info;
177
std::vector<std::pair<int, int>> picture_sizes, preview_sizes, video_sizes;
178
std::vector<int> active_array;
179
180
explicit AndroCamera(int camera_id) : Glib::Object(), camera_id(camera_id) {}
181
182
void connect() {
183
// Provides facing and orientation
184
droid_media_camera_get_info(&physical_info, camera_id);
185
186
camera = droid_media_camera_connect(camera_id);
187
188
cbs.shutter_cb = shutter_cb;
189
cbs.compressed_image_cb = compressed_image_cb;
190
droid_media_camera_set_callbacks(camera, &cbs, this);
191
192
buffer_cbs.buffers_released = buffers_released_cb;
193
buffer_cbs.frame_available = frame_available_cb;
194
buffer_cbs.buffer_created = buffer_created_cb;
195
196
bq = droid_media_camera_get_buffer_queue(camera);
197
droid_media_buffer_queue_set_callbacks(bq, &buffer_cbs, this);
198
199
std::stringstream param;
200
param << droid_media_camera_get_parameters(camera);
201
while(!param.eof()) {
202
std::string key, value;
203
std::getline(param, key, '=');
204
std::getline(param, value, ';');
205
parameters[key] = value;
206
}
207
208
picture_sizes = parse_size_list(parameters.at("picture-size-values"));
209
video_sizes = parse_size_list(parameters.at("video-size-values"));
210
preview_sizes = parse_size_list(parameters.at("preview-size-values"));
211
active_array = parse_tuple(parameters.at("active-array-size"));
212
}
213
214
void disconnect() {
215
droid_media_camera_disconnect(camera);
216
camera = nullptr;
217
cbs = {};
218
buffer_cbs = {};
219
physical_info = {};
220
}
221
};
222
223
class PanoramaCamera : public Gtk::Application {
224
Gtk::Window *window = nullptr;
225
Gtk::Box *box = nullptr, *format_box = nullptr, *format_resolution_control = nullptr, *control_row = nullptr;
226
Gtk::Scale *format_scale = nullptr;
227
Gtk::Revealer *format_bar = nullptr;
228
Gtk::DrawingArea *drawing = nullptr;
229
Gtk::Button *capture_button = nullptr, *format_down_button = nullptr, *format_up_button = nullptr;
230
Gtk::Label *format_label = nullptr;
231
232
Glib::RefPtr<Gtk::Adjustment> format_adjustment;
233
234
std::unique_ptr<AndroCamera> cam;
235
Cairo::RefPtr<Cairo::ImageSurface> surface;
236
Glib::RefPtr<Gio::DBus::Connection> dbus;
237
Glib::RefPtr<Gio::DBus::Proxy> accelerometer;
238
239
Glib::RefPtr<Gtk::CssProvider> css;
240
Glib::RefPtr<Gdk::Surface> window_surface;
241
Glib::RefPtr<Gtk::GestureClick> clicker;
242
243
sigc::connection compressed_image_connection, frame_available_connection, accelerometer_connection;
244
245
std::unordered_map<std::string, std::string> parameters;
246
247
int device_rotation_degrees = 0;
248
249
void on_compressed_image(DroidMediaData *mem) {
250
std::vector<uint8_t> image_copy(static_cast<uint8_t*>(mem->data), static_cast<uint8_t*>(mem->data) + mem->size);
251
std::thread([image_copy = std::move(image_copy)]() mutable {
252
auto now = std::chrono::system_clock::now();
253
auto now_time_t = std::chrono::system_clock::to_time_t(now);
254
std::tm local_time = *std::localtime(&now_time_t);
255
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
256
257
std::ostringstream filename;
258
filename.imbue(std::locale::classic());
259
filename << (local_time.tm_year + 1900) << '-'
260
<< std::setw(2) << std::setfill('0') << (local_time.tm_mon + 1) << '-'
261
<< std::setw(2) << std::setfill('0') << local_time.tm_mday << '_'
262
<< std::setw(2) << std::setfill('0') << local_time.tm_hour << '-'
263
<< std::setw(2) << std::setfill('0') << local_time.tm_min << '-'
264
<< std::setw(2) << std::setfill('0') << local_time.tm_sec << '.'
265
<< std::setw(3) << std::setfill('0') << ms.count()
266
<< ".jpeg";
267
FILE *f = fopen(filename.str().c_str(), "wb");
268
if(!f) {
269
perror("fopen");
270
return;
271
}
272
fwrite(image_copy.data(), 1, image_copy.size(), f);
273
fclose(f);
274
fprintf(stderr, "%s written: %zu bytes\n", filename.str().c_str(), image_copy.size());
275
}).detach();
276
}
277
278
void on_frame_available(std::shared_ptr<std::vector<uint8_t>> pixels, int width, int height) {
279
// TODO: fix repeated allocations
280
if(!(width && height && pixels)) {
281
return;
282
}
283
int actual_orientation = (cam->physical_info.orientation + device_rotation_degrees) % 360;
284
if(actual_orientation == 0) {
285
surface = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, width, height);
286
287
uint8_t *dst = surface->get_data();
288
const uint8_t *src = pixels->data();
289
int stride = surface->get_stride();
290
291
for(int y = 0; y < height; ++y) {
292
memcpy(dst + y * stride, src + y * width * 4, width * 4);
293
}
294
295
surface->mark_dirty();
296
drawing->queue_draw();
297
} else if(actual_orientation == 180) {
298
surface = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, width, height);
299
300
uint8_t *dst = surface->get_data();
301
const uint8_t *src = pixels->data();
302
int stride = surface->get_stride();
303
304
for(int y = 0; y < height; ++y) {
305
for(int x = 0; x < width; ++x) {
306
const uint8_t *src_px = src + ((height - 1 - y) * width + (width - 1 - x)) * 4;
307
uint8_t *dst_px = dst + y * stride + x * 4;
308
memcpy(dst_px, src_px, 4);
309
}
310
}
311
312
surface->mark_dirty();
313
drawing->queue_draw();
314
} else if(actual_orientation == 90) {
315
surface = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, height, width);
316
317
uint8_t *dst = surface->get_data();
318
const uint8_t *src = pixels->data();
319
int stride = surface->get_stride();
320
321
for(int y = 0; y < width; ++y) {
322
for(int x = 0; x < height; ++x) {
323
const uint8_t *src_px = src + ((height - 1 - x) * width + y) * 4;
324
uint8_t *dst_px = dst + y * stride + x * 4;
325
memcpy(dst_px, src_px, 4);
326
}
327
}
328
329
surface->mark_dirty();
330
drawing->queue_draw();
331
} else if(actual_orientation == 270) {
332
surface = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, height, width);
333
334
uint8_t *dst = surface->get_data();
335
const uint8_t *src = pixels->data();
336
int stride = surface->get_stride();
337
338
for(int y = 0; y < width; ++y) {
339
for(int x = 0; x < height; ++x) {
340
const uint8_t *src_px = src + ((height - 1 - x) * width + y) * 4;
341
uint8_t *dst_px = dst + (width - y - 1) * stride + (height - x - 1) * 4;
342
memcpy(dst_px, src_px, 4);
343
}
344
}
345
346
surface->mark_dirty();
347
drawing->queue_draw();
348
}
349
}
350
351
public:
352
PanoramaCamera() : Gtk::Application("com.roundabout_host.roundabout.Camera", Gio::Application::Flags::NONE) {}
353
354
static Glib::RefPtr<PanoramaCamera> create() {
355
return Glib::make_refptr_for_instance<PanoramaCamera>(new PanoramaCamera());
356
}
357
358
void on_startup() override {
359
// TODO: i18n
360
Gtk::Application::on_startup();
361
dbus = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::SYSTEM);
362
droid_media_init();
363
lfb_init("com.roundabout_host.roundabout.Camera", nullptr);
364
css = Gtk::CssProvider::create();
365
css->load_from_string(
366
".panorama-camera-window { "
367
"background-color: #000000; "
368
"}"
369
);
370
Gtk::StyleContext::add_provider_for_display(Gdk::Display::get_default(), css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
371
372
int count = droid_media_camera_get_number_of_cameras();
373
if(count < 1) {
374
fprintf(stderr, "No cameras available!\n");
375
return;
376
}
377
378
cam = std::make_unique<AndroCamera>(0);
379
cam->connect();
380
381
format_adjustment = Gtk::Adjustment::create(0, 0, cam->picture_sizes.size() - 1);
382
format_adjustment->signal_value_changed().connect([this]() {
383
int value = format_adjustment->get_value(), w = cam->picture_sizes.at(cam->picture_sizes.size() - 1 - value).first, h = cam->picture_sizes.at(cam->picture_sizes.size() - 1 - value).second;
384
droid_media_camera_stop_preview(cam->camera);
385
parameters["picture-size"] = std::to_string(w) + "x" + std::to_string(h);
386
387
std::string new_params = write_parameters(parameters);
388
droid_media_camera_set_parameters(cam->camera, new_params.c_str());
389
droid_media_camera_start_preview(cam->camera);
390
droid_media_camera_set_parameters(cam->camera, new_params.c_str());
391
if(format_label) {
392
format_label->set_text(std::to_string(w) + "×" + std::to_string(h));
393
}
394
std::cerr << w << 'x' << h << '\n';
395
});
396
397
std::cout << droid_media_camera_get_parameters(cam->camera) << '\n';
398
399
parameters["picture-size"] = "1920x1080";
400
parameters["preview-size"] = "1024x768";
401
parameters["video-size"] = "176x144";
402
parameters["sensitivity"] = "100";
403
parameters["exposure-time"] = "83200";
404
parameters["flash-mode"] = "off";
405
parameters["focus-mode"] = "auto";
406
407
droid_media_camera_set_parameters(
408
cam->camera,
409
write_parameters(parameters).c_str()
410
);
411
412
compressed_image_connection = cam->signal_compressed_image.connect(sigc::mem_fun(*this, &PanoramaCamera::on_compressed_image));
413
frame_available_connection = cam->signal_frame_available.connect(sigc::mem_fun(*this, &PanoramaCamera::on_frame_available));
414
415
if(!cam->camera) {
416
fprintf(stderr, "Failed to connect camera!\n");
417
return;
418
}
419
420
if(!droid_media_camera_start_preview(cam->camera)) {
421
fprintf(stderr, "start_preview failed\n");
422
cam->disconnect();
423
return;
424
}
425
426
droid_media_camera_set_parameters(
427
cam->camera,
428
write_parameters(parameters).c_str()
429
);
430
431
fprintf(stderr, "start_preview OK\n");
432
}
433
434
void set_gui_orientation(Gtk::Orientation orientation) {
435
box->set_orientation(orientation);
436
auto reverse_orientation = orientation == Gtk::Orientation::HORIZONTAL? Gtk::Orientation::VERTICAL : Gtk::Orientation::HORIZONTAL;
437
438
control_row->set_orientation(reverse_orientation);
439
format_scale->set_orientation(reverse_orientation);
440
format_resolution_control->set_orientation(reverse_orientation);
441
442
set_expand_in_direction(*format_bar, reverse_orientation);
443
set_expand_in_direction(*format_scale, reverse_orientation);
444
445
if(orientation == Gtk::Orientation::HORIZONTAL) {
446
format_scale->set_inverted(true);
447
format_bar->set_transition_type(Gtk::RevealerTransitionType::SLIDE_LEFT);
448
format_down_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-down-symbolic")));
449
format_up_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-up-symbolic")));
450
format_resolution_control->reorder_child_after(*format_down_button, *format_scale);
451
format_resolution_control->reorder_child_at_start(*format_up_button);
452
} else {
453
format_scale->set_inverted(false);
454
format_bar->set_transition_type(Gtk::RevealerTransitionType::SLIDE_UP);
455
format_down_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-previous-symbolic")));
456
format_up_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-next-symbolic")));
457
format_resolution_control->reorder_child_after(*format_up_button, *format_scale);
458
format_resolution_control->reorder_child_at_start(*format_down_button);
459
}
460
}
461
462
void set_window_size() {
463
if(window_surface->get_width() > window_surface->get_height()) {
464
set_gui_orientation(Gtk::Orientation::HORIZONTAL);
465
} else {
466
set_gui_orientation(Gtk::Orientation::VERTICAL);
467
}
468
}
469
470
void on_orientation_changed(std::string const &orientation) {
471
device_rotation_degrees = ORIENTATIONS.at(orientation);
472
int actual_orientation = (cam->physical_info.orientation + device_rotation_degrees) % 360;
473
std::cout << "rotation is " << actual_orientation << " degrees!\n";
474
parameters["jpeg-orientation"] = std::to_string(actual_orientation);
475
droid_media_camera_set_parameters(cam->camera, write_parameters(parameters).c_str());
476
}
477
478
void on_activate() override {
479
Gtk::Application::on_activate();
480
window = Gtk::make_managed<Gtk::Window>();
481
auto settings = window->get_settings();
482
settings->property_gtk_application_prefer_dark_theme().set_value(true);
483
drawing = Gtk::make_managed<Gtk::DrawingArea>();
484
485
drawing->set_draw_func([this](const Cairo::RefPtr<Cairo::Context>& cr, int w, int h) {
486
if(surface) {
487
double scale = std::min(double(w) / surface->get_width(),
488
double(h) / surface->get_height());
489
double offset_x = (w - surface->get_width() * scale) / 2;
490
double offset_y = (h - surface->get_height() * scale) / 2;
491
cr->translate(offset_x, offset_y);
492
cr->scale(scale, scale);
493
cr->set_source(surface, 0, 0);
494
cr->paint();
495
}
496
});
497
498
window->add_css_class("panorama-camera-window");
499
box = Gtk::make_managed<Gtk::Box>();
500
box->append(*drawing);
501
control_row = Gtk::make_managed<Gtk::Box>();
502
503
format_bar = Gtk::make_managed<Gtk::Revealer>();
504
auto format_button = Gtk::make_managed<Gtk::ToggleButton>();
505
format_label = Gtk::make_managed<Gtk::Label>();
506
format_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("image-x-generic-symbolic")));
507
format_button->signal_clicked().connect([this, format_button]() {
508
format_bar->set_reveal_child(format_button->get_active());
509
});
510
format_scale = Gtk::make_managed<Gtk::Scale>();
511
format_scale->set_adjustment(format_adjustment);
512
format_scale->set_round_digits(0);
513
format_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
514
format_resolution_control = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
515
format_down_button = Gtk::make_managed<Gtk::Button>();
516
format_down_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-previous-symbolic")));
517
format_down_button->signal_clicked().connect([this]() {
518
format_adjustment->set_value(std::max(format_adjustment->get_lower(), format_adjustment->get_value() - 1));
519
});
520
format_up_button = Gtk::make_managed<Gtk::Button>();
521
format_up_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-next-symbolic")));
522
format_up_button->signal_clicked().connect([this]() {
523
format_adjustment->set_value(std::min(format_adjustment->get_upper(), format_adjustment->get_value() + 1));
524
});
525
format_resolution_control->append(*format_down_button);
526
format_resolution_control->append(*format_scale);
527
format_resolution_control->append(*format_up_button);
528
format_box->append(*format_label);
529
format_box->append(*format_resolution_control);
530
format_bar->set_child(*format_box);
531
format_bar->set_hexpand(true);
532
control_row->append(*format_button);
533
box->append(*format_bar);
534
535
format_adjustment->set_value(format_adjustment->get_upper());
536
537
box->append(*control_row);
538
capture_button = Gtk::make_managed<Gtk::Button>();
539
capture_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("camera-photo-symbolic")));
540
capture_button->signal_clicked().connect([this]() {
541
LfbEvent *event = lfb_event_new("camera-shutter");
542
lfb_event_trigger_feedback(event, NULL);
543
g_object_unref(event);
544
droid_media_camera_take_picture(cam->camera, 0);
545
});
546
box->append(*capture_button);
547
clicker = Gtk::GestureClick::create();
548
clicker->signal_released().connect([this](int n_presses, double x, double y) {
549
// TODO: handle differing aspect ratios
550
int actual_orientation = (cam->physical_info.orientation + device_rotation_degrees) % 360;
551
int focus_x, focus_y;
552
if(actual_orientation == 0) {
553
focus_x = x / drawing->get_width() * (cam->active_array.at(2) - cam->active_array.at(0)) + cam->active_array.at(0);
554
focus_y = y / drawing->get_height() * (cam->active_array.at(3) - cam->active_array.at(1)) + cam->active_array.at(1);
555
} else if(actual_orientation == 90) {
556
focus_x = y / drawing->get_height() * (cam->active_array.at(2) - cam->active_array.at(0)) + cam->active_array.at(0);
557
focus_y = (1 - x / drawing->get_width()) * (cam->active_array.at(3) - cam->active_array.at(1)) + cam->active_array.at(1);
558
} else if(actual_orientation == 180) {
559
focus_x = (1 - x / drawing->get_width()) * (cam->active_array.at(2) - cam->active_array.at(0)) + cam->active_array.at(0);
560
focus_y = (1 - y / drawing->get_height()) * (cam->active_array.at(3) - cam->active_array.at(1)) + cam->active_array.at(1);
561
} else if(actual_orientation == 270) {
562
focus_x = (1 - y / drawing->get_height()) * (cam->active_array.at(2) - cam->active_array.at(0)) + cam->active_array.at(0);
563
focus_y = x / drawing->get_width() * (cam->active_array.at(3) - cam->active_array.at(1)) + cam->active_array.at(1);
564
}
565
int focus_area_size = (cam->active_array.at(3) - cam->active_array.at(1) + cam->active_array.at(2) - cam->active_array.at(0)) / 128;
566
std::ostringstream str;
567
str.imbue(std::locale::classic());
568
str << '(' << focus_x - focus_area_size << ',' << focus_y - focus_area_size << ',' << focus_x + focus_area_size << ',' << focus_y + focus_area_size << ',' << 1000 << ')';
569
std::cout << "focus is " << str.str() << '\n';
570
parameters["focus-areas"] = str.str();
571
droid_media_camera_set_parameters(cam->camera, write_parameters(parameters).c_str());
572
std::cout << "AF result: " << droid_media_camera_start_auto_focus(cam->camera) << '\n';
573
});
574
drawing->add_controller(clicker);
575
drawing->set_hexpand(true);
576
drawing->set_vexpand(true);
577
window->set_child(*box);
578
add_window(*window);
579
window->present();
580
window_surface = window->get_surface();
581
window_surface->property_width().signal_changed().connect(sigc::mem_fun(*this, &PanoramaCamera::set_window_size));
582
window_surface->property_height().signal_changed().connect(sigc::mem_fun(*this, &PanoramaCamera::set_window_size));
583
584
for(auto e: cam->picture_sizes) {
585
std::cout << e.first << "*" << e.second << '\n';
586
}
587
588
// To support screen rotation
589
accelerometer = Gio::DBus::Proxy::create_sync(
590
dbus,
591
"net.hadess.SensorProxy",
592
"/net/hadess/SensorProxy",
593
"net.hadess.SensorProxy"
594
);
595
Glib::VariantBase value;
596
accelerometer->get_cached_property(value, "AccelerometerOrientation");
597
598
if(value) {
599
auto orientation = value.get_dynamic<std::string>();
600
on_orientation_changed(orientation);
601
}
602
accelerometer->call_sync("ClaimAccelerometer");
603
accelerometer_connection = accelerometer->signal_properties_changed().connect([this](const Gio::DBus::Proxy::MapChangedProperties& changed, const std::vector<Glib::ustring>&) {
604
auto it = changed.find("AccelerometerOrientation");
605
if(it != changed.end()) {
606
auto orientation = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(it->second).get();
607
on_orientation_changed(orientation);
608
}
609
}
610
);
611
}
612
613
void on_shutdown() override {
614
Gtk::Application::on_shutdown();
615
accelerometer->call_sync("ReleaseAccelerometer");
616
if(accelerometer_connection) {
617
accelerometer_connection.disconnect();
618
}
619
if(frame_available_connection) {
620
frame_available_connection.disconnect();
621
}
622
if(compressed_image_connection) {
623
compressed_image_connection.disconnect();
624
}
625
droid_media_camera_stop_preview(cam->camera);
626
cam->disconnect();
627
}
628
};
629
630
int main(int argc, char *argv[]) {
631
auto app = PanoramaCamera::create();
632
return app->run(argc, argv);
633
}
634
635