UEFI开发探索32 – 有趣的图像特效

(请保留->发布地址: http://yiiyee.cn/blog/author/luobing/ )

PhotoShop和ACD See中,有各种对图像进行特效处理的功能,我在大学的时候就很着迷,一度考虑报考图像处理相关的研究生专业。

可惜生活总是变幻无常,推着我走向了另外一条道路,曾经的梦想还没出航就搁浅了。

不过这也影响了我之后的编程生涯,在底层开发的时候,总想着搞清楚各种图像的特效是怎么实现的。

这次准备拿UEFI App来开刀。

1 灰度转换

先上效果图:

图1 灰度化图像效果图

函数实现在/Window.c中。后续所有的特效实现函数,都在这个文件中。

实现灰度效果的函数为TransferToGray(),它可以对指定范围的屏幕图像进行灰度处理。其公式为:灰度值=0.3x红色分量  + 0.59x绿色分量 + 0.11x蓝色分量。我为了计算简单,做了一些修改,公式改为:灰度值=0.25x红色分量  + 0.5x绿色分量 + 0.125x蓝色分量,这个公式可以用移位计算实现。

针对每个颜色像素进行上述计算,然后将灰度值填入各个RGB分量,也是就是说各个分量的值相同,效果就完成了。

2 镜像显示

代码中实现了三种镜像效果:上下颠倒、左右颠倒、上下左右同时颠倒:

图2 镜像效果

实现镜像效果的函数为ReviseImage(),对屏幕上指定范围的图像进行镜像处理。原理上比较直白,将屏幕上指定矩形范围的图像读入缓冲区,并对缓冲区进行数据变换,以实现镜像效果。

设立目的缓冲区,将源缓冲区的最后一行拷贝到目的缓冲区的第一行;源缓冲区的倒数第二行拷贝到目的缓冲区第二行…依次进行操作,可以实现上下颠倒的镜像效果。

左右颠倒有点类似,只是对列进行操作了。

原理上不难,需要注意的是所使用的函数CopyMem,其原型在BaseMemoryLib.h中,类似于C语言库函数的memcpy。缓冲区的大小要由程序员来保证足够,注意不要溢出。

3 透明特效

第一次从WinXP转到Win7的时候,被透明的图标效果惊艳不已。随后似乎是一夜之间,所看到的的操作系统都有了类似的效果。甚至有人模仿Aero,在WinXP上做了类似的桌面效果。

现在是Win10的时代,似乎周边可见的操作系统,包括ios、andriod、各种Linux发行版,都是透明的图标。这种若隐若现的效果,似乎满足了人类内心深处的某种癖好,现在成了各种软件的标配。不过…确实好看。

试着实现这种效果:

图3 透明效果

之前的鼠标GUI程序构建中,鼠标的图标已经使用了这种效果了。

实现此效果的函数为AlphaTransparent()。它将图像缓冲区*ImagePointer所指向的图像,以透明度Alpha(0-16,值越大,透明度越小)显示在指定区域。

其公式为:颜色分量=(屏幕颜色分量*(16-Alpha)+覆盖图像颜色分量*Alpha)/16。如果需要更细致的透明效果,把Alpha再弄大写就可以了。

4 显示清屏之拉幕效果

后续的几个图像特效依赖于Graphic.c下的putPartRectImage()函数,它实现了对给定图像的部分显示。也即给定图像缓冲区BltBuffer,对其中的子矩形区域进行显示。采用了Blt()的显示子矩形功能,注意其最后一个参数是以字节为单位的,具体可以参考UEFI Spec。

效果图:

图4 拉幕效果演示

代码不解释了,实际上用个for循环就实现了,逻辑很简单。

5 显示清屏之落雨效果

这个效果,不是特别喜欢,主要是图像会被拉长。看每个人的喜好了:

图5 落雨特效

实现的函数为RainFallShow()和RainAscendShow(),见源文件Window.c。图中使用的是RainFallShow(),特效时间太长,Gif很难压缩在2M以内,只能截取部分时长来演示了。

代码粗糙得很,应该想办法解决图像拉长的问题,留待后续吧。

6 显示清屏的其他特效

后面的特效都不解释了,代码都不复杂,主要看看效果吧。

百叶窗特效,函数hWindowShadesShow()和vWindowShadesShow():

图6 百叶窗特效

扩散收拢特效,函数为CornerToCenter()与CenterToCorner()。这两个函数属于半完成状态,程序还是比较粗糙。测试中发现,显示Jpeg的函数有些问题,当图像高度大于宽度时不显示。很可能是ffjpeg内部的问题,留到以后再研究吧。

图7 扩散收拢特效

交错展现特效。使用的函数为hInterlaceShow()和vInterlaceShow(),分别为水平交错的特效和垂直交错的特效。两个特效都比较费时间,所以只截取了水平交错特效的动图:

图8 水平交错特效

交错互补特效。使用的函数为hInteractiveShow()和vInteractiveShow(),与上面的特效有点类似。

图9 交错互补特效

总结一下,所有显示清屏特效,都是以一定的规律显现图像的一部分。所需要考虑的,在循环完成后,图像缓冲区应该遍历完成。

这几个特效编写得比较仓促,有很多地方没有考虑完整,包括是否遍历完、显示速度等等。可以参照这些代码,编写其他的各种特效,比如马赛克、雾化特效等。

在代码中,还涉及到延时问题。我所用的是Boot Service中的Stall()来延时,以ms为单位。不过,在实际运用在Option ROM的开发中,发现这个函数的延时时长不是所要求的ms,有长有短。

我没有搞明白问题出在哪儿。因此,在产品商用化中,涉及到延时的特效,都没有启用。

7 螺旋特效

其实这也属于显示清屏的特效之一,只是它所使用的底层显示函数与上面的有点不同。上面的是以块为显示的底层函数putPartRectImage(),而这个特效使用的是putImagLine(),以线段为单位。

图10 螺旋展示

所使用的函数为SpiralShow()与SpiralShowClear(),定义于Window.c中。美中不足的是,速度有点慢。如何加快显示速度,是个值得研究的课题。

从方向上来说,我觉得可以好好研究下Spec中Blt()函数的实现,核心点仍旧是减少显存换页。

以putImageLine()为基础,还能实现更多的特效,比如风扇状显现、射线状显现等。时间原因,就不再编写代码了。

另外,实际上还可以构建以圆为基础的putImageCircle(),实现圆为基础单位的显现和清除。总觉得这样下去会没完没了,到此为止吧—虽然很有趣。

8 渐隐和渐显效果

最后一个效果,是很久以前编写好的,让图像逐渐从暗到明的显现以及反过来,逐渐消失。

图11 渐隐效果

渐隐渐显的效果是很久以前编写的代码,分别为SmoothInZone()和SmoothOutZone(),只针对屏幕指定区域进行操作。这两个函数的局限性比较大,通用性不怎么好。理论上应该对指定的图像缓冲区进行操作,并显示到屏幕上。

那时没有这么写,应该是因为刚接触UEFI图形显示,还没整体思考好如何构建代码。对比现在写的代码和之前写的代码,也能看出来之前考虑不够全面。最突出的问题是通用性考虑以及跨屏截断的问题(Blt()内部可能已经解决截断问题,我不大确定),有机会再全部推倒重写吧。

9 我的想法

在UEFI下把这些特效全部展示出来,在开始刚学UEFI的时候就有这样的想法了。可惜项目比较紧,而UEFI的开发,除了在联想的双机项目以及UEFI Option ROM外,公司其他产品用不上。就这么一直耽搁下来了,这次算是实现了一个当初的小目标了。

除去对UEFI提供的protocol的理解外,特效的实现还有两个制约因素:一是图形显示的速度;二是准确的延时。

我在前面说过,实际测试中发现,延时时间没有按照想象的运作。我目前没有花时间去了解UEFI的时间中断是怎么实现的,为什么会产生这样的结果?这是一个小课题,值得研究一下。

另外一个是图形显示的速度。我还是觉得与显存的换页有关系,程序应该符合奥姆剃刀原则:如非必要,勿增实体。在没必要的情况下,不应该去换页。但我不确定Blt()内部是怎么实现的,这是另一个小课题。

10 皮一下,很开心

女儿三岁的时候,我在编程,她在旁边玩玩具。为了逗她,我用之前编好的几个函数,做了一个小动画。把代码移植过来,还能运行得不错:

图12 Just For Fun

百度云链接:https://pan.baidu.com/s/1gccSosw8_UAGTI5gZPnLCA
提取码:dx23
文件在 22 ImageEffect 下

2,155 total views, 1 views today

发表评论

电子邮件地址不会被公开。