PROWAREtech
Windows API: DirectX 11 (Direct2D) Screen Saver Example
A Windows API screen saver example using DirectX 11 that displays the images in the Pictures folder; written in C++.
Here is another basic example of a Windows screen saver written in C/C++ and the Windows API, specifically, the DirectX API to speed up the drawing operations considerably when compared to the ordinary GDI version. It spans all monitors with a single window. Note: the executable should have a .SCR file extension to properly be recognized as a screen saver by the operating system.
Excuse the spaghetti code.
// *********************************************************************
// * *
// * EXAMPLE DIRECTX SCREEN SAVER APP *
// * *
// * ROTATE THROUGH THE FIRST 25 IMAGES IN THE USER'S PICTURES FOLDER. *
// * *
// * SAVE THE EXECUTABLE WITH .SCR EXTENSION. *
// * RUN THE EXECUTABLE WITH /s PARAMETER. *
// * *
// *********************************************************************
#ifndef UNICODE
#define UNICODE
#endif
// These "#pragma comment" lines only work in Visual Studio; otherwise include these libraries with the linker.
#pragma comment(lib, "d2d1") // This is the DirectX 11 2D library.
#pragma comment(lib, "dwrite.lib")
#pragma comment(lib, "windowscodecs.lib")
#pragma comment(lib, "shell32.lib")
#pragma comment(lib, "comctl32.lib")
#ifdef UNICODE
#pragma comment(lib, "ScrnSavw.lib")
#else
#pragma comment(lib, "ScrnSave.lib")
#endif
#include <windows.h>
#include <d2d1.h>
#include <dwrite.h>
#include <wincodec.h>
#include <shlobj.h>
#include <strsafe.h>
// include the special screen saver header.
#include <scrnsave.h>
#define MAX_IMAGES 25
WCHAR images[MAX_IMAGES][MAX_PATH + 1];
int imageIndex = 0;
BOOL WINAPI RegisterDialogClasses(HANDLE hInst)
{
return TRUE;
}
BOOL WINAPI ScreenSaverConfigureDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
return FALSE;
}
template<class iface>
void Release(iface** ppInterface)
{
if (*ppInterface)
{
(*ppInterface)->Release();
(*ppInterface) = NULL;
}
}
// ******************************************************************
// * *
// * The ScreenSaverApp *
// * *
// ******************************************************************
// COVER the window while not distorting image
#define IM_COVER 1
// CONTAIN in window while not distorting image - usually results in black bars
#define IM_CONTAIN 2
// STRETCH to cover window - distorts image
#define IM_STRETCH 3
class ScreenSaverApp
{
HWND hwnd;
ID2D1Factory* pD2DFactory;
IWICImagingFactory* pWICFactory;
IDWriteFactory* pDWriteFactory;
ID2D1HwndRenderTarget* pRenderTarget;
IDWriteTextFormat* pTextFormat;
ID2D1SolidColorBrush* pRedBrush;
FLOAT fontSize;
int imageMode;
HRESULT LoadBitmapFromFile(ID2D1RenderTarget* pRenderTarget, IWICImagingFactory* pIWICFactory, PCWSTR uri, UINT destinationWidth, UINT destinationHeight, ID2D1Bitmap** ppBitmap);
HRESULT LoadBitmapFromResource(ID2D1RenderTarget* pRenderTarget, IWICImagingFactory* pIWICFactory, PCWSTR resourceName, PCWSTR resourceType, UINT destinationWidth, UINT destinationHeight, ID2D1Bitmap** ppBitmap);
public:
// Initialize members.
ScreenSaverApp() : hwnd(NULL), pD2DFactory(NULL), pWICFactory(NULL), pDWriteFactory(NULL), pRenderTarget(NULL), pTextFormat(NULL), pRedBrush(NULL), fontSize(0.0f), imageMode(IM_COVER) {}
// Release resources.
~ScreenSaverApp()
{
Release(&pD2DFactory);
Release(&pWICFactory);
Release(&pDWriteFactory);
Release(&pRenderTarget);
Release(&pTextFormat);
Release(&pRedBrush);
}
HRESULT OnPaint(HWND hwnd);
HRESULT CreateDeviceIndependentResources(HWND hwnd);
};
// The ScreenSaverApp instance
ScreenSaverApp app;
// Create resources not bound to any device.
// Their lifetime is for the duration of the app.
HRESULT ScreenSaverApp::CreateDeviceIndependentResources(HWND hwnd)
{
static const WCHAR fontName[] = L"Verdana";
HRESULT hr;
RECT rc;
GetClientRect(hwnd, &rc);
fontSize = (rc.bottom - rc.top) / 8.0f;
// Create a Direct2D factory.
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pD2DFactory);
if (SUCCEEDED(hr))
{
// Create Windows Imaging Component factory.
hr = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IWICImagingFactory, (void**)(&pWICFactory));
}
if (SUCCEEDED(hr))
{
// Create a DirectWrite factory.
hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(pDWriteFactory), (IUnknown**)(&pDWriteFactory));
}
if (SUCCEEDED(hr))
{
// Create a DirectWrite text format object.
hr = pDWriteFactory->CreateTextFormat(fontName, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, fontSize, L"", &pTextFormat);
}
if (SUCCEEDED(hr))
{
// Center the text horizontally and vertically.
pTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);
pTextFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
}
return hr;
}
// Called whenever the application needs to display the client
// window. This method draws the current bitmap, and it will
// not render anything if the window is occluded; when the
// screen is locked for example.
HRESULT ScreenSaverApp::OnPaint(HWND hwnd)
{
HRESULT hr = S_OK;
if (!pRenderTarget)
{
RECT rc;
GetClientRect(hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top);
// Create a Direct2D render target.
hr = pD2DFactory->CreateHwndRenderTarget(D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(hwnd, size), &pRenderTarget);
if (SUCCEEDED(hr))
{
// Create a black brush.
hr = pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Red), &pRedBrush);
}
}
if (SUCCEEDED(hr) && !(pRenderTarget->CheckWindowState() & D2D1_WINDOW_STATE_OCCLUDED)) // don't draw if window is occluded
{
if (images[imageIndex][0])
{
ID2D1Bitmap* pBitmap = NULL;
// Create a bitmap by loading it from a file.
hr = LoadBitmapFromFile(pRenderTarget, pWICFactory, images[imageIndex], 0, 0, &pBitmap); // 0 width and 0 height will load the full size image
if (SUCCEEDED(hr))
{
pRenderTarget->BeginDraw();
pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
FLOAT left = 0.0f, top = 0.0f, right = 0.0f, bottom = 0.0f;
// Retrieve the size of the render target.
D2D1_SIZE_F windowSize = pRenderTarget->GetSize();
if (IM_COVER == imageMode)
{
// Retrieve the size of the bitmap.
D2D1_SIZE_F bmSize = pBitmap->GetSize();
FLOAT percentWidth = windowSize.width / bmSize.width;
FLOAT percentHeight = windowSize.height / bmSize.height;
FLOAT percent = percentHeight > percentWidth ? percentHeight : percentWidth;
right = bmSize.width * percent;
right += (windowSize.width - right) / 2;
bottom = bmSize.height * percent;
bottom += (windowSize.height - bottom) / 2;
left = windowSize.width - right;
top = windowSize.height - bottom;
}
else if (IM_CONTAIN == imageMode)
{
// Retrieve the size of the bitmap.
D2D1_SIZE_F bmSize = pBitmap->GetSize();
FLOAT percentWidth = windowSize.width / bmSize.width;
FLOAT percentHeight = windowSize.height / bmSize.height;
FLOAT percent = windowSize.width > windowSize.height ? percentHeight : percentWidth;
right = bmSize.width * percent;
right += (windowSize.width - right) / 2;
bottom = bmSize.height * percent;
bottom += (windowSize.height - bottom) / 2;
left = windowSize.width - right;
top = windowSize.height - bottom;
pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::Black));
}
else if(IM_STRETCH == imageMode)
{
right = windowSize.width;
bottom = windowSize.height;
left = top = 0.0f;
}
// Draw the bitmap.
pRenderTarget->DrawBitmap(pBitmap, D2D1::RectF(left, top, right, bottom));
Release(&pBitmap);
}
}
else
{
// Retrieve the size of the render target.
D2D1_SIZE_F windowSize = pRenderTarget->GetSize();
pRenderTarget->BeginDraw();
pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::Black));
pRenderTarget->SetTransform(D2D1::Matrix3x2F::Rotation(0, D2D1::Point2F(windowSize.width / 2, windowSize.height / 2)));
pRenderTarget->DrawText(L"Add pictures to", 15, pTextFormat, D2D1::RectF(0, -fontSize, windowSize.width, windowSize.height), pRedBrush);
pRenderTarget->DrawText(L"pictures folder", 15, pTextFormat, D2D1::RectF(0, fontSize, windowSize.width, windowSize.height), pRedBrush);
}
if (SUCCEEDED(hr))
hr = pRenderTarget->EndDraw();
if (hr == D2DERR_RECREATE_TARGET)
{
hr = S_OK;
Release(&pRenderTarget);
Release(&pRedBrush);
}
}
return hr;
}
// Creates a Direct2D bitmap from the specified file name.
HRESULT ScreenSaverApp::LoadBitmapFromFile(ID2D1RenderTarget* pRenderTarget, IWICImagingFactory* pIWICFactory, PCWSTR fileName, UINT destinationWidth, UINT destinationHeight, ID2D1Bitmap** ppBitmap)
{
HRESULT hr = S_OK;
IWICBitmapDecoder* pDecoder = NULL;
IWICBitmapFrameDecode* pSource = NULL;
IWICStream* pStream = NULL;
IWICFormatConverter* pConverter = NULL;
IWICBitmapScaler* pScaler = NULL;
hr = pIWICFactory->CreateDecoderFromFilename(fileName, NULL, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &pDecoder);
if (SUCCEEDED(hr))
{
// Create initial frame.
hr = pDecoder->GetFrame(0, &pSource);
}
if (SUCCEEDED(hr))
{
// Convert image format to 32bppPBGRA which is DXGI_FORMAT_B8G8R8A8_UNORM + D2D1_ALPHA_MODE_PREMULTIPLIED.
hr = pIWICFactory->CreateFormatConverter(&pConverter);
}
if (SUCCEEDED(hr))
{
// If a new width or height was specified, create an
// IWICBitmapScaler and use it to resize the image.
if (destinationWidth || destinationHeight)
{
UINT originalWidth, originalHeight;
hr = pSource->GetSize(&originalWidth, &originalHeight);
if (SUCCEEDED(hr))
{
if (!destinationWidth)
{
FLOAT scalar = destinationHeight / (FLOAT)originalHeight;
destinationWidth = scalar * (FLOAT)originalWidth;
}
else if (!destinationHeight)
{
FLOAT scalar = destinationWidth / (FLOAT)originalWidth;
destinationHeight = scalar * (FLOAT)originalHeight;
}
hr = pIWICFactory->CreateBitmapScaler(&pScaler);
if (SUCCEEDED(hr))
hr = pScaler->Initialize(pSource, destinationWidth, destinationHeight, WICBitmapInterpolationModeCubic);
if (SUCCEEDED(hr))
hr = pConverter->Initialize(pScaler, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.0f, WICBitmapPaletteTypeMedianCut);
}
}
else // Don't scale the image.
hr = pConverter->Initialize(pSource, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.0f, WICBitmapPaletteTypeMedianCut);
}
if (SUCCEEDED(hr))
{
// Create a Direct2D bitmap from the "Windows Imaging Component" bitmap.
hr = pRenderTarget->CreateBitmapFromWicBitmap(pConverter, NULL, ppBitmap);
}
Release(&pDecoder);
Release(&pSource);
Release(&pStream);
Release(&pConverter);
Release(&pScaler);
return hr;
}
// Creates a Direct2D bitmap from a resource in the application's resource file.
// If resourceName is an identifier then it should be passed with MAKEINTRESOURCE(IDENTIFIER).
// The resourceType should be RT_BITMAP or RT_RCDATA
HRESULT ScreenSaverApp::LoadBitmapFromResource(ID2D1RenderTarget* pRenderTarget, IWICImagingFactory* pIWICFactory, PCWSTR resourceName, PCWSTR resourceType, UINT destinationWidth, UINT destinationHeight, ID2D1Bitmap** ppBitmap)
{
HRESULT hr = S_OK;
IWICBitmapDecoder* pDecoder = NULL;
IWICBitmapFrameDecode* pSource = NULL;
IWICStream* pStream = NULL;
IWICFormatConverter* pConverter = NULL;
IWICBitmapScaler* pScaler = NULL;
HRSRC imageResHandle = NULL;
HGLOBAL imageResDataHandle = NULL;
void* pImageFile = NULL;
DWORD imageFileSize = 0;
// Get the Instance Handle for the screen saver executable.
HINSTANCE hInstance = ::GetModuleHandle(NULL);
// Locate the resource.
imageResHandle = FindResource(hInstance, resourceName, resourceType);
hr = imageResHandle ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
// Load the resource.
imageResDataHandle = LoadResource(hInstance, imageResHandle);
hr = imageResDataHandle ? S_OK : E_FAIL;
}
if (SUCCEEDED(hr))
{
// Lock it to get a system memory pointer.
pImageFile = LockResource(imageResDataHandle);
hr = pImageFile ? S_OK : E_FAIL;
}
if (SUCCEEDED(hr))
{
// Calculate the size of the resource.
imageFileSize = SizeofResource(hInstance, imageResHandle);
hr = imageFileSize ? S_OK : E_FAIL;
}
if (SUCCEEDED(hr))
{
// Create a "Windows Imaging Component" stream to map into memory.
hr = pIWICFactory->CreateStream(&pStream);
}
if (SUCCEEDED(hr))
{
// Initialize the stream with the memory pointer and size.
hr = pStream->InitializeFromMemory((BYTE*)pImageFile, imageFileSize);
}
if (SUCCEEDED(hr))
{
// Create a decoder for the stream.
hr = pIWICFactory->CreateDecoderFromStream(pStream, NULL, WICDecodeMetadataCacheOnLoad, &pDecoder);
}
if (SUCCEEDED(hr))
{
// Create the initial frame.
hr = pDecoder->GetFrame(0, &pSource);
}
if (SUCCEEDED(hr))
{
// Convert the image format to 32bppPBGRA which is DXGI_FORMAT_B8G8R8A8_UNORM + D2D1_ALPHA_MODE_PREMULTIPLIED.
hr = pIWICFactory->CreateFormatConverter(&pConverter);
}
if (SUCCEEDED(hr))
{
// If a new width or height was specified, create an IWICBitmapScaler and use it to resize the image.
if (destinationWidth || destinationHeight)
{
UINT originalWidth, originalHeight;
hr = pSource->GetSize(&originalWidth, &originalHeight);
if (SUCCEEDED(hr))
{
if (!destinationWidth)
{
FLOAT scalar = destinationHeight / (FLOAT)originalHeight;
destinationWidth = (UINT)(scalar * originalWidth);
}
else if (!destinationHeight)
{
FLOAT scalar = destinationWidth / (FLOAT)originalWidth;
destinationHeight = (UINT)(scalar * originalHeight);
}
hr = pIWICFactory->CreateBitmapScaler(&pScaler);
if (SUCCEEDED(hr))
{
hr = pScaler->Initialize(pSource, destinationWidth, destinationHeight, WICBitmapInterpolationModeCubic);
if (SUCCEEDED(hr))
hr = pConverter->Initialize(pScaler, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.0f, WICBitmapPaletteTypeMedianCut);
}
}
}
else
hr = pConverter->Initialize(pSource, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.0f, WICBitmapPaletteTypeMedianCut);
}
// Create a Direct2D bitmap from the "Windows Imaging Component" bitmap.
if (SUCCEEDED(hr))
hr = pRenderTarget->CreateBitmapFromWicBitmap(pConverter, NULL, ppBitmap);
Release(&pDecoder);
Release(&pSource);
Release(&pStream);
Release(&pConverter);
Release(&pScaler);
return hr;
}
// handle screen saver window messages; most are already handled by "DefScreenSaverProc"
LRESULT WINAPI ScreenSaverProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_TIMER:
{
imageIndex++;
if (MAX_IMAGES == imageIndex || !images[imageIndex][0])
imageIndex = 0;
RECT rect;
GetClientRect(hwnd, &rect);
InvalidateRect(hwnd, &rect, FALSE);
}
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
app.OnPaint(hwnd);
EndPaint(hwnd, &ps);
}
return 0;
case WM_CREATE:
{
if (FAILED(CoInitialize(NULL)))
return -1;
// Initialize device-indpendent resources
if (FAILED(app.CreateDeviceIndependentResources(hwnd)))
return -1;
// get images to display from the user's pictures folder
ZeroMemory(images, sizeof(images));
imageIndex = 0;
WCHAR myPics[MAX_PATH + 1];
if (FAILED(SHGetFolderPath(NULL, CSIDL_MYPICTURES, NULL, SHGFP_TYPE_CURRENT, myPics)))
return 0;
StringCchCat(myPics, MAX_PATH, L"\\*");
WIN32_FIND_DATA fd;
HANDLE hFind;
if ((hFind = FindFirstFile(myPics, &fd)) != INVALID_HANDLE_VALUE)
{
int i = 0;
do
{
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
// TODO: search this directory for more images
}
else if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN || fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM))
{
if (FAILED(SHGetFolderPath(NULL, CSIDL_MYPICTURES, NULL, SHGFP_TYPE_CURRENT, images[i])))
return 0;
StringCchCat(images[i], MAX_PATH, L"\\");
StringCchCat(images[i], MAX_PATH, fd.cFileName);
i++;
}
} while (i < MAX_IMAGES && FindNextFile(hFind, &fd) != 0);
}
SetTimer(hwnd, 1, 2000, NULL); // 2000ms
}
return 0;
case WM_DESTROY:
KillTimer(hwnd, 1);
return 0;
}
return DefScreenSaverProc(hwnd, msg, wParam, lParam);
}
Using LoadBitmapFromResource()
How to add to and use an image from the Visual Studio project resources:
- Add an image file (like a JPEG) to the project's directory.
- Add the image file (a JPEG in this example) to the project's Resource Files.
-
Modify .rc (resource) file with the Visual Studio C++ text editor. Add this line of code:
991133 RCDATA "FILENAME.JPG" // replace 991133 with any unused number
-
Call
LoadBitmapFromResource()
:hr = LoadBitmapFromResource(pRenderTarget, pWICFactory, MAKEINTRESOURCE(991133), RT_RCDATA, 0, 0, &pBitmap);
Comment