2 * cyclabile - CYC Light Augmented Bike Light Emitter
4 * Copyright (C) 2018 Antonio Ospite <ao2@ao2.it>
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include <sys/types.h>
27 #include <linux/input.h>
28 #include <leptonica/allheaders.h>
29 #include <turbojpeg.h>
32 #include "projective_split.h"
34 #include "fps-meter.h"
36 #define M_PI 3.14159265358979323846 /* pi */
38 /* Rotary encoder properties. */
39 #define PULSES_PER_REVOLUTION 120
41 /* Video projector property. */
42 #define HEIGHT_IN_PIXELS 480
44 /* Bike setup properties */
45 #define WHEEL_RADIUS_IN_METERS 0.33
46 #define HEIGHT_IN_METERS 0.92
48 #define PIXELS_PER_METER (HEIGHT_IN_PIXELS / HEIGHT_IN_METERS)
51 static unsigned int run = 1;
58 unsigned int x_correction;
59 unsigned int y_correction;
60 unsigned int zoom_mode;
61 unsigned int power_mode;
69 .zoom_mode = AM7XXX_ZOOM_ORIGINAL,
70 .power_mode = AM7XXX_POWER_LOW,
74 #define POSITION_FILE "/sys/devices/platform/ocp/48304000.epwmss/48304180.eqep/position"
75 static int read_position(int *position)
80 file = fopen(POSITION_FILE, "r");
82 fprintf(stderr, "Cannot open %s\n", POSITION_FILE);
86 ret = fscanf(file, "%d", position);
96 static int read_position(int *position)
103 static int mod(int a, int b)
106 return (c < 0) ? c + b : c;
109 static int projector_device_init(am7xxx_context **ctx, am7xxx_device **dev, unsigned int device_index)
111 int log_level = AM7XXX_LOG_INFO;
114 ret = am7xxx_init(ctx);
116 perror("am7xxx_init");
120 am7xxx_set_log_level(*ctx, log_level);
122 ret = am7xxx_open_device(*ctx, dev, device_index);
124 perror("am7xxx_open_device");
128 ret = am7xxx_set_zoom_mode(*dev, config.zoom_mode);
130 perror("am7xxx_set_zoom_mode");
131 goto err_close_device;
134 ret = am7xxx_set_power_mode(*dev, config.power_mode);
136 perror("am7xxx_set_power_mode");
137 goto err_close_device;
143 am7xxx_close_device(*dev);
145 am7xxx_shutdown(*ctx);
149 static void unset_run(int signo)
155 static int set_signal_handler(int signum, void (*signal_handler)(int))
157 struct sigaction new_action;
158 struct sigaction old_action;
161 new_action.sa_handler = signal_handler;
162 sigemptyset(&new_action.sa_mask);
163 new_action.sa_flags = 0;
165 ret = sigaction(signum, NULL, &old_action);
167 perror("sigaction on old_action");
171 if (old_action.sa_handler != SIG_IGN) {
172 ret = sigaction(signum, &new_action, NULL);
174 perror("sigaction on new_action");
183 static struct p *calc_perspective_map(void)
188 l_float32 *transform_coeffs;
189 struct p *perspective_map = NULL;
197 ptaAddPt(src, config.width, 0);
198 ptaAddPt(src, config.width, config.height);
199 ptaAddPt(src, 0, config.height);
203 goto out_destroy_pta_src;
205 ptaAddPt(dst, config.x_correction, config.y_correction);
206 ptaAddPt(dst, config.width - config.x_correction, config.y_correction);
207 ptaAddPt(dst, config.width, config.height);
208 ptaAddPt(dst, 0, config.height);
210 transform_coeffs = NULL;
211 ret = getProjectiveXformCoeffs(dst, src, &transform_coeffs);
213 goto out_destroy_pta_dst;
215 viewport = boxCreateValid(0, 0, config.width, config.height);
216 if (viewport == NULL)
217 goto out_free_transform_coeffs;
219 perspective_map = _pixProjectiveSampled_precalc_map(viewport, transform_coeffs);
221 boxDestroy(&viewport);
223 out_free_transform_coeffs:
224 LEPT_FREE(transform_coeffs);
230 return perspective_map;
233 static void usage(char *name)
235 printf("usage: %s [OPTIONS]\n\n", name);
236 printf("OPTIONS:\n");
237 printf("\t-f <image_file>\t\tthe image file to upload\n");
238 printf("\t-e <event device>\t\tthe event device to get input from\n");
239 printf("\t-x <X correction>\t\tthe perspective correction in the X direction, in pixels (default 200)\n");
240 printf("\t-y <Y correction>\t\tthe perspective correction in the Y direction, in pixels (default 50)\n");
241 printf("\t-Z <zoom mode>\t\tthe display zoom mode, between %d (original) and %d (test)\n",
242 AM7XXX_ZOOM_ORIGINAL, AM7XXX_ZOOM_TEST);
243 printf("\t-h \t\t\tthis help message\n");
244 printf("\n\nEXAMPLES OF USE:\n");
245 printf("\t%s -f road.png -e /dev/input/event5\n", name);
246 printf("\t%s -f narrow-road.png -e /dev/input/event0 -x 130 -Z 1\n", name);
249 int main(int argc, char *argv[])
252 PIX *transformed_image;
254 struct p *perspective_map;
256 tjhandle jpeg_compressor;
257 unsigned char *out_buf;
258 unsigned long out_buf_size;
260 am7xxx_context *projector_ctx = NULL;
261 am7xxx_device *projector_dev = NULL;
264 struct input_event ev[64];
268 int old_position = 0;
272 struct fps_meter_stats fps_stats;
280 while ((opt = getopt(argc, argv, "e:f:x:y:P:Z:h")) != -1) {
283 if (config.event_device != NULL)
284 fprintf(stderr, "Warning: event device already specified\n");
285 config.event_device = optarg;
288 if (config.image_file != NULL)
289 fprintf(stderr, "Warning: image file already specified\n");
290 config.image_file = optarg;
293 config.x_correction = atoi(optarg); /* atoi() is as nasty as it is easy */
296 config.y_correction = atoi(optarg); /* atoi() is as nasty as it is easy */
299 config.zoom_mode = atoi(optarg); /* atoi() is as nasty as it is easy */
300 switch(config.zoom_mode) {
301 case AM7XXX_ZOOM_ORIGINAL:
303 case AM7XXX_ZOOM_H_V:
304 case AM7XXX_ZOOM_TEST:
307 fprintf(stderr, "Invalid zoom mode value, must be between %d and %d\n",
308 AM7XXX_ZOOM_ORIGINAL, AM7XXX_ZOOM_TEST);
314 config.power_mode = atoi(optarg); /* atoi() is as nasty as it is easy */
315 switch(config.power_mode) {
316 case AM7XXX_POWER_OFF:
317 case AM7XXX_POWER_LOW:
318 case AM7XXX_POWER_MIDDLE:
319 case AM7XXX_POWER_HIGH:
320 case AM7XXX_POWER_TURBO:
323 fprintf(stderr, "Invalid power mode value, must be between %d and %d\n",
324 AM7XXX_POWER_OFF, AM7XXX_POWER_TURBO);
340 if (config.image_file == NULL) {
341 fprintf(stderr, "An image file MUST be specified with the -f option.\n\n");
347 if (config.event_device == NULL) {
348 fprintf(stderr, "An event device MUST be specified with the -e option.\n\n");
354 ret = set_signal_handler(SIGINT, unset_run);
356 perror("setting SIGINT");
360 ret = set_signal_handler(SIGTERM, unset_run);
362 perror("setting SIGTERM");
366 image = pixRead(config.image_file);
368 fprintf(stderr, "Cannot load image\n");
372 lastframe_limit = (pixGetHeight(image) - config.height);
374 config.width = pixGetWidth(image);
376 transformed_image = pixCreate(config.width, config.height, pixGetDepth(image));
377 if (transformed_image == NULL) {
378 fprintf(stderr, "Cannot create image\n");
380 goto out_destroy_image;
383 /* Transform only a part of the whole image */
384 viewport = boxCreateValid(0, y, config.width, config.height);
385 if (viewport == NULL) {
387 goto out_destroy_transformed_image;
390 perspective_map = calc_perspective_map();
391 if (perspective_map == NULL) {
393 goto out_destroy_viewport;
396 /* jpeg encoder init */
397 jpeg_compressor = tjInitCompress();
398 if (jpeg_compressor == NULL) {
399 fprintf(stderr, "tjInitCompress failed: %s\n", tjGetErrorStr());
401 goto out_free_perspective_map;
404 out_buf_size = tjBufSize(config.width, config.height, TJSAMP_420);
405 ret = (int) out_buf_size;
407 fprintf(stderr, "tjBufSize failed: %s\n", tjGetErrorStr());
408 goto out_jpeg_destroy;
410 out_buf = malloc(out_buf_size);
411 if (out_buf == NULL) {
413 goto out_jpeg_destroy;
417 ret = projector_device_init(&projector_ctx, &projector_dev, 0);
419 goto out_free_out_buf;
421 /* input event init */
422 input_fd = open(config.event_device, O_RDONLY | O_NONBLOCK);
426 goto out_am7xxx_shutdown;
429 ret = read_position(&old_position);
434 fps_meter_init(&fps_stats);
437 fps_meter_update(&fps_stats);
440 ret = read(input_fd, ev, sizeof(ev));
441 if (ret < (int)sizeof(ev[0]) && errno != EAGAIN && errno != EWOULDBLOCK) {
448 * Handle keypad input to adjust the perspective map.
449 * This can go in a preliminar "setup" stage instead of the
452 if (ret >= (int)sizeof(ev[0])) {
453 for (i = 0; i < ret / sizeof(ev[0]); i++) {
455 * This is for testing the software with the
458 if (ev[i].type == EV_REL && ev[i].code == REL_WHEEL) {
459 position += ev[i].value;
461 else if (ev[i].type == EV_KEY && ev[i].value > 0) {
464 if (config.y_correction < config.height)
465 config.y_correction += 1;
468 if (config.y_correction > 0)
469 config.y_correction -= 1;
472 if (config.x_correction > 0)
473 config.x_correction -= 1;
476 if (config.x_correction < config.width)
477 config.x_correction += 1;
483 free(perspective_map);
484 perspective_map = calc_perspective_map();
485 if (perspective_map == NULL) {
489 pixClearAll(transformed_image);
495 ret = read_position(&position);
498 position_delta = old_position - position;
499 old_position = position;
501 /* distance in meters */
502 distance = 2 * WHEEL_RADIUS_IN_METERS * M_PI / PULSES_PER_REVOLUTION * position_delta;
504 fprintf(stderr, "position_delta: %d distance: %f pixels: %d\n", position_delta, distance, (int) (distance * PIXELS_PER_METER));
507 /* convert distance to pixels */
508 y += distance * PIXELS_PER_METER;
509 y = mod(y, lastframe_limit);
511 boxSetGeometry(viewport, -1, y, -1, -1);
513 /* Apply the perspective transformation */
514 transformed_image = _pixProjectiveSampled_apply_map_dest_roi(image,
519 ret = tjCompress2(jpeg_compressor, (unsigned char *)pixGetData(transformed_image),
520 config.width, 0, config.height, TJPF_XBGR, &out_buf, &out_buf_size,
521 TJSAMP_420, 70, TJFLAG_NOREALLOC | TJFLAG_FASTDCT);
524 fprintf(stderr, "tjCompress2 failed: %s\n", tjGetErrorStr());
528 ret = am7xxx_send_image_async(projector_dev,
529 AM7XXX_IMAGE_FORMAT_JPEG,
536 perror("am7xxx_send_image");
547 am7xxx_set_zoom_mode(projector_dev, AM7XXX_ZOOM_TEST);
548 am7xxx_set_power_mode(projector_dev, AM7XXX_POWER_OFF);
549 am7xxx_close_device(projector_dev);
550 am7xxx_shutdown(projector_ctx);
554 tjDestroy(jpeg_compressor);
555 out_free_perspective_map:
556 free(perspective_map);
557 out_destroy_viewport:
558 boxDestroy(&viewport);
559 out_destroy_transformed_image:
560 pixDestroy(&transformed_image);