From: Jihoon Lee Date: Tue, 28 Jul 2020 00:59:42 +0000 (+0900) Subject: [Example] Add feature extraction part X-Git-Tag: submit/tizen/20200807.102403~5 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=934e62bc0f4784a60e52d0d6a98fc40aee820e35;p=platform%2Fcore%2Fml%2Fnntrainer.git [Example] Add feature extraction part **Changes proposed in this PR:** - Add feature extraction using `ml-pipeline-api` (saved to training.dat) - Handle exit canvas / proceed / reset button - Add path parsing utils - Add logger macro **Self evaluation:** 1. Build test: [X]Passed [ ]Failed [ ]Skipped 2. Run test: [X]Passed [ ]Failed [ ]Skipped Signed-off-by: Jihoon Lee --- diff --git a/Applications/Tizen_native/CustomShortcut/README.md b/Applications/Tizen_native/CustomShortcut/README.md index ba5ba1c..b0293ee 100644 --- a/Applications/Tizen_native/CustomShortcut/README.md +++ b/Applications/Tizen_native/CustomShortcut/README.md @@ -4,7 +4,9 @@ For example, user can draw `^^` few times to train a model to label the drawing as 😊. -Built and tested at `tizen studio 3.7` with `wearable-5.5 profile` +Built and tested at `tizen studio 3.7` with `wearable-6.0 profile` + +tested on `SM-R800` (it should run in other wearable but it is not guaranteed) ## How to Build and Run diff --git a/Applications/Tizen_native/CustomShortcut/inc/data.h b/Applications/Tizen_native/CustomShortcut/inc/data.h index e07e2cc..fa1f907 100644 --- a/Applications/Tizen_native/CustomShortcut/inc/data.h +++ b/Applications/Tizen_native/CustomShortcut/inc/data.h @@ -1,4 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0-only /** + * Copyright (C) 2020 Jihoon Lee + * * @file data.h * @date 15 May 2020 * @brief TIZEN Native Example App dataentry with NNTrainer/CAPI. @@ -15,7 +18,9 @@ #include #include #include +#include #include +#include #include #ifdef LOG_TAG @@ -42,6 +47,8 @@ typedef struct appdata { Elm_Object_Item *home; Evas_Object *layout; + char edj_path[PATH_MAX]; + /**< drawing related */ Evas_Object *canvas; /**< image object that cairo surface flushes to */ unsigned char *pixels; /**< actual pixel data */ @@ -55,7 +62,12 @@ typedef struct appdata { DRAW_MODE mode; /**< drawing mode of current canvas */ int tries; /**< tells how many data has been labeled */ - char edj_path[PATH_MAX]; + /**< ML related */ + ml_pipeline_h pipeline; /**< handle of feature extractor */ + ml_pipeline_sink_h pipe_sink; /**< sink of pipeline */ + char pipe_dst[PATH_MAX]; /**< destination path where to save */ + pthread_mutex_t pipe_lock; /**< ensures that only one pipe runs at a time */ + pthread_cond_t pipe_cond; /**< pipe condition to block at a point */ } appdata_s; /** @@ -69,7 +81,28 @@ typedef struct appdata { * @param[out] length of data len * @retval 0 if no error */ -int parse_route(const char *source, char **route, char **data); +int data_parse_route(const char *source, char **route, char **data); + +/** + * @brief get full resource path for given file. + * @param[in] file relative file path from resource path + * @param[out] full_path path of the output file + * @param[in] shared true if resource is in shared/res + * @retval APP_ERROR_NONE if no error + */ +int data_get_resource_path(const char *file, char *full_path, bool shared); + +/** + * @brief extract data feature from given model. + * @param[in] ad appdata + * @param[in] dst state the name of the data set + * @param[in] append decide whether to append to the exisiting file + * + * This function runs a mobilnetv2 last layer detached and saves an output + * vector. input for this model is png file drawn to the canvas(stored in + * appdata) output can be pased to nntrainer and used. + */ +int data_extract_feature(appdata_s *ad, const char *dst, bool append); /** * @brief nntrainer sanity test. contructing model and destroy. This will be @@ -82,23 +115,23 @@ void nntrainer_test(); #endif #if !defined(_D) -#define _D(fmt, arg...) \ +#define LOG_D(fmt, arg...) \ dlog_print(DLOG_DEBUG, LOG_TAG, "[%s:%d] " fmt "\n", __func__, __LINE__, \ ##arg) #endif #if !defined(_I) -#define _I(fmt, arg...) \ +#define LOG_I(fmt, arg...) \ dlog_print(DLOG_INFO, LOG_TAG, "[%s:%d] " fmt "\n", __func__, __LINE__, ##arg) #endif #if !defined(_W) -#define _W(fmt, arg...) \ +#define LOG_W(fmt, arg...) \ dlog_print(DLOG_WARN, LOG_TAG, "[%s:%d] " fmt "\n", __func__, __LINE__, ##arg) #endif #if !defined(_E) -#define _E(fmt, arg...) \ +#define LOG_E(fmt, arg...) \ dlog_print(DLOG_DEBUG, LOG_TAG, "[%s:%d] " fmt "\n", __func__, __LINE__, \ ##arg) #endif diff --git a/Applications/Tizen_native/CustomShortcut/res/edje/main.edc b/Applications/Tizen_native/CustomShortcut/res/edje/main.edc index fe2d120..856daf3 100644 --- a/Applications/Tizen_native/CustomShortcut/res/edje/main.edc +++ b/Applications/Tizen_native/CustomShortcut/res/edje/main.edc @@ -128,8 +128,8 @@ collections { rel2.relative: 1.0 0.8; } } - PART_BUTTON("draw/reset", "↩️", 0.2, 0.8, 0.45, 0.9) - PART_BUTTON("draw/proceed", "▶️", 0.55, 0.8, 0.8, 0.9) + PART_BUTTON("draw/reset", "↩️", 0.2, 0.83, 0.45, 0.9) + PART_BUTTON("draw/proceed", "▶️", 0.55, 0.83, 0.81, 0.9) } programs { PROGRAM_BUTTON("draw/reset", "draw/reset", "") diff --git a/Applications/Tizen_native/CustomShortcut/src/data.c b/Applications/Tizen_native/CustomShortcut/src/data.c index 0725f83..9bfc1aa 100644 --- a/Applications/Tizen_native/CustomShortcut/src/data.c +++ b/Applications/Tizen_native/CustomShortcut/src/data.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0-only /** - * Copyright (C) 2020 Jijoong Moon + * Copyright (C) 2020 Jihoon Lee * * @file data.c * @date 21 Jul 2020 @@ -11,10 +11,10 @@ * */ #include "data.h" - +#include #include -int parse_route(const char *source, char **route, char **data) { +int data_parse_route(const char *source, char **route, char **data) { char *dst = strdup(source); const char sep = ':'; char *i; @@ -43,6 +43,197 @@ int parse_route(const char *source, char **route, char **data) { return APP_ERROR_NONE; } +int data_get_resource_path(const char *file, char *full_path, bool shared) { + char *root_path; + if (shared) { + root_path = app_get_shared_resource_path(); + } else { + root_path = app_get_resource_path(); + } + + if (root_path == NULL) { + LOG_E("failed to get resource path"); + return APP_ERROR_INVALID_PARAMETER; + } + + if (full_path == NULL) { + LOG_E("full_path is null"); + free(root_path); + return APP_ERROR_INVALID_PARAMETER; + } + + snprintf(full_path, PATH_MAX, "%s%s", root_path, file); + LOG_D("resource path: %s", full_path); + free(root_path); + + return APP_ERROR_NONE; +} + +int data_get_data_path(const char *file, char *full_path) { + char *root_path; + + root_path = app_get_data_path(); + + if (root_path == NULL) { + LOG_E("failed to get data path"); + return APP_ERROR_INVALID_PARAMETER; + } + + if (full_path == NULL) { + LOG_E("full_path is null"); + free(root_path); + return APP_ERROR_INVALID_PARAMETER; + } + + snprintf(full_path, PATH_MAX, "%s%s", root_path, file); + LOG_D("data path: %s", full_path); + free(root_path); + + return APP_ERROR_NONE; +} + +static void _on_data_receive(ml_tensors_data_h data, + const ml_tensors_info_h info, void *user_data) { + appdata_s *ad = (appdata_s *)user_data; + + void *raw_data; + size_t data_size, write_result; + FILE *file; + + pthread_mutex_lock(&ad->pipe_lock); + + /// @note data_size written here will be overriden and it is intended. + int status = ml_tensors_data_get_tensor_data(data, 0, &raw_data, &data_size); + if (status != ML_ERROR_NONE) { + LOG_E("get tensor data failed %d", status); + status = ml_tensors_info_get_tensor_size(info, -1, &data_size); + goto CLEAN; + } + + file = fopen(ad->pipe_dst, ad->tries == 1 ? "wb" : "wb+"); + + if (file == NULL) { + LOG_E("cannot open file"); + goto CLEAN; + } + + if (write_result = fwrite(raw_data, data_size, 1, file) < 0) { + LOG_E("write error happend"); + } + + if (write_result < data_size) { + LOG_E("data was not fully written to file"); + } + + if (fclose(file) < 0) { + LOG_E("there was error closing"); + goto CLEAN; + } + + LOG_D("using pipeline finished, destroying pipeline"); + +CLEAN: + pthread_cond_signal(&ad->pipe_cond); + pthread_mutex_unlock(&ad->pipe_lock); +} + +static int _run_nnpipeline(appdata_s *ad, const char *src, bool append) { + char pipe_description[5000]; + + char model_path[PATH_MAX]; + + int status = ML_ERROR_NONE; + + data_get_resource_path("mobilenetv2.tflite", model_path, false); + + status = pthread_mutex_lock(&ad->pipe_lock); + if (status != 0) { + LOG_E("acquiring lock failed status: %d", status); + return status; + } + + LOG_D("pipe ready, starting pipeline"); + + sprintf(pipe_description, + "filesrc location=%s ! pngdec ! " + "videoconvert ! videoscale ! " + "video/x-raw,width=224,height=224,format=RGB ! " + "tensor_converter ! " + "tensor_transform mode=arithmetic option=%s ! " + "tensor_filter framework=tensorflow-lite model=%s ! " + "tensor_sink name=sink", + src, "typecast:float32,add:-127.5,div:127.5", model_path); + + LOG_D("setting inference \n pipe: %s", pipe_description); + status = ml_pipeline_construct(pipe_description, NULL, NULL, &ad->pipeline); + + if (status != ML_ERROR_NONE) { + LOG_E("something wrong constructing pipeline %d", status); + ml_pipeline_destroy(ad->pipeline); + pthread_mutex_unlock(&ad->pipe_lock); + return status; + } + + status = ml_pipeline_sink_register(ad->pipeline, "sink", _on_data_receive, + (void *)ad, &ad->pipe_sink); + if (status != ML_ERROR_NONE) { + LOG_E("sink register failed %d", status); + goto CLEAN; + } + + LOG_D("starting inference"); + status = ml_pipeline_start(ad->pipeline); + if (status != ML_ERROR_NONE) { + LOG_E("failed to start pipeline %d", status); + goto CLEAN; + } + + pthread_cond_wait(&ad->pipe_cond, &ad->pipe_lock); + + LOG_D("stopping pipeline"); + status = ml_pipeline_stop(ad->pipeline); + if (status != ML_ERROR_NONE) { + LOG_E("stopping pipeline failed"); + goto CLEAN; + } + + LOG_D("unregister pipeline"); + if (status != ML_ERROR_NONE) { + LOG_E("unregistering sink failed"); + } + +CLEAN: + LOG_D("destroying pipeline"); + ml_pipeline_sink_unregister(ad->pipe_sink); + ml_pipeline_destroy(ad->pipeline); + ad->pipe_sink = NULL; + ad->pipeline = NULL; + pthread_mutex_unlock(&ad->pipe_lock); + return status; +} + +int data_extract_feature(appdata_s *ad, const char *dst, bool append) { + char png_path[PATH_MAX]; + cairo_status_t cr_stat = CAIRO_STATUS_SUCCESS; + int status = APP_ERROR_NONE; + + data_get_data_path("temp.png", png_path); + LOG_D("start writing to png_path: %s ", png_path); + cr_stat = cairo_surface_write_to_png(ad->cr_surface, png_path); + + if (cr_stat != CAIRO_STATUS_SUCCESS) { + LOG_E("failed to write cairo surface as a file reason: %d", cr_stat); + return APP_ERROR_INVALID_PARAMETER; + } + + data_get_data_path(dst, ad->pipe_dst); + + LOG_I("start inference to dataset: %s ", ad->pipe_dst); + status = _run_nnpipeline(ad, png_path, append); + + return status; +} + void nntrainer_test() { ml_train_model_h model = NULL; diff --git a/Applications/Tizen_native/CustomShortcut/src/main.c b/Applications/Tizen_native/CustomShortcut/src/main.c index d6a7a4b..bed1d33 100644 --- a/Applications/Tizen_native/CustomShortcut/src/main.c +++ b/Applications/Tizen_native/CustomShortcut/src/main.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0-only /** * @file main.c * @date 14 May 2020 @@ -10,6 +11,7 @@ #include "main.h" #include "data.h" #include "view.h" +#include #include static bool app_create(void *data) { @@ -19,14 +21,10 @@ static bool app_create(void *data) { If this function returns false, the application is terminated */ appdata_s *ad = data; - char *res_path = app_get_resource_path(); - if (res_path == NULL) { - dlog_print(DLOG_ERROR, LOG_TAG, "failed to get resource."); - return false; - } + pthread_mutex_init(&ad->pipe_lock, NULL); + pthread_cond_init(&ad->pipe_cond, NULL); - snprintf(ad->edj_path, sizeof(ad->edj_path), "%s%s", res_path, EDJ_PATH); - free(res_path); + data_get_resource_path(EDJ_PATH, ad->edj_path, false); view_init(ad); @@ -51,6 +49,10 @@ static void app_resume(void *data) { } static void app_terminate(void *data) { /* Release all resources. */ + appdata_s *ad = data; + + pthread_mutex_destroy(&ad->pipe_lock); + pthread_cond_destroy(&ad->pipe_cond); } static void ui_app_lang_changed(app_event_info_h event_info, void *user_data) { @@ -61,8 +63,7 @@ static void ui_app_lang_changed(app_event_info_h event_info, void *user_data) { ret = app_event_get_language(event_info, &language); if (ret != APP_ERROR_NONE) { - dlog_print(DLOG_ERROR, LOG_TAG, - "app_event_get_language() failed. Err = %d.", ret); + LOG_E("app_event_get_language() failed. Err = %d.", ret); return; } diff --git a/Applications/Tizen_native/CustomShortcut/src/view.c b/Applications/Tizen_native/CustomShortcut/src/view.c index c1cbb95..fab2eee 100644 --- a/Applications/Tizen_native/CustomShortcut/src/view.c +++ b/Applications/Tizen_native/CustomShortcut/src/view.c @@ -23,15 +23,18 @@ static void _on_back_pressed(void *data, Evas_Object *obj, void *event_info) { appdata_s *ad = data; Elm_Widget_Item *nf_it = elm_naviframe_top_item_get(obj); + ad->tries = 0; + if (!nf_it) { /* app should not reach hear */ - dlog_print(DLOG_ERROR, LOG_TAG, "naviframe is empty."); + LOG_E("naviframe is null"); + dlog_print(DLOG_ERROR, LOG_TAG, "naviframe is e."); ui_app_exit(); return; } if (nf_it == ad->home) { - dlog_print(DLOG_DEBUG, LOG_TAG, "navi empty"); + LOG_D("naviframe is empty"); elm_win_lower(ad->win); return; } @@ -55,8 +58,7 @@ int view_init(appdata_s *ad) { win = elm_win_util_standard_add(PACKAGE, PACKAGE); if (win == NULL) { - dlog_print(DLOG_ERROR, LOG_TAG, "failed to get create window err = %d", - status); + LOG_E("failed to create window err = %d", status); return status; } elm_win_conformant_set(win, EINA_TRUE); @@ -73,7 +75,7 @@ int view_init(appdata_s *ad) { // Adding conformant conform = elm_conformant_add(win); if (conform == NULL) { - dlog_print(DLOG_ERROR, LOG_TAG, "failed to create conformant object"); + LOG_E("failed to create conformant object"); evas_object_del(win); return APP_ERROR_INVALID_CONTEXT; } @@ -87,7 +89,7 @@ int view_init(appdata_s *ad) { // Adding naviframe nf = elm_naviframe_add(conform); if (nf == NULL) { - dlog_print(DLOG_ERROR, LOG_TAG, "failed to create naviframe object"); + LOG_E("failed to create naviframe object"); evas_object_del(win); return APP_ERROR_INVALID_CONTEXT; } @@ -116,18 +118,18 @@ int view_routes_to(appdata_s *ad, const char *group_name) { char *path, *path_data; int status; - status = parse_route(group_name, &path, &path_data); + status = data_parse_route(group_name, &path, &path_data); if (status) { - _E("something wrong with parsing %s", group_name); + LOG_E("something wrong with parsing %s", group_name); return status; } - _D("%s %s", path, path_data); + LOG_D("%s %s", path, path_data); ad->layout = _create_layout(ad->naviframe, ad->edj_path, path, NULL, NULL); if (ad->layout == NULL) { - _E("failed to create layout"); + LOG_E("failed to create layout"); status = APP_ERROR_INVALID_CONTEXT; evas_object_del(ad->win); goto CLEAN_UP; @@ -137,6 +139,7 @@ int view_routes_to(appdata_s *ad, const char *group_name) { ad->layout, "empty"); if (ad->nf_it == NULL) { + LOG_E("naviframe_item_push failed"); status = APP_ERROR_INVALID_PARAMETER; goto CLEAN_UP; } @@ -172,7 +175,7 @@ static Evas_Object *_create_layout(Evas_Object *parent, const char *edj_path, Evas_Object *layout = NULL; if (parent == NULL) { - dlog_print(DLOG_ERROR, LOG_TAG, "parent cannot be NULL"); + LOG_E("parent cannot be NULL"); return NULL; } @@ -180,7 +183,7 @@ static Evas_Object *_create_layout(Evas_Object *parent, const char *edj_path, elm_layout_file_set(layout, edj_path, group_name); if (layout == NULL) { - dlog_print(DLOG_ERROR, LOG_TAG, "There was error making layout"); + LOG_E("There was error making layout"); evas_object_del(layout); return NULL; } @@ -198,7 +201,7 @@ static void _on_draw_start(void *data, Evas *e, Evas_Object *obj, void *event_info) { appdata_s *ad = (appdata_s *)data; Evas_Event_Mouse_Down *eemd = (Evas_Event_Mouse_Down *)event_info; - _D("x: %d, y: %d", eemd->canvas.x, eemd->canvas.y); + LOG_D("x: %d, y: %d", eemd->canvas.x, eemd->canvas.y); cairo_set_source_rgba(ad->cr, 1, 1, 1, 1); cairo_move_to(ad->cr, eemd->canvas.x - ad->x_offset, @@ -210,7 +213,7 @@ static void _on_draw_move(void *data, Evas *e, Evas_Object *obj, appdata_s *ad = (appdata_s *)data; Evas_Event_Mouse_Move *eemm = (Evas_Event_Mouse_Move *)event_info; - _D("x: %d, y: %d", eemm->cur.canvas.x, eemm->cur.canvas.y); + LOG_D("x: %d, y: %d", eemm->cur.canvas.x, eemm->cur.canvas.y); cairo_line_to(ad->cr, eemm->cur.canvas.x - ad->x_offset, eemm->cur.canvas.y - ad->y_offset); } @@ -218,47 +221,94 @@ static void _on_draw_move(void *data, Evas *e, Evas_Object *obj, static void _on_draw_end(void *data, Evas *e, Evas_Object *obj, void *event_info) { appdata_s *ad = (appdata_s *)data; - _D("draw end"); + LOG_D("draw end"); cairo_stroke(ad->cr); cairo_surface_flush(ad->cr_surface); evas_object_image_data_update_add(ad->canvas, 0, 0, ad->width, ad->height); } -static Eina_Bool _on_canvas_exit(void *data, Elm_Object_Item *it) { - _D("exiting canvas"); +static void _on_canvas_exit(void *data, Evas *e, Evas_Object *obj, + void *event_info) { + LOG_D("deleting canvas"); appdata_s *ad = (appdata_s *)data; - /// @todo save file to loadable data format evas_object_del(ad->canvas); - cairo_surface_destroy(ad->cr_surface); cairo_destroy(ad->cr); - return EINA_TRUE; + if (cairo_status(ad->cr) != CAIRO_STATUS_SUCCESS) { + LOG_E("delete cairo failed"); + } + cairo_surface_destroy(ad->cr_surface); + if (cairo_surface_status(ad->cr_surface) != CAIRO_STATUS_SUCCESS) { + LOG_E("delete cr_surface failed"); + } +} + +static void _canvas_erase_all(appdata_s *ad) { + cairo_set_source_rgba(ad->cr, 0.5, 0.5, 0.5, 0.5); + cairo_set_operator(ad->cr, CAIRO_OPERATOR_SOURCE); + cairo_paint(ad->cr); + cairo_surface_flush(ad->cr_surface); + evas_object_image_data_update_add(ad->canvas, 0, 0, ad->width, ad->height); +} + +static void _on_draw_reset(void *data, Evas_Object *obj, const char *emission, + const char *source) { + appdata_s *ad = (appdata_s *)data; + LOG_D("draw reset"); + _canvas_erase_all(ad); +} + +static void _on_draw_proceed(void *data, Evas_Object *obj, const char *emission, + const char *source) { + appdata_s *ad = (appdata_s *)data; + int status = APP_ERROR_NONE; + LOG_D("draw proceed"); + + char buf[256]; + ad->tries++; + if (ad->tries >= MAX_TRIES) { + ad->tries = 0; + elm_naviframe_item_pop(ad->naviframe); + view_routes_to(ad, "train_result"); + return; + } + + sprintf(buf, "draw your symbol [%d/%d]", ad->tries, MAX_TRIES); + elm_object_part_text_set(obj, "draw/title", buf); + LOG_D("starting extraction"); + status = data_extract_feature(ad, "trainingSet.dat", true); + + if (status != APP_ERROR_NONE) { + LOG_E("feature extraction failed"); + } + + _canvas_erase_all(ad); } static int _create_canvas(appdata_s *ad, const char *draw_mode) { - _D("init canvas"); + LOG_D("init canvas"); Eina_Bool status; Evas_Object *frame = elm_layout_add(ad->layout); status = elm_layout_content_set(ad->layout, "draw/canvas", frame); if (status == EINA_FALSE) { - _E("failed to get canvas object"); + LOG_E("failed to get canvas object"); return APP_ERROR_INVALID_PARAMETER; } - evas_object_move(frame, 72, 72); - evas_object_resize(frame, 216, 216); + evas_object_move(frame, 70, 70); + evas_object_resize(frame, 224, 224); evas_object_show(frame); Evas_Coord width, height, x, y; evas_object_geometry_get(frame, &x, &y, &width, &height); - _D("frame info, %d %d width: %d height: %d", x, y, width, height); + LOG_D("frame info, %d %d width: %d height: %d", x, y, width, height); Evas_Object *canvas = evas_object_image_filled_add(evas_object_evas_get(frame)); if (canvas == NULL) { - _E("failed to initiate canvas"); + LOG_E("failed to initiate canvas"); return APP_ERROR_INVALID_PARAMETER; } @@ -272,14 +322,14 @@ static int _create_canvas(appdata_s *ad, const char *draw_mode) { ad->pixels = (unsigned char *)evas_object_image_data_get(canvas, 1); if (ad->pixels == NULL) { - _E("cannot fetch pixels from image"); + LOG_E("cannot fetch pixels from image"); return APP_ERROR_INVALID_PARAMETER; } cairo_surface_t *cairo_surface = cairo_image_surface_create_for_data( ad->pixels, CAIRO_FORMAT_ARGB32, width, height, width * 4); if (cairo_surface_status(cairo_surface) != CAIRO_STATUS_SUCCESS) { - _E("cannot make cairo surface"); + LOG_E("cannot make cairo surface"); evas_object_del(canvas); cairo_surface_destroy(cairo_surface); return APP_ERROR_INVALID_PARAMETER; @@ -287,7 +337,7 @@ static int _create_canvas(appdata_s *ad, const char *draw_mode) { cairo_t *cr = cairo_create(cairo_surface); if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) { - _E("Cannot initiate cairo surface"); + LOG_E("Cannot initiate cairo surface"); evas_object_del(canvas); cairo_surface_destroy(cairo_surface); cairo_destroy(cr); @@ -310,8 +360,16 @@ static int _create_canvas(appdata_s *ad, const char *draw_mode) { evas_object_event_callback_add(canvas, EVAS_CALLBACK_MOUSE_MOVE, _on_draw_move, (void *)ad); - elm_naviframe_item_pop_cb_set(ad->nf_it, _on_canvas_exit, ad); + evas_object_event_callback_add(ad->layout, EVAS_CALLBACK_DEL, _on_canvas_exit, + (void *)ad); + + elm_layout_signal_callback_add(ad->layout, "draw/reset", "", _on_draw_reset, + ad); + + elm_layout_signal_callback_add(ad->layout, "draw/proceed", "", + _on_draw_proceed, ad); + ad->tries = 0; ad->canvas = canvas; ad->cr_surface = cairo_surface; ad->cr = cr;