用GTK实现模糊阴影技术

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用GTK实现模糊阴影技术相关的知识,希望对你有一定的参考价值。

背景:

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

目的:

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

整体思路:

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

优化策略:

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

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

实现:

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

技术分享

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

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

技术分享

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区域即被污染的区域扔掉了。

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

技术分享

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_image_surface_create (CAIRO_FORMAT_ARGB32,

                                                                           width,

                                                                           height);

                            self->shadowSurface= sur;

                            stride= cairo_image_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_image_surface_get_format (sur);

                            assert(format == CAIRO_FORMAT_ARGB32);

 

                            unsignedchar *surfaceData = cairo_image_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);

                 &nb

以上是关于用GTK实现模糊阴影技术的主要内容,如果未能解决你的问题,请参考以下文章

css 只写div右边的阴影怎么写?

阴影贴图上的高斯模糊(FBO/纹理)

用 CSS 设计漂亮的阴影,css阴影和原理,所有知识点多图动画演示

用CSS3怎么实现盒阴影 box-shadow?

WPF中阴影效果和模糊效果的使用

2016.11.22css实现几个效果