背景:

为了美观,图形编辑软件一般都有线条和图片的阴影效果,阴影表现为深灰色的模糊图形,与原图形的形状一致。而GTK并没有内置的阴影效果,因此需要自己实现。

目的:

    利用GTK函数实现阴影效果。

整体思路:

阴影效果即一个位图,先画它,然后再画主图,就组成了阴影效果。首先,创建一个cairo_p_w_picpath_surface,然后用灰色在上面画图形。然后获取cairo_p_w_picpath_surface的像素数据,对其进行box-blur。最后,把位图和主图都画出来即可。

优化策略:

一,             时间优化,每个图形保存一份自己的阴影,只有在图形改变时才需要重绘阴影,其他时候(如平移、缩放)不用重新计算阴影,节省计算时间。

二,             空间优化,对每个图形进行box-blur时,可以共用一个缓存进行,这个缓存的大小是固定的。因为缓存大小固定,所以如果图形大小超过了缓存大小,则需要对图形进行分割,然后对分割后的每一块进行box-blur,再把box-blur的结果考回阴影位图。

实现:

主要复杂点在于上述优化策略中的分割策略实现,下面用图figure1.1阐释:

用GTK实现模糊阴影技术_gtk

Figure1.1阴影整体流程:1)shadowSurface为图形阴影的像素数据,比如用cairo画一些线条在上面。当这个阴影数据大于缓存shadowBuf时,就需要分块,shadowBufshadowBuf2是预先分配的固定大小的缓存,专门用来box-blur。关于如何分块后面会具体描述。2)将一个块拷贝到shadowBuf中。3)对这个块box-blur4)把对这个块box-blur后的结果拷贝到tmpBluredSurfaceData中。5)当所有块都拷贝到tmpBluredSurfaceData中后,把最终结果考回shadowSurface中,整个过程完成。

整体流程中第1)步的分块和第4)步的拷贝是需要特别说明的,见图Figure1.2

用GTK实现模糊阴影技术_技术_02

Figure1.2阴影分块:shadowSurface为需要box-blur的数据。shadowBuf的大小为固定的,红色虚线所示。shadowSurface里面每个蓝色块为切分(cut)大小,红色虚线为挖出来(dig)的大小,之所以有cutdig这两个概念,是因为box-blur会使每个块的边界像素失真。导致失真的原因是box-blur会对每个像素的周围像素求均值,那么处于边界的像素周围包含了空像素,即值为0的像素,求均值后就被0值“污染”了,因此边界像素会失真。为了避免失真,就需要额外dig一些像素,例如想对(A0,C2)块进行box-blur,如果不额外dig的话,C2的边界像素就会被“0污染”,结果就会出在阴影中现浅色的分块线。正确的方法是取到(A0,D3)的区域,box-blur后,再考回(A0,C2)区域。注:shadowBuf的大小是能容纳最大的块的,如(B1,G6)。在shadowBufdig区域(红虚线)的最大值已经预先分配好了,那么如何确定中间的cut区域(蓝色区域)的大小呢,即如何确定左上右下四个方向红虚线边到蓝区域边的距离,这个距离即需要额外dig的像素?以左边距离为例,其他方向类似,应该是在水平方向box-blur的次数*box-blur的半径,这是因为进行nbox-blur就会使边界内n*box-blur范围内的像素被“0污染”,所以额外dig这些像素,box-blur完当做废料扔掉就好了。例如,要对(A0,C2)进行box-blurx方向cut大小为ACdig大小为CDy方向cut大小为02dig大小为23,总的额外dig出来的区域为(C0,D3)(A2,D3)的并集,(A0,D3)作为box-blur的输入,box-blur后数据在shadowBuf中,然后拷贝到tmpBluredSurfaceData中,拷贝的区域是等同于(A0,C2)大小的区域,dig区域即被污染的区域扔掉了。

下图为阴影结果的效果图:

用GTK实现模糊阴影技术_阴影_03

Figure1.3阴影效果

核心代码参考实现如list1-1所示,为图形类MyShape的画阴影函数。List1-2为工具函数。List1-3box-blur函数。

List1-1

    void           my_shape_draw_self_shadow        (MyShape* self, ApplicationState*appState) {

        

        

         my_debug("my_shape_draw_self_shadow ...");

        

         MyShapePrivate*priv = MY_SHAPE_GET_PRIVATE (self);

         cairo_t*copy = appState->cr;

         cairo_t*c;

         cairo_surface_t*sur;

         cairo_t*cr_window;

         int  cut_width_byte;

         int  cut_height_byte;

         int  dig_extra_left_byte;

         int  dig_extra_top_byte;

         int  dig_extra_right_byte;

         int  dig_extra_bottom_byte;

         int  width;

         int  height;

         int  stride;

         int    blockCountX;

         int  blockCountY;

         int  blockIndexX;

         int  blockIndexY;

         int  blockX;

         int  blockY;

         int  block_width_byte;

         int  block_height_byte;

         int  block_inner_left_byte;

         int  block_inner_top_byte;

         int  block_inner_right_byte;

         int  block_inner_bottom_byte;

        

         if(self->isShowing && self->isShadowing) {

                  

                   MY_SHAPE_GET_CLASS(self)->update_shadow_rect (self);

                  

                   if(self->isShadowDirty) { // dirty,需要重新blur shadow

                           

                            //let sub class draw shadow

                           

                            if(self->shadowSurface) {

                                     cairo_surface_destroy(self->shadowSurface);

                            }

                            width= self->shadowWidth;

                            height= self->shadowHeight;

                            sur= cairo_p_w_picpath_surface_create (CAIRO_FORMAT_ARGB32,

                                                                           width,

                                                                           height);

                            self->shadowSurface= sur;

                            stride= cairo_p_w_picpath_surface_get_stride (sur);

                            self->shadowStride= stride;

                            c= cairo_create (sur);

                            appState->cr= c;

                            cairo_set_source_rgb(c, 0.5, 0.5, 0.5);

                            cairo_set_dash(c, self->dashes, self->dashCount, self->dashOffset);

                            cairo_set_line_width(c, self->strokeWidth * appState->scale);

                            MY_SHAPE_GET_CLASS(self)->draw_self_shadow (self, appState);

                            cairo_destroy(c);

                            appState->cr= copy;

                           

                            //let us blur

                            cairo_format_tformat = cairo_p_w_picpath_surface_get_format (sur);

                            assert(format == CAIRO_FORMAT_ARGB32);

 

                            unsignedchar *surfaceData = cairo_p_w_picpath_surface_get_data (sur);

                            if(stride * height <= appState->shadowBufSize) { // buf够大,不用分块blur

                                     memset(appState->shadowBuf, 0, appState->shadowBufSize);

                                     my_box_blur_horizontal (surfaceData, appState->shadowBuf, width,height, stride, self->boxRadius);

                                     my_box_blur_horizontal (appState->shadowBuf, surfaceData, width,height, stride, self->boxRadius);

                                     my_box_blur_vertical      (surfaceData, appState->shadowBuf,width, height, stride, self->boxRadius);

                                     my_box_blur_vertical      (appState->shadowBuf, surfaceData,width, height, stride, self->boxRadius);

                                    

                            }else { // buf太小,需要分块blur

                                     unsignedchar *tmpBluredSurfaceData = g_malloc0( sizeof(unsigned char) * stride *height);

                                     memset(appState->shadowBuf, 0, appState->shadowBufSize);

                                     memset(appState->shadowBuf2, 0, appState->shadowBufSize);

                                     intblurTimes = 1;     // 各方向blur次数

                                     dig_extra_left_byte                   = self->boxRadius * 4 *blurTimes;

                                     dig_extra_top_byte                   = self->boxRadius *blurTimes;

                                     dig_extra_right_byte       = self->boxRadius * 4 * blurTimes;

                                     dig_extra_bottom_byte  = self->boxRadius * blurTimes;

                                     cut_width_byte        = appState->shadowBufStride -dig_extra_left_byte - dig_extra_right_byte;

                                     cut_height_byte      = appState->shadowBufHeight -dig_extra_top_byte - dig_extra_bottom_byte;

                                     assert(cut_width_byte > 0);

                                     assert(cut_width_byte > 0);

                                     blockCountX= ceil ((double) stride / cut_width_byte);

                                     blockCountY= ceil ((double) height / cut_height_byte);

                                    

                                     for(blockIndexY = 0; blockIndexY < blockCountY; blockIndexY++) {

                                               for(blockIndexX = 0; blockIndexX < blockCountX; blockIndexX++) {

 

                                                        my_util_block_position_in_buffer(surfaceData,

                                                                                                                stride,

                                                                                                                height,

                                                                                                                blockIndexX,

                                                                                                                blockIndexY,

                                                                                                                cut_width_byte,

                                                                                                                cut_height_byte,

                                                                                                                dig_extra_left_byte,

                                                                                                                dig_extra_top_byte,

                                                                                                                dig_extra_right_byte,

                                                                                                                dig_extra_bottom_byte,

                                                                                                                &blockX,

                                                                                                                &blockY,

                                                                                                                &block_width_byte,

                                                                                                                &block_height_byte,

                                                                                                                &block_inner_left_byte,

                                                                                                                &block_inner_top_byte,

                                                                                                                &block_inner_right_byte,

                                                                                                                &block_inner_bottom_byte);

                                                                                                               

                                                        my_util_memcpy_box_to_continuous(surfaceData,

                                                                                                                stride,

                                                                                                                height,

                                                                                                                blockX,

                                                                                                                blockY,

                                                                                                                block_width_byte,

                                                                                                                block_height_byte,

                                                                                                                appState->shadowBuf,

                                                                                                                0);

                                                        //每个方向必须严格blur 2

                                                        my_box_blur_horizontal (appState->shadowBuf,appState->shadowBuf2, block_width_byte / 4, block_height_byte,block_width_byte, self->boxRadius);

//                                                     my_box_blur_horizontal (appState->shadowBuf2,appState->shadowBuf, block_width_byte / 4, block_height_byte,block_width_byte, self->boxRadius);

//                                                     my_box_blur_vertical      (appState->shadowBuf,appState->shadowBuf2, block_width_byte / 4, block_height_byte,block_width_byte, self->boxRadius);

                                                        my_box_blur_vertical      (appState->shadowBuf2, appState->shadowBuf,block_width_byte / 4, block_height_byte, block_width_byte, self->boxRadius);

 

                                                        my_util_memcpy_box_to_box(appState->shadowBuf,

                                                                                                                block_width_byte,

                                                                                                                block_height_byte,

                                                                                                                block_inner_left_byte,

                                                                                                                block_inner_top_byte,

                                                                                                                block_width_byte- block_inner_left_byte - block_inner_right_byte,

                                                                                                                block_height_byte- block_inner_top_byte - block_inner_bottom_byte,

                                                                                                                tmpBluredSurfaceData,

                                                                                                                blockX+ block_inner_left_byte,

                                                                                                                blockY+ block_inner_top_byte,

                                                                                                                stride,

                                                                                                                height);

                                               }

                                     }

                                     memcpy(surfaceData, tmpBluredSurfaceData, sizeof(unsigned char) * stride * height);

                                     g_free(tmpBluredSurfaceData);

                            }

                            self->isShadowDirty= FALSE;

                   }                

                   //let us draw the shadow surface finally

                  

                   cr_window= gdk_cairo_create (appState->pixmap);

                   cairo_rectangle(cr_window,

                                                        self->shadowX+ self->shadowDeltaX + appState->orignX,

                                                        self->shadowY+ self->shadowDeltaY + appState->orignY,

                                                        self->shadowWidth,

                                                        self->shadowHeight);

                   cairo_clip(cr_window);

                   cairo_set_source_surface(cr_window,

                                                                           self->shadowSurface,

                                                                           self->shadowX+ self->shadowDeltaX + appState->orignX,

                                                                           self->shadowY+ self->shadowDeltaY + appState->orignY);

                   cairo_set_operator(cr_window, CAIRO_OPERATOR_MULTIPLY);

                   cairo_paint(cr_window);

                   //for debug start

#ifndef MY_GUI_NDEBUG

                   cairo_set_source_rgb(cr_window, 1., 0., 0.);

                   cairo_rectangle(cr_window,

                                                        self->shadowX+ self->shadowDeltaX + appState->orignX,

                                                        self->shadowY+ self->shadowDeltaY + appState->orignY,

                                                        self->shadowWidth,

                                                        self->shadowHeight);

                   cairo_rectangle(cr_window,

                                                        self->shadowX+ self->shadowDeltaX + self->shadowPadding + appState->orignX,

                                                        self->shadowY+ self->shadowDeltaY + self->shadowPadding + appState->orignY,

                                                        self->shadowWidth- self->shadowPadding * 2,

                                                        self->shadowHeight- self->shadowPadding * 2);

                   cairo_stroke(cr_window);

#endif

                   //for debug end

                  cairo_destroy (cr_window);

         }

}

 

List1-2:

void           my_util_block_position_in_buffer   (unsigned char *buf,

                                                                                                                int  buf_stride,

                                                                                                                int  buf_height,

                                                                                                                int  block_index_x,

                                                                                                                int  block_index_y,

                                                                                                                int  block_width_byte,

                                                                                                                int  block_height_byte,

                                                                                                                int  extra_left_byte,

                                                                                                                int  extra_top_byte,

                                                                                                                int  extra_right_byte,

                                                                                                                int  extra_bottom_byte,

                                                                                                                int  *out_x,

                                                                                                                int  *out_y,

                                                                                                                int  *out_width_byte,

                                                                                                                int  *out_height_byte,

                                                                                                                int  *out_inner_left_byte,

                                                                                                                int  *out_inner_top_byte,

                                                                                                                int  *out_inner_right_byte,

                                                                                                                int  *out_inner_bottom_byte) {

        

         assert(buf_stride >= 0);

         assert(buf_height >= 0);

         assert(block_width_byte >= 0);

         assert(block_height_byte >= 0);

         assert(block_index_x >= 0);

         assert(block_index_y >= 0);

         assert(extra_left_byte >= 0);

         assert(extra_top_byte >= 0);

         assert(extra_right_byte >= 0);

         assert(extra_bottom_byte >= 0);

        

         intx, y, width_byte, height_byte;

         //calculate x

         x= block_index_x * block_width_byte;

         assert(x < buf_stride);

         if(x + block_width_byte < buf_stride) {

                   width_byte= block_width_byte;

         }else {

                   width_byte= buf_stride - x;

         }

         if(x - extra_left_byte >= 0) {

                   x-= extra_left_byte;

                   width_byte+= extra_left_byte;

                   *out_inner_left_byte= extra_left_byte;

         }else {

                   *out_inner_left_byte= 0;

         }

         if(x + width_byte + extra_right_byte <= buf_stride) {

                   width_byte+= extra_right_byte;

                   *out_inner_right_byte= extra_right_byte;

         }else {

                   *out_inner_right_byte= 0;

         }

         //calculate y

         y= block_index_y * block_height_byte;

         assert(y < buf_height);

         if(y + block_height_byte < buf_height) {

                   height_byte= block_height_byte;

         }else {

                   height_byte= buf_height - y;

         }

         if(y - extra_top_byte >= 0) {

                   y-= extra_top_byte;

                   height_byte+= extra_top_byte;

                   *out_inner_top_byte= extra_top_byte;

         }else {

                   *out_inner_top_byte= 0;

         }

         if(y + height_byte + extra_bottom_byte <= buf_height) {

                   height_byte+= extra_bottom_byte;

                   *out_inner_bottom_byte= extra_bottom_byte;

         }else {

                   *out_inner_bottom_byte= 0;

         }

         *out_x= x;

         *out_y= y;

         *out_width_byte= width_byte;

         *out_height_byte= height_byte;

}

 

 

void                   my_util_memcpy_box_to_box                           (unsigned char *src,

                                                                                                                int  src_stride,

                                                                                                                int  src_height_byte,

                                                                                                                int  box_src_x,

                                                                                                                int  box_src_y,

                                                                                                                int  box_width_byte,

                                                                                                                int  box_height_byte,

                                                                                                                unsigned         char         *dst,

                                                                                                                int  box_dst_x,

                                                                                                                int  box_dst_y,

                                                                                                                int  dst_stride,

                                                                                                                int  dst_height_byte) {

                                                                                                                        

         assert(box_src_x >= 0);

         assert(box_src_y >= 0);

         assert(box_src_x + box_width_byte <= src_stride);

         assert(box_src_y + box_height_byte <= src_height_byte);

         assert(box_dst_x >= 0);

         assert(box_dst_y >= 0);

         assert(box_dst_x + box_width_byte <= dst_stride);

         assert(box_dst_y + box_height_byte <= dst_height_byte);

        

         inti, j;

         unsignedchar *p;    // points to start address of eachrow of src

         unsignedchar *q;    // points to start address ofeach row of dst

         p= src + box_src_y * src_stride + box_src_x;

         q= dst + box_dst_y * dst_stride + box_dst_x;

         for(i = 0; i < box_height_byte; i++) {

                   for(j = 0; j < box_width_byte; j++) {

                            q[j]= p[j];

                   }

                   p+= src_stride;

                   q+= dst_stride;

         }

}

 

 

void                   my_util_memcpy_box_to_continuous    (unsigned char *src,

                                                                                                                int  src_stride,

                                                                                                                int  src_height_byte,

                                                                                                                int  box_x,

                                                                                                                int  box_y,

                                                                                                                int  box_width_byte,

                                                                                                                int  box_height_byte,

                                                                                                                unsigned         char         *dst,

                                                                                                                int  dst_offset) {

                                                                                                                        

         assert(box_x >= 0);

         assert(box_y >= 0);

         assert(box_x + box_width_byte <= src_stride);

         assert(box_y + box_height_byte <= src_height_byte);

         assert(dst_offset >= 0);

        

         inti, j;

         unsignedchar *p;    // points to start address ofeach row of src

         unsignedchar *q;    // points to address of dst

         p= src + box_y * src_stride + box_x;

         q= dst + dst_offset;

         for(i = 0; i < box_height_byte; i++) {

                   for(j = 0; j < box_width_byte; j++) {

                            *q= p[j];

                            q++;

                   }

                   p+= src_stride;

         }

}

List1-3

void  my_box_blur_horizontal (unsigned        char * src,

                                                                             unsigned     char * dst,

                                                                             int        width,

                                                                             int        height,

                                                                             int        stride,

                                                                             int        radius){

        

         my_debug("my_box_blur_horizontal from %#x to %#x, %d %d %d", src, dst,stride, rowCount, radius);

         g_assert(radius >= 1);

        

         intx, y, k; // pixel coordinary

         intboxSize = radius * 2 + 1;

         unsignedint totalR, totalG, totalB, totalA;

         unsignedchar *p, *q;

        

         for(y = 0; y < height; y++) {

                   totalR= 0;

                   totalG= 0;

                   totalB= 0;

                   totalA= 0;

                   for(k = 0; k < min (width, radius); k++) {

                            p= src + y * stride + k * 4;

                            totalR+= p[0];

                            totalG+= p[1];

                            totalB+= p[2];

                            totalA+= p[3];

                   }

                   for(x = 0; x < width; x++) {

                            if(x - radius - 1 >= 0) {

                                     p= src + y * stride + (x - radius - 1) * 4;

                                     totalR-= p[0];

                                     totalG-= p[1];

                                     totalB-= p[2];

                                     totalA-= p[3];

                            }

                            if(x + radius < width) {

                                     p= src + y * stride + (x + radius) * 4;

                                     totalR+= p[0];

                                     totalG+= p[1];

                                     totalB+= p[2];

                                     totalA+= p[3];

                            }

                            q= dst + y * stride + x * 4;

                            q[0]= totalR / boxSize;

                            q[1]= totalG / boxSize;

                            q[2]= totalB / boxSize;

                            q[3]= totalA / boxSize;

                   }

         }

}

 

 

void  my_box_blur_vertical      (unsigned        char*src,

                                                                             unsigned     char*dst,

                                                                             int        width,

                                                                             int        height,

                                                                             int        stride,

                                                                             int        radius){

        

         intx, y, k;

         intboxSize = radius * 2 + 1;

         unsignedint totalR, totalG, totalB, totalA;

         unsignedchar *p,    // src pointer, points toaddress of pixel

                                     *q;             // dst pointer

        

         for(x = 0; x < width; x++) {

                   totalR= 0;

                   totalG= 0;

                   totalB= 0;

                   totalA= 0;

                   for(k = 0; k < min (height, radius); k++) {

                            p= src + k * stride + x * 4;

                            totalR+= p[0];

                            totalG+= p[1];

                            totalB+= p[2];

                            totalA+= p[3];

                   }

                   for(y = 0; y < height; y++) {

                            if(y - radius - 1 >= 0) {

                                     p= src + (y - radius - 1) * stride + x * 4;

                                     totalR-= p[0];

                                     totalG-= p[1];

                                     totalB-= p[2];

                                     totalA-= p[3];

                            }

                            if(y + radius < height) {

                                     p= src + (y + radius) * stride + x * 4;

                                     totalR+= p[0];

                                     totalG+= p[1];

                                     totalB+= p[2];

                                     totalA+= p[3];

                            }

                            q= dst + y * stride + x * 4;

                            q[0]= totalR / boxSize;

                            q[1]= totalG / boxSize;

                            q[2]= totalB / boxSize;

                            q[3]= totalA / boxSize;                      

                   }

         }

}

结语:

综上,讲述了如何利用GTK实现图形的阴影效果,按此思路完全可以推广的任何平台,并不局限于GTK。才疏学浅,难免有所纰漏,欢迎批评指正。

谢谢观赏!