Sources
Delphi Russian Knowledge Base
DRKB - это самая большая и удобная в использовании база знаний по Дельфи в рунете, составленная Виталием Невзоровым

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

01.01.2007

Автор: Бочаров Александр

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

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

unit RgnUnit; 
 

 
interface 
 
uses 
  Windows, SysUtils, Classes; 
 
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. в Дельфи структура 
RGNDATA описана так: 
 
  _RGNDATA = record 
    rdh: TRgnDataHeader; 
    Buffer: array[0..0] of CHAR; 
    Reserved: array[0..2] of CHAR; 
  end; 
  RGNDATA = _RGNDATA; 
 
Скорее всего, поле Reserved было введено программистами Дельфи только для того, 
чтобы в нее умещался хотя бы один прямоугольник, т.к. в Microsoft Platfrom SDK 
этого поля нет. Однако, данная структура нам не подходит, т.к. нам необходимо 
учитывать сразу несколько прямоугольников. Для решения этой задачи приходится 
выделять память вручную, с учетом RGNDATAHEADER и количества прямоугольников, 
необходимых нам, заносить туда прямоугольники (после RGNDATAHEADER), 
создавать указатель на структуру RGNDATA и ставить его на выделнную память. 
 
Следовательно, придется два раза пройтись по растру: первый раз - для расчета 
количества прямоугольников, а второй - для уже фактического их занесения 
в выделенную память. 
 
Есть несколько способов для избежания двойного прохода растра, но все они 
имеют свои недостатки и здесь не рассматриваются. В любом случае, даже для 
больших и сложных изображений эти два прохода достаточно быстры. 
 
по окнчании работы функции освобождается память, выделенная на конвертируемый 
растр и структуру RGNDATA. 
} 
 
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); //размер структруы 
  bmDIB:=CreateDIBSection(DC, bmiInfo, DIB_RGB_COLORS, 
                          Pointer(lpBits), 0, 0); 
//создаем независимый растр WxHx24, без палитры, в указателе lpBits получаем 
//адрес первого байта этого растра. bmDIB - дискрептор растра 
 
//заполняем первые шесть членов 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); //размер структуры 
  GetDIBits(DC, Bitmap, 0, H, lpBits, bmiInfo, DIB_RGB_COLORS); 
//конвертируем исходный растр в наш с его копированием по адресу lpBits 
 
  lpOldBits:=lpBits;  //запоминаем адрес lpBits
 
//первый проход - подсчитываем число прямоугольников, необходимых для
//создания региона
  C:=0;                         //сначала ноль
  for Y:=H-1 downto 0 do begin  //проход снизу вверх
    X:=0;
    while X<W do begin             //от 0 до ширины-1
//пропускаем прзрачный цвет, увеличивая координату и указатель
      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.

Взято из https://forum.sources.ru

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

Взято с Vingrad.ru https://forum.vingrad.ru