How to burn WPF Image + Shape to new image - c#

I've got a WPF User control with a canvas. It looks roughly like this (more complete version at the bottom):
<UserControl>
<Canvas>
<Image x:Name="CurrentImage" ... etc .../>
<Path x:Name="SurfacePath" ... etc .../>
</Canvas>
</UserControl>
Looks great on the screen but I need to write code that will create from all of this a new image "mask" of the same size as the original image. This mask will represent a combination of the two, with the original image pixels as black but the overlaid shape pixels as white. (A low-level SDK will later use the pixel data for some heavy math.)
Unfortunately, I am at a loss as to how to even approach creating this "mask" image. Aside from laying out elements in panels and building paths with geometries, the way that WPF drawing really works has always confused me. I get lost in all classes with similar names and when to use them, ImageDrawing vs DrawingImage, etc. I've read and googled and searched here quite a bit and I still don't know where to start.
Can anyone give me even the rough outline of the steps I would take to even approach this?
Here's an somewhat more complete version of what I'm using.
<UserControl x:Class="MyProject.Core.Shapes.ShapeCanvas"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
UseLayoutRounding="true"
>
<Canvas x:Name="Scene">
<!--Image is drawn first, underneath everything else. Image 0,0 is Canvas 0,0-->
<Image x:Name="CurrentImage"
Canvas.Left="0" Canvas.Top="0"
Source="{Binding Image, Mode=OneWay}"/>
<!-- Path showing the current polyline created with the "Surface" tool. -->
<Path x:Name="SurfacePath"
ClipToBounds="False"
Canvas.Left="0"
Canvas.Top="0"
Stroke="Gray"
StrokeThickness="50"
>
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="{Binding CurrentSegmentStartPoint}">
<PathFigure.Segments>
<PolyBezierSegment Points="{Binding CurrentSegmentPoints}"/>
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Canvas>
</UserControl>

I have used this code for rendering a WPF control to a BitmapSource/Image for code-behind:
public static BitmapSource Create(UIElement control, Size size = default(Size))
{
if (control == null)
return null;
if (size == Size.Empty || (size.Height == 0 && size.Width == 0))
size = new Size(double.PositiveInfinity, double.PositiveInfinity);
control.Measure(size);
RenderTargetBitmap bmp = new RenderTargetBitmap((int)control.DesiredSize.Width, (int)control.DesiredSize.Height, 96, 96, PixelFormats.Pbgra32);
Rect rect = new Rect(0, 0, control.DesiredSize.Width, control.DesiredSize.Height);
control.Arrange(rect);
control.UpdateLayout();
bmp.Render(control);
bmp.Freeze();
return bmp;
}
It is derivative of answers here: https://stackoverflow.com/a/27077188/8302901
If your control is an instance of the control that is already on the screen, skip the measure, arrange and layout steps and just use the already calculated size.

Related

How to properly align Canvas with the underlying Image?

My application detects a foreign object (blob, cluster etc) in the live webcam image and displays object's outline on top of the image. To achieve that I employ Image and Canvas elements as follows:
<Border x:Name="ViewportBorder" Grid.Column="0" Grid.Row="1" BorderThickness="3" Background="AliceBlue" BorderBrush="Red">
<Grid>
<Image x:Name="videoPlayer" Stretch="Uniform" MouseDown="videoPlayer_MouseDown"></Image>
<Canvas x:Name="ObjectsCanvas"></Canvas>
</Grid>
</Border>
Border element in the above XAML is used just to draw a thick red line border around the Grid containing videoPlayer and ObjectsCanvas. Stretch="Uniform" is set to preserve image aspect ratio while being able it to stretch when application window gets maximized.
Every time the new frame arrives from the camera videoPlayer.Source gets updated with frame's bitmap whereas blob detection method yields a list of coordinates used for drawing a Polyline. The Polyline object is then added to ObjectsCanvas to be shown on top of the actual image frame.
Here's a part which draws the blob and adds it to the ObjectsCanvas.Children:
private void DrawBlob(List<Point> corners)
{
this.Dispatcher.Invoke(() =>
{
var myPolyline = new Polyline();
myPolyline.Stroke = System.Windows.Media.Brushes.Yellow;
myPolyline.StrokeThickness = 4;
myPolyline.FillRule = FillRule.EvenOdd;
myPolyline.Points = corners;
Canvas.SetLeft(myPolyline, 0);
Canvas.SetTop(myPolyline, 0);
ObjectsCanvas.Children.Clear(); // remove any old blob polyline
ObjectsCanvas.Children.Add(myPolyline); // add new polyline
});
}
When running the application I observe imperfect overlap of the blob object (thick yellow polyline), it gets somewhat right-shifted as shown in the image below.
Observed imperfection is not due to blob detection algorithm! I verified that by drawing the polylines of very same coordinates using old-fashion GDI methods on the actual bitmap.
It gets worse when I maximize the application window, an action causing videoPlayer to stretch:
I tried setting HorizontalAlignment and VerticalAlignment properties of ObjectsCanvas to Stretch but that does not help. Is there any method to align canvas exactly with the actual displayed image region?
I could get back to drawing the polylines using GDI, but I think it's a shame doing so in WPF...
I think I found a solution.
So, in order to stretch your canvas up to your image size you could wrap your canvas in ViewvBox control and bind your Canvas.Height and Canvas.Width to the image source's Height and Width like so:
<Grid>
<Image x:Name="MyFrame" Stretch="Uniform" />
<Viewbox Stretch="Uniform">
<Canvas
x:Name="MyCanvas"
Width="{Binding ElementName=MyFrame, Path=Source.Width}"
Height="{Binding ElementName=MyFrame, Path=Source.Height}">
<Canvas.Background>
<SolidColorBrush Opacity="0" Color="White" />
</Canvas.Background>
</Canvas>
</Viewbox>
</Grid>
However you need to set your Canvas.Background (you can make it transparent like in the example above), or ViewvBox will hide all of the canvas children otherwise.
Here are some screenshots of it working with a yellow polyline:
One more thing to note here - the coordinates of your polyline should be relative to image resolution.
Hope that helps!

DrawingImage is getting fuzzy and image gets bigger and cut-off when Windows scaling is set to more than 100%, for example 125%

I have an WPF usercontrol which is used in a winforms applications. WPF usercontrol is contained within an ElementHost container.
This WPF usercontrol has some images, button with images, labels, etc.
I have a dictionary with some geometries, the following is one of them.
<Geometry x:Key="hlpGeometry">F0 M22,22z M0,0z M11,22C17.0751,22 22,17.0751 22,11 22,4.92487 17.0751,0 11,0 4.92487,0 0,4.92487 0,11 0,17.0751 4.92487,22 11,22z M12.1901,16.6889L10.2662,16.6889 10.2662,18.6998 12.1901,18.6998 12.1901,16.6889z M8.18758,5.59005C7.40125,6.43439,7.00808,7.55265,7.00808,8.94484L8.72898,8.94484C8.76121,8.10695 8.89334,7.46564 9.12537,7.02091 9.53787,6.2217 10.2823,5.82209 11.3587,5.82209 12.2288,5.82209 12.8508,6.05412 13.2246,6.51818 13.6049,6.98224 13.795,7.53009 13.795,8.16173 13.795,8.61291 13.6661,9.04797 13.4083,9.46691 13.2665,9.70539 13.0796,9.9342 12.8475,10.1533L12.0741,10.9171C11.3329,11.6454 10.8527,12.2932 10.6336,12.8604 10.4144,13.4211 10.3049,14.1623 10.3049,15.084L12.0258,15.084C12.0258,14.2719 12.116,13.6596 12.2965,13.2471 12.4834,12.8281 12.8862,12.319 13.505,11.7195 14.3557,10.8945 14.9197,10.2694 15.1969,9.84396 15.4804,9.41857 15.6222,8.86427 15.6222,8.18107 15.6222,7.05314 15.2387,6.12824 14.4718,5.40636 13.7112,4.67804 12.6961,4.31388 11.4263,4.31388 10.0535,4.31388 8.9739,4.73927 8.18758,5.59005z</Geometry>
<DrawingGroup x:Key="hlpDrawingGroup" ClipGeometry="M0,0 V22 H22 V0 H0 Z">
<GeometryDrawing Brush="#FF00AA2B" Geometry="{StaticResource hlpGeometry}" />
</DrawingGroup>
<DrawingImage x:Key="ico_helpDrawingImage" Drawing="{StaticResource hlpDrawingGroup}" />
I have an WPF Image and I bound above DrawingImage to it using the Source attribute. I bind the source attribute to a property in the view model.
Something like below:
<Image x:Name="MyImage"
Height="24"
Width="24"
VerticalAlignment="Center"
Source="{Binding Path=MyIcon}"/>
It is working fine when windows is scaled to 100% but when scaled to a higher one, let's say, 125%, then the image gets fuzzy.
Also the image look like gets bigger than 24x24 and it is being cut-off when I set a scale greater than 100% (125%).
How can I make image to not get fuzzy and set image to always be the same size 24x24?
You can use the UseLayoutRounding and SnapsToDevicePixels properties.
Set the UseLayoutRounding property to true to prevent blurring caused by anti-aliasing and to tell the layout system to align elements with pixel boundaries.
RenderOptions.BitmapScalingMode is mainly for larger images to be displayed more smoothly, you can choose HighQuality.
Use high quality bitmap scaling, which is slower than LowQuality mode, but produces higher quality output.
You can check this link:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.bitmapscalingmode?redirectedfrom=MSDN&view=windowsdesktop-7.0
Codeļ¼š
<Image x:Name="img" MouseWheel="img_MouseWheel"
Height="24"
Width="24"
UseLayoutRounding="True"
SnapsToDevicePixels="True"
VerticalAlignment="Center"
RenderOptions.BitmapScalingMode="HighQuality"
Source="2.jpg"/>
With images I have always found the below to always clear up the fuzzy stuff.
<Image x:Name="MyImage"
Height="24"
Width="24"
RenderOptions.BitmapScalingMode="HighQuality"
VerticalAlignment="Center"
Source="{Binding Path=MyIcon}"/>

Tint a partially transparent image in WPF

How can I tint/colorize an image in WPF (using MVVM) without sacrificing performance? A purely XAML solution would be ideal, as modifying bitmaps in the code will cause performance loss with lots of changing images. The image is made up of more than simple shapes, so it is not possible using a path.
Unlike WinForms/GDI+, WPF does not seem to contain any easy ways to colorize/tint an image as it is being rendered. Two ideas for accomplishing this are, using a shader, or overlaying a colored rectangle over the image.
I decided to try the rectangle route and found that it works. Basically, all you need to do is overlay a colored rectangle over your image, and use an OpacityMask to restrict the color fill to a certain area. OpacityMask is primarily used with paths, but it can take any kind of brush, including an ImageBrush. This means you can use your image as a "stencil" for the colored fill.
Example: (Taken from my application where a user can "highlight" a section of a map, the actual image looks like this)
Before Overlay & Mask
After Overlay & Mask
Here is all of the required XAML for this:
<Image
Source="{Binding MyImage}"
Width="150"
Height="150" />
<Rectangle Width="150" Height="150">
<Rectangle.Fill>
<SolidColorBrush Color="{Binding Color}"/>
</Rectangle.Fill>
<Rectangle.OpacityMask>
<ImageBrush ImageSource="{Binding MyImage}"/>
</Rectangle.OpacityMask>
</Rectangle>
To bind the color to a brush as I did, use a ColorToBrushConverter.

how to user Rectangle

I would like to user Rectangle to display two colors in VS 2010 window phone, if Rectangle can't, please tell how to do.
<Rectangle Height="20" Width="400" Stroke="White" Fill="Green" StrokeThickness="1">
maybe its possible through a gradient. but otherwise i would suggest to do two rectangles, so both just have the half of the height/width (depending on where you want do have the colors)

C# WPF grab screenshot with SnippingTool effect

I'm trying to integrate a screenshot grabbing feature in my WPF app and I'd like it to look like snipping tool.
So far I've managed accomplish something similar by creating a fullscreen window (with a canvas) with opacity set to 0.5 and dark background. When I click somewhere and start dragging, a white rectangle is drawn, generating an effect similar to this.
What I'd like to have is the inner part of that rectangle opening a opacity hole in the background canvas, so that I could see through the selected area - just like snipping tool.
Problem is, being fairly new to .NET, I have no idea how or where to start. Did some research and tests on the OpacityMask field of the screenshot window but got nowhere.
Here's a little vid to show the current effect.
Edit: Also, as bonus question, is there an easy way to grab a screenshot that spans across multiple monitors (virtual screen)? Graphics.CopyFromScreen() only seems to work for 1 screen.
Already fixed this and seems to work for all possible weird virtual desktop layouts:
// Capture screenie (rectangle is the area previously selected
double left = Canvas.GetLeft(this.rectangle);
double top = Canvas.GetTop(this.rectangle);
// Calculate left/top offset regarding to primary screen (where the app runs)
var virtualDisplay = System.Windows.Forms.SystemInformation.VirtualScreen;
var primaryScreen = System.Windows.Forms.Screen.PrimaryScreen.Bounds;
if (virtualDisplay.Left < primaryScreen.Left)
{
left -= Math.Abs(virtualDisplay.Left - primaryScreen.Left);
}
if (virtualDisplay.Top < primaryScreen.Top)
{
top -= Math.Abs(virtualDisplay.Top - primaryScreen.Top);
}
You can have a CombinedGeometry with GeometryCombineMode="Exclude" creating a "punched" effect. Sample:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" AllowsTransparency="True"
WindowStyle="None" Background="Transparent">
<Canvas >
<Path Stroke="Black" Fill="White" Opacity=".5">
<Path.Data>
<CombinedGeometry GeometryCombineMode="Exclude">
<CombinedGeometry.Geometry1>
<RectangleGeometry Rect="0,0,800,600" >
</RectangleGeometry>
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<RectangleGeometry Rect="50,50,100,100" >
</RectangleGeometry>
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</Path.Data>
</Path>
</Canvas>
</Window>

Categories