When I try to change a CroppedBitmap's SourceRect property at runtime, nothing happens. There's no error, and the property value doesn't actually get changed.
I'm trying to do sprite animation. I have a BitmapSource that contains a spritesheet, which is a single bitmap containing a grid of different poses for the sprite. Then I have a CroppedBitmap that has the spritesheet as its Source, and a SourceRect that pulls one of the poses out of the spritesheet. At runtime, when I want to animate, I'm trying to change the CroppedBitmap's SourceRect property, to pull a different pose out of the larger bitmap; but, as noted above, the new property value simply doesn't stick. It's the weirdest thing.
Here's some sample XAML:
<UserControl.Resources>
<BitmapImage x:Key="spritesheet" UriSource="Sprites/elf.png"/>
</UserControl.Resources>
<Image>
<Image.Source>
<CroppedBitmap x:Name="image" Source="{StaticResource spritesheet}"
SourceRect="240 640 240 320"/>
</Image.Source>
</Image>
And the codebehind tries to do this:
var newRect = new Int32Rect(...);
Debug.WriteLine(" Before: " + image.SourceRect);
Debug.WriteLine("Assigning new value: " + newRect);
image.SourceRect = newRect;
Debug.WriteLine(" After: " + image.SourceRect);
That gives me this debug output:
Before: 240,640,240,320
Assigning new value: 240,0,240,320
After: 240,640,240,320
So it's actually assigning the new rectangle (with Y=0) into the property; there's no exception; but afterward, the property value simply didn't change (Y is still 640).
Any ideas about why this happens, and how to fix it?
I eventually found the answer. From the documentation for CroppedBitmap:
CroppedBitmap implements the ISupportInitialize interface to optimize initialization on multiple properties. Property changes can occur only during object initialization. Call BeginInit to signal that initialization has begun and EndInit to signal that initialization has completed. After initialization, property changes are ignored. (emphasis mine)
Just for fun, I tried adding BeginInit()..EndInit() calls in my method, to see if that would make it modifiable. Not surprisingly, I got an InvalidOperationException ("Cannot set the initializing state more than once").
So CroppedBitmap is effectively immutable. (But they ignored their own Freezable system, which would have thrown an exception to tell me I was doing something wrong, and implemented something more surprising instead.)
Which means, no-go on changing the SourceRect property. I'll need to create a separate CroppedBitmap instance for each sub-image within the spritesheet.
Here is an alternate way to deal with this:
Instead of using a CroppedBitmap
, use the full source image, but:
image.RenderTransform
to adjust the viewable area.Image.Clip
if necessary, to avoid showing portions of the image that are unwanted.This means that you don't need to keep making new CroppedBitmaps
, you can just adjust the transform.
In my testing, I saw no difference in speed doing it either way.
For completeness, here's how I'd adjust your code to do what I'm suggesting:
<Image RenderTransform="1, 0, 0, 1, -240, -640">
<!-- Still include your Image.Source here, just not as a CroppedBitmap -->
<Image.Clip>
<RectangleGeometry Rect="0, 0, 240, 320" />
</Image.Clip>
</Image>
Then the later call to do the equivalent of adjusting the SourceRect
is:
image.RenderTransform = new MatrixTransform(1d, 0d, 0d, 1d, -240d, 0d);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With