精确的遮盖力 [英] Precise OpacityMask

查看:59
本文介绍了精确的遮盖力的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我需要在WPF控件上设置一个不透明蒙版,以在精确位置突出显示它的一部分(假设在(50; 50)位置为50x50正方形).为此,我创建了一个包含2个GeometryDrawing对象的DrawingGroup:1个半透明矩形(用于控件的整个实际大小)和1个不透明矩形(用于突出显示区域).然后,我从此DrawingGroup创建一个DrawingBrush,将其Stretch属性设置为None,并将此画笔设置为需要遮罩的控件的OpacityMask.

所有这些工作正常,而没有任何东西超出"所述控件的范围.但是,如果控件在其边界之外绘制某些内容,则外部点将成为应用不透明蒙版的起点(如果画笔与该侧对齐),并且整个蒙版会移动该距离,从而导致意外行为.

我似乎无法找到一种方法来强制从控件的边界应用蒙版,或者至少无法获得控件的实际边界(包括粘贴部分),因此我可以相应地调整蒙版.

任何想法都受到赞赏!

更新:以下是一个简单的测试用例XAML和屏幕快照,展示了该问题:

在最后一个带有上述正方形的框中,我们有2个嵌套的Borders和Canvas:

 <边框填充="20"背景="DarkGray"宽度="240"高度="240">< Border Background ="LightBlue"><画布><矩形Canvas.Left ="50" Canvas.Top ="50"宽度="50"高度="50"Stroke ="Red" StrokeThickness ="2"填充=白色"/></画布></Border></Border> 

这是它的外观:


(来源:
(来源:
(来源: ailon.org )

Update2 :作为一种解决方法,我在边界外添加了一个荒谬的透明矩形,并相应地调整了我的遮罩,但这是一个非常讨厌的解决方法.

Update3 :注意:带有矩形和线条的画布仅作为某些对象的示例,这些对象的边界超出范围.在此示例的上下文中,应将其视为某种黑匣子.您不能更改其属性来解决一般问题.这与只移动线以免伸出线一样.

解决方案

确实有一个有趣的问题-这就是我所想的:您所遇到的效果似乎由边界框://msdn.microsoft.com/zh-cn/library/system.windows.frameworkelement.aspx"rel =" noreferrer> FrameworkElement (例如您的画布)是受元素超出范围影响的微妙影响/扩展,即框的尺寸会扩展,但框的坐标系不会缩放,而是会扩展到超出范围的方向.

以图形方式进行说明可能会更容易,但是由于时间的限制,我将仅提供一个解决方案,并将介绍我目前为使您入门而采取的步骤:


解决方案:

 <边框背景=浅蓝色"宽度="198"高度="198">< Border.OpacityMask>< DrawingBrush Stretch ="None" AlignmentX ="Center" AlignmentY ="Center"Viewport =-10,0,222,202" ViewportUnits ="Absolute">< DrawingBrush.Drawing>< DrawingGroup>< GeometryDrawing Brush =#30000000">< GeometryDrawing.Geometry>< RectangleGeometry Rect =-10,0,220,200"/></GeometryDrawing.Geometry></GeometryDrawing>< GeometryDrawing Brush =黑色"> ...</GeometryDrawing></DrawingGroup></DrawingBrush.Drawing></DrawingBrush></Border.OpacityMask>< Canvas x:Name ="myGrid"> ...</Canvas></Border> 

请注意,我已经在这里和那里以+/- 2像素为单位调整了单位,以提高像素精度,而又不知道偏移的来源,但是我认为可以忽略此点,以示例为目的,以后可以根据需要解决.


说明:

为简化图示,通常应首先使所有相关的隐式/自动属性显式显示.

内部边界从外部边界接收198的自动尺寸(240-20填充-2像素(通过实验推导;不知道其来源,但现在可忽略)),也就是说,如果您按照以下方式指定此内容,则不应更改,而使用其他值则产生图形更改:

 <边框背景="LightBlue"宽度="198"高度="198"> ...</Border> 

进一步默认默示的 Viewport ViewportUnits 就像这样:

 < DrawingBrush Stretch ="None" AlignmentX ="Left" AlignmentY ="Top"Viewport ="0,0,1,1" ViewportUnits ="RelativeToBoundingBox"> ...</DrawingBrush> 

您正在通过覆盖来增强DrawingBrush的大小使用 None 进行 Stretch 的拉伸,同时将基础图块的位置和尺寸保留为默认值,并且相对于其边界框.另外,您(可以理解)正在覆盖 AlignmentX / AlignmentY ,它确定基础图块在其边界框内的位置.将其重置为默认值 Center 已经可以说明:遮罩会相应移动,这意味着它必须小于边界框,否则它们将不能居中.

可以通过更改 ViewportUnits 转换为 Absolute ,这将完全不产生图形,除非对单元进行了适当的调整.再次,通过实验,以下显式值与自动值匹配,而使用其他值则产生图形变化:

 < DrawingBrush Stretch ="None" AlignmentX ="Center" AlignmentY ="Center"Viewport ="0,0,202,202" ViewportUnits ="Absolute"> ...</DrawingBrush> 

现在不透明蒙版已与控件正确对齐.显然,仍然存在一个问题,因为该掩码现在正在剪裁,考虑到它的大小以及没有任何

 < DrawingBrush Stretch ="None" AlignmentX ="Center" AlignmentY ="Center"Viewport =-10,0,222,202" ViewportUnits ="Absolute"> ...</DrawingBrush> 

最后,不透明蒙版会根据需要匹配控制范围


补充:

可以通过 Windows Presentation Foundation图形渲染概述,特别是 VisualTreeHelper类

  • 布局系统,尤其是元素边界框
  • Suppose I need to set an opacity mask on a WPF control that highlights a portion of it in precise position (suppose a 50x50 square at (50;50) position). To do that I create a DrawingGroup containing 2 GeometryDrawing objects: 1 semi-transparent rectangle for the whole actual size of the control and 1 opaque rectangle for highlighted area. Then I create a DrawingBrush from this DrawingGroup, set it's Stretch property to None and set this brush as OpacityMask of the control that needs to be masked.

    All this works fine while nothing is "sticking" out of bounds of said control. But if control draws something outside of it's bounds the outer point becomes a starting point from where opacity mask is applied (if the brush is aligned to that side) and the whole mask shifts by that distance resulting in unexpected behavior.

    I can't seem to find a way to force mask to be applied from control's bounds or at least get the actual bounds of the control (including sticking parts) so I can adjust my mask accordingly.

    Any ideas highly appreciated!

    Update: Here's a simple test-case XAML and screenshots demonstrating the issue:

    We have 2 nested Borders and Canvas in the last one with the above mentioned square:

    <Border Padding="20" Background="DarkGray" Width="240" Height="240">
        <Border Background="LightBlue">
            <Canvas>
                <Rectangle Canvas.Left="50" Canvas.Top="50" Width="50" Height="50" 
                           Stroke="Red" StrokeThickness="2" 
                           Fill="White"
                           />
            </Canvas>
        </Border>
    </Border>
    

    Here's how it looks:


    (source: ailon.org)

    Now we add an OpacityMask to the second border so that every part of it except our square is semi-transparent:

    <Border.OpacityMask>
        <DrawingBrush Stretch="None" AlignmentX="Left" AlignmentY="Top">
            <DrawingBrush.Drawing>
                <DrawingGroup>
                    <GeometryDrawing Brush="#30000000">
                        <GeometryDrawing.Geometry>
                            <RectangleGeometry Rect="0,0,200,200" />
                        </GeometryDrawing.Geometry>
                    </GeometryDrawing>
                    <GeometryDrawing Brush="Black">
                        <GeometryDrawing.Geometry>
                            <RectangleGeometry Rect="50,50,50,50" />
                        </GeometryDrawing.Geometry>
                    </GeometryDrawing>
                </DrawingGroup>
            </DrawingBrush.Drawing>
        </DrawingBrush>
    </Border.OpacityMask>
    

    Everything looks as expected:


    (source: ailon.org)

    And now we add a line to the canvas that sticks 10 pixels out on the left of our border:

    <Line X1="-10" Y1="150" X2="120" Y2="150"
          Stroke="Red" StrokeThickness="2" 
          />
    

    And the mask shifts 10 pixels to the left:


    (source: ailon.org)

    Update2: As a workaround I add a ridiculously large transparent rectangle outside of bounds and adjust my mask accordingly but that is a really nasty workaround.

    Update3: Note: The canvas with rectangle and line is there just as an example of some object that has something outside of it bounds. In context of this sample it should be treated as some sort of a black box. You can't change it's properties to solve the general issue. This would be the same as just moving the line so it doesn't stick out.

    解决方案

    Interesting issue indeed - here's what I've figured: The effect you are experiencing seems to be determined by the Viewport concept/behavior of TileBrush (see Viewbox too for the complete picture). Apparently the implicit bounding box of a FrameworkElement (i.e. the Canvas in your case) is affected/expanded by elements sticking out of bounds in a subtle way, that is, the dimensions of the box expand but the coordinate system of the box does not scale, rather expands too into the out of bounds direction.

    It might be easier to illustrate that graphically, but due to time constraints I'll just offer a solution first and will explain the steps I've taken for the moment in order to get you started:


    Solution:

    <Border Background="LightBlue" Width="198" Height="198">
        <Border.OpacityMask>
            <DrawingBrush Stretch="None" AlignmentX="Center" AlignmentY="Center" 
                          Viewport="-10,0,222,202" ViewportUnits="Absolute">
                <DrawingBrush.Drawing>
                    <DrawingGroup>
                        <GeometryDrawing Brush="#30000000">
                            <GeometryDrawing.Geometry>
                                <RectangleGeometry Rect="-10,0,220,200" />
                            </GeometryDrawing.Geometry>
                        </GeometryDrawing>
                        <GeometryDrawing Brush="Black">...</GeometryDrawing>
                    </DrawingGroup>
                </DrawingBrush.Drawing>
            </DrawingBrush>
        </Border.OpacityMask>
        <Canvas x:Name="myGrid">...</Canvas>
    </Border>
    

    Please note that I've adjusted units by +/- 2 pixels here and there for pixel precision without knowing where the offset originates, but I think this can be ignored for the purpose of the example and resolved later if need be.


    Explanation:

    To simplify the illustration one should usually make all related implied/auto properties explicit first.

    The inner border receives auto dimensions of 198 from the outer border (240 - 20 padding - 2 pixels deduced by experiment; don't know their origin, but ignorable right now), that is if you specify this as follows nothing should change, while using other values yields graphical changes:

    <Border Background="LightBlue" Width="198" Height="198">...</Border>
    

    Further the default implied Viewport and ViewportUnits like so:

    <DrawingBrush Stretch="None" AlignmentX="Left" AlignmentY="Top" 
        Viewport="0,0,1,1" ViewportUnits="RelativeToBoundingBox">...</DrawingBrush>
    

    You are enforcing the DrawingBrush size by overriding Stretch with None, while keeping the position and dimension of the base tile at default and relative to its bounding box. In addition you (understandably) are overriding AlignmentX/AlignmentY, which determine the placement within the base tile, that is within its bounding box. Resetting those to their defaults of Center is already telling: The mask shifts accordingly, meaning it has to be smaller than the bounding box, else their would be nothing to center within.

    This can be taken further by changing ViewportUnits to Absolute, which will yield no graphics at all until the units are properly adjusted of course; again, by experiment the following explicit values are matching the auto ones, while using other values yields graphical changes:

    <DrawingBrush Stretch="None" AlignmentX="Center" AlignmentY="Center" 
        Viewport="0,0,202,202" ViewportUnits="Absolute">...</DrawingBrush>
    

    Now the opacity mask already aligns properly with the control. Obviously there is one problem left though, as the mask is clipping the line now, which is no surprise given its size and the absence of any Stretch effect. Adjusting its size and position accordingly resolves this:

    <RectangleGeometry Rect="-10,0,220,200" />
    

    and

    <DrawingBrush Stretch="None" AlignmentX="Center" AlignmentY="Center" 
        Viewport="-10,0,222,202" ViewportUnits="Absolute">...</DrawingBrush>
    

    Finally the opacity mask matches the control bounds as desired!


    Supplement:

    The required offsets determined by deduction and experiment in the explanation above can be retrieved at runtime by means of the VisualTreeHelper Class:

    Rect descendantBounds = VisualTreeHelper.GetDescendantBounds(myGrid);
    

    Depending on your visual element composition and needs you may need to factor in the LayoutInformation Class and build the union of both to get the all-encompassing bounding box:

    Rect descendantBounds = VisualTreeHelper.GetDescendantBounds(myGrid);
    Rect layoutSlot = LayoutInformation.GetLayoutSlot(myGrid);
    Rect boundingBox = descendantBounds;
    boundingBox.Union(layoutSlot);
    

    See the following links for more details on both topics:

    这篇关于精确的遮盖力的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆