wpfxamlbordertextblockopacitymask

Invert OpacityMask


Consider the following piece of Xaml

<Grid Background="Blue">
    <Border Width="100" Height="60" BorderBrush="Black" BorderThickness="2">
        <Border Background="Red">
            <Border.OpacityMask>
                <VisualBrush>
                    <VisualBrush.Visual>
                        <TextBlock Text="Text"
                                   Foreground="#FF000000"
                                   Background="#00000000"/>
                    </VisualBrush.Visual>
                </VisualBrush>
            </Border.OpacityMask>
        </Border>
    </Border>
</Grid>

It will look like this because of the OpacityMask whos only non-transparent part is the Foreground of the TextBlock.
alt text

Now if I switch the Colors for Foreground and Background in the TextBlock like this

<TextBlock Text="Text"
           Foreground="#00000000"
           Background="#FF000000"/>

I get this because the even though the Foreground is transparent the Background behind it is not, resulting in a useless OpacityMask :)
alt text

Is there anyway I can get this? Basically an inverted OpacityMask
alt text

Am I missing some other way to do this here?

Update
To clarify, even though my example is about a TextBlock, it could be anything. Ellipse/Image/Path etc. The feature I'm after is "Invert OpacityMask"


Solution

  • You can use my HollowTextBlock which is different answer to the same question:

    <Grid Background="Blue">
        <Border Width="100" Height="60" BorderBrush="Black" BorderThickness="2">
            <Border Background="Red">
                <Border.OpacityMask>
                    <VisualBrush Stretch="None">
                        <VisualBrush.Visual>
                            <local:HollowTextBlock Width="200" Height="50" Text="Text" Background="White" HorizontalAlignment="Center"/>
                        </VisualBrush.Visual>
                    </VisualBrush>
                </Border.OpacityMask>
            </Border>
        </Border>
    </Grid>
    

    Update:

    Here is a more fleshed out version of HollowTextBlock with proper measure capability, property value inheritance for the usual text properties, and a new VerticalTextAlignment property for centering the text vertically in it's allocated space:

    public class HollowTextBlock : FrameworkElement
    {
        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }
    
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text", typeof(string), typeof(HollowTextBlock), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure, new PropertyChangedCallback(HollowTextBlock.OnTextChanged), new CoerceValueCallback(HollowTextBlock.CoerceText)));
    
        public Brush Background
        {
            get { return (Brush)GetValue(BackgroundProperty); }
            set { SetValue(BackgroundProperty, value); }
        }
    
        public static readonly DependencyProperty BackgroundProperty =
            TextElement.BackgroundProperty.AddOwner(typeof(HollowTextBlock), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
    
        public double FontSize
        {
            get { return (double)GetValue(FontSizeProperty); }
            set { SetValue(FontSizeProperty, value); }
        }
    
        public static readonly DependencyProperty FontSizeProperty =
            TextElement.FontSizeProperty.AddOwner(typeof(HollowTextBlock));
    
        public FontFamily FontFamily
        {
            get { return (FontFamily)GetValue(FontFamilyProperty); }
            set { SetValue(FontFamilyProperty, value); }
        }
    
        public static readonly DependencyProperty FontFamilyProperty =
            TextElement.FontFamilyProperty.AddOwner(typeof(HollowTextBlock));
    
        public FontStyle FontStyle
        {
            get { return (FontStyle)GetValue(FontStyleProperty); }
            set { SetValue(FontStyleProperty, value); }
        }
    
        public static readonly DependencyProperty FontStyleProperty =
            TextElement.FontStyleProperty.AddOwner(typeof(HollowTextBlock));
    
        public FontWeight FontWeight
        {
            get { return (FontWeight)GetValue(FontWeightProperty); }
            set { SetValue(FontWeightProperty, value); }
        }
    
        public static readonly DependencyProperty FontWeightProperty =
            TextElement.FontWeightProperty.AddOwner(typeof(HollowTextBlock));
    
        public FontStretch FontStretch
        {
            get { return (FontStretch)GetValue(FontStretchProperty); }
            set { SetValue(FontStretchProperty, value); }
        }
    
        public static readonly DependencyProperty FontStretchProperty =
            TextElement.FontStretchProperty.AddOwner(typeof(HollowTextBlock));
    
        public TextAlignment TextAlignment
        {
            get { return (TextAlignment)GetValue(TextAlignmentProperty); }
            set { SetValue(TextAlignmentProperty, value); }
        }
    
        public static readonly DependencyProperty TextAlignmentProperty =
            Block.TextAlignmentProperty.AddOwner(typeof(HollowTextBlock));
    
        public VerticalAlignment VerticalTextAlignment
        {
            get { return (VerticalAlignment)GetValue(VerticalTextAlignmentProperty); }
            set { SetValue(VerticalTextAlignmentProperty, value); }
        }
    
        public static readonly DependencyProperty VerticalTextAlignmentProperty =
            DependencyProperty.Register("VerticalTextAlignment", typeof(VerticalAlignment), typeof(HollowTextBlock), new FrameworkPropertyMetadata(VerticalAlignment.Top, FrameworkPropertyMetadataOptions.AffectsRender));
    
        private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            OnTextChanged(d, (string)e.NewValue);
        }
    
        private static void OnTextChanged(DependencyObject d, string newText)
        {
        }
    
        private static object CoerceText(DependencyObject d, object baseValue)
        {
            return baseValue;
        }
    
        protected override Size MeasureOverride(Size availableSize)
        {
            var face = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
            var size = FontSize;
            var ft = new FormattedText(Text, Thread.CurrentThread.CurrentUICulture, FlowDirection.LeftToRight, face, size, Brushes.Black);
            return new Size(ft.Width, ft.Height);
        }
    
        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);
            var extent = new RectangleGeometry(new Rect(0.0, 0.0, RenderSize.Width, RenderSize.Height));
            var face = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
            var size = FontSize;
            var ft = new FormattedText(Text, Thread.CurrentThread.CurrentUICulture, FlowDirection.LeftToRight, face, size, Brushes.Black);
            var originX = GetHorizontalOrigin(ft.Width, RenderSize.Width);
            var originY = GetVerticalOrigin(ft.Height, RenderSize.Height);
            var hole = ft.BuildGeometry(new Point(originX, originY));
            var combined = new CombinedGeometry(GeometryCombineMode.Exclude, extent, hole);
            drawingContext.PushClip(combined);
            drawingContext.DrawRectangle(Background, null, new Rect(0.0, 0.0, RenderSize.Width, RenderSize.Height));
            drawingContext.Pop();
        }
    
        private double GetHorizontalOrigin(double textWidth, double renderWidth)
        {
            switch (TextAlignment)
            {
                case TextAlignment.Center:
                    return (renderWidth - textWidth) / 2;
                case TextAlignment.Left:
                    return 0;
                case TextAlignment.Right:
                    return renderWidth - textWidth;
            }
            return 0;
        }
    
        private double GetVerticalOrigin(double textHeight, double renderHeight)
        {
            switch (VerticalTextAlignment)
            {
                case VerticalAlignment.Center:
                    return (renderHeight - textHeight) / 2;
                case VerticalAlignment.Top:
                    return 0;
                case VerticalAlignment.Bottom:
                    return renderHeight - textHeight;
            }
            return 0;
        }
    }