Delphi - объектно-ориентированный язык программирования, разработанный компанией Borland в 1995 году. Он основан на языке программирования Pascal, но имеет более расширенные возможности и добавлены новые функции.
Delphi является интегрированной средой разработки (IDE), которая позволяет разрабатывать программное обеспечение для различных платформ, включая Windows, macOS, Android и iOS. Delphi достигает многоплатформенности с помощью...
Ну вот, добрались и до фильтров. В неформальных испытаниях этот код оказался вдвое быстрее, чем это делает Adobe Photoshop. Мне кажется есть множество фильтров, которые можно переделать или оптимизировать для быстроты обработки изображений.
Ядро гауссовой функции exp(-(x^2 + y^2)) есть разновидность формулы f(x)*g(y), которая означает, что мы можем выполнить двумерную свертку, делая последовательность одномерных сверток - сначала мы свертываем каждую строчку изображения, затем - каждую колонку. Хороший повод для ускорения (N^2 становится N*2). Любая свертка требует некоторого место для временного хранения результатов - ниже в коде программа BlurRow как раз распределяет и освобождает память для каждой колонки. Вероятно это должно ускорить обработку изображения, правда не ясно насколько.
Поле "size" в записи TKernel ограничено значением 200. Фактически, если вы хотите использовать еще больший радиус, это не вызовет проблем - попробуйте со значениями radius = 3, 5 или другими. Для большого количества данных методы свертки на поверку оказываются эффективнее преобразований Фурье (как показали опыты).
Еще один комментарий все же необходим: гауссово размывание имеет одно магическое свойство, а именно - вы можете сначала размыть каждую строчку (применить фильтр), затем каждую колонку - фактически получается значительно быстрее, чем двумерная свертка.
Во всяком случае вы можете сделать так:
unitGBlur2; interface
uses
Windows, Graphics; type
PRGBTriple = ^TRGBTriple; TRGBTriple = packed
record
b: byte; //легче для использования чем типа rgbtBlue... g: byte; r: byte; end
; PRow = ^TRow; TRow = array
[0..1000000] of
TRGBTriple; PPRows = ^TPRows; TPRows = array
[0..1000000] of
PRow; const
MaxKernelSize = 100; type
TKernelSize = 1..MaxKernelSize; TKernel = record
Size: TKernelSize; Weights: array
[-MaxKernelSize..MaxKernelSize] of
single; end
;
//идея заключается в том, что при использовании TKernel мы игнорируем //Weights (вес), за исключением Weights в диапазоне -Size..Size. procedureGBlur(theBitmap: TBitmap; radius: double); implementation
uses
SysUtils; procedure
MakeGaussianKernel(var
K: TKernel; radius: double; MaxData, DataGranularity: double);
//Делаем K (гауссово зерно) со среднеквадратичным отклонением = radius. //Для текущего приложения мы устанавливаем переменные MaxData = 255, //DataGranularity = 1. Теперь в процедуре установим значение //K.Size так, что при использовании K мы будем игнорировать Weights (вес) //с наименее возможными значениями. (Малый размер нам на пользу, //поскольку время выполнения напрямую зависит от //значения K.Size.) varj: integer; temp, delta: double; KernelSize: TKernelSize; begin
for
j := Low(K.Weights) to
High(K.Weights) do
begin
temp := j / radius; K.Weights[j] := exp(-temp * temp / 2); end
;
//делаем так, чтобы sum(Weights) = 1: temp := 0; forj := Low(K.Weights) to
High(K.Weights) do
temp := temp + K.Weights[j]; for
j := Low(K.Weights) to
High(K.Weights) do
K.Weights[j] := K.Weights[j] / temp;
//теперь отбрасываем (или делаем отметку "игнорировать" //для переменной Size) данные, имеющие относительно небольшое значение - //это важно, в противном случае смазавание происходим с малым радиусом и //той области, которая "захватывается" большим радиусом... KernelSize := MaxKernelSize; delta := DataGranularity / (2 * MaxData); temp := 0; while(temp < delta) and
(KernelSize > 1) do
begin
temp := temp + 2 * K.Weights[KernelSize]; dec(KernelSize); end
; K.Size := KernelSize;
//теперь для корректности возвращаемого результата проводим ту же //операцию с K.Size, так, чтобы сумма всех данных была равна единице: temp := 0; forj := -K.Size to
K.Size do
temp := temp + K.Weights[j]; for
j := -K.Size to
K.Size do
K.Weights[j] := K.Weights[j] / temp; end
; function
TrimInt(Lower, Upper, theInteger: integer): integer; begin
if
(theInteger <= Upper) and
(theInteger >= Lower) then
result := theInteger else
if
theInteger > Upper then
result := Upper else
result := Lower; end
; function
TrimReal(Lower, Upper: integer; x: double): integer; begin
if
(x < upper) and
(x >= lower) then
result := trunc(x) else
if
x > Upper then
result := Upper else
result := Lower; end
; procedure
BlurRow(var
theRow: array
of
TRGBTriple; K: TKernel; P: PRow); var
j, n, LocalRow: integer; tr, tg, tb: double;
//tempRed и др. w: double; beginfor
j := 0 to
High(theRow) do
begin
tb := 0; tg := 0; tr := 0; for
n := -K.Size to
K.Size do
begin
w := K.Weights[n];
//TrimInt задает отступ от края строки... withtheRow[TrimInt(0, High(theRow), j - n)] do
begin
tb := tb + w * b; tg := tg + w * g; tr := tr + w * r; end
; end
; with
P[j] do
begin
b := TrimReal(0, 255, tb); g := TrimReal(0, 255, tg); r := TrimReal(0, 255, tr); end
; end
; Move(P[0], theRow[0], (High(theRow) + 1) * Sizeof(TRGBTriple)); end
; procedure
GBlur(theBitmap: TBitmap; radius: double); var
Row, Col: integer; theRows: PPRows; K: TKernel; ACol: PRow; P: PRow; begin
if
(theBitmap.HandleType <> bmDIB) or
(theBitmap.PixelFormat <> pf24Bit) then
raise
exception.Create('GBlur может работать только с 24-битными изображениями'); MakeGaussianKernel(K, radius, 255, 1); GetMem(theRows, theBitmap.Height * SizeOf(PRow)); GetMem(ACol, theBitmap.Height * SizeOf(TRGBTriple));
//запись позиции данных изображения: forRow := 0 to
theBitmap.Height - 1 do
theRows[Row] := theBitmap.Scanline[Row];
//размываем каждую строчку: P := AllocMem(theBitmap.Width * SizeOf(TRGBTriple)); forRow := 0 to
theBitmap.Height - 1 do
BlurRow(Slice(theRows[Row]^, theBitmap.Width), K, P);
//теперь размываем каждую колонку ReAllocMem(P, theBitmap.Height * SizeOf(TRGBTriple)); forCol := 0 to
theBitmap.Width - 1 do
begin
//- считываем первую колонку в TRow: for
Row := 0 to
theBitmap.Height - 1 do
ACol[Row] := theRows[Row][Col]; BlurRow(Slice(ACol^, theBitmap.Height), K, P);
//теперь помещаем обработанный столбец на свое место в данные изображения: forRow := 0 to
theBitmap.Height - 1 do
theRows[Row][Col] := ACol[Row]; end
; FreeMem(theRows); FreeMem(ACol); ReAllocMem(P, 0); end
; end
.
Должно работать, если только вы не удалите некоторый код вместе с глупыми коментариями. Для примера:
procedureTForm1.Button1Click(Sender: TObject); var
b: TBitmap; begin
if
not
openDialog1.Execute then
exit; b := TBitmap.Create; b.LoadFromFile(OpenDialog1.Filename); b.PixelFormat := pf24Bit; Canvas.Draw(0, 0, b); GBlur(b, StrToFloat(Edit1.text)); Canvas.Draw(b.Width, 0, b); b.Free; end
;
Имейте в виду, что 24-битные изображения при системной 256-цветной палитре требуют некоторых дополнительных хитростей, так как эти изображения не только выглядят в таком случае немного "странными", но и серьезно нарушают работу фильтра.