Two Image Layers and OpacityMask - c#

I'm trying to crop a circle from one image, and put it on top another image in WPF.
The Circle's center changes according to the mouse movements, and needs to be bounded dynamically.
I tried to position two images on top of each other, and use a third image that I draw in real time as an opacity mask.
Could you please provide short code to solve this problem efficiently ?

The code below describes what you can do with an OpacityMask. It's a little counterintuitive, because we expect a XAML rendering to layer elements bottom-to-top.
However, in this case you want your "background" image to layer on top of the foreground, because the OpacityMask will serve to display only that portion of the foreground described by the position and size of the VisualBrush, rendering the rest transparent. It's given as follows:
<Grid x:Name="MainGrid" MouseMove="Grid_MouseMove">
<Rectangle Fill="Red" ></Rectangle>
<Rectangle Fill="Green">
<Rectangle.OpacityMask>
<VisualBrush Stretch="None" >
<VisualBrush.Visual>
<Ellipse Width="40" Height="40" StrokeThickness="1" Fill="Black" />
</VisualBrush.Visual>
<VisualBrush.RelativeTransform>
<TransformGroup>
<TranslateTransform x:Name="OpacityFilterTransform" X="1" Y="1"/>
</TransformGroup>
</VisualBrush.RelativeTransform>
</VisualBrush>
</Rectangle.OpacityMask>
</Rectangle>
</Grid>
Then, this event handler code computes the position of the ellipse and applies it to the OpacityFilter's TranslateTransform object, giving you control over the position of the image.
private void Grid_MouseMove(object sender, MouseEventArgs e)
{
var position = e.GetPosition(this);
var height = MainGrid.ActualHeight;
var width = MainGrid.ActualWidth;
// with the position values, interpolate a TranslateTransform for the opacity mask
var transX = position.X / width;
var transY = position.Y / height;
OpacityFilterTransform.X = transX - 0.5;
OpacityFilterTransform.Y = transY - 0.5;
}
This solution should work for any descendant of Visual you care to layer.

Related

How to override all Shapes in a specific area WPF

Currently I am filling my MainWindow with a slightly transparent black:
But I want it to have a "hole" where this effect doesn't take place which should look like the following:
So this needs to be done at runtime since the area which the hole represents is going to change multiple times while the program is running.
What I thought I could do
So at first I thought I could just cut the area in the middle out
like you could do with a Graphics object, but the slightly
transparent black is nothing but a rectangle which is added as a child on a canvas which is currently done like this:
var background = new System.Windows.Shapes.Rectangle
{
Fill = new SolidColorBrush(System.Windows.Media.Color.FromArgb(150, 0, 0, 0)),
Width = ScreenInfo.Width,
Height = ScreenInfo.Height
};
MainCanvas.Children.Add(background);
But I couldn't fine any way to achieve this cut effect.
Creating 4 Rectangles which would look something like this: but this way of doing it didn't seem to me as the most effecient way of achieving this.
Thanks for any kind of help!
Create a CombinedGeometry by cutting a smaller square out of a larger one and then use that with a path. How you size it will depend on your application, a Viewbox will probably be good enough for most cases:
<Grid>
<TextBlock Text="Hello World!" FontSize="200" Foreground="Red" TextWrapping="Wrap" TextAlignment="Center"/>
<Viewbox Stretch="UniformToFill">
<Path Fill="#C0000000">
<Path.Data>
<CombinedGeometry GeometryCombineMode="Exclude">
<CombinedGeometry.Geometry1>
<RectangleGeometry Rect="0,0,4,4" />
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<RectangleGeometry x:Name="cutRect" Rect="1,1,2,2" />
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</Path.Data>
</Path>
</Viewbox>
</Grid>
Then to change the size of the inner geometry you can either bind its Rect to a view model property or change it directly in code-behind:
cutRect.Rect = new Rect(1, 1, 1, 1);

How can I get the new position and size of a UIElement after performing a RenderTransform (Scale & Translate)?

I am performing scaling, translation and rotation on an image using RenderTransform. Transformations are not done using mouse events, rather, the user will click a button and then the image will scale/translate/rotate on a fixed value.
My problem is I want to determine the new position/size of the image each time a scaling, translation or rotation is performed. So I added Changed events on the code-behind. The question is how do you get the new position/size?
Please take a look at what I've done so far:
XAML:
<Border x:Name="mainImageBorderCtrl" ClipToBounds="True">
<Grid x:Name="imageGridCtrl">
<Grid.RenderTransform>
<TranslateTransform Changed="TranslateTransform_Changed"/>
</Grid.RenderTransform>
<Image x:Name="mainImageCtrl" RenderTransformOrigin="0.5, 0.5" Source="{Binding Image}">
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform Changed="ScaleTransform_Changed"/>
<RotateTransform />
</TransformGroup>
</Image.RenderTransform>
</Image>
</Grid>
</Border>
Please note that I separated TranslateTransform from ScaleTransform and RotateTransform so that the orientation won't be affected.
The following are the 2 changed events. In here, I expect to get the new position/size every time a scale or a translate has occurred. But the bounds values do not change.
Code-behind
private Rect bounds;
private void TranslateTransform_Changed(object sender, EventArgs e)
{
bounds = imageGridCtrl.TransformToAncestor(mainImageBorderCtrl).TransformBounds(new Rect(imageGridCtrl.RenderSize));
}
private void ScaleTransform_Changed(object sender, EventArgs e)
{
bounds = imageGridCtrl.TransformToAncestor(mainImageBorderCtrl).TransformBounds(new Rect(imageGridCtrl.RenderSize));
}
EDIT: My goal here is to restrict the translation(panning) of the Image inside the Border control, that's why I need to get the bounds of the Image after each transformation, whatever the transformation is, so that I can check whether the bounds of the Image exceeds the Border.
TranslateTransform is generating correct bounds. However, for ScaleTransform you are getting bounds on control imageGridCtrl but the transform is added as child of control mainImageCtrl.
So you should use mainImageCtrl in place of imageGridCtrl to get correct bounds:
bounds = mainImageCtrl.TransformToAncestor(mainImageBorderCtrl)
.TransformBounds(new Rect(mainImageCtrl.RenderSize));
The transform change events fire too early. You should hook up to LayoutUpdated event of the image control. Then try:
mainImageCtrl.TransformToAncestor(mainImageBorderCtrl)
.TransformBounds(new Rect(mainImageCtrl.RenderSize))

How do I use C# to pin a marker on a WPF image that can be resized

I have an image that is dynamically loaded into a WPF image control. (It's a map.)
When the user sets a coordinate (which is a saved waypoint from a GPS) I want to mark that location on the map from C#.
When the application window is resized, the mark should stay in the correct place on the map.
The map should scale with the window, but not stretch.
I've been digging around for days looking for ways to get this done, but can't get any further than loading the image into the window. The image is in a dockpanel, with some other controls down the left side of the window.
Things I know:
I have the coordinates (longitude and latitude) of the corners of the map. I have the longitude and latitude of the waypoint.
UI XAML:
<DockPanel>
<Grid x:Name="ImageGrid">
<Image Stretch="UniformToFill" x:Name="MapImage" Source="locimages/woorim.jpg" />
<Ellipse Name="Marker1" Height="20" Fill="Red" Margin="0,179,152,0" HorizontalAlignment="Right" VerticalAlignment="Top" Width="20"/>
</Grid>
</DockPanel>
Code (Not functional, just showing how the lat/long calcs are currently being done.):
if (wayPoint.Longitude < minLong || wayPoint.Longitude > maxLong || wayPoint.Latitude < minLat ||
wayPoint.Latitude > maxLat)
{
MessageBox.Show("This point is not in this area. It will not display on this map.");
return;
}
// Map location to image coordinates.
var newX = (wayPoint.Longitude - minLong)/(maxLong - minLong) * MapImage.Source.Width;
var newY = (wayPoint.Latitude - minLat) / (maxLat - minLat) * MapImage.Source.Height;
//Marker1.Margin = ??;

Zoom and Move for Canvas

What's the best way for implementing Zoom (possibly with Pinch) and Move (possibly with for Slide) for a Canvas?
I'm drawing some very simple stuff (e.g Lines, Ellipses and more) on a Canvas and now I want to allow the user the Zoom-in, Zoom-out and move the view-port freely around.
here you go. in the XAML code, wrap it with scroll viewer. like this
<ScrollViewer x:Name="scrl" ZoomMode="Enabled" HorizontalScrollMode="Enabled" VerticalScrollMode="Enabled" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible" SizeChanged="OnSizeChanged" MinZoomFactor="1">
<Canvas Background="AliceBlue" RenderTransformOrigin="0.5,0.5" x:Name="Main">
<Image Source="Assets/Floorplan.gif" Canvas.Left="358" Canvas.Top="84"></Image>
</Canvas>
</ScrollViewer>
then in my c#code you will put this.
private void OnSizeChanged(Object sender, SizeChangedEventArgs args) {
Main.Width = scrl.ViewportWidth;
Main.Height = scrl.ViewportHeight;
}
this will make your canvas zoom to pinch and pan enabled.
the image there is just a sample to see if the zoom function is working. It works fine.
the best way is creating a matrix for your canvas and scale and move the matrix like this :
Canvas can = new Canvas();
Matrix matrix = new Matrix();
matrix.Translate(50, 0);
matrix.Scale(1.5,1.5);
can.RenderTransform = new MatrixTransform(matrix);
Hope helps you

Animation of images producing unexpected, extra movement

I'm trying to create a function that moves images arranged in a radial layout around in a circle by swapping each one's position with their neighbor's position. The final effect is that the images are rotating around in a circle. The transform is activated when the S (counterclockwise) or D (clockwise) keys are pressed. I'm using an array to track the positions of the images and sending those coordinates to a function that actually does the transform.
The first rotation in either direction works fine. But any consecutive rotation in the same direction produces strange unwanted movement. In essence, with every new rotation the images all move inward towards the center of the circle before moving out again to take their final positions. The amount of inward motion gets worse with each key press.
Since I'm not allowed to attach an image to this email I have posted one here:
http://i1266.photobucket.com/albums/jj532/ik_al/screencap.jpg
The image shows a series of screenshots to illustrate the phenomenon. Please note that the screenshots are all happening on ONE rotation.
Here's my XAML file:
<Window x:Class="radialLayout.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:MyNamespace="clr-namespace:radialLayout"
Title="MainWindow" Height="350" Width="525" KeyUp="Window_KeyUp">
<Grid Width="1024" Height="768">
<MyNamespace:RadialPanel Margin="27,21,31,32" MouseWheel="RadialPanel_MouseWheel" x:Name="ImagePanel">
<!--Must use same namespace declared above-->
<!--Each image must have a unique name-->
<Image Height="49" Name="image1" Width="74" Source="/radialLayout;component/Images/profile.jpg" />
<Image Height="49" Name="image2" Width="74" Source="/radialLayout;component/Images/IMG_0841.JPG" />
<Image Height="49" Name="image3" Width="74" Source="/radialLayout;component/Images/profile.jpg" />
<Image Height="49" Name="image4" Width="74" Source="/radialLayout;component/Images/IMG_0841.JPG" />
<Image Height="49" Name="image5" Width="74" Source="/radialLayout;component/Images/profile.jpg" />
<Image Height="49" Name="image6" Width="74" Source="/radialLayout;component/Images/IMG_0863.JPG" />
<Image Height="49" Name="image7" Width="74" Source="/radialLayout;component/Images/profile.jpg" />
<Image Height="49" Name="image8" Width="74" Source="/radialLayout;component/Images/IMG_1043.JPG" />
<Image Height="49" Name="image9" Width="74" Source="/radialLayout;component/Images/profile.jpg" />
<Image Height="49" Name="image10" Width="74" Source="/radialLayout;component/Images/IMG_0863.JPG" />
<Image Height="49" Name="image11" Width="74" Source="/radialLayout;component/Images/profile.jpg" />
<Image Height="49" Name="image12" Width="74" Source="/radialLayout;component/Images/IMG_0863.JPG" />
</MyNamespace:RadialPanel>
And here is the function call and function implementation:
for (int o = 0; o < VisualTreeHelper.GetChildrenCount(ImagePanel); o++)
{
Visual childVisual = (Visual)VisualTreeHelper.GetChild(ImagePanel, o);
MyExtensions.MoveTo((Image)childVisual, lastPosition[o, 0], lastPosition[o, 1], ImagePanel.imageCoordinates[o, 0], ImagePanel.imageCoordinates[o, 1]);
}
public static void MoveTo(this Image target, double currentX, double currentY, double newX, double newY)
{
Vector offset = VisualTreeHelper.GetOffset(target);
var top = offset.Y;
var left = offset.X;
TranslateTransform trans = new TranslateTransform();
target.RenderTransform = trans;
DoubleAnimation anim1 = new DoubleAnimation(0, newY - top, TimeSpan.FromSeconds(1));
DoubleAnimation anim2 = new DoubleAnimation(0, newX - left, TimeSpan.FromSeconds(1));
trans.BeginAnimation(TranslateTransform.YProperty, anim1);
trans.BeginAnimation(TranslateTransform.XProperty, anim2);
}
Does anyone know what is causing this behavior or how to fix it?
After much investigation I finally figured out what I was doing wrong in my code.
I didn't understand that when using translateTranform the original position of the images is retained as the starting point for all consecutive transforms; I assumed it was updated to the last most current position. In addition, this starting point is always referenced by coordinates (0,0).
So to fix my animation problem I had to offset the start and stop positions of my images by subtracting each image's original position (before the first transform) from the current placement of the image on the screen (as stored in the array). For the first pass through these values will always add up to 0.
I was already completing this offset for the stop positions since it was necessary to get even the first transform to work. But as it turned out, this subtraction was needed to update the start positions as well.
In the code the original position of the image is stored in the left and top variables which reference the X and Y coordinates respectively.
Here's the part of the code that I changed:
DoubleAnimation anim1 = new DoubleAnimation(currentY-top, newY - top, TimeSpan.FromSeconds(1));
DoubleAnimation anim2 = new DoubleAnimation(currentX-left, newX - left, TimeSpan.FromSeconds(1));
What was interesting about this error was that the resulting animation when all 12 images were transformed together was much more complex and interesting than it would have been if each image was moved individually. So there must be some interaction between how the image transforms are computed that produces emergent output. The output I was seeing made me think the solution was much more complex than it actually was.

Categories

Resources