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);

PROWAREtech

Hello there! How can I help you today?
Ask any question

PROWAREtech

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.
ACCEPT REJECT