страничка tripsin'а


Главная | Статьи | Заметки | Файлы| Ссылки | я

Назад

Дальше

Формула Прозрачности или Прозрачная Магия

Уровень «ПАУК»: Оптимизация и Плащ-невидимка.

скачай демо-проект 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

Назад

Дальше

Hosted by uCoz