DrawingContext.PushOpacityMask() with BitmapImage does not work - wpf

I want to take a given BitmapImage and use its grayscale/black-and-white representation as an opacity mask on a DrawingContext.
I got far enough to do the color conversion, so I ended up with the follwing state of a demo application:
Method on ViewModel:
public void Demo()
{
var width = 400;
var height = 400;
var target = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
var target2 = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
var drawingVisual = new DrawingVisual();
var drawingVisual2 = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
var bitmapImage = new BitmapImage(new Uri(#"pack://application:,,,/WarningFilled.png"));
using (var drawingContext2 = drawingVisual2.RenderOpen())
{
drawingContext2.DrawImage(bitmapImage, new Rect(0, 0, width, height));
}
target2.Render(drawingVisual2);
var mask = target2.ToGrayscale();
this.Mask = mask;
drawingContext.PushOpacityMask(new ImageBrush(mask));
drawingContext.DrawRectangle(Brushes.Red, null, new Rect(0, 0, width, height));
// drawingContext.Pop();
}
target.Render(drawingVisual);
this.Rendered = target;
}
View:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<wpfApplication1:MainViewModel></wpfApplication1:MainViewModel>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" BorderThickness="2" BorderBrush="Blue" HorizontalAlignment="Center">
<Image Source="{Binding Rendered}" Width="400" Height="400"></Image>
</Border>
<Border Grid.Column="1" BorderThickness="2" BorderBrush="Green" HorizontalAlignment="Center">
<Image Source="{Binding Mask}" Width="400" Height="400"></Image>
</Border>
</Grid>
Running the demo application produces this:
I expected the red rectangle on the left to reflect the opacity mask pushed onto the DrawingContext before drawing the actual rectangle.
The image you see on the right is the BitmapImage after the color conversion, right before it is pushed as an opacity mask. As I mentioned before I also tried it with the black/white conversion, but even with only two colors there's no effect.
I've tried many similar setups and scenarios, but I don't get the opacity mask to work.

From the Opacity Masks Overview article on MSDN (emphasis mine):
To create an opacity mask, you apply a Brush to the OpacityMask
property of an element or Visual. The brush is mapped to the element
or visual, and the opacity value of each brush pixel is used to
determine the resulting opacity of each corresponding pixel of the
element or visual.
The color values of the pixel are ignored.

Related

wpf oxyplot PlotAreaBackground image

I want a colorful background. My code not work. Version OxyPlotWpf: 1.0.0-unstable2100. Date published: Tuesday, April 12, 2016 (4/12/2016)
Can not imagine what else you can write in detail, my questions not passed since the system thinks there is not enough detail, so I added the text (sorry)
What i have
What i want
My GrpahPhone.png
<Window x:Class="OxiPlotTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:OxiPlotTest"
xmlns:oxy="clr-namespace:OxyPlot.Wpf;assembly=OxyPlot.Wpf"
xmlns:oxy2="http://oxyplot.codeplex.com"
mc:Ignorable="d"
Title="MainWindow" Height="250" Width="750"
Background="#333333">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="200"/>
</Grid.RowDefinitions>
<oxy:Plot Grid.Row="0" x:Name="oxyPlot2" Title="" Margin="10" Background="Transparent" PlotAreaBorderThickness="0,0,0,1" PlotAreaBorderColor="#CCCCCC">
<oxy:Plot.Axes>
<oxy:LinearAxis Position="Left" Minimum="0" Maximum="100" MajorGridlineColor="#333333" MajorGridlineStyle="Solid" MajorGridlineThickness="1" MajorStep="25" MinorStep="25"
TickStyle="None" TextColor="#CCCCCC" FontSize="16" FontWeight="Bold" AxisDistance="5"/>
<oxy:LinearAxis Position="Bottom" Minimum="0" Maximum="5" MajorGridlineStyle="None" MajorStep="1" MinorStep="1"
TextColor="#CCCCCC" FontSize="16" FontWeight="Bold" TicklineColor="#CCCCCC"/>
</oxy:Plot.Axes>
<oxy:Plot.Series>
<oxy:LineSeries ItemsSource="{Binding Points}" Color="AliceBlue" StrokeThickness="4"/>
</oxy:Plot.Series>
</oxy:Plot>
</Grid>
public partial class MainWindow : Window
{
public IList<DataPoint> Points { get; private set; }
public MainWindow()
{
InitializeComponent();
Points = new List<DataPoint>
{
new DataPoint(0, 5),
new DataPoint(1, 40),
new DataPoint(2, 55),
new DataPoint(3, 80),
new DataPoint(4, 90),
new DataPoint(5, 50),
};
DataContext = this;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.UriSource = new Uri("pack://application:,,,/OxiPlotTest;component/Resources/GraphFon.png", UriKind.Absolute);
bitmapImage.EndInit();
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create((BitmapImage)bitmapImage));
var stream = new System.IO.MemoryStream();
encoder.Save(stream);
stream.Flush();
var image = new System.Drawing.Bitmap(stream);
var bitmap = new System.Drawing.Bitmap(image);
var bitmapSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(bitmap.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
bitmap.Dispose();
var brush = new ImageBrush(bitmapSource);
oxyPlot2.PlotAreaBackground = brush;
}
}
I made 5 different List Points by color and added LineSeries

Add controls into textblock wpf in code behind

How can i convert this UI into code behind wpf
<TextBlock x:Name="tblImgCorrectAnswer">
<Span>Hello</Span>
<Span Style="{DynamicResource FontMSMincho}">て</Span>
<InlineUIContainer BaselineAlignment="Center">
<TextBlock>
<Image Source="Images/icon.ico" Width="40" Height="40"/>
</TextBlock>
</InlineUIContainer>
</TextBlock>
I cann't anyway to add item control into textblock control or TextBlock control into InlineUIContainer control by using code-behind.
Thanhks.
The result like This
Code Behind
var textBlock = new TextBlock();
var inlineHello = new Span();
inlineHello.Inlines.Add("Hello");
var inlineJSighn = new Span();
inlineJSighn.Inlines.Add(" JSighn");
BitmapImage logo = new BitmapImage();
logo.BeginInit();
logo.UriSource = new Uri("pack://application:,,,/Images/Koala.jpg", UriKind.Absolute);
logo.EndInit();
var inlineUiContainer = new InlineUIContainer(new Image { Source = logo });
inlineUiContainer.BaselineAlignment = BaselineAlignment.Center;
textBlock.Inlines.Add(inlineHello);
textBlock.Inlines.Add(inlineJSighn);
textBlock.Inlines.Add(inlineUiContainer);
LayoutRoot.Children.Add(textBlock);
Xaml
<Window x:Class="AddChildrenToGrinInCode.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid x:Name="LayoutRoot"/></Window>

Canvas print - wpf

I used these code in order to print out the UI. Printing out is working, but if the size of paper is over, the UI cuts off in the middle of a canvas.
Is there any possible way not to be cut off in the middle?
<--cs code-->
PrintDialog dialog = new PrintDialog();
dialog.PrintVisual(lst , "print");
<--Xaml -->
<ListView Name="lst">
<Grid Name="grdPrint">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Canvas Grid.Row="0" >
.......
</Canvas>
<HListBox x:Name="lstImage" ItemsSource="{Binding IMG, Mode=TwoWay}" Grid.Row="1" IsHitTestVisible="True">
<HListBox.ItemTemplate>
<DataTemplate>
<HImage Margin="0" Width="590" Height="590" Stretch="Fill" Source="{Binding IMG_PATH_NM, Converter={StaticResource StrUriConverter}}" Tag="{Binding IMG_PATH_NM}">
</HImage>
</DataTemplate>
</HListBox.ItemTemplate>
<HListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" IsHitTestVisible="True"/>
</ItemsPanelTemplate>
</HListBox.ItemsPanel>
</HListBox>
</Grid>
</ListView>
This method will print the canvas to PNG file.
public void ExportToPNG(string imgpath, Canvas surface)
{
Uri path = new Uri(imgpath);
if (path == null)
return;
Transform transform = surface.LayoutTransform;
surface.LayoutTransform = null;
Size size = new Size(surface.Width, surface.Height);
surface.Measure(size);
surface.Arrange(new Rect(size));
RenderTargetBitmap renderBitmap =
new RenderTargetBitmap(
(int)size.Width,
(int)size.Height,
96d,
96d,
PixelFormats.Pbgra32);
renderBitmap.Render(surface);
using (FileStream outStream = new FileStream(path.LocalPath, FileMode.Create))
{
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
encoder.Save(outStream);
}
surface.LayoutTransform = transform;
}
You could create an BitmapImage (see RenderTargetBitmap to create a bitmap from an element). This bitmap can then be saved as a JPEG file and manipulated using GDI+ (System.Image).

LayoutTransform animation on ScaleX and ScaleY is breaking binding with them

I have a custom class say 'MyCanvas' derived from wpf Canvas class. MyCanvas has a Dependency Property 'Scale' which specify the scale transform for the canvas. Now when the value of Scale changes I want to animate the transform from old value to new value. for that I am using LayoutTransform.BeginAnimation(...) method.
Code:
//This represents the time it will take to zoom
Duration zoomDuration = new Duration(TimeSpan.Parse("0:0:0.3"));
//Set up animation for zooming
DoubleAnimationUsingKeyFrames scaleAnimation = new DoubleAnimationUsingKeyFrames();
scaleAnimation.Duration = zoomDuration;
scaleAnimation.KeyFrames = GetAnimationSplines(newScale);
scaleAnimation.Completed += new EventHandler(
(sender, e) => Scale = newScale);
// Start the scale (zoom) animations
LayoutTransform.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnimation);
LayoutTransform.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnimation);
XMAL:
<Grid>
<ItemsControl x:Name="Items">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<local:MyCanvas Scale="{Binding Scale, Mode=TwoWay}">
<local:MyCanvas.LayoutTransform UseLayoutRounding="True">
<ScaleTransform ScaleX="{Binding Scale, Mode=TwoWay}"
ScaleY="{Binding Scale, Mode=TwoWay}"/>
</local:MyCanvas.LayoutTransform>
</local:MyCanvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
But after executing this code, the binding of ScaleX and ScaleY with Scale ( a property in viewmodel) is breaking i.e. changing value of Scale does not change scale on canvas.
Error Message (using Snoop):
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Scale; DataItem=null; target element is 'ScaleTransform' (HashCode=796423); target property is 'ScaleX' (type 'Double')
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Scale; DataItem=null; target element is 'ScaleTransform' (HashCode=796423); target property is 'ScaleY' (type 'Double')
Please let me know if any one has solution for this. Thanks
This is not a solution to above problem but a working sample WPF Application to reproduce the above mention issue.
XAML:
<Window x:Class="TestWpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.LayoutTransform>
<ScaleTransform ScaleX="{Binding ElementName=zoomer, Path=Value}" ScaleY="{Binding ElementName=zoomer, Path=Value}" x:Name="scaleTx" />
</Grid.LayoutTransform>
<Border Background="Aqua" BorderThickness="3" BorderBrush="Blue" Height="50" Width="50" />
</Grid>
<StackPanel Orientation="Horizontal" Grid.Row="1">
<Slider x:Name="zoomer" Width="200" Value="1" Minimum="0.1" Maximum="3" TickFrequency="0.1" IsSnapToTickEnabled="True" Margin="3"/>
<Button Content="Animate" Click="Button_Click" Margin="3"/>
</StackPanel>
</Grid>
Code:
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//This represents the time it will take to zoom
Duration zoomDuration = new Duration(TimeSpan.Parse("0:0:0.5"));
//Set up animation for zooming
DoubleAnimationUsingKeyFrames scaleAnimation = new DoubleAnimationUsingKeyFrames();
scaleAnimation.Duration = zoomDuration;
scaleAnimation.KeyFrames = GetAnimationSplines(zoomer.Maximum);
scaleTx.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnimation);
scaleTx.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnimation);
}
/// <summary>
/// This creates a spline for zooming with non-linear acceleration patterns.
/// </summary>
/// <param name="moveTo"></param>
/// <returns></returns>
protected DoubleKeyFrameCollection GetAnimationSplines(double moveTo)
{
DoubleKeyFrameCollection returnCollection = new DoubleKeyFrameCollection();
returnCollection.Add(new LinearDoubleKeyFrame(moveTo, KeyTime.FromPercent(1.0)));
return returnCollection;
}
}
The shape in the center of window will scale when slide is moved. Once you click animate button to apply animation, the zooming will animate but after that slider stops working.
Well, Today I was trying some thing and suddenly the issue got resolved. I did not understand how it start working. I made following changes to the code, which I tried earlier too.
scaleAnimation.FillBehavior = FillBehavior.Stop;
Can anyone explain this to me. Thanks.
I can corroborate that I've had this happen too, but I haven't yet found a clean solution. I had a case just like you describe in one of your answers - regarding the slider change not affecting the bound object after the animation has run (and yes, I had the FillBehavior set to Stop and/or run BeginAnimation(someProperty, null) to "release" the animation's "hold" on the property. To illustrate that I could still edit the property after animating (but not with the slider that was the source of the binding), I setup a button that would set the property to a specific value. This button did in fact work perfectly to change that property after the animation. It seems that in some cases WPF's binding may break as the result of certain animations.
A not-so-clean workaround might be to re-establish the binding in code in the animation's Completed handler.

How to draw a line on an existing BitmapSource in WPF?

I would like to draw a line (or any geometric shape) on an existing BitmapSource object in my WPF application. What is the best way to do it ?
The BitmapSource is the result of a BitmapSource.Create(...) call.
Thanks
Romain
Below sample will display an image created from a BitmapSource with a red line on top of it. Is that what you are trying to achieve?
XAML:
<Window x:Class="WpfApplication.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid Background="LightBlue">
<Image Source="{Binding Path=ImageSource}" />
<Line
Stroke="Red" StrokeThickness="10"
X1="0"
Y1="0"
X2="{Binding Path=ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}}"
Y2="{Binding Path=ActualHeight, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}}" />
</Grid>
</Window>
Code behind:
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace WpfApplication
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = this;
}
public BitmapSource ImageSource
{
get
{
PixelFormat pf = PixelFormats.Bgr32;
int width = 200;
int height = 200;
int rawStride = (width * pf.BitsPerPixel + 7) / 8;
byte[] rawImage = new byte[rawStride * height];
Random value = new Random();
value.NextBytes(rawImage);
return BitmapSource.Create(width, height, 96, 96, pf, null, rawImage, rawStride);
}
}
}
}

Resources