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.
Related
Many Windows applications support two fingered touchpad gestures. You can scroll by moving two fingers horizontally or vertically, and you can zoom by changing the distance between the fingers.
I'm trying replicate this behaviour in a WinUI 3 canvas.
According to the Microsoft documentation "The touchpad does not raise manipulation events. Instead, pointer events will be raised for touchpad input."
Listening for the PointerWheelChanged event handler, I'm able to detect two finger scrolls with the following code
private void OnPointerWheelChanged(object sender, PointerRoutedEventArgs e)
{
var pointer = e.GetCurrentPoint(myCanvas);
var isHorizontalScroll = pointer.Properties.IsHorizontalMouseWheel;
var scrollDelta = pointer.Properties.MouseWheelDelta;
// Perform the scrolling
e.Handled = true;
}
But, I'm unable to get the fingers position so I can calculate the zoom delta. Is there some lower API I can use that will give me access to the individual fingers? Or some other way to capture the zoom gesture?
I have created a little sample for you, which handles zoom using the trackpad:
MainWindow.xaml:
<Canvas x:Name="myCanvas" PointerWheelChanged="Canvas_PointerWheelChanged" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Green">
<Rectangle Width="40" Height="40" Fill="Red" Canvas.Left="0" Canvas.Top="0" Canvas.ZIndex="0" />
</Canvas>
MainWindow.xaml.cs:
private void Canvas_PointerWheelChanged(object sender, PointerRoutedEventArgs e)
{
var ctrl = Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Control
if (ctrl.HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down))
{
var delta = e.GetCurrentPoint(myCanvas).Properties.MouseWheelDelta;
//Here you can handle your zooming
//My sample just resizes the rectangle
foreach (UIElement children in myCanvas.Children)
{
if (children is Rectangle rect)
{
double newSize = rect.Height + delta;
if (newSize < 0)
newSize = 0;
rect.Height = rect.Width = newSize;
}
}
}
}
The trick is, to check for the control-key press in the PointerWheelChanged-event, because the touchpad behaves the same like when you zoom using control + Mousewheel
I am trying to use some animations to make my application feel good. But I could not help the choppy animation that no matter what I do, it is always end up stuttering.
Take a look:
DoubleAnimation anim = new DoubleAnimation()
{
//ht is height of DockPanel, I wanted to start from 200 less than Actual DockPanel Height
From = ht - 200,
To = ht,
Duration = TimeSpan.FromSeconds(1),
AccelerationRatio = 0.5,
DecelerationRatio = 0.5
};
x.BeginAnimation(HeightProperty, anim);
//x is the UserControl
Also, what I need to animate is a custom UserControl, which contains some text like 100 words and bunch of Images. I just want to make it grow in to the height of the current DockPanel as soon it is loaded.
What I saw by searching for the solution is this,
Timeline.SetDesiredFrameRate(anim, 10);
Even trying any value in there nothing really happens.
Framerate is like the frame rate of a film.
A low frame rate will give you a choppy film or a choppy animation.
Using a dockpanel is probably a bad idea for some content you are going to animate, because it will try and adjust things every time your height changes.
I suggest you go with a grid instead.
You should use a scaletransform. Partly because as you animate height you will find all the content of your usercontrol have their measures invalidated and they will want to start off the whole measure arrange cycle many times.
If you're thinking measure arrange? Then read up on how the wpf layout system works.
I would also advise you to use xaml rather than code.
Here's some code to think about:
private void StartAnimate_Click(object sender, RoutedEventArgs e)
{
var tran = testRectangle.RenderTransform = new ScaleTransform(1d, 1d)
{
CenterX = 0.5d,
CenterY = 0.5d
};
var anim = new DoubleAnimation
{
To = 1.0,
From=0,
Duration = TimeSpan.FromSeconds(0.5d),
DecelerationRatio = 0.5d,
FillBehavior = FillBehavior.Stop
};
tran.BeginAnimation(
ScaleTransform.ScaleYProperty,
anim,
HandoffBehavior.Compose);
}
I put a rectangle and my button in a dockpanel to prove this works.
<DockPanel>
<Rectangle Fill="Blue" Name="testRectangle"
Width="500"
/>
<Button Content="Animate"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Name="StartAnimate"
Click="StartAnimate_Click" />
</DockPanel>
A rectangle is pretty simple but it animates smoothly.
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 an image in a scrollviewer.The image has Pinch in and out feature implemented on it.
But while scrolling the zoomed image,the aspect ratio changes and images becomes distorted.
Following the xaml:
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Name="scroller" >
<Image Name="image_new" Visibility="Visible" CacheMode="BitmapCache" >
<Image.RenderTransform >
<CompositeTransform x:Name="transform"/>
</Image.RenderTransform >
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener Flick="OnFlick" PinchStarted="OnPinchStarted" PinchDelta="OnPinchDelta" DoubleTap="Onimage_doubletap" Tap="Onimage_singletap" />
</toolkit:GestureService.GestureListener>
</Image>
</ScrollViewer>
And in the .cs file the methods are :
private void OnPinchStarted(object sender, PinchStartedGestureEventArgs e)
{
Point point0 = e.GetPosition(image_new, 0);
Point point1 = e.GetPosition(image_new, 1);
Point midpoint = new Point((point0.X + point1.X) / 2, (point0.Y + point1.Y) / 2);
image_new.RenderTransformOrigin = new Point(midpoint.X / image_new.ActualWidth, midpoint.Y / image_new.ActualHeight);
initialScale = transform.ScaleX;
}
private void OnPinchDelta(object sender, PinchGestureEventArgs e)
{
transform.ScaleX = Math.Max(Math.Min(initialScale * e.DistanceRatio, 3.0), 0.5);
transform.ScaleY = Math.Max(Math.Min(initialScale * e.DistanceRatio, 3.0), 0.5);
}
I think the problem here is that you are changing the RenderTransformOrigin for each pinch gesture, which is resulting in the distortion. I would try leaving the RenderTransformOrigin fixed at 0.5,0.5 to ensure that you get an even scale.
I assume you were moving the origin to try to zoom into/out of the part of the image that the user had started the gesture on. To achieve this, I think you will need to enable the user to pan around the image once zoomed in.
One other point, the scale factor is always the same, so you shoudl just calculate it once, and then assign it to both ScaleX and ScaleY.
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);
}