什么是羽化?
图像羽化
是指将图像边缘
进行渐变式透明化
并融合背景当中,实现一种平滑的融合效果
,一般应用在画中画
,或者一张图插入到另外一种图
当中。
在软件开发里也经常将图像羽化用于UI开发中
,这么做能够起到美观作用
。
以WinUI 3的demo应用程序Gallery
为例:
上图是使用了羽化
,下图是关闭羽化
效果:
可以看到没有羽化效果图像看起来平平无奇,与整个UI融合的并不好,看起来像是一个广告板,而使用了羽化效果会让图像看起来就是UI背景的一部分。
如何在WinUI3中使用羽化效果?
想要在WinUI3中实现羽化效果不能通过单纯的静态XML方式实现,需要静态与动态结合,使用代码方式实现,需要使用WinUI3较为底层的类:Visual
,Visual里有一个Compositor
模块,Compositor是合成器模块,它是用来创建管理每个控件的视觉、动画、合成效果例如:各种 Brush(画刷)
、动画(KeyFrameAnimation)
、Effect(效果)
、Surface(绘图表面)
功能模块的工厂类,它可以从任何一个控件里获取到,然后生命周期由某个控件来管理,它属于较为底层的工厂模块,它必须使用某个控件的Compositor,它不能单独被创建,因为它的一些渲染之类的使用的是Composition Engine
的组件,而该组件会使用DirectX
来使用GPU
完成绘制,每个控件都有一个DirectX
的Composition Engine
组件来绘制自身Visual,所以Compositor一般是通过获取某个组件上的来实现具体功能,但它所提供的API不针对该组件,它只使用该组件的GPU相关的模块并且生命周期由该组件管理。
那么如何通过Compositor
实现羽化效果呢?Compositor里有一个合成器模块:CompositionMaskBrush
,该模块用于实现不规则图形,它接受两个参数:背景、Mask,它会根据Mask的透明度信息,调整背景上面的每个像素的透明度,也就是蒙版融合。
实现一个羽化效果
首先原始效果是这个样子的:
接下来我们要在这个img
下方的边缘部分实现羽化效果,具体实现方式是首先创建一个Grid
,并在里面放置一张图片控件和一个渐变的Rectangle
控件
<Grid x:Name="Root1" Width="400" Height="300" Margin="0, 100, 0, 40">
<Image x:Name="BgImg" Source="C:\\Users\\stephen\\Downloads\\test.jpg" Stretch="UniformToFill" />
<Rectangle x:Name="MaskRect">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#FF000000" Offset="0.0"/>
<GradientStop Color="#00FFB4FF" Offset="1.0"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
Grid控件可以让每个控件大小保持一致并且XY都为0,这样后续作为Mark
的时候可以更好的对齐。
增加之后效果如下:
那么这个时候就需要在代码里开始实现这个功能了,首先先获取Grid
的Compositor
,因为我们要在Grid
里面绘制:
auto compositor = ElementCompositionPreview::GetElementVisual(Root1()).Compositor();
然后获取Grid
的Visual
,这些目的是为了将融合后的图像直接替换原本的Grid
的Vsual
,因为融合后的图像必须有个载体显示:
Visual rootVisual = ElementCompositionPreview::GetElementVisual(Root1());
现在我们已经有了Grid的Compositor和Visual,接下来要再获取Image的Visual:
Visual bgVisual = ElementCompositionPreview::GetElementVisual(BgImg());
Visual是每个控件较为底层的一个类
,负责整个控件生命周期的渲染
、动画
、一切可视化效果
,获取到Image的Visual之后我们使用compositor来创建一个CompositionVisualSurface
,CompositionVisualSurface是一个表面工具功能,它是用于获取某个控件的表面图像,表面图像就是控件可视区域显示的图像,与截图一样。
CompositionVisualSurface bgSurface = compositor.CreateVisualSurface();
bgSurface.SourceVisual(bgVisual);
{
auto expr = compositor.CreateExpressionAnimation(L"v.Size");
expr.SetReferenceParameter(L"v", rootVisual);
bgSurface.StartAnimation(L"SourceSize", expr);
}
然后将SourceVisual
设置为Image
的Visual
,这样它就会实时获取Image的表面图像。
bgSurface.SourceVisual(bgVisual);
注意调用它之后虽然会实时获取但是它获取时的size并不会改变,默认还是0x0,所以需要使用表达式动画,来让它能够实时根据控件大小变化而改变大小:
{
auto expr = compositor.CreateExpressionAnimation(L"v.Size");
expr.SetReferenceParameter(L"v", rootVisual);
bgSurface.StartAnimation(L"SourceSize", expr);
}
注意这里获取的是Grid的,这里解释一下为什么,在WinUI3里面有两种大小,一种是Visual大小
,一种是实际大小
,Visual大小是你图像显示出来时能够客观看到的大小
,而实际大小是真正的宽高,控件的宽高大小
,例如Image Visual大小是200x300,但实际大小是600x400,这个原因是因为你在布局控件里使用了它,并且使用Height/Width
属性来控制控件大小(如果在布局控件里修改宽高属性仅修改Visual
的,布局控件会强制改变它的实际宽高),但布局控件会强制改变它的大小
,例如Grid
,如果在Image
使用了UniformToFill
拉伸了它,那么它的Visual
大小也会改变,就会导致我们实际根据宽高设置时会发现跟我们预想的不一样,所以这里使用Grid
的宽高来作为实际应用的宽高。
接下来如果想要在别的地方使用它,例如合成、绘制到别处就需要使用Brush
,所以为它创建一个Brush
:
CompositionSurfaceBrush bgBrush = compositor.CreateSurfaceBrush(bgSurface);
随后我们按照上面一样的步骤获取Mask
的Surface
:
Visual maskVisual = ElementCompositionPreview::GetElementVisual(MaskRect());
CompositionVisualSurface maskSurface = compositor.CreateVisualSurface();
maskSurface.SourceVisual(maskVisual);
{
auto expr = compositor.CreateExpressionAnimation(L"v.Size");
expr.SetReferenceParameter(L"v", maskVisual);
maskSurface.StartAnimation(L"SourceSize", expr);
}
CompositionSurfaceBrush maskBrush = compositor.CreateSurfaceBrush(maskSurface);
最后我们就可以使用CompositionMaskBrush来合成了,它接受两个参数一个是Source和Mask,Source就是背景,Mask就是蒙版,会根据Mask上面的透明度信息来调整Source的像素透明度:
CompositionMaskBrush maskEffect = compositor.CreateMaskBrush();
maskEffect.Source(bgBrush);
maskEffect.Mask(maskBrush);
Tips
需要值得注意的是MaskBrush在融合的时候Mask的坐标从左上(0, 0)开始,并且它使用的是实际大小作为
最后图像已经融合好了,那么就要将它设置到Grid表面去了,接下来就要使用SpriteVisual了,SpriteVisual是每个控件元素里的画布,也就是实际显示内容的模块,在WinUI3里面每个控件都以树形结构表示,控件里有许多子节点,其中SpriteVisual就是用来控制要显示的内容的。
SpriteVisual sprite = compositor.CreateSpriteVisual();
sprite.RelativeSizeAdjustment(float2{ 1.f, 1.f });
sprite.Brush(maskEffect);
RelativeSizeAdjustment
是用来设置显示时的缩放程度,0~1是Float类型,通过Brush给它设置一个新的画刷。
最后通过SetElementChildVisual来给控件增加一个渲染子节点SpriteVisual,由于Grid的Visual根节点本身就是透明的什么都没有,所以新增的子节点不会被Grid本身的渲染内容所遮挡:
ElementCompositionPreview::SetElementChildVisual(Root1(), sprite);
改变之后Grid可视区域就变成了刚刚融合的图像,由于Grid内部有控件会遮挡所以这里需要将这两个控件隐藏掉:
BgImg().Opacity(0);
MaskRect().Opacity(0);
最终运行效果:
可以看到实现与Gallery
一样的下方边缘羽化效果,与整个软件背景融为一体了,效果比之前硬广告板那样的效果自然很多,该羽化效果不光可以应用在图像上,也可以应用在其它任何控件上,只要该控件的Visual不是透明的。