用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时,就需要分块,shadowBuf和shadowBuf2是预先分配的固定大小的缓存,专门用来box-blur。关于如何分块后面会具体描述。2)将一个块拷贝到shadowBuf中。3)对这个块box-blur。4)把对这个块box-blur后的结果拷贝到tmpBluredSurfaceData中。5)当所有块都拷贝到tmpBluredSurfaceData中后,把最终结果考回shadowSurface中,整个过程完成。
整体流程中第1)步的分块和第4)步的拷贝是需要特别说明的,见图Figure1.2
Figure1.2阴影分块:shadowSurface为需要box-blur的数据。shadowBuf的大小为固定的,红色虚线所示。shadowSurface里面每个蓝色块为切分(cut)大小,红色虚线为挖出来(dig)的大小,之所以有cut和dig这两个概念,是因为box-blur会使每个块的边界像素失真。导致失真的原因是box-blur会对每个像素的周围像素求均值,那么处于边界的像素周围包含了空像素,即值为0的像素,求均值后就被0值“污染”了,因此边界像素会失真。为了避免失真,就需要额外dig一些像素,例如想对(A0,C2)块进行box-blur,如果不额外dig的话,C和2的边界像素就会被“0污染”,结果就会出在阴影中现浅色的分块线。正确的方法是取到(A0,D3)的区域,box-blur后,再考回(A0,C2)区域。注:shadowBuf的大小是能容纳最大的块的,如(B1,G6)。在shadowBuf中dig区域(红虚线)的最大值已经预先分配好了,那么如何确定中间的cut区域(蓝色区域)的大小呢,即如何确定左上右下四个方向红虚线边到蓝区域边的距离,这个距离即需要额外dig的像素?以左边距离为例,其他方向类似,应该是在水平方向box-blur的次数*box-blur的半径,这是因为进行n次box-blur就会使边界内n*box-blur范围内的像素被“0污染”,所以额外dig这些像素,box-blur完当做废料扔掉就好了。例如,要对(A0,C2)进行box-blur,x方向cut大小为AC,dig大小为CD,y方向cut大小为02,dig大小为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-3为box-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实现模糊阴影技术的主要内容,如果未能解决你的问题,请参考以下文章