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

Немного предыстории: надо было мне создать скиновое окошко. Вроде несложно, исходников по этому делу везде лежит навалом, бери да делай. Проблема организовалась в том, что для сложных фигур просчет такого окна из растра занимает достаточно много времени. А когда окон несколько? Короче, я решил все это дело написать самостоятельно, причем отказавшись от таких вещей, как 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
Category:Графика
Category:Bitmap