Windows 2D 绘图 (GDI, GDI+, Direct2D)

GDI

GDI 是 Graphics Device Interface 的缩写,含义是图形设备接口,它的主要任务是负责系统与绘图程序之间的信息交换,处理所有 Windows 程序的图形输出。

在 Windows 操作系统下,绝大多数具备图形界面的应用程序都离不开 GDI,我们利用 GDI 所提供的众多 API 就可以方便的在屏幕、打印机及其它输出设备上输出图形、文本等操作。

GDI 具有如下特点:

  • 不允许程序直接访问物理显示硬件,通过称为“设备环境”的抽象接口间接访问显示硬件
  • 程序需要与显示硬件(显示器、打印机等) 进行通讯时,必须首先获得与特定窗口相关联的设备环境
  • 用户无需关心具体的物理设备类型
  • Windows 参考设备环境的数据结构完成数据的输出

GDI 函数

GDI 函数大致可分类为:

  • 设备上下文函数(如 GetDC、CreateDC、DeleteDC)
  • 画线函数(如 LineTo、Polyline、Arc)
  • 填充画图函数(如 Ellipse、FillRect、Pie)
  • 画图属性函数(如 SetBkColor、SetBkMode、SetTextColor)
  • 文本、字体函数(如 TextOut、GetFontData)
  • 位图函数(如 SetPixel、BitBlt、StretchBlt)
  • 坐标函数(如 DPtoLP、LPtoDP、ScreenToClient、ClientToScreen)
  • 映射函数(如 SetMapMode、SetWindowExtEx、SetViewportExtEx)
  • 元文件函数(如 PlayMetaFile、SetWinMetaFileBits)
  • 区域函数(如 FillRgn、FrameRgn、InvertRgn)
  • 路径函数(如 BeginPath、EndPath、StrokeAndFillPath)
  • 裁剪函数(如 SelectClipRgn、SelectClipPath)

GDI+

GDI+ 是 GDI 的后续版本,最早于 2000 年随 Windows 2000 一起推出,后来又被包装进 .NET 框架的托管类库中,成为 .NET 中窗体绘图的主要工具。

GDI+ 主要提供了以下三类服务:

  • 二维矢量图形:GDI+ 提供了存储图形基元自身信息的类(或结构体)、存储图形基元绘制方式信息的类以及实际进行绘制的类;
  • 图像处理:大多数图片都难以划定为直线和曲线的集合,无法使用二维矢量图形方式进行处理。因此,GDI+ 为我们提供了 Bitmap、Image 等类。它们可用于显示、操作和保存 BMP、JPG、GIF 等图像。
  • 文字显示:GDI+ 支持使用各种字体、字号和样式来显示文本。

GDI 接口是基于函数的,而 GDI+ 是基于 C++ OO 的编程接口,因此使用起来比 GDI 要方便。因为 GDI+ 实际上是 GDI 的封装和扩展,所以执行效率一般要低于 GDI。
GDI+

GDI 和 GDI+ 的区别

GDI 的核心是设备上下文,GDI 函数都依赖于设备上下文句柄,其编程方式是基于句柄的;GDI+ 无需时刻依赖于句柄或设备上下文,用户只需创建一个 Graphics 对象,就可以用面向对象的方式调用其成员函数进行图形操作,编程方式是基于对象的。

GDI 在使用设备上下文绘制线条之前,必须先调用 SelectObject 以使钢笔对象和设备上下文关联。其后,在设备上下文中绘制的所有线条均使用该钢笔,直到选择另一支不同的钢笔为止。GDI 中有当前位置的概念,所以在使用 GDI 绘制线条前应该先使用MoveTo 移动当前位置,再使用 LineTo 画线。

  CPen pen(PS_SOLID, 1, RGB(255, 0, 0));
  dc.SelectObject(pen.GetSafeHandle());
  dc.MoveTo(0, 0);
  dc.LineTo(100, 100);

在 GDI+ 中,只需将 Pen 对象直接作为参数传递给 Graphics 类的 DrawLine 等方法即可,而不必使 Pen 对象与 Graphics 对象关联。且 GDI+ 中则没有当前位置的概念,画线函数中可以直接指定起点和终点。

  Pen myPen(Color::Red);
  graphics.DrawLine(&myPen, 0, 0, rect.right, 0);

GDI+ 新特性

  • 改进了颜色管理
    GDI+ 不仅提供了更多可供选择使用的颜色,使其支持 Alpha 通道合成运算,而且还保持了与其他颜色的兼容性。
  • 绘图支持反锯齿
    通过设置 GDI+ 对象的相关属性,GDI+ 可以与相关的显示驱动程序搭配完成图形绘制时的反锯齿功能,使得绘制的图形更加平滑、美观,而整个过程是由 GDI+ 对象自动计算完成的。
  • 提供渐变画刷
    GDI+ 拓展了 GDI 的功能,提供线性渐变和路径渐变画刷来填充图形、路径和区域,甚至也可用来绘制直线、曲线等。
  • 独立的路径对象
    GDI+ 使用 Graphics 对象来进行绘图操作,并将路径操作从 Graphics 对象分离出来,提供一个 Graphics 类供用户使用,用户不必担心对象会受到 Graphics 对象操作的影响,从而可以使用同一个操作对象进行多次的路径绘制操作。
  • 样条曲线
    GDI+ 封装了绘制基数样条曲线和贝塞尔样条曲线的方法。
  • 变形和矩阵运算
    GDI+ 提供了功能强大的 Matrix 类来实现矩阵的旋转,错切、平移、比例等变换操作,以便产生复杂的新图形。
  • 多图片格式的支持
    GDI+ 改进了图形处理能力,通过 GDI+,用户能够访问多种格式的图片文件,转换文件格式等,还能进行图像重新着色、色彩修正、消除走样等图像处理。

Direct2D

Direct2D 是一个基于 Direct3D 的 2D 图形 API,可以利用硬件加速特性来提供高性能、高质量的 2D 渲染。而且十分方便的是,Direct2D 与 GDI,GDI+ 和 D3D 都是可以交互的。一项技术总是有其受众面,看看微软怎么说的:

  • 大型企业级本机应用程序开发人员。
  • 创建供下游开发人员使用的控件工具包和库的开发人员。
  • 需要对二维图形进行服务器端呈现的开发人员。
  • 使用 Direct3D 图形,并且需要在菜单、用户界面 (UI) 元素和抬头显示器 (HUD) 中使用高性能的简单二维和文本呈现的开发人员。
    direct2D
    在 Direct2D 架构的右下方,有一个软件光栅化(software rasterizer),假如显卡不支持硬件加速,那么 Direct2D 可以使用软件方式渲染,即便是这样,其效果还是要优于 GDI。

视觉效果

使用 Direct2D 渲染出来的效果要比 GDI 要好的多。因为 Direct2D 使用基于图元的反锯齿效果(这样会使线条更加的平滑),而且在渲染二维图元的时候,完全支持透明和 Alpha 混合。以下是对比的照片:
direct2d vs gdi
显然,右边的 Direct2D 的线条效果要好于左边的 GDI。

demo

  • GDI
    正弦曲线
  • GDI+
    渐变色,Alpha混合
  • D2D
    螺旋路径动画
    2d draw demo

GDI 绘图代码:

void CChildView::_drawWithGDI( CPaintDC& dc )
{
    CRect rect;
    GetClientRect(&rect);

    // 绘制 title
    dc.TextOut(10, 10, _T("GDI"), 3);

    //逻辑坐标与设备坐标变换
    dc.SetMapMode(MM_ANISOTROPIC);
    dc.SetWindowOrg(0, 0);
    dc.SetWindowExt(rect.right, rect.bottom);
    dc.SetViewportOrg(0, rect.bottom / 2);
    dc.SetViewportExt(rect.right, - rect.bottom);

    //创建绘制 x 轴的 pen 并将其选入设备上下文
    CPen penx(PS_SOLID, 1, RGB(0, 0, 255));
    HGDIOBJ oldObject = dc.SelectObject(penx.GetSafeHandle());
    //绘制 x 轴
    dc.MoveTo(0, 0);
    dc.LineTo(rect.right, 0);

    //创建绘制正旋曲线的 pen 并将其选入设备上下文
    CPen pen(PS_SOLID, 1, RGB(255, 0, 0));
    dc.SelectObject(pen.GetSafeHandle());

    //绘制正弦曲线
    dc.MoveTo(0, 0);
    for (int i = 0; i < rect.right; i++)
        dc.LineTo(i, (int)(100 * sin(2 *(i / (rect.right / 5.0)) * M_PI)));

    //恢复原先的 pen
    dc.SelectObject(oldObject);

    // 恢复逻辑坐标与设备坐标变换
    dc.SetViewportOrg(0, 0);
    dc.SetViewportExt(rect.right, rect.bottom);
}

GDI+ 绘图代码:

void CChildView::_drawWithGDIPlus( CPaintDC& dc )
{
    using namespace Gdiplus;
    CRect rect;
    GetClientRect(&rect);

    Graphics graphics(dc);

    //创建渐变画刷
    LinearGradientBrush lgb(Point(0, 0), Point(rect.right, rect.bottom), Color::Blue, Color::Purple);
    graphics.FillRectangle(&lgb, 0, 0, rect.right, rect.bottom);

    //创建ColorMatrix
    ColorMatrix ClrMatrix =
    {
        1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
        0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.0f, 0.5f, 0.0f,
        0.0f, 0.0f, 0.0f, 0.0f, 1.0f
    };

    //将 ColorMatrix 赋给 ImageAttributes
    ImageAttributes ImgAttr;
    ImgAttr.SetColorMatrix(&ClrMatrix, ColorMatrixFlagsDefault,ColorAdjustTypeBitmap);

    FontFamily fontFamily1(_T("Calibri"));
    Font font1(&fontFamily1, 12, FontStyleRegular, UnitPoint);

    //Alpha 混合
    graphics.DrawString(_T("LENA"), 4, &font1, PointF(140.0f, 140.0f), &SolidBrush(Color::White));
    TCHAR szImgPath[MAX_PATH] = {0};
    //确保程序所在目录下存在 lena.png (res 文件夹下有)
    PathHelper::makeFullPathWithModuleDir(szImgPath, MAX_PATH, _T("lena.png"));
    Image img(szImgPath);
    graphics.DrawImage(&img, RectF(60, 140, 200, 200), 0, 0, 
        (REAL)img.GetWidth(), (REAL)img.GetHeight(), UnitPixel, &ImgAttr);

    // 绘制 title
    graphics.DrawString(_T("GDI+"), 4, &font1, PointF(10.0f, 10.0f), &SolidBrush(Color::White));
}

Direct2D 绘制代码(部分):

HRESULT D2DImpl::render(HWND hwnd)
{
    using namespace D2D1;
    HRESULT hr;

    hr = createDeviceResources(hwnd);
    RETURN_IF_FAILED(hr);

    int wndState = m_pRenderTarget->CheckWindowState();
    RETURN_IF_TRUE(wndState & D2D1_WINDOW_STATE_OCCLUDED, E_FAIL);

    D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();

    // Prepare to draw.
    m_pRenderTarget->BeginDraw();

    // Reset to identity transform
    m_pRenderTarget->SetTransform(Matrix3x2F::Identity());

    m_pRenderTarget->Clear(ColorF(ColorF::Black));

    TCHAR szTitle[] = _T("Direct2D");
    m_pRenderTarget->DrawText(
        szTitle,
        _tcslen(szTitle),
        m_pTextFormat,
        D2D1::RectF(10, 10, rtSize.width, rtSize.height),
        m_pTextBrush);

    //center the path
    float minWidthHeightScale = min(rtSize.width, rtSize.height) / 512;
    Matrix3x2F scale = Matrix3x2F::Scale(minWidthHeightScale, minWidthHeightScale);
    Matrix3x2F translation = Matrix3x2F::Translation(rtSize.width / 2, rtSize.height / 2);
    m_pRenderTarget->SetTransform(scale * translation);

    //draw the path in red
    m_pRenderTarget->DrawGeometry(m_pPathGeometry, m_pRedBrush);

    static float float_time = 0.0f;
    float length = m_animation.GetValue(float_time);

    D2D1_POINT_2F point;
    D2D1_POINT_2F tangent;
    // Ask the geometry to give us the point that corresponds with the length at the current time.
    hr = m_pPathGeometry->ComputePointAtLength(length, NULL, &point, &tangent);

    // Reorient the triangle so that it follows the direction of the path.
    D2D1_MATRIX_3X2_F triangleMatrix = Matrix3x2F(
        tangent.x, tangent.y,
        -tangent.y, tangent.x,
        point.x, point.y );

    m_pRenderTarget->SetTransform(triangleMatrix * scale * translation);

    // Draw the yellow triangle.
    m_pRenderTarget->FillGeometry(m_pObjectGeometry, m_pYellowBrush);

    // Commit the drawing operations.
    hr = m_pRenderTarget->EndDraw();

    if (hr == D2DERR_RECREATE_TARGET) {
        hr = S_OK;
        discardDeviceResources();
    }

    // When we reach the end of the animation, loop back to the beginning.
    if (float_time >= m_animation.GetDuration())
        float_time = 0.0f;
    else
        float_time += (float)(m_dwmTiming.rateCompose.uiDenominator) / (m_dwmTiming.rateCompose.uiNumerator);

    InvalidateRect(hwnd, NULL, FALSE);

    return hr;
}

Blueware
EOF

  • 3
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值