VisualBrush of geometry DrawingImage is blurry and cut off - wpf

I would like to draw circular geometry in VisualBrush (in order to create an OpacityMask), the result however are of rather low quality:
This image is 500% zoomed in but the cut off of the circle is apparent (especially on top and bottom) and even at original size the mask is rather blurry. The image was generated with following code:
<Border Background="Blue">
<Border.OpacityMask>
<VisualBrush TileMode="None" Stretch="Uniform" AlignmentX="Center" AlignmentY="Center">
<VisualBrush.Visual>
<Image>
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Brush="Black">
<GeometryDrawing.Geometry>
<EllipseGeometry Center="0,0" RadiusX="1" RadiusY="1" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</VisualBrush.Visual>
</VisualBrush>
</Border.OpacityMask>
</Border>
How can I fix the mask so that is neither blurry nor cut off?

This may be better. At least it is simpler.
<Border Background="Blue">
<Border.OpacityMask>
<DrawingBrush Stretch="Uniform" AlignmentX="Center" AlignmentY="Center">
<DrawingBrush.Drawing>
<GeometryDrawing Brush="Black">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Border.OpacityMask>
</Border>

Related

VisualBrush color not changing when selected

I've created a VisualBrush in WPF to give me a wavy underline for a character.
<VisualBrush x:Key="WavyBrush">
<VisualBrush.Visual>
<Path Data="M 0,2 L 2,0 4,2 6,0 8,2 10,0 12,2" Stroke="Black" />
</VisualBrush.Visual>
</VisualBrush>
It works great except when I put the result in a DataGrid and select the row. If an unselected row has black text with white background and selecting the row turns the text white, (and the background blue or some other color) my VisualBrush with Stroke="Black" stays black, doesn't go white and looks confusing.
Is there a way to get the brush to act like color of text?
You can use DataTrigger to change the color of the brush when the row is selected. The code uses ListBox but you could easily adapt it to use DataGrid.
<Window.Resources>
<VisualBrush x:Key="WavyBrush">
<VisualBrush.Visual>
<Path Data="M 0,2 L 2,0 4,2 6,0 8,2 10,0 12,2" Stroke="Black" />
</VisualBrush.Visual>
</VisualBrush>
<VisualBrush x:Key="whiteWavyBrush">
<VisualBrush.Visual>
<Path Data="M 0,2 L 2,0 4,2 6,0 8,2 10,0 12,2" Stroke="White" />
</VisualBrush.Visual>
</VisualBrush>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="50" Width="350">
<Border x:Name="border" Width="350" Height="10" VerticalAlignment="Bottom" Background="{StaticResource WavyBrush}">
</Border>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ListBoxItem}},Path=IsSelected}" Value="True">
<Setter TargetName="border" Property="Background" Value="{StaticResource whiteWavyBrush}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
If anyone else is interested, the DrawingBrush acts like text and changes color when selected.
<DrawingBrush x:Key="TextBoxWavyBrush">
<DrawingBrush.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Geometry="M 0,2 L 2,0 4,2 6,0 8,2 10,0 12,2">
<GeometryDrawing.Pen>
<Pen Thickness="1" StartLineCap="Round" EndLineCap="Round" LineJoin="Round" Brush="{Binding RelativeSource={RelativeSource AncestorType=TextBox, Mode=FindAncestor}, Path=Foreground}"/>
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>

DrawingBrush on Canvas not working

The XAML that I have in my question was taken directly from this question on SO. It's simply XAML that creates a tiled DrawingBrush to render rectangles (squares in my case) against the background of a canvas. My problem is that this XAML only works when I set the Width and Height of the Canvas to anything other than "Auto". But when I set the Width and Height of canvasMain to Auto then no squares are drawn on the background.
How can I have my Width and Height set to "Auto" and also have the DrawingBrush render the squares on my canvas?
Here is my XAML:
<Window.Resources>
<DrawingBrush x:Key="GridTile" Stretch="None" TileMode="Tile"
Viewport="0,0 20,20" ViewportUnits="Absolute">
<!-- ^^^^^^^^^^^ set the size of the tile-->
<DrawingBrush.Drawing>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<!-- draw a single X -->
<GeometryGroup>
<!-- top-left to top-right -->
<LineGeometry StartPoint="0,0" EndPoint="20,0" />
<!-- top-left to bottom-left -->
<LineGeometry StartPoint="0,0" EndPoint="0,20" />
<!-- bottom-left to bottom-right -->
<LineGeometry StartPoint="0,20" EndPoint="20,20" />
<!-- top-right to bottom-right -->
<LineGeometry StartPoint="20,0" EndPoint="20,20" />
</GeometryGroup>
</GeometryDrawing.Geometry>
<GeometryDrawing.Pen>
<!-- set color and thickness of lines -->
<Pen Thickness="1" Brush="Silver" />
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
<DrawingBrush x:Key="OffsetGrid" Stretch="None" AlignmentX="Left" AlignmentY="Top">
<DrawingBrush.Transform>
<!-- set the left and top offsets -->
<TranslateTransform X="0" Y="0" />
</DrawingBrush.Transform>
<DrawingBrush.Drawing>
<GeometryDrawing Brush="{StaticResource GridTile}" >
<GeometryDrawing.Geometry>
<!-- set the width and height filled with the tile from the origin -->
<RectangleGeometry Rect="0,0 160,160" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Window.Resources>
<Canvas Name="canvasMain" HorizontalAlignment="Left" Margin="0,0,0,0" VerticalAlignment="Top" Width="Auto" Height="Auto" Background="{StaticResource OffsetGrid}"></Canvas>
Try changing the alignments:
<Canvas Name="canvasMain" HorizontalAlignment="Stretch" Margin="0,0,0,0" VerticalAlignment="Stretch" Width="Auto" Height="Auto" Background="{StaticResource OffsetGrid}"></Canvas>

Painting a different brush over the intesection of two separate shape objects

Is there a way in WPF when two shape objects overlap each other that the overlapping portions of the object get painted in a different brush?
Worked it out.
You can use a geometry drawing containing a GeometryGroup with a fill rule of EvenOdd. This paints any overlapping items in white. Then just put another image over the top with CombinedGeometry containing the same objects as the Geometry group with a GeometryCombineMode of Intersect and that will highlight the intersect in your custom brush. The sample code is below:
<Grid>
<Image Stretch="None">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Brush="Red">
<GeometryDrawing.Pen>
<Pen Brush="Black" Thickness="3" />
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<GeometryGroup FillRule="EvenOdd">
<EllipseGeometry RadiusX="80" RadiusY="80" Center="0,0" />
<EllipseGeometry RadiusX="80" RadiusY="80" Center="40,0" />
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
<Image HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Stretch="None">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Brush="LightBlue">
<GeometryDrawing.Geometry>
<CombinedGeometry GeometryCombineMode="Intersect">
<CombinedGeometry.Geometry1>
<EllipseGeometry RadiusX="80" RadiusY="80" Center="0,0" />
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<EllipseGeometry RadiusX="80" RadiusY="80" Center="40,0" />
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Grid>
Thanks!

Nothing displayed when set a line to DashStyles.Dot

<DockPanel LastChildFill="True" Height="18">
<Image Height="18" Width="80">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Brush="Black">
<GeometryDrawing.Geometry>
<LineGeometry StartPoint="0,9" EndPoint="38,9" />
</GeometryDrawing.Geometry>
<GeometryDrawing.Pen>
<Pen DashCap="Flat"
Brush="Black"
Thickness="0.5"
DashStyle="{x:Static DashStyles.Dot}, Mode=OneTime}"
/>
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
<TextBlock Height="18" Text="{Binding Name, Mode=OneTime}" Margin="3,2,5,0" />
</DockPanel>
Remove DashCap="Flat" and you will see the dots. There are other DashCaps that will work: Round, Square, Triangle.

WPF drawing that stretches without stretching the Pen

I need to draw some simple lines within a Border control (or similar) that always stretch to the bounds of the Border. Is there a way to stretch the lines only but not its pen? Without involving lots of C#?
In this version the lines stretch:
<Border>
<Border.Background>
<DrawingBrush>
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="Red">
<GeometryDrawing.Geometry>
<GeometryGroup>
<RectangleGeometry Rect="0,0 100,1000" />
<LineGeometry StartPoint="0,0" EndPoint="100,1000"/>
<LineGeometry StartPoint="100,0" EndPoint="0,1000"/>
</GeometryGroup>
</GeometryDrawing.Geometry>
<GeometryDrawing.Pen>
<Pen Thickness="20" Brush="Black"/>
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Border.Background>
</Border>
The best solution I have come up with is this:
<Border>
<Grid>
<Path Stretch="Fill" Fill="Red" Stroke="Black" StrokeThickness="4" Data="M0,0 L100,0 100,1000 0,1000 z" />
<Path Stretch="Fill" Stroke="Black" StrokeThickness="4" Data="M 0,0 L0,0 100,1000" />
<Path Stretch="Fill" Stroke="Black" StrokeThickness="4" Data="M 100,0 L100,0 0,1000" />
</Grid>
</Border>
But isn't there a better solution? That doesn't involve extra Grid?
I've done this by scaling a Path's Data, not the visual component.
Place a Path in a Canvas.
Set path.Data to a Geometry representing your data as percentages of the logical range.
Set path.Data.Transform to a ScaleTransform with ScaleX and ScaleY bound to the actual width and height.
Within a line, you can bind the width (or height, depending on which way you are drawing the line) to that of the parent container to achieve what you want.
<Grid x:Name="Grid" Margin="10">
<Border BorderBrush="Black" BorderThickness="1" />
<Line X1="0" X2="{Binding ElementName=Grid, Path=ActualWidth}" Y1="1" Y2="1" Stroke="Red" Margin="0,10,0,0" />
<Line X1="0" X2="{Binding ElementName=Grid, Path=ActualWidth}" Y1="1" Y2="1" Stroke="Green" Margin="0,30,0,0" />
<Line X1="0" X2="{Binding ElementName=Grid, Path=ActualWidth}" Y1="1" Y2="1" Stroke="Blue" Margin="0,50,0,0" />
</Grid>
Edit: Here is another way without using binding
<Border BorderBrush="Black" BorderThickness="1" >
<Path Stroke="Red" StrokeThickness="1" Data="M0,0 1,0Z" Stretch="Fill" />
</Border>
None that I know of. But unless you're doing something really extravagant, it really isn't a lot of effort to override OnRender and draw it yourself:
public class CustomBorder : Border
{
protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
dc.DrawLine(new Pen(BorderBrush, BorderThickness.Top), new Point(0, 0), new Point(ActualWidth, ActualHeight));
}
}
Result:

Resources