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
Related
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.
In a page I display a picture that I receive from CameraCapureTask in the View
<Grid x:Name="EditPageGrid" Margin="{Binding}">
<Grid Name="ViewportContainer" Margin="12,0,12,24">
<Image x:Name="Viewport" LayoutUpdated="Viewport_LayoutUpdated"
Source="{Binding}"/>
</Grid>
</Grid>
And I wish to be able to place a border around this image. How would it be possible to do this? I was thinking perhaps on a click event of some sort a border could be toggled on or off, but actually applying a border is where I am at a loss.
You can contain the image in a Border, like this:
<Grid x:Name="EditPageGrid" Margin="{Binding}">
<Grid Name="ViewportContainer" Margin="12,0,12,24">
<Border HorizontalAlignment="Center" BorderThickness="4" BorderBrush="Red">
<Image Source="C:\Users\Public\Pictures\Sample Pictures\Jellyfish.jpg"/>
</Border>
</Grid>
</Grid>
I figured out such an event: (probably there is better method, but this also works)
private void Viewport_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
int imageHeight = (Viewport.Source as BitmapImage).PixelHeight;
int imageWidth = (Viewport.Source as BitmapImage).PixelWidth;
Canvas myCanvas = new Canvas();
Rectangle myBorder = new Rectangle();
myBorder.Width = imageWidth;
myBorder.Height = imageHeight;
myBorder.Stroke = new SolidColorBrush(Colors.Red);
myBorder.StrokeThickness = 10;
Image toBorder = new Image();
toBorder.Source = Viewport.Source as BitmapImage;
myCanvas.Children.Add(toBorder);
myCanvas.Children.Add(myBorder);
WriteableBitmap newImage = new WriteableBitmap(myCanvas, null);
//Viewport.Source = newImage; - you can use this but watch out that Viewport.Source now is not BitmapImage
//Below is one method how to make it BitmapImage
//You can of course save newImage to file or whatever you want
//You can also unsubscribe this event to prevent it from second tap which will cause Exception at first line (BitmaImage != WriteableBitmap)
MemoryStream memoryStream = new MemoryStream();
newImage.SaveJpeg(memoryStream, imageWidth, imageHeight, 0, 100);
BitmapImage newBitmap = new BitmapImage();
newBitmap.SetSource(memoryStream);
Viewport.Source = newBitmap;
}
Playing with this memory stream isn't good, but I've not known what you are planning to do with your new Bitmap.
As I've said - it's only example and I'm sure better methods exist (which I don't know). Hope this helps.
I am using DoubleAnimation for zooming and panning in and out of map. My map is an image with huge resolution (15,000 x 8,438). The problem is that on first time the zoom animation is very faltering and not smooth, at second time it`s getting better and so on. How can I make my animation smoother or make some cashing of the image or animation before performing it, or maybe using other form of animation?
My Code:
namespace AnimationTest
{
public partial class MainWindow : Window
{
ScaleTransform transP;
TranslateTransform trans2P;
DoubleAnimation animP;
DoubleAnimation animYP;
DoubleAnimation animXP;
TransformGroup myTransformGroupP;
public MainWindow()
{
InitializeComponent();
transP = new ScaleTransform();
trans2P = new TranslateTransform();
myTransformGroupP = new TransformGroup();
myTransformGroupP.Children.Add(transP);
myTransformGroupP.Children.Add(trans2P);
animP = new DoubleAnimation(1, 20, TimeSpan.FromMilliseconds(3000));
animXP = new DoubleAnimation(0, -14000, TimeSpan.FromMilliseconds(3000));
animYP = new DoubleAnimation(0, -4000, TimeSpan.FromMilliseconds(3000));
}
private void button1_Click(object sender, RoutedEventArgs e)
{
image1.RenderTransform = myTransformGroupP;
transP.BeginAnimation(ScaleTransform.ScaleXProperty, animP);
transP.BeginAnimation(ScaleTransform.ScaleYProperty, animP);
trans2P.BeginAnimation(TranslateTransform.XProperty, animXP);
trans2P.BeginAnimation(TranslateTransform.YProperty, animYP);
}
}
}
I have not tried your animation approach, i tried to implement my own logic to to this.
First i am inspired by zooming animation used by Picasa. So i tried to implement similar type of animation and this works fine for me on my core2duo processor with image size of 10000x5000 without any lag.
This approach consumed a lot of memory, but when i compared my memory usage with Picasa ImageViewer it was almost same. This approach may increase the loading time of your application but this can be handled and not a problem here.
Here is the Code for Main Window Grid that i have Used.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Grid.Row="0" Height="30" Width="100" Content="Zoom" Click="ButtonZoom_OnClick" />
<Image RenderOptions.BitmapScalingMode="HighQuality" Stretch="Uniform" Width="100" Height="100" Grid.Row="1"
Margin="30" VerticalAlignment="Center" HorizontalAlignment="Center" Source="mad1.jpg" Name="ImageMain"
x:FieldModifier="private" />
</Grid>
Button Click event Code
private void ButtonZoom_OnClick(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
var i = 0;
while (i++ < 100)
{
var i1 = i;
//var i1 = (-0.00092)*(i*i) + (0.092)*i + 0.2;
Dispatcher.Invoke(new Action(() =>
{
if (i1 < 10 || i1 > 90)
{
ImageMain.Height += 0.5;
ImageMain.Width += 0.5;
}
else if (i1 < 30 || i1 > 70)
{
ImageMain.Height += 1;
ImageMain.Width += 1;
}
else
{
ImageMain.Height += 3;
ImageMain.Width += 3;
}
}));
Thread.Sleep(30);
}
});
}
The commented line in this code is a quadratic equation for a smooth animation for acceleration and acceleration of animation. the roots are calculated for starting zooming by 0.2 and half at 2.5 and stops at 0.2 with in range of [0-100]. if you want to create your fully customized animation you may use WolframAlpha to check your animation graph. but the simple approach is to use simple control statements to control your animation.
This code is only for zooming your image, your approach will be similar for zoom out.
Have you looked into Microsoft's DeepZoom technology (this is what they use for Bing Maps)? http://msdn.microsoft.com/en-us/library/cc645050(v=vs.95).aspx#deep_zoom_examples
Since you have not shown any XAML I'll try from the most basic - try to reduce bitmap scaling mode with RenderOptions.BitmapScalingMode="LowQuality" on your image element like this:
<Image x:Name="image1"
Source="huge-image.jpg"
Stretch="Uniform"
RenderOptions.BitmapScalingMode="LowQuality" />
Note, that this is only actual if you targeting .NET 3.0-3.5 since starting from .NET 4.0 the "LowQuality" setting is already set as default, so you have no need to specify it explicitly. But if your zoom-in animation still faltering you could try to change this default scaling from LowQuality to even more lower NearestNeighbor which, according to documentation:
...provides performance benefits over LowQuality mode when the software rasterizer is used. This mode is often used to magnify a bitmap.
Also since you are about to show large image with some loss of quality it may be better to specify UseLayoutRounding="True" on your image or it parent element to improve image quality.
You want to use a cached composition. Render your map but assign a BitmapCache to the CacheMode property and set the RenderAtScale to a value larger than 1. If you zoom into your map 5x you should use the RenderAtScale with this value as it caches the image for this type of zoom.
This may result in a much higher memory consumption but may smooth the scrolling.
Further more Nolonar may be right. You may need to create mipmaps for the image and provide tile rendering to partially load seen tiles as your image is quite large.
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>