text: /**
   * @file lv_refr.c
   *
   */
  
  /*********************
   *      INCLUDES
   *********************/
  #include <stddef.h>
  #include "lv_refr.h"
  #include "lv_disp.h"
  #include "../hal/lv_hal_tick.h"
  #include "../hal/lv_hal_disp.h"
  #include "../misc/lv_timer.h"
  #include "../misc/lv_mem.h"
  #include "../misc/lv_math.h"
  #include "../misc/lv_gc.h"
  #include "../draw/lv_draw.h"
  #include "../font/lv_font_fmt_txt.h"
  
  #if LV_USE_PERF_MONITOR || LV_USE_MEM_MONITOR
      #include "../widgets/lv_label.h"
  #endif
  
  /*********************
   *      DEFINES
   *********************/
  
  /**********************
   *      TYPEDEFS
   **********************/
  
  /**********************
   *  STATIC PROTOTYPES
   **********************/
  static void lv_refr_join_area(void);
  static void lv_refr_areas(void);
  static void lv_refr_area(const lv_area_t * area_p);
  static void lv_refr_area_part(const lv_area_t * area_p);
  static lv_obj_t * lv_refr_get_top_obj(const lv_area_t * area_p, lv_obj_t * obj);
  static void lv_refr_obj_and_children(lv_obj_t * top_p, const lv_area_t * mask_p);
  static void lv_refr_obj(lv_obj_t * obj, const lv_area_t * mask_ori_p);
  static void draw_buf_flush(void);
  static void call_flush_cb(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p);
  
  /**********************
   *  STATIC VARIABLES
   **********************/
  static uint32_t px_num;
  static lv_disp_t * disp_refr; /*Display being refreshed*/
  #if LV_USE_PERF_MONITOR
      static uint32_t fps_sum_cnt;
      static uint32_t fps_sum_all;
  #endif
  
  /**********************
   *      MACROS
   **********************/
  #if LV_LOG_TRACE_DISP_REFR
  #  define TRACE_REFR(...) LV_LOG_TRACE( __VA_ARGS__)
  #else
  #  define TRACE_REFR(...)
  #endif
  
  /**********************
   *   GLOBAL FUNCTIONS
   **********************/
  
  /**
   * Initialize the screen refresh subsystem
   */
  void _lv_refr_init(void)
  {
      /*Nothing to do*/
  }
  
  /**
   * Redraw the invalidated areas now.
   * Normally the redrawing is periodically executed in `lv_timer_handler` but a long blocking process
   * can prevent the call of `lv_timer_handler`. In this case if the GUI is updated in the process
   * (e.g. progress bar) this function can be called when the screen should be updated.
   * @param disp pointer to display to refresh. NULL to refresh all displays.
   */
  void lv_refr_now(lv_disp_t * disp)
  {
      lv_anim_refr_now();
  
      if(disp) {
          _lv_disp_refr_timer(disp->refr_timer);
      }
      else {
          lv_disp_t * d;
          d = lv_disp_get_next(NULL);
          while(d) {
              _lv_disp_refr_timer(d->refr_timer);
              d = lv_disp_get_next(d);
          }
      }
  }
  
  /**
   * Invalidate an area on display to redraw it
   * @param area_p pointer to area which should be invalidated (NULL: delete the invalidated areas)
   * @param disp pointer to display where the area should be invalidated (NULL can be used if there is
   * only one display)
   */
  void _lv_inv_area(lv_disp_t * disp, const lv_area_t * area_p)
  {
      if(!disp) disp = lv_disp_get_default();
      if(!disp) return;
  
      /*Clear the invalidate buffer if the parameter is NULL*/
      if(area_p == NULL) {
          disp->inv_p = 0;
          return;
      }
  
      lv_area_t scr_area;
      scr_area.x1 = 0;
      scr_area.y1 = 0;
      scr_area.x2 = lv_disp_get_hor_res(disp) - 1;
      scr_area.y2 = lv_disp_get_ver_res(disp) - 1;
  
      lv_area_t com_area;
      bool suc;
  
      suc = _lv_area_intersect(&com_area, area_p, &scr_area);
      if(suc == false)  return; /*Out of the screen*/
  
      /*If there were at least 1 invalid area in full refresh mode, redraw the whole screen*/
      if(disp->driver->full_refresh) {
          disp->inv_areas[0] = scr_area;
          disp->inv_p = 1;
          lv_timer_resume(disp->refr_timer);
          return;
      }
  
      if(disp->driver->rounder_cb) disp->driver->rounder_cb(disp->driver, &com_area);
  
      /*Save only if this area is not in one of the saved areas*/
      uint16_t i;
      for(i = 0; i < disp->inv_p; i++) {
          if(_lv_area_is_in(&com_area, &disp->inv_areas[i], 0) != false) return;
      }
  
      /*Save the area*/
      if(disp->inv_p < LV_INV_BUF_SIZE) {
          lv_area_copy(&disp->inv_areas[disp->inv_p], &com_area);
      }
      else {   /*If no place for the area add the screen*/
          disp->inv_p = 0;
          lv_area_copy(&disp->inv_areas[disp->inv_p], &scr_area);
      }
      disp->inv_p++;
      lv_timer_resume(disp->refr_timer);
  }
  
  /**
   * Get the display which is being refreshed
   * @return the display being refreshed
   */
  lv_disp_t * _lv_refr_get_disp_refreshing(void)
  {
      return disp_refr;
  }
  
  /**
   * Set the display which is being refreshed.
   * It shouldn't be used directly by the user.
   * It can be used to trick the drawing functions about there is an active display.
   * @param the display being refreshed
   */
  void _lv_refr_set_disp_refreshing(lv_disp_t * disp)
  {
      disp_refr = disp;
  }
  
  /**
   * Called periodically to handle the refreshing
   * @param tmr pointer to the timer itself
   */
  void _lv_disp_refr_timer(lv_timer_t * tmr)
  {
      TRACE_REFR("begin");
  
      uint32_t start = lv_tick_get();
      volatile uint32_t elaps = 0;
  
      disp_refr = tmr->user_data;
  
  #if LV_USE_PERF_MONITOR == 0 && LV_USE_MEM_MONITOR == 0
      /**
       * Ensure the timer does not run again automatically.
       * This is done before refreshing in case refreshing invalidates something else.
       */
      lv_timer_pause(tmr);
  #endif
  
      /*Refresh the screen's layout if required*/
      lv_obj_update_layout(disp_refr->act_scr);
      if(disp_refr->prev_scr) lv_obj_update_layout(disp_refr->prev_scr);
  
      lv_obj_update_layout(disp_refr->top_layer);
      lv_obj_update_layout(disp_refr->sys_layer);
  
      /*Do nothing if there is no active screen*/
      if(disp_refr->act_scr == NULL) {
          disp_refr->inv_p = 0;
          LV_LOG_WARN("there is no active screen");
          TRACE_REFR("finished");
          return;
      }
  
      lv_refr_join_area();
  
      lv_refr_areas();
  
      /*If refresh happened ...*/
      if(disp_refr->inv_p != 0) {
          if(disp_refr->driver->full_refresh) {
              draw_buf_flush();
          }
  
          /*Clean up*/
          lv_memset_00(disp_refr->inv_areas, sizeof(disp_refr->inv_areas));
          lv_memset_00(disp_refr->inv_area_joined, sizeof(disp_refr->inv_area_joined));
          disp_refr->inv_p = 0;
  
          elaps = lv_tick_elaps(start);
          /*Call monitor cb if present*/
          if(disp_refr->driver->monitor_cb) {
              disp_refr->driver->monitor_cb(disp_refr->driver, elaps, px_num);
          }
      }
  
      lv_mem_buf_free_all();
      _lv_font_clean_up_fmt_txt();
  
  #if LV_DRAW_COMPLEX
      _lv_draw_mask_cleanup();
  #endif
  
  #if LV_USE_PERF_MONITOR && LV_USE_LABEL
      static lv_obj_t * perf_label = NULL;
      if(perf_label == NULL) {
          perf_label = lv_label_create(lv_layer_sys());
          lv_obj_set_style_bg_opa(perf_label, LV_OPA_50, 0);
          lv_obj_set_style_bg_color(perf_label, lv_color_black(), 0);
          lv_obj_set_style_text_color(perf_label, lv_color_white(), 0);
          lv_obj_set_style_pad_top(perf_label, 3, 0);
          lv_obj_set_style_pad_bottom(perf_label, 3, 0);
          lv_obj_set_style_pad_left(perf_label, 3, 0);
          lv_obj_set_style_pad_right(perf_label, 3, 0);
          lv_obj_set_style_text_align(perf_label, LV_TEXT_ALIGN_RIGHT, 0);
          lv_label_set_text(perf_label, "?");
          lv_obj_align(perf_label, LV_ALIGN_BOTTOM_RIGHT, 0, 0);
      }
  
      static uint32_t perf_last_time = 0;
      static uint32_t elaps_sum = 0;
      static uint32_t frame_cnt = 0;
      if(lv_tick_elaps(perf_last_time) < 300) {
          if(px_num > 5000) {
              elaps_sum += elaps;
              frame_cnt ++;
          }
      }
      else {
          perf_last_time = lv_tick_get();
          uint32_t fps_limit = 1000 / disp_refr->refr_timer->period;
          uint32_t fps;
  
          if(elaps_sum == 0) elaps_sum = 1;
          if(frame_cnt == 0) fps = fps_limit;
          else fps = (1000 * frame_cnt) / elaps_sum;
          elaps_sum = 0;
          frame_cnt = 0;
          if(fps > fps_limit) fps = fps_limit;
  
          fps_sum_all += fps;
          fps_sum_cnt ++;
          uint32_t cpu = 100 - lv_timer_get_idle();
          lv_label_set_text_fmt(perf_label, "%d FPS\n%d%% CPU", fps, cpu);
      }
  #endif
  
  #if LV_USE_MEM_MONITOR && LV_MEM_CUSTOM == 0 && LV_USE_LABEL
      static lv_obj_t * mem_label = NULL;
      if(mem_label == NULL) {
          mem_label = lv_label_create(lv_layer_sys());
          lv_obj_set_style_bg_opa(mem_label, LV_OPA_50, 0);
          lv_obj_set_style_bg_color(mem_label, lv_color_black(), 0);
          lv_obj_set_style_text_color(mem_label, lv_color_white(), 0);
          lv_obj_set_style_pad_top(mem_label, 3, 0);
          lv_obj_set_style_pad_bottom(mem_label, 3, 0);
          lv_obj_set_style_pad_left(mem_label, 3, 0);
          lv_obj_set_style_pad_right(mem_label, 3, 0);
          lv_label_set_text(mem_label, "?");
          lv_obj_align(mem_label, LV_ALIGN_BOTTOM_LEFT, 0, 0);
      }
  
      static uint32_t mem_last_time = 0;
      if(lv_tick_elaps(mem_last_time) > 300) {
          mem_last_time = lv_tick_get();
          lv_mem_monitor_t mon;
          lv_mem_monitor(&mon);
          uint32_t used_size = mon.total_size - mon.free_size;;
          uint32_t used_kb = used_size / 1024;
          uint32_t used_kb_tenth = (used_size - (used_kb * 1024)) / 102;
          lv_label_set_text_fmt(mem_label, "%d.%d kB used (%d %%)\n%d%% frag.", used_kb,  used_kb_tenth, mon.used_pct, mon.frag_pct);
      }
  #endif
  
      TRACE_REFR("finished");
  }
  
  #if LV_USE_PERF_MONITOR
  uint32_t lv_refr_get_fps_avg(void)
  {
      return fps_sum_all / fps_sum_cnt;
  }
  #endif
  
  /**********************
   *   STATIC FUNCTIONS
   **********************/
  
  /**
   * Join the areas which has got common parts
   */
  static void lv_refr_join_area(void)
  {
      uint32_t join_from;
      uint32_t join_in;
      lv_area_t joined_area;
      for(join_in = 0; join_in < disp_refr->inv_p; join_in++) {
          if(disp_refr->inv_area_joined[join_in] != 0) continue;
  
          /*Check all areas to join them in 'join_in'*/
          for(join_from = 0; join_from < disp_refr->inv_p; join_from++) {
              /*Handle only unjoined areas and ignore itself*/
              if(disp_refr->inv_area_joined[join_from] != 0 || join_in == join_from) {
                  continue;
              }
  
              /*Check if the areas are on each other*/
              if(_lv_area_is_on(&disp_refr->inv_areas[join_in], &disp_refr->inv_areas[join_from]) == false) {
                  continue;
              }
  
              _lv_area_join(&joined_area, &disp_refr->inv_areas[join_in], &disp_refr->inv_areas[join_from]);
  
              /*Join two area only if the joined area size is smaller*/
              if(lv_area_get_size(&joined_area) < (lv_area_get_size(&disp_refr->inv_areas[join_in]) +
                                                   lv_area_get_size(&disp_refr->inv_areas[join_from]))) {
                  lv_area_copy(&disp_refr->inv_areas[join_in], &joined_area);
  
                  /*Mark 'join_form' is joined into 'join_in'*/
                  disp_refr->inv_area_joined[join_from] = 1;
              }
          }
      }
  }
  
  /**
   * Refresh the joined areas
   */
  static void lv_refr_areas(void)
  {
      px_num = 0;
  
      if(disp_refr->inv_p == 0) return;
  
      /*Find the last area which will be drawn*/
      int32_t i;
      int32_t last_i = 0;
      for(i = disp_refr->inv_p - 1; i >= 0; i--) {
          if(disp_refr->inv_area_joined[i] == 0) {
              last_i = i;
              break;
          }
      }
  
      disp_refr->driver->draw_buf->last_area = 0;
      disp_refr->driver->draw_buf->last_part = 0;
  
      for(i = 0; i < disp_refr->inv_p; i++) {
          /*Refresh the unjoined areas*/
          if(disp_refr->inv_area_joined[i] == 0) {
  
              if(i == last_i) disp_refr->driver->draw_buf->last_area = 1;
              disp_refr->driver->draw_buf->last_part = 0;
              lv_refr_area(&disp_refr->inv_areas[i]);
  
              px_num += lv_area_get_size(&disp_refr->inv_areas[i]);
          }
      }
  }
  
  /**
   * Refresh an area if there is Virtual Display Buffer
   * @param area_p  pointer to an area to refresh
   */
  static void lv_refr_area(const lv_area_t * area_p)
  {
      /*With full refresh just redraw directly into the buffer*/
      if(disp_refr->driver->full_refresh) {
          lv_disp_draw_buf_t * draw_buf = lv_disp_get_draw_buf(disp_refr);
          draw_buf->area.x1        = 0;
          draw_buf->area.x2        = lv_disp_get_hor_res(disp_refr) - 1;
          draw_buf->area.y1        = 0;
          draw_buf->area.y2        = lv_disp_get_ver_res(disp_refr) - 1;
          disp_refr->driver->draw_buf->last_part = 1;
          lv_refr_area_part(area_p);
          return;
      }
  
      /*Normal refresh: draw the area in parts*/
      lv_disp_draw_buf_t * draw_buf = lv_disp_get_draw_buf(disp_refr);
      /*Calculate the max row num*/
      lv_coord_t w = lv_area_get_width(area_p);
      lv_coord_t h = lv_area_get_height(area_p);
      lv_coord_t y2 = area_p->y2 >= lv_disp_get_ver_res(disp_refr) ?
                          lv_disp_get_ver_res(disp_refr) - 1 : area_p->y2;
  
      int32_t max_row = (uint32_t)draw_buf->size / w;
  
      if(max_row > h) max_row = h;
  
      /*Round down the lines of draw_buf if rounding is added*/
      if(disp_refr->driver->rounder_cb) {
          lv_area_t tmp;
          tmp.x1 = 0;
          tmp.x2 = 0;
          tmp.y1 = 0;
  
          lv_coord_t h_tmp = max_row;
          do {
              tmp.y2 = h_tmp - 1;
              disp_refr->driver->rounder_cb(disp_refr->driver, &tmp);
  
              /*If this height fits into `max_row` then fine*/
              if(lv_area_get_height(&tmp) <= max_row) break;
  
              /*Decrement the height of the area until it fits into `max_row` after rounding*/
              h_tmp--;
          } while(h_tmp > 0);
  
          if(h_tmp <= 0) {
              LV_LOG_WARN("Can't set draw_buf height using the round function. (Wrong round_cb or to "
                          "small draw_buf)");
              return;
          }
          else {
              max_row = tmp.y2 + 1;
          }
      }
  
      /*Always use the full row*/
      lv_coord_t row;
      lv_coord_t row_last = 0;
      for(row = area_p->y1; row + max_row - 1 <= y2; row += max_row) {
          /*Calc. the next y coordinates of draw_buf*/
          draw_buf->area.x1 = area_p->x1;
          draw_buf->area.x2 = area_p->x2;
          draw_buf->area.y1 = row;
          draw_buf->area.y2 = row + max_row - 1;
          if(draw_buf->area.y2 > y2) draw_buf->area.y2 = y2;
          row_last = draw_buf->area.y2;
          if(y2 == row_last) disp_refr->driver->draw_buf->last_part = 1;
          lv_refr_area_part(area_p);
      }
  
      /*If the last y coordinates are not handled yet ...*/
      if(y2 != row_last) {
          /*Calc. the next y coordinates of draw_buf*/
          draw_buf->area.x1 = area_p->x1;
          draw_buf->area.x2 = area_p->x2;
          draw_buf->area.y1 = row;
          draw_buf->area.y2 = y2;
  
          disp_refr->driver->draw_buf->last_part = 1;
          lv_refr_area_part(area_p);
      }
  }
  
  /**
   * Refresh a part of an area which is on the actual Virtual Display Buffer
   * @param area_p pointer to an area to refresh
   */
  static void lv_refr_area_part(const lv_area_t * area_p)
  {
      lv_disp_draw_buf_t * draw_buf = lv_disp_get_draw_buf(disp_refr);
  
      /* Below the `area_p` area will be redrawn into the draw buffer.
       * In single buffered mode wait here until the buffer is freed.*/
      if(draw_buf->buf1 && !draw_buf->buf2) {
  		while(draw_buf->flushing) {
  			if(disp_refr->driver->wait_cb) disp_refr->driver->wait_cb(disp_refr->driver);
  		}
      }
  
      lv_obj_t * top_act_scr = NULL;
      lv_obj_t * top_prev_scr = NULL;
  
      /*Get the new mask from the original area and the act. draw_buf
       It will be a part of 'area_p'*/
      lv_area_t start_mask;
      _lv_area_intersect(&start_mask, area_p, &draw_buf->area);
  
      /*Get the most top object which is not covered by others*/
      top_act_scr = lv_refr_get_top_obj(&start_mask, lv_disp_get_scr_act(disp_refr));
      if(disp_refr->prev_scr) {
          top_prev_scr = lv_refr_get_top_obj(&start_mask, disp_refr->prev_scr);
      }
  
      /*Draw a display background if there is no top object*/
      if(top_act_scr == NULL && top_prev_scr == NULL) {
          if(disp_refr->bg_img) {
              lv_draw_img_dsc_t dsc;
              lv_draw_img_dsc_init(&dsc);
              dsc.opa = disp_refr->bg_opa;
              lv_img_header_t header;
              lv_res_t res;
              res = lv_img_decoder_get_info(disp_refr->bg_img, &header);
              if(res == LV_RES_OK) {
                  lv_area_t a;
                  lv_area_set(&a, 0, 0, header.w - 1, header.h - 1);
                  lv_draw_img(&a, &start_mask, disp_refr->bg_img, &dsc);
              }
              else {
                  LV_LOG_WARN("Can't draw the background image");
              }
          }
          else {
              lv_draw_rect_dsc_t dsc;
              lv_draw_rect_dsc_init(&dsc);
              dsc.bg_color = disp_refr->bg_color;
              dsc.bg_opa = disp_refr->bg_opa;
              lv_draw_rect(&start_mask, &start_mask, &dsc);
  
          }
      }
      /*Refresh the previous screen if any*/
      if(disp_refr->prev_scr) {
          /*Get the most top object which is not covered by others*/
          if(top_prev_scr == NULL) {
              top_prev_scr = disp_refr->prev_scr;
          }
          /*Do the refreshing from the top object*/
          lv_refr_obj_and_children(top_prev_scr, &start_mask);
  
      }
  
      if(top_act_scr == NULL) {
          top_act_scr = disp_refr->act_scr;
      }
      /*Do the refreshing from the top object*/
      lv_refr_obj_and_children(top_act_scr, &start_mask);
  
      /*Also refresh top and sys layer unconditionally*/
      lv_refr_obj_and_children(lv_disp_get_layer_top(disp_refr), &start_mask);
      lv_refr_obj_and_children(lv_disp_get_layer_sys(disp_refr), &start_mask);
  
      /*In true double buffered mode flush only once when all areas were rendered.
       *In normal mode flush after every area*/
      if(disp_refr->driver->full_refresh == false) {
          draw_buf_flush();
      }
  }
  
  /**
   * Search the most top object which fully covers an area
   * @param area_p pointer to an area
   * @param obj the first object to start the searching (typically a screen)
   * @return
   */
  static lv_obj_t * lv_refr_get_top_obj(const lv_area_t * area_p, lv_obj_t * obj)
  {
      lv_obj_t * found_p = NULL;
  
      /*If this object is fully cover the draw area check the children too*/
      if(_lv_area_is_in(area_p, &obj->coords, 0) && lv_obj_has_flag(obj, LV_OBJ_FLAG_HIDDEN) == false) {
          lv_cover_check_info_t info;
          info.res = LV_COVER_RES_COVER;
          info.area = area_p;
          lv_event_send(obj, LV_EVENT_COVER_CHECK, &info);
          if(info.res == LV_COVER_RES_MASKED) return NULL;
  
          uint32_t i;
          uint32_t child_cnt = lv_obj_get_child_cnt(obj);
          for(i = 0; i < child_cnt; i++) {
              lv_obj_t * child = obj->spec_attr->children[i];
              found_p = lv_refr_get_top_obj(area_p, child);
  
              /*If a children is ok then break*/
              if(found_p != NULL) {
                  break;
              }
          }
  
          /*If no better children use this object*/
          if(found_p == NULL) {
              if(info.res == LV_COVER_RES_COVER) {
                  found_p = obj;
              }
          }
      }
  
      return found_p;
  }
  
  /**
   * Make the refreshing from an object. Draw all its children and the youngers too.
   * @param top_p pointer to an objects. Start the drawing from it.
   * @param mask_p pointer to an area, the objects will be drawn only here
   */
  static void lv_refr_obj_and_children(lv_obj_t * top_p, const lv_area_t * mask_p)
  {
      /*Normally always will be a top_obj (at least the screen)
       *but in special cases (e.g. if the screen has alpha) it won't.
       *In this case use the screen directly*/
      if(top_p == NULL) top_p = lv_disp_get_scr_act(disp_refr);
      if(top_p == NULL) return;  /*Shouldn't happen*/
  
      /*Refresh the top object and its children*/
      lv_refr_obj(top_p, mask_p);
  
      /*Draw the 'younger' sibling objects because they can be on top_obj*/
      lv_obj_t * par;
      lv_obj_t * border_p = top_p;
  
      par = lv_obj_get_parent(top_p);
  
      /*Do until not reach the screen*/
      while(par != NULL) {
          bool go = false;
          uint32_t i;
          uint32_t child_cnt = lv_obj_get_child_cnt(par);
          for(i = 0; i < child_cnt; i++) {
              lv_obj_t * child = par->spec_attr->children[i];
              if(!go) {
                  if(child == border_p) go = true;
              } else {
                  /*Refresh the objects*/
                  lv_refr_obj(child, mask_p);
              }
          }
  
          /*Call the post draw draw function of the parents of the to object*/
          lv_event_send(par, LV_EVENT_DRAW_POST_BEGIN, (void*)mask_p);
          lv_event_send(par, LV_EVENT_DRAW_POST, (void*)mask_p);
          lv_event_send(par, LV_EVENT_DRAW_POST_END, (void*)mask_p);
  
          /*The new border will be the last parents,
           *so the 'younger' brothers of parent will be refreshed*/
          border_p = par;
          /*Go a level deeper*/
          par = lv_obj_get_parent(par);
      }
  }
  
  /**
   * Refresh an object an all of its children. (Called recursively)
   * @param obj pointer to an object to refresh
   * @param mask_ori_p pointer to an area, the objects will be drawn only here
   */
  static void lv_refr_obj(lv_obj_t * obj, const lv_area_t * mask_ori_p)
  {
      /*Do not refresh hidden objects*/
      if(lv_obj_has_flag(obj, LV_OBJ_FLAG_HIDDEN)) return;
  
      bool union_ok; /*Store the return value of area_union*/
      /*Truncate the original mask to the coordinates of the parent
       *because the parent and its children are visible only here*/
      lv_area_t obj_mask;
      lv_area_t obj_ext_mask;
      lv_area_t obj_area;
      lv_coord_t ext_size = _lv_obj_get_ext_draw_size(obj);
      lv_obj_get_coords(obj, &obj_area);
      obj_area.x1 -= ext_size;
      obj_area.y1 -= ext_size;
      obj_area.x2 += ext_size;
      obj_area.y2 += ext_size;
      union_ok = _lv_area_intersect(&obj_ext_mask, mask_ori_p, &obj_area);
  
      /*Draw the parent and its children only if they ore on 'mask_parent'*/
      if(union_ok != false) {
          /*Redraw the object*/
          lv_event_send(obj, LV_EVENT_DRAW_MAIN_BEGIN, &obj_ext_mask);
          lv_event_send(obj, LV_EVENT_DRAW_MAIN, &obj_ext_mask);
          lv_event_send(obj, LV_EVENT_DRAW_MAIN_END, &obj_ext_mask);
  
  #if LV_USE_REFR_DEBUG
          lv_color_t debug_color = lv_color_make(lv_rand(0, 0xFF), lv_rand(0, 0xFF), lv_rand(0, 0xFF));
          lv_draw_rect_dsc_t draw_dsc;
          lv_draw_rect_dsc_init(&draw_dsc);
          draw_dsc.bg_color.full = debug_color.full;
          draw_dsc.bg_opa = LV_OPA_20;
          draw_dsc.border_width = 1;
          draw_dsc.border_opa = LV_OPA_30;
          draw_dsc.border_color = debug_color;
          lv_draw_rect(&obj_ext_mask, &obj_ext_mask, &draw_dsc);
  #endif
          /*Create a new 'obj_mask' without 'ext_size' because the children can't be visible there*/
          lv_obj_get_coords(obj, &obj_area);
          union_ok = _lv_area_intersect(&obj_mask, mask_ori_p, &obj_area);
          if(union_ok != false) {
              lv_area_t mask_child; /*Mask from obj and its child*/
              lv_area_t child_area;
              uint32_t i;
              uint32_t child_cnt = lv_obj_get_child_cnt(obj);
              for(i = 0; i < child_cnt; i++) {
                  lv_obj_t * child = obj->spec_attr->children[i];
                  lv_obj_get_coords(child, &child_area);
                  ext_size = _lv_obj_get_ext_draw_size(child);
                  child_area.x1 -= ext_size;
                  child_area.y1 -= ext_size;
                  child_area.x2 += ext_size;
                  child_area.y2 += ext_size;
                  /*Get the union (common parts) of original mask (from obj)
                   *and its child*/
                  union_ok = _lv_area_intersect(&mask_child, &obj_mask, &child_area);
  
                  /*If the parent and the child has common area then refresh the child*/
                  if(union_ok) {
                      /*Refresh the next children*/
                      lv_refr_obj(child, &mask_child);
                  }
              }
          }
  
          /*If all the children are redrawn make 'post draw' draw*/
          lv_event_send(obj, LV_EVENT_DRAW_POST_BEGIN, &obj_ext_mask);
          lv_event_send(obj, LV_EVENT_DRAW_POST, &obj_ext_mask);
          lv_event_send(obj, LV_EVENT_DRAW_POST_END, &obj_ext_mask);
      }
  }
  
  static void draw_buf_rotate_180(lv_disp_drv_t *drv, lv_area_t *area, lv_color_t *color_p) {
      lv_coord_t area_w = lv_area_get_width(area);
      lv_coord_t area_h = lv_area_get_height(area);
      uint32_t total = area_w * area_h;
      /*Swap the beginning and end values*/
      lv_color_t tmp;
      uint32_t i = total - 1, j = 0;
      while(i > j) {
          tmp = color_p[i];
          color_p[i] = color_p[j];
          color_p[j] = tmp;
          i--;
          j++;
      }
      lv_coord_t tmp_coord;
      tmp_coord = area->y2;
      area->y2 = drv->ver_res - area->y1 - 1;
      area->y1 = drv->ver_res - tmp_coord - 1;
      tmp_coord = area->x2;
      area->x2 = drv->hor_res - area->x1 - 1;
      area->x1 = drv->hor_res - tmp_coord - 1;
  }
  
  static LV_ATTRIBUTE_FAST_MEM void draw_buf_rotate_90(bool invert_i, lv_coord_t area_w, lv_coord_t area_h, lv_color_t *orig_color_p, lv_color_t *rot_buf) {
  
      uint32_t invert = (area_w * area_h) - 1;
      uint32_t initial_i = ((area_w - 1) * area_h);
      for(lv_coord_t y = 0; y < area_h; y++) {
          uint32_t i = initial_i + y;
          if(invert_i)
              i = invert - i;
          for(lv_coord_t x = 0; x < area_w; x++) {
              rot_buf[i] = *(orig_color_p++);
              if(invert_i)
                  i += area_h;
              else
                  i -= area_h;
          }
      }
  }
  
  /**
   * Helper function for draw_buf_rotate_90_sqr. Given a list of four numbers, rotate the entire list to the left.
   */
  static inline void draw_buf_rotate4(lv_color_t *a, lv_color_t *b, lv_color_t * c, lv_color_t * d) {
      lv_color_t tmp;
      tmp = *a;
      *a = *b;
      *b = *c;
      *c = *d;
      *d = tmp;
  }
  
  /**
   * Rotate a square image 90/270 degrees in place.
   * @note inspired by https://stackoverflow.com/a/43694906
   */
  static void draw_buf_rotate_90_sqr(bool is_270, lv_coord_t w, lv_color_t * color_p) {
      for(lv_coord_t i = 0; i < w/2; i++) {
          for(lv_coord_t j = 0; j < (w + 1)/2; j++) {
              lv_coord_t inv_i = (w - 1) - i;
              lv_coord_t inv_j = (w - 1) - j;
              if(is_270) {
                  draw_buf_rotate4(
                      &color_p[i * w + j],
                      &color_p[inv_j * w + i],
                      &color_p[inv_i * w + inv_j],
                      &color_p[j * w + inv_i]
                  );
              } else {
                  draw_buf_rotate4(
                      &color_p[i * w + j],
                      &color_p[j * w + inv_i],
                      &color_p[inv_i * w + inv_j],
                      &color_p[inv_j * w + i]
                  );
              }
  
          }
      }
  }
  
  /**
   * Rotate the draw_buf to the display's native orientation.
   */
  static void draw_buf_rotate(lv_area_t *area, lv_color_t *color_p) {
      lv_disp_drv_t * drv = disp_refr->driver;
      if(disp_refr->driver->full_refresh && drv->sw_rotate) {
          LV_LOG_ERROR("cannot rotate a full refreshed display!");
          return;
      }
      if(drv->rotated == LV_DISP_ROT_180) {
          draw_buf_rotate_180(drv, area, color_p);
          call_flush_cb(drv, area, color_p);
      } else if(drv->rotated == LV_DISP_ROT_90 || drv->rotated == LV_DISP_ROT_270) {
          /*Allocate a temporary buffer to store rotated image*/
          lv_color_t * rot_buf = NULL;
          lv_disp_draw_buf_t * draw_buf = lv_disp_get_draw_buf(disp_refr);
          lv_coord_t area_w = lv_area_get_width(area);
          lv_coord_t area_h = lv_area_get_height(area);
          /*Determine the maximum number of rows that can be rotated at a time*/
          lv_coord_t max_row = LV_MIN((lv_coord_t)((LV_DISP_ROT_MAX_BUF/sizeof(lv_color_t)) / area_w), area_h);
          lv_coord_t init_y_off;
          init_y_off = area->y1;
          if(drv->rotated == LV_DISP_ROT_90) {
              area->y2 = drv->ver_res - area->x1 - 1;
              area->y1 = area->y2 - area_w + 1;
          }
          else {
              area->y1 = area->x1;
              area->y2 = area->y1 + area_w - 1;
          }
  
          /*Rotate the screen in chunks, flushing after each one*/
          lv_coord_t row = 0;
          while(row < area_h) {
              lv_coord_t height = LV_MIN(max_row, area_h-row);
              draw_buf->flushing = 1;
              if((row == 0) && (area_h >= area_w)) {
                  /*Rotate the initial area as a square*/
                  height = area_w;
                  draw_buf_rotate_90_sqr(drv->rotated == LV_DISP_ROT_270, area_w, color_p);
                  if(drv->rotated == LV_DISP_ROT_90) {
                      area->x1 = init_y_off;
                      area->x2 = init_y_off + area_w - 1;
                  }
                  else {
                      area->x2 = drv->hor_res - 1 - init_y_off;
                      area->x1 = area->x2 - area_w + 1;
                  }
              }
              else {
                  /*Rotate other areas using a maximum buffer size*/
                  if(rot_buf == NULL) rot_buf = lv_mem_buf_get(LV_DISP_ROT_MAX_BUF);
                  draw_buf_rotate_90(drv->rotated == LV_DISP_ROT_270, area_w, height, color_p, rot_buf);
  
                  if(drv->rotated == LV_DISP_ROT_90) {
                      area->x1 = init_y_off + row;
                      area->x2 = init_y_off + row + height - 1;
                  }
                  else {
                      area->x2 = drv->hor_res - 1 - init_y_off - row;
                      area->x1 = area->x2 - height + 1;
                  }
              }
  
              /* The original part (chunk of the current area) were split into more parts here.
               * Set the original last_part flag on the last part of rotation. */
              if(row + height >= area_h && draw_buf->last_area && draw_buf->last_part) {
                  draw_buf->flushing_last = 1;
              } else {
                  draw_buf->flushing_last = 0;
              }
  
              /*Flush the completed area to the display*/
              call_flush_cb(drv, area, rot_buf == NULL ? color_p : rot_buf);
              /*FIXME: Rotation forces legacy behavior where rendering and flushing are done serially*/
              while(draw_buf->flushing) {
                  if(drv->wait_cb) drv->wait_cb(drv);
              }
              color_p += area_w * height;
              row += height;
          }
          /*Free the allocated buffer at the end if necessary*/
          if(rot_buf != NULL) lv_mem_buf_release(rot_buf);
      }
  }
  
  /**
   * Flush the content of the draw buffer
   */
  static void draw_buf_flush(void)
  {
      lv_disp_draw_buf_t * draw_buf = lv_disp_get_draw_buf(disp_refr);
      lv_color_t * color_p = draw_buf->buf_act;
  
      /*Flush the rendered content to the display*/
      lv_disp_t * disp = _lv_refr_get_disp_refreshing();
      if(disp->driver->gpu_wait_cb) disp->driver->gpu_wait_cb(disp->driver);
  
       /* In double buffered mode wait until the other buffer is freed
  	  * and driver is ready to receive the new buffer */
  	 if(draw_buf->buf1 && draw_buf->buf2) {
  		 while(draw_buf->flushing) {
  			 if(disp_refr->driver->wait_cb) disp_refr->driver->wait_cb(disp_refr->driver);
  		 }
  	 }
  
  	 draw_buf->flushing = 1;
  
  	if(disp_refr->driver->draw_buf->last_area && disp_refr->driver->draw_buf->last_part) draw_buf->flushing_last = 1;
  	else draw_buf->flushing_last = 0;
  
      if(disp->driver->flush_cb) {
          /*Rotate the buffer to the display's native orientation if necessary*/
          if(disp->driver->rotated != LV_DISP_ROT_NONE && disp->driver->sw_rotate) {
              draw_buf_rotate(&draw_buf->area, draw_buf->buf_act);
          } else {
              call_flush_cb(disp->driver, &draw_buf->area, color_p);
          }
      }
      if(draw_buf->buf1 && draw_buf->buf2) {
          if(draw_buf->buf_act == draw_buf->buf1)
              draw_buf->buf_act = draw_buf->buf2;
          else
              draw_buf->buf_act = draw_buf->buf1;
      }
  }
  
  static void call_flush_cb(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p)
  {
      TRACE_REFR("Calling flush_cb on (%d;%d)(%d;%d) area with %p image pointer", area->x1, area->y1, area->x2, area->y2, color_p);
      drv->flush_cb(drv, area, color_p);
  }
  