Нарисуйте 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 ответ
Вот маленький простой 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)
где каналы и альфа - 8-битные целые числа без знака... Для скорости я использовал 24-битный пиксельный формат (обычно я использую 32-битный вместо).
Избежать *,/
в смешивании я предварительно вычислил 2 LUT со всеми возможными комбинациями number*alpha
а также number*(255-alpha)
, Деление осуществляется по битам >>8
,
Для улучшения скорости вы можете запомнить все ScanLine[]
из dst
изображение в ваш массив один раз, а затем использовать его в качестве целевого изображения будет использоваться много раз...
Когда я проверил это на смешивание 2 1024x768
изображения вместе это заняло <=9ms
на моей настройке. Самая медленная операция ScanLine[]
доступ и изображения, отформатированные в формате пикселей перед смешиванием...
Вот предварительный просмотр в формате GIF (уменьшенный на 1/4 и уменьшенный моим захватчиком, чтобы он соответствовал пределу в 2 МБ):
Вот код, который я использовал для этого (VCL-приложение с одним таймером):
//$$---- 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();
}
//---------------------------------------------------------------------------
А вот изображения (первые красивые картинки 1024x768, которые я нашел на изображениях Google):
Вот предварительный просмотр результата смешивания:
Для получения дополнительной информации о ScanLine см.:
Если вам нужна еще большая скорость, вам следует использовать смешивание графических процессоров (OpenGL или DirectX).
[Edit2] массив + пример прямоугольника
После того, как вы отредактировали ваш вопрос, теперь это очевидно:
Ваш массив растровых изображений не является массивом вообще
это скорее какой-то шаблон списка вроде
vector<Graphics::TBitmap*>
или подобное... Так что у вас нет доступа к линейному массиву BMP, как у меня. Чтобы сделать вашу жизнь проще, я использовал мой шаблон с похожими свойствами, чтобы вы могли видеть, как с ними работать (извините, я не могу поделиться кодом шаблона, но вам просто нужно изменитьList<T>
вVector<T>
или что вы используете...Это причина, почему указатель массива не работает для вас, так как у вас его нет. Возможно, ваш шаблон предоставляет его некоторым членам. Мой делает это как
map.dat
так что у вас может быть что-то подобное или нет вообще, если не хранится линейно.Вы смешиваете только 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
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------