PROWAREtech

articles » current » windows » application-programming-interface » directx-11-screen-saver

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:

  1. Add an image file (like a JPEG) to the project's directory.
  2. Add the image file (a JPEG in this example) to the project's Resource Files.
  3. 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
  4. Call LoadBitmapFromResource():
    hr = LoadBitmapFromResource(pRenderTarget, pWICFactory, MAKEINTRESOURCE(991133), RT_RCDATA, 0, 0, &pBitmap);

This site uses cookies. Cookies are simple text files stored on the user's computer. They are used for adding features and security to this site. Read the privacy policy.
CLOSE