[Example] Add drawing part
authorJihoon Lee <jhoon.it.lee@samsung.com>
Tue, 21 Jul 2020 02:35:52 +0000 (11:35 +0900)
committerJijoong Moon <jijoong.moon@samsung.com>
Tue, 21 Jul 2020 06:23:01 +0000 (15:23 +0900)
This PR adds drawing parti of example/customShortcut

**Self evaluation:**
1. Build test: [X]Passed [ ]Failed [ ]Skipped
2. Run test: [X]Passed [ ]Failed [ ]Skipped

Signed-off-by: Jihoon Lee <jhoon.it.lee@samsung.com>
Applications/Tizen_native/CustomShortcut/.gitignore
Applications/Tizen_native/CustomShortcut/inc/data.h
Applications/Tizen_native/CustomShortcut/inc/view.h
Applications/Tizen_native/CustomShortcut/res/edje/main.edc
Applications/Tizen_native/CustomShortcut/src/data.c [new file with mode: 0644]
Applications/Tizen_native/CustomShortcut/src/view.c

index 5263a47..168fa31 100644 (file)
@@ -11,6 +11,8 @@
 
 #include <Elementary.h>
 #include <app.h>
+#include <cairo.h>
+#include <cairo/cairo-evas-gl.h>
 #include <dlog.h>
 #include <efl_extension.h>
 #include <tizen.h>
 
 #define EDJ_PATH "edje/main.edj"
 
+typedef enum _DRAW_MODE {
+  INFER = 0,
+  TRAIN_SMILE,
+  TRAIN_SAD,
+} DRAW_MODE;
+
+int MAX_TRIES = 5;
 typedef struct appdata {
   Evas_Object *win;
   Evas_Object *conform;
   Evas_Object *label;
   Evas_Object *naviframe;
+  Elm_Object_Item *nf_it;
   Eext_Circle_Surface *circle_nf;
   Elm_Object_Item *home;
   Evas_Object *layout;
+
+  /**< drawing related */
+  Evas_Object *canvas;   /**< image object that cairo surface flushes to */
+  unsigned char *pixels; /**< actual pixel data */
+  Evas_Coord x_offset;   /**< x starting point of canvas position */
+  Evas_Coord y_offset;   /**< y starting point of canvas position */
+  int width;             /**< width of the canvas */
+  int height;            /**< height of the canvas */
+
+  cairo_surface_t *cr_surface; /**< cairo surface for the canvas */
+  cairo_t *cr;                 /**< cairo engine for the canvas */
+  DRAW_MODE mode;              /**< drawing mode of current canvas */
+  int tries;                   /**< tells how many data has been labeled */
+
   char edj_path[PATH_MAX];
 } appdata_s;
 
+/**
+ * @brief separate route path from route
+ * @note this function copies @a source, and change ':' to '\0' and return start
+ * of the route and data accordingly
+ * @param[in] source raw source that comes like "route_target:data\0"
+ * @param[out] route route part of the string, free after use
+ * @param[out] data returns pointer of raw source, freed when route is freed. do
+ * not free.
+ * @param[out] length of data len
+ * @retval 0 if no error
+ */
+int parse_route(const char *source, char **route, char **data);
+
 #if !defined(PACKAGE)
 #define PACKAGE "org.example.nntrainer-example-custom-shortcut"
 #endif
 
+#if !defined(_D)
+#define _D(fmt, arg...)                                                    \
+  dlog_print(DLOG_DEBUG, LOG_TAG, "[%s:%d] " fmt "\n", __func__, __LINE__, \
+             ##arg)
+#endif
+
+#if !defined(_I)
+#define _I(fmt, arg...) \
+  dlog_print(DLOG_INFO, LOG_TAG, "[%s:%d] " fmt "\n", __func__, __LINE__, ##arg)
+#endif
+
+#if !defined(_W)
+#define _W(fmt, arg...) \
+  dlog_print(DLOG_WARN, LOG_TAG, "[%s:%d] " fmt "\n", __func__, __LINE__, ##arg)
+#endif
+
+#if !defined(_E)
+#define _E(fmt, arg...)                                                    \
+  dlog_print(DLOG_DEBUG, LOG_TAG, "[%s:%d] " fmt "\n", __func__, __LINE__, \
+             ##arg)
+#endif
+
 #endif /* __nntrainer_example_custom_shortcut_data_H__ */
index c3886b6..1befd30 100644 (file)
@@ -11,6 +11,7 @@
 
 #include "data.h"
 #include <Elementary.h>
+#include <Evas_GL.h>
 #include <dlog.h>
 #include <efl_extension.h>
 
@@ -25,11 +26,9 @@ int view_init(appdata_s *ad);
 
 /**
  * @brief creates layout from edj
- * @param[in] ad appdata of the app
+ * @param[in/out] ad appdata of the app
  * @param[in] group_name name of the layout to be pushed to main naviframe.
- * @param[out] data naviframe_item that is pushed.
  */
-int view_routes_to(appdata_s *ad, const char *group_name,
-                   Elm_Object_Item **data);
+int view_routes_to(appdata_s *ad, const char *group_name);
 
 #endif /* __nntrainer_example_custom_shortcut_view_H__ */
index 94fc60a..fe2d120 100644 (file)
@@ -100,13 +100,19 @@ collections {
     }
     programs {
       PROGRAM_BUTTON("home/to_train", "routes/to", "select")
-      PROGRAM_BUTTON("home/to_test", "routes/to", "draw")
+      PROGRAM_BUTTON("home/to_test", "routes/to", "draw:inference")
     }
   }
   group {
     name: "select";
     parts {
       PART_TITLE("select/title", "Select target emoji...")
+      PART_BUTTON("select/sad", "😢", 0.1, 0.3, 0.45, 0.7)
+      PART_BUTTON("select/smile", "😊", 0.55, 0.3, 0.9, 0.7)
+    }
+    programs {
+      PROGRAM_BUTTON("select/smile", "routes/to", "draw:smile")
+      PROGRAM_BUTTON("select/sad", "routes/to", "draw:sad")
     }
   }
   // this group is meant to be used for train / test, if it is not applicable, this group is reserved for training part
@@ -114,6 +120,20 @@ collections {
     name: "draw";
     parts {
       PART_TITLE("draw/title", "draw your symbol")
+      part {
+        name: "draw/canvas";
+        type: SWALLOW;
+        description { state: "default" 0.0;
+          rel1.relative: 0.0 0.2;
+          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)
+    }
+    programs {
+      PROGRAM_BUTTON("draw/reset", "draw/reset", "")
+      PROGRAM_BUTTON("draw/proceed", "draw/proceed", "")
     }
   }
   group {
diff --git a/Applications/Tizen_native/CustomShortcut/src/data.c b/Applications/Tizen_native/CustomShortcut/src/data.c
new file mode 100644 (file)
index 0000000..4ed7b1a
--- /dev/null
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: Apache-2.0-only
+/**
+ * Copyright (C) 2020 Jijoong Moon <jijoong.moon@samsung.com>
+ *
+ * @file data.c
+ * @date 21 Jul 2020
+ * @brief TIZEN Native Example App data entry with NNTrainer/CAPI.
+ * @see  https://github.com/nnstreamer/nntrainer
+ * @author Jihoon Lee <jhoon.it.lee@samsung.com>
+ * @bug No known bugs except for NYI items
+ *
+ */
+#include "data.h"
+
+#include <string.h>
+
+int parse_route(const char *source, char **route, char **data) {
+  char *dst = strdup(source);
+  const char sep = ':';
+  char *i;
+  bool find_data = false;
+
+  if (route == NULL || data == NULL) {
+    free(dst);
+    return APP_ERROR_INVALID_PARAMETER;
+  }
+
+  *route = dst;
+
+  for (i = dst; *i != '\0'; ++i) {
+    if (*i == sep) {
+      *i = '\0';
+      *data = i + 1;
+      find_data = true;
+      break;
+    }
+  }
+
+  if (!find_data) {
+    *data = NULL;
+  }
+
+  return APP_ERROR_NONE;
+}
index 1d36340..c1cbb95 100644 (file)
@@ -7,11 +7,14 @@
  * @bug No known bugs except for NYI items
  */
 #include "view.h"
+#include "data.h"
 
 static Evas_Object *_create_layout(Evas_Object *parent, const char *edj_path,
                                    const char *group_name,
                                    Eext_Event_Cb back_cb, void *user_data);
 
+static int _create_canvas(appdata_s *ad, const char *draw_mode);
+
 static void _on_win_delete(void *data, Evas_Object *obj, void *event_info) {
   ui_app_exit();
 }
@@ -74,6 +77,7 @@ int view_init(appdata_s *ad) {
     evas_object_del(win);
     return APP_ERROR_INVALID_CONTEXT;
   }
+
   elm_win_indicator_mode_set(win, ELM_WIN_INDICATOR_SHOW);
   elm_win_indicator_opacity_set(win, ELM_WIN_INDICATOR_OPAQUE);
   evas_object_size_hint_weight_set(conform, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
@@ -105,46 +109,61 @@ int view_init(appdata_s *ad) {
 
 /**
  * @brief creates layout from edj
- * @param[in] ad app data of the add
+ * @param[in/out] ad app data of the add
  * @param[in] group_name name of the layout to be pushed to main naviframe.
- * @param[out] data naviframe_item that is pushed.
  */
-int view_routes_to(appdata_s *ad, const char *group_name,
-                   Elm_Object_Item **data) {
-  Elm_Object_Item *nf_it;
-  ad->layout =
-    _create_layout(ad->naviframe, ad->edj_path, group_name, NULL, NULL);
+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);
+  if (status) {
+    _E("something wrong with parsing %s", group_name);
+    return status;
+  }
+
+  _D("%s %s", path, path_data);
+
+  ad->layout = _create_layout(ad->naviframe, ad->edj_path, path, NULL, NULL);
 
   if (ad->layout == NULL) {
-    dlog_print(DLOG_ERROR, LOG_TAG, "failed to create a layout of no alarm.");
+    _E("failed to create layout");
+    status = APP_ERROR_INVALID_CONTEXT;
     evas_object_del(ad->win);
-    return APP_ERROR_INVALID_CONTEXT;
+    goto CLEAN_UP;
   }
 
-  elm_layout_signal_callback_add(ad->layout, "routes/to", "*", _on_routes_to,
-                                 ad);
+  ad->nf_it = elm_naviframe_item_push(ad->naviframe, NULL, NULL, NULL,
+                                      ad->layout, "empty");
 
-  nf_it = elm_naviframe_item_push(ad->naviframe, NULL, NULL, NULL, ad->layout,
-                                  "empty");
-  if (nf_it == NULL)
-    return APP_ERROR_INVALID_PARAMETER;
+  if (ad->nf_it == NULL) {
+    status = APP_ERROR_INVALID_PARAMETER;
+    goto CLEAN_UP;
+  }
 
-  if (data != NULL)
-    *data = nf_it;
+  if (!strcmp(path, "draw")) {
+    status = _create_canvas(ad, path_data);
+  }
 
-  return APP_ERROR_NONE;
+  elm_layout_signal_callback_add(ad->layout, "routes/to", "*", _on_routes_to,
+                                 ad);
+
+CLEAN_UP:
+  free(path);
+  return status;
 }
 
 static void _on_routes_to(void *data, Evas_Object *obj, const char *emission,
                           const char *source) {
-  view_routes_to((appdata_s *)data, source, NULL);
+  view_routes_to((appdata_s *)data, source);
 }
+
 /**
  * @brief creates a layout for parent object with EDJ file
  * @param[in] parent Parent object to attach to
  * @param[in] file_path EDJ file path
  * @param[in] group_name group name from edj
- * @param[in] back_cb callback when back even fired.
+ * @param[in] back_cb callback when back event fired.
  * @param[in] user_data data to pass to the callback
  */
 static Evas_Object *_create_layout(Evas_Object *parent, const char *edj_path,
@@ -174,3 +193,132 @@ static Evas_Object *_create_layout(Evas_Object *parent, const char *edj_path,
 
   return layout;
 }
+
+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);
+
+  cairo_set_source_rgba(ad->cr, 1, 1, 1, 1);
+  cairo_move_to(ad->cr, eemd->canvas.x - ad->x_offset,
+                eemd->canvas.y - ad->y_offset);
+}
+
+static void _on_draw_move(void *data, Evas *e, Evas_Object *obj,
+                          void *event_info) {
+  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);
+  cairo_line_to(ad->cr, eemm->cur.canvas.x - ad->x_offset,
+                eemm->cur.canvas.y - ad->y_offset);
+}
+
+static void _on_draw_end(void *data, Evas *e, Evas_Object *obj,
+                         void *event_info) {
+  appdata_s *ad = (appdata_s *)data;
+  _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");
+  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;
+}
+
+static int _create_canvas(appdata_s *ad, const char *draw_mode) {
+  _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");
+    return APP_ERROR_INVALID_PARAMETER;
+  }
+
+  evas_object_move(frame, 72, 72);
+  evas_object_resize(frame, 216, 216);
+  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);
+
+  Evas_Object *canvas =
+    evas_object_image_filled_add(evas_object_evas_get(frame));
+  if (canvas == NULL) {
+    _E("failed to initiate canvas");
+    return APP_ERROR_INVALID_PARAMETER;
+  }
+
+  evas_object_image_content_hint_set(canvas, EVAS_IMAGE_CONTENT_HINT_DYNAMIC);
+  evas_object_image_size_set(canvas, width, height);
+  evas_object_move(canvas, x, y);
+  evas_object_resize(canvas, width, height);
+  evas_object_image_colorspace_set(canvas, EVAS_COLORSPACE_ARGB8888);
+  evas_object_image_alpha_set(canvas, 1);
+  evas_object_show(canvas);
+
+  ad->pixels = (unsigned char *)evas_object_image_data_get(canvas, 1);
+  if (ad->pixels == NULL) {
+    _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");
+    evas_object_del(canvas);
+    cairo_surface_destroy(cairo_surface);
+    return APP_ERROR_INVALID_PARAMETER;
+  }
+
+  cairo_t *cr = cairo_create(cairo_surface);
+  if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) {
+    _E("Cannot initiate cairo surface");
+    evas_object_del(canvas);
+    cairo_surface_destroy(cairo_surface);
+    cairo_destroy(cr);
+    return APP_ERROR_INVALID_PARAMETER;
+  }
+
+  cairo_rectangle(cr, 0, 0, width, height);
+  cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 0.5);
+  cairo_fill(cr);
+  cairo_surface_flush(cairo_surface);
+
+  evas_object_image_data_update_add(canvas, 0, 0, width, height);
+
+  evas_object_event_callback_add(canvas, EVAS_CALLBACK_MOUSE_DOWN,
+                                 _on_draw_start, (void *)ad);
+
+  evas_object_event_callback_add(canvas, EVAS_CALLBACK_MOUSE_UP, _on_draw_end,
+                                 (void *)ad);
+
+  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);
+
+  ad->canvas = canvas;
+  ad->cr_surface = cairo_surface;
+  ad->cr = cr;
+  ad->width = width;
+  ad->height = height;
+  ad->x_offset = x;
+  ad->y_offset = y;
+
+  return APP_ERROR_NONE;
+}