text: /* ------------------------------------------------ *
   * The MIT License (MIT)
   * Copyright (c) 2019 terryky1220@gmail.com
   * ------------------------------------------------ */
  #include <stdio.h>
  #include <stdlib.h>
  #include <unistd.h>
  #include <time.h>
  #include <float.h>
  #include <GLES2/gl2.h>
  #include "util_egl.h"
  #include "util_debugstr.h"
  #include "util_pmeter.h"
  #include "util_texture.h"
  #include "util_render2d.h"
  #include "trt_classification.h"
  #include "camera_capture.h"
  #include "video_decode.h"
  
  #define UNUSED(x) (void)(x)
  
  
  #if defined (USE_INPUT_CAMERA_CAPTURE)
  static void
  update_capture_texture (texture_2d_t *captex)
  {
      int      cap_w, cap_h;
      uint32_t cap_fmt;
      void     *cap_buf;
  
      get_capture_dimension (&cap_w, &cap_h);
      get_capture_pixformat (&cap_fmt);
      get_capture_buffer (&cap_buf);
      if (cap_buf)
      {
          int texw = cap_w;
          int texh = cap_h;
          int texfmt = GL_RGBA;
          switch (cap_fmt)
          {
          case pixfmt_fourcc('Y', 'U', 'Y', 'V'):
              texw = cap_w / 2;
              break;
          default:
              break;
          }
  
          glBindTexture (GL_TEXTURE_2D, captex->texid);
          glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, texw, texh, texfmt, GL_UNSIGNED_BYTE, cap_buf);
      }
  }
  
  static int
  init_capture_texture (texture_2d_t *captex)
  {
      int      cap_w, cap_h;
      uint32_t cap_fmt;
  
      get_capture_dimension (&cap_w, &cap_h);
      get_capture_pixformat (&cap_fmt);
  
      create_2d_texture_ex (captex, NULL, cap_w, cap_h, cap_fmt);
      start_capture ();
  
      return 0;
  }
  
  #endif
  
  #if defined (USE_INPUT_VIDEO_DECODE)
  static void
  update_video_texture (texture_2d_t *captex)
  {
      int   video_w, video_h;
      uint32_t video_fmt;
      void *video_buf;
  
      get_video_dimension (&video_w, &video_h);
      get_video_pixformat (&video_fmt);
      get_video_buffer (&video_buf);
  
      if (video_buf)
      {
          int texw = video_w;
          int texh = video_h;
          int texfmt = GL_RGBA;
          switch (video_fmt)
          {
          case pixfmt_fourcc('Y', 'U', 'Y', 'V'):
              texw = video_w / 2;
              break;
          default:
              break;
          }
  
          glBindTexture (GL_TEXTURE_2D, captex->texid);
          glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, texw, texh, texfmt, GL_UNSIGNED_BYTE, video_buf);
      }
  }
  
  static int
  init_video_texture (texture_2d_t *captex, const char *fname)
  {
      int      vid_w, vid_h;
      uint32_t vid_fmt;
  
      open_video_file (fname);
  
      get_video_dimension (&vid_w, &vid_h);
      get_video_pixformat (&vid_fmt);
  
      create_2d_texture_ex (captex, NULL, vid_w, vid_h, vid_fmt);
      start_video_decode ();
  
      return 0;
  }
  #endif /* USE_INPUT_VIDEO_DECODE */
  
  
  /* resize image to DNN network input size and convert to fp32. */
  void
  feed_classification_image(texture_2d_t *srctex, int win_w, int win_h)
  {
      int x, y, w, h;
      float *buf_fp32 = (float *)get_classification_input_buf (&w, &h);
      unsigned char *buf_ui8 = NULL;
      static unsigned char *pui8 = NULL;
  
      if (pui8 == NULL)
          pui8 = (unsigned char *)malloc(w * h * 4);
  
      buf_ui8 = pui8;
  
      draw_2d_texture_ex (srctex, 0, win_h - h, w, h, 1);
  
      glPixelStorei (GL_PACK_ALIGNMENT, 4);
      glReadPixels (0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, buf_ui8);
  
      /* convert UI8 [0, 255] ==> FP32 [-1, 1] */
      float mean = 128.0f;
      float std  = 128.0f;
      for (y = 0; y < h; y ++)
      {
          for (x = 0; x < w; x ++)
          {
              int r = *buf_ui8 ++;
              int g = *buf_ui8 ++;
              int b = *buf_ui8 ++;
              buf_ui8 ++;          /* skip alpha */
              *buf_fp32 ++ = (float)(r - mean) / std;
              *buf_fp32 ++ = (float)(g - mean) / std;
              *buf_fp32 ++ = (float)(b - mean) / std;
          }
      }
  
      return;
  }
  
  
  static void
  render_classification_result (int ofstx, int ofsty, int texw, int texh, classification_result_t *class_ret)
  {
      /* class name */
      for (int i = 0; i < class_ret->num; i ++)
      {
          float col_str [] = {1.0f, 1.0f, 1.0f, 1.0f};
          float col_blue[] = {0.0f, 0.0f, 0.8f, 0.8f};
          float col_gray[] = {0.3f, 0.3f, 0.3f, 0.8f};
          float *col_bg;
          int dy = texh - 22 * (class_ret->num - i);
  
          classify_t *item = &class_ret->classify[i];
          col_bg = item->score > 0.5f ? col_blue : col_gray;
  
          char buf[512];
          sprintf (buf, "[%d] %3.2f (%4d) %s", i, item->score, item->id, item->name);
          draw_dbgstr_ex (buf, ofstx, ofsty + dy, 1.0f, col_str, col_bg);
      }
  }
  
  
  /* Adjust the texture size to fit the window size
   *
   *                      Portrait
   *     Landscape        +------+
   *     +-+------+-+     +------+
   *     | |      | |     |      |
   *     | |      | |     |      |
   *     +-+------+-+     +------+
   *                      +------+
   */
  static void
  adjust_texture (int win_w, int win_h, int texw, int texh, 
                  int *dx, int *dy, int *dw, int *dh)
  {
      float win_aspect = (float)win_w / (float)win_h;
      float tex_aspect = (float)texw  / (float)texh;
      float scale;
      float scaled_w, scaled_h;
      float offset_x, offset_y;
  
      if (win_aspect > tex_aspect)
      {
          scale = (float)win_h / (float)texh;
          scaled_w = scale * texw;
          scaled_h = scale * texh;
          offset_x = (win_w - scaled_w) * 0.5f;
          offset_y = 0;
      }
      else
      {
          scale = (float)win_w / (float)texw;
          scaled_w = scale * texw;
          scaled_h = scale * texh;
          offset_x = 0;
          offset_y = (win_h - scaled_h) * 0.5f;
      }
  
      *dx = (int)offset_x;
      *dy = (int)offset_y;
      *dw = (int)scaled_w;
      *dh = (int)scaled_h;
  }
  
  
  /*--------------------------------------------------------------------------- *
   *      M A I N    F U N C T I O N
   *--------------------------------------------------------------------------- */
  int
  main(int argc, char *argv[])
  {
      char input_name_default[] = "pakutaso_strawberry.jpg";
      char *input_name = NULL;
      int count;
      int win_w = 960;
      int win_h = 540;
      int texid;
      int texw, texh, draw_x, draw_y, draw_w, draw_h;
      texture_2d_t captex = {0};
      double ttime[10] = {0}, interval, invoke_ms;
      int enable_camera = 1;
      UNUSED (argc);
      UNUSED (*argv);
  #if defined (USE_INPUT_VIDEO_DECODE)
      int enable_video = 0;
  #endif
  
      {
          int c;
          const char *optstring = "v:x";
  
          while ((c = getopt (argc, argv, optstring)) != -1)
          {
              switch (c)
              {
  #if defined (USE_INPUT_VIDEO_DECODE)
              case 'v':
                  enable_video = 1;
                  input_name = optarg;
                  break;
  #endif
              case 'x':
                  enable_camera = 0;
                  break;
              }
          }
  
          while (optind < argc)
          {
              input_name = argv[optind];
              optind++;
          }
      }
  
      if (input_name == NULL)
          input_name = input_name_default;
  
      egl_init_with_platform_window_surface (2, 0, 0, 0, win_w, win_h);
  
      init_2d_renderer (win_w, win_h);
      init_pmeter (win_w, win_h, 500);
      init_dbgstr (win_w, win_h);
  
      init_trt_classification ();
  
  #if defined (USE_GL_DELEGATE) || defined (USE_GPU_DELEGATEV2)
      /* we need to recover framebuffer because GPU Delegate changes the context */
      glBindFramebuffer (GL_FRAMEBUFFER, 0);
      glViewport (0, 0, win_w, win_h);
  #endif
  
  #if defined (USE_INPUT_VIDEO_DECODE)
      /* initialize FFmpeg video decode */
      if (enable_video && init_video_decode () == 0)
      {
          init_video_texture (&captex, input_name);
          texw = captex.width;
          texh = captex.height;
          enable_camera = 0;
      }
      else
  #endif
  #if defined (USE_INPUT_CAMERA_CAPTURE)
      /* initialize V4L2 capture function */
      if (enable_camera && init_capture () == 0)
      {
          init_capture_texture (&captex);
          texw = captex.width;
          texh = captex.height;
      }
      else
  #endif
      {
          load_jpg_texture (input_name, &texid, &texw, &texh);
          captex.texid  = texid;
          captex.width  = texw;
          captex.height = texh;
          captex.format = pixfmt_fourcc ('R', 'G', 'B', 'A');
      }
      adjust_texture (win_w, win_h, texw, texh, &draw_x, &draw_y, &draw_w, &draw_h);
  
      glClearColor (0.f, 0.f, 0.f, 1.0f);
  
      for (count = 0; ; count ++)
      {
          classification_result_t class_ret = {0};
          char strbuf[512];
  
          PMETER_RESET_LAP ();
          PMETER_SET_LAP ();
  
          ttime[1] = pmeter_get_time_ms ();
          interval = (count > 0) ? ttime[1] - ttime[0] : 0;
          ttime[0] = ttime[1];
  
          glClear (GL_COLOR_BUFFER_BIT);
  
  #if defined (USE_INPUT_VIDEO_DECODE)
          /* initialize FFmpeg video decode */
          if (enable_video)
          {
              update_video_texture (&captex);
          }
  #endif
  #if defined (USE_INPUT_CAMERA_CAPTURE)
          if (enable_camera)
          {
              update_capture_texture (&captex);
          }
  #endif
  
          /* invoke pose estimation using TensorflowLite */
          feed_classification_image (&captex, win_w, win_h);
  
          ttime[2] = pmeter_get_time_ms ();
          invoke_classification (&class_ret);
          ttime[3] = pmeter_get_time_ms ();
          invoke_ms = ttime[3] - ttime[2];
  
          glClear (GL_COLOR_BUFFER_BIT);
  
          /* visualize the object detection results. */
          draw_2d_texture_ex (&captex, draw_x, draw_y, draw_w, draw_h, 0);
          render_classification_result (draw_x, draw_y, draw_w, draw_h, &class_ret);
  
          draw_pmeter (0, 40);
  
          sprintf (strbuf, "Interval:%5.1f [ms]\nTFLite  :%5.1f [ms]", interval, invoke_ms);
          draw_dbgstr (strbuf, 10, 10);
  
          egl_swap();
      }
  
      return 0;
  }
  
  