Zoom on Canvas, centered on mouse position - c#

I know there are several posts on stack and others websites, but it seems I still make something wrong. When I zoom with MouseWheel event, the zoom is always not centered, but the left side of my canvas always stays on the left on my ViewBox, so when I zoom in, I only can see the left of my canvas.
XAML code :
<Grid x:Name="MainGrid">
<Viewbox x:Name="ViewBoxDessin" Stretch="None" HorizontalAlignment="Center" VerticalAlignment="Center">
<Canvas x:Name="monDessin" Background="WhiteSmoke" MouseWheel="monDessin_MouseWheel">
<Canvas.LayoutTransform>
<ScaleTransform x:Name="st" ScaleX="1" ScaleY="-1" CenterX=".5" CenterY=".5" />
</Canvas.LayoutTransform>
</Canvas>
</Viewbox>
<Viewbox x:Name="ViewBoxDessin2" Stretch="None">
<Canvas x:Name="monDessin2">
<Canvas.LayoutTransform>
<ScaleTransform x:Name="st2" ScaleX="1" ScaleY="1" CenterX=".5" CenterY=".5" />
</Canvas.LayoutTransform>
</Canvas>
</Viewbox>
</Grid>
Code behind
public AfficheGraphiquePiece()
{
InitializeComponent();
MakeMyDrawing();
ViewBoxDessin.Width = System.Windows.SystemParameters.PrimaryScreenWidth;
ViewBoxDessin.Height = System.Windows.SystemParameters.PrimaryScreenHeight;
double ech_x = monDessin.Width / System.Windows.SystemParameters.PrimaryScreenWidth;
double ech_y = monDessin.Height / System.Windows.SystemParameters.PrimaryScreenHeight;
double ech = Math.Min(ech_x, ech_y);
this.ech_full = ech;
st.ScaleX = ech;
st.ScaleY = -ech;
st2.ScaleX = ech;
st2.ScaleY = ech;
}
private void monDessin_MouseWheel(object sender, MouseWheelEventArgs e)
{
double zoom = e.Delta > 0 ? 1.1 : 0.9;
if(st.ScaleX<this.ech_full*1.1 && zoom<1)
{
st.ScaleX = this.ech_full;
st.ScaleY = -this.ech_full;
}
else
{
st.ScaleX *= zoom;
st.ScaleY *= zoom;
double coor_x = Mouse.GetPosition(monDessin).X;
double coor_y = Mouse.GetPosition(monDessin).Y;
st.CenterX = coor_x;
st.CenterY = coor_y;
}
}
Excuse me, didn't remove some code, and it could make confusion, just replaced it by a function MakeMyDrawing()
Well, after Clemens advise, and help of that link for matrix use, I could do the following :
XAML :
<Grid x:Name="MainGrid">
<Canvas x:Name="monDessin" Background="WhiteSmoke" MouseWheel="monDessin_MouseWheel" MouseLeftButtonDown="image_MouseLeftButtonDown" MouseMove="image_MouseMove" MouseLeftButtonUp="image_MouseLeftButtonUp" MouseLeave="image_MouseLeave" >
<Canvas.RenderTransform >
<MatrixTransform/>
</Canvas.RenderTransform>
</Canvas>
<Canvas x:Name="monDessin2">
<Canvas.RenderTransform >
<MatrixTransform/>
</Canvas.RenderTransform>
</Canvas>
</Grid>
Code behind
public AfficheGraphiquePiece(Repere rep)
{
InitializeComponent();
ClassGraphique monGraphe = new ClassGraphique(monDessin);
ClassGraphique monGraphe2 = new ClassGraphique(monDessin2);
MakeMyDrawing();
double screenWidth = System.Windows.SystemParameters.PrimaryScreenWidth;
double screenHeight = System.Windows.SystemParameters.PrimaryScreenHeight;
double ech_x = screenWidth/ monDessin.Width ;
double ech_y = screenHeight/ monDessin.Height;
double ech = Math.Min(ech_x, ech_y)*0.9;
this.ech_full = ech;
this.echelleNow = this.ech_full;
MatrixTransform maTrans =(MatrixTransform)monDessin.RenderTransform;
var mat = maTrans.Matrix;
mat.ScaleAt(ech, -ech, 0.1* screenWidth, (screenHeight-monDessin.Height*ech)/2-0.1*screenHeight);
MatrixTransform maTrans2 = (MatrixTransform)monDessin2.RenderTransform;
var mat2 = maTrans2.Matrix;
mat2.ScaleAt(ech, ech, 0.1 * screenWidth, screenHeight*ech-((screenHeight - monDessin.Height * ech) / 2 - 0.1 * screenHeight));
maTrans.Matrix = mat;
maTrans2.Matrix = mat2;
}
private void monDessin_MouseWheel(object sender, MouseWheelEventArgs e)
{
try
{
var position = e.GetPosition(monDessin);
MatrixTransform transform = (MatrixTransform)monDessin.RenderTransform;
MatrixTransform transform2 = (MatrixTransform)monDessin2.RenderTransform;
var matrix = transform.Matrix;
var matrix2 = transform2.Matrix;
var scale = e.Delta >= 0 ? 1.1 : (1.0 / 1.1);
this.echelleNow *= scale;
matrix.ScaleAtPrepend(scale, scale, position.X, position.Y);
matrix2.ScaleAtPrepend(scale, scale, position.X,monDessin.Height-position.Y);
monDessin.RenderTransform = new MatrixTransform(matrix);
monDessin2.RenderTransform = new MatrixTransform(matrix2);
}
catch { }
}

Here is a very basic example for zooming and panning a Canvas with fixed initial size. The MatrixTransform in the RenderTransform of the inner Canvas provides the necessary transformations, while the outer Canvas handles mouse input and sets an initial scaling.
<Canvas Background="Transparent"
SizeChanged="ViewportSizeChanged"
MouseLeftButtonDown="ViewportMouseLeftButtonDown"
MouseLeftButtonUp="ViewportMouseLeftButtonUp"
MouseMove="ViewportMouseMove"
MouseWheel="ViewportMouseWheel">
<Canvas x:Name="canvas" Width="1000" Height="600">
<Canvas.RenderTransform>
<MatrixTransform x:Name="transform"/>
</Canvas.RenderTransform>
<Ellipse Fill="Red" Width="100" Height="100" Canvas.Left="100" Canvas.Top="100"/>
<Ellipse Fill="Green" Width="100" Height="100" Canvas.Right="100" Canvas.Bottom="100"/>
</Canvas>
</Canvas>
Code behind:
private Point? mousePos;
private void ViewportSizeChanged(object sender, SizeChangedEventArgs e)
{
((MatrixTransform)canvas.RenderTransform).Matrix = new Matrix(
e.NewSize.Width / canvas.ActualWidth,
0, 0,
e.NewSize.Height / canvas.ActualHeight,
0, 0);
}
private void ViewportMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var viewport = (UIElement)sender;
viewport.CaptureMouse();
mousePos = e.GetPosition(viewport);
}
private void ViewportMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
((UIElement)sender).ReleaseMouseCapture();
mousePos = null;
}
private void ViewportMouseMove(object sender, MouseEventArgs e)
{
if (mousePos.HasValue)
{
var pos = e.GetPosition((UIElement)sender);
var matrix = transform.Matrix;
matrix.Translate(pos.X - mousePos.Value.X, pos.Y - mousePos.Value.Y);
transform.Matrix = matrix;
mousePos = pos;
}
}
private void ViewportMouseWheel(object sender, MouseWheelEventArgs e)
{
var pos = e.GetPosition((UIElement)sender);
var matrix = transform.Matrix;
var scale = e.Delta > 0 ? 1.1 : 1 / 1.1;
matrix.ScaleAt(scale, scale, pos.X, pos.Y);
transform.Matrix = matrix;
}

Related

How can I capture the canvas area from the Image in the ScrollViewer

I use the scrollviewer to zoom in or out of the picture, and it has been successful. Now I hope to draw an area on the picture and capture the area to an image. But the image I get is blank except the border. Here is my code.
XAML:
<Window x:Class="Capture.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:Capture"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Slider Grid.Column="0" Orientation="Vertical"
HorizontalAlignment="Left" Minimum="1" x:Name="slider"/>
<ScrollViewer Name="scrollViewer" Grid.Column="1"
VerticalScrollBarVisibility="Visible"
HorizontalScrollBarVisibility="Visible" Margin="0,0,0,40">
<Grid Name="grid" Width="400" Height="400" >
<Grid.LayoutTransform>
<TransformGroup x:Name="TfGroup">
<ScaleTransform x:Name="scaleTransform"/>
</TransformGroup>
</Grid.LayoutTransform>
<Viewbox Grid.Column="0" Grid.Row="0">
<Image x:Name="img" Source="C:\Users\citic\Desktop\微信截图_20200728104010.png" />
</Viewbox>
<Canvas x:Name="canvas" MouseDown="Canvas_MouseDown" MouseMove="Canvas_MouseMove" MouseUp="Canvas_MouseUp" Background="Transparent"/>
</Grid>
</ScrollViewer>
<Button Grid.Column="1" Content="Capture" Margin="338,381,337.333,9.667" Click="Button_Click"/>
</Grid>
</Window>
public partial class MainWindow : Window
{
Point? lastCenterPositionOnTarget;
Point? lastMousePositionOnTarget;
Point? lastDragPoint;
private System.Windows.Point startPoint;
private System.Windows.Shapes.Rectangle rect;
private int w1;
private int h1;
public MainWindow()
{
InitializeComponent();
scrollViewer.ScrollChanged += OnScrollViewerScrollChanged;
scrollViewer.PreviewMouseLeftButtonUp += OnMouseLeftButtonUp;
scrollViewer.PreviewMouseWheel += OnPreviewMouseWheel;
slider.ValueChanged += OnSliderValueChanged;
}
void OnScrollViewerScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (e.ExtentHeightChange != 0 || e.ExtentWidthChange != 0)
{
Point? targetBefore = null;
Point? targetNow = null;
if (!lastMousePositionOnTarget.HasValue)
{
if (lastCenterPositionOnTarget.HasValue)
{
var centerOfViewport = new Point(scrollViewer.ViewportWidth / 2,
scrollViewer.ViewportHeight / 2);
Point centerOfTargetNow =
scrollViewer.TranslatePoint(centerOfViewport, grid);
targetBefore = lastCenterPositionOnTarget;
targetNow = centerOfTargetNow;
}
}
else
{
targetBefore = lastMousePositionOnTarget;
targetNow = Mouse.GetPosition(grid);
lastMousePositionOnTarget = null;
}
if (targetBefore.HasValue)
{
double dXInTargetPixels = targetNow.Value.X - targetBefore.Value.X;
double dYInTargetPixels = targetNow.Value.Y - targetBefore.Value.Y;
double multiplicatorX = e.ExtentWidth / grid.Width;
double multiplicatorY = e.ExtentHeight / grid.Height;
double newOffsetX = scrollViewer.HorizontalOffset -
dXInTargetPixels * multiplicatorX;
double newOffsetY = scrollViewer.VerticalOffset -
dYInTargetPixels * multiplicatorY;
if (double.IsNaN(newOffsetX) || double.IsNaN(newOffsetY))
{
return;
}
scrollViewer.ScrollToHorizontalOffset(newOffsetX);
scrollViewer.ScrollToVerticalOffset(newOffsetY);
}
}
}
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
scrollViewer.Cursor = Cursors.Arrow;
scrollViewer.ReleaseMouseCapture();
lastDragPoint = null;
}
private void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
lastMousePositionOnTarget = Mouse.GetPosition(grid);
System.Windows.Point centerPoint = e.GetPosition(img);
var val = e.Delta * 0.01; //描述鼠标滑轮滚动
if (scaleTransform.ScaleX + val < 0.1) return;
if (scaleTransform.ScaleX + val > 100) return;
scaleTransform.CenterX = centerPoint.X;
scaleTransform.CenterY = centerPoint.Y;
scaleTransform.ScaleX += val;
scaleTransform.ScaleY += val;
if (e.Delta > 0)
{
slider.Value += 1;
}
if (e.Delta < 0)
{
slider.Value -= 1;
}
e.Handled = true;
}
private void OnSliderValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
scaleTransform.ScaleX = e.NewValue;
scaleTransform.ScaleY = e.NewValue;
var centerOfViewport = new System.Windows.Point(scrollViewer.ViewportWidth / 2, scrollViewer.ViewportHeight / 2);
lastCenterPositionOnTarget = scrollViewer.TranslatePoint(centerOfViewport, grid);
}
private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
canvas.Children.Clear();
startPoint = e.GetPosition(canvas);
rect = new System.Windows.Shapes.Rectangle
{
Stroke = System.Windows.Media.Brushes.IndianRed,
StrokeThickness = 0.2,
StrokeDashArray = new DoubleCollection { 2 }
};
Canvas.SetLeft(rect, startPoint.X);
Canvas.SetTop(rect, startPoint.Y);
canvas.Children.Add(rect);
}
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Released || rect == null)
return;
var pos = e.GetPosition(canvas);
var x = Math.Min(pos.X, startPoint.X);
var y = Math.Min(pos.Y, startPoint.Y);
var w = Math.Max(pos.X, startPoint.X) - x;
var h = Math.Max(pos.Y, startPoint.Y) - y;
w1 = (int)w;
h1 = (int)h;
rect.Width = w;
rect.Height = h;
Canvas.SetLeft(rect, x);
Canvas.SetTop(rect, y);
}
private void Canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var rect = new Rect(canvas.RenderSize);
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
dc.DrawRectangle(new VisualBrush(canvas), null, rect);
}
var bitmap = new RenderTargetBitmap(
(int)rect.Width, (int)rect.Height, 96, 96, PixelFormats.Default);
bitmap.Render(visual);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using (var file = File.OpenWrite(#"C:\Users\citic\Desktop\test.jpg"))
{
encoder.Save(file);
}
}
}
I am a WPF novice, and many contents are still learning. Please help me, thank you!
The reason why it isn't working is because you're only capturing the Canvas and converting it to a bitmap. If you look at your layout the Image is on another layer. It is not placed on the Canvas.
<Grid>
<Viewbox>
<Image /> <!-- This is on a separate layer of the layout -->
</Viewbox>
<Canvas /> <!-- You're only capturing this -->
</Grid>
Instead of capturing the Canvas, maybe you should try the Grid instead.

Windows phone 8.1 crop rectangle

I m making windows phone 8.1 app which have crop function. The problem is that i don't know how to make a graphical rectangle that user can resize and than crop image. Image is located on :
Image x:Name="ImagePreview" HorizontalAlignment="Left" Height="492" Margin="10,10,0,0" Stretch="UniformToFill" VerticalAlignment="Top" Width="380" >
Here is the xaml for croping image(including croping rectangle)
<Grid >
<ScrollViewer>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Button Content="Pick a image" Click="Button_Click" />
<Canvas Grid.Row="1" Width="380" Height="492" Margin="10,10,0,0" VerticalAlignment="Top">
<Image x:Name="original" ImageOpened="original_ImageOpened" Stretch="Uniform" ManipulationCompleted="Rectangle_ManipulationCompleted" ManipulationDelta="Rectangle_ManipulationDelta" ManipulationStarted="Rectangle_ManipulationStarted" ManipulationMode="All" PointerPressed="rect_PointerPressed" Source="Assets/new_arrivals.png" HorizontalAlignment="Left" Height="492" Width="380" >
</Image>
<Rectangle x:Name="rect" StrokeThickness="1" Stroke="Red">
</Rectangle>
</Canvas>
<Button Grid.Row="2" Name="CropBtn" Content="CropImage" Click="CropBtn_Click" />
<Image Grid.Row="3" Stretch="None" Name="FinalCroppedImage"/>
</Grid>
</ScrollViewer>
</Grid>
code behind
public sealed partial class MainPage : Page
{
bool pointerpressed = false;
WriteableBitmap WB_CapturedImage;//for original image
WriteableBitmap WB_CroppedImage;//for cropped image
Point Point1, Point2;
public MainPage()
{
this.InitializeComponent();
// view = CoreApplication.GetCurrentView();
this.NavigationCacheMode = NavigationCacheMode.Required;
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
void CompositionTarget_Rendering(object sender, object e)
{
rect.SetValue(Canvas.LeftProperty, (Point1.X < Point2.X) ? Point1.X : Point2.X);
rect.SetValue(Canvas.TopProperty, (Point1.Y < Point2.Y) ? Point1.Y : Point2.Y);
rect.Width = (int)Math.Abs(Point2.X - Point1.X);
rect.Height = (int)Math.Abs(Point2.Y - Point1.Y);
}
//To generate croping rectangle
private void Rectangle_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
{
Point1 = e.Position;//Set first touchable cordinates as point1
Point2 = Point1;
}
private void Rectangle_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
{
var point = Point2;
if (e.Position.X <= original.ActualWidth && e.Position.X >= 0)
point.X = e.Position.X;
if (e.Position.Y <= original.ActualHeight && e.Position.Y >= 0)
point.Y = e.Position.Y;
Point2 = point;
}
private void Rectangle_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
var point = Point2;
// Debug.WriteLine(e.Position.X + "&&&" + original.ActualWidth);
if (e.Position.X <= original.ActualWidth && e.Position.X>=0)
point.X = e.Position.X;
if (e.Position.Y <= original.ActualHeight && e.Position.Y >= 0)
point.Y = e.Position.Y;
Point2 = point;
}
private void rect_PointerPressed(object sender, PointerRoutedEventArgs e)
{
pointerpressed = true;
Point1 = e.GetCurrentPoint(original).Position;//Set first touchable cordinates as point1
Point2 = Point1;
}
private async void CropBtn_Click(object sender, RoutedEventArgs e)
{
BitmapDecoder decoder = null;
BitmapImage bImage = original.Source as BitmapImage;
if (storageFile == null && bImage.UriSource!=null)
{
storageFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx://"+bImage.UriSource.AbsolutePath));
}
using (IRandomAccessStream streamWithContent = await storageFile.OpenAsync(FileAccessMode.Read))
{
decoder = await BitmapDecoder.CreateAsync(streamWithContent);
BitmapFrame bitmapFrame = await decoder.GetFrameAsync(0);
WB_CapturedImage = new WriteableBitmap((int)bitmapFrame.PixelWidth,
(int)bitmapFrame.PixelHeight);
Size cropsize = new Size(Math.Abs(Point2.X - Point1.X), Math.Abs(Point2.Y - Point1.Y));
double originalImageWidth = WB_CapturedImage.PixelWidth;
double originalImageHeight = WB_CapturedImage.PixelHeight;
// Get the size of the image when it is displayed on the phone
double displayedWidth = original.ActualWidth;
double displayedHeight = original.ActualHeight;
// Calculate the ratio of the original image to the displayed image
double widthRatio = originalImageWidth / displayedWidth;
double heightRatio = originalImageHeight / displayedHeight;
WB_CroppedImage = await GetCroppedBitmapAsync(streamWithContent, Point1, cropsize, widthRatio, heightRatio);
FinalCroppedImage.Source = WB_CroppedImage;
}
}
public static async Task<WriteableBitmap> GetCroppedBitmapAsync(IRandomAccessStream originalImage,
Point startPoint, Size cropSize, double scaleWidth, double scaleheight)
{
if (double.IsNaN(scaleWidth) || double.IsInfinity(scaleWidth))
{
scaleWidth = 1;
}
if (double.IsNaN(scaleheight) || double.IsInfinity(scaleheight))
{
scaleheight = 1;
}
// Convert start point and size to integer.
var startPointX = (uint)Math.Floor(startPoint.X * scaleWidth);
var startPointY = (uint)Math.Floor(startPoint.Y * scaleheight);
var height = (uint)Math.Floor(cropSize.Height * scaleheight);
var width = (uint)Math.Floor(cropSize.Width * scaleWidth);
// Create a decoder from the stream. With the decoder, we can get
// the properties of the image.
var decoder = await BitmapDecoder.CreateAsync(originalImage);
// The scaledSize of original image.
var scaledWidth = (uint)(decoder.PixelWidth);
var scaledHeight = (uint)(decoder.PixelHeight);
// Refine the start point and the size.
if (startPointX + width > scaledWidth)
{
startPointX = scaledWidth - width;
}
if (startPointY + height > scaledHeight)
{
startPointY = scaledHeight - height;
}
// Get the cropped pixels.
var pixels = await GetPixelData(decoder, startPointX, startPointY, width, height,
scaledWidth, scaledHeight);
// Stream the bytes into a WriteableBitmap
var cropBmp = new WriteableBitmap((int)width, (int)height);
var pixStream = cropBmp.PixelBuffer.AsStream();
pixStream.Write(pixels, 0, (int)(width * height * 4));
return cropBmp;
}
private static async Task<byte[]> GetPixelData(BitmapDecoder decoder, uint startPointX, uint startPointY,
uint width, uint height, uint scaledWidth, uint scaledHeight)
{
var transform = new BitmapTransform();
var bounds = new BitmapBounds();
bounds.X = startPointX;
bounds.Y = startPointY;
bounds.Height = height;
bounds.Width = width;
transform.Bounds = bounds;
transform.ScaledWidth = scaledWidth;
transform.ScaledHeight = scaledHeight;
// Get the cropped pixels within the bounds of transform.
var pix = await decoder.GetPixelDataAsync(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Premultiplied,
transform,
ExifOrientationMode.IgnoreExifOrientation,
ColorManagementMode.ColorManageToSRgb);
var pixels = pix.DetachPixelData();
return pixels;
}
}
Microsoft have published a sample Windows 10 application which offer crop capabilities on images. Most of the code can be reused in Windows Phone 8.1.
The control is here:
https://github.com/Microsoft/Appsample-Photosharing/blob/master/PhotoSharingApp/PhotoSharingApp.Universal/Controls/CropControl.cs
And it is used in the following page:
https://github.com/Microsoft/Appsample-Photosharing/blob/master/PhotoSharingApp/PhotoSharingApp.Universal/Views/CropPage.xaml
You also have to check the template declared in (search for controls:CropControl):
https://github.com/Microsoft/Appsample-Photosharing/blob/master/PhotoSharingApp/PhotoSharingApp.Universal/Styles/Styles.xaml

Flipped copy of a Rectangle using ScaleTransform in WPF

I am trying to create a flipped copy of a rectangle in Left, Right, Up and Down directions.
However I am able to do it when the selected rectangle has no prior transformation applied on it.
But once a rectangle is being transformed with ScaleTransform and I try to flip it again in the desired direction, the solution does not work for me.
Please correct me what I am doing wrong here.
Here is the code I am using
XAML
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="10"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Canvas Name="canvas" Background="LightGray" MouseDown="Canvas_MouseDown">
<Rectangle x:Name="rect" StrokeThickness="2" Stroke="Black" Height="60" Width="100" Canvas.Left="500" Canvas.Top="300" MouseDown="rect_MouseDown">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
<GradientStop Color="#FFFB7D0E" Offset="0" />
<GradientStop Color="#FF59C103" Offset="1" />
<GradientStop Color="#FFFFFFFF" Offset="0.51" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Canvas>
<StackPanel Grid.Column="2">
<Button Content="Left" Click="Button_Click"></Button>
<Button Content="Right" Click="Button_Click"></Button>
<Button Content="Up" Click="Button_Click"></Button>
<Button Content="Down" Click="Button_Click"></Button>
</StackPanel>
</Grid>
C#
Rectangle selectedRect;
//Flip
private void Button_Click(object sender, RoutedEventArgs e)
{
if (selectedRect == null)
return;
//Create a copy of rectangle
string rectXaml = XamlWriter.Save(selectedRect);
StringReader stringReader = new StringReader(rectXaml);
XmlReader xmlReader = XmlReader.Create(stringReader);
Rectangle newRect = (Rectangle)XamlReader.Load(xmlReader);
//Calculate the bounding box
var boundingRect = newRect.RenderTransform.TransformBounds(new Rect(0, 0, newRect.Width, newRect.Height));
double cX, cY, sX, sY;
sX = 1;
sY = 1;
cX = (boundingRect.Left + boundingRect.Right) / 2;
cY = (boundingRect.Top + boundingRect.Bottom) / 2;
switch ((sender as Button).Content.ToString())
{
case "Up":
sX = 1;
sY = -1;
cY = boundingRect.Top;
break;
case "Down":
sX = 1;
sY = -1;
cY = boundingRect.Bottom;
break;
case "Left":
sX = -1;
sY = 1;
cX = boundingRect.Left;
break;
case "Right":
sX = -1;
sY = 1;
cX = boundingRect.Right;
break;
}
newRect.Stroke = Brushes.Black;
newRect.RenderTransform = new ScaleTransform(sX, sY, cX, cY);
newRect.MouseDown += new MouseButtonEventHandler(rect_MouseDown);
//Add new rect to Canvas
canvas.Children.Add(newRect);
}
//To select a rectangle on canvas
private void rect_MouseDown(object sender, MouseButtonEventArgs e)
{
if (selectedRect != null)
selectedRect.Stroke = Brushes.Black;
selectedRect = sender as Rectangle;
selectedRect.Stroke = Brushes.Blue;
e.Handled = true;
}
//clear the selection
private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
if (selectedRect != null)
{
selectedRect.Stroke = Brushes.Black;
selectedRect = null;
}
e.Handled = true;
}
the issue in your code was that you were trying to flip again the flipped, but the transform used was not not flipped. also the canvas location were not set properly to get the desired.
I did try to rewrite your code, this approach will flip the rectangle from the center and will move via canvas location properties i.e. left & top
private void Button_Click(object sender, RoutedEventArgs e)
{
if (selectedRect == null)
return;
//Create a copy of rectangle
string rectXaml = XamlWriter.Save(selectedRect);
StringReader stringReader = new StringReader(rectXaml);
XmlReader xmlReader = XmlReader.Create(stringReader);
Rectangle newRect = (Rectangle)XamlReader.Load(xmlReader);
double cX, cY, sX, sY;
sX = 1;
sY = 1;
cX = newRect.Width / 2;
cY = newRect.Height / 2;
double top = Canvas.GetTop(selectedRect);
double left = Canvas.GetLeft(selectedRect);
switch ((sender as Button).Content.ToString())
{
case "Up":
sX = 1;
sY = -1;
top -= selectedRect.Height;
break;
case "Down":
sX = 1;
sY = -1;
top += selectedRect.Height;
break;
case "Left":
sX = -1;
sY = 1;
left -= selectedRect.Width;
break;
case "Right":
sX = -1;
sY = 1;
left += selectedRect.Width;
break;
}
Canvas.SetLeft(newRect, left);
Canvas.SetTop(newRect, top);
newRect.Stroke = Brushes.Black;
ScaleTransform trans = newRect.RenderTransform as ScaleTransform;
if (trans != null)
{
//flip the previous transform
trans.ScaleX *= sX;
trans.ScaleY *= sY;
}
else
{
//create a new if none
trans = new ScaleTransform(sX, sY, cX, cY);
}
newRect.RenderTransform = trans;
newRect.MouseDown += new MouseButtonEventHandler(rect_MouseDown);
//Add new rect to Canvas
canvas.Children.Add(newRect);
//optional to set the new rect as selected for continuous flip copy
newRect.RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left) { RoutedEvent = Rectangle.MouseDownEvent });
}
give it a try, see if this is what you are looking for. I would suggest you to go for data binding, that will make things lot easier.

How to limit transformation of images inside canvas in window store app

I have been struggling for two weeks for this problem . I am applying dragging and scaling to an image inside canvas.Dragging works fine and is limiting inside canvas IsBoundary functions but when I am applying scaling its drag area changes . If increases scaling with mouse drag area increases also and whem I make it shrink in size drag area also shrinks.Help me to solve this problem of limiting scaling
Thanks.
Here is my code link
sample
I think I understand your question. When you scale an item in a canvas the translation needs to account for the change in scale. Is that right?
Assuming this XAML:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Border Width="500"
Height="500"
BorderBrush="White"
BorderThickness="1">
<Canvas x:Name="MyCanvas">
<Rectangle x:Name="MyRectangle"
Width="50"
Height="50"
Fill="CornflowerBlue">
<Rectangle.RenderTransform>
<CompositeTransform TranslateX="225" TranslateY="225" />
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>
</Border>
</Grid>
Try this code-behind:
void MainPage_Loaded(object sender, RoutedEventArgs args)
{
MyRectangle.ManipulationMode =
ManipulationModes.TranslateX
| ManipulationModes.TranslateY;
var transform = MyRectangle.RenderTransform as CompositeTransform;
var reposition = new Action<double, double>((x, y) =>
{
var size = new Size(MyRectangle.ActualWidth * transform.ScaleX, MyRectangle.ActualHeight * transform.ScaleY);
var location = MyRectangle.TransformToVisual(MyRectangle).TransformPoint(new Point(0, 0));
var minX = -location.X;
var maxX = MyCanvas.ActualWidth - size.Width;
var newX = Within(x, minX, maxX);
transform.TranslateX = Within(newX, minX, maxX);
var minY = -location.Y;
var maxY = MyCanvas.ActualHeight - size.Height;
var newY = Within(y, minY, maxX);
transform.TranslateY = Within(newY, minY, maxY);
});
MyRectangle.ManipulationDelta += (s, e) =>
{
var newX = transform.TranslateX + e.Delta.Translation.X;
var newY = transform.TranslateY + e.Delta.Translation.Y;
reposition(newX, newY);
};
MyRectangle.PointerWheelChanged += (s, e) =>
{
// require control
if (Window.Current.CoreWindow.GetKeyState(VirtualKey.Control)
== Windows.UI.Core.CoreVirtualKeyStates.None)
return;
// ignore horizontal
var props = e.GetCurrentPoint(MyRectangle).Properties;
if (props.IsHorizontalMouseWheel)
return;
// apply scale
var newScale = transform.ScaleX + (double)props.MouseWheelDelta * .001;
transform.ScaleX = transform.ScaleY = newScale;
// reposition
reposition(transform.TranslateX, transform.TranslateY);
};
}
public double Within(double value, double min, double max)
{
if (value <= min)
return min;
else if (value >= max)
return max;
else
return value;
}
I hope this helps.
Note: Since I am not on a touch machine right now, I implemented the mouse wheel to scale. But you can modify the code as you want. The logic would be identical.
Best of luck!

How can I do both zoom and rotate on an inkcanvas?

Using the following XAML:
<Grid x:Name="grid" Background="LightBlue" ClipToBounds="True">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Viewbox x:Name="imgViewbox" >
<InkCanvas Grid.Row="0" Name="inkCanvas" Background="Red" >
<Image Source="Images/pic.png" HorizontalAlignment="Left" x:Name="imgObject" VerticalAlignment="Top" />
<Label>Testing</Label>
</InkCanvas>
</Viewbox>
</Grid>
I am trying to rotate around the center of the image and also use the wheel mouse to zoom. I have set up this transform group and event:
public MainWindow() {
InitializeComponent();
DataContext = new MainWindowViewModel();
transformGroup = new TransformGroup();
scaleTransform = new ScaleTransform();
rotateTransform = new RotateTransform();
translateTransform = new TranslateTransform();
transformGroup.Children.Add(rotateTransform);
transformGroup.Children.Add(scaleTransform);
transformGroup.Children.Add(translateTransform);
imgViewbox.RenderTransform = transformGroup;
imgViewbox.MouseWheel += ImageViewboxMouseWheel;
}
Rotate is simple:
void Rotate(object sender, RoutedEventArgs e) {
//imgViewbox.RenderTransformOrigin = new Point(0.5,0.5);
rotateTransform.Angle += 90;
}
but zoom is doing all sorts of weird stuff jumping around the screen. The code for zoom is here:
void ImageViewboxMouseWheel(object sender, MouseWheelEventArgs e) {
//imgViewbox.RenderTransformOrigin = new Point(0, 0);
double zoomFactor = DefaultZoomFactor;
if (e.Delta <= 0) zoomFactor = 1.0 / DefaultZoomFactor;
// DoZoom requires both the logical and physical location of the mouse pointer
var physicalPoint = e.GetPosition(imgViewbox);
if (transformGroup.Inverse != null) {
DoZoom(zoomFactor, transformGroup.Inverse.Transform(physicalPoint), physicalPoint);
}
else {
throw new ArgumentException("Missing Inverse");
}
//Set the center point of the ScaleTransform object to the cursor location.
scaleTransform.CenterX = e.GetPosition(imgViewbox).X;
scaleTransform.CenterY = e.GetPosition(imgViewbox).Y;
Debug.WriteLine(string.Format("IVMW Center {0},{1}", scaleTransform.CenterX, scaleTransform.CenterY));
}
public void DoZoom(double deltaZoom, Point mousePosition, Point physicalPosition) {
double currentZoom = scaleTransform.ScaleX;
currentZoom *= deltaZoom;
translateTransform.X = -1*(mousePosition.X*currentZoom - physicalPosition.X);
translateTransform.Y = -1*(mousePosition.X*currentZoom - physicalPosition.Y);
scaleTransform.ScaleX = currentZoom;
scaleTransform.ScaleY = currentZoom;
}
I have removed as much as I can, animations and such. Hopefully leaving only the key parts. I believe that the major problem is the scaleTransform.Center[X|Y] as the numbers that are being returned are all over the quadrant even when I try to click exactly in the same location. The RenderTransformOrigin doesn't seem to make any difference with the Center position but I am aware that I need it to rotate around center.
What am I doing wrong?
You need to offset the jump you get from changing the ScaleTranform's CenterX/Y in the TranslateTransform, here is a snippet from a pan & zoom control i wrote:
private void This_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (IsZoomEnabled)
{
Point cursorPos = e.GetPosition(this);
Point newCenter = _scaleT.Inverse.Transform(_translateT.Inverse.Transform(cursorPos));
Point oldCenter = new Point(_scaleT.CenterX, _scaleT.CenterY);
Vector oldToNewCenter = newCenter - oldCenter;
_scaleT.CenterX = newCenter.X;
_scaleT.CenterY = newCenter.Y;
_translateT.X += oldToNewCenter.X * (_scaleT.ScaleX - 1.0);
_translateT.Y += oldToNewCenter.Y * (_scaleT.ScaleY - 1.0);
...
Hopefully you can adapt this to your code. Where the new center is calculated you might need to take your RotateTransform into account.

Categories

Resources