WPF+SkiaSharp实现自绘弹幕效果
作者:驚鏵 发布时间:2022-09-30 09:52:38
标签:WPF,SkiaSharp,弹幕
SkiaSharp 自绘弹幕效果
框架使用.NET60
;
Visual Studio 2022
;
项目使用 MIT 开源许可协议;
接着上一篇 WPF 弹幕
上期有网友建议使用Skia实现弹幕。
弹幕消息使用
SKElement
做弹幕展现,然后在SKCanvas
进行绘制弹幕。由于需要绘制矩形与文本所以需要使用到
SKBitmap
进行绘制弹幕类。创建
SKBitmap
设置宽(根据文本的长度定义宽度)与高度40
。创建对象
SKCanvas
并实例化的时候将SKBitmap
传入,然后对SKCanvas
进行绘制背景DrawRoundRect
与文本DrawText
,使用属性记录X
与Y
的值方便在动画的时候让弹幕动起来。Barrage
在Render
的时候进行绘制弹幕图片DrawImage(SKBitmap,x,y)
。弹幕每次移动多少值 等于
SKCanvas
的宽除以弹幕的宽
。当弹幕移动
Move()
时如超过-Width
则通过out
返回GUID
就移除弹幕对象。
实现代码
1) 准备 MsgInfo 弹幕消息类如下:
using System;
using SkiaSharp;
namespace SkiaSharpBarrage
{
/// <summary>
/// msg info
/// </summary>
public class MsgInfo
{
private string _msg;
public string GUID;
public MsgInfo(string msg, SKTypeface _font, float windowsWidth)
{
_msg = msg;
var _random = new Random();
var skColor = new SKColor((byte) _random.Next(1, 255),
(byte) _random.Next(1, 255), (byte) _random.Next(1, 233));
using var paint = new SKPaint
{
Color = skColor,
Style = SKPaintStyle.Fill,
IsAntialias = true,
StrokeWidth = 2
};
paint.Shader = SKShader.CreateLinearGradient(
new SKPoint(0, 0),
new SKPoint(1, 1),
new[] {SKColors.Transparent, skColor},
new float[] {0, 1},
SKShaderTileMode.Repeat);
using var paintText = new SKPaint
{
Color = SKColors.White,
IsAntialias = true,
Typeface = _font,
TextSize = 24
};
var textBounds = new SKRect();
paintText.MeasureText(msg, ref textBounds);
var width = textBounds.Width + 100;
SKImage skImage;
using (var bitmap = new SKBitmap((int) width, 40, true))
using (var canvas = new SKCanvas(bitmap))
{
canvas.DrawRoundRect(0, 0, width, 40, 20, 20, paint);
canvas.DrawText(msg, width / 2 - textBounds.Width / 2, bitmap.Height / 2 + textBounds.Height / 2,
paintText);
var image = SKImage.FromBitmap(bitmap);
skImage = image;
}
SKImage = skImage;
Width = width;
X = windowsWidth + Width;
CanvasWidth = windowsWidth;
CostTime = TimeSpan.FromMilliseconds(Width);
GUID = Guid.NewGuid().ToString("N");
}
public float X { get; set; }
public float Y { get; set; }
public float Width { get; set; }
public float CanvasWidth { get; set; }
public SKImage SKImage { get; set; }
public float MoveNum => CanvasWidth / (float) CostTime.TotalMilliseconds;
public TimeSpan CostTime { get; set; }
/// <summary>
/// 定时调用,移动指定距离
/// </summary>
public void Move(out string guid)
{
guid = string.Empty;
X = X - MoveNum;
if (X <= -Width)
guid = GUID;
}
}
}
2) 新建 Barrage.cs 类如下:
using System.Collections.Generic;
using System.Linq;
using SkiaSharp;
namespace SkiaSharpBarrage
{
public class Barrage
{
private readonly SKTypeface _font;
private readonly List<MsgInfo> _MsgInfo;
private int _num, _index;
private double _right, _top;
private float _width;
private readonly float _height;
public Barrage(SKTypeface font, float width, float height, List<string> strList)
{
_width = width;
_height = height;
_font = font;
_num = (int) height / 40;
_MsgInfo = new List<MsgInfo>();
foreach (var item in strList) BuildMsgInfo(item);
}
private void BuildMsgInfo(string text)
{
_index++;
if (_right != 0)
_width = (float) _right;
var model = new MsgInfo(text, _font, _width);
_right = _right == 0 ? _height + model.Width : _right;
var y = _height - 40;
_top = _top + 40 >= y ? 40 : _top;
model.Y = (float) _top;
_MsgInfo.Add(model);
_top += 60;
}
public void AddBarrage(string text)
{
BuildMsgInfo(text);
}
public void Render(SKCanvas canvas, SKTypeface font, int width, int height, List<string> strList)
{
for (var i = 0; i < _MsgInfo.Count; i++)
{
var info = _MsgInfo[i];
var guid = string.Empty;
info.Move(out guid);
if (!string.IsNullOrEmpty(guid))
{
var model = _MsgInfo.FirstOrDefault(x => x.GUID == guid);
_MsgInfo.Remove(model);
}
canvas.DrawImage(info.SKImage, info.X, info.Y);
}
}
}
}
3) MainWindow.xaml.cs 如下:
<wpfdev:Window x:Class="SkiaSharpBarrage.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wpfdev="https://github.com/WPFDevelopersOrg/WPFDevelopers"
xmlns:skia="clr-namespace:SkiaSharp.Views.WPF;assembly=SkiaSharp.Views.WPF"
mc:Ignorable="d" WindowStartupLocation="CenterScreen"
ResizeMode="CanMinimize"
Title="SkiaSharpBarrage - 弹幕篇" Height="450" Width="800">
<Grid Margin="4">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<MediaElement Stretch="Uniform" Grid.RowSpan="2"
Name="myMediaElement" />
<skia:SKElement x:Name="skElement" />
<Grid Grid.Row="1" Name="MyGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox wpfdev:ElementHelper.IsWatermark="True"
x:Name="tbBarrage"
wpfdev:ElementHelper.Watermark="请弹幕内容" />
<Button Grid.Column="1" Style="{StaticResource PrimaryButton}"
Content="发射弹幕" Margin="4,0,0,0"
Click="ButtonBase_OnClick" />
</Grid>
</Grid>
</wpfdev:Window>
3) 逻辑 MainWindow.xaml.cs 如下:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using SkiaSharp;
using SkiaSharp.Views.Desktop;
namespace SkiaSharpBarrage
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow
{
private readonly Barrage _barrage;
private readonly SKTypeface _font;
private readonly List<string> list = new List<string>();
public MainWindow()
{
list.Add("2333");
list.Add("测试弹幕公众号:WPF开发者");
list.Add("很难开心");
list.Add("LOL~");
list.Add("青春记忆");
list.Add("bing");
list.Add("Microsoft");
InitializeComponent();
var index = SKFontManager.Default.FontFamilies.ToList().IndexOf("微软雅黑");
_font = SKFontManager.Default.GetFontStyles(index).CreateTypeface(0);
_barrage = new Barrage(_font, (float) Width, (float) Height - (float) MyGrid.ActualHeight, list);
skElement.PaintSurface += SkElement_PaintSurface;
Loaded += delegate
{
myMediaElement.Source =
new Uri(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Leagueoflegends.mp4"));
};
_ = Task.Run(() =>
{
try
{
while (true)
{
Dispatcher.Invoke(() => { skElement.InvalidateVisual(); });
_ = SpinWait.SpinUntil(() => false, 1000 / 60); //每秒60帧
}
}
catch (Exception e)
{
}
});
}
private void SkElement_PaintSurface(object? sender, SKPaintSurfaceEventArgs e)
{
var canvas = e.Surface.Canvas;
canvas.Clear();
_barrage.Render(canvas, _font, e.Info.Width, e.Info.Height, list);
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
_barrage.AddBarrage(tbBarrage.Text);
}
}
}
实现效果
来源:https://mp.weixin.qq.com/s/O8wyKm9ZSEhjagTq24WuPw


猜你喜欢
- 在使用springMVC框架构建web应用,客户端常会请求字符串、整型、json等格式的数据,通常使用@ResponseBody注解使 co
- 主要是应对这种需求:软件只允许启动一次。将这个问题转化一下,可以这样描述:对于一个软件,在启动一个进程之后,不允许启动其它进程,如果第二次打
- 某天突然发现idea非常重要的快捷键ctrl+shift+f无效了,网上搜了很多都说是qq快捷键冲突,但是找了下qq快捷键却没有解决,现在给
- 任何一个类都是Class类的实例对象,这个实例对象有三种表示方式第一种表示方式(任何一个类都有一个隐含的静态成员变量class):Class
- 基础知识介绍: @RequestBody主要用来接收前端传递给后端的json字符串中的
- 最近做局域网socket连接问题,要在多个activity之间公用一个socket连接,就在网上搜了下资料,感觉还是application方
- Android通过wifi连接手机的方法,供大家参考,具体内容如下1.首先电脑,手机连接同一个网络2.在Android studio中Ter
- 提示:建议一定要看后面的@RequestBody的核心逻辑源码以及六个重要结论!本文前半部分的内容都是一些基本知识常识,可选择性跳过。声明:
- springboot整合vue实现上传下载文件,供大家参考,具体内容如下环境springboot 1.5.x完整代码下载:springboo
- ref和out都是C#中的关键字,所实现的功能也差不多,都是指定一个参数按照引用传递。对于编译后的程序而言,它们之间没有任何区别,也就是说它
- 一:hibernate-validator 基础1. 简介:通过使用注解Annotations 给类或者类的属性加上约束(constrain
- 目录前言I. 字符串转列表1. jdk支持方式2. guava方式3. apache-commonsII. 列表转字符串1. StringB
- Java 调用long的最大值和最小值今天对Java八种基本数据类型进行总结,当总结到整数类型中的long时,出现了测试long最大值和最小
- 我就废话不多说了,大家还是直接看代码吧~//执行的是删除信息的操作 String a=request.getParameter("
- 网络应用分为客户端和服务端两部分,而Socket类是负责处理客户端通信的Java类。通过这个类可以连接到指定IP或域名的服务器上,并且可以和
- springboot 针对jackson是自动化配置的,如果需要修改,有两种方式:方式一:通过application.yml配置属性说明:#
- 之前的项目中,在Socket通信的时候需要传int类型的值,不过java中outputsteam貌似不能直接传int类型,只能传byte[]
- 一:日志:1、配置日志级别日志记录器(Logger)的行为是分等级的。如下表所示:分为:OFF、FATAL、ERROR、WARN、INFO、
- 一. 什么是Spring SecuritySpring Security是Spring家族的一个安全管理框架, 相比于另一个安全框架Shir
- 一、概述最近在群里听到各种讨论okhttp的话题,可见okhttp的口碑相当好了。再加上Google貌似在6.0版本里面删除了HttpCli