Назад |
скачай демо-проект invisible-coat.rar (56,2 кб)
Уровень ПАУК (Пресложная Аттестация Умений Колдуна) предполагает доскональное знание изучаемого предмета и готовность к реальному применению знаний.
Как уже говорилось функции GetPixel, SetPixel и использующее их свойство канвы Pixels[] работают бессовестно медленно. А в функции BlendImage свойство Pixels используется 4 раза. Если ты захочешь сделать так, чтобы перец на голове проявлялся постепенно и будешь в цикле рисовать его с постепенно увеличивающейся непрозрачностью, то можешь и не дождаться окончания процесса. Срочно необходима оптимизация!
Выкинем API функции GetPixel и SetPixel и напишем свои быстрые. Для этого будем получать цвет пикселя непосредственно из области памяти, в которую записано изображение. К счастью разработчики Delphi предусмотрели такой вариант. Объект TBitmap имеет свойство Scanline[] – массив указателей на начала строк пикселей в изображении. Зная объем памяти занимаемый одним пикселем можно легко вычислить указатель на пиксель, прибавив к указателю на строку (это координата Y) положение пикселя в строке (координата X) умноженное на объем памяти, занимаемый одним пикселем (для 24-битной картинки 24/8 = 3 байта). Для доступа к цветовым компонентам в системе RGB используем структуру TRGBTriple (описана в Windows.pas).
// Быстрое получение цвета пикселя. Только для 24-битных картинок!
// Нет проверки на ошибки !
function GetPixel(Bitmap: TBitmap; X,Y: Integer): TColor;
var pixel: PRGBTriple;
begin
if Bitmap.PixelFormat = pf24bit then begin
DWORD(pixel) := DWORD(Bitmap.ScanLine[Y]) + (X * SizeOf(TRGBTriple));
Result := RGB(pixel.rgbtRed, pixel.rgbtGreen, pixel.rgbtBlue);
end else Result := 0;
end;
// Быстрая установка цвета пикселя. Только для 24-битных картинок!
// Нет проверки на ошибки !
procedure SetPixel(Bitmap: TBitmap; X,Y: Integer; Color: TColor);
var pixel: PRGBTriple;
begin
if Bitmap.PixelFormat = pf24bit then begin
DWORD(pixel) := DWORD(Bitmap.ScanLine[Y]) + (X * SizeOf(TRGBTriple));
pixel.rgbtRed := GetRValue(Color);
pixel.rgbtGreen := GetGValue(Color);
pixel.rgbtBlue := GetBValue(Color);
end;
end;
Кроме того, можно оптимизировать и функцию смешивания цветов BlendColor. Для этого надо преобразовать формулу так, чтобы убрать все математические операции с плавающей запятой. Учтем также, что операция X shr 8 эквивалентна X / 256, но выполняется быстрее:
function FastBlendColor(Src, Dest: TColor; CommonAlpha: Byte): TColor;
begin
Result := RGB(
GetRValue(Dest)+(GetRValue(Src)-GetRValue(Dest))*CommonAlpha shr 8,
GetGValue(Dest)+(GetGValue(Src)-GetGValue(Dest))*CommonAlpha shr 8,
GetBValue(Dest)+(GetBValue(Src)-GetBValue(Dest))*CommonAlpha shr 8);
end;
В результате мы получаем увеличение производительности более, чем в 5 раз! И, как увидим дальше, это далеко не предел.
Теперь о плаще-невидимке. Мне не удалось достать хорошего работающего плаща для проведения наших опытов, но кое-что у меня все-таки есть. Как-то, бродя по задворкам Хогварца, я обнаружил на куче мусора старый потрепанный плащ-невидимку. Он не работал. Вернее работал наполовину и добиться полной невидимости в нем не получалось. Наверное поэтому его и выбросили. При осмотре плаща я обнаружил причину поломки. Как раз в области оптического процессора прямо сквозь цепи фотонных нанодатчиков была сделана довольно корявая вышивка: «Harry Potter». Очевидно старый владелец этой вещи, абсолютно не разбирался в ее работе. Ну что же, приспособим это барахло для наших нужд. Заставим Дамблдора одеть этот плащ и постепенно появляться перед нами.
Плащ-невидимка представляет собой 8-битную серую картинку. Размер каждого пикселя как раз представляет собой значение от 0 до 255. Значения цвета пикселей плаща будут определять прозрачность каждого пикселя на изображении волшебника. То есть плащ будет альфа-каналом картинки волшебника. В этом примере сразу будем использовать быстрый доступ к пикселям изображения с использованием свойства TBitmap.Scanline[]. Установку значений цветовых компонентов будем делать по полной формуле прозрачности, но оптимизируем ее, чтобы не было операций с плавающей точкой:
|
Код функции откомментирован построчно, поэтому объяснения будут лишними. Смотри код и читай комментарии.
procedure FastBlendImage(Src, Dst, Map: TBitmap; SCA: Byte; X,Y: Integer);
var
xx, yy: Integer;// Счетчики циклов
SrcBase, MapBase, DstBase: Pointer; // Указатели на строки пикселей
SrcInc, MapInc, DstInc: Integer; // Длины строк пикселей
SrcPixel, DstPixel: PRGBTriple; // Указатели на 24-битные пиксели
Alpha: PByte; // Указатель на 8-битный пиксель
Transparency: Byte; // Per-pixel прозрачность. 255 = непрозрачность
begin
// Получим указатели на начало первой строки во всех картинках
SrcBase := Src.ScanLine[0];
MapBase := Map.ScanLine[0];
DstBase := Dst.ScanLine[Y]; // Первой строкой будет Y-строка
// Получим длину строки каждой картинки в байтах. Для этого
// найдем разницу между указателями на 1 и вторую строки
SrcInc := Integer(Src.ScanLine[1]) - Integer(Src.ScanLine[0]);
MapInc := Integer(Map.ScanLine[1]) - Integer(Map.ScanLine[0]);
DstInc := Integer(Dst.ScanLine[1]) - Integer(Dst.ScanLine[0]);
// цикл yy перебирает все строки в картинке источнике
for yy := 0 to Src.Height - 1 do begin
// Получаем указатели на первый пиксель в текущей строке
DWORD(SrcPixel) := DWORD(SrcBase);
Alpha := MapBase;
DWORD(DstPixel) := DWORD(DstBase) + (X * SizeOf(TRGBTriple));
// цикл xx перебирает все пиксели текущей строки картинки источника
for xx := 0 to Src.Width - 1 do begin
// Получаем значение прозрачности из плаща-невидимки
Transparency := Byte(Alpha^);
// Смешиваем цветовые компоненты по полной формуле прозрачности
DstPixel.rgbtRed := DstPixel.rgbtRed +
(SrcPixel.rgbtRed - DstPixel.rgbtRed) *
Transparency * SCA shr 16;
DstPixel.rgbtGreen := DstPixel.rgbtGreen +
(SrcPixel.rgbtGreen - DstPixel.rgbtGreen) *
Transparency * SCA shr 16;
DstPixel.rgbtBlue := DstPixel.rgbtBlue +
(SrcPixel.rgbtBlue - DstPixel.rgbtBlue) *
Transparency * SCA shr 16;
// Получаем указатели на следующие пиксели в текущей строке,
// увеличивая указатель на пиксель на размер пикселя
DWORD(SrcPixel) := DWORD(SrcPixel) + SizeOf(TRGBTriple);
DWORD(DstPixel) := DWORD(DstPixel) + SizeOf(TRGBTriple);
DWORD(Alpha) := DWORD(Alpha) + SizeOf(Byte);
end;
// Получаем указаетель на следующую строку, увеличивая указатель
// на строку на вычисленый ранее размер строки.
DWORD(SrcBase) := DWORD(SrcBase) + SrcInc;
DWORD(MapBase) := DWORD(MapBase) + MapInc;
DWORD(DstBase) := DWORD(DstBase) + DstInc;
end;
end;
Эта процедура работает очень быстро. В демонстрационном свитке для сравнения используются все рассмотренные методы доступа к пикселям. Так вот эта процедура быстрее доступа с помощью новых GetPixel и SetPixel в 8 раз. А при сравнению с использованием свойства Canvas.Pixels[] производительность возрастает в ≈56 раз !!
Теперь ты готов к реальному применению заклинаний прозрачности и можешь переходить к боевой магии.
Орехов Роман also known as tripsin © 2006
tripsin@yandex.ru
Назад |