WPF+SkiaSharp实现自绘弹幕效果
作者:驚鏵 时间:2022-09-30 09:52:38
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
标签:WPF,SkiaSharp,弹幕
0
投稿
猜你喜欢
C#操作PowerPoint的方法
2023-03-17 03:48:16
Java多线程编程综合案例详解
2023-12-09 18:13:25
SpringMVC+EasyUI实现页面左侧导航菜单功能
2022-09-30 17:11:01
Android账号注册实现点击获取验证码倒计时效果
2023-05-18 05:46:33
Android Apt之Activity Route的示例
2022-03-31 17:05:18
新闻列表的分页查询java代码实现
2022-02-24 19:46:25
详解Android应用main函数的调用
2021-09-30 12:01:19
源码解析JDK 1.8 中的 Map.merge()
2023-11-16 23:49:25
Android自定义控件实现边缘凹凸的卡劵效果
2022-10-01 01:20:51
JavaEE开发之SpringMVC中的自定义消息转换器与文件上传
2023-11-24 19:36:02
Spring Security 中如何让上级拥有下级的所有权限(案例分析)
2022-01-28 16:55:26
Java单元测试工具之JUnit的使用
2022-09-05 13:20:54
dialog dismiss时键盘不消失的问题浅析及解决办法
2021-09-18 10:06:30
Java实现文件上传到服务器本地并通过url访问的方法步骤
2021-12-01 11:45:20
C#实现温度转换功能
2021-10-06 22:54:18
Android自定义简单的顶部标题栏
2023-04-23 06:02:17
SpringBoot2零基础到精通之映射与常用注解请求处理
2022-06-11 15:41:51
Java用单向环形链表来解决约瑟夫环Josepfu问题
2023-03-13 18:54:20
Android应用App更新实例详解
2023-06-26 21:27:44
C#算法之整数反转
2021-09-24 18:36:49