|
||||
|
Программирование на Visual C++Выпуск №45 от 20 мая 2001 г. Всем привет! СТАТЬЯПрозрачность – это просто Терминология Прежде чем начать, убедимся, что понимаем друг друга: • Растровое изображение, растр (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.dllWindows 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 данного устройства, что может дать прирост производительности по сравнению с традиционными методами. AlphaBlend(): "Полу" – не обязательно ½ Все вышеописанные методы работают только с одной моделью прозрачности, называемой в компьютерной графике 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.
Использование 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 :) При его задании используется альфа-канал растра источника и не используется альфа-канал приемника (возможно и такое).
И в завершение напомню, что AlphaBlend() также требует включения в проект при сборке библиотеки импорта msimg32.lib, которая отсутствует в Windows 95. ВОПРОС-ОТВЕТКак создать многострочный тултип?
Начиная с версии 4.70 библиотеки Comctl32.dll тултипы поддерживают многострочный режим работы. По умолчанию он выключен, и всё, что требуется от нас – активизировать его. Для этого предназначено сообщение TTM_SETMAXTIPWIDTH, которое позволяет задать ширину тултипа (в пикселях). По умолчанию ширина установлена в –1, что соответствует однострочному режиму работы. В этом режиме тултип игнорирует все пары '\r\n' в тексте подсказки, выдавая его в одну строку. Задание любого положительного значения ширины переводит тултип в многострочный режим работы. В многострочном режиме тултип корректно обрабатывает комбинации '\r\n', переходя на новую строку. Кроме того, он старается вписать текст в заданную ширину, разбивая его на строки самостоятельно. Переход на новую строку возможен только между словами, поэтому если в тексте подсказки есть длинные слова, заданная ширина может быть превышена. Если вы не хотите, чтобы тултип разбивал текст на строки по своему усмотрению, задайте значение ширины, заведомо превышающее ширину экрана. Например: SendMessage(hTip, TTM_SETMAXTIPWIDTH, 0, (LPARAM)0xFFFFFF); В MFC аналогичного эффекта можно добиться, используя фукцию CToolTipCtrl::SetMaxTipWidth. Единственный параметр, который она получает – новое значение ширины тултипа. Например: // m_tt - объект класса CToolTipCtrl m_tt.SetMaxTipWidth(0xFFFFFF); Проблемы возникают только в том случае, когда вы используете встроенную поддержку тултипов класса CWnd. В этом случае тултип создаётся и уничтожается в недрах MFC, причём документированного способа добраться до него не существует. Выйти из положения можно, воспользовавшись недокументированным: MFC сохраняет указатель на созданный ею объект класса CToolTipCtrl в структуре _AFX_THREAD_STATE, и можно получить к нему доступ, используя выражение AfxGetThreadState()->m_pToolTip. Вторая проблема состоит в том, что MFC сама следит за временем жизни тултипа, и мы не можем точно знать, когда он уничтожается и создаётся заново. Поэтому ширину тултипа необходимо заново задавать всякий раз, когда тултип "собирается" появиться на экране. Удобнее всего делать это в ответ на уведомление TTN_NEEDTEXT, например: BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ON_NOTIFY_EX_RANGE(TTN_NEEDTEXT, 0, 0xFFFFFFFF, OnToolTipText) END_MESSAGE_MAP() BOOL CMainFrame::OnToolTipText(...) { CToolTipCtrl* ptt = AfxGetThreadState()->m_pToolTip; ptt->SetMaxTipWidth(0xFFFFFF); return CFrameWnd::OnToolTipText(…); }ОБРАТНАЯ СВЯЗЬ
Большое спасибо, Андрей. Это все на сегодня. Пока! (Алекс Jenter jenter@rsdn.ru) (Красноярск, 2001. Рассылка является частью проекта RSDN.) |
|
||
Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх |
||||
|