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); } } }
实现效果
到此这篇关于WPF+SkiaSharp实现自绘弹幕效果的文章就介绍到这了,更多相关WPF SkiaSharp弹幕内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!
赞 (0)