Последние записи
- Преобразовать массив байт в вещественное число (single)
- TChromium (CEF3), сохранение изображений
- Как в Delphi XE обнулить таймер?
- Изменить цвет шрифта TextBox на форме
- Ресайз PNG без потери прозрачности
- Вывод на печать графического файла
- Взаимодействие через командную строку
- Перенести программу из Delphi в Lazarus
- Определить текущую ОС
- Автоматическая смена языка (раскладки клавиатуры)
Интенсив по Python: Работа с API и фреймворками 24-26 ИЮНЯ 2022. Знаете Python, но хотите расширить свои навыки?
Slurm подготовили для вас особенный продукт! Оставить заявку по ссылке - https://slurm.club/3MeqNEk
Online-курс Java с оплатой после трудоустройства. Каждый выпускник получает предложение о работе
И зарплату на 30% выше ожидаемой, подробнее на сайте академии, ссылка - ttps://clck.ru/fCrQw
21st
Фев
Layered в windows. Использование слоев
Posted by Chas under Журнал, Статьи
Многие профессионалы и новички в программировании под Windows задаются вопросом, как создавать окна необычной формы с использованием различных эффектов, таких как тени, к примеру. Такие окна часто можно встретить в виде виджетов/дополнений на рабочем столе Windows 7/Vista или же заставки при запуске приложений – к примеру, Photoshop (все мы помним то перышко, выходящее за границу прямоугольника). Также имеет смысл создавать подобные окна с полноценными элементами управления: данная технология позволяет форматировать вывод окна попиксельно, то есть можно настроить прозрачность и цвет любого пикселя окна вашего приложения – разве не здорово?
Владимир Лихонос
by ВОВАН13 www.programmersforum.ru
Введение
Обычно после осознания пре- имуществ тех- нологии мы понимаем, что есть смысл использовать ее. Основная проблема в том, что нет возможности использовать стандартные компоненты той же VCL (Delphi), Win API Controls и другое, т.к. после настройки окна на использование данной технологии окно более не получает сообщение WM_PAINT, соответственно пользователь более не увидит элементы управления.
Основная часть
Давайте рассмотрим простой пример: отображение PNG иконки в окне, причем будем рисовать загруженную иконку с тенью. Нам понадобятся:
- Microsoft Visual Studio 2010 / MFC / C++;
- Иконка: android.png (см. ресурсы к статье в папке Images).
Вот и все. Будем идти, рассматривая детально весь процесс подготовки и корректной организации подобных окон (оговорюсь, что подобный подход я лично вывел для себя в течение нескольких лет практики; пишите как вам удобней, решений может быть множество).
Итак, если кто-то не знаком с MFC, давайте создадим пустой проект MS VS ? File ? New ? Project ? Visual C++ ? General ? Empty Project, назовем его LayersExample. Теперь создадим иерархию файлов подобным образом. Header Files:
dib.h
layersExample.h
stdafx.h
Source Files:
dib.cpp
layersExample.cpp
main.cpp
Теперь рассмотрим app.h/.cpp:
#define APP_H
#include <afxwin.h>
#include <gdiplus.h>
#include "stdafx.h"
#include "layersExample.h"
class CApp: public CWinApp
{
public:
CApp();
~CApp();
virtual BOOL InitInstance();
virtual int ExitInstance();
public:
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
};
#endif /* APP_H */
Принципиально нового здесь ничего нет: наследуем класс CWinApp и основные его методы InitInstance и ExitInstance для обработки запуска и завершения нашего приложения соответственно. Для работы с графикой в приложении будет отвечать GDI+. Следовательно, его надо подключить #include <gdiplus.h> и инициализировать. Для этого объявим переменные gdiplusStartupInput и gdiplusToken. Давайте рассмотрим app.cpp файл:
CApp::CApp()
{
}
CApp::~CApp()
{
}
BOOL CApp::InitInstance()
{
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
m_pMainWnd = new CLayersExample();
m_pMainWnd->ShowWindow(SW_SHOW);
return CWinApp::InitInstance();
}
int CApp::ExitInstance()
{
Gdiplus::GdiplusShutdown(gdiplusToken);
return CWinApp::ExitInstance();
}
Как вы уже заметили, для инициализации и деинициализации GDI+ используются функции GdiplusStartup и GdiplusShutdown соответственно. Как основное окно используется класс CLayersExample – он содержит весь основной код (рассмотрим его чуть позже). Также был замечен файл под странным названием stdafx.h, Microsoft предлагает в нем писать различные декларации (дополнения) для приложения.
К примеру, наш stdafx.h будет содержать следующее:
#define STDAFX_H
#pragma comment(lib, "gdiplus.lib")
#ifdef _UNICODE
#if defined _M_IX86
#pragma comment(linker,"/manifestdependency:\"type=’win32′
name=‘Microsoft.Windows.Common-Controls’ version=‘6.0.0.0’
processorArchitecture=‘x86’ publicKeyToken=‘6595b64144ccf1df’language=‘*’\"")
#elif defined _M_IA64
#pragma comment(linker,"/manifestdependency:\"type=’win32′
name=‘Microsoft.Windows.Common-Controls’ version=‘6.0.0.0’
processorArchitecture=‘ia64’ publicKeyToken=‘6595b64144ccf1df’language=‘*’\"")
#elif defined _M_X64
#pragma comment(linker,"/manifestdependency:\"type=’win32′
name=‘Microsoft.Windows.Common-Controls’ version=‘6.0.0.0’
processorArchitecture=‘amd64’
publicKeyToken=‘6595b64144ccf1df’ language=‘*’\"")
#else
#pragma comment(linker,"/manifestdependency:\"type=’win32′
name=‘Microsoft.Windows.Common-Controls’ version=‘6.0.0.0’
processorArchitecture=‘*’ publicKeyToken=‘6595b64144ccf1df’
language=‘*’\"")
#endif
#endif
#endif /* STDAFX_H */
Принципиально здесь ничего сложного, #pragma comment(lib, «gdiplus.lib»), как можно догадаться, используется для подключения GDI+ к проекту, иначе функции, которые продекларированы в gdiplus.h, не будут найдены при линковке проекта. То, что ниже, – обычный манифест; в принципе, для нашего проекта он не особо нужен. Прежде чем перейдем к самому вкусному, надо рассмотреть main.cpp, иначе наше приложение попросту не запустится. Как мы помним, мы описали класс Capp. Теперь надо где-то объявить его экземпляр:
static CApp app;
Ничего сложного, статически создастся экземпляр, и наше приложение запустится. Т.е., по сути, произойдет вызов CApp::InitInstance, где его поджидает инициализация GDI+ и создание главного окна. Теперь давайте рассмотрим CLayersExample:
#define LAYERSEXAMPLE_H
#include <afxwin.h>
#include <gdiplus.h>
#include "stdafx.h"
#include "dib.h"
class CLayersExample: public CFrameWnd
{
public:
CLayersExample();
~CLayersExample();
public:
void LayerDraw(CDIB *dib);
void LayerUpdate(CDIB *dib);
private:
CString applicationPath;
CDIB *dibAndroid;
CDIB *dib;
public:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnDestroy();
afx_msg LRESULT OnNcHitTest(CPoint point);
};
#endif /* LAYERSEXAMPLE_H */
Сразу оговорюсь: вы, возможно, уже заметили #include «dib.h», этот модуль я написал для связки HBITMAP + HDC + Scan0 + Gdiplus::Bitmap и добавил некие методы для обработки и изменения данных. Я счел ненужным описывать и обсуждать методы класса CDIB: в принципе, все наглядно и открыто, понять назначение того или иного метода не составит труда. За формирование нужного нам изображения (напомню, это иконка с диска и уже нарисованная к ней тень) отвечает void LayerDraw(CDIB *dib):
{
if (!dib->Ready() || !dibAndroid->Ready())
{
return;
}
Graphics g(dib->dc);
g.SetCompositingMode(CompositingModeSourceCopy);
g.SetInterpolationMode(InterpolationModeHighQualityBicubic);
g.DrawImage(dibAndroid->bmp,
RectF(AndroidShadowSize, AndroidShadowSize, (REAL)AndroidSize — AndroidShadowSize, (REAL)AndroidSize — AndroidShadowSize), 0, 0, (REAL)dibAndroid->Width(), (REAL)dibAndroid->Height(), UnitPixel);
DIB_ARGB *p = dib->scan0;
int size = dib->Width() * dib->Height();
for(int i = 0; i < size; i++, p++)
{
p->r = 0;
p->g = 0;
p->b = 0;
}
dib->Blur(dib->Rect(), CRect(0, AndroidShadowSize, 0, 0),
AndroidShadowSize);
g.SetCompositingMode(CompositingModeSourceOver);
g.DrawImage(dibAndroid->bmp, RectF(AndroidShadowSize / 2, 0, AndroidSize, AndroidSize), 0, 0, (REAL)dibAndroid->Width(), (REAL)dibAndroid->Height(), UnitPixel);
}
Рассмотрим подробнее. В начале кода мы видим if (!dib ? Ready() || !dibAndroid ? Ready()), метод CDIB::Ready() возвращает булево значение, т.е. если TRUE, то CDIB содержит корректно сформированный либо загруженный битмап, иначе что-то не так и доступ к любому из полей CDIB может вызвать исключение. В нашем случае, dib – это CDIB экземпляр, который будет хранить готовое изображение нашего будущего окна, в то время как dibAndroid – это загруженная иконка (Images\android.png) с диска:
g.SetCompositingMode(CompositingModeSourceCopy);
g.SetInterpolationMode(InterpolationModeHighQualityBicubic);
Данный код связывает контекст GDI+ с нашим HDC, а именно dib ? dc. Это необходимо для рисования нужной нам иконки на dib. SetCompositingMode устанавливает режим рисования, в нашем случае это CompositingModeSourceCopy, т.е. рисование с перекрытием. SetInterpolationMode необходим для настройки качества масштабирования изображений при рисовании их на контекст GDI+. InterpolationModeHighQualityBicubic – для установки лучшего качества.
На выходе мы желаем получить dib, внутри которого была бы иконка с тенью. Так как наша иконка без тени, алгоритм будет выглядеть приблизительно так:
- рисуем уменьшенную копию иконки; делаем изображение черным – тень будет черной;
- применяем эффект Blur для создания тени;
- применяем эффект AlphaBlend для задания тени большей прозрачности;
- рисуем оригинальную иконку поверх полученного изображения.
В принципе, ничего сложного. Однако замечу, что эффекты AlphaBlend и Blur реализованы собственноручно и будут выполняться на CPU, а не на GPU, как бы хотелось. Следовательно, если задать большой радиус размытия для Blur, то это займет много времени (обычно 10-20 вполне хватает, и работает относительно быстро). Приступим к воплощению мечты в реальность по заданному алгоритму.
- Рисуем уменьшенную копию иконки:
g.DrawImage(dibAndroid->bmp,
RectF(AndroidShadowSize, AndroidShadowSize,
(REAL)AndroidSize — AndroidShadowSize,
(REAL)AndroidSize — AndroidShadowSize), 0, 0,
(REAL)dibAndroid->Width(),
(REAL)dibAndroid->Height(), UnitPixel);Как видим, используется метод GDI+ DrawImage, где применяем следующие методы:
- dibAndroid ? bmp – Gdiplus::Bitmap, что рисуем;
- RectF(AndroidShadowSize, AndroidShadowSize, (REAL)AndroidSize – AndroidShadowSize, (REAL)AndroidSize – AndroidShadowSize) – это Gdiplus::RectF, куда рисуем, т.е. координаты x и y, а за ними следуют ширина и высота. О константах поговорим позже;
- 0, 0, (REAL)dibAndroid ? Width(), (REAL)dibAndroid ? Height() – далее 4 параметра, а именно x, y, ширина и высота части изображения dibAndroid->bmp, которое будет взято для рисования на контексте g;
- UnitPixel – это формат значений, которые используются в параметрах вызова метода DrawImage.
- Делаем изображение черным, тень будет черной:
DIB_ARGB *p = dib->scan0;
int size = dib->Width() * dib->Height();
for(int i = 0; i < size; i++, p++)
{
p->r = 0;
p->g = 0;
p->b = 0;
}Здесь вообще все просто: получили указатель scan0 на первый пиксель, рассчитали общее количество пикселей в нашем dib и в цикле каждому пикселю присвоили черный цвет RGB, не затрагивая альфа-составляющую.
- Применяем эффект Blur для создания тени:
dib->Blur(dib->Rect(), CRect(0, AndroidShadowSize, 0, 0), AndroidShadowSize);
Вызов прост, 1-й параметр CRect – рабочая область (по которой будет проходить размытие), 2-й параметр CRect – область смещения от краев изображения, которая будет игнорироваться для размытия, далее радиус размытия.
- Применяем эффект AlphaBlend для задания тени большей прозрачности:
dib->AlphaBlend(dib->Rect(), 160, DrawFlagsPaint);
Здесь 3 параметра: 1-й – область, к которой будет применен эффект (в нашем случае вся область), далее 2-й параметр в диапазоне от 0 до 255 (уровень прозрачности) и 3-й параметр – константа. Так как мы уже рисовали с помощью GDI+, используем DrawFlagsPaint.
- Рисуем оригинальную иконку поверх полученного изображения:
g.SetCompositingMode(CompositingModeSourceOver);
g.DrawImage(dibAndroid->bmp, RectF(AndroidShadowSize / 2, 0, AndroidSize, AndroidSize), 0, 0, (REAL)dibAndroid->Width(), (REAL)dibAndroid->Height(), UnitPixel);Мы уже знакомы с данными методами, остановимся лишь на новом значении CompositingModeSourceOver, которое отвечает за то, что иконка будет нарисована поверх уже имеющегося на нашем dib.
Итак, в итоге у нас готовый dib, содержащий нужное нам изображение. Теперь дело за малым: отобразить dib на нашем окне.
Для этого используется определенный нами метод void LayerUpdate(CDIB *dib):
{
return;
}
CRect rect;
GetWindowRect(&rect);
CWnd *wndDst = GetDesktopWindow();
CDC *hdcDst = wndDst->GetDC();
CDC *dc = new CDC();
dc->Attach(dib->dc);
BLENDFUNCTION blend = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
CPoint zp(0, 0);
CPoint dstPt(rect.left, rect.top);
CSize dstSize(rect.Width(), rect.Height());
UpdateLayeredWindow(hdcDst, &dstPt, &dstSize, dc, &zp, NULL,
&blend, ULW_ALPHA);
dc->Detach();
delete dc;
wndDst->ReleaseDC(hdcDst);
Для начала формирования нужных переменных необходимо определить размер и положения нашего окна. Воспользуемся API функцией GetWindowRect, сохраним результат в переменную rect. Сразу могу сказать, что данный метод в основном сводит все к MFC.
Перевести на чистый Win API, я думаю, никому не составит труда.
Теперь необходимы два CDC*: 1-й – экран, 2-й – наш dib/изображение окна. Получим окно экрана CWnd *wndDst = GetDesktopWindow();, а затем и его CDC*, CDC *hdcDst = wndDst ? GetDC(). Так как у нас в CDIB есть только HDC, создадим новый CDC* – CDC *dc = new CDC(). И свяжем его с нашим HDC dc ? Attach(dib ? dc);
Теперь необходимо подготовить структуру, описывающую детали отображения:
Здесь мы видим также 255, это значение прозрачности нашего изображения на окне. Сразу скажу: менять его нежелательно, намного лучше перерисовать само изображение, так как (по моим наблюдениям) происходит уменьшение производительности при изменении данного параметра.
Далее сформируем позиции и размеры области обновления окна:
CPoint dstPt(rect.left, rect.top);
CSize dstSize(rect.Width(), rect.Height());
А именно: Zero Point – указатель на левый верхний угол окна и его размеры в данный момент. Теперь мы готовы обновить окно, вызовем Win API функцию:
В конце, обычная ситуация для каждого из нас (попользовались ресурсами – пришло время освободить их):
delete dc;
wndDst->ReleaseDC(hdcDst);
Заключение
Я опустил детали создания объектов CDIB и окна. Если интересно, можете попробовать в прилагающихся материалах. Насчет констант AndroidSize и AndroidShadowSize: это просто размеры иконки исходной и размер тени соответственно. Сначала код кажется громоздким, но в итоге все компактно и процесс формирования нового splashscreen’а или виджета превращается в удовольствие, когда смотришь на уже «живой» результат на экране монитора.
Надеюсь, пример и статья помогут вам понять основы создания Layered окон в Windows. Всего наилучшего и успехов в практике!
Все упомянутые в статье модули приведены в виде ресурсов в теме «Журнал клуба программистов. Седьмой выпуск» или непосредственно в архиве с журналом.
Статья из седьмого выпуска журнала «ПРОграммист».
Обсудить на форуме — Layered в windows. Использование слоев
Похожие статьи
Купить рекламу на сайте за 1000 руб
пишите сюда - alarforum@yandex.ru
Да и по любым другим вопросам пишите на почту
пеллетные котлы
Пеллетный котел Emtas
Наши форумы по программированию:
- Форум Web программирование (веб)
- Delphi форумы
- Форумы C (Си)
- Форум .NET Frameworks (точка нет фреймворки)
- Форум Java (джава)
- Форум низкоуровневое программирование
- Форум VBA (вба)
- Форум OpenGL
- Форум DirectX
- Форум CAD проектирование
- Форум по операционным системам
- Форум Software (Софт)
- Форум Hardware (Компьютерное железо)