Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to change a CroppedBitmap's SourceRect at runtime

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?

like image 341
Joe White Avatar asked Nov 29 '22 07:11

Joe White


2 Answers

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.

like image 147
Joe White Avatar answered Dec 18 '22 22:12

Joe White


Here is an alternate way to deal with this:
Instead of using a CroppedBitmap, use the full source image, but:

  1. Set the image.RenderTransform to adjust the viewable area.
  2. Set an 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);
like image 29
Joel Rondeau Avatar answered Dec 18 '22 20:12

Joel Rondeau