InfoCity
InfoCity - виртуальный город компьютерной документации
Реклама на сайте







Размещение сквозной ссылки

 

Прозрачность - это просто


Виталий Брусенцев, The RSDN Group


Терминология

Введение

Windows 95 и списки изображений
Создание списков прозрачных изображений
Рисование с помощью Image List

Windows 98 и библиотека msimg32.dll

AlphaBlend(): "Полу" - не обязательно 1/2
Простой режим (общая прозрачность)
Режим с альфа-каналом

Терминология

Прежде чем начать, убедимся, что понимаем друг друга:

  • Растровое изображение, растр (bitmap) - прямоугольная картинка, состоящая из пикселов.
  • Пиксел - минимальный элемент изображения, точка на экране или в памяти растра.
  • Прозрачность - свойство некоторых пикселов не отображаться на устройстве вывода, оставляя оригинальное изображение неизменным.
  • Полупрозрачность - такое взаимодействие пикселов, при котором видны как пикселы выводимого растра, так и фоновое изображение.
  • Спрайт - растровое изображение с прозрачными и полупрозрачными участками.

Введение


Зачем нужны растровые изображения с прозрачностью или полупрозрачностью отдельных участков? Это важные элементы графического интерфейса Windows, которые вы можете наблюдать каждый раз, когда включаете компьютер. Иконки на рабочем столе имеют прозрачные участки, что позволяет видеть "сквозь" них. Когда вы, работая в папке Windows 98, перетаскиваете какой-нибудь объект, его значок становится полупрозрачным, позволяя видеть, что в данный момент находится под ним. И, наконец, нельзя забывать о таких "графикоемких" программах, как игры. Трудно себе представить, чтобы в тщательно спроектированной плоской космической "стрелялке" все корабли имели прямоугольную форму. А при отрисовке взрывов желательно делать их полупрозрачными, приближая картинку к реальности.

Вообще - то на эту тему писали довольно много. Для понимания основных механизмов получения эффектов прозрачности рекомендую прочитать статью Рона Джери "Bitmaps with transparency" (ее можно найти в MSDN в разделе Technical Articles->Multimedia->GDI). Также рекомендую изучить находящиеся там статьи Дейла Роджерсона ("Sprites Make the World Go Round") и Германа Родента ("Animation in Win32").

К сожалению, все эти статьи разделяют общий недостаток - почтенный возраст. Цель данной статьи - показать, что с появлением Windows 95, а затем Windows 98 и Windows 2000 жить программистам стало намного проще (и интереснее!). Все приводимые примеры написаны, для удобства, с использованием библиотеки MFC, но принципы остаются общими.

Windows 95 и списки изображений


С выходом Windows 95 в распоряжении программистов оказалась удобная библиотека Common Controls. В ее составе были не только новые (теперь уже известные всем) элементы управления, но и невизуальный компонент - список изображения (Image List control). Его основное предназначение - содержать набор картинок одинакового размера. Это удобно для применения в разнообразных элементах - например, в панелях инструментов (toolbar):


Нас же больше интересует другая интересная возможность - хранить в списках изображений информацию о прозрачности. Это достигается одним из двух способов:

  • Загрузкой растрового изображения с указанием, пикселы какого цвета считать прозрачными;
  • Подготовкой специальной маски прозрачности - черно-белого растра, в котором пикселы черного цвета означают прозрачность для соответствующих точек основного растра. При этом маска прозрачности должна иметь такие же размеры, что и основной растр.

Нужно понимать, что в обоих случаях список изображений будет содержать маску прозрачности, просто при первом способе она будет создана за вас. Какой способ избрать - дело вкусов каждого программиста. Я обычно нахожу в палитре какой-нибудь ненужный цвет и назначаю его прозрачным. В большинстве случаев не везет ярко-сиреневому цвету (, RGB = (255,0,255)), но в этот раз для демонстрации эффекта прозрачности я выбрал белый:


Создание списков прозрачных изображений


Создать список изображения и загрузить в него растр с прозрачностью можно так:

    CBitmap m_Bmp;
    m_Bmp.LoadBitmap(IDB_BITMAP1); 
    CImageList imgList;    
    imgList.Create(cx, cy, ILC_COLOR24|ILC_MASK, 1, 0);
    imgList.Add(&m_Bmp, RGB(255,255,255));

В приводимых примерах будет предполагаться, что растровые изображения находятся в ресурсах программы и имеют глубину цвета 24 бита (16 млн. цветов). При создании списка необходимо указать размеры загружаемого растра (cx, cy), его цветовой формат (ILC_COLOR24, 16 миллионов оттенков) и признак наличия маски (ILC_MASK). Последние два параметра Create() определяют число хранимых в списке изображений и величину приращения списка при нехватке места. Макрос RGB удобен для указания цвета в 24-битовом диапазоне, в данном случае - цвета прозрачности.

В принципе, загрузить картинку можно и одним вызовом, но этот метод не поддерживает полноцветные изображения (как в нашем примере):

    CImageList imgList;
    imgList.Create(IDB_BITMAP1, cx, 0, RGB(255,255,255));

По умолчанию, такой растр ограничен 16 цветами.

Рисование с помощью Image List


Для чего списки изображений действительно полезны, так это для облегчения работы по рисованию. Вы вспомните навскидку число и порядок параметров у BitBlt()? А ее неудобство из-за необходимости подготовить дополнительный контекст устройства в памяти? Все это способно смутить не только новичка, но и более опытного программиста.

Ситуация еще более усложняется, когда мы имеем дело с прозрачностью. Необходимо выводить картинку несколько раз с различными параметрами растровых операций (Raster operation, ROP). Каждый раз, когда мы собираемся вывести на экран небольшое изображение космического корабля, необходимо проделать уйму работы.

К счастью, программисты Microsoft уже сделали ее за нас. Можно нарисовать растр, содержащийся в списке изображений, просто вызвав функцию ImageList_Draw(). С использованием MFC этот вызов выглядит, например, так:

    imgList.Draw(pDC, 0, m_drawPoint, ILD_TRANSPARENT);

Здесь pDC - указатель на контекст устройства (CDC), 0 - порядковый номер выводимого из списка изображения, m_drawPoint - координаты начала области вывода. Флаг ILD_TRANSPARENT указывает, что вывод нужно осуществлять с учетом маски прозрачности.

Для самых любознательных сообщу, что реализация эффекта прозрачности при этом достигается методом, который Рон Джери называет Black Source Method, т.е., "метод черного источника". Он позволяет выводить изображение с прозрачными участками за два вызова BitBlt() вместо трех, но требует предварительно заменить пикселы, являющиеся прозрачными, черным цветом. Поэтому, загружая растр в список изображений, вы меняете его.

В результате вызова получится примерно вот что:


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

Windows 98 и библиотека msimg32.dll


Windows 98 принесла новый простой способ вывода прозрачных изображений. Входящая в ее состав библиотека msimg32.dll содержит новые функции для получения соблазнительных графических эффектов. Для ее использования нужно подключить к проекту при сборке файл msimg32.lib.

Теперь растр с прозрачностью можно вывести за один прием с помощью функции TransparentBlt, указав прозрачный цвет в последнем параметре функции:

    CDC memDC;
    memDC.CreateCompatibleDC(pDC);
    CBitmap *temp = memDC.SelectObject(m_Bmp);    
    TransparentBlt(pDC->m_hDC, x, y, dstX, dstY, memDC.m_hDC,
                   x1, y1, srcX, srcY, RGB(255,255,255));
    memDC.SelectObject(temp);

"Ничего себе - за один прием!" скажете вы и ...будете правы. Вновь появляется необходимость в совместимом контексте устройства, в котором нужно выбрать выводимый растр. Вновь - длинный (и не интуитивный) список параметров. Но зато появились возможности по управлению выводом. В данном примере x, y, x1, y1 - координаты начальной точки растра в приемнике и источнике соответственно. Параметры dstX и dstY - размеры области вывода, а srcX и srcY - ширина и высота прямоугольника, отображаемого из растра. Что если задать для них разные значения? Результат приведен здесь:


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

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

Совет
Не забудьте после использования контекста устройства вновь выбрать в нем начальный растр (temp в нашем примере). В противном случае произойдет утечка графических ресурсов системы.

AlphaBlend(): "Полу" - не обязательно 1/2


Все вышеописанные методы работают только с одной моделью прозрачности, называемой в компьютерной графике Chroma Key. Это означает, что прозрачным назначается определенный цвет. Другая, более развитая, модель называется Alpha Blending. При ее использовании для описания характеристик пикселов кроме цветовых компонент (R, G, B) применяется прозрачность (Alpha). Степень прозрачности определяется обратной величиной этого параметра.

Для поддержки этого режима Windows 98, а затем и Windows 2000 и Windows ME предоставляют функцию AlphaBlend():

BOOL AlphaBlend(
  HDC hdcDest,                 // handle to destination DC
  int nXOriginDest,            // x-coord of upper-left corner
  int nYOriginDest,            // y-coord of upper-left corner
  int nWidthDest,              // destination width
  int nHeightDest,             // destination height
  HDC hdcSrc,                  // handle to source DC
  int nXOriginSrc,             // x-coord of upper-left corner
  int nYOriginSrc,             // y-coord of upper-left corner
  int nWidthSrc,               // source width
  int nHeightSrc,              // source height
  BLENDFUNCTION blendFunction  // alpha-blending function
);

Назначение ее параметров, в-общем, ясно из прототипа. Особый интерес представляет последний параметр - BLENDFUNCTION. Он представляет собой структуру (всего из четырех байт), определяющую режим вывода.

typedef struct _BLENDFUNCTION {
  BYTE     BlendOp;
  BYTE     BlendFlags;
  BYTE     SourceConstantAlpha;
  BYTE     AlphaFormat;
}BLENDFUNCTION, *PBLENDFUNCTION, *LPBLENDFUNCTION;

Что это такое? Дело в том, что AlphaBlend() может работать в двух разных режимах.

Простой режим (общая прозрачность)


Первый (и наиболее простой в использовании) режим работы AlphaBlend() предполагает, что значение Alpha задано для всей картинки. В таком случае, оно применяется ко всем пикселам без исключения:


Этот результат был получен при таком формате BLENDFUNCTION:

     BLENDFUNCTION blend;
     blend.BlendOp = AC_SRC_OVER;
     blend.BlendFlags = 0;
     blend.AlphaFormat = 0;
     blend.SourceConstantAlpha = 180;

Для поля BlendOp в данный момент определено только одно допустимое значение - AC_SRC_OVER.
Поле BlendFlags должно содержать 0.
Плохо документированный параметр AlphaFormat определяет взаимодействие пикселов источника и приемника, о чем мы еще поговорим далее.
Параметр SourceConstantAlpha определяет степень непрозрачности. Задав для этого поля 0, вы не увидите свой растр вообще. Максимальное значение, умещающееся в тип BYTE, равно 255. При этом выводимый растр полностью перекроет область назначения. Но зачем вам, в таком случае, AlphaBlend()? И это значение используется, в-основном, для второго режима.

Режим с альфа-каналом


Он требует некоторой дополнительной подготовки. В этом режиме растр, подготовленный для вывода, должен содержать информацию о степени прозрачности каждого пиксела. Это достигается, например, применением формата 32 бита на пиксел (по одному байту на каждый цветовой компонент и одному - на альфа-канал).

Сложности возникнут вот с чем. Мне неизвестен ни один графический пакет, позволяющий сохранять растры с альфа-каналом в формате, пригодном для AlphaBlend(). При создании такого растра программно мы можем сохранить его на диск (в формате Windows Bitmap 32-bit). Он прочитывается популярными программами типа ACDSee, но функции LoadBitmap() и LoadImage() отказываются его загружать. При попытке поместить его в ресурс rc.exe у меня вывалился с сообщением Internal error...

Но унывать не стоит. Мы можем подготовить растр в памяти, скомбинировав, например, два растра - с изображением объекта и его тени. К счастью, функция AlphaBlend() может работать с растрами, созданными с помощью CreateDIBSection():

HBITMAP CreateDIBSection(
  HDC hdc,                 // handle to DC
  CONST BITMAPINFO *pbmi,  // bitmap data
  UINT iUsage,             // data type indicator
  VOID **ppvBits,          // bit values
  HANDLE hSection,         // handle to file mapping object
  DWORD dwOffset           // offset to bitmap bit values
);

Здесь hdc - контекст, совместимый с устройством вывода, pbmi - указатель на структуру BITMAPINFO с информацией о размерах и цветовой глубине создаваемого растра. Параметр iUsage определяет, что будет содержаться в буфере растра - индексы в палитре цветов (DIB_PAL_COLORS) или прямые их значения (DIB_RGB_COLORS). Очевидно, что второе - для режима TrueColor палитра не нужна.
Указатель на созданный буфер при успешном вызове будет возвращен в параметре ppvBits.
В параметрах hSection и dwOffset при работе с растром в памяти необходимо указывать 0.

Примечание
Все вышесказанное не означает, что спрайты с альфа-каналом нельзя хранить в ресурсах программы. Вы можете включить их как custom resource, загрузить с помощью LoadResource() и заполнить полученными данными буфер, созданный с помощью CreateDIBSection(). Но не забывайте, что это значительно увеличит размер вашего исполняемого модуля. Кроме того, можно рассмотреть вариант подгрузки предварительно рассчитанных в подобной программе растров из внешних файлов - средствами библиотеки исполнения или используя параметры hSection и dwOffset при вызове CreateDIBSection().

Использование CreateDIBSection() облегчает доступ к битам изображения: в противном случае такие картинки можно было бы вывести только в режиме экрана True Color 32 bit. Для обращения с таким форматом данных идеально подходит структура RGBQUAD:

typedef struct tagRGBQUAD {
  BYTE    rgbBlue; 
  BYTE    rgbGreen; 
  BYTE    rgbRed; 
  BYTE    rgbReserved; 
} RGBQUAD;

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

Результат приведен здесь:


Детали работы с битами растров мы опустим (все это можно найти в прилагаемом проекте). Отмечу только, что для вывода использовался такой формат BLENDFUNCTION:

    blend.BlendOp = AC_SRC_OVER;
    blend.BlendFlags = 0;
    blend.AlphaFormat = AC_SRC_NO_PREMULT_ALPHA;
    blend.SourceConstantAlpha = 255;

Параметр AC_SRC_NO_PREMULT_ALPHA не описан в MSDN за январь 2000 года и найден экспериментально (и подглядыванием в wingdi.h :) При его задании используется альфа-канал растра источника и не используется альфа-канал приемника (возможно и такое).

Совет
При использовании альфа-канала у вас все равно остается возможность без пересчета битов растра изменять прозрачность всей картинки - SourceConstantAlpha работает и в этом случае.

И в завершение напомню, что AlphaBlend() также требует включения в проект при сборке библиотеки импорта msimg32.lib, которая отсутствует в Windows 95.


Реклама на InfoCity

Яндекс цитирования



Финансы: форекс для тебя








1999-2009 © InfoCity.kiev.ua