wpf计算字符大小占像素_计算机图形原理及实践第二章

第二章 2D 图形学简介——基于WPF

2.1 引言

选择具体的图形学平台:微软的 Window Presentation Foundation(WPF) WPF 是同时支持 2D 和 3D应用的现代图形学平台

2.2 2D图形流水线概述

  • 2D图形平台是应用程序和显示硬件的中介,提供的功能与输入和输出相关联。
  • 应用通常是将某些数据(称为 应用模型 (Application Modle), AM) 转化为图像
  • 应用程序开设客户区域有两个目的:
    • 一部分用于应用程序的用户界面(UI)控制
    • 其余部分为 视图,用来显示 场景 绘制的结果。显示内容由 应用程序的 场景生成器 模块从 AM 中提取或导出

2.3 2D图形平台的演变

  • 从整数坐标到浮点数坐标
    • 早期的2D光栅图形平台采用整数坐标
      • 应用程序并非对单个像素进行着色,而是通过调用绘制 基元 的程序来绘制场景。基于可以是 几何形状 或 预先读入的矩形图像(通常叫做位图)。每一个几何基元的外观由具体的属性参数控制。
      • 应用程序采用整数坐标,可一对一地直接映射屏幕像素。
        • 原点 (0, 0) 置于画布的左上角,x 值按从左至右的方向,y值按从上到下的方向
      • 应用程序通过函数对每个基元进行设置
      • 采用整数坐标系,显示大小取决于输出设备的分辨率。同样的图形,如果输出设备的分辨率更大,则输出的图形会更小
    • 之后图形平台采用 浮点数坐标系将详细的几何数据与具体设备的特性分离开。 整数坐标系对应 物理坐标系统 浮点数坐标系对应 抽象坐标系统

  • 即时模式与保留模式
  • 基于整数的数据 和 基于浮点数数据这两种表示 最终形成了 两种不同目标和功能的架构:即时模式(IM) 和 保留模式(RM)
    • 即时模式当绘制函数被调用时,立即执行任务,将几何基元的坐标映射为设备坐标,并在显示缓冲器中对相关像素进行着色,然后将控制权返回给应用程序。
      • 在即时模式下,程序员的工作就是:对绘制图像做任何修改时,让场景生成器遍历 AM,重新生成表示场景的基元集合
    • 保留模式在一个专用数据库中保留了需绘制或观看的场景的表示,我们称之场景图。
      • 应用程序的 UI 和 场景生成器 使用 RM平台的 API 构建场景图。可通过编辑场景图对场景进行增量式修改。任何增量式修改都会导致 RM平台的同步显示器自动更新客户区域的绘制结果。
      • RM平台除了承担显示任务外,还可以承担许多与用户交互相关的任务

  • 过程语言 与 描述语言
  • 为了给出对用户接口和场景的详细描述,图形平台提供以下两种技术:
    • 面向过程的代码:采用命令式编程语言编写(通常是面向对象的,但并非必须如此),可通过大量的图形 API 与显示设备进行交互。例如,Java Swing、WPF、DirectX 等
    • 描述性语言:通过标记语言表达,例如 SVG 或 XAML
  • 最底层:面向对象的 API
    • 核心层是一组提供所有 WPF 功能的类,程序员可以在这一层使用 .Net 语言来定义应用的对外接口和行为。仅通过这一层就可以创建一个 WPF 应用程序。但另外两层可以提高开发效率。
  • 中间层:XAML
    • 中间层 提供了定义 API 大部分功能的另一种途径,它使用描述语言 XAML,其语法与 HTML 和 XML 类似。可通过对描述性语言的解释性执行程序支持应用的快速原型实现。
  • 最高层:工具
    • WPF 的最高层集成了设计师和工程师用于生成 XAML 的实用程序包括 绘图工具、3D建模工具、创建复杂用户界面的工具

2.4 使用 WPF 定义 2D场景

在 2.4 节中,我们将构造一个简单的 XAML 应用程序,来显示一个模拟时钟

  • 像 HTML 一样,XAML 也给出元素的层次化结构,但其元素类型不同,它包含:
    • 布局面板(例如,负责对紧密安放在一起的控件或菜单进行空间布局的 Stack Panel)
    • 用户界面控件(例如,按钮 和 文本输入框)
    • 用于绘制场景的 Canvas(画布)
  • 第一个例子,用 XAML 创建一个Canvas(画布):
<Canvas
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
        Cx,lipToBounds="True"
    >
    </Canvas>
  • 通常都将 ClipToBoudns 设为 True,它将保证画布是有界的,即不会再指定的矩形区域之外显示任何数据
  • 我们没有指定画布大小
  • XAML 具有某些语法特性(例如上面提到的 Canvas 标签中奇特的 xmlns 性质),但它们并不会掩盖标签和属性的语义。

  • 采用抽象坐标系定义场景
    • 在图纸上建立 抽象坐标系,在抽象坐标系中我们不需要关心其一个单位的长度的具体物理单位(是 米 还是 厘米 还是 毫米)
      • 原点为 (0, 0) x 从左 到 右 y 从上 到 下
    • 绘制顺序:按从后(离观察者最远处) 到 近 的顺序绘制。即最开始绘制时钟的钟面

XAML:

<Canvas
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
        ClipToBounds="True">
        <Ellipse
            Canvas.Left="-10.0" Canvas.Top="-10.0"
            Width="20.0" Height="20.0"
            Fill="LightGray"/>
    </Canvas>

0172704061833ea4621279adb528676d.png
  • 此时会发现抽象坐标系带来的问题
    • 抽象坐标映射到显示设备上后,显示出的灰色圆太小
      • 只能看到 四分之一的圆
    • 这是由于我们的抽象坐标系 并不 适应WPF画布坐标系统。我们希望钟面的半径为 1英寸,而对应 WPF坐标系统,96个单位 = 1英寸。因此,我们需要定义一个 抽象坐标系 到 WPF坐标系 的 显示变换
    • 我们抽象坐标系 20个单位 映射到 WPF坐标系上 希望是其96个单位,那么就是x、y轴都乘上 96/20 即 4.8。可以通过 RenderTransform 实现比例变换
<Canvas
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
        ClipToBounds="True">
        <Canvas>
            <Ellipse
            Canvas.Left="-10.0" Canvas.Top="-10.0"
            Width="20.0" Height="20.0"
            Fill="LightGray" >
            </Ellipse>
            <Canvas.RenderTransform>
                <TransformGroup>
                    <ScaleTransform ScaleX="4.8" ScaleY="4.8" CenterX="0" CenterY="0"/>
                </TransformGroup>
            </Canvas.RenderTransform>
        </Canvas>
    </Canvas>

775d2fe5231ee27f07a3b257205c7ef1.png
  • 坐标系/基原则:始终选择你工作作为方便的坐标系或基,并通过变换使它和不同的坐标系或基关联起来
  • 此时,我们仍是只能看到四分之一圆,我们再添加一条 平移变换:
<Canvas
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
        ClipToBounds="True">
        <Canvas>
            <Ellipse
            Canvas.Left="-10.0" Canvas.Top="-10.0"
            Width="20.0" Height="20.0"
            Fill="LightGray" >
            </Ellipse>
            <Canvas.RenderTransform>
                <TransformGroup>
                    <ScaleTransform ScaleX="4.8" ScaleY="4.8" CenterX="0" CenterY="0"/>
                    <TranslateTransform X="48" Y="48"/>
                </TransformGroup>
            </Canvas.RenderTransform>
        </Canvas>
    </Canvas>

198a9dea7b37867d02c2fd61157fc17e.png
  • 比例变换 和 位移变换的执行顺序不同,结果也不同。即它们是相关联的

  • 构造并使用模块化模板
    • WPF 中 有重用模板(控制模板 Control),对于我们指针的时针和分针,它们的形状是相同的,只是缩放不同,因此我们可以先构造 模板,然后通过 模板 来实例化出具体的元素
<Canvas
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
        ClipToBounds="True">
        <Canvas>
            <Canvas.Resources>
                <ControlTemplate x:Key="ClockHandTemplate">
                    <Polygon
                        Points="-0.3,-1 -0.2,8  0,9 0.2,8 0.3,-1  "
                        Fill="Navy"/>
                </ControlTemplate>
            </Canvas.Resources>
            <Ellipse
                Canvas.Left="-10.0" Canvas.Top="-10.0"
                Width="20.0" Height="20.0"
                Fill="LightGray"
            />
            <Ellipse
                Canvas.Left="-2" Canvas.Top="-10.0"
                Width="4" Height="4"
                Fill="Blue"
            />
            <Control Name="MinuteHand"
                        Template="{StaticResource ClockHandTemplate}"
            />
            <Canvas.RenderTransform>
                <TransformGroup>
                    <ScaleTransform ScaleX="4.8" ScaleY="4.8" CenterX="0" CenterY="0"/>
                    <TranslateTransform X="60" Y="60"/>
                </TransformGroup>
            </Canvas.RenderTransform>
        </Canvas>
    </Canvas>
  • - 在 Canvas.Resources 内通过 ControlTemplate 定义模板,每个模板必须赋予一个唯一的名称(x:Key属性)
  • 之后通过 Control 使用 Name 属性调用之前定义的模板进行实例化

f70b95a080ec43b980b6b957e872616f.png
  • 我们再实例化出一个时针,并为该时针 即 Control 元素,添加 缩放 和 旋转变换
<Canvas
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
        ClipToBounds="True">
        <Canvas>
            <Canvas.Resources>
                <ControlTemplate x:Key="ClockHandTemplate">
                    <Polygon
                        Points="-0.3,-1 -0.2,8  0,9 0.2,8 0.3,-1  "
                        Fill="Navy"/>
                </ControlTemplate>
            </Canvas.Resources>
            <Ellipse
                Canvas.Left="-10.0" Canvas.Top="-10.0"
                Width="20.0" Height="20.0"
                Fill="LightGray"
            />
            <Ellipse
                Canvas.Left="-2" Canvas.Top="-10.0"
                Width="4" Height="4"
                Fill="Blue"
            />
            <Control Name="MinuteHand"
                        Template="{StaticResource ClockHandTemplate}"
                />
            <Control Name="HourHand"
                        Template="{StaticResource ClockHandTemplate}"
            >
                <Control.RenderTransform>
                    <TransformGroup>
                        <ScaleTransform ScaleX="1.7" ScaleY="0.7" CenterX="0.0" CenterY="0.0"/>
                        <RotateTransform Angle="45" CenterX="0.0" CenterY="0.0"/>
                    </TransformGroup>
                </Control.RenderTransform>
            </Control>
            <Canvas.RenderTransform>
                <TransformGroup>
                    <ScaleTransform ScaleX="4.8" ScaleY="4.8" CenterX="0" CenterY="0"/>
                    <TranslateTransform X="60" Y="60"/>
                </TransformGroup>
            </Canvas.RenderTransform>
        </Canvas>
    </Canvas>
  • 之前我们对整块画布做的变换,是为了控制场景在显示设备上的尺寸和位置,因此 称为 显示变换
  • 而对时针的变换,只是作用于一部分场景,称为 建模变换
  • 上述时针模板是一个非常基本的、单层次的 层次化建模

2.5 用 WPF实现 2D 图形动态显示

  • 这一节介绍 WPF 应用程序中两种可行的动态显示
    • 自动、非交互的动态显示,此时 2D 形状由 XAML 定义的动画对象操纵
    • 传统的用户界面动态显示,此时用户通过操纵 GUI 控件,例如 按钮、列表框、文本输入等,来激活动态过程的代码

  • 基于描述性动画的动态显示
    • 可以用 XAML 动画元素来实现动画效果。通过插值使对象的动态属性随时间而变化。
    • 每一种 XAML 元素的属性都可以成为动画的对象,例如:
      • 形状的局部原点(例如,椭圆左上角) 可由动画元素操控,从而使动画振动
      • 形状的基本填充色,边界色 和 边界粗细等属性均可由动画元素操控,以实现反馈式动画、例如发光和脉动
      • 动画元素也可以操控旋转变换的角度属性,从而使指定物体产生旋转
    • 回到我们上面构建的时钟。我们想要时针能够被动画程序控制。
      • 我们增加一个 RotateTransform 并设置标签 ActualTimeHour 使其可被动画程序控制
<Control.RenderTransform>
        <TransformGroup>
            <ScaleTransform ScaleX="1.7" ScaleY="0.7" CenterX="0.0" CenterY="0.0"/>
            <RotateTransform Angle="180" CenterX="0.0" CenterY="0.0"/>
            <RotateTransform x:Name="ActualTimeHour" Angle="0"/>
        </TransformGroup>
    </Control.RenderTransform>
  • 接下来是 动画程序的 XAML 代码。创建一个 EventTrigger,在定义触发器时,必须指定启动它的事件类型(本例为画布内容全部载入时) 和 它将执行什么内容(本例为时针的动画元素,其被封装在 Storyboard中) (为了更好观察,这里将动画时间从 1h 该为 1s
<Canvas
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
        ClipToBounds="True">
        <Canvas>
            <Canvas.Resources>
                <ControlTemplate x:Key="ClockHandTemplate">
                    <Polygon
                        Points="-0.3,-1 -0.2,8  0,9 0.2,8 0.3,-1  "
                        Fill="Navy"/>
                </ControlTemplate>
            </Canvas.Resources>
            <Ellipse
                Canvas.Left="-10.0" Canvas.Top="-10.0"
                Width="20.0" Height="20.0"
                Fill="LightGray"
            />
            <Ellipse
                Canvas.Left="-2" Canvas.Top="-10.0"
                Width="4" Height="4"
                Fill="Blue"
            />
            <Control Name="MinuteHand"
                        Template="{StaticResource ClockHandTemplate}"
                />
            <Control Name="HourHand"
                        Template="{StaticResource ClockHandTemplate}"
                >
                <Control.RenderTransform>
                    <TransformGroup>
                        <ScaleTransform ScaleX="1.7" ScaleY="0.7" CenterX="0.0" CenterY="0.0"/>
                        <RotateTransform Angle="180" CenterX="0.0" CenterY="0.0"/>
                        <RotateTransform x:Name="ActualTimeHour" Angle="0"/>
                    </TransformGroup>
                </Control.RenderTransform>
            </Control>
            <Canvas.Triggers>
                <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation
                                Storyboard.TargetName="ActualTimeHour"
                                Storyboard.TargetProperty="Angle"
                                From="0.0" To="360.0"
                                Duration="00:00:01.00" RepeatBehavior="Forever"
                                />
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Canvas.Triggers>
            <Canvas.RenderTransform>
                <TransformGroup>
                    <ScaleTransform ScaleX="4.8" ScaleY="4.8" CenterX="0" CenterY="0"/>
                    <TranslateTransform X="60" Y="60"/>
                </TransformGroup>
            </Canvas.RenderTransform>
        </Canvas>
    </Canvas>
  • 再来看里面的 DoubleAnimation 其就是一个动画元素,并且是通过双精度数来控制属性的变换
    • TargetName 和 TargetProperty 可将时针设置成动画 它们被关联在 RotateTransform 元素的 Angle 属性上
    • From 和 To 属性决定旋转的区间和方向
    • Duration 则控制旋转角度跨越这个区间所需时间。即单次动画时间
      • Duration 的格式为:Hours : Minutes : Secounds . FractionalSecond
    • RepeatBehavior 设为 Forever 即为循环动画,当动画到达 To 条件时 就会回到 From 条件,重新向 To 步进

4964adc0333248dc04fe250aed2ce25b.png
  • 完整的时针 XAML:
<Canvas
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
        ClipToBounds="True">
        <Canvas>
            <Canvas.Resources>
                <ControlTemplate x:Key="ClockHandTemplate">
                    <Polygon
                        Points="-0.3,-1 -0.2,8  0,9 0.2,8 0.3,-1  "
                        Fill="Navy"/>
                </ControlTemplate>
                <ControlTemplate x:Key="ClockSecoundHandTemplate">
                    <Polygon
                        Points="-0.3,-1 -0.2,8  0,9 0.2,8 0.3,-1  "
                        Fill="Pink"/>
                </ControlTemplate>
            </Canvas.Resources>
            <Ellipse
                Canvas.Left="-10.0" Canvas.Top="-10.0"
                Width="20.0" Height="20.0"
                Fill="LightGray"
            />
            <Ellipse
                Canvas.Left="-2" Canvas.Top="-10.0"
                Width="4" Height="4"
                Fill="Blue"
            />
            <Control Name="MinuteHand"
                        Template="{StaticResource ClockHandTemplate}"
                >
                <Control.RenderTransform>
                    <TransformGroup>
                        <RotateTransform Angle="180" CenterX="0.0" CenterY="0.0"/>
                        <RotateTransform x:Name="ActualTimeMinute" Angle="0"/>
                    </TransformGroup>
                </Control.RenderTransform>
            </Control>
            <Control Name="HourHand"
                        Template="{StaticResource ClockHandTemplate}"
                >
                <Control.RenderTransform>
                    <TransformGroup>
                        <ScaleTransform ScaleX="1.7" ScaleY="0.7" CenterX="0.0" CenterY="0.0"/>
                        <RotateTransform Angle="180" CenterX="0.0" CenterY="0.0"/>
                        <RotateTransform x:Name="ActualTimeHour" Angle="0"/>
                    </TransformGroup>
                </Control.RenderTransform>
            </Control>
            <Control Name="SecoundHand" Template="{StaticResource ClockSecoundHandTemplate}">
                <Control.RenderTransform>
                    <TransformGroup>
                        <ScaleTransform ScaleX="1" ScaleY="1.2" CenterX="0.0" CenterY="0.0"/>
                        <RotateTransform Angle="180" CenterX="0.0" CenterY="0.0"/>
                        <RotateTransform x:Name="ActualTimeSecound" Angle="0"/>
                    </TransformGroup>
                </Control.RenderTransform>
            </Control>
            <Canvas.Triggers>
                <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation
                                Storyboard.TargetName="ActualTimeHour"
                                Storyboard.TargetProperty="Angle"
                                From="0.0" To="360.0"
                                Duration="12:00:00.00" RepeatBehavior="Forever"
                                />
                        </Storyboard>
                    </BeginStoryboard>
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation
                                Storyboard.TargetName="ActualTimeMinute"
                                Storyboard.TargetProperty="Angle"
                                From="0" To="360"
                                Duration="01:00:00.00" RepeatBehavior="Forever"
                            />
                        </Storyboard>
                    </BeginStoryboard>
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation
                                Storyboard.TargetName="ActualTimeSecound"
                                Storyboard.TargetProperty="Angle"
                                From="0" To="360"
                                Duration="00:01:00.00" RepeatBehavior="Forever"
                            />
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Canvas.Triggers>
            <Canvas.RenderTransform>
                <TransformGroup>
                    <ScaleTransform ScaleX="4.8" ScaleY="4.8" CenterX="0" CenterY="0"/>
                    <TranslateTransform X="60" Y="60"/>
                </TransformGroup>
            </Canvas.RenderTransform>
        </Canvas>
    </Canvas>

55550c43cc0a05a71e04c1b03d57ec34.png
  • 基于过程代码的动态显示
    • 这里书中只是做了简短的介绍。在展示一个真实时针时,可采用过程代码给出正确的本地时间、提供闹铃、对用户交互进行反馈等。

2.6 支持各种形状系数

  • 这一小节对 用户界面(UI) 和 屏幕显示区域 的自适应策略进行了探讨
    • 用户界面(UI)
      • 当 UI 控件的尺寸变小、屏幕区域有限时。可以采用 省略策略(例如 隐藏不常用控件)
        • 例如窗口宽度明显变短时,可以隐藏掉一些不常用控件,并提供一个 扩展按钮,用来对已隐藏的菜单进行访问
      • 屏幕显示区域
        • 缩小绘制图形以便视窗中容纳更多内容,并提供可拖动控件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值