Вот маленький простой 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 МБ):
![animation](https://i.stack.imgur.com/2VwOm.gif)
Это код, который я использовал для этого (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):
![img0](https://i.stack.imgur.com/rSZ50.jpg)
![img1](https://i.stack.imgur.com/NDF3S.jpg)
Вот предварительный просмотр результата смешивания:
![blend](https://i.stack.imgur.com/ClcYf.jpg)
Для получения дополнительной информации о ScanLine см .:
Если вам нужна еще более высокая скорость, вам следует выбрать GPU Blending ( 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
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------