同屏连线背后的代码 - SJTU-Art-Center/ACLiveConsole GitHub Wiki
行动代号:PIP
在 WPF 中,一个控件在 2 维空间的变换(RenderTransform
)通过一个三阶方阵定义,根据变换类的定义,用代码写就是:
Matrix M = new Matrix(
M11, M12,
M21, M22,
OffsetX, OffsetY
);
可见,你只能更改其中的六个变量,第 3 列是闲置的。初始情况下,控件以几何中心作为渲染原点(RenderOrigin
),初始变换矩阵 M_i
为恒等矩阵(I)。后来我们需要将其变换为 M_f
后,横向基向量 i
将会被变换为 [M11,M21]
,纵向基向量 j
将会被变换为 [M12,M22]
。渲染原点将会被平移至 [OffsetX,OffsetY]
的位置。
我们可能不希望直接硬切到另一个状态。这时我们就需要插值地算出中间状态。AC Live Console 使用直线形的移动方式(别问我为什么不是贝塞尔曲线,问就是太麻烦了),数学上的定比分点公式在这里也可以套用在矩阵上来计算中间状态:
或许你会认为代码应该这样写:
Matrix M = M_i + p * (M_f - M_i);
p
代表进度,范围是 [0,1]
。
遗憾的是,在 C# 中矩阵运算是没有定义的,如果需要的话需要重载,但是我比较懒,还是这样写了代码:
// Line 2862
Matrix backMatrix = new Matrix(
backMatrix_init.M11 + (backMatrix_fin.M11 - backMatrix_init.M11) * progress, 0,
0, backMatrix_init.M22 + (backMatrix_fin.M22 - backMatrix_init.M22) * progress,
backMatrix_init.OffsetX + (backMatrix_fin.OffsetX - backMatrix_init.OffsetX) * progress, backMatrix_init.OffsetY + (backMatrix_fin.OffsetY - backMatrix_init.OffsetY) * progress);
然后,应用这样的矩阵变换于空间的 RenderTransform
属性上:
// Line 2866
focaldephov.BackImg.RenderTransform = new MatrixTransform(backMatrix);
那么定比 p
如何计算呢?AC Live Console 通过滑动条的值(Value
)来计算。在同屏连线模式下,第一次按下按钮,刷新目的地,Value
将从 1 减到 0.5;第二次按下相同的按钮,刷新目的地,Value
将从 0.5 减到 0。我们更希望通过一个连续的函数来定义这个过程(哈哈哈 我后来才发现我傻在了这个地方),就这样定义:
// Line 2861
double progress = 1 - Math.Abs(2 * (ProgressTran.Value - 0.5));
你可以通过手动滑动滑块来体验这个过程。
但是,这种线性的定比分点函数对于动画而言还是太机械了。那么怎么去改进呢?
时间重映射! 我们只要更改滑块移动的快慢(缓动地更改 ProgressTran.Value
)就可以产生更加顺滑的动画,这也就用到了缓动函数来产生这种效果。这个道理和弹幕动画是一致的。
我们看到 AC Live Console 提供了肖像型同屏的模式,该模式下需要将画面两边裁切掉。
这时候我们就使用了 Clip
属性,将 Image 控件的定义如下:
<Image x:Name="BackImg" Width="{Binding ActualWidth, ElementName=grid, Mode=OneWay}" Height="{Binding ActualHeight, ElementName=grid, Mode=OneWay}" RenderTransformOrigin="0.5,0.5" >
<Image.Clip>
<RectangleGeometry x:Name="BackClipRect"/>
</Image.Clip>
</Image>
添加了一个矩形的选区来裁切。
Rect R = new Rect(pointX, pointY,
Width, Height);
这时我们只要在上面的基础上,对矩形的性状进行计算就可以完成裁切的效果:
// Line 2874
Rect backRect = new Rect(
backRect_init.X + (backRect_fin.X - backRect_init.X) * progress,
backRect_init.Y + (backRect_fin.Y - backRect_init.Y) * progress,
backRect_init.Width + (backRect_fin.Width - backRect_init.Width) * progress,
backRect_init.Height + (backRect_fin.Height - backRect_init.Height) * progress
);
focaldephov.BackClipRect.Rect = backRect;
又是没有重载运算符的代码啊
综合一下,代码框图大致如下:
Function | Description |
---|---|
TranEffect |
改变同屏连线的状态 |
TranEffectChanged() |
刷新控件状态 |
DefineStates() |
根据样式定义目的地状态 |
TranEffectSytle() |
定比分点计算 |
selectItem(byte to) |
转换到该监视 |
第二次按下反向定义为恒等是因为进度的第二次是由 100 减少到 0,恰好相反。
在预监(PVW)启用后,TranImg
将会加载BackImg
源,而 BackImg
将会加载预监的源。
- 如果是标准切换,将会直接地线性变化。
- 如果是同屏模式下的同模式切换,第一部分是线性到100%的。后面一直保持这个不透明度。
- 而如果是不同模式切换,将会一直保持100%。
点击直播监视器右上方的按钮进入音控台。
Item | Description |
---|---|
线路编号 | 对应于监视器左上、右上、左下 |
线路名称 | 对应监视器的源名称 |
输出开关 | 切换该音道是否开启 |
音控滑块 | 音量 0~100 |
静音 | 音控滑块变为 0 |
独奏 | 切换为只有该音道是开启的 |
智慧型音控 | 用 J-cut 或 L-cut 的方式切换声音 |
当启用智慧型音控台后,可以使用两种方式切换音频:
- J-cut:过渡开始,主监声音退出,预监声音进入。
- L-cut:过渡结束,主监声音退出,预监声音进入。
- FADE:线性改变两个源的音量。
这主要对于淡入淡出(快捷键A
,S
,D
,F
)的切换模式。同屏模式下两者并存;直切情况下,直接切换。只有在打开音控台并启用智慧型音控台时才会生效。