更快地使用比例和Alpha通道绘制位图

时间:2019-01-14 16:03:09

标签: graphics c++builder tbitmap

下面的代码复制一个大的位图,将其与正确的背景混合,然后绘制带有裁剪区域的半透明图像,以节省绘制时间...图像位于数组中并预先缩放...

这是基于我对C ++和Builder图形的有限了解而进行的几级优化...

编辑::更新了代码... blend();

void blend(Graphics::TBitmap *dst,int x,int y,Graphics::TBitmap *src,BYTE 
alpha)
{
const int n=3;          // pixel align [Bytes]
int dx0,dy0,dx1,dy1,    // dst BBOX
    sx0,sy0,sx1,sy1,    // src BBOX
    dx,dy,sx,sy,i;
BYTE *dp,*sp;
WORD a,_a,sc,dc,da[256],sa[256];

// compute BBOX (handle clipping)
dx=src->Width; dy=src->Height;
dx0=x; sx0=0; dx1=x+dx; sx1=dx;
dy0=y; sy0=0; dy1=y+dy; sy1=dy;


// blend
a=alpha; _a=255-a;
for (i=0;i<256;i++){ da[i]=_a*i; sa[i]=a*i; }   // precompute BYTE*a and 
BYTE*_a LUTs

for (dy=dy0,sy=sy0;dy<dy1;dy++,sy++)        // ScanLines
    {
    dp=(BYTE*)dst->ScanLine[dy]+(n*dx0);
    sp=(BYTE*)src->ScanLine[sy]+(n*sx0);
    for (dx=dx0,sx=sx0;dx<dx1;dx++,sx++)    // single ScanLine
     for (i=0;i<n;i++,dp++,sp++)            // RGB
      *dp=WORD((sa[*sp]+da[*dp])>>8);       // blend function
    }
}

//--------------------------------------------------------------------------

    det1maps.push_back( new Graphics::TBitmap() );
    for (int i = 1; i < 176; i++)
    {
        det1maps.push_back( new Graphics::TBitmap() );
        det1maps[i]->SetSize(t,t);
        det1maps[i]->Canvas->StretchDraw(Rect(0, 0, t, t), Det1_bmp.get()); // scale
        t = t + 24;
    }

// ------------------编辑3当前版本1/18

det1maps[ss]->Transparent = true;
Form1->imgTemp->Picture->Assign(layer0_bmap.get()); //why background first?
HRGN MyRgn;
MyRgn = ::CreateRectRgn(0,0,Sw,Sh);
::SelectClipRgn(Form1->imgTemp->Canvas->Handle,MyRgn); //clip

Form1->imgTemp->Canvas->Draw(X3,Y3,det1maps[ss]); // draw det

blend(layer0_bmap.get(),0,0,Form1->imgTemp->Picture->Bitmap,int(obj[index]));

1 个答案:

答案 0 :(得分:2)

这里有一个简单的 C ++ / VCL ScanLine 我刚刚整理的Alpha Blend 示例:

//---------------------------------------------------------------------------
void blend(Graphics::TBitmap *dst,int x,int y,Graphics::TBitmap *src,BYTE alpha)
    {
    const int n=3;          // pixel align [Bytes]
    int dx0,dy0,dx1,dy1,    // dst BBOX
        sx0,sy0,sx1,sy1,    // src BBOX
        dx,dy,sx,sy,i;
    BYTE *dp,*sp;
    WORD a,_a,sc,dc,da[256],sa[256];
    // compute BBOX (handle clipping)
    dx=src->Width; dy=src->Height;
    dx0=x; sx0=0; dx1=x+dx; sx1=dx;
    dy0=y; sy0=0; dy1=y+dy; sy1=dy;
    if (dx0<0){ sx0-=dx0; dx0=0; }
    if (dy0<0){ sy0-=dy0; dy0=0; }
    dx=dst->Width; dy=dst->Height;
    if (dx1>dx){ sx1+=dx-dx1; dx1=dx; }
    if (dy1>dy){ sy1+=dy-dy1; dy1=dy; }
    // make sure config is compatible with ScanLine[]
    dst->HandleType=bmDIB; dst->PixelFormat=pf24bit;
    src->HandleType=bmDIB; src->PixelFormat=pf24bit;
    // blend
    a=alpha; _a=255-a;
    for (i=0;i<256;i++){ da[i]=_a*i; sa[i]=a*i; }   // precompite BYTE*a and BYTE*_a LUTs
    for (dy=dy0,sy=sy0;dy<dy1;dy++,sy++)        // ScanLines
        {
        dp=(BYTE*)dst->ScanLine[dy]+(n*dx0);
        sp=(BYTE*)src->ScanLine[sy]+(n*sx0);
        for (dx=dx0,sx=sx0;dx<dx1;dx++,sx++)    // single ScanLine
         for (i=0;i<n;i++,dp++,sp++)            // RGB
          *dp=WORD((sa[*sp]+da[*dp])>>8);       // blend function
        }
    }
//---------------------------------------------------------------------------

我只按每个像素/通道处理图像,并针对每个通道(R,G,B)计算:

dst_pixel =  src_pixel*alpha + dst_pixel*(255-alpha)

其中channel和alpha是8位无符号整数...为了提高速度,我使用24位像素格式(通常我改用32bit)。

为了避免在混合中使用*,/,我预先计算了2个 LUT number*alphanumber*(255-alpha)的所有可能组合。除法是通过移位>>8完成的。

为提高速度,您可以将ScanLine[]图像中的所有dst记住一次到阵列中,然后将其用作目标图像,因为它将多次使用...

当我将2张1024x768图像融合在一起进行测试时,<=9ms用在了我的设置上。最慢的操作是ScanLine[]访问,图像在混合之前已格式化为像素格式...

这里有GIF预览(缩小到1/4并由我的捕获器抖动,因此它适合imgur 2MByte限制):

animation

这是我用于此的代码(单计时器VCL App):

//$$---- Form CPP ----
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "win_main.h"
#include <math.h>
#include <jpeg.hpp>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMain *Main;
Graphics::TBitmap *bmp,*bmp0,*bmp1; // back buffer, image0, image1, ...
//---------------------------------------------------------------------------
void blend(Graphics::TBitmap *dst,int x,int y,Graphics::TBitmap *src,BYTE alpha)
    {
    const int n=3;          // pixel align [Bytes]
    int dx0,dy0,dx1,dy1,    // dst BBOX
        sx0,sy0,sx1,sy1,    // src BBOX
        dx,dy,sx,sy,i;
    BYTE *dp,*sp;
    WORD a,_a,sc,dc,da[256],sa[256];
    // compute BBOX (handle clipping)
    dx=src->Width; dy=src->Height;
    dx0=x; sx0=0; dx1=x+dx; sx1=dx;
    dy0=y; sy0=0; dy1=y+dy; sy1=dy;
    if (dx0<0){ sx0-=dx0; dx0=0; }
    if (dy0<0){ sy0-=dy0; dy0=0; }
    dx=dst->Width; dy=dst->Height;
    if (dx1>dx){ sx1+=dx-dx1; dx1=dx; }
    if (dy1>dy){ sy1+=dy-dy1; dy1=dy; }
    // make sure config is compatible with ScanLine[]
    dst->HandleType=bmDIB; dst->PixelFormat=pf24bit;
    src->HandleType=bmDIB; src->PixelFormat=pf24bit;
    // blend
    a=alpha; _a=255-a;
    for (i=0;i<256;i++){ da[i]=_a*i; sa[i]=a*i; }   // precompite BYTE*a and BYTE*_a LUTs
    for (dy=dy0,sy=sy0;dy<dy1;dy++,sy++)        // ScanLines
        {
        dp=(BYTE*)dst->ScanLine[dy]+(n*dx0);
        sp=(BYTE*)src->ScanLine[sy]+(n*sx0);
        for (dx=dx0,sx=sx0;dx<dx1;dx++,sx++)    // single ScanLine
         for (i=0;i<n;i++,dp++,sp++)            // RGB
          *dp=WORD((sa[*sp]+da[*dp])>>8);       // blend function
        }
    }
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
void TMain::draw()
    {
    bmp->Canvas->Draw(0,0,bmp0);            // render background bmp0
    static float a=0.0; a+=0.025*M_PI;
    blend(bmp,0,0,bmp1,fabs(255.0*sin(a))); // alfa blend in bmp1
    Main->Canvas->Draw(0,0,bmp);            // show result on screen
    }
//---------------------------------------------------------------------------
__fastcall TMain::TMain(TComponent* Owner) : TForm(Owner)
    {
    // create bitmaps
    bmp=new Graphics::TBitmap;
    bmp0=new Graphics::TBitmap;
    bmp1=new Graphics::TBitmap;
    // laod images
    TJPEGImage *jpg=new TJPEGImage;
    jpg->LoadFromFile("img0.jpg"); bmp0->Assign(jpg);
    jpg->LoadFromFile("img1.jpg"); bmp1->Assign(jpg);
    delete jpg;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormDestroy(TObject *Sender)
    {
    // delete bitmaps
    delete bmp0;
    delete bmp1;
    delete bmp;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormResize(TObject *Sender)
    {
    bmp->Width =ClientWidth;
    bmp->Height=ClientHeight;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormPaint(TObject *Sender)
    {
    draw();
    }
//---------------------------------------------------------------------------
void __fastcall TMain::tim_redrawTimer(TObject *Sender)
    {
    draw();
    }
//---------------------------------------------------------------------------

下面是图片(我在Google图片上找到的第一张不错的1024x768图片):

img0

img1

此处预览混合结果:

blend

有关 ScanLine 的更多信息,请参见:

如果您需要更高的速度,则应该使用 GPU 混合( OpenGL DirectX )。

[Edit2]数组+矩形示例

在您编辑完问题后,现在显而易见了:

  1. 您的位图数组根本不是数组

    它有点像vector<Graphics::TBitmap*>或类似的列表模板...因此您无法像我一样访问bmp的线性数组。为了使您的生活更轻松,我使用了具有类似属性的我的模板,以便您了解如何处理这些模板(很抱歉,我无法共享模板代码,但是您只需要将List<T>更改为Vector<T>即可使用...

    这就是数组指针对您不起作用的原因,因为您没有一个。它可能是您的模板向某些成员公开它。我的操作就像map.dat一样,因此如果不线性存储,您的文件可能会有些相似或根本没有。

  2. 您仅混合2张图像,而不是整个阵列

    因此您可以使用第一个示例并在图像静态的情况下添加ScanLine预加载...对后缓冲图像执行相同的操作,因为仅在调整大小后才会更改。

当我将所有结果放在一起时:

//$$---- Form CPP ----
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "win_main.h"
#include <math.h>
#include <jpeg.hpp>
#include "list.h"           // mine list<T> template you got probably vector<> or something similar instead
#include "performance.h"    // this is mine tbeg/tend/tstr time measurement
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMain *Main;
//---------------------------------------------------------------------------
// [back buffer]
Graphics::TBitmap *bmp;             // bitmap
BYTE **bmp_pyx=NULL;                // preloaded ScanLines [y][x]
void bmp_init()                     // create preloaded ScanLines
    {
    bmp_pyx=new BYTE*[bmp->Height];
    for (int y=0;y<bmp->Height;y++)
     bmp_pyx[y]=(BYTE*)bmp->ScanLine[y];
    }
void bmp_exit()                     // release preloaded ScanLines
    {
    delete[] bmp_pyx;
    }
//---------------------------------------------------------------------------
// [array of images]
const AnsiString filename[]=        // filenames
    {
    "img0.jpg",
    "img1.jpg",
    "img2.jpg",
    "img3.jpg",
    "img4.jpg",
    "img5.jpg",
    "img6.jpg",
    "img7.jpg",
    "img8.jpg",
    "img9.jpg",
    ""
    };
List<Graphics::TBitmap*> map;       // your "array" of bitmaps
int maps=0;                         // number of images
BYTE ***map_pyx=NULL;               // preloaded ScanLines [ix][y][x]
//---------------------------------------------------------------------------
void map_init()                     // alocate and prepare data
    {
    int i,y;
    Graphics::TBitmap *bmp;
    TJPEGImage *jpg=new TJPEGImage;
    // create "array" of bmp (you already got this)
    for (maps=0;filename[maps]!="";maps++)
        {
        map.add(new Graphics::TBitmap); // this is like your push_back(new Graphics::TBitmap)
        jpg->LoadFromFile(filename[maps]);  // filename[] -> jpg -> bmp -> map[]
        map[maps]->Assign(jpg);             // here you can also rescale or whatever you want to do...
        map[maps]->HandleType=bmDIB;
        map[maps]->PixelFormat=pf24bit;
        }
    // create preloaded ScanLines (you need to add this into your app init)
    map_pyx=new BYTE**[maps];                   // **map_pyx[]
    for (i=0;i<maps;i++)
        {
        map_pyx[i]=new BYTE*[map[i]->Height];   // *map_pyx[][]
        for (y=0;y<map[i]->Height;y++)          // map_pyx[][]]
         map_pyx[i][y]=(BYTE*)map[i]->ScanLine[y];
        }
    delete jpg;
    }
//---------------------------------------------------------------------------
void map_exit()                     // release data (you need to add this in app exit)
    {
    int i;
    for (i=0;i<maps;i++)
        {
        delete   map[i];
        delete[] map_pyx[i];
        }
    delete[] map_pyx;
    }
//---------------------------------------------------------------------------
void blend_rec(BYTE **dp,int x0,int y0,int x1,int y1,BYTE **sp,BYTE alpha)
    {
    const int n=3;          // pixel align [Bytes]
    int x,y,i;
    BYTE *d,*s;
    WORD da[256],sa[256];
    // pixelformat align
    x0*=n; x1*=n;
    // prepare alpha*BYTE and (255-alpha)*BYTE LUTs
    y=    alpha; for (x=0;x<256;x++) sa[x]=x*y;
    y=255-alpha; for (x=0;x<256;x++) da[x]=x*y;
    // blend
    for (y=y0;y<y1;y++)
        {
        d=dp[y]+x0;
        s=sp[y]+x0;
        for (x=x0;x<x1;x++,d++,s++)
         *d=WORD((sa[*s]+da[*d])>>8);       // blend function
        }
    // release data
    }
//---------------------------------------------------------------------------
void TMain::draw()
    {
    bmp->Canvas->Draw(0,0,map[0]);              // render background bmp[0]
    static float a=0.0; a+=0.025*M_PI;          // animation ...
    BYTE alpha=128+float(127.0*sin(a));
    tbeg();
    blend_rec(bmp_pyx,200,500,400,600,map_pyx[1],alpha);    // add the blended rectangle (except background which is bmp[0]
    tend(); Caption=tstr();
    Canvas->Draw(0,0,bmp);                      // show on screen
//  bmp->SaveToFile("out.bmp");
    }
//---------------------------------------------------------------------------
__fastcall TMain::TMain(TComponent* Owner) : TForm(Owner)
    {
    // create bitmaps
    bmp=new Graphics::TBitmap;
    bmp_init();
    map_init();
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormDestroy(TObject *Sender)
    {
    // delete bitmaps
    delete bmp;
    bmp_exit();
    map_exit();
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormResize(TObject *Sender)
    {
    bmp->Width =ClientWidth;
    bmp->Height=ClientHeight;
    bmp->HandleType=bmDIB;
    bmp->PixelFormat=pf24bit;
    bmp_exit();
    bmp_init();
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormPaint(TObject *Sender)
    {
    draw();
    }
//---------------------------------------------------------------------------
void __fastcall TMain::tim_redrawTimer(TObject *Sender)
    {
    draw();
    }
//---------------------------------------------------------------------------

在我为所选矩形设置的0.5ms内完成混合。如您所见,它的速度比原始9ms快。因为如果您使用剪切区域,您仍将混合整个图像,只是不复制结果。这种方法只会融合并复制所需内容。

当心我删除了范围检查,因此请确保矩形在图像内...

如果您想用与我相同的方式来测量时间,那么我将使用以下代码:

Performance.h:

//---------------------------------------------------------------------------
//--- Performance counter time measurement: 2.01 ----------------------------
//---------------------------------------------------------------------------
#ifndef _performance_h
#define _performance_h
//---------------------------------------------------------------------------
const int   performance_max=64;                 // push urovni
double      performance_Tms=-1.0,               // perioda citaca [ms]
            performance_tms=0.0,                // zmerany cas po tend [ms]
            performance_t0[performance_max];    // zmerane start casy [ms]
int         performance_ix=-1;                  // index aktualneho casu
//---------------------------------------------------------------------------
void tbeg(double *t0=NULL)  // mesure start time
    {
    double t;
    LARGE_INTEGER i;
    if (performance_Tms<=0.0)
        {
        for (int j=0;j<performance_max;j++) performance_t0[j]=0.0;
        QueryPerformanceFrequency(&i); performance_Tms=1000.0/double(i.QuadPart);
        }
    QueryPerformanceCounter(&i); t=double(i.QuadPart); t*=performance_Tms;
    if (t0) { t0[0]=t; return; }
    performance_ix++;
    if ((performance_ix>=0)&&(performance_ix<performance_max)) performance_t0[performance_ix]=t;
    }
//---------------------------------------------------------------------------
void tpause(double *t0=NULL)    // stop counting time between tbeg()..tend() calls
    {
    double t;
    LARGE_INTEGER i;
    QueryPerformanceCounter(&i); t=double(i.QuadPart); t*=performance_Tms;
    if (t0) { t0[0]=t-t0[0]; return; }
    if ((performance_ix>=0)&&(performance_ix<performance_max)) performance_t0[performance_ix]=t-performance_t0[performance_ix];
    }
//---------------------------------------------------------------------------
void tresume(double *t0=NULL)   // resume counting time between tbeg()..tend() calls
    {
    double t;
    LARGE_INTEGER i;
    QueryPerformanceCounter(&i); t=double(i.QuadPart); t*=performance_Tms;
    if (t0) { t0[0]=t-t0[0]; return; }
    if ((performance_ix>=0)&&(performance_ix<performance_max)) performance_t0[performance_ix]=t-performance_t0[performance_ix];
    }
//---------------------------------------------------------------------------
double tend(double *t0=NULL)    // return duration [ms] between matching tbeg()..tend() calls
    {
    double t;
    LARGE_INTEGER i;
    QueryPerformanceCounter(&i); t=double(i.QuadPart); t*=performance_Tms;
    if (t0) { t-=t0[0]; performance_tms=t; return t; }
    if ((performance_ix>=0)&&(performance_ix<performance_max)) t-=performance_t0[performance_ix]; else t=0.0;
    performance_ix--;
    performance_tms=t;
    return t;
    }
//---------------------------------------------------------------------------
double tper(double *t0=NULL)    // return duration [ms] between tper() calls
    {
    double t,tt;
    LARGE_INTEGER i;
    if (performance_Tms<=0.0)
        {
        for (int j=0;j<performance_max;j++) performance_t0[j]=0.0;
        QueryPerformanceFrequency(&i); performance_Tms=1000.0/double(i.QuadPart);
        }
    QueryPerformanceCounter(&i); t=double(i.QuadPart); t*=performance_Tms;
    if (t0) { tt=t-t0[0]; t0[0]=t; performance_tms=tt; return tt; }
    performance_ix++;
    if ((performance_ix>=0)&&(performance_ix<performance_max))
        {
        tt=t-performance_t0[performance_ix];
        performance_t0[performance_ix]=t;
        }
    else { t=0.0; tt=0.0; };
    performance_ix--;
    performance_tms=tt;
    return tt;
    }
//---------------------------------------------------------------------------
AnsiString tstr()
    {
    AnsiString s;
    s=s.sprintf("%8.3lf",performance_tms); while (s.Length()<8) s=" "+s; s="["+s+" ms]";
    return s;
    }
//---------------------------------------------------------------------------
AnsiString tstr(int N)
    {
    AnsiString s;
    s=s.sprintf("%8.3lf",performance_tms/double(N)); while (s.Length()<8) s=" "+s; s="["+s+" ms]";
    return s;
    }
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------