Im creating UWP app which displays collection of MapIcons on map. MapIcon images are dynamically created from XAML and rendered to bitmap. MapIcon's are updated every few second. There can be around 200 MapIcon on map.
The problem is that my MapIcon update code gets somehow out of sync, MapIcon images are not correct for spesific MapIcon/plane. E.g. some planes get previous plane image etc.
Im storing my MapIcons to separate _planesDictionary (Dictionary<string, MapIcon>), the idea is to speed up the update process as I hope it is more faster to get map icon from dictionary than from mapControl MapElements list?
My code:
// This is called every few seconds with new planes collection
public async void AddAndUpdatePlanes(IReadOnlyCollection<IRealtimePlane> planes)
{
foreach (var plane in planes)
{
MapIcon foundMapIcon = null;
if(!_planesDictionary.TryGetValue(plane.PlaneId, out foundMapIcon))
{
var planeIcon = new MapIcon
{
Location = new Geopoint(plane.Location),
NormalizedAnchorPoint = new Point(0.5, 0.5),
ZIndex = 10,
CollisionBehaviorDesired = MapElementCollisionBehavior.RemainVisible,
Image = await GetMapIconImage(GetPlaneMarContainer(plane))
};
MapControl.MapElements.Add(planeIcon);
_planesDictionary.Add(plane.PlaneId, planeIcon);
}
else if(foundMapIcon != null)
{
// Update all field to get all markers updated?
foundMapIcon.Location = new Geopoint(plane.Location);
foundMapIcon.NormalizedAnchorPoint = new Point(0.5, 0.5);
foundMapIcon.ZIndex = 10;
foundMapIcon.CollisionBehaviorDesired = MapElementCollisionBehavior.RemainVisible;
foundMapIcon.Image = await GetMapIconImage(GetPlaneMarContainer(plane));
}
}
}
// Get XAML element (grid) which is used to render MapIcon image
private Grid GetPlaneMarContainer(IRealtimePlane plane)
{
PlaneImage.RenderTransformOrigin = new Point(0.5, 0.5);
PlaneImage.RenderTransform = new RotateTransform
{
Angle = plane.Angle
};
PlaneImageText.Text = plane.NumberCode;
return PlaneMarkerContainer;
}
// Convert UIElement MapIcon image source
public async Task<IRandomAccessStreamReference> GetMapIconImage(UIElement uielement)
{
var rtb = new RenderTargetBitmap();
await rtb.RenderAsync(uielement);
var dpi = Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi;
var pixels = (await rtb.GetPixelsAsync()).ToArray();
var stream = new InMemoryRandomAccessStream();
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.BmpEncoderId, stream);
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Straight, (uint)rtb.PixelWidth, (uint)rtb.PixelHeight, dpi, dpi, pixels);
await encoder.FlushAsync();
stream.Seek(0);
return RandomAccessStreamReference.CreateFromStream(stream.AsStream().AsRandomAccessStream());
}
This is the XAML which is used to render MapIcon images (this is behind the MapControl).
<Grid x:Name="PlaneMarkerContainer" Width="36" Height="36" Background="Transparent">
<Image x:Name="PlaneImage" Width="36" Height="36" VerticalAlignment="Center" HorizontalAlignment="Center" Source="ms-appx:///Assets/Icons/Plane.png" />
<TextBlock x:Name="PlaneImageText" Text="" FontSize="10" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
More finding:
With my PC everything works fine if I have less than 100 MapIcons. With Lumia 950XL, even 50 MapIcon updates is too much. MapIcons (planes) gets wrong images.
UPDATE
This code works completely fine. The issue was in my GetPlaneMarContainer() which had small functionality to pic correct grid. This code is not shown in my example. Somehow I managed to mess up this functionality and it lead my code to not work correctly.
Related
To learn about animation and UI, I'm making a basic WPF/C# application where a user selects the number of vehicles to display then those vehicles (ie images of different vehicles) appear in the canvas and move around.
The WPF is very simple:
<Grid>
<Canvas x:Name="MenuTabCanvas" Visibility="Visible">
<Label x:Name="AnimateDemo" Content="Animate!" HorizontalAlignment="Left" VerticalAlignment="Top" Width="104" Background="#25A0DA" Foreground="White" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Cursor="Hand" MouseDown="AnimateGrid" Canvas.Left="640" Canvas.Top="87"/>
<Canvas x:Name="AnimationCanvas" Canvas.Top="150" Canvas.Left="224" Width="814" Height="489">
</Canvas>
</Grid>
I started with a fade animation using the method described in this post and it works fine. Then I tried the translatetransform as shown below leaving the fade code in comments, and the images appear but nothing moves:
private void AnimateGrid(object sender, EventArgs e)
{
int NumberOfVehicles = 5;
var sb = new Storyboard();
for (int i = 0; i < NumberOfVehicles; i++)
{
//create & add the images to our target canvas
Image Img = getRandomVehicleImage(); //returns image of vehicle
AnimationCanvas.Children.Add(Img);
Canvas.SetTop(Img, 30 + 60 * i); //position image w/in canvas
Canvas.SetLeft(Img, 30 + 80 * i);
//add an animation
DoubleAnimation myAnimation = new DoubleAnimation()
{
// From = 0,
To = 150,
Duration = TimeSpan.FromSeconds(2),
};
Storyboard.SetTarget(myAnimation, Img);
// Storyboard.SetTargetProperty(myAnimation, new PropertyPath(Button.OpacityProperty));
Storyboard.SetTargetProperty(myAnimation, new PropertyPath(TranslateTransform.XProperty));
sb.Children.Add(myAnimation);
}
sb.Begin();
}
I have been able to get it to work with TranslateTransform.BeginAnimation but I would prefer to use the storyboard here.
Why does the translate transform behave differently than the opacity animation and what do I need to do to get it to function as intended?
By default, there is no TranslateTransform applied to a UIElement. So if you want to move an Image, you first have to set its RenderTransform property to a TranslateTransform, and then set the TargetProperty to the correct property path:
Img.RenderTransform = new TranslateTransform();
...
Storyboard.SetTargetProperty(myAnimation, new PropertyPath("RenderTransform.X"));
Alternatively, you could directly animate the TranslateTransform, even without a Storyboard:
var transform = new TranslateTransform();
Img.RenderTransform = transform;
transform.BeginAnimation(TranslateTransform.XProperty, myAnimation);
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
So, how to make such effect using C# & XAML?
I've been trying to create something like this ever since iOS 7 introduced the frosted glass. Now thanks to Win2D, it's a lot simpler to create similar effects in WinRT.
First, you will need to get the Win2D.uwp package from NuGet.
The idea is to create a GaussianBlurEffect based on the image source and put it on top of another white colored mask to mimic the frosted glass look.
To get the GaussianBlurEffect ready, you will need to create a xaml control CanvasControl from the Win2D library. So the UI structure is something like this -
<Grid x:Name="ImagePanel2" Width="356" Height="200" Margin="0,0,0,40" VerticalAlignment="Bottom">
<Image x:Name="Image2" Source="Assets/Food.jpg" Stretch="UniformToFill" />
<Grid x:Name="Overlay" ManipulationMode="TranslateX" ManipulationStarted="Overlay_ManipulationStarted" ManipulationDelta="Overlay_ManipulationDelta" ManipulationCompleted="Overlay_ManipulationCompleted" RenderTransformOrigin="0.5,0.5">
<Grid.Clip>
<RectangleGeometry x:Name="Clip" Rect="0, 0, 356, 200" />
</Grid.Clip>
<Rectangle x:Name="WhiteMask" Fill="White" />
<Xaml:CanvasControl x:Name="Canvas" CreateResources="Canvas_CreateResources" Draw="Canvas_Draw" />
</Grid>
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="Frosted Glass" VerticalAlignment="Top" Foreground="#FF595959" Margin="12,12,0,0" FontWeight="Light" FontSize="26.667" FontStyle="Italic" TextLineBounds="Tight" />
</Grid>
Note I've created a Clip for the Overlay element, 'cause I need to reduce the Rect's third parameter (i.e. the Width) while I am panning it to the left to make an illusion that the Overlay is sliding along with my finger.
The code behind is quite straight forward -
void Canvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEventArgs args)
{
args.TrackAsyncAction(CreateResourcesAsync(sender).AsAsyncAction());
}
async Task CreateResourcesAsync(CanvasControl sender)
{
// give it a little bit delay to ensure the image is load, ideally you want to Image.ImageOpened event instead
await Task.Delay(200);
using (var stream = new InMemoryRandomAccessStream())
{
// get the stream from the background image
var target = new RenderTargetBitmap();
await target.RenderAsync(this.Image2);
var pixelBuffer = await target.GetPixelsAsync();
var pixels = pixelBuffer.ToArray();
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.BmpEncoderId, stream);
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Straight, (uint)target.PixelWidth, (uint)target.PixelHeight, 96, 96, pixels);
await encoder.FlushAsync();
stream.Seek(0);
// load the stream into our bitmap
_bitmap = await CanvasBitmap.LoadAsync(sender, stream);
}
}
void Canvas_Draw(CanvasControl sender, CanvasDrawEventArgs args)
{
using (var session = args.DrawingSession)
{
var blur = new GaussianBlurEffect
{
BlurAmount = 50.0f, // increase this to make it more blurry or vise versa.
//Optimization = EffectOptimization.Balanced, // default value
//BorderMode = EffectBorderMode.Soft // default value
Source = _bitmap
};
session.DrawImage(blur, new Rect(0, 0, sender.ActualWidth, sender.ActualHeight),
new Rect(0, 0, _bitmap.SizeInPixels.Width, _bitmap.SizeInPixels.Height), 0.9f);
}
}
void Overlay_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
{
// reset the inital with of the Rect
_x = (float)this.ImagePanel2.ActualWidth;
}
void Overlay_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
// get the movement on X axis
_x += (float)e.Delta.Translation.X;
// keep the pan within the bountry
if (_x > this.ImagePanel2.ActualWidth || _x < 0) return;
// we clip the overlay to reveal the actual image underneath
this.Clip.Rect = new Rect(0, 0, _x, this.ImagePanel2.ActualHeight);
}
void Overlay_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
{
// reset the clip to show the full overlay
this.Clip.Rect = new Rect(0, 0, this.ImagePanel2.ActualWidth, this.ImagePanel2.ActualHeight);
}
You can further adjust the BlurAmount property as well as the opacity figure (0.9f) to get youself the exact effect you want.
Also to note that there might be another (better?) way to do this in the future. If the new Composition API does support the GaussianBlurEffect in a future release, I will update the answer.
You can find the current working sample from this GitHub repo. And I've attached an image here to showcase how it looks like. :)
You should look into the Win2D classes and in there for the Canvas Effects - there is a GaussianBlurEffect that might be helpful. Here's the reference:
http://microsoft.github.io/Win2D/html/N_Microsoft_Graphics_Canvas_Effects.htm
and for the GaussianBlurEffect:
http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_GaussianBlurEffect.htm
including some C# code samples.
Addition: if you want to know how to use win2D, I just found a handy tutorial (which I am following myself right now :)) http://blogs.msdn.com/b/uk_faculty_connection/archive/2014/09/05/win2d.aspx
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.
My requirements:
a persistent UserControl that handles logic for a custom image, such as a map or drawing
a set of containers to implement caching on the image during zoom or pan movements
VisualBrush copies of the UserControl that I can add to the containers for use with Effects
I currently implement image caching with a RenderTargetBitmap, but that seems to have trouble with the VisualBrush-covered Rectangle objects I'm using.
My question: What can I add/change in this code to get the VisualBrush objects to render correctly after RenderTargetBitmap uses them? What strange thing is RenderTargetBitmap doing that makes the VisualBrush invisible?
This is a problem that I have been unable to reproduce without a decent amount of code.
In my xaml file I have:
<Window x:Class="ElementRender.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="350" Width="525">
<Grid>
<Grid Name="_contentContainer">
<Rectangle Fill="White"/>
<Grid Name="_content">
<Grid Name="_back"/>
<Grid Name="_body"/>
</Grid>
</Grid>
<StackPanel VerticalAlignment="Bottom" Orientation="Horizontal">
<Button Content="New" Name="New"/>
<Button Content="Move" Name="Move"/>
<Button Content="Update" Name="Update"/>
</StackPanel>
</Grid>
</Window>
and the .xaml.cs:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
public partial class MainWindow : Window
{
private const int imageWidth = 150;
private const int imageHeight = 150;
private readonly UserControl Control;
public MainWindow()
{
InitializeComponent();
// User Control setup
Control = new UserControl() {
Width = imageWidth, Height = imageHeight,
Content = BuildImage()
};
_body.Children.Add(SoftCopy(Control));
// event setup
Move.Click += (sender, e) => _content.RenderTransform = new TranslateTransform(50, 50);
New.Click += (sender, e) => {
HardCopy();
_content.RenderTransform = null;
Control.Content = BuildImage();
};
}
private FrameworkElement BuildImage()
{
return new Rectangle{Fill=Brushes.Blue};
}
private void HardCopy()
{
int width = (int) _contentContainer.ActualWidth;
int height = (int) _contentContainer.ActualHeight;
// render the current image
var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
DrawingVisual dv = new DrawingVisual();
using (var context = dv.RenderOpen())
{
var brush = new VisualBrush(_contentContainer) { Opacity = .5 };
context.DrawRectangle(brush, null, new Rect(0, 0, width, height));
}
rtb.Render(dv);
var lastRender = new Image
{
Source = rtb,
Stretch = Stretch.None,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Width = width,
Height = height
};
_back.Children.Clear();
_back.Children.Add(lastRender);
}
private FrameworkElement SoftCopy(FrameworkElement element)
{
return new Rectangle{Fill= new VisualBrush(element), Width=element.Width, Height=element.Height};
}
}
A few helping notes about the code:
the xaml's _contentContainer works with HardCopy() to copy the current images into the image cache, _back.
SoftCopy returns a FrameworkElement that looks exactly like the one past in, but without any transforms, effects, or visual parents. This is very important.
BuildImage simulates building a new image to be pasted over the cache after the initial image has been transformed somehow.
If you build and run the application removing the SoftCopy() from the _body.Children.Add(SoftCopy(Control));, you see the effect that I want to get: the new element is pasted above the old element, and the old element seems to retain its transform.
Alternatively, if you cut out the line var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32); from HardCopy, the caching function is broken, but the SoftCopy is displayed correctly.
However, if you run the application as-is, you notice that the new BlueRectangle (as rendered through a VisualBrush) doesn't display at all, until you hit the "New" button again, pushing the image to the cache, and still not showing you the new created image.
I'm going to be pompous enough to call this a bug in WPF. I eventually found out how to fix the strange behavior I was getting:
var visual = visualBrush.Visual;
visualBrush.Visual = null;
visualBrush.Visual = visual;
This should essentially be a null operation: by the end, the visual brush has the same visual as when it started. However, adding this code segment after rendering the VisualBrush into the RenderTargetBitmap fixed the issue I was having.
I didn't quite understand the post but there are few important things:
If you apply RenderTransform/Margins to element and take picture of it(RenderTargetBItmap), you're gonna have bad time. It will be offseted and you will get only sub-picture.
The idea is to take picture without any rendertransforms, and then later copy RenderTransform over from the old one. If needed.
I have an image and i want to render the control(datgrid or any ui element with its content ) on that image and output the whole thing as an image.
Please Help.
I found some similar link in so, just taking a look at his answer but not helpful..
Silverlight: Create image from silverlight controls
Thanks in advance. :)
I used the following method I found somewhere to create an image from a frameworkelement (which is basically a grid, canvas, button, textbox, ...
it takes whatever it can find inside the bounds of the control and returns it as an ImageSource type, from there I think the road to saving it to a file or outputting it to the screen is a small step.
take note: I removed some code which should work out margin issues, so be sure to take that into account, or do not set a margin for the control you wish to convert to an image.
what you basically want to do now is use the GridCombiner and give that to the method below, so it will create an image from the DataGridMyData over the ImageBackground as an ImageSource.
Hopefully this is what you were looking for, if not, let me know.
public ImageSource ToImageSource(FrameworkElement obj) // FOR WPF
{
// Save current canvas transform
Transform transform = obj.LayoutTransform;
obj.LayoutTransform = null;
// Get the size of canvas
Size size = new Size(obj.Width, obj.Height);
// force control to Update
obj.Measure(size);
obj.Arrange(new Rect(size));
RenderTargetBitmap bmp = new RenderTargetBitmap(
(int)obj.Width, (int)obj.Height, 96, 96, PixelFormats.Pbgra32);
bmp.Render(obj);
// return values as they were before
obj.LayoutTransform = transform;
return bmp;
}
public ImageSource ToImageSource(FrameworkElement obj) // FOR SILVERLIGHT
{
// Save current canvas transform
Transform transform = obj.RenderTransform;
obj.RenderTransform = null;
// Get the size of canvas
Size size = new Size(obj.Width, obj.Height);
// force control to Update
obj.Measure(size);
obj.Arrange(new Rect(new Point(), size));
WriteableBitmap bmp = new WriteableBitmap(obj, transform);
bmp.Render(obj, transform);
// return values as they were before
obj.RenderTransform = transform;
return bmp;
}
And your xaml would be something like:
<Grid x:Name="GridCombiner" Width="300" Height="150">
<Image x:Name="ImageBackground" Source="c:/myimg.jpg" Width="300" Height="150" />
<DataGrid x:Name="DataGridMyData" ItemsSource="{Binding}" Width="300" Height="150" />
</Grid>