I have a ContentDialog where I have an image and another image overlayed to place a watermark on it:
<StackPanel Orientation="Vertical">
<Grid>
<Image x:Name="ImageOriginal" Source="{x:Bind OriginalImage, Mode=OneWay}" MinHeight="120" MinWidth="160"/>
<Image x:Name="ImageOverlay" Source="{x:Bind OverlayImage, Mode=OneWay}" MinHeight="120" MinWidth="160" PointerPressed="ImageOverlay_PointerPressed" />
</Grid>
...
So the goal was, to place a text (centered horizontally and vertically) on the clicked position.
private void ImageOverlay_PointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
{
e.Handled = true;
var point = e.GetCurrentPoint(ImageOverlay);
var pointPosition = new Windows.Foundation.Point((int)point.Position.X, (int)point.Position.Y);
Position = new PointF((float)pointPosition.X, (float)pointPosition.Y);
UpdateOverlayImage();
}
(PointF) is a floating point point of ImageSharp. With the code above my text placement is always a bit off, like 50 pixels in each direction.
I also tried it with the following code, but this just makes the text placed wrongly in the opposite direction:
GeneralTransform transform = ImageOverlay.TransformToVisual(Window.Current.Content);
Windows.Foundation.Point coordinatePointToWindow = transform.TransformPoint(pointPosition);
Update:
Someone wanted to know, how I draw the text,s o here it is, this code is inside UpdateOverlayImage():
var watermarkedImage = OverlayImageBase.Clone(ctx => ctx.ApplyWatermark(font, tbWatermarkText.Text, color, Position));
OverlayImage = watermarkedImage.ToBitmap();
And the ApplyWatermark extension method looks like this:
public static IImageProcessingContext ApplyWatermark(this IImageProcessingContext processingContext,
Font font,
string text,
Color color,
PointF position)
{
var textGraphicOptions = new TextGraphicsOptions(true)
{
ColorBlendingMode = SixLabors.ImageSharp.PixelFormats.PixelColorBlendingMode.Normal,
HorizontalAlignment = SixLabors.Fonts.HorizontalAlignment.Center,
VerticalAlignment = SixLabors.Fonts.VerticalAlignment.Center,
};
return processingContext.DrawText(textGraphicOptions, text, font, color, position);
}
Any ideas why my coordinates are off?
I found out what was wrong. As the displayed image inside the ContentDialog is scaled, the coordinates of the drawn text cannot be directly the same coordintes as received from the pointer pressed event (of course).
So we have to calculate the relative to the size of the scaling. This is how I could solve it:
private void ImageOverlay_PointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
{
e.Handled = true;
PointerPoint ptrPt = e.GetCurrentPoint(ImageOverlay);
// As the image is scaled we have to caluclate the position inside the image based on the position of the pointer pressed
float imagePressedX = (float)(OriginalImage.PixelWidth / ImageOverlay.ActualWidth * ptrPt.Position.X);
float imagePressedY = (float)(OriginalImage.PixelHeight / ImageOverlay.ActualHeight * ptrPt.Position.Y); ;
Position = new PointF(imagePressedX, imagePressedY);
UpdateOverlayImage();
}
Related
I have a series of rectangles in which the user can add images to, by dragging the images in.
The image is then scaled down in proportion and the rectangle is then filled with an ImageBrush.
I need for the user to be able to manipulate the image within the rectangle to fit their needs. Like any photo collage app does.
My question is: How can I show the full, unmasked image on top of the rectangle so that the user can manipulate it to their needs? I don't know where to start with this one.
private async void Mask_Drop(object sender, DragEventArgs e)
{
Rectangle maskSq = e.OriginalSource as Rectangle;
var maskW = maskSq.Width.ToSingle();
var maskH = maskSq.Height.ToSingle();
double maskX = Canvas.GetLeft(maskSq);
double maskY = Canvas.GetTop(maskSq);
// Image sizes for bounding to mask
float boundH = Convert.ToSingle(size.Height);
float boundW = Convert.ToSingle(size.Width);
maskSq.Fill = new ImageBrush
{
ImageSource = new BitmapImage(new Uri("ms-appdata:///local/" + SelectedImage.Name, UriKind.Absolute)),
Stretch = Stretch.UniformToFill
};
}
private void Tap_Collage(object sender, TappedRoutedEventArgs e)
{
//Gets the full image from ImageBrush
ImageBrush brush = (ImageBrush)(((Rectangle)sender).Fill);
Rectangle rect = sender as Rectangle;
//Mask sure rectangle does not drag, just the image brush
rect.CanDrag = false;
rect.StrokeThickness = 6;
//Drag Image Functionality
rect.ManipulationDelta += ImageManipulation.Resize_ImageEdit;
ImageManipulation.ImageEdit_Drag = new TranslateTransform();
brush.Transform = ImageManipulation.ImageEdit_Drag;
//Zoom Image Functionality
ImageManipulation.ImageEdit_Zoom = new ScaleTransform();
brush.RelativeTransform = ImageManipulation.ImageEdit_Zoom;
}
Class
public static class ImageManipulation
{
public static TranslateTransform ImageEdit_Drag;
public static ScaleTransform ImageEdit_Zoom;
public static RotateTransform ImageEdit_Rotate;
public static void Resize_ImageEdit(object sender, ManipulationDeltaRoutedEventArgs e)
{
ImageEdit_Drag.X += e.Delta.Translation.X;
ImageEdit_Drag.Y += e.Delta.Translation.Y;
ImageEdit_Zoom.ScaleX *= e.Delta.Scale;
ImageEdit_Zoom.ScaleY *= e.Delta.Scale;
if (ImageEdit_Zoom.ScaleX < 1.0)
{
ImageEdit_Zoom.ScaleX = 1.0;
ImageEdit_Zoom.ScaleY = 1.0;
}
ImageEdit_Rotate.Angle += e.Delta.Rotation;
}
}
XAML
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="local:CollageGrid">
<Rectangle Width="{Binding CollageW}" Height="{Binding CollageH}" AllowDrop="True" CanDrag="True" Fill="Transparent"
Drop="Mask_Drop"
DragOver="Mask_DragOver"
ManipulationMode="TranslateX, TranslateY" Stroke="Black" StrokeThickness="2" DragEnter="Mask_DragEnter" DragLeave="Mask_DragLeave" Tapped="Tap_Collage">
<Rectangle.RenderTransform>
<TranslateTransform X="{Binding CollageX}" Y="{Binding CollageY}"/>
</Rectangle.RenderTransform>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
Example of what I'm looking to acheive:
How it looks currently:
You can try to replace the Rectangle with the Polyline to draw the Rectangle so that you can access the Image which is on the bottom of the Rectangle.
<Grid>
<Image Source="Assets/image1.jpg" Width="800"
Height="400" Tapped="Image_Tapped" />
<Polyline Stroke="Black" HorizontalAlignment="Center"
VerticalAlignment="Center"
StrokeThickness="4" Tapped="Polyline_Tapped"
Points="0,0,0,200,200,200,200,0,0,0" />
</Grid>
---Update---
UniformToFill will cause the image is resized to fill the destination dimensions while it preserves its native aspect ratio. If the aspect ratio of the destination rectangle differs from the source, the source content is clipped to fit in the destination dimensions. So it is not suitable for your requirement. You should manually scale the Image to make image fit one of your Rectangle's border and keep other part out of the Rectangle.
It seems you put the image as the Rectangle's background image brush, there are no other place to display the image out of the Rectangle. So I think, we may take a considerition for a new scenario.
As my pervious reply, using Image control to display the image and Polylines to draw a Rectangle above the Image so that we can operate the Image which is below the Rectangle using the manipulation events to fit the rectangle, meanwhile we can also use the community toolkit BackdropBlurBrush on the xaml layout to Blur the outer image.
I am trying to implement a zoom-functionality for a canvas using the mouse wheel.
Currently I am just Zooming to the center position of the canvas using CenterX="0.5" and CenterY="0.5".
I would like to change the behavior so that the zooming happens at the mouse position and I would like to know if this is possible with a ScaleTransform.
Currently I use the following code:
<Canvas Width="500" Height="500">
<Canvas.LayoutTransform>
<ScaleTransform CenterX="0.5" CenterY="0.5"
ScaleX="{Binding Zoom}"
ScaleY="{Binding Zoom}" />
</Canvas.LayoutTransform>
</Canvas>
A very basic approach to zoom a Canvas (or any other UIElement) at a specific position would be to use a MatrixTransform for the RenderTransform property
<Canvas Width="500" Height="500" MouseWheel="Canvas_MouseWheel">
<Canvas.RenderTransform>
<MatrixTransform/>
</Canvas.RenderTransform>
</Canvas>
and update the Matrix property of the transform like in this MouseWheel handler:
private void Canvas_MouseWheel(object sender, MouseWheelEventArgs e)
{
var element = (UIElement)sender;
var position = e.GetPosition(element);
var transform = (MatrixTransform)element.RenderTransform;
var matrix = transform.Matrix;
var scale = e.Delta >= 0 ? 1.1 : (1.0 / 1.1); // choose appropriate scaling factor
matrix.ScaleAtPrepend(scale, scale, position.X, position.Y);
transform.Matrix = matrix;
}
I spent the past two days agonizing over this issue and I figured it out. This will get you smooth zooming in toward the mouse and smooth zooming out. I'm posting my solution here for anyone who might search and stumble back here.
// Class constructor
public YourClass(Canvas theCanvas) //You may not need the Canvas as an argument depending on your scope
{
panTransform = new TranslateTransform();
zoomTransform = new ScaleTransform();
bothTransforms = new TransformGroup();
bothTransforms.Children.Add(panTransform);
bothTransforms.Children.Add(zoomTransform);
theCanvas.RenderTransform = bothTransforms;
//Handler
theCanvas.MouseWheel += wheelEvent;
//You also need your own handlers for panning, which I'm not showing here.
}
private void returnCalculatedScale()
{
double d;
//Do some math to get a new scale. I keep track of an integer, and run it through the formula y^(x/3) where X is the integer.
return d;
}
// Mouse wheel handler, where the magic happens
private void wheelEvent(object sender, MouseWheelEventArgs e)
{
Point position = e.GetPosition(mainCanvas);
zoomTransform.CenterX = position.X;
zoomTransform.CenterY = position.Y;
zoomTransform.ScaleX = returnCalculatedScale();
zoomTransform.ScaleY = returnCalculatedScale();
Point cursorpos = Mouse.GetPosition(mainCanvas); //This was the secret, as the mouse position gets out of whack when the transform occurs, but Mouse.GetPosition lets us get the point accurate to the transformed canvas.
double discrepancyX = cursorpos.X - position.X;
double discrepancyY = cursorpos.Y - position.Y;
//If your canvas is already panned an arbitrary amount, this aggregates the discrepancy to the TranslateTransform.
panTransform.X += discrepancyX;
panTransform.Y += discrepancyY;
I have 2 buttons created in code behind ,I am trying to draw a line that connects them by getting the button coordinates, but it doesn't work the way that I wanted , I am new to this , am I doing this the right way?
I searched internet for line connecting 2 controls, but I just want a simple one that make use of coordinates to connect them .
Here are my codes :
Button btn1 = new Button();
btn1.Content = "Hello";
btn1.Width = 150;
btn1.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
wrapPanel1.Children.Add(btn1);
btn1.PreviewMouseDown += new MouseButtonEventHandler(btn1_MouseDown);
Button btn2 = new Button();
btn2.Content = "World";
btn2.Width = 150;
btn2.HorizontalAlignment = System.Windows.HorizontalAlignment.Right;
btn2.PreviewMouseDown += new MouseButtonEventHandler(btn1_MouseDown);
wrapPanel1.Children.Add(btn2);
private void ShowLocation(ContentControl element)
{
var location = element.PointToScreen(new Point(0, 0));
MessageBox.Show(string.Format(
"{2}'s location is ({0}, {1})",
location.X,
location.Y,
element.Content));
Line redLine = new Line();
redLine.X1 = location.X;
redLine.Y1 = location.Y;
redLine.X2 = 323;
redLine.Y2 = 167;
//Create a red Brush
SolidColorBrush redBrush = new SolidColorBrush();
redBrush.Color = Colors.Red;
//Set Line's width and color
redLine.StrokeThickness = 4;
redLine.Stroke = redBrush;
// Add line to the Grid.
wrapPanel1.Children.Add(redLine);
}
private void btn1_MouseDown(object sender, RoutedEventArgs e)
{
var element = sender as ContentControl;
if (element != null)
{
ShowLocation(element);
}
}
As you notice the X2 and Y2 coordinates I just random giving it a number but the X1 and Y1 is suppose to be drawn from the button but it is not.
The line is appearing somewhere else away from the button, but the coords I use are the coords of the button.
Also, I have to dynamically create many buttons from db, This is just testing out yet I am stuck here.
__EDIT____
trying out with 1 button , i realise when maximize and minimize , the coords are different , this is how it looks like , the button seems to increase its height everytime i click on it to generate the line .
i want the line to start from the end of the button width at the side , if its not possible , it will be good for the button to overlap the line but is that possible?
The main problem is that WPF mostly uses layout-based positioning (Grid, DockPanel, etc), instead of Windows Forms, which is coordinate-based. This means, that real location of left top element's corner depends from layout control being used and its current settings (and yes, this usually blows up your mind, if you are very new to WPF).
If you want to manage position of elements by coordinates in WPF, then you should use Canvas (you can easily translate this sample from XAML to C#):
<Canvas>
<Button Canvas.Left="10" Canvas.Top="10" Width="20" Height="20" Content="A"/>
<Button Canvas.Left="50" Canvas.Top="50" Width="20" Height="20" Content="B"/>
<Line Canvas.Left="30" Canvas.Top="30" X2="20" Y2="20" StrokeThickness="4" Stroke="Red"/>
</Canvas>
I have a Canvas in my MainWindow and I draw a line there. When it draws over the width/height of my Canvas, the drawing continues in my MainWindow. Is there a mistake in my code or is that normal?
<Canvas x:Name="coordinateSystem" HorizontalAlignment="Right" Height="580" Margin="0,10,283,0" VerticalAlignment="Top" Width="1024" Cursor="Cross" UseLayoutRounding="False"/>
Here is my function I call everytime when I get a new coordinate for my line:
// xOld, yOld and t are static
// t represents the time
private void drawPoly(double value)
{
t++;
Point pOne = new Point(xOld, yOld);
Point pTwo = new Point(t, value);
GeometryGroup lineGroup = new GeometryGroup();
LineGeometry connectorGeometry = new LineGeometry();
connectorGeometry.StartPoint = pOne;
connectorGeometry.EndPoint = pTwo;
lineGroup.Children.Add(connectorGeometry);
System.Windows.Shapes.Path path = new System.Windows.Shapes.Path();
path.Data = lineGroup;
path.StrokeThickness = 1;
path.Stroke = path.Fill = Brushes.Red;
coordinateSystem.Children.Add(path);
xOld = t;
yOld = value;
}
thx
PS: Is there a way to save all drawn points? I want later resize my canvas (zoom out/zoom in) or if the time going to big move my painted line in my canvas and then I need to draw all points again.
Canvases do not clip child elements. If you want to stop the child elements from being drawn outside of the Canvas, you'll need to set ClipToBounds to true or set the Clip of the Canvas.
I'm trying to learn WPF, so here's a simple question, I hope:
I have a window that contains an Image element bound to a separate data object with user-configurable Stretch property
<Image Name="imageCtrl" Source="{Binding MyImage}" Stretch="{Binding ImageStretch}" />
When the user moves the mouse over the image, I would like to determine the coordinates of the mouse with respect to the original image (before stretching/cropping that occurs when it is displayed in the control), and then do something with those coordinates (update the image).
I know I can add an event-handler to the MouseMove event over the Image control, but I'm not sure how best to transform the coordinates:
void imageCtrl_MouseMove(object sender, MouseEventArgs e)
{
Point locationInControl = e.GetPosition(imageCtrl);
Point locationInImage = ???
updateImage(locationInImage);
}
Now I know I could compare the size of Source to the ActualSize of the control, and then switch on imageCtrl.Stretch to compute the scalars and offsets on X and Y, and do the transform myself. But WPF has all the information already, and this seems like functionality that might be built-in to the WPF libraries somewhere. So I'm wondering: is there a short and sweet solution? Or do I need to write this myself?
EDIT I'm appending my current, not-so-short-and-sweet solution. Its not that bad, but I'd be somewhat suprised if WPF didn't provide this functionality automatically:
Point ImgControlCoordsToPixelCoords(Point locInCtrl,
double imgCtrlActualWidth, double imgCtrlActualHeight)
{
if (ImageStretch == Stretch.None)
return locInCtrl;
Size renderSize = new Size(imgCtrlActualWidth, imgCtrlActualHeight);
Size sourceSize = bitmap.Size;
double xZoom = renderSize.Width / sourceSize.Width;
double yZoom = renderSize.Height / sourceSize.Height;
if (ImageStretch == Stretch.Fill)
return new Point(locInCtrl.X / xZoom, locInCtrl.Y / yZoom);
double zoom;
if (ImageStretch == Stretch.Uniform)
zoom = Math.Min(xZoom, yZoom);
else // (imageCtrl.Stretch == Stretch.UniformToFill)
zoom = Math.Max(xZoom, yZoom);
return new Point(locInCtrl.X / zoom, locInCtrl.Y / zoom);
}
It would probably be easier if you used a ViewBox. For example:
<Viewbox Stretch="{Binding ImageStretch}">
<Image Name="imageCtrl" Source="{Binding MyImage}" Stretch="None"/>
</Viewbox>
Then when you go and call GetPosition(..) WPF will automatically account for the scaling.
void imageCtrl_MouseMove(object sender, MouseEventArgs e)
{
Point locationInControl = e.GetPosition(imageCtrl);
}