I want to place an image over a parent image and save the final image. So I used parent image inside Canvas and added the child image in the canvas.
Problems:
Right after when i loaded the thumbnail(child) image, if i click on the parent image, then the thumbnail(child) image become in-Visible.
I couldn't place the the thumbnail(child) image over the parent image precisely using the mouse left button up release.
I couldn't save the final image with the actual image Height & Width. The final saved image output height & width is wrong.
please guide me to fix the above problems.
SpecialEffects XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:System="clr-namespace:System;assembly=mscorlib"
x:Class="ImagePrintUtility.SpecialEffects"
Title="SpecialEffects" Height="768" Width="1024" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="125"/>
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<DockPanel Grid.Row="0" HorizontalAlignment="Stretch" Margin="0" Background="AliceBlue" Name="DockPanel1">
<WrapPanel>
<StackPanel Margin="5">
<Button Content="AddLogo" Click="Button_Click" Height="50" Width="90" />
<Button Content="Reset Logo" x:Name="bresetLogo" Height="50" Width="90" Margin="0,5" />
</StackPanel>
<StackPanel Margin="5">
<Button Name="bSave" Width="90" Height="50" Foreground="White" Content="Save" Click="bSave_Click" />
<Button x:Name="btnClose" Content="Close" Height="50" Width="90" Margin="0,5" FontSize="20" Click="btnClose_Click" />
</StackPanel>
</WrapPanel>
</DockPanel>
<!--<GridSplitter Grid.Row="1" Grid.RowSpan="1" ResizeDirection="Rows" Width="Auto" Height="10" HorizontalAlignment="Stretch" Margin="0" Name="GridSplitter1" />-->
<Grid Grid.Row="1" Margin="0" Background="AliceBlue" Name="Grid1">
<StackPanel >
<Canvas x:Name="canvas" HorizontalAlignment="Stretch"
MouseLeftButtonDown="CanvasMouseLeftButtonDown"
MouseLeftButtonUp="CanvasMouseLeftButtonUp"
MouseMove="CanvasMouseMove" Margin="0,0,31,0">
<Image x:Name="SpecialPhoto" Source="IMG_0071.JPG" Height="586" Width="780"
Stretch="Uniform" VerticalAlignment="Top" HorizontalAlignment="Center" />
</Canvas>
</StackPanel>
</Grid>
</Grid>
</Window>
SpecialEffects.cs Code:
public partial class SpecialEffects : Window
{
private string FileNmae;
private int actualWidth;
private int actualHeight;
public SpecialEffects(string getTheFN) //Load the selected Image from ParentWindow
{
InitializeComponent();
FileNmae = getTheFN;
BitmapImage src = new BitmapImage();
src.BeginInit();
src.UriSource = new Uri(FileNmae, UriKind.Relative);
src.CacheOption = BitmapCacheOption.OnLoad;
src.EndInit();
SpecialPhoto.Source = src;
}
private Image draggedImage;
private Point mousePosition;
bool captured = false;
private void CanvasMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var image = e.Source as Image;
if (image != null && canvas.CaptureMouse())
{
mousePosition = e.GetPosition(canvas);
draggedImage = image;
Panel.SetZIndex(draggedImage, 1); // in case of multiple images
}
}
private void CanvasMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (draggedImage != null)
{
canvas.ReleaseMouseCapture();
Panel.SetZIndex(draggedImage, 0);
draggedImage = null;
Mouse.Capture(null);
captured = false;
}
}
private void CanvasMouseMove(object sender, MouseEventArgs e)
{
if (draggedImage != null)
{
var position = e.GetPosition(canvas);
var offset = position - mousePosition;
mousePosition = position;
double left = Canvas.GetLeft(draggedImage) + offset.X;
double top = Canvas.GetTop(draggedImage) + offset.Y;
if (left < 0)
{
left = 0;
}
if (top < 0)
{
top = 0;
}
if (left + draggedImage.ActualWidth > SpecialPhoto.ActualWidth)
{
left = SpecialPhoto.ActualWidth - draggedImage.ActualWidth;
}
if (top + draggedImage.ActualHeight > SpecialPhoto.ActualHeight)
{
top = SpecialPhoto.ActualHeight - draggedImage.ActualHeight;
}
Canvas.SetLeft(draggedImage, left);
Canvas.SetTop(draggedImage, top);
}
}
private void btnClose_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
private void bSave_Click(object sender, RoutedEventArgs e)
{
string fileLocation = System.IO.Path.GetDirectoryName(FileNmae);// get the selected file location
string getFileName = System.IO.Path.GetFileName(FileNmae); //get the seleceted filename
DateTime time = DateTime.Now; // Use current time
string format = "MMMddddHHmmssyyyy";
string s2 = time.ToString(format) + getFileName; // add $ at front along with the folde name
string filenamecombined = System.IO.Path.Combine(fileLocation, s2);//combine path.
RenderTargetBitmap renderTarget = new RenderTargetBitmap(
(int)SpecialPhoto.Height,
(int)SpecialPhoto.Width,
96, 96, PixelFormats.Pbgra32);
//renderTarget.Render(ViewedPhoto);
ModifyPosition(canvas as FrameworkElement);
renderTarget.Render(canvas);
ModifyPositionBack(canvas as FrameworkElement);
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderTarget));
//string imagePath = System.IO.Path.GetTempFileName();
using (FileStream stream = new FileStream(filenamecombined, FileMode.Create))
{
encoder.Save(stream);
stream.Dispose();
}
}
private void ModifyPosition(FrameworkElement fe)
{
/// get the size of the visual with margin
System.Windows.Size fs = new System.Windows.Size(
fe.ActualWidth +
fe.Margin.Left + fe.Margin.Right,
fe.ActualHeight +
fe.Margin.Top + fe.Margin.Bottom);
/// measure the visual with new size
fe.Measure(fs);
/// arrange the visual to align parent with (0,0)
fe.Arrange(new Rect(
-fe.Margin.Left, -fe.Margin.Top,
fs.Width, fs.Height));
}
private void ModifyPositionBack(FrameworkElement fe)
{
/// remeasure a size smaller than need, wpf will
/// rearrange it to the original position
fe.Measure(new System.Windows.Size());
}
private void Button_Click(object sender, RoutedEventArgs e) // To load another image
{
var dialog = new Microsoft.Win32.OpenFileDialog();
dialog.Filter =
"Image Files (*.jpg;*.png; *.jpeg; *.gif; *.bmp)|*.jpg;*.png; *.jpeg; *.gif; *.bmp";
if ((bool)dialog.ShowDialog())
{
BitmapImage src = new BitmapImage();
src.BeginInit();
src.UriSource = new Uri(dialog.FileName, UriKind.Relative);
src.DecodePixelHeight = 120;
src.DecodePixelWidth = 120;
src.CacheOption = BitmapCacheOption.OnLoad;
src.EndInit();
var image = new Image { Source = src };
Canvas.SetLeft(image, 0);
Canvas.SetTop(image, 0);
canvas.Children.Add(image);
}
}
}
Problem 1 - In your xaml for the Image, add IsEnabled="False". This will stop the clicking from hiding the child image.
<Image x:Name="SpecialPhoto" Source="IMG_0071.JPG" Height="586" Width="780"
IsEnabled="False"
Stretch="Uniform" VerticalAlignment="Top" HorizontalAlignment="Center" />
Problem 2 - I didn't experience this, maybe you have some sort of mouse acceleration turned on in windows?
Problem 3 - You are using the height for the width and the width for the height. Change:
RenderTargetBitmap renderTarget = new RenderTargetBitmap(
(int)SpecialPhoto.Height,
(int)SpecialPhoto.Width,
96, 96, PixelFormats.Pbgra32);
To:
RenderTargetBitmap renderTarget = new RenderTargetBitmap(
(int)SpecialPhoto.Width,
(int)SpecialPhoto.Height,
96, 96, PixelFormats.Pbgra32);
The code for the comment question you asked:
private void bSave_Click(object sender, RoutedEventArgs e)
{
string fileLocation = System.IO.Path.GetDirectoryName(FileNmae);// get the selected file location
string getFileName = System.IO.Path.GetFileName(FileNmae); //get the seleceted filename
DateTime time = DateTime.Now; // Use current time
string format = "MMMddddHHmmssyyyy";
string s2 = time.ToString(format) + getFileName; // add $ at front along with the folde name
string filenamecombined = System.IO.Path.Combine(fileLocation, s2);//combine path.
#region Change the SpecialPhoto to be the size of its image and adjust the other images to match
double w = SpecialPhoto.Width;
double h = SpecialPhoto.Height;
SpecialPhoto.Width = SpecialPhoto.Source.Width;
SpecialPhoto.Height = SpecialPhoto.Source.Height;
// Get the ratio of the change in width/height
double rw = SpecialPhoto.Width / w;
double rh = SpecialPhoto.Height / h;
// Adjust the logos added to keep in the same relative position and size
foreach (Image img in canvas.Children)
{
if (img == SpecialPhoto)
continue;
double left = Canvas.GetLeft(img);
double top = Canvas.GetTop(img);
Canvas.SetLeft(img, left * rw);
Canvas.SetTop(img, top * rh);
img.RenderTransform = new ScaleTransform(rw, rh);
}
#endregion
RenderTargetBitmap renderTarget = new RenderTargetBitmap(
(int)SpecialPhoto.Width,
(int)SpecialPhoto.Height,
96, 96, PixelFormats.Pbgra32);
//renderTarget.Render(ViewedPhoto);
ModifyPosition(canvas as FrameworkElement);
renderTarget.Render(canvas);
ModifyPositionBack(canvas as FrameworkElement);
#region Undo the changes we did to the SpecialPhoto/logos
SpecialPhoto.Width = w;
SpecialPhoto.Height = h;
foreach (Image img in canvas.Children)
{
if (img == SpecialPhoto)
continue;
double left = Canvas.GetLeft(img);
double top = Canvas.GetTop(img);
Canvas.SetLeft(img, left / rw);
Canvas.SetTop(img, top / rh);
img.RenderTransform = new ScaleTransform(1, 1);
}
#endregion
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderTarget));
//string imagePath = System.IO.Path.GetTempFileName();
using (FileStream stream = new FileStream(filenamecombined, FileMode.Create))
{
encoder.Save(stream);
stream.Dispose();
}
}
Code for the 2nd comment question (keeping child image in the parent image):
We need to change two methods. The first method is the CanvasMouseMove:
private void CanvasMouseMove(object sender, MouseEventArgs e)
{
if (draggedImage != null)
{
var position = e.GetPosition(canvas);
var offset = position - mousePosition;
mousePosition = position;
double left = Canvas.GetLeft(draggedImage) + offset.X;
double top = Canvas.GetTop(draggedImage) + offset.Y;
Point tl = SpecialPhoto.TranslatePoint(new Point(0, 0), canvas);
Point br = SpecialPhoto.TranslatePoint(new Point(SpecialPhoto.ActualWidth, SpecialPhoto.ActualHeight), canvas);
if (left < tl.X)
{
left = tl.X;
}
if (top < tl.Y)
{
top = tl.Y;
}
if (left + draggedImage.ActualWidth > br.X)
{
left = br.X - draggedImage.ActualWidth;
}
if (top + draggedImage.ActualHeight > br.Y)
{
top = br.Y - draggedImage.ActualHeight;
}
Canvas.SetLeft(draggedImage, left);
Canvas.SetTop(draggedImage, top);
}
}
The 2nd method is the Button_Click:
private void Button_Click(object sender, RoutedEventArgs e) // To load another image
{
var dialog = new Microsoft.Win32.OpenFileDialog();
dialog.Filter =
"Image Files (*.jpg;*.png; *.jpeg; *.gif; *.bmp)|*.jpg;*.png; *.jpeg; *.gif; *.bmp";
if ((bool)dialog.ShowDialog())
{
BitmapImage src = new BitmapImage();
src.BeginInit();
src.UriSource = new Uri(dialog.FileName, UriKind.Relative);
src.DecodePixelHeight = 120;
src.DecodePixelWidth = 120;
src.CacheOption = BitmapCacheOption.OnLoad;
src.EndInit();
var image = new Image { Source = src };
Point p = SpecialPhoto.TranslatePoint(new Point(0, 0), canvas);
Canvas.SetLeft(image, p.X);
Canvas.SetTop(image, p.Y);
canvas.Children.Add(image);
}
}
Related
I was following this article and I got my canvas to be saved, however, I want to extend the code's functionality and save a particular part of my canvas as an image, rather than my entire canvas.
I tried setting the rect.Offset and rect.Location properties but the image is always saved from the upper left corner of my canvas.
Does anyone know how can I achieve my wanted functionality in a similar way?
Thanks!
A simple method would be to use a CroppedBitmap after rendering the whole canvas. You could reuse the same RenderTargetBitmap, if you need multiple images.
RenderTargetBitmap rtb = new RenderTargetBitmap((int)canvas.RenderSize.Width,
(int)canvas.RenderSize.Height, 96d, 96d, System.Windows.Media.PixelFormats.Default);
rtb.Render(canvas);
var crop = new CroppedBitmap(rtb, new Int32Rect(50, 50, 250, 250));
BitmapEncoder pngEncoder = new PngBitmapEncoder();
pngEncoder.Frames.Add(BitmapFrame.Create(crop));
using (var fs = System.IO.File.OpenWrite("logo.png"))
{
pngEncoder.Save(fs);
}
If you want to save to a bitmap object instead of a file, you can use:
using (Stream s = new MemoryStream())
{
pngEncoder.Save(s);
Bitmap myBitmap = new Bitmap(s);
}
I know this is an old question, but it took me a while of searching and trying different answers to come up with something that worked reliably well. So to save some time for those in the future, here is a little service to either save a canvas out to a file, or return an ImageSource for display elsewhere in your application.
It should be made more robust for a production application, additional null and error checking, etc..
public static class RenderVisualService
{
private const double defaultDpi = 96.0;
public static ImageSource RenderToPNGImageSource(Visual targetControl)
{
var renderTargetBitmap = GetRenderTargetBitmapFromControl(targetControl);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
var result = new BitmapImage();
using (var memoryStream = new MemoryStream())
{
encoder.Save(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);
result.BeginInit();
result.CacheOption = BitmapCacheOption.OnLoad;
result.StreamSource = memoryStream;
result.EndInit();
}
return result;
}
public static void RenderToPNGFile(Visual targetControl, string filename)
{
var renderTargetBitmap = GetRenderTargetBitmapFromControl(targetControl);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
var result = new BitmapImage();
try
{
using (var fileStream = new FileStream(filename, FileMode.Create))
{
encoder.Save(fileStream);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"There was an error saving the file: {ex.Message}");
}
}
private static BitmapSource GetRenderTargetBitmapFromControl(Visual targetControl, double dpi = defaultDpi)
{
if (targetControl == null) return null;
var bounds = VisualTreeHelper.GetDescendantBounds(targetControl);
var renderTargetBitmap = new RenderTargetBitmap((int)(bounds.Width * dpi / 96.0),
(int)(bounds.Height * dpi / 96.0),
dpi,
dpi,
PixelFormats.Pbgra32);
var drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
var visualBrush = new VisualBrush(targetControl);
drawingContext.DrawRectangle(visualBrush, null, new Rect(new Point(), bounds.Size));
}
renderTargetBitmap.Render(drawingVisual);
return renderTargetBitmap;
}
}
And a sample WPF app demonstrating it's use.
MainWindow.xaml
<Window x:Class="CanvasToBitmapDemo.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:CanvasToBitmapDemo"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Center">
<Button Click="Button_Click" Content="Capture Image" Width="100"/>
<Button Click="Button_Click_1" Content="Save To Disk" Width="100"/>
</StackPanel>
<Canvas x:Name="PART_Canvas" Grid.Row="1" Grid.Column="0">
<Ellipse Canvas.Top="50"
Canvas.Left="60"
Fill="Gold"
Width="250"
Height="250" />
<Polyline Stroke="#FF853D00"
StrokeThickness="10"
StrokeEndLineCap="Round"
StrokeStartLineCap="Round"
Points="110,100 120,97 130,95 140,94 150,95 160,97 170,100" />
<Ellipse Canvas.Top="115"
Canvas.Left="114"
Fill="#FF853D00"
Width="45"
Height="50" />
<Polyline Stroke="#FF853D00"
StrokeThickness="10"
StrokeEndLineCap="Round"
StrokeStartLineCap="Round"
Points="205,120 215,117 225,115 235,114 245,115 255,117 265,120" />
<Ellipse Canvas.Top="120"
Canvas.Left="208"
Fill="#FF853D00"
Width="45"
Height="50" />
<Polyline Stroke="#FF853D00"
StrokeThickness="10"
StrokeEndLineCap="Round"
StrokeStartLineCap="Round"
Points="150,220 160,216 170,215 180,215 190,216 202,218 215,221" />
</Canvas>
<Image x:Name="PART_Image" Grid.Row="1" Grid.Column="1" Stretch="None"/>
</Grid>
And the code behind making the calls into the service.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
PART_Image.Source = RenderVisualService.RenderToPNGImageSource(PART_Canvas);
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
RenderVisualService.RenderToPNGFile(PART_Canvas, "myawesomeimage.png");
}
}
Looking at the link you posted, obviously you can choose the rendered target coordinates here.
RenderTargetBitmap rtb = new RenderTargetBitmap((int)rect.Right,
(int)rect.Bottom, 96d, 96d, System.Windows.Media.PixelFormats.Default);
See if this solution works for you.
Size size = new Size(width, height);
canvas.Measure(size);
canvas.Arrange(new Rect(X, Y, width, height));
//Save Image
...
...
// Revert old position
canvas.Measure(new Size());
In my case, I needed to apply a size constraint for the resulting image, since the images needed to be stored and later used as a small icon.
I was able to scale the image down to a reasonable size using CreateScaledRect below in combination with GetScaledRenderTargetBitmapFromControl (thanks to code from #Andy Stagg's post).
Then, to store the image for later use, I used the SaveImageOfControlToStream method below.
private static Rect CreateScaledRect(Visual targetControl)
{
Rect scaledRect;
var bounds = VisualTreeHelper.GetDescendantBounds(targetControl);
// maintain aspect ratio and make sure scaledRect is at least 64 wide or 64 high
if (bounds.Width < bounds.Height)
{
scaledRect = new Rect(new Point(), new Size(64, bounds.Height / bounds.Width * 64));
}
else
{
scaledRect = new Rect(new Point(), new Size(bounds.Width / bounds.Height * 64, 64));
}
return scaledRect;
}
private static BitmapSource GetScaledRenderTargetBitmapFromControl(Visual targetControl, double dpi = defaultDpi)
{
if (targetControl == null) return null;
// Calling CreateScaledRect here to reduce image size
var bounds = CreateScaledRect(targetControl);
var renderTargetBitmap = new RenderTargetBitmap((int)(bounds.Width * dpi / 96.0),
(int)(bounds.Height * dpi / 96.0),
dpi,
dpi,
PixelFormats.Pbgra32);
var drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
var visualBrush = new VisualBrush(targetControl);
drawingContext.DrawRectangle(visualBrush, null, new Rect(new Point(), bounds.Size));
}
renderTargetBitmap.Render(drawingVisual);
return renderTargetBitmap;
}
public static void SaveImageOfControlToStream(Stream outputStream, Visual targetControl)
{
var bitmapSource = GetScaledRenderTargetBitmapFromControl(targetControl);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmapSource ));
encoder.Save(outputStream);
}
There is a Grid, which is filled dynamically with Image controls in code behind(Sorry for that).
Grid has 1 column, many pages, each page has 1 Border with Image as Border.Child inside. What I need is to Zoom (Scale) my Image in Grid when Button.Click event fires. I used Scale Transform with the Image before, but I didn't manage to bind Grid element Image with the Click handler.
Please suggest, how I can zoom images inside grid, step by step.
Thanks in advance!
Yes, I know this is horrible, should be done in different way, I'm still learning, how to do this right.
Method, that generates Grid. After that ZOOM click method ( only for zoom, there is another method for zoom out)
public void RefreshView(List<TiffImage> tiffImageList)
{
try
{
if (tiffImageList.Count == 0)
return;
SetControlSizes();
gridImageList.Children.Clear();
gridImageList.RowDefinitions.Clear();
gridImageList.ColumnDefinitions.Clear();
RowDefinitionCollection rd = gridImageList.RowDefinitions;
ColumnDefinitionCollection cd = gridImageList.ColumnDefinitions;
cd.Add(new ColumnDefinition() { Width = GridLength.Auto });
for (int i = 0; i < tiffImageList.Count; i++)
{
rd.Add(new RowDefinition() { Height = GridLength.Auto });
}
int rowIndex = 0;
foreach (var tiffImage in tiffImageList)
{
Image imageListViewItem = new Image();
imageListViewItem.Margin = new Thickness(0, 0, 0, 0);
RenderOptions.SetBitmapScalingMode(imageListViewItem, BitmapScalingMode.HighQuality);
imageListViewItem.Name = $"Image{tiffImage.index.ToString()}";
imageListViewItem.Source = tiffImage.image;
imageListViewItem.HorizontalAlignment = HorizontalAlignment.Center;
imageListViewItem.VerticalAlignment = VerticalAlignment.Center;
imageListViewItem.Stretch = Stretch.Uniform;
imageListViewItem.VerticalAlignment = VerticalAlignment.Center;
imageListViewItem.HorizontalAlignment = HorizontalAlignment.Center;
Border border = new Border();
border.BorderBrush = Brushes.LightGray;
border.BorderThickness = new Thickness(1);
Thickness margin = border.Margin;
border.Margin = new Thickness(20, 10, 20, 10);
border.Child = imageListViewItem;
Grid.SetColumn(border, 0);
Grid.SetRow(border, rowIndex);
gridImageList.Children.Add(border);
rowIndex++;
}
}
catch (Exception ex)
{
throw ex;
}
}
private void btnZoom_Click(object sender, RoutedEventArgs e)
{
foreach (UIElement item in gridImageList.Children)
{
Border border = (Border)item;
Image image = (Image)border.Child;
var imgViewerScaleTransform = (ScaleTransform)(image.LayoutTransform);
if ((imgViewerScaleTransform.ScaleX + 0.2) > 3 || (imgViewerScaleTransform.ScaleY + 0.2) > 3)
return;
imgViewerScaleTransform.ScaleX += 0.2;
imgViewerScaleTransform.ScaleY += 0.2;
image.LayoutTransform = imgViewerScaleTransform;
}
}
Here is a very simple version of a scalable ItemsControl in a ScrollViewer.
It might be improved in many ways. First of all, you should replace handling Button Click events by binding the Button Command properties to ZoomIn and ZoomOut commands in the view model (left out for brevity).
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Images}">
<ItemsControl.LayoutTransform>
<ScaleTransform ScaleX="{Binding Scale}" ScaleY="{Binding Scale}"/>
</ItemsControl.LayoutTransform>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" BorderBrush="LightGray">
<Image Source="{Binding}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content=" + " Click="ZoomInButtonClick"/>
<Button Content=" - " Click="ZoomOutButtonClick"/>
</StackPanel>
</Grid>
The code behind:
public partial class MainWindow : Window
{
private readonly ViewModel viewModel = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = viewModel;
foreach (string imageFile in Directory.EnumerateFiles(
#"C:\Users\Public\Pictures\Sample Pictures", "*.jpg"))
{
viewModel.Images.Add(new BitmapImage(new Uri(imageFile)));
}
}
private void ZoomInButtonClick(object sender, RoutedEventArgs e)
{
viewModel.Scale *= 1.1;
}
private void ZoomOutButtonClick(object sender, RoutedEventArgs e)
{
viewModel.Scale /= 1.1;
}
}
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<ImageSource> Images { get; }
= new ObservableCollection<ImageSource>();
private double scale = 1;
public double Scale
{
get { return scale; }
set
{
scale = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Scale)));
}
}
}
I managed to find an ugly, horrible solution, sorry for that. Use it only, if there is no any alternative. Please, add answers with better solutions. Thanks for your time!
We need to add (in code behind) Image.LayoutTransform defined as ScaleTransform:
imageListViewItem.LayoutTransform = new ScaleTransform();
I used // to emphasize changes in method below. Also, most changes happened in Zoom/ZoomOut methods below.
public void RefreshView(List<TiffImage> tiffImageList)
{
try
{
if (tiffImageList.Count == 0)
return;
SetControlSizes();
gridImageList.Children.Clear();
gridImageList.RowDefinitions.Clear();
gridImageList.ColumnDefinitions.Clear();
RowDefinitionCollection rd = gridImageList.RowDefinitions;
ColumnDefinitionCollection cd = gridImageList.ColumnDefinitions;
cd.Add(new ColumnDefinition() { Width = GridLength.Auto });
for (int i = 0; i < tiffImageList.Count; i++)
{
rd.Add(new RowDefinition() { Height = GridLength.Auto });
}
int rowIndex = 0;
foreach (var tiffImage in tiffImageList)
{
Image imageListViewItem = new Image();
imageListViewItem.Margin = new Thickness(0, 0, 0, 0);
RenderOptions.SetBitmapScalingMode(imageListViewItem, BitmapScalingMode.HighQuality);
imageListViewItem.Name = $"Image{tiffImage.index.ToString()}";
imageListViewItem.Source = tiffImage.image;
imageListViewItem.HorizontalAlignment = HorizontalAlignment.Center;
imageListViewItem.VerticalAlignment = VerticalAlignment.Center;
imageListViewItem.Stretch = Stretch.Uniform;
imageListViewItem.VerticalAlignment = VerticalAlignment.Center;
imageListViewItem.HorizontalAlignment = HorizontalAlignment.Center;
// Add HERE!!!
imageListViewItem.LayoutTransform = new ScaleTransform();
//
Border border = new Border();
border.BorderBrush = Brushes.LightGray;
border.BorderThickness = new Thickness(1);
Thickness margin = border.Margin;
border.Margin = new Thickness(20, 10, 20, 10);
border.Child = imageListViewItem;
Grid.SetColumn(border, 0);
Grid.SetRow(border, rowIndex);
gridImageList.Children.Add(border);
rowIndex++;
}
}
catch (Exception ex)
{
throw ex;
}
}
We take all elements from the Grid and Scale(Zoom) them, then we clear the Grid.Children and fill it with new Items.
private void btnZoom_Click(object sender, RoutedEventArgs e)
{
List<Border> list = new List<Border>();
foreach (UIElement item in gridImageList.Children)
{
Border border = (Border)item;
Image image = (Image)border.Child;
var imgViewerScaleTransform = (ScaleTransform)(image.LayoutTransform);
imgViewerScaleTransform.CenterX = 0.5;
imgViewerScaleTransform.CenterY = 0.5;
if ((imgViewerScaleTransform.ScaleX + 0.2) > 3 || (imgViewerScaleTransform.ScaleY + 0.2) > 3)
return;
imgViewerScaleTransform.ScaleX += 0.2;
imgViewerScaleTransform.ScaleY += 0.2;
image.LayoutTransform = imgViewerScaleTransform;
border.Child = image;
list.Add(border);
}
gridImageList.Children.Clear();
foreach (Border border in list)
{
gridImageList.Children.Add(border);
}
}
private void btnZoomOut_Click(object sender, RoutedEventArgs e)
{
List<Border> list = new List<Border>();
foreach (UIElement item in gridImageList.Children)
{
Border border = (Border)item;
Image image = (Image)border.Child;
var imgViewerScaleTransform = (ScaleTransform)(image.LayoutTransform);
imgViewerScaleTransform.CenterX = 0.5;
imgViewerScaleTransform.CenterY = 0.5;
if ((imgViewerScaleTransform.ScaleX - 0.2) < 0.8 || (imgViewerScaleTransform.ScaleY - 0.2) < 0.8)
return;
imgViewerScaleTransform.ScaleX += -0.2;
imgViewerScaleTransform.ScaleY += -0.2;
image.LayoutTransform = imgViewerScaleTransform;
border.Child = image;
list.Add(border);
}
gridImageList.Children.Clear();
foreach (Border border in list)
{
gridImageList.Children.Add(border);
}
}
I want to blur the image using slider. For this, I have used Win2D Blur effects and draw the image in CanvasControl and added the canvas over the actual image.
Download sample here
Steps.
1. Added CanvasControl in button click. It will add a child to grid over the actual element
2. Change Slider to apply the blur
3. Issue: Image stretched and it's size too large and look like cropped. Not in the given size (500, 400)
[XAML]
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="7*"/>
<ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions>
<Grid x:Name="imageGrid">
<Image x:Name="image" Source="Flower1.jpg" Width="500" Height="400"
HorizontalAlignment="Left" VerticalAlignment="Top"/>
</Grid>
<StackPanel Grid.Column="1" Orientation="Vertical">
<Button Content="AddCanvas" Width="100" x:Name="addCanvas"
Click="AddCanvas_Click"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Blur" Width="100"/>
<Slider x:Name="slider" Minimum="0" Maximum="5" Width="200"
ValueChanged="Slider_ValueChanged"/>
</StackPanel>
</StackPanel>
</Grid>
[C#]
bool isBlurred;
GaussianBlurEffect blur = new GaussianBlurEffect();
CanvasControl canv = new CanvasControl();
CanvasBitmap cbi;
public MainPage()
{
this.InitializeComponent();
}
private void AddCanvas_Click(object sender, RoutedEventArgs e)
{
canv.HorizontalAlignment = HorizontalAlignment.Left;
canv.VerticalAlignment = VerticalAlignment.Top;
canv.Draw += Canv_Draw;
canv.CreateResources += Canv_CreateResources;
canv.Height = 400;
canv.Width = 500;
imageGrid.Children.Add(canv);
}
private void Canv_Draw(Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasDrawEventArgs args)
{
args.DrawingSession.Units = CanvasUnits.Pixels;
if (isBlurred)
{
args.DrawingSession.DrawImage(blur, new Rect(0, 0, 500, 400), new Rect(0, 0, 500, 400));
}
}
private void Canv_CreateResources(Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
{
args.TrackAsyncAction(CreateResourcesAsync(sender).AsAsyncAction());
}
async Task CreateResourcesAsync(CanvasControl sender)
{
cbi = await CanvasBitmap.LoadAsync(sender, "Flower1.jpg");
}
private void Slider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
isBlurred = true;
blur.Source = cbi;
blur.BlurAmount = (float)e.NewValue;
canv.Invalidate();
}
I would like to apply the Blur effects to the Image using slider dynamically. But i don't want to change the Actual image to Blur. So, I used Canvas over the Actual image and draw with Blur effect.
Regards,
Bharathi.
Drawing image using Blur effects in UWP is not properly set size for the image
The problem is that the size of CanvasControl is not suit for image source (Image: 960*640 CanvasControl:500*400). If you want to make CanvasControl could display the full image, please set available dpi when call CanvasBitmap.LoadAsync method. For my testing 185 is suit for your scenario.
async Task CreateResourcesAsync(CanvasControl sender)
{
cbi = await CanvasBitmap.LoadAsync(sender, "Flower1.jpg",185);
}
Update
//var dpi = DisplayInformation.GetForCurrentView().LogicalDpi;
var display = DisplayInformation.GetForCurrentView();
// calculate your monitor's dpi
var dpi = Math.Sqrt(Math.Pow(display.ScreenWidthInRawPixels, 2) + Math.Pow(display.ScreenHeightInRawPixels, 2)) / display.DiagonalSizeInInches;
// get the CanvasControl inch
var inch = Math.Sqrt(Math.Pow(500, 2) + Math.Pow(400, 2)) / dpi;
// calculate last dpi with the image size
var lastdpi = Math.Sqrt(Math.Pow(960, 2) + Math.Pow(640, 2)) / inch;
Update 1
If could also set CanvasControl size with image size that could avoid to calculate Dpi.
Update 2
And you could also resize your image to suit for CanvasControl, Please refer the following code.
public async Task<IRandomAccessStream> ResizeImage(StorageFile file, int reqWidth, int reqHeight)
{
var memStream = new MemoryStream();
using (Stream stream = await file.OpenStreamForReadAsync())
{
stream.CopyTo(memStream);
}
IRandomAccessStream imageStream = memStream.AsRandomAccessStream();
var decoder = await BitmapDecoder.CreateAsync(imageStream);
if (decoder.PixelHeight > reqHeight || decoder.PixelWidth > reqWidth)
{
using (imageStream)
{
var resizedStream = new InMemoryRandomAccessStream();
BitmapEncoder encoder = await BitmapEncoder.CreateForTranscodingAsync(resizedStream, decoder);
double widthRatio = (double)reqWidth / decoder.PixelWidth;
double heightRatio = (double)reqHeight / decoder.PixelHeight;
double scaleRatio = Math.Min(widthRatio, heightRatio);
if (reqWidth == 0)
scaleRatio = heightRatio;
if (reqHeight == 0)
scaleRatio = widthRatio;
uint aspectHeight = (uint)Math.Floor(decoder.PixelHeight * scaleRatio);
uint aspectWidth = (uint)Math.Floor(decoder.PixelWidth * scaleRatio);
encoder.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Linear;
encoder.BitmapTransform.ScaledHeight = aspectHeight;
encoder.BitmapTransform.ScaledWidth = aspectWidth;
await encoder.FlushAsync();
return resizedStream;
}
}
return imageStream;
}
Usage
async Task CreateResourcesAsync(CanvasControl sender)
{
var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Flower1.jpg"));
cbi = await CanvasBitmap.LoadAsync(sender, await ResizeImage(file, 500, 400));
}
I have referred the below thread for combing multiple images as single image in uwp.
Merging multiple Images
But the image is not properly converted from Uri to bitmap images for the tiles downloaded from below link
https://a.tile.openstreetmap.org/0/0/0.png
public MainPage()
{
this.InitializeComponent();
MergeImages();
}
async void MergeImages()
{
byte[] Canvas = new byte[512 * 512 * 4];
Windows.Storage.Streams.IRandomAccessStream random = await Windows.Storage.Streams.RandomAccessStreamReference.CreateFromUri(
new Uri("https://a.tile.openstreetmap.org/1/0/0.png",UriKind.RelativeOrAbsolute)).OpenReadAsync();
Windows.Graphics.Imaging.BitmapDecoder decoder = await Windows.Graphics.Imaging.BitmapDecoder.CreateAsync(random);
Windows.Graphics.Imaging.PixelDataProvider pixelData = await decoder.GetPixelDataAsync();
byte[] tileImage = pixelData.DetachPixelData();
Canvas = PutOnCanvas(Canvas, tileImage, 0,
0, 256, 256, 512);
InMemoryRandomAccessStream memStream = new InMemoryRandomAccessStream();
var encoder = await BitmapEncoder.CreateAsync(Windows.Graphics.Imaging.BitmapEncoder.PngEncoderId, memStream);
encoder.SetPixelData(
Windows.Graphics.Imaging.BitmapPixelFormat.Bgra8,
Windows.Graphics.Imaging.BitmapAlphaMode.Straight,
512, // pixel width
512, // pixel height
96, // horizontal DPI
96, // vertical DPI
Canvas);
try
{
await encoder.FlushAsync();
var bitmapImage = new BitmapImage();
await bitmapImage.SetSourceAsync(memStream);
image.Source = bitmapImage;
}
catch { }
memStream.Dispose();
}
byte[] PutOnCanvas(byte[] Canvas, byte[] Image, uint x, uint y, uint imageheight, uint imagewidth, uint CanvasWidth)
{
for (uint row = y; row < y + imageheight; row++)
for (uint col = x; col < x + imagewidth; col++)
for (int i = 0; i < 4; i++)
Canvas[(row * CanvasWidth + col) * 4 + i] = Image[((row - y) * imagewidth + (col - x)) * 4 + i];
return Canvas;
}
Sample
Actual Output:
Expected output
I found your previous two threads:
How to avoid the flickering effect when dynamically changes the image source in uwp
How to combine multiple image as single image in UWP
They all aimed to avoid the flickering effect. As I comment in your second thread, the flickering effect is caused by you change the images's source in a short time, if you want to avoid it, you can use the animation to change the images with a progress. Here is a example to use the DoubleAnimation Storyboard to animate Opacity property of the Images stackpanel base on your first thread sample:
MainPage.xaml: I create two storyboard to make the images stackpanel disappear or display and give the disappearStoryboard a disappearStoryboard_Completed event handler.
<Page.Resources>
<Storyboard x:Name="disappearStoryboard" Completed="disappearStoryboard_Completed">
<DoubleAnimation
Storyboard.TargetName="RootStackPanel"
Storyboard.TargetProperty="Opacity"
From="1.0" To="0.0" Duration="0:0:1"/>
</Storyboard>
<Storyboard x:Name="displayStoryboard">
<DoubleAnimation
Storyboard.TargetName="RootStackPanel"
Storyboard.TargetProperty="Opacity"
From="0.0" To="1.0" Duration="0:0:1"/>
</Storyboard>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Name="RootStackPanel">
<StackPanel Orientation="Horizontal">
<Image x:Name="image1" Height="200" Width="200" Source="ms-appx:///Assets/custom200.png"/>
<Image x:Name="image2" Height="200" Width="200" Source="ms-appx:///Assets/custom201.png"/>
<Image x:Name="image3" Height="200" Width="200" Source="ms-appx:///Assets/custom202.png"/>
<Image x:Name="image4" Height="200" Width="200" Source="ms-appx:///Assets/custom203.png"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Image x:Name="image5" Height="200" Width="200" Source="ms-appx:///Assets/custom210.png"/>
<Image x:Name="image6" Height="200" Width="200" Source="ms-appx:///Assets/custom211.png"/>
<Image x:Name="image7" Height="200" Width="200" Source="ms-appx:///Assets/custom212.png"/>
<Image x:Name="image8" Height="200" Width="200" Source="ms-appx:///Assets/custom213.png"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Image x:Name="image9" Height="200" Width="200" Source="ms-appx:///Assets/custom220.png"/>
<Image x:Name="image10" Height="200" Width="200" Source="ms-appx:///Assets/custom221.png"/>
<Image x:Name="image11" Height="200" Width="200" Source="ms-appx:///Assets/custom222.png"/>
<Image x:Name="image12" Height="200" Width="200" Source="ms-appx:///Assets/custom223.png"/>
</StackPanel>
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<Button Height="50" Width="150" HorizontalAlignment="Center" VerticalAlignment="Center"
Content="ReplaceCustom" x:Name="replaceCustom" Click="replaceCustom_Click" Margin="5"/>
<Button Height="50" Width="150" HorizontalAlignment="Center" VerticalAlignment="Center"
Content="Replace OSM" x:Name="replaceOSM" Click="replace_Click" Margin="5"/>
<Button Height="50" Width="150" HorizontalAlignment="Center" VerticalAlignment="Center"
Content="Replace Tile" x:Name="replaceTile" Click="replaceTile_Click" Margin="5"/>
</StackPanel>
</Grid>
MainPage.xaml.cs: I change the Images' resource in the disappearStoryboard 's disappearStoryboard_Completed event handler.
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
bool replaceButtonClicked, replaceCustomButtonClicked, replaceTileButtonClicked;
private void replace_Click(object sender, RoutedEventArgs e)
{
replaceButtonClicked = true;
disappearStoryboard.Begin();
}
private void replaceCustom_Click(object sender, RoutedEventArgs e)
{
replaceCustomButtonClicked = true;
disappearStoryboard.Begin();
}
private void replaceTile_Click(object sender, RoutedEventArgs e)
{
replaceTileButtonClicked = true;
disappearStoryboard.Begin();
}
private void disappearStoryboard_Completed(object sender, object e)
{
if (replaceButtonClicked)
{
image1.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM200.png",
UriKind.RelativeOrAbsolute));
image2.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM201.png",
UriKind.RelativeOrAbsolute));
image3.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM202.png",
UriKind.RelativeOrAbsolute));
image4.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM203.png",
UriKind.RelativeOrAbsolute));
image5.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM210.png",
UriKind.RelativeOrAbsolute));
image6.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM211.png",
UriKind.RelativeOrAbsolute));
image7.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM212.png",
UriKind.RelativeOrAbsolute));
image8.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM213.png",
UriKind.RelativeOrAbsolute));
image9.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM220.png",
UriKind.RelativeOrAbsolute));
image10.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM221.png",
UriKind.RelativeOrAbsolute));
image11.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM222.png",
UriKind.RelativeOrAbsolute));
image12.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM223.png",
UriKind.RelativeOrAbsolute));
replaceButtonClicked = false;
}
if (replaceCustomButtonClicked)
{
image1.Source = new BitmapImage(new Uri("ms-appx:///Assets/custom200.png",
UriKind.RelativeOrAbsolute));
image2.Source = new BitmapImage(new Uri("ms-appx:///Assets/custom201.png",
UriKind.RelativeOrAbsolute));
image3.Source = new BitmapImage(new Uri("ms-appx:///Assets/custom202.png",
UriKind.RelativeOrAbsolute));
image4.Source = new BitmapImage(new Uri("ms-appx:///Assets/custom203.png",
UriKind.RelativeOrAbsolute));
image5.Source = new BitmapImage(new Uri("ms-appx:///Assets/custom210.png",
UriKind.RelativeOrAbsolute));
image6.Source = new BitmapImage(new Uri("ms-appx:///Assets/custom211.png",
UriKind.RelativeOrAbsolute));
image7.Source = new BitmapImage(new Uri("ms-appx:///Assets/custom212.png",
UriKind.RelativeOrAbsolute));
image8.Source = new BitmapImage(new Uri("ms-appx:///Assets/custom213.png",
UriKind.RelativeOrAbsolute));
image9.Source = new BitmapImage(new Uri("ms-appx:///Assets/custom220.png",
UriKind.RelativeOrAbsolute));
image10.Source = new BitmapImage(new Uri("ms-appx:///Assets/custom221.png",
UriKind.RelativeOrAbsolute));
image11.Source = new BitmapImage(new Uri("ms-appx:///Assets/custom222.png",
UriKind.RelativeOrAbsolute));
image12.Source = new BitmapImage(new Uri("ms-appx:///Assets/custom223.png",
UriKind.RelativeOrAbsolute));
replaceCustomButtonClicked = false;
}
if (replaceTileButtonClicked)
{
image1.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM300.png",
UriKind.RelativeOrAbsolute));
image2.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM301.png",
UriKind.RelativeOrAbsolute));
image3.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM302.png",
UriKind.RelativeOrAbsolute));
image4.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM303.png",
UriKind.RelativeOrAbsolute));
image5.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM304.png",
UriKind.RelativeOrAbsolute));
image6.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM305.png",
UriKind.RelativeOrAbsolute));
image7.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM306.png",
UriKind.RelativeOrAbsolute));
image8.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM301.png",
UriKind.RelativeOrAbsolute));
image9.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM302.png",
UriKind.RelativeOrAbsolute));
image10.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM303.png",
UriKind.RelativeOrAbsolute));
image11.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM310.png",
UriKind.RelativeOrAbsolute));
image12.Source = new BitmapImage(new Uri("ms-appx:///Assets/OSM311.png",
UriKind.RelativeOrAbsolute));
replaceTileButtonClicked = false;
}
displayStoryboard.Begin();
}
}
I have an application where I draw polygons on inkCanvas. I would like to add a function where after clicking one of drawn polygons it would be in editing mode and then I could change some of this proporties for example Fill.
I wrote this code but it selects all area from top left of inkcanvas to the end of my polygon but I need only polygon area.
Xaml:
<DockPanel>
<ToolBarTray DockPanel.Dock="Left" Orientation="Vertical" IsLocked="True">
<ToolBar Padding="2">
<RadioButton x:Name="rbDraw" IsChecked="False"
ToolTip="Add Rectangle" Margin="3" Checked="rbDraw_Checked">
<Rectangle Width="20" Height="12" Stroke="Blue"
Fill="LightBlue" />
</RadioButton>
<RadioButton x:Name="rbSelect" IsChecked="False"
ToolTip="Select" Margin="3">
<Path Stroke="Blue" Fill="LightBlue" Width="20" Height="20">
<Path.Data>
<PathGeometry Figures="M5,15L 10,0 15,15 12,15 12,20 8,20 8,15Z">
<PathGeometry.Transform>
<RotateTransform CenterX="10" CenterY="10" Angle="45"/>
</PathGeometry.Transform>
</PathGeometry>
</Path.Data>
</Path>
</RadioButton>
</ToolBar>
</ToolBarTray>
<Border BorderThickness="1" BorderBrush="Black">
<InkCanvas x:Name="canvas1" MouseMove="canvas1_MouseMove" PreviewMouseLeftButtonDown="canvas1_PreviewMouseLeftButtonDown" EditingMode="None">
</InkCanvas>
</Border>
</DockPanel>
Code behind
private Polyline polyline;
private PointCollection polylinePoints;
private bool drawOnMove = false;
private List<Polygon> polygons = new List<Polygon>();
public MainWindow()
{
InitializeComponent();
}
private void canvas1_MouseMove(object sender, MouseEventArgs e)
{
if (drawOnMove && (bool)rbDraw.IsChecked)
{
polyline.Points = polylinePoints.Clone();
polyline.Points.Add(e.GetPosition(canvas1));
}
}
private void canvas1_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (rbDraw.IsChecked ?? false)
{
if (e.OriginalSource is Ellipse)
{
canvas1.Children.Remove((Ellipse)e.OriginalSource);
canvas1.Children.Remove(polyline);
Polygon tmpPolygon = new Polygon();
tmpPolygon.StrokeThickness = 2;
tmpPolygon.Stroke = Brushes.Black;
tmpPolygon.Points = polylinePoints.Clone();
polylinePoints.Clear();
polygons.Add(tmpPolygon);
drawOnMove = false;
rbDraw.IsChecked = false;
tmpPolygon.Fill = Brushes.Gray;
canvas1.Children.Add(tmpPolygon);
rbSelect.IsChecked = true;
}
else
{
polylinePoints.Add(e.GetPosition(canvas1));
polyline.Points = polylinePoints.Clone();
if (polyline.Points.Count == 1)
{
Ellipse el = new Ellipse();
el.Width = 10;
el.Height = 10;
el.Stroke = Brushes.Black;
el.StrokeThickness = 2;
el.Fill = new SolidColorBrush { Color = Colors.Yellow };
el.Margin =
new Thickness(left: polyline.Points[0].X - el.Width / 2, top: polyline.Points[0].Y - el.Height / 2, right: 0, bottom: 0);
canvas1.Children.Add(el);
}
drawOnMove = true;
}
}
else if (rbSelect.IsChecked ?? false)
{
if (e.OriginalSource is Polygon)
{
Polygon pol = (Polygon)e.OriginalSource;
canvas1.Select(new UIElement[] { pol });
}
}
}
private void rbDraw_Checked(object sender, RoutedEventArgs e)
{
polyline = new Polyline();
polylinePoints = new PointCollection();
polyline.StrokeThickness = 2;
polyline.Stroke = Brushes.Black;
canvas1.Children.Add(polyline);
}
Edit: I edited my code my first sample was a little too general. Selecting polygon looks like this but I want to select only polygon area.
I know this is a really old post, but I had this exact same problem and solved it by translating the points before the conversion to a polygon, and then back again, as such:
StrokeCollection sc = InkCanvas1.GetSelectedStrokes();
Rect r = sc.GetBounds();
PointCollection pc = new PointCollection();
//Shift all the points by the calculated extent of the strokes.
Matrix mat = new Matrix();
mat.Translate(-r.Left, -r.Top);
sc.Transform(mat, false);
foreach (Stroke s in sc)
{
foreach (Point p in s.StylusPoints){pc.Add(p);}
}
Polygon poly_ = new Polygon();
//Shift the polygon back to original location
poly_.SetValue(InkCanvas.LeftProperty, r.Left);
poly_.SetValue(InkCanvas.TopProperty, r.Top);
poly_.Points = pc;
InkCanvas1.Children.Add(poly_);
This makes the select box only equal to the size of the polygon:
Ok I solved my problem I added a custom Dependency Property to my Window which holds selected polygon. To show that polygon is selected I change its opacity.
public static readonly DependencyProperty SelectedShapeProperty =
DependencyProperty.Register
("SelectedShape", typeof(Polygon), typeof(MainWindow));
public Polygon Polygon
{
set{SetValue(SelectedShapeProperty, value);}
get{return (Polygon) GetValue(SelectedShapeProperty);}
}
private void canvas1_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (rbDraw.IsChecked ?? false)
{
if (e.OriginalSource is Ellipse)
{
canvas1.Children.Remove((Ellipse)e.OriginalSource);
canvas1.Children.Remove(polyline);
Polygon tmpPolygon = new Polygon();
tmpPolygon.StrokeThickness = 2;
tmpPolygon.Stroke = Brushes.Black;
tmpPolygon.Points = polylinePoints.Clone();
polylinePoints.Clear();
polygons.Add(tmpPolygon);
drawOnMove = false;
rbDraw.IsChecked = false;
tmpPolygon.Fill = Brushes.Gray;
canvas1.Children.Add(tmpPolygon);
rbSelect.IsChecked = true;
}
else
{
polylinePoints.Add(e.GetPosition(canvas1));
polyline.Points = polylinePoints.Clone();
if (polyline.Points.Count == 1)
{
Ellipse el = new Ellipse();
el.Width = 10;
el.Height = 10;
el.Stroke = Brushes.Black;
el.StrokeThickness = 2;
el.Fill = new SolidColorBrush { Color = Colors.Yellow };
InkCanvas.SetLeft(el, polyline.Points[0].X - el.Width / 2);
InkCanvas.SetTop(el, polyline.Points[0].Y - el.Height / 2);
el.Margin =
new Thickness(left: polyline.Points[0].X - el.Width / 2, top: polyline.Points[0].Y - el.Height / 2, right: 0, bottom: 0);
canvas1.Children.Add(el);
}
drawOnMove = true;
}
}
else if (rbSelect.IsChecked ?? false)
{
if (e.OriginalSource is Polygon && Polygon == null)
{
Shape s = (Shape)e.OriginalSource;
Polygon = (Polygon)s;
Polygon.Opacity = 0.75;
}
else if (e.OriginalSource is Polygon && Polygon != null)
{
Polygon.Opacity = 1;
Polygon = null;
Shape s = (Shape)e.OriginalSource;
Polygon = (Polygon)s;
Polygon.Opacity = 0.75;
}
else if (Polygon != null)
{
Polygon.Opacity = 1;
Polygon = null;
}
}
else
{
if(Polygon != null)
Polygon = null;
}
}
You can select any drawing on your canvas by setting
drawCanvas.EditingMode = InkCanvasEditingMode.Select;
and then just clicking on this drawing.