UEFI开发学习9 – 高效画图

之前在用UEFI 进行GUI绘图的时候,发现画稍微复杂点的图(如圆形)就会很慢,肉眼都可以看得出这个图像是怎么被画出来的,这样的话就很难做一些复杂的GUI窗口图像绘制。后来研究了一下,发现是写法的问题导致的,看下图,同样画两个圆,所用时间相差非常大。

这是什么原因呢?首先来看看第一个圆的代码:

VOID FillCircle( 
    IN EFI_GRAPHICS_OUTPUT_PROTOCOL  *GraphicsOutput, 
    INTN x0, 
    INTN y0, 
    INTN r, 
    EFI_GRAPHICS_OUTPUT_BLT_PIXEL c,
    UINT8 Transparence
    )
{
   INTN  x, y, xd;
   INTN  lastx[4] = { 0 };

   if ( x0 < 0 ) return;
   if ( y0 < 0 ) return;
   if ( r <= 0 ) return;

   xd = 3 - (r << 1);
   x = 0;
   y = r;

   while ( x <= y )
   {
     if( y > 0 )
     {
        if (lastx[0] != x0 - x) {
            DrawLine(GraphicsOutput, x0 - x, y0 - y,x0 - x, y0 + y, 1, c, Transparence );
            lastx[0] = x0 - x;
        }

        if (lastx[1] != x0 + x) {
            if (x0 + x != x0) {
                DrawLine(GraphicsOutput, x0 + x, y0 - y,x0 + x, y0 + y, 1, c, Transparence );
                lastx[1] = x0 + x;
            }
        }

     }
     if( x > 0 )
     {
        if (lastx[2] != x0 - y && x0 - y != x0 - x) {
            DrawLine(GraphicsOutput, x0 - y, y0 - x,x0 - y, y0 + x, 1, c, Transparence);
            lastx[2] = x0 - y;
        }

        if (lastx[3] != x0 + y && x0 + y != x0 + x) {
            DrawLine(GraphicsOutput, x0 + y, y0 - x,x0 + y, y0 + x, 1, c, Transparence);
            lastx[3] = x0 + y;
        }
     }
     if ( xd < 0 )
     {
        xd += (x << 2) + 6;
     }
     else
     {
        xd += ((x - y) << 2) + 10;
        y--;
     }
     x++;
   }
}

EFI_STATUS DrawLine(
        IN EFI_GRAPHICS_OUTPUT_PROTOCOL  *GraphicsOutput,
        IN UINTN x0, UINTN y0, UINTN x1, UINTN y1, 
        IN UINTN BorderWidth,
        IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL BorderColor,
        IN UINT8 Transparence
){

    INT32 dx  = abs((int)(x1 - x0));
    INT32 sx  = x0 < x1 ? 1 : -1;
    INT32 dy  = abs((int)(y1-y0)), sy = y0 < y1 ? 1 : -1;
    INT32 err = ( dx > dy ? dx : -dy) / 2, e2;
    
    for(;;)
    {	
        DrawPoint(GraphicsOutput, x0, y0, BorderWidth, BorderColor, Transparence);
    
        if (x0==x1 && y0==y1) break;
    
        e2 = err;
    
        if (e2 > -dx) { err -= dy; x0 += sx; }
        if (e2 <  dy) { err += dx; y0 += sy; }
    }
    return EFI_SUCCESS;
}

EFI_STATUS DrawPoint(
    IN EFI_GRAPHICS_OUTPUT_PROTOCOL  *GraphicsOutput,
    IN UINTN x,
    IN UINTN y,
    IN UINTN Width,
    IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL PixelColor,
    IN UINT8 Alpha
){
    EFI_STATUS Status;
    EFI_GRAPHICS_OUTPUT_BLT_PIXEL Color;

    if (Alpha){

    	Status = GraphicsOutput->Blt(
    				GraphicsOutput,
    				&Color,
    				EfiBltVideoToBltBuffer,
    				x, y,
    				0, 0,
    				1, 1,
    				1
    				);	

        PixelColor.Red   = (PixelColor.Red   * (100 - Alpha) + Color.Red   * Alpha) / 100 ;
        PixelColor.Green = (PixelColor.Green * (100 - Alpha) + Color.Green * Alpha) / 100 ;
        PixelColor.Blue  = (PixelColor.Blue  * (100 - Alpha) + Color.Blue  * Alpha) / 100 ;

    }
  
    Status = GraphicsOutput->Blt(
                GraphicsOutput,
                &PixelColor,
                EfiBltVideoFill,
                0, 0,
                x, y,
                Width, Width,
                0
                );		
                
    return EFI_SUCCESS;
}

这里画圆的算法称为Bresenham画圆又称中点画圆(具体实现可百度),最终都是通过算法得到的圆的坐标再用EFI_GRAPHICS_OUTPUT_PROTOCOL画一个像素点实现的,也就是说,如果一个圆有1000个点组成,将调用1000次EFI_GRAPHICS_OUTPUT_PROTOCOL这个协议画点,所以这样的效率就非常低了。怎么提高呢?EFI_GRAPHICS_OUTPUT_PROTOCOL.BLT()本身就支持用矩形的方式填充绘制图形,做法是将它的第三个参数设置为EfiBltBufferToVideo,然后传一个图形的buffer到第二个参数即可,详情参考->UEFI开发学习3 -高级GUI编程。所以的画图的时候,可以预先算好整个图形的坐标,然后将其转换到一个buffer,最后再一次性绘制出来就是了。下面看一下改良后的画圆算法:

VOID BufferDrawFillCircle( 
    IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL  *ColorBuf,
    INTN x0, 
    INTN y0, 
    INTN r, 
    EFI_GRAPHICS_OUTPUT_BLT_PIXEL c,
    UINT8 Transparence
    )
{
    INTN  x,y,xd;
    INTN  lastx[4] = { 0 };
    POSITION_LEFT_TOP Ref;

    if ( x0 < 0 ) return;
    if ( y0 < 0 ) return;
    if ( r <= 0 ) return;

    xd = 3 - (r << 1);
    x = 0;
    y = r;
    Ref.x = x0 - r;
    Ref.y = y0 - r;

   while ( x <= y )
   {
     if( y > 0 )
     {
        if (lastx[0] != x0 - x) {
            BufferDrawLine(ColorBuf, Ref, 2 * r, 2 * r, x0 - x, y0 - y, x0 - x, y0 + y, c, Transparence);
            lastx[0] = x0 - x;
        }

        if (lastx[1] != x0 + x) {
            if (x0 + x != x0) {
                BufferDrawLine(ColorBuf, Ref, 2 * r, 2 * r, x0 + x, y0 - y,x0 + x, y0 + y, c, Transparence);
                lastx[1] = x0 + x;
            }
        }

     }
     if( x > 0 )
     {
        if (lastx[2] != x0 - y && x0 - y != x0 - x) {
            BufferDrawLine(ColorBuf, Ref, 2 * r, 2 * r, x0 - y, y0 - x,x0 - y, y0 + x, c, Transparence);
            lastx[2] = x0 - y;
        }

        if (lastx[3] != x0 + y && x0 + y != x0 + x) {
            BufferDrawLine(ColorBuf, Ref, 2 * r, 2 * r, x0 + y, y0 - x,x0 + y, y0 + x, c, Transparence);
            lastx[3] = x0 + y;
        }
     }
     if ( xd < 0 )
     {
        xd += (x << 2) + 6;
     }
     else
     {
        xd += ((x - y) << 2) + 10;
        y--;
     }
     x++;
   }
}

EFI_STATUS BufferDrawLine(
    IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL  *ColorBuf,
    IN POSITION_LEFT_TOP Ref,
    IN UINTN Width,
    IN UINTN Height,
    IN UINTN x0,
    IN UINTN y0,
    IN UINTN x1,
    IN UINTN y1,
    IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL PixelColor,
    IN UINT8 Alpha
){
    INT32 dx  = abs((int)(x1 - x0));
    INT32 sx  = x0 < x1 ? 1 : -1;
    INT32 dy  = abs((int)(y1-y0)), sy = y0 < y1 ? 1 : -1;
    INT32 err = ( dx > dy ? dx : -dy) / 2, e2;
    
    for(;;)
    {	
        BufferDrawPoint(ColorBuf, Ref, Width, Height, x0, y0, PixelColor, Alpha);
    
        if (x0==x1 && y0==y1) break;
    
        e2 = err;
    
        if (e2 > -dx) { err -= dy; x0 += sx; }
        if (e2 <  dy) { err += dx; y0 += sy; }
    }
    return EFI_SUCCESS;
}

EFI_STATUS BufferDrawPoint(
    IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL  *ColorBuf,
    IN POSITION_LEFT_TOP Ref,
    IN UINTN Width,
    IN UINTN Height,
    IN UINTN x,
    IN UINTN y,
    IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL PixelColor,
    IN UINT8 Alpha
){
//	EFI_STATUS Status;
    EFI_GRAPHICS_OUTPUT_BLT_PIXEL Color;
    UINTN Position;

    if (x > Ref.x + Width  || x < Ref.x ||
        y > Ref.y + Height || y < Ref.y)
    {
        return EFI_UNSUPPORTED;
    }

    Position = Width * (y - Ref.y) + (x - Ref.x);

    if (Alpha)
    {
    	gBS->CopyMem(&Color, &ColorBuf[Position], sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL));

        PixelColor.Red   = (PixelColor.Red   * (100 - Alpha) + Color.Red   * Alpha) / 100 ;
        PixelColor.Green = (PixelColor.Green * (100 - Alpha) + Color.Green * Alpha) / 100 ;
        PixelColor.Blue  = (PixelColor.Blue  * (100 - Alpha) + Color.Blue  * Alpha) / 100 ;
    }

    gBS->CopyMem(&(ColorBuf[Position]), &PixelColor, sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL));

    return EFI_SUCCESS;
}

这个程序可看到,最终都会把每个点都存到一个buffer上,然后一次性绘制出来。在这里可能会有个疑问,程序虽然减少了Blt()的调用,但是却多了转换的算法,看起来程序跑的次数比原来的还多了,为什么时间减少了?原因是调用Blt()绘制一次所需的时间远大于跑一次其它算法的时间,主要是Blt()绘制图像的时候是需要跟硬件(显示设备)进行通信的,会消耗较多的时间,就好比BIOS的DEBUG版本跟RELEASE版本,DEBUG版本需要打印信息出来,才拖慢了其它程序的运行时间。所以写程序最好能尽量避免大量操作硬件次数,这样才能提高程序运行的效率。

 

版权声明:
作者:bin
链接:https://ay123.net/mystudy/852/
来源:爱影博客
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
海报
UEFI开发学习9 – 高效画图
之前在用UEFI 进行GUI绘图的时候,发现画稍微复杂点的图(如圆形)就会很慢,肉眼都可以看得出这个图像是怎么被画出来的,这样的话就很难做一些复杂的GUI窗口……
<<上一篇
下一篇>>