XAML 图形系列 - 第 2 部分 Silverlight 2.0 桌面艺术动画






3.77/5 (5投票s)
这是关于使用 Silverlight 2.0 和 C# 创建 XAML 应用程序的介绍。

文章索引评分 - 开发者 (5) / 5 (中级)
- 架构师 (2.86) / 5 (初学者)
- 平面设计师 (2.38) / 5 (初学者)

目录
引言
在本系列的前一篇文章中,我们概述了一些目标。该系列的主要目标是弥合应用程序开发中所有利益相关者之间的差距。本场景中的利益相关者是一家低预算初创公司的员工,该公司正试图通过各种资源筹集资金。这些资源不属于本文的范畴,公司也没有管理层向其汇报。由于资金有限,我们饥肠辘辘的艺术家、开发者和架构师正在为他们的虚拟产品利用快速应用程序开发方法。该产品的目的是通过创建交互式的数字艺术作品供互联网社区消费,从而为艺术家带来范式转变。
本系列分为初学者(无需编程经验)、中级(一些编程知识有帮助)和高级(高级编程概念知识非常有帮助)受众的章节。
系列内容分解
初学者系列
1. Silverlight 1.0 桌面艺术动画
2. Silverlight 2.0 桌面艺术动画
3. XAML 2D 图形概念
4. XAML 3D 图形概念
中级系列
5. 动态 XAML
6. WPF 应用程序设计与架构
7. Silverlight 应用程序设计与架构
8. 高级概念简介:DirectX、OpenGL 和过滤
高级系列
9. GPU 的解剖以及 XAML 如何针对 GPU 进行优化
10. GPU 优化技术
11. 整合:DirectX、OpenGL 等
12. 整合所有内容:设计自定义图形引擎的方法
用例 - 灯光!摄像机!行动!
现在,为我们的演员们搭好了舞台,可以探索原型应用程序的架构了。在我们演员之前的一次会议上,他们确定平面设计师是他们工作流程中最薄弱的环节。他们没有像“电视上那样”把最薄弱的环节淘汰掉,而是决定围绕设计师来构建应用程序,因为他们也确定了设计师作为正在开发的应用程序的原理作用。设计师测试了公司为开发此应用程序购买的工具,发现它们不像某些其他工具那样易于使用。设计师的主要抱怨是无法轻松地将设计与应用程序集成。架构师和开发者同情设计师,并提出了一个解决方案,以最大限度地提高设计师作为生产性团队成员的效率。在此场景中,设计师具有设计和 Web 开发背景,曾是一名流行网站的网站管理员。团队同意投资资源开发一个内部基于 Shell 的脚本工具,设计师可以使用该工具快速将其设计集成到原型应用程序基础中。
架构设计规则
- 架构规则
- 代码隐藏隔离 - 基础 XAML(页面、窗口)(宿主层)没有代码隐藏
- 代码隐藏隔离 - 优先在资源字典中使用代码隐藏(逻辑层)
- 代码隐藏隔离 - 用户控件事件路由优先使用代码隐藏(事件路由)
- 矢量图像隔离 - 矢量光栅图像将被隔离在单独的资源字典中
- 层抽象 - 定义所有抽象规则的能力。(代码策略执行)
- 资源标识 - 将使用 x:Type 属性在 XAML 中严格定义资源类型
- RAD - RAD 工具将自动化代码和 XAML 存根。生成器解析设计器级别的 XAML。
- 设计器规则
- 层抽象 - 通过层定义图形化隔离自定义代码的能力
- Shell 脚本 - 通过 Shell 脚本应用代码生成器的接口
- 开发者规则
- 层抽象 - 将 XAML 转换为资源的能力。
- 层抽象 - 定义自定义代码转换的能力
平面设计师的操作

利用公司开发的新型快速应用程序工具,设计师开始修改原始原型。设计师首先定义覆盖在图像上的层。然后,每个层被划分为定义属性的区域,这些属性将与区域中的图形元素相关联。例如,设计师想创建一个阳光透过树木(图像中未显示)并在建筑物侧面的藤蔓上投射阴影和光线的效果。架构师设计了一个简单的 XML 解析器,该解析器获取层的名称,然后查找层中每个区域包含的所有对象。层的名称告诉解析器要应用于代码生成器找到的对象的哪个效果。设计师对架构师的创新感到如释重负。设计师还希望在各个区域创建闪烁效果,因此创建了另一个层,开发者负责设计新效果。此外,一位潜在投资者查看了第一个原型,并认为如果下一个原型演示一种交互式效果,当鼠标在艺术作品上滚动时,会跟随鼠标指针产生涟漪,那就太棒了,因此设计师创建了另外两个用于此效果的层:一个用于映射受此事件影响的区域,另一个用于定义该事件将应用的效果。
开发者的操作

在此场景中,开发者承担了最多的工作。团队不仅将创建新工具的负担加给了开发者,而且架构师还为自动代码生成定义了规则。开发者同意这将是值得的努力,并有信心迎接挑战。不幸的是,开发者很快发现这个想法非常独特,他必须手工编写 80% 的代码,并且除了团队之外,开发者无法利用其他任何资源来完成应用程序的这部分工作。此场景中的开发者是因其在快速应用程序开发技术方面的专业知识而被纳入该项目的。首先,开发者审查将用于原型的代码指南和技术。然后,一种模式开始出现。在阅读了有关图形包的技术文章后,他意识到可以通过尝试将引用对象强制转换为框架的基础类型来轻松找到框架中的对象。此时,开发者必须决定此方法可用性的问题。使用 C# 的反射类编写了一个简单的测试应用程序。使用反射打开 UI 程序集,为程序集中的每个类构建一个 for each 循环。循环简单地递增两个计数器:一个用于计数反射发现的所有类,另一个用于记录可以强制转换为框架基础类的类的命中次数。然后将命中次数除以类数。这为开发者提供了使用此方法可以预期的代码覆盖率的近似值。以下是测试代码和结果的列表
/** * * XAMLBox.Localizer Class * * This class is used for testing the localization and isolation of Framework classes * */ using System; using System.Threading; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; using System.Windows; namespace XAMLBox { class Localizer { static void Main(string[] args) { //WPF requires the runtime to run as an STA thread.. Thread t = new Thread(CastingHitCounter); t.SetApartmentState(ApartmentState.STA); t.Start(); } private static void CastingHitCounter() { //Create an instance if a base class object in the framework FrameworkElement fe = new FrameworkElement(); //Get a reference to the Type to reflect on Type t = Type.GetType(fe.GetType().AssemblyQualifiedName); //Get a reference to the assembly containing the Type 't' //(System.Windows.FrameworkElement) Assembly FrameWork = Assembly.GetAssembly(t); //Create a box for our reflected Class to be used in a loop. Object RefClass = new Object(); //Get all types defined in the assembly.. Type[] TypeArr = FrameWork.GetTypes(); //Total qualified types reported by the assembly... int qualifiedTypes = TypeArr.Length; //Total Class instances created.. int classCount = 0; //Total bad reference counts.. int badInstance = 0; //Total FrameworkElement casts.. int feCount = 0; //Total FrameworkContentElement casts.. int fceCount = 0; //Total Framework castable classes.. int castCount = 0; //Total missed Framework counts.. int missCount = 0; //Loop through each Type in the reflected assembly and count //casting hits. foreach (Type thisType in TypeArr) { try { //Create an instance of a the reflected Type RefClass = FrameWork.CreateInstance _ (thisType.AssemblyQualifiedName.ToString()); try { //Try to cast the reflected type to a FrameworkElement.. RefClass = (FrameworkElement)RefClass; //Increment our counters.. feCount++; castCount++; //Try to cast the reflected type to a //FrameworkContentElement.. RefClass = FrameWork.CreateInstance _ (//#thisType.AssemblyQualifiedName.ToString()); RefClass = (FrameworkContentElement)RefClass; //Increment our counters.. fceCount++; castCount++; } catch (Exception e) { try { //We missed a refrence of type FrameworkElement try //to cast it to a FrameworkContentElement.. RefClass = (FrameworkContentElement)RefClass; //Incriment our counters.. fceCount++; castCount++; } catch (Exception ex) { //Unable to cast to a Framework type, increment our //counter.. missCount++; } } finally { //Clean up our box.. RefClass = null; //Incriment Class counter.. classCount++; } } catch (Exception e) { //Incriment Class counter.. classCount++; //This is not an instance class, increment the counter.. badInstance++; } } //Report the statistics: Console.Out.WriteLine("Total Qualified Types: " + _ qualifiedTypes); Console.Out.WriteLine("Total Qualified Classes: " + _ classCount); Console.Out.WriteLine("Total Bad Instances: " + _ badInstance); Console.Out.WriteLine("Total Workable FrameWork Types: " _ + castCount); Console.Out.WriteLine("Total FrameworkElements: " _ + feCount); Console.Out.WriteLine("Total FrameworkContentElements: " _ + fceCount); Console.Out.WriteLine("Total Missed Types: " + missCount); Console.Out.WriteLine("Qualified Type to Missed Type Ratio: " _ + //#((missCount / qualifiedTypes) * 100)); Console.Out.WriteLine("FrameworkElement to Qualified Class Ratio: " _ + //#((feCount / classCount) * 100)); Console.Out.WriteLine("FrameworkContentElement to Qualified Class Ratio: " _ + ((fceCount / classCount) * 100)); Console.Out.WriteLine("Bad Instance to Qualified Class Ratio: " + _ ((badInstance / classCount) * 100)); Console.Out.WriteLine("Error Ratio: " + _ (((badInstance + missCount / classCount) * 100)); //Set breakpoint here to view the results.. Console.Out.WriteLine("End of Report"); } } }
本地化器测试工具的输出:
合格类型总数:2336
合格类总数:2336
总坏实例:0
可用框架类型总数:4672
FrameworkElement总数:2336
FrameworkContentElement 总数:2336
遗漏类型总数:0
合格类型与遗漏类型比率:0
FrameworkElement与合格类比率:100
FrameworkContentElement 与合格类比率:100
坏实例与合格类比率:0
错误率:0
审查本地化器工具的结果后,开发者决定了正确的行动方案,架构师也同意了。经过架构师的代码审查,发现了错误并进行了纠正。尽管结果似乎符合框架的设计模式,但基础反射类与框架中的所有类之间有 100% 的相关性,这有些可疑。这可能是开发周期过于雄心勃勃的常见错误。以下是纠正缺陷后的结果
合格类型总数:2336
合格类总数:2336
可强制转换的框架类型总数:131
可强制转换的FrameworkElement总数:105
可强制转换的 FrameworkContentElement 总数:26
唯一基类总数:306
遗漏类型总数:883
坏实例总数:1427
FrameworkElement的直接后代总数:0
FrameworkContentElement 的直接后代总数:0
FrameworkElement的祖先总数:145
FrameworkContentElement 的祖先总数:30
密封类总数:1112
抽象类总数:288
合格类型与遗漏类型比率:37.7996575342466
FrameworkElement与合格类比率:4.49486301369863
FrameworkContentElement 与合格类比率:1.11301369863014
坏实例与合格类比率:61.0873287671233
唯一基类型与合格类比率:13.0993150684931
FrameworkElement的直接后代与类计数比率:0
FrameworkContentElement 的直接后代与类计数比率:0
FrameworkElement的直接后代与唯一基类型比率:0
FrameworkContentElement 的直接后代与唯一基类型比率:0
FrameworkElement的祖先与类计数比率:6.20719178082192
FrameworkContentElement 的祖先与类计数比率:1.28424657534247
FrameworkElement的祖先与唯一基类型比率:47.3856209150327
FrameworkContentElement 的祖先与唯一基类型比率:9.80392156862745
错误率:98.8869863013699
结果的解释可能有点棘手。乍一看,98% 的错误率似乎表明相关性很差。然而,基类型上的 57% 的综合命中率表明这是一个进入该框架的良好切入点。框架的文档证实了这一指标。然而,缺少直接后代仍然可疑,因为框架中的至少一个类必须直接派生自反射类,除非它们共享一个共同的接口。文档表明,在此测试中使用的类不是用作基类的良好候选者,并且应该使用更高级别的入口点来派生新类。这一点对于这些类的预期用途来说是无关紧要的。通过使用具有共同祖先或接口的低级 API 来简化代码自动生成或领域特定语言(DSL)的过程。将高级 API 类多态化为通用类型可以简化自定义代码分支的数量,而不会牺牲任何定制的高级实现。理解基于通用接口和通用祖先的继承实现之间的区别是必要的,因为强制转换为基类祖先类会将实现降级到其最低级别实现,并会丢失由更高级别类应用的任何自定义。这两种实现风格之间存在很多歧义,因为每种风格都可以实现为框架增加价值。这更多是设计和架构的选择,取决于低级 API 的设计方式。两者都可以单独使用,如果设计实现正确,也可以协同工作。简单性和可用性之间的平衡是良好 OO 设计的共同偏好。
检查“直接后代”计算的比率后,显示代码中进一步存在错误。进行了以下修改以计算直接后代
if (BaseType.FullName.ToString() == "System.Windows.FrameworkElement")
fwDdCount++;
if (BaseType.FullName.ToString() == "System.Windows.FrameworkContentElement") fwcDdCount++;
修改后的比率结果
FrameworkElement的直接后代与类计数比率:1.41267123287671
FrameworkContentElement 的直接后代与类计数比率:0.256849315068493
FrameworkElement的直接后代与唯一基类型比率:10.7843137254902
FrameworkContentElement 的直接后代与唯一基类型比率:1.96078431372549
从这些结果中可以计算出分支比率
(fwDdCount/fwAnsestorCount) * 100 = 23%
(fwcDdCount/fwcAnsestorCount) * 100 = 20%
几乎相等。
计算完框架的分支后,需要创建一些原型来获得要使用的效果的性能结果。前两种效果是闪光和阳光效果。原型使用一个可编辑的图像缓冲区来渲染效果,然后通过 PNG 编码器将它们流式传输到浏览器。效果使用 ImageBrush
显示,另一种方法是重新编码和编码效果,然后使用 VideoBrush
显示。将在本系列的下一篇文章中探讨替代方法。创建了一个带有 XAML 测试页面和 SparkleFilter
类的测试应用程序。SparkleFilter 类使用 4 个图像缓冲区进行图像处理:AlphaBuffer
、SparkleBuffer
、Back Buffer
和 Cache Buffer
。Alpha 缓冲区包含应用 alpha 滤镜的指令,Sparkle 缓冲区包含应用亮度因子的指令,Back Buffer 是渲染动画的地方,Cache Buffer 包含原始图像的副本,在每帧开始时复制到 Back Buffer 以刷新源图像。使用 Dictionary<Color, EditableImage>
来缓存相同颜色的复合闪光缓冲区。这是测试页面的代码
在 1.76Ghz 的 Intel Pentium 4 Willamette 上,测试以 20 帧/秒的速度运行,CPU 使用率为 100%。50% 的 CPU 使用率下为 18 帧/秒,7% 的 CPU 使用率下为 10 帧/秒。由于此原型中使用的目标图像有超过 4000 个要动画的图像元素,因此必须采用另一种方法或进行更多性能修改,才能以 20 帧/秒的速度流畅运行动画。
架构师的操作模拟代码审查

架构师首先审查了许多设计器应用程序的技术、文件格式、代码限制、安全注意事项和集成构件。为所有发现的构件创建一个矩阵。然后按以下方式对列表进行索引:可用性、代码能力和隔离级别。每个索引都在 1 到 5 的范围内进行加权。1 表示不相关,5 表示核心依赖。矩阵完成后,将每个构件的索引值计算为比率。高于 3 的构件的比率索引被定义为架构必须遵循的规则。构件的发现基于业务决策、团队反馈以及架构师设计可重用代码的知识和经验。
查看代码后,架构师注意到开发者犯了一些笨拙的错误。第一个错误是代码没有正确动态生成引用类。结果是,反射代码一遍又一遍地生成相同的类。第二个错误是使用整数进行精度数学计算。除非使用浮点精度,否则无法正确计算比率。架构师还添加了更多代码来进一步理解程序集中类之间的依赖关系和关系。这是修正后的列表
/** * * XAMLBox.Localizer Class * * This class is used for testing the localization and isolation of Framework classes * */ using System;using System.Threading; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; using System.Windows; namespace XAMLBox { class Localizer { static Thread STAThread = new Thread(CastingHitCounter); static void Main(string[] args) { STAThread.SetApartmentState(ApartmentState.STA); STAThread.Start(); } private static void CastingHitCounter() { //Create an instance if a base class object in the framwork FrameworkElement fe = new FrameworkElement(); FrameworkContentElement fce = new FrameworkContentElement(); //Get a refrence to the Type to reflect on Type t = Type.GetType(fe.GetType().AssemblyQualifiedName); //Get a refrence to the assembly containing the Type 't' (System.Windows.FrameworkElement) Assembly FrameWork = Assembly.GetAssembly(t); //Create a box for our reflected Class to be used in a loop. Object RefClass = new Object(); //Create a box for our reflected Class to be used in a loop. Type BaseType; //Get all types defined in the assembly.. Type[] TypeArr = FrameWork.GetTypes(); //List of unique base types.. ArrayList UBaseTypes = new ArrayList(250); //Total qualified types reported by the assembly... double qualifiedTypes = TypeArr.Length; //Total Class instances created.. double classCount = 0; //Total unique base types.. double baseTypeCount = 0; //Total Direct FrameworkElement Decendants.. double fwDdCount = 0; //Total Direct FrameworkContentElement Decendants.. double fwcDdCount = 0; //Total anserstoral FrameworkElements.. double fwAnsestorCount = 0; //Total anserstoral FrameworkContentElements.. double fwcAnsestorCount = 0; //Sealed Classes.. double sealedClasses = 0; //Abstract Classes.. double abstractClasses = 0; //Total bad refrence counts.. double badInstance = 0; //Total FrameworkElement casts.. double feCount = 0; //Total FrameworkContentElement casts.. double fceCount = 0; //Total Framework castable classes.. double castCount = 0; //Total missed Framework counts.. double missCount = 0; //Loop through each Type in the reflected assembly and count casting hits. foreach (Type thisType in TypeArr) { try { //Discover ansestory.. BaseType = thisType.BaseType; if (UBaseTypes.IndexOf(BaseType) == -1) { UBaseTypes.Add(BaseType); baseTypeCount++; } if (thisType.FullName.ToString() == "System.Windows.FrameworkElement") fwDdCount++; if (thisType.FullName.ToString() == "System.Windows.FrameworkContentElement") fwcDdCount++; if (thisType.IsSubclassOf(fe.GetType())) fwAnsestorCount++; if (thisType.IsSubclassOf(fce.GetType())) fwcAnsestorCount++; if (thisType.IsSealed) sealedClasses++; if (thisType.IsAbstract) abstractClasses++; //Create an instance of a the reflected Type Thread.BeginCriticalRegion(); RefClass = FrameWork.CreateInstance(thisType.FullName.ToString()); try { //Try to cast the reflected type to a FrameworkElement.. RefClass = (FrameworkElement)RefClass; //Incriment our counters.. feCount++; castCount++; //Try to cast the reflected type to a FrameworkContentElement.. RefClass = FrameWork.CreateInstance(thisType.FullName.ToString()); RefClass = (FrameworkContentElement)RefClass; //Incriment our counters.. fceCount++; castCount++; } catch (Exception e) { try { //We missed a reference of type FrameworkElement try to cast it to a FrameworkContentElement.. RefClass = (FrameworkContentElement)RefClass; //Increment our counters.. fceCount++; castCount++; } catch (Exception ex) { //Unable to cast to a Framework type, increment our counter.. missCount++; } } finally { //Clean up our box.. RefClass = null; //Increment Class counter.. classCount++; } Thread.EndCriticalRegion(); } catch (Exception e) { //Increment Class counter.. classCount++; //This is not an instance class, increment the counter.. badInstance++; } } //Report the statistics: Console.Out.WriteLine("Total Qualified Types: " + qualifiedTypes); Console.Out.WriteLine("Total Qualified Classes: " + classCount); Console.Out.WriteLine("Total Cast able FrameWork Types: " + castCount); Console.Out.WriteLine("Total Cast able FrameworkElements: " + feCount); Console.Out.WriteLine("Total Cast able FrameworkContentElements: " + fceCount); Console.Out.WriteLine("Total Unique Base Classes: " + UBaseTypes.Count); Console.Out.WriteLine("\nTotal Missed Types: " + missCount); Console.Out.WriteLine("Total Bad Instances: " + badInstance); Console.Out.WriteLine("\nTotal Direct Decendants of FrameworkElement: " + fwDdCount); Console.Out.WriteLine("Total Direct Decendants of FrameworkContentElement: " + fwcDdCount); Console.Out.WriteLine("Total Ansestors of FrameworkElement: " + fwAnsestorCount); Console.Out.WriteLine("Total Ansestors of FrameworkContentElement: " + fwcAnsestorCount); Console.Out.WriteLine("Total Sealed Classes: " + sealedClasses); Console.Out.WriteLine("Total Abstract Classes: " + abstractClasses); Console.Out.WriteLine("\nQualified Type to Missed Type Ratio: " + ((missCount / qualifiedTypes) * 100)); Console.Out.WriteLine("FrameworkElement to Qualified Class Ratio: " + ((feCount / classCount) * 100)); Console.Out.WriteLine("FrameworkContentElement to Qualified Class Ratio: " + ((fceCount / classCount) * 100)); Console.Out.WriteLine("Bad Instance to Qualified Class Ratio: " + ((badInstance / classCount) * 100)); Console.Out.WriteLine("\nUnique Base Type to Qualified Class Ratio: " + ((UBaseTypes.Count / classCount) * 100)); Console.Out.WriteLine("\nDirect Decendants of FrameworkElement to ClassCount: " + ((fwDdCount / classCount) * 100)); Console.Out.WriteLine("Direct Decendants of FrameworkContentElement to ClassCount: " + ((fwcDdCount / classCount) * 100)); Console.Out.WriteLine("Direct Decendants of FrameworkElement to Unique Base Types: " + ((fwDdCount / UBaseTypes.Count) * 100)); Console.Out.WriteLine("Direct Decendants of FrameworkContentElement to Unique Base Types: " + ((fwcDdCount / UBaseTypes.Count) * 100)); Console.Out.WriteLine("\nAnsestors of FrameworkElement to ClassCount: " + ((fwAnsestorCount / classCount) * 100)); Console.Out.WriteLine("Ansestors of FrameworkContentElement to ClassCount: " + ((fwcAnsestorCount / classCount) * 100)); Console.Out.WriteLine("Ansestors of FrameworkElement to Unique Base Types: " + ((fwAnsestorCount / UBaseTypes.Count) * 100)); Console.Out.WriteLine("Ansestors of FrameworkContentElement to Unique Base Types: " + ((fwcAnsestorCount / UBaseTypes.Count) * 100)); Console.Out.WriteLine("\nError Ratio: " + (((badInstance + missCount) / classCount) * 100)); //Set brekpodouble here to view the results.. Console.Out.WriteLine("End of Report"); } } }
闪光效果的使用给性能带来了许多问题。但是,使用 Silverlight 无法采样浏览器显示的生成图像的任何颜色。因此,有必要使用 Joe Stegman 的 EditableImage
和 PngEncode
类创建特殊的过滤器。链接到 Joe 的博客。SparkleFilter
类使用 4 个图像缓冲区进行图像处理:AlphaBuffer
、SparkleBuffer
、Back Buffer
和 Cache Buffer
。Alpha 缓冲区包含应用 alpha 滤镜的指令,Sparkle 缓冲区包含应用亮度因子的指令,Back Buffer 是渲染动画的地方,Cache Buffer 包含原始图像的副本,在每帧开始时复制到 Back Buffer 以刷新源图像。使用 Dictionary<Color, EditableImage>
来缓存相同颜色的复合闪光缓冲区。这是测试页面的代码
<UserControl Name="UserControl1" x:Class="SilverlightTestApp.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="500" Height="500" Loaded="UserControl_Loaded"> <UserControl.Resources> <SolidColorBrush x:Key="GreenBrush" Color="#FF00FF00"/> <ImageBrush x:Key="IBrushRandom" ImageFailed="ImageBrush_ImageFailed" /> <SolidColorBrush x:Key="IBrushSparkle1" Color="Red" /> <SolidColorBrush x:Key="IBrushSparkle2" Color="Blue" /> <SolidColorBrush x:Key="IBrushSparkle3" Color="Green" /> <SolidColorBrush x:Key="IBrushSparkle4" Color="#70ff0000" /> <SolidColorBrush x:Key="IBrushSparkle5" Color="#7000ff00" /> <SolidColorBrush x:Key="IBrushSparkle6" Color="#700000ff" /> </UserControl.Resources> <Canvas Height="500" Width="500"> <Rectangle Height="500" Width="500" Fill="#FF000000" Stroke="#FF000000"/> <Grid Height="500" Width="500"> <StackPanel Margin="3,3,0,0"> <TextBlock x:Name="GreenFade" Canvas.Left="5" Canvas.Top="200" FontFamily="Verdana" FontSize="70" FontWeight="Bold" Text="Green Fade" Foreground="{StaticResource GreenBrush}"/> <TextBlock x:Name="RandomPixels" Canvas.Left="5" Canvas.Top="200" FontFamily="Verdana" FontSize="50" FontWeight="Bold" Text="Random Pixels" Foreground="{StaticResource IBrushRandom}"/> <TextBlock x:Name="SparkleText1" Canvas.Left="5" Canvas.Top="200" FontFamily="Verdana" FontSize="20" FontWeight="Bold" Text="Sparkle" Foreground="{StaticResource IBrushSparkle1}"/> <TextBlock x:Name="SparkleText2" Canvas.Left="5" Canvas.Top="200" FontFamily="Verdana" FontSize="40" FontWeight="Bold" Text="Sparkle" Foreground="{StaticResource IBrushSparkle2}"/> <TextBlock x:Name="SparkleText3" Canvas.Left="5" Canvas.Top="200" FontFamily="Verdana" FontSize="80" FontWeight="Bold" Text="Sparkle" Foreground="{StaticResource IBrushSparkle3}"/> </StackPanel> <Canvas Height="174" VerticalAlignment="Bottom"> <Rectangle Height="174" Width="500" Stroke="#FF000000"> <Rectangle.Fill> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="#FF000000"/> <GradientStop Color="#FFFFFFFF" Offset="1"/> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> <Canvas Canvas.Top="35" Canvas.Left="145" Height="154" Width="190"> <Ellipse Name="BlueCircle" Canvas.Top="45" Canvas.Left="80" Height="90" Width="90" Fill="{StaticResource IBrushSparkle6}"/> <Ellipse Name="GreenCircle" Canvas.Top="45" Canvas.Left="20" Height="90" Width="90" Fill="{StaticResource IBrushSparkle5}"/> <Ellipse Name="RedCircle" Canvas.Left="50" Height="90" Width="90" Fill="{StaticResource IBrushSparkle4}"/> </Canvas> </Canvas> </Grid> </Canvas> </UserControl>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Windows.Browser;
using Silverlight.Samples;
namespace SilverlightTestApp
{
public partial class Page : UserControl
{
private EditableImage image;
public Page()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
// Set timer for changing the first image
DispatcherTimer dt = new DispatcherTimer();
//Set Sparkle Filters
SparkleFilter sf1 = new SparkleFilter(SparkleText1, "Foreground", null);
SparkleFilter sf2 = new SparkleFilter(SparkleText2, "Foreground", null);
SparkleFilter sf3 = new SparkleFilter(SparkleText3, "Foreground", null);
SparkleFilter sf4 = new SparkleFilter(RedCircle, "Fill", null);
SparkleFilter sf5 = new SparkleFilter(GreenCircle, "Fill", null);
SparkleFilter sf6 = new SparkleFilter(BlueCircle, "Fill", null);
//Set Main Timer Intrerval
dt.Interval = new TimeSpan(0, 0, 0, 0, 100);
//Add all event handlers for timers
dt.Tick += new EventHandler(dt_Tick1);
dt.Tick += new EventHandler(dt_Tick2);
dt.Tick += new EventHandler(sf1.TimerTick);
dt.Tick += new EventHandler(sf2.TimerTick);
dt.Tick += new EventHandler(sf3.TimerTick);
dt.Tick += new EventHandler(sf4.TimerTick);
dt.Tick += new EventHandler(sf5.TimerTick);
dt.Tick += new EventHandler(sf6.TimerTick);
//Set dispatch timer for each Sparkle filter
sf1.Timer = dt;
sf2.Timer = dt;
sf3.Timer = dt;
sf4.Timer = dt;
sf5.Timer = dt;
sf6.Timer = dt;
// Start the timer, only need to start the first one since they all share the same dispatcher.
sf1.StartDispatcher();
}
bool peak = true;
byte b;
void dt_Tick1(object sender, EventArgs e)
{
if (((SolidColorBrush)this.GreenFade.Foreground).Color.G == 255) peak = true;
else if (((SolidColorBrush)this.GreenFade.Foreground).Color.G == 0) peak = false;
b = ((SolidColorBrush)this.GreenFade.Foreground).Color.G;
if (peak)
((SolidColorBrush)this.GreenFade.Foreground).Color = Color.FromArgb(255, 0, --b, 0);
else ((SolidColorBrush)this.GreenFade.Foreground).Color = Color.FromArgb(255, 0, ++b, 0); ;
}
void dt_Tick2(object sender, EventArgs e)
{
BitmapImage bm = new BitmapImage();
Random rand = new Random();
int height = 128;
int width = 128;
bool first = true;
// Re-use a single EditableImage
if (null == image)
{
image = new EditableImage(height, width);
}
// Generate a random image that changes every tick
for (int idx = 0; idx < height; idx++) // Height (y)
{
for (int jdx = 0; jdx < width; jdx++) // Width (x)
{
image.SetPixel(jdx, idx, (byte)rand.Next(255), (byte)rand.Next(255), (byte)rand.Next(255), 255);
}
}
// Get stream and set image source
bm.SetSource(image.GetStream);
RandomPixels.Foreground = new ImageBrush();
((ImageBrush)RandomPixels.Foreground).Stretch = System.Windows.Media.Stretch.UniformToFill;
((ImageBrush)RandomPixels.Foreground).ImageSource = bm;
if (first)
{
//img.InvalidateMeasure();
first = false;
}
}
private void ImageBrush_ImageFailed(object sender, ExceptionRoutedEventArgs e)
{
HtmlPage.Window.Alert(e.ErrorException.Message);
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Windows.Browser;
using Silverlight.Samples;
namespace SilverlightTestApp
{
public class SparkleFilter
{
private EditableImage _ei;
private EditableImage _eiCache;
private EditableImage _backBuffer;
private BitmapImage _bm;
private Dictionary ColorMap = new Dictionary();
public SparkleFilter(FrameworkElement ReferenceElement, String ReferenceProperty, EditableImage ImageIn)
{
try
{
_backBuffer = new EditableImage(5, 5);
this.Stretch = Stretch.None;
this.Brush = new ImageBrush();
ReferenceColor = ((SolidColorBrush)ReferenceElement.GetType().GetProperty(ReferenceProperty).GetValue(ReferenceElement,null)).Color;
ReferenceElement.GetType().GetProperty(ReferenceProperty).SetValue(ReferenceElement,this.Brush, null);
}
catch (Exception Ex)
{
throw new ArgumentException("Reference object's refrence property not supported.", Ex);
}
try
{
this.ReferenceImageSize = new Rect(0, 0,((double)ReferenceElement.GetType().GetProperty("ActualWidth").GetValue(ReferenceElement, null)), ((double)ReferenceElement.GetType().GetProperty("ActualHeight").GetValue(ReferenceElement, null)));
//this.ReferenceImageSize.Height = ((double)ReferenceElement.GetType().GetProperty("ActualHeight").GetValue(ReferenceElement, null));
//this.ReferenceImageSize.Width = ((double)ReferenceElement.GetType().GetProperty("ActualWidth").GetValue(ReferenceElement, null));
}
catch (Exception Ex)
{
throw new ArgumentException("Reference object has no Geometry!", Ex);
}
if (ImageIn != null)
_ei = ImageIn;
this.SampleReference = true;
this.ReferenceElement = ReferenceElement;
this.ImageIn = ImageIn;
Sparkle();
}
//set the Timer.
public DispatcherTimer Timer {get; set;}
//This is the FrameworkElement the effect is applied to.
public FrameworkElement ReferenceElement { get; set; }
public EditableImage ImageIn
{
get { return _ei; }
set { _ei = value; Sparkle(); }
}
public EditableImage ImageOut
{
get { return _ei; }
}
public BitmapImage ImageSource
{
get { return _bm; }
}
//ReferenceColor the color of the ReferenceElement.
public Color ReferenceColor { get; set; }
//Send the Geometry.Bounds in as size
public Rect ReferenceImageSize { get; set; }
//distance between Sparkles
public bool SparkleDeltaBasedOnImageSize { get; set; }
//global pixel modifier, modifies luminance gausienblur.
public int SparkleWeight { get; set; }
private int[][] Sparklebuffer = new int[5][];
//5x5 byte pixel array used to set a custom Sparkle patern,
//byte used as a weight to modify sample color.
public int[][] SparkleBuffer { get { return Sparklebuffer; } set { Sparklebuffer = value; } }
private byte[][] alphabuffer = new byte[5][];
//5x5 byte pixel array used to set a custom Sparkle patern,
//byte used to modify alpha component of color.
public byte[][] AlphaBuffer { get { return alphabuffer; } set { alphabuffer = value; } }
//Set to tru to sample the reference object's color. Otherwise sample the buffer.
public bool SampleReference { get; set; }
//Set to true if the filter is still rendering.
public bool IsRendering { get; set; }
public void StartDispatcher() { Timer.Start(); }
public void StopDispatcher() { Timer.Stop(); }
//Defaults to 20 frames per second. 100 milli seconds.
public TimeSpan TimeIntrival { get; set; }
public ImageBrush Brush { get; set; }
//Fill mode.
public Stretch Stretch { get; set; }
public void Sparkle()
{
// Set timer for changing the first image
if (this.Timer == null)
{
this.Timer = new DispatcherTimer();
this.Timer.Interval = new TimeSpan(0, 0, 0, 0, 100);
}
if (this.TimeIntrival == null)
this.TimeIntrival = Timer.Interval;
if (SparkleWeight == 0)
SparkleWeight = 10;
if (Sparklebuffer[0] == null)
{
Sparklebuffer = new int[5][];
Sparklebuffer[0] = new int[] { 25, 50, 85, 50, 25 };
Sparklebuffer[1] = new int[] { 50, 85, 125, 85, 50 };
Sparklebuffer[2] = new int[] { 85, 125, 150, 125, 85 };
Sparklebuffer[3] = new int[] { 50, 85, 125, 85, 50 };
Sparklebuffer[4] = new int[] { 25, 50, 85, 50, 25 };
}
if (alphabuffer[0] == null)
{
alphabuffer = new byte[5][];
alphabuffer[0] = new byte[] { 20, 100, 130, 100, 20 };
alphabuffer[1] = new byte[] { 100, 130, 200, 130, 100 };
alphabuffer[2] = new byte[] { 130, 200, 250, 200, 130 };
alphabuffer[3] = new byte[] { 100, 130, 200, 130, 100 };
alphabuffer[4] = new byte[] { 20, 100, 130, 100, 20 };
}
Timer.Tick += new EventHandler(TimerTick);
}
/*
* RGB to YUV LuminanceTransform
*/
private static Color LuminanceTransform(Color SampleColor, int Weight, byte Alpha)
{
byte Y, U, V;
Y = (byte)(((66 * SampleColor.R + 129 * SampleColor.G + 25 * SampleColor.B + 128) >> 8) + 16);
U = (byte)(((-38 * SampleColor.R - 74 * SampleColor.G + 112 * SampleColor.B + 128) >> 8) + 128);
V = (byte)(((112 * SampleColor.R - 94 * SampleColor.G - 18 * SampleColor.B + 128) >> 8) + 128);
Y = (byte)Math.Min((Y+Weight), 255);
int C = Y - 16;
int D = U - 128;
int E = V - 128;
SampleColor.R = (byte)Math.Max(Math.Min(((298 * C + 409 * E + 128) >> 8), 255), 0);
SampleColor.G = (byte)Math.Max(Math.Min(((298 * C - 100 * D - 208 * E + 128) >> 8), 255), 0);
SampleColor.B = (byte)Math.Max(Math.Min(((298 * C + 516 * D + 128) >> 8), 255), 0);
SampleColor.A = Alpha;
return SampleColor;
}
internal void TimerTick(object sender, EventArgs e)
{
this.IsRendering = true;
this.Timer.Stop();
_bm = new BitmapImage();
Random rand = new Random();
int height = 128;
int width = 128;
//Re-use a single EditableImage
if (_ei == null)
{
width = Math.Min(128,Convert.ToInt32(ReferenceImageSize.Width));
height = Math.Min(128,Convert.ToInt32(ReferenceImageSize.Height));
if (width == 128 || height == 128)
Stretch = Stretch.UniformToFill;
_ei = new EditableImage(width, height);
for (int idx = 0; idx < height; idx++) // Height (y)
{
for (int jdx = 0; jdx < width; jdx++) // Width (x)
{
_ei.SetPixel(jdx, idx, ReferenceColor.R, ReferenceColor.G, ReferenceColor.B, ReferenceColor.A);
}
}
//Cache the origional image
_eiCache = new EditableImage(_ei.Width,_ei.Height);
_eiCache.SetPixels(_ei);
}
int density = (Convert.ToInt32(ReferenceImageSize.Width) + Convert.ToInt32(ReferenceImageSize.Height))/2;
Color SampleColor;
int sampleY;
int sampleX;
//Replace image buffer with cache.
_ei.SetPixels(_eiCache);
// Generate a random image that changes every tick
for (int sp = 0; sp < density; sp++)
{
sampleY = rand.Next(_ei.Height - 5); //Have to pad, 5 pixels on both sides.
sampleX = rand.Next(_ei.Width - 5);
if (SampleReference)
SampleColor = ReferenceColor;
else
SampleColor = _ei.GetPixel(sampleY,sampleX);
if (!ColorMap.ContainsKey(SampleColor))
{
for (int y = 0; y < 5; y++)
{
for (int x = 0; x < 5; x++)
{
Color c = LuminanceTransform(SampleColor, Sparklebuffer[y][x], alphabuffer[y][x]);
_backBuffer.SetPixel(x, y, c);
}
}
ColorMap.Add(SampleColor, _backBuffer);
}
else
{
_backBuffer = ColorMap[SampleColor];
}
for (int idx = sampleY, y1 = 0; idx < (_backBuffer.Height + sampleY); idx++, y1++)
{
for (int jdx = sampleX, x1 = 0; jdx < (_backBuffer.Width + sampleX); jdx++, x1++)
{
_ei.BlendAlphaPixel(jdx, idx, _backBuffer.GetPixel(y1, x1));
}
}
}
// Get stream and set image source
_bm.SetSource(_ei.GetStream);
((ImageBrush)this.Brush).Stretch = this.Stretch;
((ImageBrush)this.Brush).ImageSource = _bm;
this.Timer.Start();
this.IsRendering = false;
}
}
}
就是这样!学到的教训
使用 C# 中的 Reflection 时必须谨慎。很容易犯许多错误,并且必须采取安全措施以确保恶意代码无法注入方法调用。最好的方法是限制包含反射代码的方法的可访问性,使用 internal、private 或 protected 访问。关注点
Silverlight 不允许采样渲染内容。此外,当添加新内容时,所有元素都会被重新混合,在本例中,使用 Joe Stegman 的 EditableImage 类可以避免这种情况。外部链接:
- 维基百科 - XML 架构
- 维基百科 - 文档类型定义
- 维基百科 - 矢量图形
- 维基百科 - 矢量化(计算机图形学)
- 维基百科 - 可缩放矢量图形
- 凯尔特结图标 © Catherine Prickett
XAML 叠加原型研究



XAML 概述
WPF 命名空间 XAML 扩展
XAML 命名空间 (x:) 语言特性
标记扩展和 XAML
如何:定义和引用资源
XAML 命名空间和命名空间映射
x:Key
x:Name
x:Type
代码隐藏和 XAML
装箱、拆箱和自动装箱
路由事件概述
WPF XAML 命名空间
FrameworkElement
FrameworkContentElement
Windows Presentation Foundation 部分信任安全
XAML 语法术语
Windows Presentation Foundation 入门
构建 WPF 应用程序 (WPF)
依赖属性概述
XAMLPad