65.9K
CodeProject 正在变化。 阅读更多。
Home

GDI+ 画笔和矩阵

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.72/5 (9投票s)

2001年5月13日

9分钟阅读

viewsIcon

388708

downloadIcon

2402

使用 GDI+ 绘制实心/渐变填充和纹理形状

Sample Image - GDIPlusBrushes.jpg

引言

欢迎阅读我的第二篇关于 GDI+ 的文章。本教程假设您已了解第一篇文章中的所有内容,即您知道在哪里可以获取 GDI+、如何设置它、如何创建一个正确初始化和关闭它的项目,以及 Color 类是如何工作的。

好的,您还在。本文将介绍五种画刷类型中的三种(SolidBrushLinearGradientBrushTextureBrush),PathGradientBrush 更适合下一篇关于使用画笔和路径的教程,而 HatchBrush 主要被排除在外,因为它并不有趣,而且一旦您完成了本教程,它就非常容易理解。

在介绍任何画刷之前,我想先介绍将在第一个示例的画布上以及其他两个示例的画刷上使用的三个方法。

Status RotateTransform(
  REAL angle,
  MatrixOrder order
)

Status ScaleTransform(
  REAL sx,
  REAL sy,
  MatrixOrder order
)

Status TranslateTransform(
  REAL dx,
  REAL dy,
  MatrixOrder order
)

首先,我需要解释一下 REALfloat 的一个类型定义。MatrixOrder 更有趣一些,它是一个常量,可能的值是 MatrixOrderPrepend(默认)或 MatrixOrderAppend

任何熟悉 3D 图形的人都会认出这些方法——Rotate 显然会旋转一个对象,Scale 允许我们调整对象的大小,Translate 移动一个对象。实际上,更准确地说,是我们的参考点被修改了,这反映在后续的绘图操作中。对于 3D 工作过的人来说,MatrixOrder 的原因也会很明显。想象一下我要做一个旋转和一个平移。如果我先旋转,我的对象认为是“向上”的方向就会改变,所以我的平移方向会受到影响。如果我旋转 45 度并应用一个 (100, 0) 的平移,我将在对角线上移动 100 个单位,但如果我颠倒顺序,我将向右硬移动然后旋转。示例程序允许您尝试操作顺序并查看结果的差异。在“现实生活中”,您可以定义两种每种类型的操作,一种是前置,一种是追加。还可以创建一个矩阵,对其执行操作,然后将其传递给一个对象。

那么,来看看我们的第一个画刷。它是 SolidBrush,它有一个空的构造函数,还有一个以 COLOR 作为其参数的构造函数。这个画刷大致相当于 CBrush——它唯一的方法是 GetColorSetColor。例如,要绘制一个实心红色矩形,您可以这样做:

SolidBrush brush(Color(255,255,0,0));
graphics.FillRectangle(&brush, 300, 300, 100, 50);  
   // Note the last two parameters are Width and Height, NOT X2/Y2 
由于此类使用纯色,因此不受益于任何类型的矩阵操作,因此在使用实心画刷绘图时,这些操作会在演示程序中的画布上执行。使用的方法如下:
graphics.ScaleTransform(m_ScaleX, m_ScaleY, m_AScale ? 
	MatrixOrderPrepend : MatrixOrderAppend);
				
graphics.TranslateTransform(m_TransX, m_TransY, m_ATrans ? 
	MatrixOrderPrepend : MatrixOrderAppend);

graphics.RotateTransform(m_Rotate, m_ARotate ? 
	MatrixOrderPrepend : MatrixOrderAppend);
请注意,如果您在自己的代码中使用其他类型的画刷,这些操作也可以在画布上执行。

下一个类更有趣:LinearGradientBrush。它有七个构造函数,可以让我们预设许多选项,所有这些选项都可以通过 Set 方法在之后进行访问。所有这些方法最终都做同一件事:设置一个有两个已知颜色(有时还有渐变模式)的画刷。渐变模式可以设置为以下之一:

LinearGradientModeVertical
LinearGradientModeHorizontal
LinearGradientModeBackwardDiagonal
LinearGradientModeForwardDiagonal

还有一个采用渐变角度的构造函数,我认为这更为灵活。这是我们在演示程序中使用的构造函数,如下所示:

LinearGradientBrush brush(rc,            // Rect of gradient
	Color(m_Alpha1, m_Red1, m_Green1, m_Blue1), // First colour
	Color(m_Alpha2, m_Red2, m_Green2, m_Blue2), // Second colour
	m_Rotate,                        // Angle of gradient
	TRUE);                           // Is angle Scalable

如您所见,有两个变量尚未解释。第一个是一个 rect,它定义了渐变的大小。通过将其与您正在绘制的对象的 Rect 不同,您可以使渐变重复。通过将 scale 变量修改为 0 到 1 之间的数字,可以达到相同的效果。最后的 BOOL 的名称是 isAngleScalable

您还可以输入到示例程序中的另一个变量是 Blend Focus,一个介于 0 和 1 之间的值。该值指定了在填充过程中,渐变达到 50% 渐变的点。这可以通过以下代码实现:

REAL blend1[3] = {0, .5, 1.0};
REAL blend2[3] = {0, m_Focus, 1.0};
brush.SetBlend(blend1, blend2, 3);

这仅仅是这个函数可以做的很酷的事情的一小部分。您可以指定的最小值是三个值,第一个数组指定不同点的百分比,第二个指定沿途的百分比点。您可以看到这是一个强大而灵活的函数,它开启了各种可能性。

此外,您还可以从菜单中将混合类型设置为 normal、bell 或 triangle。在所有情况下,都会使用 focus 变量,如下所示:

case bell:
	brush.SetBlendBellShape(m_Focus);
	break;
case triangle:
	brush.SetBlendTriangularShape(m_Focus);
	break;

如果您在示例程序的这部分中尝试矩阵操作,您会发现您可以做各种有趣的事情。例如,设置一个小于 1 的比例可以创建重复的渐变填充。这些操作在画刷上执行如下:

brush.TranslateTransform(m_TransX, m_TransY, m_ATrans  ? 
	MatrixOrderPrepend : MatrixOrderAppend);
brush.ScaleTransform(m_ScaleX, m_ScaleY, m_AScale ? 
	MatrixOrderPrepend : MatrixOrderAppend);

请注意,可以旋转混合,但这不会产生与在构造函数中指定旋转相同效果,我已经将其省略了,因为在两个位置应用相同的值不能很好地代表其作用。

示例程序还在背景中绘制了一个黑色线条图案,让您看到渐变在 alpha 通道中的效果。LinearGradientBrush 还有许多我没有涵盖的功能,例如设置伽马校正、插值颜色等。

最后一个也是最强大的画刷是 TextureBrush。为了涵盖它,我必须先解释另一个 GDI+ 类——Image。这个类可以从 IStream(一个 COM 接口)或文件路径构造。还有一个类叫做 Bitmap,它派生自 Image,可以从 HBITMAP、surface (DirectDraw)、icon、指向位数据的指针等构造。在示例程序中,我提供了一张我女儿的照片,它被硬编码为位图。这个类可以加载多种图像格式,包括 jpeg、tiff、bmp、png 等。它还支持设置其他编解码器,例如我最终会为 Targa 编写一个,我们在工作中经常使用 Targa。Image 的酷之处在于它也可以以这些格式保存。如果您想从用户指定的 文件加载 Image,您需要将字符串转换为 WCHAR 字符串,如下所示:

// Assuming the string 's' contains our filepath
WCHAR*  filename = new WCHAR[s.GetLength()+1];
mbstowcs(filename, s, s.GetLength()+1);

Image image(filename);
delete filename;

一旦我们初始化了 Image 对象,我们就可以如下创建我们的纹理画刷:

Image image(L"image.jpg");

Rect rct(0, 0, image.GetWidth(), image.GetHeight());

TextureBrush brush(ℑ,rct);

请注意,这与渐变的工作方式类似——如果我们指定的 Rect 与我们绘制的相同,图像将不会缩放到适合,但平铺将不起作用。为了使图像缩放到一个矩形,我们需要使用 TranslateScale

除了缩放、旋转和移动我们的图像之外,我们还可以设置环绕模式,可以通过菜单访问。可能的值是:

WrapModeClamp       (Draws the image at 0,0 at correct size, once only)
WrapModeTile        (Draws the image over and over in all directions)
WrapModeTileFlipX   (As above, but every second image is flipped in the X plane)
WrapModeTileFlipY   (As WrapModeTile, but every second image flipped in the Y plane)
WrapModeTileFlipXY  (As WrapModeTile, flipping both in the X and Y plane 
                     every second tile)

您还会发现,旋转、缩放和移动与上述相结合会产生许多可能性。

提供的示例程序允许您尝试不同的颜色/alpha 值以及所有三个矩阵操作的不同值。希望通过玩这些,您能很好地了解所有这些变量如何组合在一起产生最终结果,特别是当您更改矩阵操作顺序时会发生什么。

还提供了另外两组选项,一组在菜单中,另一组在对话框中。首先,在菜单中,您可以选择平滑模式为以下之一:

graphics.SetSmoothingMode(SmoothingModeHighSpeed);
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetSmoothingMode(SmoothingModeHighQuality);

如果您不熟悉抗锯齿的概念,请想象一下打开画图并绘制一条小于 45 度的倾斜线。您会期望看到一条锯齿状的线,带有明显的跳跃,当屏幕分辨率较低或放大时,这种情况会更加明显。这是由一个显而易见的事实引起的:我们的屏幕具有定义的ied 分辨率,这与我们的显示器尺寸相结合,设定了一个像素大小,即屏幕上的“点”的最小尺寸。抗锯齿尝试通过处理“子像素”来做得更好,并根据我们要绘制的像素中有多少“子像素”来设置像素的 RGB 值的一部分。它对正方形没有影响,所以从形状菜单中选择“椭圆”,然后您就会看到两种模式之间的区别。最后一种模式是高质量,根据帮助文件,这是一种高级抗锯齿形式,专门设计用于利用 LCD 屏幕的功能。我没有 LCD 屏幕,所以它们在我看来是一样的;0)

最后,底部有一组选项,允许您尝试 DrawImage 函数。这个函数有 16 个版本,我们正在使用的是这个原型:

Status DrawImage(
  Image* image,
  const PointF* destPointsF,
  INT count,
  REAL srcxR,
  REAL srcyR,
  REAL srcwidthR,
  REAL srcheightR,
  Unit srcUnit,
  ImageAttributes* imageAttributes,
  DrawImageAbort callback,
  VOID* callbackData)

指定回调的能力使它成为一个非常强大的函数,但我们将限制自己只使用其中的两个选项。首先,如果我们传递三个点,它们将指定一个平行四边形的左上角、右上角和左下角(不是右下角,正如您可能认为的那样)。要看到这一点,只需勾选“Parallelogram”。

您会注意到,您现在看到的是一张不同的图片:它周围是洋红色。如果您勾选 Use Colour Key,洋红色将不会被绘制,并且我们会看到下面的对话框。这是通过设置我们随后传递的 ImageAttributes 对象中的透明颜色来完成的。实际上,我们可以通过指定两种颜色来设置一个范围,如下所示:

Status SetColorKey(
  const Color& colorLow,
  const Color& colorHigh,
  ColorAdjustType type)

“设置颜色”按钮允许您更改颜色,我们将这两个参数都传递为该颜色。如果您愿意,可以替换程序目录中的 image.bmp 来尝试其他图像。

您还可以使用 ImageProperties 对象设置颜色阈值、颜色映射(即,将红色变为蓝色)、伽马(强度)、颜色环绕模式等。它是一个非常强大的工具,当然值得比我在这里给它的更多的关注。它可以与 DrawImage 或 TextureBrush 一起使用,从而可以与其提供的纹理环绕模式/矩阵操作结合使用。

我希望这篇教程能激发您的兴趣,它更深入地探讨了 GDI+ 提供的一些令人兴奋的可能性。除非我因这两篇文章被骂得很惨,否则我将很快发布更多文章,首先是关于使用画笔(也可以进行纹理环绕)和路径对象进行绘制。我可能还会介绍如何创建软画刷(尽管令人沮丧的是,我不得不自己编写,而现在微软却免费提供它们;0)感谢您的阅读——如果您有任何问题,我几乎住在这里,所以我肯定会回答通过电子邮件发送给我的任何问题,或者在这里的评论部分。

© . All rights reserved.