// Third Party credit here... // The Stack Blur Algorithm was invented by Mario Klingemann, // mario@quasimondo.com and described here: // https://underdestruction.com/2004/02/25/stackblur-2004/ // This original C++ RGBA (32 bit color) multi-threaded version // by Victor Laskin (victor.laskin@gmail.com) could be found here: // http://vitiy.info/stackblur-algorithm-multi-threaded-blur-for-cpp /* * blur.c * This code is based on the above but modified to integrate with LVGL * Created on: 13 Apr 2022 * Author: PeterB */ #include "lvgl.h" static uint16_t const stackblur_mul[255] = { 512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512, 454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512, 482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456, 437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512, 497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328, 320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456, 446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335, 329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512, 505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405, 399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328, 324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271, 268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456, 451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388, 385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335, 332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292, 289,287,285,282,280,278,275,273,271,269,267,265,263,261,259 }; static uint8_t const stackblur_shr[255] = { 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 }; /** * Apply stacked blur on a canvas or an area of a canvas * @param canvas pointer to a canvas object * @param area the area to blur. If `NULL` the whole canvas will be blurred. * @param radius, radius of the blur */ void lv_canvas_stackblur_rgba( lv_obj_t *canvas, const lv_area_t *area, uint16_t radius ) { uint32_t x, y, xp, yp, i; uint32_t sp; uint32_t stack_start; uint32_t rsum; uint32_t gsum; uint32_t bsum; uint32_t asum; uint32_t rsum_in; uint32_t gsum_in; uint32_t bsum_in; uint32_t asum_in; uint32_t rsum_out; uint32_t gsum_out; uint32_t bsum_out; uint32_t asum_out; lv_color_t src_px, dst_px, *stack_ptr; lv_color_t *stack; ///< stack buffer LV_ASSERT_OBJ(canvas, LV_OBJX_NAME); if(radius < 2) radius = 2; if(radius > 254) radius = 254; lv_canvas_ext_t * ext = lv_obj_get_ext_attr(canvas); lv_area_t blur_area; if(area) { lv_area_copy(&blur_area, area); if(blur_area.x1 < 0) blur_area.x1 = 0; if(blur_area.y1 < 0) blur_area.y1 = 0; if(blur_area.x2 > ext->dsc.header.w - 1) blur_area.x2 = ext->dsc.header.w - 1; if(blur_area.y2 > ext->dsc.header.h - 1) blur_area.y2 = ext->dsc.header.h - 1; } else { blur_area.x1 = 0; blur_area.y1 = 0; blur_area.x2 = ext->dsc.header.w - 1; blur_area.y2 = ext->dsc.header.h - 1; } lv_color_t color = lv_obj_get_style_image_recolor(canvas, LV_CANVAS_PART_MAIN); bool has_alpha = lv_img_cf_has_alpha(ext->dsc.header.cf); uint32_t w = lv_area_get_width( &blur_area ); ///< blur width uint32_t h = lv_area_get_height( &blur_area ); ///< blur height uint32_t wm = w - 1; uint32_t hm = h - 1; uint32_t dv = (radius * 2) + 1; uint16_t mul_sum = stackblur_mul[radius]; uint8_t shr_sum = stackblur_shr[radius]; stack = lv_mem_alloc(dv*sizeof(lv_color_t)); //vertical for(y = blur_area.y1; y < blur_area.y2; y++) { rsum = gsum = bsum = asum = rsum_in = gsum_in = bsum_in = asum_in = rsum_out = gsum_out = bsum_out = asum_out = 0; src_px = lv_img_buf_get_px_color( &ext->dsc, blur_area.x1, y, color); if(has_alpha) src_px.ch.alpha = lv_img_buf_get_px_alpha(&ext->dsc, blur_area.x1, y); for(i = 0; i <= radius; i++) { stack_ptr = &stack[i]; *stack_ptr = src_px; rsum += src_px.ch.red * (i + 1); gsum += src_px.ch.green * (i + 1); bsum += src_px.ch.blue * (i + 1); if(has_alpha) asum += src_px.ch.alpha * (i + 1); rsum_out += src_px.ch.red; gsum_out += src_px.ch.green; bsum_out += src_px.ch.blue; if(has_alpha) asum_out += src_px.ch.alpha; } x = blur_area.x1; for(i = 1; i <= radius; i++) { if (i <= wm) { src_px = lv_img_buf_get_px_color( &ext->dsc, ++x, y, color); if(has_alpha) src_px.ch.alpha = lv_img_buf_get_px_alpha(&ext->dsc, x, y); } stack_ptr = &stack[(i + radius)]; *stack_ptr = src_px; rsum += src_px.ch.red * (radius + 1 - i); gsum += src_px.ch.green * (radius + 1 - i); bsum += src_px.ch.blue * (radius + 1 - i); if(has_alpha) asum += src_px.ch.alpha * (radius + 1 - i); rsum_in += src_px.ch.red; gsum_in += src_px.ch.green; bsum_in += src_px.ch.blue; if(has_alpha) asum_in += src_px.ch.alpha; } sp = radius; xp = radius; if (xp > wm) xp = wm; src_px = lv_img_buf_get_px_color( &ext->dsc, xp + blur_area.x1, y, color); for(x = blur_area.x1; x < blur_area.x2; x++) { dst_px.ch.red = (rsum * mul_sum) >> shr_sum; dst_px.ch.green = (gsum * mul_sum) >> shr_sum; dst_px.ch.blue = (bsum * mul_sum) >> shr_sum; if(has_alpha) dst_px.ch.alpha = (asum * mul_sum) >> shr_sum; if(has_alpha) lv_img_buf_set_px_alpha(&ext->dsc, x, y, dst_px.ch.alpha); lv_img_buf_set_px_color( &ext->dsc, x, y, dst_px ); rsum -= rsum_out; gsum -= gsum_out; bsum -= bsum_out; if(has_alpha) asum -= asum_out; stack_start = sp + dv - radius; if(stack_start >= dv) stack_start -= dv; stack_ptr = &stack[stack_start]; rsum_out -= stack_ptr->ch.red; gsum_out -= stack_ptr->ch.green; bsum_out -= stack_ptr->ch.blue; if(has_alpha) asum_out -= stack_ptr->ch.alpha; if(xp < wm) { ++xp; src_px = lv_img_buf_get_px_color( &ext->dsc, xp + blur_area.x1, y, color); if(has_alpha) src_px.ch.alpha = lv_img_buf_get_px_alpha(&ext->dsc, xp + blur_area.x1, y); } *stack_ptr = src_px; rsum_in += src_px.ch.red; gsum_in += src_px.ch.green; bsum_in += src_px.ch.blue; if(has_alpha) asum_in += src_px.ch.alpha; rsum += rsum_in; gsum += gsum_in; bsum += bsum_in; if(has_alpha) asum += asum_in; ++sp; if (sp >= dv) sp = 0; stack_ptr = &stack[sp]; rsum_out += stack_ptr->ch.red; gsum_out += stack_ptr->ch.green; bsum_out += stack_ptr->ch.blue; if(has_alpha) asum_out += stack_ptr->ch.alpha; rsum_in -= stack_ptr->ch.red; gsum_in -= stack_ptr->ch.green; bsum_in -= stack_ptr->ch.blue; if(has_alpha) asum_in -= stack_ptr->ch.alpha; } } // Horizontal for(x = blur_area.x1; x < blur_area.x2; x++) { rsum = gsum = bsum = asum = rsum_in = gsum_in = bsum_in = asum_in = rsum_out = gsum_out = bsum_out = asum_out = 0; src_px = lv_img_buf_get_px_color( &ext->dsc, x, blur_area.y1, color); if(has_alpha) src_px.ch.alpha = lv_img_buf_get_px_alpha(&ext->dsc, x, blur_area.y1); for(i = 0; i <= radius; i++) { stack_ptr = &stack[i]; *stack_ptr = src_px; rsum += src_px.ch.red * (i + 1); gsum += src_px.ch.green * (i + 1); bsum += src_px.ch.blue * (i + 1); if(has_alpha) asum += src_px.ch.alpha * (i + 1); rsum_out += src_px.ch.red; gsum_out += src_px.ch.green; bsum_out += src_px.ch.blue; if(has_alpha) asum_out += src_px.ch.alpha; } y = blur_area.y1; for(i = 1; i <= radius; i++) { if(i <= hm) { src_px = lv_img_buf_get_px_color( &ext->dsc, x, ++y, color);//src_ptr += w4; // +stride if(has_alpha) src_px.ch.alpha = lv_img_buf_get_px_alpha(&ext->dsc, x, y); } stack_ptr = &stack[(i + radius)]; *stack_ptr = src_px; rsum += src_px.ch.red * (radius + 1 - i); gsum += src_px.ch.green * (radius + 1 - i); bsum += src_px.ch.blue * (radius + 1 - i); if(has_alpha) asum += src_px.ch.alpha * (radius + 1 - i); rsum_in += src_px.ch.red; gsum_in += src_px.ch.green; bsum_in += src_px.ch.blue; if(has_alpha) asum_in += src_px.ch.alpha; } sp = radius; yp = radius; if (yp > hm) yp = hm; src_px = lv_img_buf_get_px_color( &ext->dsc, yp + blur_area.y1, x, color); for(y = blur_area.y1; y < blur_area.y2; y++) { dst_px.ch.red = (rsum * mul_sum) >> shr_sum; dst_px.ch.green = (gsum * mul_sum) >> shr_sum; dst_px.ch.blue = (bsum * mul_sum) >> shr_sum; if(has_alpha) dst_px.ch.alpha = (asum * mul_sum) >> shr_sum; if(has_alpha) lv_img_buf_set_px_alpha(&ext->dsc, x, y, dst_px.ch.alpha); lv_img_buf_set_px_color( &ext->dsc, x, y, dst_px ); rsum -= rsum_out; gsum -= gsum_out; bsum -= bsum_out; if(has_alpha) asum -= asum_out; stack_start = sp + dv - radius; if(stack_start >= dv) stack_start -= dv; stack_ptr = &stack[stack_start]; rsum_out -= stack_ptr->ch.red; gsum_out -= stack_ptr->ch.green; bsum_out -= stack_ptr->ch.blue; if(has_alpha) asum_out -= stack_ptr->ch.alpha; if(yp < hm) { ++yp; src_px = lv_img_buf_get_px_color( &ext->dsc, x, yp + blur_area.y1, color); if(has_alpha) src_px.ch.alpha = lv_img_buf_get_px_alpha(&ext->dsc, x, yp + blur_area.y1); } *stack_ptr = src_px; rsum_in += src_px.ch.red; gsum_in += src_px.ch.green; bsum_in += src_px.ch.blue; if(has_alpha) asum_in += src_px.ch.alpha; rsum += rsum_in; gsum += gsum_in; bsum += bsum_in; if(has_alpha) asum += asum_in; ++sp; if (sp >= dv) sp = 0; stack_ptr = &stack[sp]; rsum_out += stack_ptr->ch.red; gsum_out += stack_ptr->ch.green; bsum_out += stack_ptr->ch.blue; if(has_alpha) asum_out += stack_ptr->ch.alpha; rsum_in -= stack_ptr->ch.red; gsum_in -= stack_ptr->ch.green; bsum_in -= stack_ptr->ch.blue; if(has_alpha) asum_in -= stack_ptr->ch.alpha; } } lv_obj_invalidate(canvas); }