How to create ImageBrush from System.Drawing.Image in WPF? - c#

I have image in System.Drawing.Image object and I need to create an ImageBrush object (used for Fill property of Rectangle in WPF for example) from it. I guess there should be a way to do this, but I can't find one.

var image = System.Drawing.Image.FromFile("..."); // or wherever it comes from
var bitmap = new System.Drawing.Bitmap(image);
var bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(bitmap.GetHbitmap(),
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions()
);
bitmap.Dispose();
var brush = new ImageBrush(bitmapSource);
This solution, however, doesnt free the memory of the handle. For information on how to remove the memory leak see WPF CreateBitmapSourceFromHBitmap() memory leak

<Rectangle x:Name="RectangleName"
StrokeThickness="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Width="200"
Height="300"
Stroke="Black" >
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding SelectedComponentsImage}" x:Name="ComponentVisualBrush" ViewboxUnits="Absolute"
Viewbox="0,0,300,300" ViewportUnits="RelativeToBoundingBox" Stretch="UniformToFill" Viewport="0,0,1,1"
RenderOptions.EdgeMode="Aliased" />
</Rectangle.Fill>
</Rectangle>
This is with viewmodel Binding. You can replace the Binding with an image uri.

Related

RenderTargetBitmap Image Sliding

I'm having a problem with RenderTargetBitmap whenever I render canvas and clear its children and set the rendered bitmap as background of canvas it slide toward bottom right.
can't insert images until 10 reputation :(.
WPF:
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="500" Width="700"
KeyDown="Window_KeyDown">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<Border BorderBrush="Black" BorderThickness="1" Grid.Row="1" Grid.Column="1">
<Canvas x:Name="Pad">
<Rectangle Height="100" Width="100" Fill="Red" Canvas.Left="10" Canvas.Top="10"></Rectangle>
</Canvas>
</Border>
</Grid>
</Window>
c# code:
namespace WpfApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_KeyDown(object sender, KeyEventArgs e)
{
RenderTargetBitmap rendrer = new RenderTargetBitmap(Convert.ToInt32(Pad.ActualWidth), Convert.ToInt32(Pad.ActualHeight), 96, 96, PixelFormats.Pbgra32);
rendrer.Render(Pad);
Pad.Background = new ImageBrush(rendrer);
Pad.Children.Clear();
}
}
}
To avoid any offset problems with drawing a Visual into a RenderTargetBitmap, you may use an intermediate DrawingVisual:
var rect = new Rect(Pad.RenderSize);
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
dc.DrawRectangle(new VisualBrush(Pad), null, rect);
}
var bitmap = new RenderTargetBitmap(
(int)rect.Width, (int)rect.Height, 96, 96, PixelFormats.Default);
bitmap.Render(visual);
Pad.Background = new ImageBrush(bitmap);
Pad.Children.Clear();
Note that without setting any further properties of the ImageBrush (like e.g. its Viewport), it will fill the entire area of the Rectangle. For details, see TileBrush Overview.
Your primary problem stems from the fact that, due to the 1-pixel border around the Canvas, its VisualOffset vector is (1,1). Thus, any visual effect, like the background brush, will be applied at that offset. When you render the visual into a bitmap, it captures the present appearance, and then when you set the bitmap as the brush, it gets shifted.
Ironically, one of the easiest ways to fix this is to insert another <Border/> element into your XAML:
<Border BorderBrush="Black" BorderThickness="1" Grid.Row="1" Grid.Column="1">
<Border>
<Canvas x:Name="Pad">
<Rectangle Height="100" Width="100" Fill="Red" Canvas.Left="10" Canvas.Top="10"/>
</Canvas>
</Border>
</Border>
Then the offset caused by the outer <Border/> element is handled by the new <Border/> element's transform, rather than being applied to the <Canvas/> element.
That change alone will almost fix your code completely. However, there's one other little artifact that you may still notice: every time you render the visual, it gets just a teensy bit blurrier. This is because the default value for the Brush object's Stretch property is Stretch.Fill, and because your <Canvas/> element is not precisely an integral width or height, the bitmap (which necessarily does have integral width and height) gets stretched just a teensy bit when rendered. With each iteration, this becomes more and more apparent.
You can fix that by setting the Stretch property to Stretch.None. At the same time, you'll also want to set the brush's alignment to Left and Top:
private void Window_KeyDown(object sender, KeyEventArgs e)
{
RenderTargetBitmap renderer = new RenderTargetBitmap(
Convert.ToInt32(Pad.ActualWidth), Convert.ToInt32(Pad.ActualHeight), 96, 96, PixelFormats.Pbgra32);
renderer.Render(Pad);
ImageBrush brush = new ImageBrush(renderer);
brush.AlignmentX = AlignmentX.Left;
brush.AlignmentY = AlignmentY.Top;
brush.Stretch = Stretch.None;
Pad.Background = brush;
Pad.Children.Clear();
}
The defaults are Center, which again incurs the rounding error and will cause both movement and blurring of the image after repeated iterations of the process.
With the above changes, I found a perfectly stable image, regardless of the number of iterations.
The "wrap in a border" idea came from here: https://blogs.msdn.microsoft.com/jaimer/2009/07/03/rendertargetbitmap-tips/
On that page you'll find a more general-purpose solution which does not require modification of the actual XAML. In your example above, the "wrap in a border" approach seems like a reasonable work-around, but it is admittedly not as clean as forcing an unadorned context into which you can render the visual, as shown on that blog page.

WPF - InkCanvas - Background Image

I successfully changed the background of my InkCanvas from code behind with image using following code:
ImageBrush imageBrush = new ImageBrush();
imageBrush.ImageSource = new BitmapImage(new Uri("temp.jpg", UriKind.Relative));
inkCanvas1.Background = imageBrush;
Now I want to resize the resolution of background image only.
For example, if my InkCanvas size is 500 x 500, I want to show the background image in my InkCanvas at center with resolution of 300 x 300.
Is this possible ?
Any help in this regard will be highly appreciated..
This, of course, there are even many ways, for instance, you can set the RelativeTransform property:
<InkCanvas.Background>
<ImageBrush>
<ImageBrush.RelativeTransform>
<TransformGroup>
<ScaleTransform CenterY="0.5" CenterX="0.5" ScaleX="2" ScaleY="2"/>
</TransformGroup>
</ImageBrush.RelativeTransform>
</ImageBrush>
</InkCanvas.Background>
That way, your background image is twice the size of the previous one. If you want more precise control of the background, you can use the VisualBrush, just like below:
<Grid.Background>
<VisualBrush>
<VisualBrush.Visual>
<Image Width="200" Height="200"></Image>
</VisualBrush.Visual>
</VisualBrush>
</Grid.Background>

Different images are printed as same image using PrintVisual

I'm using PrintDialog.PrintVisual() to print my viewmodel. For this I use binding on a dictionary:
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="329" Height="204">
<Image Source="{Binding Details.Properties[Imagedata].ImageStream}" Stretch="Fill" Width="95" Height="122" Canvas.Left="14" Canvas.Top="41"/>
<Image Source="{Binding Details.Properties[Signature].ImageStream}" Stretch="Fill" Height="25" Width="100" Canvas.Left="122" Canvas.Top="143"/>
</Canvas>
If I put this Canvas on my WPF window, it's showing fine. But if I want to print it, it's using the first image on my second image. In debugger I checked after UpdateLayout the Image.Source - both are right. The next step is PrintDialog.Printvisual(canvas, "print"); The output is as described but not the same every time. Sometimes the first image is even repeated in the second Image.
Why does this happen?
UPDATE: I found a workaround: I search for all Image elements, copy the ImageSource into a MemoryStream and create a new BitmapImage out of it. It's not nice, but it works right now. If anybody could explain, why this works, it would be great.

Adding collection of Images in Xaml

I would like to paint Images to the XAML page in Windows Store App.
The main goal is that:
Adding Images (e.g. flower leaf) to a circle on the center like that:
I have a simple solution to that, but its is very redundant.
<Image Height="200" Width="200" Source="{Binding ActualImage.NormalUri}">
<Image.RenderTransform>
<RotateTransform Angle="12"></RotateTransform>
</Image.RenderTransform>
</Image>
... // And 28 other like these
<Image Height="200" Width="200" Source="{Binding ActualImage.NormalUri}">
<Image.RenderTransform>
<RotateTransform Angle="360"></RotateTransform>
</Image.RenderTransform>
</Image>
How can I do that with a binding of a Image collection? What XAML control should I use?
Use a custom class which inherits from ItemsControl. You can then override the necessary functions, such as ones to determine the angle to rotate between each item. I think it's likely you'll want to use this PrepareContainerForItemOverride for this.
One thing to note that you will have to do is to define a new ItemsPanel. The default is a StackPanel, which will not work. You'll likely want to use something like a Canvas, which allows you to explicitly position items in it.
The Solution of Nate Diamond is much more nicer and better, but I solved it from code-behind for earn the easier way:
foreach (Petal petalObject in MainPageViewModel.Petals)
{
var petalImage = new Image
{
Height = petalObject.Height,
Width = petalObject.Width,
RenderTransform = new RotateTransform() {Angle = petalObject.Angle},
Source = new BitmapImage(new Uri(petalObject.NormalUri)),
};
PetalsGrid.Children.Add(petalImage);
}

How to set the background of Rectangle with a picture?

I create a Rectangle
public void Set(Rectangle maps, int y, int x) {
Map.Children.Add(maps);
maps.SetValue(Grid.RowProperty, x);
maps.SetValue(Grid.ColumnProperty, y);
}
But How to change the background with "Resources/1.jpg"?
Like this:
<Rectangle>
<Rectangle.Fill>
<ImageBrush ImageSource="/YourAppName;component/Resources/1.jpg" />
</Rectangle.Fill>
</Rectangle>
EDITED AGAIN (Sorry)
Or in C#
maps.Fill = new ImageBrush {
ImageSource = new BitmapImage(new Uri(#"pack://application:,,,/YourAppName;component/Resources/1.jpg", UriKind.Absolute))
};
I was having trouble using the "/YourAppName;" portion of the address as suggested by #Jonny Piazzi. It probably works, I just couldn't get it to. Alternatively, I was able to make this method work.
1) I added the image to my project in a folder I created: Images > Backgrounds > JellyFishBackground.jpg
2) I right clicked the image in Solution Explorer > Properties > Set Build Action to Resource
3) Build project
4) Simply target the image as such: (in my case I targeted a row of my grid, and used a Stretch property, which are beyond the scope of this question, just fyi to avoid confusion)
<Rectangle Grid.Row ="0">
<Rectangle.Fill>
<ImageBrush ImageSource="/Images/Backgrounds/JellyFishBackground.jpg" Stretch="UniformToFill"/>
</Rectangle.Fill>
</Rectangle>

Categories

Resources