I was following along this tutorial about how to include Pixel Shader Effects in the form of an animation within a WPF application.
Background
I felt like things were going smoothly, however I wanted to make a change to the application. In the tutorial the author has a separate GrayscaleEffect project and in his XAML does the following:
xmlns:effect="clr-namespace:GrayscaleEffect;assembly=GrayscaleEffect"
Later he has this:
DataTemplate x:Key="itemTemplate">
<Grid Width="225" Margin="3">
<Border BorderBrush="White" BorderThickness="2">
<Image Source="{Binding}" HorizontalAlignment="Center" VerticalAlignment="Center">
<Image.Effect>
<local:GrayscaleEffectTest x:Name="grayscaleEffect"/>
</Image.Effect>
<Image.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<BeginStoryboard>
<Storyboard>
<!--HERE--> <DoubleAnimation To="1.0" Duration="0:0:0.3" AccelerationRatio="0.10" DecelerationRatio="0.25" Storyboard.TargetName="grayscaleEffect" Storyboard.TargetProperty="(local:GrayscaleEffectTest.DesaturationFactor)" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="0.0" Duration="0:0:4" AccelerationRatio="0.10" DecelerationRatio="0.25" Storyboard.TargetName="grayscaleEffect" Storyboard.TargetProperty="(local:GrayscaleEffectTest.DesaturationFactor)" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Image.Triggers>
</Image>
</Border>
</Grid>
</DataTemplate>
The key point here is the very long line <DoubleAnimation ....:
Storyboard.TargetProperty="(effect:GrayscaleEffect.DesaturationFactor)" />
My Approach
I wanted to make the same thing, except I wanted to keep all of my code in the same project, rather than having two projects.
So, I don't include the ;assembly=GrayscaleEffect. Furthermore my <DoubleAnimation ... line reads as:
<DoubleAnimation ... Storyboard.TargetName="grayscaleEffect" Storyboard.TargetProperty="(local:GrayscaleEffectTest.DesaturationFactor)" />
However the WPF designer throws an initializer exception. The program runs but no animation ever gets triggered when I mouse over...
Anyone have any ideas? I feel confident that WPF should be able to run a pixel shader from within the same project...My project has the pre-build event, and other than renaming as GrayscaleEffect Test and having both projects combined into one, my project should be identical to the tutorial. I've tried a number of other failure approaches, mainly setting Storyboard.TargetProeprty = every damn thing under the sun. Also tried to hack together something to set up the animation in code behind in hopes I could at least walk through with a debugger and try to see what is going on. Obviously, nothing worked :(.
Any help would be greatly appreciated. Cheers.
Slightly embarrassing but the error was between chair and keyboard. So, the code was working just fine. To maybe help others I'll explain my debugging process, then explain what the problem was.
First I changed the pixel shader to just go from Red to Red and Green. That is:
result.r = 255;
result.g = 0;
result.b = 255 * factor;
Factor is the data variable that gets sent to the graphics card. Once I did this, I noticed that things were kind of working. The transition is a step-function, not a linear interpolation, but colors were changing. Then, I figured pixel color range must be [0 to 1.0]. This gave me the effect I was supposed to be getting.
result.r = 1.0;
result.g = 0;
result.b = 1.0 * factor;
Lastly, I loaded a new penguin picture instead of the original test image I was using and I realized my major mistake. The penguin picture was working just fine and I thought, what the hell?! Then I looked at my test image, which was a black and white image!!! (Dumb, dumb dumb dumb). The shame of having to post this "solution" will hopefully prevent me from making such a mistake again. Thank you for the help #Nico Schertler.
Related
I have several instances of the following xaml code. Essentially it's a bunch of grids which show up in a stackpanel in a small (24 pixel tall) state, showing one line of the textblock. When you click on the arrow image (or rather the border around the image as there's transparency in the image) then the grid expands to show all the details within it. I have 15 of these in total:
<Grid x:Name="borLecSec1" Style="{StaticResource SearchedSectionGrid}">
<TextBlock x:Name="txtLecSec1" Style="{StaticResource SearchedSectionText}"
PointerEntered="SearchSectionEntered" PointerExited="SearchSectionExited"
Tapped="SearchSectionTapped"/>
<Border x:Name="backArrowSection_Lec_1" Style="{StaticResource ExpandSectionButton}"
PointerEntered="backArrowSectionEnter" PointerExited="backArrowSectionExit"
Tapped="backArrowSectionTapped">
<Image x:Name="arrowSection_Lec_1" Style="{StaticResource ExpandSectionImage}">
<Image.RenderTransform>
<CompositeTransform ScaleY="1"/>
</Image.RenderTransform>
</Image>
</Border>
</Grid>
I haven't figured out how to animate the grids themselves expanding yet as they would have to go from 24 pixels to Auto which I haven't gotten to work. What I have gotten to work is that when clicked, this arrow flips vertically so that it now signifies that another click will cause the grid to collapse down. The storyboard for this animation is this:
<Storyboard x:Name="SearchSectionArrowExpand">
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)"
From="1" To="-1" Duration="00:00:0.15"/>
</Storyboard>
<Storyboard x:Name="SearchSectionArrowCollapse">
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)"
From="-1" To="1" Duration="00:00:0.15"/>
</Storyboard>
As there are many instances of this arrow image control, the target property of the storyboard is changed before every Begin() statement in the C# code behind. The code I have follows. In it, the working_grid and working_image objects correspond to borLecSec1 and arrowSection_Lec_1 respectively in the xaml above.
if (working_grid.Height == 24)
{
System.Diagnostics.Debug.WriteLine("expand");
working_grid.Height = double.NaN;
SearchSectionArrowCollapse.Stop();
SearchSectionArrowExpand.Stop();
Storyboard.SetTargetName(SearchSectionArrowExpand, working_image_name);
SearchSectionArrowExpand.Begin();
}
The collapse part of the code is very similar in the subsequent else. The Stop() commands are necessary as I get an error if they're not there saying the root storyboard must be stopped before re-targeting. So everything I've said works fine. What doesn't work is that if I expand the first grid, so arrowSection_Lec_1 has a ScaleY of -1, if I then expand the second grid giving arrowSection_Lec_2 a ScaleY of -1 as well, the first image reverts back to having a ScaleY of 1 even though its corresponding grid is still expanded.
The solution I thought up is to have the storyboard Completed event set the ScaleY of the appropriate arrow explicitly so that it would maintain this position even if the storyboard is run again for a different arrow. I can't figure out how to reference this property in C#.
So for clarity, my question is how would I set the ScaleY transform of arrowSection_Lec_1 to -1 from the code behind?
Here is how to access the Scale from the codebehind:
var transform = (CompositeTransform)arrowSection_Lec_1.RenderTransform;
transform.ScaleY = -1;
suppose you want to animate something, Which one is better to use by performance?
using Storyboards in XAML ?
OR
classes like DoubleAnimation in Media.Animation in Code-Behind?
As said previously, Storyboards are not Animation, thus you can't compare them.
I must suppose that your question is about creating a DoubleAnimation in code behind or in XAML like that:
<Storyboard>
<DoubleAnimation />
</Storyboard>
Thus, your question is pretty similar to: is there a performance difference if I create my object in XAML or in C# code behind.
And my answer is...You should really don't care, at first. To quote a certain Kent Beck: Make it works, make it clean, make it fast. In WPF, to make it clean, you should implement MVVM to have a clear separation of concern. And an animation is clearly an UI concern, so it should be design in the view: your xaml file.
Now if I try to anwser your question anyway, there is almost no performance difference, because a DoubleAnimation object is created eventually in any case.
So do it in XAML, it will be better for the architecture of your software.
Let me conclude by this: if you really have performance constraints for your application, WPF is probably a poor choice anyway as it was not designed to be efficient. The real power of WPF is to allow this clear separation of concern.
1st thing is StoryBoard is not an animation. If you follow below example:
<Storyboard x:Name="myStoryboard">
<DoubleAnimation
Storyboard.TargetName="MyAnimatedRectangle"
Storyboard.TargetProperty="Opacity"
From="1.0" To="0.0" Duration="0:0:1"
AutoReverse="True" RepeatBehavior="Forever" />
</Storyboard>
Here animation is created and applied it to the rectangle's Opacity property using StoryBoard
More information is here
I've spent about the entire day looking at examples of XAML storyboarding, but WinRT/Windows8 apps doesn't work the same way WPF does (At least I think), and I'm all confused.
All I want is a button that moves 100px left and 100px up when clicked. I'm having the hardest time figuring this out, and I know that once I get something I want working, I'll be able to work from there.
Also, if anyone can teach me how to use "Storyboard.TargetProperty" That'd be amazing.
<Rectangle
Name="Rectangle01"
Height="100"
Width="100"
Fill="Red">
<Rectangle.Resources>
<Storyboard x:Name="myboard">
<DoubleAnimation
Storyboard.TargetName="Rectangle01"
Storyboard.TargetProperty="Width"
From="100" To="3600" Duration="0:0:6" />
</Storyboard>
</Rectangle.Resources>
</Rectangle>
Here's an example of something I tried doing just to mess around with storyboards. This didn't throw an error until I tried to execute it, but it still doesn't work.
So yea, WRT is a little different in some ways, but many not.
What you're wanting to do is move your Button around, up and left so logically on an X and Y axis right? So in my mind, I think RenderTransform/TranslateTransform to leverage the X and Y like;
<Button Click="StartTheMove">
<Button.RenderTransform>
<TranslateTransform x:Name="MoveFoo"/>
</Button.RenderTransform>
</Button>
So now we have MoveFoo and setup our Transform, and since the only default Routed.Event I know of for WRT is Loaded we can't do that, so tie into the Click handler with StartTheMove like;
private void StartTheMove(object sender, RoutedEventArgs e)
{
MoveTheButton.Begin();
}
So now we've got our handler and we want to fire off MoveTheButton which will be your actual storyboard set as a resource somewhere in say your window.resources or whatever like;
<Storyboard x:Name="MoveTheButton">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="MoveFoo"
Storyboard.TargetProperty="Y">
<SplineDoubleKeyFrame KeyTime="0:0:1.25" Value="100" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="MoveFoo"
Storyboard.TargetProperty="X">
<SplineDoubleKeyFrame KeyTime="0:0:1.25" Value="-100" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
So once we hit our Storyboard it's then going to go and tell our Transform that we want the properties of X and Y to get some new properties declared (here's your Storyboard.TargetProperty coming in to join the fun) and we're going to set the values to those two properties to something other than what they already are and basically tell it we want it to move 100px up on the Y (up) and -100px on the X (left) of our 2d axis. Which you'll also prob want to play with the keytimes for however fast/slow you want the effect to happen.
While I didn't have time to test this and am kind of just adhoc throwing it together before I leave the office for the day, we've all been where you are and hopefully it helps. You should also be able to add the interaction behaviors to kind of leverage the same type of event triggers you're probably more used to (see tutorials) and handle your event that way to ditch your handler code behind.
However hopefully this helps, if not I'll see it again in the morning and will take another stab at it. Cheers!
I have two overlapping (in same position, having the same size) MediaElements. They will contain images. The opacity of one element will be set to 1.0 and the opacity of the other set to 0.0. The idea here would be a simple transition for a slide-show type deal. When it's time to display the next slide, the background element loads a picture and the opacity of both elements switches gradually.
I tried (successfully) to implement this behavior using System.Timers, only to find that having more than some arbitrary number of timers in the same application would cause .NET to randomly spawn and cede control of timer_elapsed to several different threads. This caused unpredictable results and generally made me question my sanity.
So, I decided to do the same thing, but with System.Threads and their Sleep functions. For whatever reason, gradually cycling the opacity worked perfectly with the insane timers but fails utterly with threads. And it fails in a ridiculous way. The opacity of both elements does change, but there's no in between. The element is shown either with opacity at 1.0 or 0.0. Otherwise I would notice that roughly half the pictures weren't being cycled through.
After much googling, I thought perhaps the priority of the thread that the opacity changes were occurring on was somehow keeping the UI elements from being rendered immediately. But then I recalled that because I was using dispatcher invocations on the media elements, all of the action was taking place on the main thread anyway, so it wouldn't make a difference.
Contemplate the following code: https://gist.github.com/956093
As suggested you should use the native animations; i have come accross this thread issue before as well and in general i try to avoid using Dispatchers, i pretty much only use them to modify data if i must (e.g. ObservableCollection cannot be modified in a background thread, don't know any other examples actually).
You could still use normal threads, they work well if you use bindings to update the UIElements which nicely bypasses the dispatching issue.
Animation example:
<Grid Name="testGrid" Tag="2">
<Grid.Resources>
<Storyboard x:Key="FadeAnim2to1">
<DoubleAnimation Storyboard.Target="{x:Reference img1}"
Storyboard.TargetProperty="Opacity"
Duration="0:0:1" To="1"/>
<DoubleAnimation Storyboard.Target="{x:Reference img2}"
Storyboard.TargetProperty="Opacity"
Duration="0:0:1" To="0"/>
</Storyboard>
<Storyboard x:Key="FadeAnim1to2">
<DoubleAnimation Storyboard.Target="{x:Reference img1}"
Storyboard.TargetProperty="Opacity"
Duration="0:0:1" To="0"/>
<DoubleAnimation Storyboard.Target="{x:Reference img2}"
Storyboard.TargetProperty="Opacity"
Duration="0:0:1" To="1"/>
</Storyboard>
</Grid.Resources>
<Image x:Name="img1" Source="Images/Default.ico" Width="200" Height="200" Opacity="0"/>
<Image x:Name="img2" Source="Images/Error.ico" Width="200" Height="200"/>
</Grid>
<Button Content="Fade" Click="Button1_Click"/>
private void Button1_Click(object sender, RoutedEventArgs e)
{
Storyboard anim;
if ((string)testGrid.Tag == "1") //This is just for brevity, you should of course not use the Tag to store state information, let alone number strings
{
anim = testGrid.Resources["FadeAnim1to2"] as Storyboard;
testGrid.Tag = "2";
}
else
{
anim = testGrid.Resources["FadeAnim2to1"] as Storyboard;
testGrid.Tag = "1";
}
anim.Begin();
}
When I move a bitmap-filled path in Silverlight by updating its RenderTransform, the image updates with a stutter/jitter about every half-second.
The Path uses a BitmapCache, and Hardware Acceleration is turned on. The hardware acceleration is verified using the EnableCacheVisualization option, and by observing that the frame rate is high ~ 60 fps.
You can see what I am talking about here. It is a working silverlight app showing the stuttering behavior. (You might imagine what game I am trying to make when you see the image...)
http://glerok.com/smb/index.html
You will notice that the bitmap scrolls smoothly, but every 0.5 to 1 seconds, it stutters, as if some frames were skipped. I verified this on two different PCs.
Is this expected behavior in silverlight, or am I doing something wrong by manually updating the RenderTransform of the object? (Please don't take this as an opportunity to disparage Silverlight...)
Thanks in advance!!!
Here is the code I am using to generate the path:
RectangleGeometry rg = new RectangleGeometry();
BitmapImage bi = new BitmapImage(new Uri("world.png", UriKind.Relative));
int WorldWidth = 703;
int WorldHeight = 240;
rg.Rect = new Rect(WorldTilePosition, 0, WorldWidth * Scale, WorldHeight * Scale);
Path p = new Path { RenderTransform = new TranslateTransform { X = 0, Y = 0 } };
p.Data = rg;
p.CacheMode = new BitmapCache();
p.Fill = new ImageBrush { ImageSource = bi };
canvas.Children.Add(p);
And here is the code that update the path position:
WorldTilePosition-=10;
TranslateTransform tt = WorldPath.RenderTransform as TranslateTransform;
tt.X = -WorldTilePosition;
if (WorldTilePosition < -1000) WorldTilePosition = 1000;
I observed the stuttering/jittering in Silverlight even for an object that is animated with a storyboard. I guess this happens because there is currently no back-buffering in silverlight, making it currently unsuitable for even simple sprite-based games on PC? Note that the overall screen refresh rate is still reported to be 60 fps.
The code below generates an animated rectangle that flickers during the screen refresh. Vertical objects show better the banding during the refresh rate, at least on my laptop.
<Canvas x:Name="LayoutRoot2" Background="White">
<Rectangle x:Name="myRect" Fill="Red" Width="100" Height="500" CacheMode="BitmapCache" >
<Rectangle.RenderTransform>
<TranslateTransform x:Name="myRectTransform" X="0" />
</Rectangle.RenderTransform>
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.Loaded">
<BeginStoryboard>
<Storyboard x:Name="myStoryboard" >
<DoubleAnimation
From="0"
To="1000"
Storyboard.TargetName="myRectTransform"
Storyboard.TargetProperty="X"
Duration="0:0:1"
AutoReverse="True"
RepeatBehavior="Forever"
/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
</Canvas>
Can't notice any stuttering on my PC (Core 2 Quad 3 GHz, Win7 x64, IE9).
Especially for such a tile based game with low resolution sprites etc. I'd consider using a WriteableBitmap (a nice extended version can be found on Codeplex) and then update/display that one only. This also solves the missing backbuffer.
Also, to avoid noticeable stuttering, try to keep your game logic independent of framerate (using a timer or detecting the number of milliseconds passed since last frame). Skipped frames shouldn't be as noticeable then as well.
Also try to draw the visible part of the landscape only (not sure if clipping is enough; didn't try it).