As described by MSDN, DrawingVisual is a lightweight drawing class that is used to render shapes, images, or text. This class is considered lightweight because it does not provide layout, input, focus, or event handling, which improves its performance.
如MSDN所述, DrawingVisual是一种轻量级的绘图类,用于呈现形状,图像或文本。 此类被认为是轻量级的,因为它不提供布局,输入,焦点或事件处理,从而提高了其性能。
Before I started coding, I consulted MSDN page to understand the basic of DrawingVisual Objects and WPF Graphics Rendering Overview.
在开始编码之前,我查阅了MSDN页面,以了解DrawingVisual Objects和WPF Graphics Rendering Overview的基础 。
Many of the elements/controls that we commonly use in WPF like Button
, ComboBox
, Shape
, and others have these characteristics:
我们通常在WPF中使用的许多元素/控件(如Button
, ComboBox
, Shape
)以及其他具有以下特征的控件:
The scope of DrawingVisual
is to propose a lightweight approach to object drawing.
DrawingVisual
的范围是提出一种轻量级的对象绘制方法。
Regarding the matrix rain effect, I take some ideas on how to develop it from CodePen, which is an online community for testing and showcasing user-created HTML, CSS and JavaScript code snippets.
关于矩阵降雨效应,我对如何从CodePen开发它提出了一些想法, CodePen是一个在线社区,用于测试和展示用户创建HTML,CSS和JavaScript代码段。
I assume the reader knows WPF dispatcher. Briefly, when you execute a WPF application, it automatically creates a new Dispatcher
object and calls its Run
method. All the visual elements will be created by the dispatcher thread and all the modification to visual elements must be executed on Dispatcher thread.
我假设读者知道WPF调度程序。 简要地说,当您执行WPF应用程序时,它会自动创建一个新的Dispatcher
对象并调用其Run
方法。 所有视觉元素将由分派器线程创建,并且对视觉元素的所有修改都必须在Dispatcher线程上执行。
My sample is made up of two projects:
我的样本由两个项目组成:
This is the core of the solution. This project implements a UserControl
that simulates the Matrix digital rain effect. The UserControl
can be used in any Window/Page, etc.
这是解决方案的核心。 该项目实现了一个模拟Matrix数字雨效应的UserControl
。 UserControl
可以在任何窗口/页面等中使用。
Set up parameter.
设置参数。
The SetParameter
method allows to set up some animation parameter:
SetParameter
方法允许设置一些动画参数:
framePerSecond
: Frame per second refresh (this parameter affect the "speed" of the rain)
framePerSecond
:每秒刷新的帧(此参数影响下雨的“速度”)
fontFamily
: Font family used
fontFamily
:使用的字体家族
fontSize
: Dimension of the font used
fontSize
:使用的字体的尺寸
backgroundBrush
: Brush used for the background
backgroundBrush
:用于backgroundBrush
画笔
textBrush
: Brush used for the text
textBrush
:用于文本的笔刷
characterToDisplay
: The character used for the rain will be randomly chosen from this string
characterToDisplay
:用于下雨的字符将从此string
随机选择
...
public void SetParameter(int framePerSecond = 0, FontFamily fontFamily = null,
int fontSize = 0, Brush backgroundBrush = null,
Brush textBrush = null, String characterToDisplay = "")
...
Start the animation.
开始动画。
The Start
and Stop
methods allow to start and stop the animation:
Start
和Stop
方法允许开始和停止动画:
public void Start() {
_DispatcherTimer.Start();
}
public void Stop() {
_DispatcherTimer.Stop();
}
...
The animation is controlled through System.Timers.Timer. I prefer this solution over System.Windows.Threading.DispatcherTimer because the DispatcherTimer
is re-evaluated at the top of every Dispatcher loop and the timer is not guaranteed to execute exactly when the time interval occurs.
动画是通过System.Timers.Timer控制的。 我喜欢此溶液System.Windows.Threading.DispatcherTimer因为DispatcherTimer
是重新评估在每个分派器循环的顶部和计时器不能保证当的时间间隔发生准确地执行。
Every tick, the method _DispatcherTimerTick(object sender, EventArgs e)
is called. This method is not executed on the Dispatcher thread so the first thing is to sync the call on the Dispatcher thread because we need to work with some resources accessible only by the main thread.
每次滴答时,都会_DispatcherTimerTick(object sender, EventArgs e)
方法。 此方法不在Dispatcher线程上执行,因此第一件事是同步Dispatcher线程上的调用,因为我们需要使用只能由主线程访问的某些资源。
...
private void _DispatcherTimerTick(object sender, EventArgs e)
{
if (!Dispatcher.CheckAccess()) {
//synchronize on main thread
System.Timers.ElapsedEventHandler dt = _DispatcherTimerTick;
Dispatcher.Invoke(dt,sender,e);
return;
}
....
}
Draw the new frame.
绘制新框架。
Once the call from the timer is on the dispatcher thread, it performs two operations:
一旦计时器调用在调度程序线程上,它将执行两个操作:
Design the new frame
设计新框架
The frame is created by the method _RenderDrops()
. Here is a new DrawingVisual
and its DrawingContext are created to draw objects. The drawing context allows drawing line, ellipse, geometry, images and many more.
框架是通过_RenderDrops()
方法创建的。 这是一个新的DrawingVisual
并创建了DrawingContext来绘制对象。 绘图上下文允许绘制线条,椭圆,几何形状,图像等。
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
First, the method creates a black background with a 10% of opacity (I will explain later why I put 10% opacity).
首先,该方法会创建不透明度为10%的黑色背景(我将在后面解释为什么我将不透明度设为10%)。
After this, we scroll through an array called _Drops
.
之后,我们滚动浏览一个名为_Drops
的数组。
This array represents the column along which the letters are drawn (see the red column in the image). The value of the array represents the row (see the blue circle in the image) where a new letter must be drawn. When the value of the drop reaches the 'bottom' of the image, the drop re-starts from the top immediately or randomly after a series of cycle.
该数组表示沿其绘制字母的列(请参见图像中的红色列)。 数组的值表示必须绘制新字母的行(请参见图像中的蓝色圆圈)。 当墨滴的值达到图像的“底部”时,墨滴会立即或在一系列循环后随机从顶部重新开始。
...
//looping over drops
for (var i = 0; i < _Drops.Length; i++) {
// new drop position
double x = _BaselineOrigin.X + _LetterAdvanceWidth * i;
double y = _BaselineOrigin.Y + _LetterAdvanceHeight * _Drops[i];
// check if new letter does not goes outside the image
if (y + _LetterAdvanceHeight < _CanvasRect.Height) {
// add new letter to the drawing
var glyphIndex = _GlyphTypeface.CharacterToGlyphMap[_AvaiableLetterChars[
_CryptoRandom.Next(0, _AvaiableLetterChars.Length - 1)]];
glyphIndices.Add(glyphIndex);
advancedWidths.Add(0);
glyphOffsets.Add(new Point(x, -y));
}
//sending the drop back to the top randomly after it has crossed the image
//adding a randomness to the reset to make the drops scattered on the Y axis
if (_Drops[i] * _LetterAdvanceHeight > _CanvasRect.Height &&
_CryptoRandom.NextDouble() > 0.775) {
_Drops[i] = 0;
}
//incrementing Y coordinate
_Drops[i]++;
}
// add glyph on drawing context
if (glyphIndices.Count > 0) {
GlyphRun glyphRun = new GlyphRun(_GlyphTypeface,0,false,_RenderingEmSize,
glyphIndices,_BaselineOrigin,advancedWidths,glyphOffsets,
null,null,null,null,null);
drawingContext.DrawGlyphRun(_TextBrush, glyphRun);
}
...
To recap the method, _RenderDrops()
generates DrawingVisual
that contains a background with opacity and the new drops letters. For example:
回顾一下该方法, _RenderDrops()
生成DrawingVisual
包含与不透明度和新的滴字母的背景。 例如:
Copy the new frame over the previous one
将新框架复制到上一个框架
As seen before, the new frame only generates the "new" letter, but how can we fade away the previous letters?
如前所述,新框架仅生成“新”字母,但是我们如何淡出以前的字母呢?
This is performed by the background of the frame which is black with 10% opacity. When we copy a new frame over the previous frame, the blending makes the trick. The "copy over" weakens the previous letters luminance as shown in this example:
这是通过具有10%不透明度的黑色框架背景执行的。 当我们在前一帧上复制新帧时,融合就可以了。 如以下示例所示,“ copy over”削弱了前面字母的亮度:
P.S.: I render the Drawing Visual on a RenderTargetBitmap. I could apply this directly on my image:
PS :我在RenderTargetBitmap上绘制图形的视觉效果。 我可以直接在图片上应用:
_MyImage.Source = _RenderTargetBitmap
The problem with this solution is that at every cycle, this operation allocates a lot of memory at every cycle. To overlap this problem, I use WriteableBitmap which is allocated in memory only once in the initialization code.
该解决方案的问题在于,在每个周期,该操作在每个周期分配大量内存。 为了解决这个问题,我使用WriteableBitmap ,它在初始化代码中仅在内存中分配一次。
...
_WriteableBitmap.Lock();
_RenderTargetBitmap.CopyPixels(new Int32Rect(0, 0, _RenderTargetBitmap.PixelWidth,
_RenderTargetBitmap.PixelHeight),
_WriteableBitmap.BackBuffer,
_WriteableBitmap.BackBufferStride *
_WriteableBitmap.PixelHeight,
_WriteableBitmap.BackBufferStride);
_WriteableBitmap.AddDirtyRect(new Int32Rect(0, 0, _RenderTargetBitmap.PixelWidth,
_RenderTargetBitmap.PixelHeight));
_WriteableBitmap.Unlock();
...
This project references MatrixRain
and showcases the potentiality of MatrixRain
user control. The code is not commented, because it is so simple that does not need to be.
该项目引用了MatrixRain
并展示了MatrixRain
用户控件的潜力。 该代码未注释,因为它非常简单,不需要注释。
In the MainWindow.xaml, a MatrixRain
control is added to the window:
在MainWindow.xaml中 ,将MatrixRain
控件添加到窗口中:
...
xmlns:MatrixRain="clr-namespace:MatrixRain;assembly=MatrixRain"
...
...
During Initialization, I read a special font from the embedded resources and pass it to MatrixRain
control:
在初始化期间,我从嵌入的资源中读取了一种特殊的字体,并将其传递给MatrixRain
控件:
FontFamily rfam = new FontFamily(new Uri("pack://application:,,,"),
"./font/#Matrix Code NFI");
mRain.SetParameter(fontFamily: rfam);
Please pay attention to the font. This is the link where I found it: https://www.1001fonts.com/matrix-code-nfi-font.html. This is free to use only for personal purposes.
请注意字体。 这是我找到它的链接: https : //www.1001fonts.com/matrix-code-nfi-font.html 。 这是免费的,只能用于个人目的。
Two buttons: Start
and Stop
; command the animation:
两个按钮:“ Start
和“ Stop
; 命令动画:
private void _StartButtonClick(object sender, RoutedEventArgs e)
{
mRain.Start();
}
private void _StopButtonClick(object sender, RoutedEventArgs e)
{
mRain.Stop();
}
Two buttons: Set1
and Set2
; command the text color:
两个按钮: Set1
和Set2
; 命令文本颜色:
private void _ChangeColorButtonClick(object sender, RoutedEventArgs e)
{
mRain.SetParameter(textBrush: ((Button)sender).Background);
}
Quote:引用:This is your last chance. After this, there is no turning back. You take the blue pill - the story ends, you wake up in your bed and believe whatever you want to believe. You take the red pill - you stay in Wonderland, and I show you how deep the rabbit hole goes. Remember: all I'm offering is the truth. Nothing more.
这是您最后的机会。 此后,就没有回头路了。 您服用蓝色药丸-故事结束了,您在床上醒来,相信了您想相信的一切。 您服用红色药丸-您留在仙境,我告诉您兔子洞有多深。 记住:我所提供的只是事实。 而已。
The red pill is the DrawingVisual
, so make your choice.
红色药丸是DrawingVisual
,因此请选择。
P.S.: To generate the letter random, I used a personal "CryptoRandom
" class (source included) instead of the canonical Random
method. This because Random
method generates a 'pseudo random' number. Follow this link if you want to dig in.
PS :要生成随机字母,我使用了个人的“ CryptoRandom
”类(包括源),而不是规范的Random
方法。 这是因为Random
方法会生成“伪随机”数。 如果您想深入了解,请点击此链接 。
If you have any suggestions for modifications, please don't hesitate to contact me.
如果您有任何修改建议,请随时与我联系。
翻译自: https://www.codeproject.com/Articles/5164199/Matrix-Style-Rain-in-Csharp-with-WPF