什么是故障艺术(Glitch Art 风)?我们熟知的抖音的 LOGO 正是故障艺术其中一种表现形式。它有一种魔幻的感觉,看起来具有闪烁、震动的效果,很吸引人眼球。故障艺术它模拟了画面信号出现故障导致成像错误的感觉。青色色块与红色色块无法重合就是这种故障的体现。从胶片时代开始到今天的数码时代,这种故障一直是观众非常熟悉的现象。

上个月看到CSS 故障艺术这篇文章,最近想转换心情于是开始抄它的动画了(顺便为博客园的UWP板块吊命)。CSS的mix-blend-mode好像很好用,这次用UWP中Win2D的BlendEffect模仿它的玩法。

Microsoft.Graphics.Canvas.Effects命名空间下的BlendEffect 用于组合两张图片(分别是作为输入源的Background和Foreground),它包含多种模式,如下图所示:

要使用BlendEffect,需要为它设置Mode,并将Foreground和Background设置为CompositionEffectSourceParameter

  1. var blendEffect = new BlendEffect()
  2. {
  3. Mode = BlendEffectMode.Screen,
  4. Foreground = new CompositionEffectSourceParameter("Main"),
  5. Background = new CompositionEffectSourceParameter("Tint"),
  6. };

然后使用这个BlendEffect创建Brush,并用SetSourceParameter设置它的Foreground和Background为需要混合的两个CompositionBrush

  1. var effectFactory = compositor.CreateEffectFactory(blendEffect);
  2. var blendEffectBrush = effectFactory.CreateBrush();
  3. blendEffectBrush.SetSourceParameter("Main", foregroundBrush);
  4. blendEffectBrush.SetSourceParameter("Tint", backgroundBrush);

这样Foreground和Background就会根据Mode指定的模式进行混合。

CompositionAPI没有提供文字书写的功能,倒是Win2D有提供这个功能,我在用Win2D实现镂空文字有介绍如何使用Win2D创建出一个包含文字的CompositionSurfaceBrush,这次把它重新封装了一下,然后使用下面的代码显示各种模式下的混合效果。

  1. private void AddTextToRoot(BlendEffectMode blendEffectMode)
  2. {
  3. var redBrushWrapper = CreateTextToBrushWrapper(blendEffectMode.ToString(), Colors.Red);
  4. var blueBrushWrapper = CreateTextToBrushWrapper(blendEffectMode.ToString(), Colors.Cyan);
  5. blueBrushWrapper.Brush.Offset = new Vector2(-4f, 0);
  6. var textVisual = Compositor.CreateSpriteVisual();
  7. textVisual.Brush = CreateBrush(blueBrushWrapper.Brush, redBrushWrapper.Brush, blendEffectMode);
  8. textVisual.Size = new Vector2(400, 70);
  9. var background = new Rectangle { Height = 70, Width = 400 };
  10. ElementCompositionPreview.SetElementChildVisual(background, textVisual);
  11. Root.Children.Add(background);
  12. }
  13. private CompositionBrush CreateBrush(CompositionBrush foreground, CompositionBrush background, BlendEffectMode blendEffectMode)
  14. {
  15. var compositor = Window.Current.Compositor;
  16. var effect = new BlendEffect()
  17. {
  18. Mode = blendEffectMode,
  19. Foreground = new CompositionEffectSourceParameter("Main"),
  20. Background = new CompositionEffectSourceParameter("Tint"),
  21. };
  22. var effectFactory = compositor.CreateEffectFactory(effect);
  23. var compositionBrush = effectFactory.CreateBrush();
  24. compositionBrush.SetSourceParameter("Main", foreground);
  25. compositionBrush.SetSourceParameter("Tint", background);
  26. return compositionBrush;
  27. }

上图为各种BlendEffectMode下红色和蓝色文字的混合效果。

理解BlendEffect的用法和作用后,现在可以做些故障艺术的尝试了。拿下面这张图片来尝试:

  1. private (CompositionBrush compositionBrush, CompositionSurfaceBrush compositionSurfaceBrush) CreateBrush(string imageName, Color color)
  2. {
  3. var compositor = Window.Current.Compositor;
  4. var loadedSurface = LoadedImageSurface.StartLoadFromUri(new Uri("ms-appx:///Assets/Images/" + imageName));
  5. var compositionSurfaceBrush = compositor.CreateSurfaceBrush();
  6. compositionSurfaceBrush.Surface = loadedSurface;
  7. var compositionBrush = CreateBrush(compositionSurfaceBrush, compositor.CreateColorBrush(color), BlendEffectMode.Lighten);
  8. return (compositionBrush, compositionSurfaceBrush);
  9. }

使用上面的代码分别将这张图片与从Colors.Red(255,0,0)和Colors.Cyan(0,255,255)创建出来的CompositionColorBrush使用BlendEffectMode.Lighten进行混合,得到下面的效果:

  1. var compositor = Window.Current.Compositor;
  2. var (foreground, foregroundBrush) = CreateBrush(imageName, Colors.Cyan);
  3. var (background, backgroundBrush) = CreateBrush(imageName, Colors.Red);
  4. foregroundBrush.Offset = new Vector2(10, 0);
  5. var brush = CreateBrush(foreground, background, BlendEffectMode.Darken);

使用BlendEffectMode.Darken将两张图片混合,再把其中一张图偏移10像素后,得到如下效果,这样图片的故障艺术分格就完成了:

继续抄动画,连标题一起抄。将两个文字的CompositionSurfaceBrush使用BlendEffectMode.Lighten进行混合,然后对它们的Offset做动画:

  1. CreateBrush(backgroundWrapper.Brush, foregroundWrapper.Brush, BlendEffectMode.Lighten);
  2. StartOffsetAnimation(backgroundWrapper.Brush, TimeSpan.FromSeconds(0.95), TimeSpan.Zero);
  3. StartOffsetAnimation(foregroundWrapper.Brush, TimeSpan.FromSeconds(1.1), TimeSpan.FromSeconds(0.2));
  4. private void StartOffsetAnimation(CompositionSurfaceBrush brush, TimeSpan duration, TimeSpan delay)
  5. {
  6. var offsetAnimation = Compositor.CreateVector2KeyFrameAnimation();
  7. offsetAnimation.Duration = duration;
  8. offsetAnimation.DelayTime = delay;
  9. offsetAnimation.IterationBehavior = AnimationIterationBehavior.Forever;
  10. void addKey(float key, float top, float left)
  11. {
  12. offsetAnimation.InsertKeyFrame(key, new Vector2(top * 2.5f, left * 2.5f));
  13. };
  14. addKey(.1f, -0.4f, -1.1f);
  15. addKey(.2f, 0.4f, -0.2f);
  16. addKey(.3f, 0f, .5f);
  17. addKey(.4f, -0.3f, -0.7f);
  18. addKey(.5f, 0, .2f);
  19. addKey(.6f, 1.8f, 1.2f);
  20. addKey(.7f, -1f, .1f);
  21. addKey(.8f, -0.4f, -0.9f);
  22. addKey(.9f, 0, 1.2f);
  23. addKey(1, 0, -1.2f);
  24. brush.StartAnimation(nameof(CompositionSurfaceBrush.Offset), offsetAnimation);
  25. }

再在中间放一条上下移动的黑线,故障的效果就出来了:

这次没法完全抄CSS的动画了,我能力有限,大致抄个意思得了。

创建两个白色黑底带阴影(BlurAmount = 0)的文字(如下所示):

错开几个像素后用BlendEffectMode.Multiply混合在一起,一种更复杂的故障艺术动画就完成了:

再在后面放一个白色的文字,分别调整前面两个文字的高度,效果如下:

然后对前面两个文字的高度进行动画,效果就出来了:

  1. StartHeightAnimation(redBrushWrapper, new List<(double, double)>() { (0, 1), (20, 80), (60, 15), (100, 105) }, TimeSpan.FromSeconds(1), TimeSpan.Zero);
  2. StartHeightAnimation(blueBrushWrapper, new List<(double, double)>() { (0, 110), (20, 112.5), (35, 30), (50, 100), (60, 50), (70, 85), (80, 55), (100, 1) }, TimeSpan.FromSeconds(1.5), TimeSpan.Zero);
  3. private void StartHeightAnimation(TextToBrushWrapper brush, List<(double, double)> keyFrames, TimeSpan duration, TimeSpan delay)
  4. {
  5. var storyboard = new Storyboard();
  6. var animation = new DoubleAnimationUsingKeyFrames();
  7. animation.EnableDependentAnimation = true;
  8. Storyboard.SetTarget(animation, brush);
  9. Storyboard.SetTargetProperty(animation, nameof(TextToBrushWrapper.Height));
  10. foreach (var item in keyFrames)
  11. {
  12. animation.KeyFrames.Add(new LinearDoubleKeyFrame { KeyTime = duration / 100 * item.Item1, Value = item.Item2 });
  13. }
  14. storyboard.Children.Add(animation);
  15. storyboard.RepeatBehavior = RepeatBehavior.Forever;
  16. storyboard.BeginTime = delay;
  17. storyboard.Begin();
  18. }

为了从大佬那里抄动画我还特地新建了一个项目

原文还有很多动画可以参考,但我半途而废了,搞明白大致的原理后新鲜感就过去了。看起来CPU、GPU的占用也不高,只是暂时想不到有什么实际应用的场景,博客也写了,图片也截了,玩腻就抛弃。

DinoChan_uwp_design_and_animation_lab 一个收集UWP的设计和动画的项目。

版权声明:本文为dino623原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/dino623/p/UWP_Glitch_Art.html