Оптимизация скинов для окошек сложной формы

Материал из DRKB


Немного предыстории: надо было мне создать скиновое окошко. Вроде несложно, исходников по этому делу везде лежит навалом, бери да делай. Проблема организовалась в том, что для сложных фигур просчет такого окна из растра занимает достаточно много времени. А когда окон несколько? Короче, я решил все это дело написать самостоятельно, причем отказавшись от таких вещей, как GetPixel() и CombineRgn(). Получилось вроде здорово и быстро.

function CreateBitmapRgn(DC: hDC; Bitmap: hBitmap; TransClr: TColorRef): hRgn;

Данная функция создает регион, используя для этого растр Bitmap и исключая из него цвет TransClr. Все расчеты производятся для устройства DC.

Данная функция состоит из двух частей:

Первая часть выделяет память и копирует туда исходное изображение в формате 24 бита на точку, без палитры, т.е. фактически в каждых трех байтах данного раздела памяти будет записан цвет точки исходного изображения. Данный формат был выбран из удобства его обработки (нет необходимости создавать палитру), к тому же нет потери качества при конвертации исходного изображения. Однако, теоретически можно использовать любой формат.

Для выделения памяти под конвертируемое изображение используется функция WinAPI CreateDIBSection. Данная функция выделяет память и создает независимый растр. Для вызова данной функции необходимо заполнить структуру BITMAPINFO, что достаточно не сложно.

Внимание! для изображений Windows Bitmap используется разрешение в формате dots per metr (pixels per metr), стандартному разрешению 72dpi соответствует 2834dpm.

Фактически, данную функция можно не использовать, вручную выделив память для последующего переноса исходного изображения.

Для конвертации и переноса исходного изображения в выделенную память используется функция WinAPI GetDIBits. Функции передаются следующие параметры:

  • исходное изображение,
  • количество рядов для переноса,
  • указатель на память, куда следует перенести изображение,
  • структура BITMAPINFO с заполненными первыми шестью членами (именно здесь задаются параметры для конвертирования

изображения). Фактически, данная функция может перевести любой исходный растр в любой необходимый растр.

Вторая часть описываемой функции проходится по области памяти, куда было занесено конвертируемое изображение, отсекает ненужные области и создает регион. Для создания региона используется функция WinAPI ExtCreateRegion. Для вызова данной функции необходимо заполнить структуру RGNDATA, состоящую из структуры RGNDATAHEADER и необходимого количества структур RECT. в Delphi структура RGNDATA описана так:

_RGNDATA = record
    rdh: TRgnDataHeader;
    Buffer: array[0..0] of CHAR;
    Reserved: array[0..2] of CHAR;
  end;
  RGNDATA = _RGNDATA;

Скорее всего, поле Reserved было введено программистами Delphi только для того, чтобы в нее умещался хотя бы один прямоугольник, т.к. в Microsoft Platfrom SDK этого поля нет. Однако, данная структура нам не подходит, т.к. нам необходимо учитывать сразу несколько прямоугольников. Для решения этой задачи приходится выделять память вручную, с учетом RGNDATAHEADER и количества прямоугольников, необходимых нам, заносить туда прямоугольники (после RGNDATAHEADER), создавать указатель на структуру RGNDATA и ставить его на выделенную память.

Следовательно, придется два раза пройтись по растру: первый раз - для расчета количества прямоугольников, а второй - для уже фактического их занесения в выделенную память.

Есть несколько способов для избежания двойного прохода растра, но все они имеют свои недостатки и здесь не рассматриваются. В любом случае, даже для больших и сложных изображений эти два прохода достаточно быстры.

По окончании работы функции освобождается память, выделенная на конвертируемый растр и структуру RGNDATA.

Далее следует исходный код с комментариями:

unit RgnUnit;

interface

uses
  Windows, SysUtils, Classes;

{ Создает регион, используя для этого растр Bitmap
и исключая из него цвет TransClr. Все расчеты производятся для
устройства DC. }
function CreateBitmapRgn(DC: hDC; Bitmap: hBitmap; TransClr: TColorRef): hRgn;

implementation

// создает регион из растра Bitmap для DC с удалением цвета TransClr
// внимание! TColorRef и TColor не одно и тоже.
// Для перевода используется функция ColorToRGB().

function CreateBitmapRgn(DC: hDC; Bitmap: hBitmap; TransClr: TColorRef): hRgn;
var
  bmInfo: TBitmap;                // структура BITMAP WinAPI
  W, H: Integer;                  // высота и ширина растра
  bmDIB: hBitmap;                 // дескриптор независимого растра
  bmiInfo: BITMAPINFO;            // структура BITMAPINFO WinAPI
  lpBits, lpOldBits: PRGBTriple;  // указатели на структуры RGBTRIPLE WinAPI
  lpData: PRgnData;               // указатель на структуру RGNDATA WinAPI
  X, Y, C, F, I: Integer;         // переменные циклов
  Buf: Pointer;                   // указатель
  BufSize: Integer;               // размер указателя
  rdhInfo: TRgnDataHeader;        // структура RGNDATAHEADER WinAPI
  lpRect: PRect;                  // указатель на TRect (RECT WinAPI)
begin
  Result := 0;
  if Bitmap = 0 then Exit;          // если растр не задан, выходим

  GetObject(Bitmap, SizeOf(bmInfo), @bmInfo);  // узнаем размеры растра
  W := bmInfo.bmWidth;                         // используя структуру BITMAP
  H := bmInfo.bmHeight;
  I := (W*3)-((W*3) div 4)*4;                  // определяем смещение в байтах
  if I <> 0 then I := 4-I;

// Пояснение: растр Windows Bitmap читается снизу вверх, причем каждая строка
// дополняется нулевыми байтами до ее кратности 4.
// для 32-х битный растров такой сдвиг делать не надо.

// заполняем BITMAPINFO для передачи в CreateDIBSection

  bmiInfo.bmiHeader.biWidth := W;             // ширина
  bmiInfo.bmiHeader.biHeight := H;            // высота
  bmiInfo.bmiHeader.biPlanes := 1;            // всегда 1
  bmiInfo.bmiHeader.biBitCount := 24;         // три байта на пиксель
  bmiInfo.bmiHeader.biCompression := BI_RGB;  // без компрессии
  bmiInfo.bmiHeader.biSizeImage := 0;         // размер не знаем, ставим в ноль
  bmiInfo.bmiHeader.biXPelsPerMeter := 2834;  // пикселей на метр, гор.
  bmiInfo.bmiHeader.biYPelsPerMeter := 2834;  // пикселей на метр, верт.
  bmiInfo.bmiHeader.biClrUsed := 0;           // палитры нет, все в ноль
  bmiInfo.bmiHeader.biClrImportant := 0;      // то же
  bmiInfo.bmiHeader.biSize := SizeOf(bmiInfo.bmiHeader); // размер структуры
  // создаем независимый растр WxHx24, без палитры, в указателе lpBits получаем
  // адрес первого байта этого растра. bmDIB - дескриптор растра
  bmDIB := CreateDIBSection(DC, bmiInfo, DIB_RGB_COLORS, Pointer(lpBits), 0, 0);

  // заполняем первые шесть членов BITMAPINFO для передачи в GetDIBits
  bmiInfo.bmiHeader.biWidth := W;             // ширина
  bmiInfo.bmiHeader.biHeight := H;            // высота
  bmiInfo.bmiHeader.biPlanes := 1;            // всегда 1
  bmiInfo.bmiHeader.biBitCount := 24;         // три байта на пиксель
  bmiInfo.bmiHeader.biCompression := BI_RGB;  // без компрессии
  bmiInfo.bmiHeader.biSize := SizeOf(bmiInfo.bmiHeader); // размер структуры
  // конвертируем исходный растр в наш с его копированием по адресу lpBits
  GetDIBits(DC, Bitmap, 0, H, lpBits, bmiInfo, DIB_RGB_COLORS);

  lpOldBits := lpBits;  // запоминаем адрес lpBits

  // первый проход - подсчитываем число прямоугольников, необходимых для
  // создания региона
  C := 0; // сначала ноль
  for Y := H-1 downto 0 do // проход снизу вверх
  begin
    X := 0;
    while X < W do  // от 0 до ширины-1
    begin             
      // пропускаем прозрачный цвет, увеличивая координату и указатель
      while (X < W) and (RGB(lpBits.rgbtRed, lpBits.rgbtGreen, lpBits.rgbtBlue) = TransClr) do
      begin
        Inc(lpBits);
        X := X + 1;
      end;
      // если нашли не прозрачный цвет, то считаем, сколько точек в ряду он идет
      if (X < W) and (RGB(lpBits.rgbtRed, lpBits.rgbtGreen, lpBits.rgbtBlue) <> TransClr) then
      begin
        while (X < W) and (RGB(lpBits.rgbtRed, lpBits.rgbtGreen, lpBits.rgbtBlue) <> TransClr) do
        begin
          Inc(lpBits);
          X := X+1;
        end;
        C := C+1;  // увеличиваем счетчик прямоугольников
      end;
    end;
    // ряд закончился, необходимо увеличить указатель до кратности 4
    PChar(lpBits) := PChar(lpBits) + I;
  end;

  lpBits := lpOldBits;  // восстанавливаем значение lpBits

  // Заполняем структуру RGNDATAHEADER
  rdhInfo.iType := RDH_RECTANGLES;             // будем использовать прямоугольники
  rdhInfo.nCount := C;                         // их количество
  rdhInfo.nRgnSize := 0;                       // размер выделяем памяти не знаем
  rdhInfo.rcBound := Rect(0, 0, W, H);         // размер региона
  rdhInfo.dwSize := SizeOf(rdhInfo);           // размер структуры

  // выделяем память для струтуры RGNDATA:
  // сумма RGNDATAHEADER и необходимых на прямоугольников
  BufSize := SizeOf(rdhInfo) + SizeOf(TRect) * C;
  GetMem(Buf, BufSize);
  lpData := Buf;             // ставим указатель на выделенную память
  lpData.rdh := rdhInfo;     // заносим в память RGNDATAHEADER

  // Заполняем память прямоугольниками
  lpRect := @lpData.Buffer;  // первый прямоугольник
  for Y := H-1 downto 0 do
  begin
    X := 0;
    while X < W do
    begin
      while (X < W) and (RGB(lpBits.rgbtRed, lpBits.rgbtGreen, lpBits.rgbtBlue) = TransClr) do
      begin
        Inc(lpBits);
        X := X+1;
      end;
      if (X < W) and (RGB(lpBits.rgbtRed, lpBits.rgbtGreen, lpBits.rgbtBlue) <> TransClr) then
      begin
        F := X;
        while (X < W) and (RGB(lpBits.rgbtRed, lpBits.rgbtGreen, lpBits.rgbtBlue) <> TransClr) do
        begin
          Inc(lpBits);
          X := X + 1;
        end;
        lpRect^ := Rect(F, Y, X, Y+1);  // заносим координаты
        Inc(lpRect);                    // переходим к следующему
      end;
    end;
    PChar(lpBits) := PChar(lpBits) + I;
  end;

  // после окончания заполнения структуры RGNDATA можно создавать регион.
  // трансформации нам не нужны, ставим в nil, указываем размер
  // созданной структуры и ее саму.
  Result := ExtCreateRegion(nil, BufSize, lpData^);  // создаем регион

  FreeMem(Buf, BufSize);  // теперь структура RGNDATA больше не нужна, удаляем
  DeleteObject(bmDIB);    // созданный растр тоже удаляем
end;

end.


Код исправлен Петровичем


Author: Бочаров Александр
Source: http://forum.vingrad.ru
ID: 03627