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
Related
[![enter image description here][1]][1]I have folder with a series of jpgs, which are frames of video that have been converted into jpgs. I made some code that iterates through them and displays them.
I am trying to draw a green box on a JPG image from C#. The Hight, Width, XC and YC are in a XML I just access the data there for each frame. I got it to work using a bitmap but then to display it in WPF I have to convert it into a bitmapImage first. The problem is it takes way to long. I want the video to be played at 25 fps. so all the processing needs to happen under 40 ms. Right now it takes anywhere between .01 and .3 seconds to displays the new image.
Here is the code I have so far-
public void UpdateImage(string imageName, int[] boxData)
{
// imageName is the file path the image
Bitmap oldImage = new Bitmap(imageName);
// if objects are detected
if (boxData.Length != 0)
{
// transforms x and y cords to align box better to light
int newXC = boxData[0] - (boxData[2] / 2);
int newYC = boxData[1] - (boxData[3] / 2);
// ensures cords are not negative
if (newXC < 0)
newXC = 0;
if (newYC < 0)
newYC = 0;
// uses the DrawRectangleBorder to draw rectangles
Bitmap newImage = DrawRectangleBorder(oldImage, new Rectangle(new System.Drawing.Point(newXC, newYC), new System.Drawing.Size(boxData[2], boxData[3])), Color.Green);
// converts Bitmap to BitmapImage
using (MemoryStream memory = new MemoryStream())
{
newImage.Save(memory, ImageFormat.Png);
memory.Position = 0;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
ImportImage.Source = bitmapImage;
}
}
else
{
Bitmap newImage = oldImage;
// converts Bitmap to BitmapImage
using (MemoryStream memory = new MemoryStream())
{
newImage.Save(memory, ImageFormat.Png);
memory.Position = 0;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
ImportImage.Source = bitmapImage;
}
}
}
The DrawRectangleBorder Method-
private static Bitmap DrawRectangleBorder(Bitmap image, Rectangle rectangle, Color colour)
{
// makes new blank Bitmap from the old ones width and height
Bitmap newBitmap = new Bitmap(image.Width, image.Height);
// opens up the blank bit
using (Graphics graphics = Graphics.FromImage(newBitmap))
graphics.DrawImage(image, new Rectangle(0, 0, image.Width, image.Height),
new Rectangle(0, 0, image.Width, image.Height), GraphicsUnit.Pixel);
// what actually draws the rectangles
for (Int32 x = rectangle.Location.X; x <= rectangle.Right && x < image.Width; x++)
for (Int32 y = rectangle.Location.Y; y <= rectangle.Bottom && y < image.Height; y++)
if (y == rectangle.Location.Y || y == rectangle.Bottom || x == rectangle.Location.X || x == rectangle.Right)
newBitmap.SetPixel(x, y, colour);
return newBitmap;
}
Here is what one of the pictures look like, they are 640 by 480 resolution-
[1]: https://i.stack.imgur.com/ZiocC.jpg
Any help would be great!
You could simplify your code by using this XAML
<Canvas>
<Image x:Name="ImportImage"/>
<Path x:Name="ObjectBox"
Width="{Binding ActualWidth, ElementName=ImportImage}"
Height="{Binding ActualHeight, ElementName=ImportImage}"
Stretch="None" Stroke="Green" StrokeThickness="1"/>
</Canvas>
with an UpdateImage method like this:
public void UpdateImage(string imagePath, int[] boxData)
{
ImportImage.Source = new BitmapImage(new Uri(imagePath));
var boxes = new GeometryGroup();
for (int i = 0; i <= boxData.Length - 4; i += 4)
{
int width = boxData[i + 2];
int height = boxData[i + 3];
int x = boxData[i] - width / 2;
int y = boxData[i + 1] - height / 2;
boxes.Children.Add(new RectangleGeometry(new Rect(x, y, width, height)));
}
ObjectBox.Data = boxes;
}
here is a rough demo of using a rectangle overlaid in front of an image and controlling its position and size. Sorry, never shared wpf on SO before, but think this code is all you need. I tested the time and it can go well below 40 ms (but I am not updating the image at all, just the rectangle overlay. In case it is needed, I also found this Fast Video Display WPF but ya, did not implement it or test it out.
".cs"
namespace ImageStreamer
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
System.Windows.Threading.DispatcherTimer dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
Stopwatch stopWatch = new Stopwatch();
long lastTime = 0;
public MainWindow()
{
InitializeComponent();
dispatcherTimer.Tick += dispatcherTimer_Tick;
dispatcherTimer.Interval = new TimeSpan(0, 0, 0,0,25);
stopWatch.Start();
}
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
targetRect.Margin = new Thickness(targetRect.Margin.Left+1, targetRect.Margin.Top+1,0,0);
targetRect.Width += 1;
targetRect.Height += 1;
Trace.WriteLine(lastTime - stopWatch.ElapsedMilliseconds);
lastTime = stopWatch.ElapsedMilliseconds;
}
private void Grid_Initialized(object sender, EventArgs e)
{
}
private void Button_Click(object sender, RoutedEventArgs e)
{
dispatcherTimer.Start();
}
}
}
"xaml"
<Window x:Class="ImageStreamer.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:ImageStreamer"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Initialized="Grid_Initialized">
<Image x:Name="imgBox" HorizontalAlignment="Left" Height="265" Margin="166,87,0,0" VerticalAlignment="Top" Width="512" Source="/ZiocC.jpg"/>
<Rectangle x:Name="targetRect" HorizontalAlignment="Left" Height="49" Margin="323,228,0,0" Stroke="Red" VerticalAlignment="Top" Width="113" StrokeThickness="5"/>
<Button Content="Button" HorizontalAlignment="Left" Margin="24,43,0,0" VerticalAlignment="Top" Click="Button_Click"/>
</Grid>
</Window>
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.
i'm new to WPF, learning by migrating an existing winforms app.
i'm drawing rectangles on a Canvas, which contains an Image. the rectangles get the X,Y, and Z information from the imported IntPtr object on the Image, which is then analysed as data.
drawing the rectangles is easy enough with Children.Add() and the data capture is working fine, but i can only see the rectangle once it's drawn, so it's hard to know what area you are going to select. i'd like to see it during the drawing as the mouse moves (like a standard lasso rectangle). in winforms i used the Paint event, how to do similar in WPF? i'm using MouseDown and MouseUp events to capture the start and end points of the rectangle
public void MouseDown(MouseButtonEventArgs e, Canvas evImage)
{
if (e.LeftButton == MouseButtonState.Pressed)
startPos = e.GetPosition(evImage);
evImage.CaptureMouse();
}
public void MouseUp(MouseButtonEventArgs e, Canvas evImage)
{
if (Constants.count < 3)
{
if (e.LeftButton == MouseButtonState.Released)
currentPos = e.GetPosition(evImage);
rec = new System.Windows.Shapes.Rectangle()
{
Stroke = System.Windows.Media.Brushes.LightBlue,
StrokeThickness = 1,
Name = "rec" + Constants.count.ToString(),
};
if (startPos.X < currentPos.X)
rec.Width = currentPos.X - startPos.X;
else
rec.Width = startPos.X - currentPos.X;
if (startPos.Y < currentPos.Y)
rec.Height = currentPos.Y - startPos.Y;
else
rec.Height = startPos.Y - currentPos.Y;
if (rec.Height < 8)
rec.Height = 8;
if (rec.Width < 8)
rec.Width = 8;
if (evImage.Children.Count > Constants.count + 1)
evImage.Children.RemoveAt(Constants.count + 1);
evImage.Children.Insert(Constants.count + 1, rec);
Canvas.SetLeft(rec, startPos.X);
Canvas.SetTop(rec, startPos.Y);
evImage.ReleaseMouseCapture();
SetRectangleData();
Constants.count++;
}
else
{
Constants.count = 0;
RecMove(e, evImage);
}
}
XAML - this is my main learning point, i don't know if using the Image in the Canvas is a problem, or if i need to reference something here like the rectangle?
<Canvas x:Name="evCanvas" MouseUp="evCanvas_MouseUp" MouseDown="evCanvas_MouseDown" Grid.Column="1" Height="550" Width="626" HorizontalAlignment="Left" Margin="10,43,0,0" Grid.Row="1" VerticalAlignment="Top" Grid.ColumnSpan="2" Grid.RowSpan="2">
<Image x:Name="evImage" Height="550" Width="626" MouseMove="evImage_MouseMove" RenderTransformOrigin=".5,.5" />
</Canvas>
Just set Left and Top to the minimum x and y and width to max-x and height max-y.
<Canvas x:Name="canvas" MouseDown="Canvas_MouseDown" MouseMove="Canvas_MouseMove" MouseUp="Canvas_MouseUp" Background="Transparent" />
Then doing as follows:
private Point startPoint;
private Rectangle rect;
private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
startPoint = e.GetPosition(canvas);
rect = new Rectangle
{
Stroke = Brushes.LightBlue,
StrokeThickness = 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;
rect.Width = w;
rect.Height = h;
Canvas.SetLeft(rect, x);
Canvas.SetTop(rect, y);
}
private void Canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
rect = null;
}
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;
}
today I already asked you a question for my picture puzzle (Original Question).
I started to rewrite my code for better performance. And I got the most important part done!
But I have another problem.. I generate a gray overlay image to hide the image, but because I want to handle dynamic size of pictures I cannot set fixed width and height values for the "mask". So probably not the whole picture is overlayed with my "mask".
Does somebody have a solution for this? I don't know how to set the position and size of my mask exactly to the position and size of the picture.
I attached a screencast for demonstration.
XAML:
<Window x:Class="PicturePuzzle.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Loaded="WindowLoaded">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="80" />
</Grid.RowDefinitions>
<Grid x:Name="grid"
Margin="5"
HorizontalAlignment="Center"
VerticalAlignment="Top">
<Image x:Name="imgPicture"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="Images/puzzle.gif"
Stretch="Uniform" />
<Image x:Name="imgMask" RenderOptions.EdgeMode="Aliased" />
</Grid>
<StackPanel Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<Button x:Name="btnStart"
Width="60"
Margin="0,0,5,0"
Click="BtnStartClick"
Content="Start" />
<Button x:Name="btnStop"
Width="60"
Click="BtnStopClick"
Content="Stop"
IsEnabled="False" />
<ToggleButton x:Name="btnSolution"
Margin="5,0,0,0"
Checked="btnSolution_Checked"
Content="Lösung anzeigen"
Unchecked="btnSolution_Unchecked" />
</StackPanel>
<Slider x:Name="slSpeed"
IsDirectionReversed="True"
Maximum="10"
Minimum="1"
ValueChanged="SlSpeedValueChanged"
Value="10" />
</StackPanel>
</Grid>
</Window>
Codebehind:
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
namespace PicturePuzzle
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
_positionAlphaValues = new Dictionary<Point, byte>();
_images = new List<FileInfo>();
using (var s = new StreamReader("Images.txt"))
{
while (!s.EndOfStream)
{
var line = s.ReadLine();
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
var fi = new FileInfo(line);
if (!fi.Exists)
{
continue;
}
_images.Add(fi);
}
}
}
private const int MaxQuadsX = 5;
private const int MaxQuadsY = 5;
private readonly List<FileInfo> _images;
private DispatcherTimer _timer;
private DispatcherTimer _alphaTimer;
private WriteableBitmap _bitmap;
private Size _size;
private List<Point> _positions;
private Dictionary<Point, byte> _positionAlphaValues;
private int _tickCounter;
private int _imageCounter;
private int _quadWidth;
private int _quadHeight;
private void WindowLoaded(object sender, RoutedEventArgs e)
{
_size = imgPicture.RenderSize;
var width = (int)Math.Ceiling(_size.Width);
var height = (int)Math.Ceiling(_size.Height);
_quadWidth = width / MaxQuadsX;
_quadHeight = height / MaxQuadsY;
imgPicture.Width = _quadWidth * MaxQuadsX - 5;
imgPicture.Height = _quadHeight * MaxQuadsY - 5;
_bitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgra32, null);
imgMask.Source = _bitmap;
}
#region Click handlers
private void BtnStartClick(object sender, RoutedEventArgs e)
{
btnStart.IsEnabled = false;
btnStop.IsEnabled = true;
btnSolution.IsChecked = false;
// set the real picture
_imageCounter = 0;
_images.Shuffle();
SetPuzzlePicture();
_timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(slSpeed.Value / 10) };
_timer.Tick += TimerTick;
_timer.Start();
_alphaTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(0.1) };
_alphaTimer.Tick += AlphaTimerOnTick;
_alphaTimer.Start();
}
private void BtnStopClick(object sender, RoutedEventArgs e)
{
btnStart.IsEnabled = true;
btnStop.IsEnabled = false;
_timer.Stop();
_alphaTimer.Stop();
}
private void SlSpeedValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (_timer != null)
{
_timer.Interval = TimeSpan.FromSeconds(slSpeed.Value / 10);
}
}
private void btnSolution_Checked(object sender, RoutedEventArgs e)
{
btnStop.IsEnabled = false;
StopTimers();
imgMask.Visibility = Visibility.Hidden;
}
private void btnSolution_Unchecked(object sender, RoutedEventArgs e)
{
btnStart.IsEnabled = true;
btnStop.IsEnabled = false;
ResetMaskImage();
}
#endregion
private void SetPuzzlePicture()
{
_positionAlphaValues.Clear();
ResetMaskImage();
var imgFile = _images[_imageCounter++];
var image = new BitmapImage();
image.BeginInit();
image.UriSource = new Uri(imgFile.FullName, UriKind.Absolute);
image.EndInit();
imgPicture.Source = image;
}
private void TimerTick(object sender, EventArgs e)
{
if (_tickCounter >= _positions.Count)
{
if (_imageCounter >= _images.Count)
{
_timer.Stop();
btnStart.IsEnabled = true;
btnStop.IsEnabled = false;
return;
}
SetPuzzlePicture();
}
var randomPoint = _positions[_tickCounter++];
_positionAlphaValues.Add(randomPoint, 255);
}
private void AlphaTimerOnTick(object sender, EventArgs eventArgs)
{
var updatedList = new Dictionary<Point, byte>();
foreach (var e in _positionAlphaValues)
{
var newValue = e.Value - (11 - slSpeed.Value) * 5;
if (newValue <= 0)
{
continue;
}
SetAlphaChannel(e.Key, (byte)newValue);
updatedList.Add(e.Key, (byte)newValue);
}
_positionAlphaValues = updatedList;
}
private void StopTimers()
{
if (_timer != null)
{
_timer.Stop();
}
if (_alphaTimer != null)
{
_alphaTimer.Stop();
}
}
private void ResetMaskImage()
{
imgMask.Visibility = Visibility.Visible;
var width = _quadWidth * MaxQuadsX;
var height = _quadHeight * MaxQuadsY;
var size = width * height * 4;
var buffer = new byte[size];
for (int i = 0; i < size; i++)
{
buffer[i++] = 128;
buffer[i++] = 128;
buffer[i++] = 128;
buffer[i] = 255;
}
var area = new Int32Rect(0, 0, width, height);
_bitmap.WritePixels(area, buffer, width * 4, 0);
_positions = GetPositions();
_tickCounter = 0;
}
private void SetAlphaChannel(Point point, byte alpha)
{
var size = _quadWidth * _quadHeight * 4;
var buffer = new byte[size];
for (int i = 0; i < size; i++)
{
buffer[i++] = 128;
buffer[i++] = 128;
buffer[i++] = 128;
buffer[i] = alpha;
}
var startX = (int)point.X * _quadWidth;
var startY = (int)point.Y * _quadHeight;
var area = new Int32Rect(startX, startY, _quadWidth, _quadHeight);
_bitmap.WritePixels(area, buffer, _quadWidth * 4, 0);
}
private List<Point> GetPositions()
{
var generated = new List<Point>();
for (int y = 0; y < MaxQuadsY; y++)
{
for (int x = 0; x < MaxQuadsX; x++)
{
var point = new Point(x, y);
generated.Add(point);
}
}
generated.Shuffle();
return generated;
}
}
}
Extensions.cs
using System;
using System.Collections.Generic;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace PicturePuzzle
{
public static class Extensions
{
public static Color GetPixel(this WriteableBitmap wbm, int x, int y)
{
if (y > wbm.PixelHeight - 1 || x > wbm.PixelWidth - 1)
return Color.FromArgb(0, 0, 0, 0);
if (y < 0 || x < 0)
return Color.FromArgb(0, 0, 0, 0);
if (!wbm.Format.Equals(PixelFormats.Bgra32))
return Color.FromArgb(0, 0, 0, 0);
IntPtr buff = wbm.BackBuffer;
int stride = wbm.BackBufferStride;
Color c;
unsafe
{
var pbuff = (byte*)buff.ToPointer();
int loc = y * stride + x * 4;
c = Color.FromArgb(
pbuff[loc + 3],
pbuff[loc + 2], pbuff[loc + 1],
pbuff[loc]);
}
return c;
}
public static void Shuffle<T>(this IList<T> list)
{
var rng = new Random();
int n = list.Count;
while (n > 1)
{
n--;
int k = rng.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
}
}
Example Images.txt (has to be in your output folder, the images also):
Desert.jpg
Hydrangeas.jpg
Jellyfish.jpg
Koala.jpg
Lighthouse.jpg
Penguins.jpg
Tulips.jpg
androids.gif
Chrysanthemum.jpg
And the screencast: Screencast Link
Thanks for any help!
You can set the HorizontalAlignment and VerticalAlignment of your mask Image to Stretch and the Stretch to Fill like so:
<Image HorizontalAlignment="Stretch" VerticalAlignment="Stretch" x:Name="imgMask" Stretch="Fill" RenderOptions.EdgeMode="Aliased" />
This will cause the mask image to fill the grid, which will be sized to the other image.