Samstag, 3. November 2012

Working with GDI+ - Part 2

Welcome back!

In the last part we have seen how to initialize GDI+ and draw some basic shapes. In this part we wanna draw some images to the window! I have chosen an image from a site where you can get free images:
http://www.freedigitalphotos.net/images/Blues_and_Violets_g333-Blue_Wallpaper_p59652.html

Drawing images is a very simple task. You have to do the following steps:
1. Create a Gdiplus::Image object
2. Draw it

You see, nothing complex here!

As in the previous part I'm loading my image in the startup function and I dispose of it in the shutdown function. And then i draw it in the draw function. To load you can use one Image::FromFile or Image::FromStream. To emulate a Image::FromMemory you can either implement your own IStream based on a MemoryStream or you can copy it to a HGLOBAL allocated large enough and then use the CreateStreamOnHGlobal function.

Gdiplus::Image* backgroundImage = nullptr;

void startupGDIPlus(ULONG_PTR* pToken) {
	// code omitted to start Gdiplus

	backgroundImage = Gdiplus::Image::FromFile(L"ID-10059652.jpg");
	status = backgroundImage->GetLastStatus();
}

void shutdownGDIPlus(ULONG_PTR token) {
	delete backgroundImage;
	Gdiplus::GdiplusShutdown(token);
}

void drawGDI(HDC hDc) {
	Gdiplus::Graphics* g = Gdiplus::Graphics::FromHDC(hDc);
	g->SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);

	g->DrawImage(backgroundImage, 10, 10);

	g->Flush();
	delete g;
}

For DrawImage you have a lot of possible parameters. In general they can be grouped the following way:

  1. Parameters with "src" refer to the portion of the image you want to draw. They allow you to select only a part of the image
  2. Parameters that are also position or size related but with no "src"-prefix refer to the part of the window that the portion selected with "src" should be drawn to. The "src"-portion is stretched to fit into this area.
  3. The GraphicsUnit parameter lets you choose how the previous two parameter groups should be measured. Usually (and default) it uses Pixels.
  4. The ImageAttributes can define more sophisticated operations on the image. We will have a look on them later.
Now lets modify our image a bit. In a first step Id like to add a transform to the image that rotates it 45° clock wise. Thus i create a Gdiplus::Matrix for identity (no transform) and one for rotation and before i draw the image I call SetTransform to apply the transform to the next drawing calls.

Gdiplus::Matrix* matrixIdentity = nullptr;
Gdiplus::Matrix* matrixRotation = nullptr;

void startupGDIPlus(ULONG_PTR* pToken) {
	// previous code omitted

	matrixIdentity = new Gdiplus::Matrix();
	matrixRotation = new Gdiplus::Matrix();
	matrixRotation->RotateAt(45.0f, Gdiplus::PointF(210, 210));
}

void drawGDI(HDC hDc) {
	Gdiplus::Graphics* g = Gdiplus::Graphics::FromHDC(hDc);
	g->SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);

	g->SetTransform(matrixRotation);
	g->DrawImage(backgroundImage, 10, 10);
	g->SetTransform(matrixIdentity);

	g->Flush();
	delete g;
}

The first question I already see: Why RotateAt with 210/210?! Now thats just math! The behavior of a rotation matrix is to rotate around the origin which is the top left corner of the window in my case. So that means our whole image gets rotate around the top left corner of the window. That is not what we want (or at least not what I want). The rotation should be at the center of the image. My image is 400x400 pixels so its center is at 200/200 and I have to add the offset 10/10 because I draw the image at 10/10. Now i created a matrix that rotates around the center of the image. You get a result like that:

A common request is to draw images that have an alpha channel and contain transparency. GDI+ supports transparency without any further adjustments needed. So I created a PNG of my image and set the color of the border to be transparent. Its not optimal because the border changes color a bit but you see clearly that it works.

There are transparent parts as well as semi transparent parts.

You can obtain very cool effects with the ImageAttributes parameter. You crate an ImageAttribute like any other object in GDI+. Then you can add several effects to the object. The first one I like to explain is the SetOutputChannel-function. It allows you to specify one of the 4 channels in CMYK (cyan, magenta, yellow, black) that is used to calculate a grayscale picture where the shade of gray equals the intensity of that channel. An example:
imageAttribs->SetOutputChannel(Gdiplus::ColorChannelFlagsM);

and the result:

The second parameter id like to explain is the color matrix. The color matrix is a 5x5 matrix that gets applied to the color of the image. It is 5x5 because it uses homogeneous coordinates and a color vector has 4 components (r, g, b, a) in that order. So lets start with the identity matrix:

	Gdiplus::ColorMatrix clrMatrix = {
		{
			{ 1, 0, 0, 0, 0 },
			{ 0, 1, 0, 0, 0 },
			{ 0, 0, 1, 0, 0 },
			{ 0, 0, 0, 1, 0 },
			{ 0, 0, 0, 0, 1 }
		}
	};

	imageAttribs->SetColorMatrix(&clrMatrix);

As expected there is no effect on the image, because its the identity matrix. To understand which component is creating which result you have to understand how matrix multiplication works. I try to visualize it a bit more:

{ a, b, c, d, e }      
{ f, g, h, i, j }      
{ k, l, m, n, o } * { red, green, blue, alpha, 1 } 
{ p, q, r, s, t }   
{ u, v, w, x, y }

  { a * red + f * green + k * blue + p * alpha + u }
  { b * red + g * green + l * blue + q * alpha + v }
= { c * red + h * green + m * blue + r * alpha + w }
  { d * red + i * green + n * blue + s * alpha + x }
  { e * red + j * green + o * blue + t * alpha + y }

From the fact that the last component must be 1 its clear that e j and o must always be 0 and y must be 1. It also gets clear that the center diagonal is used for scaling the color. So lets try the following matrix:

			{ 1, 0, 0, 0, 0 },
			{ 0, 0, 0, 0, 0 },
			{ 0, 0, 0, 0, 0 },
			{ 0, 0, 0, 1, 0 },
			{ 0, 0, 0, 0, 1 }

What do we expect? For the first component we only have 1 * red because all other are 0, for the second component we have 0 because all matrix elements on the second row are 0, same for the third. So the resulting color should be: (red, 0, 0, alpha). Thats the output:

As expected, only the red channel was shown. Success! If we take a look at the matrix equation we see that u, v and w can be used to add something to a color component. Colors are in range 0 to 1 so lets intensify our red channel a bit:

			{ 1, 0, 0, 0, 0 },
			{ 0, 1, 0, 0, 0 },
			{ 0, 0, 1, 0, 0 },
			{ 0, 0, 0, 1, 0 },
			{ 0.5f, 0, 0, 0, 1 }

So this should add 0.5 to the red channel (dont worry, if its bigger than 1 it just stays white, GDI+ clamps the color). The result:

Now lets think of something else: Lets have a look at the red component of the resulting color. Its calculated the following way:

finalRed = { a * inputRed + f * inputGreen + k * inputBlue + p * inputAlpha + u }

Now what if we set a to 0 and k to 1 and f to 0. We get 0 * inputRed + 0 * inputGreen + 1 * inputBlue. So we map the blue channel also to the red channel. So lets have a look at the following matrix:

			{ 1, 0, 0, 0, 0 },
			{ 0, 1, 0, 0, 0 },
			{ 0.5f, 0, 0.5f, 0, 0 },
			{ 0, 0, 0, 1, 0 },
			{ 0, 0, 0, 0, 1 }

Interesting is the third line, it stands for the blue channel. What we do is taking the average of the red and blue channel together as the blue channel. We get a very interesting result:

Well thats it for the image part. You can explore the other functions of the ImageAttribute pretty easy.

See you soon in the next part, text rendering!

Greetings
Yanick

Keine Kommentare:

Kommentar veröffentlichen