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


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

Назад

Дальше

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

Боевая магия: Джинн из файла.

скачай демо-проект jinn_from_file.rar (17,1 кб)

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

Для приготовления джинна  нам пригодится уже знакомый виртуальный образ Дамблдора, а также разработанный на предыдущем уровне плащ-невидимка. Если запустить программу из ярлыка на рабочем  столе или из Explorer’а, то джинн плавненько появится прямо из файла. Предполагается, что взявшись за реализацию боевого заклинания, ты имеешь представление, что из себя представляет окно и контекст устройства.

 

 

Этот джинн является полноценным окном. Его можно таскать по столу мышкой и заставить исчезнуть по двойному клику. Он даже сможет исполнять твои желания. Правда для этого ты должен сам четко объяснить ему, что ты хочешь. То есть остается  собственно написать функции, которые этот джинн будет выполнять. ^_^  Итак, поехали.

Джинна мы сделаем из простой формы путем довольно сложной трансфигурации.

Перво-наперво обыкновенное окно надо превратить в многослойное. Это делается путем добавления к стилю окна флага WS_EX_LAYERED.


  
  SetWindowLong(Handle, GWL_EXSTYLE,
                GetWindowLong(Handle, GWL_EXSTYLE) or WS_EX_LAYERED);

Дальше с многослойным окном можно собственно проводить трансфигурацию. Для этого применяется заклинание UpdateLayeredWindow. В Windows Vista добавлено еще одно заклинание – UpdateLayeredWindowIndirect (вызывается похоже)

 

  
function UpdateLayeredWindow(
  Handle: THandle; //Дескриптор нашего многослойного окна, то есть HWND.
  hdcDest: HDC;    //Контекст устройства на которое будет проецироваться 
                   //окно. То есть контекст экрана – GetDC(0);
  pptDst: PPoint;  //Новая позиция окна на экране. Верхний левый угол. 
  _psize: PSize;   //Новый размер окна.
  hdcSrc: HDC;     //Контекст с рисунком нового окна. Об этом отдельно.
  pptSrc: PPoint;  //Левый верхний угол выводимого рисунка в hdcSrc.
  crKey: COLORREF; //Прозрачный цвет. Мы не используем. Будет равен 0.
  pblend: PBLENDFUNCTION; //Указатель на структуру с параметрами смешивания
  dwFlags: DWORD   // Одно из следующих значений: 
                   //1. ULW_ALPHA – альфа смешивание, используется 
                   //               BLENDFUNCTION – наш выбор; 
                   //2. ULW_COLORKEY – используется цветовой ключ crKey; 
                   //3. ULW_OPAQUE – непрозрачное окно.
): Boolean; // Если получится - возвратится True.

 

  
  BLENDFUNCTION = packed record
    BlendOp: BYTE; // Вид операции смешивания. Пока документирован 
                   // только один - AC_SRC_OVER.
    BlendFlags: BYTE; //Не используется. Должен быть равен нулю.
    SourceConstantAlpha: BYTE;// Общая прозрачность рисунка-источника. 
                              // 0 – полная прозрачность, 
                              // 255 – полная непрозрачность.
    AlphaFormat: BYTE; //Определяет, как интерпретируются целевой рисунок 
                       //и рисунок-источник. В настоящий момент 
                       //документирован только AC_SRC_ALPHA(означает, что 
                       //у рисунка-источника есть альфа-канал), хотя есть 
                       //еще  AC_SRC_NO_PREMULT_ALPHA, AC_SRC_NO_ALPHA,
                       // AC_DST_NO_PREMULT_ALPHA, AC_DST_NO_ALPHA.
  end;

 

В основном проблем с параметрами тут нет, кроме одного - hdcSrc: HDC;  Это должен быть контекст устройства вывода, совместимый в нашем случае с экраном, и в него должна быть выбрана картинка, которую мы хотим видеть на месте нашего окна. Для того чтобы использовать per-pixel прозрачность эта картинка должна быть 32-битной. То есть должна содержать  альфа-канал. Из всех форматов изображений наиболее универсально поддерживает альфа-канал формат PNG. И в основном именно его для этой цели и используют. Для  его загрузки можно использовать сторонние библиотеки (например PNGlib) или воспользоваться мощностями GDI+ (Пример можешь глянуть здесь ).

Но мы не пойдем этим путем и создадим 32-битную картинку вручную внутри стандартного TBitmap. В этот нам опять поможет наш любимый профессор. У нас есть 24-битная картинка с образом Дамблдора и 8-битная серая картинка с плащом-невидимкой. 24+8=32. Нам остается только свести эти картинки вместе в одну 32-битную картинку.

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

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

Если ты будешь часто менять изображение в окне, то тебе придется каждый раз заново обрабатывать пиксели изображения  альфа-каналом. Поэтому эту операцию надо оптимизировать еще жестче. Как всегда, чем быстрее работает алгоритм, тем больше он требует памяти. Это я к тому, что мы может рассчитать все возможные значения пикселя только один раз и поместить их  в таблицу (PreMultiplyTable), а потом не выполнять никаких вычислений, а просто брать данные из таблицы. Оптимизации нет предела! При загрузке программы будем заполнять таблицу значениями:

var
 .. 
  PreMultiplyTable: array [0..255,0..255] of Byte;

procedure InitPreMultiplyTable;
var
  Alpha, Color: Byte;
begin
  for Alpha := 0 to 255 do
    for Color := 0 to 255 do
      PreMultiplyTable[Alpha,Color] := Alpha * Color shr 8;
end;

Размер таблицы PreMultiplyTable составляет 256*256=65536 байт. То есть за большую скорость мы расплатились 64 Кб памяти.

Процедура сведения картинок выполняется по быстрому алгоритму и очень похожа на то, что мы делали на уровне ПАУК. Доступ к цветовым компонентам 32-битной картинки делаем с помощью структуры TRGBQuad (используем ее поле rgbReserved для альфа-канала).

procedure TForm1.FormCreate(Sender: TObject);
var
  src, map: TBitmap; //Для загрузки картинок Дамблдора и плаща
  xx, yy: Integer;  // Счетчики циклов
  SrcBase, MapBase, DstBase: Pointer; // Указатели на строки пикселей
  SrcInc, MapInc, DstInc: Integer; // Размеры строк пикселей
  SrcPixel: PRGBTriple; // пиксель 24-битной картинки
  Alpha: PByte; // пиксель 8-битной картинки
  DstPixel: PRGBQuad; // пиксель 32-битной картинки
begin
  inherited;
  // Получаем координаты мыши (TPoint). Из этой точки появится джинн
  GetCursorPos(mouse);
  // Инициируем предумноженную таблицу
  InitPreMultiplyTable;
  // Загружаем Дамблдора - 24 битный
  src := TBitmap.Create;
  src.LoadFromFile('wizard.bmp');
  // Загружаем плащ невидимку - 8 битный
  map := TBitmap.Create;
  map.LoadFromFile('map.bmp');
  // Создаем 32-битный TBitmap размерами как у Дамблдора
  dst := TBitmap.Create;
  dst.Width := src.Width;
  dst.Height := src.Height;
  dst.PixelFormat := pf32bit;
  // Получаем указатели на первые строки пикселей картинок
  SrcBase := Src.ScanLine[0];
  MapBase := Map.ScanLine[0];
  DstBase := Dst.ScanLine[0];
  // Получаем размеры строк пикселей в картинках
  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]);
  // Перебираем все строки
  for yy := 0 to Dst.Height - 1 do begin
    // Получаем первый пиксель в строке
    DWORD(SrcPixel) := DWORD(SrcBase);
    Alpha := MapBase;
    DWORD(DstPixel) := DWORD(DstBase);
    // Перебираем все пиксели в строке
    for xx := 0 to Dst.Width - 1 do begin
      // Устанавливаем цветовые компоненты 32-битной картинки, по
      // значениям предумноженной таблицы.
      DstPixel.rgbRed   := PreMultiplyTable[Byte(Alpha^), SrcPixel.rgbtRed];
      DstPixel.rgbGreen := PreMultiplyTable[Byte(Alpha^), SrcPixel.rgbtGreen];
      DstPixel.rgbBlue  := PreMultiplyTable[Byte(Alpha^), SrcPixel.rgbtBlue];
      DstPixel.rgbReserved := Byte(Alpha^); // альфа-канал без изменений
      // Получаем следующий пиксел в картинках
      DWORD(SrcPixel) := DWORD(SrcPixel) + SizeOf(TRGBTriple); 
      DWORD(DstPixel) := DWORD(DstPixel) + SizeOf(TRGBQuad); 
      DWORD(Alpha)    := DWORD(Alpha)    + SizeOf(Byte);
    end;
    // Получаем следующую строку
    DWORD(SrcBase) := DWORD(SrcBase) + SrcInc;
    DWORD(MapBase) := DWORD(MapBase) + MapInc;
    DWORD(DstBase) := DWORD(DstBase) + DstInc;
  end;
  // Освободаем память от ненужных более картинок
  src.Free;
  map.Free;
  // Убираем у окна бордюр и шапку. Без этого ничего не выйдет
  BorderStyle := bsNone;
  // Прквращаем окно в многослойное.
  SetWindowLong(Handle, GWL_EXSTYLE,
                GetWindowLong(Handle, GWL_EXSTYLE) or WS_EX_LAYERED);
end;

Как видишь весь подготовительный код мы поместили в обработчик создания формы. Так как в моем примере картинка окна не изменяется, то 32-битная картинка создается только один раз и в этом примере использование предумноженной таблицы прироста скорости не дает.

Саму трансфигурацию выполним при активации формы. Контекст устройства для заклинания UpdateLayeredWindow получим из самой картинки: TBitmap.Canvas.Handle. Дальше получим координаты мышки на экране (а она будет как раз над файлом в проводнике, ты же нему кликал для запуска примера). И будем показывать постепенно появляющегося джинна, плавно отъезжающего от мышки, то есть от файла. Создастся впечатление, что джинн появился прямо из файла. Вот так вот :) Всего лишь ловкость рук.

procedure TForm1.FormActivate(Sender: TObject);
var
  screenDC: HDC; // контекст экрана
  pt1, pt2 : TPoint; // точки для UpdateLayeredWindow
  sz : TSize; // размер окна для UpdateLayeredWindow
  bf : TBlendFunction; // структура для UpdateLayeredWindow
  i: Integer; // счетчик цикла
begin
  // Получаем контекст экрана
  screenDC := GetWindowDC(GetDesktopWindow); // или GetDC(0);
  // Крутим цикл
  for i := 2 to 64 do begin
    // Вычисляем новое положение окна от координат мыши
    pt1 := Point(mouse.X + i , mouse.Y + i);
    pt2 := Point(0,0); // Берем для вывода всю 32-битную картинку
    sz.cx := dst.Width;  // Устанавливаем размеры окна равными
    sz.cy := dst.Height; // размерам картинки
    // Заполняем параметры смешивания
    with bf do begin
      BlendOp := AC_SRC_OVER;
      BlendFlags := 0;
      SourceConstantAlpha := i * 4 - 1; // Меняем общую прозрачность джинная
      AlphaFormat := AC_SRC_ALPHA; //используем альфа-канал
    end;
    // Наконец-то вызываем заклинание.
    if not UpdateLayeredWindow(Handle, screendc, @pt1, @sz, dst.Canvas.Handle,
                               @pt2,0, @bf,ULW_ALPHA) then
    ShowMessage('UpdateLayeredWindow: ' + SysErrorMessage(GetLastError));
    // Если оно не получилось, то выведется описание ошибки
    Sleep(1); // Небольшая актерская пауза
  end;
  // Освобождаем контекст экрана
  ReleaseDC(GetDesktopWindow,screenDC);
end;

Остальной код в примере нужен для того, чтобы таскать джинна по столу за тело. С эти разберешся сам. Конечно надо сделать еще так чтобы джинн был всегда поверх всех окон, чтобы его можно было вызвать горячей клавишей, чтобы он не запускался второй раз, но это уже не относится к теме данного манускрипта. Кстати обрати внимание что, хотя окно выводится прямоугольное, прозрачные участки джинна прозрачны также и для кликов  мышью. Этот значит, что теперь ты можешь создать окно самой вычурной формы без использования всяких регионов.

Это положительные моменты. Но есть тут один очень жирный минус. Используя функцию UpdateLayeredWindow, ты сообщаешь операционной системе о том, что берешь на себя полную ответственность за рисования окна и управление им. Windows будет только брать картинку из контекста, который ты указал в hdcSrc и обеспечивать прозрачность в соответствии с параметрами функции и альфа-каналом рисунка. В твое окно перестанет приходить сообщение WM_PAINT и событие OnPaint перестанет работать. Перерисовывать окно ты будешь должен сам по мере надобности. Для этого снова вызываешь UpdateLayeredWindow и передаешь ей обновленную картинку.

И вот тут и возникает проблема с компонентами на  форме. Если во время разработки ты поместил на форму элементы управления (типа кнопок или полей ввода), то на новом окне они не прорисуются. Ведь выводится только твоя картинка. Но эти элементы будут работать, реагировать на клики (хотя и не всегда). Наиболее разумным решением этой проблемы будет полный отказ от стандартных элементов управления. Вместо этого надо рисовать элементы управления вручную в виде картинок прямо в контексте устройства, но перед его обработкой альфа-каналом. (применительно к коду примера рисовать элементы надо на картинке волшебника перед сведением картинок в 32-битную)




Орехов Роман also known as tripsin ©  2006

tripsin@yandex.ru

Назад

Дальше

Hosted by uCoz