Tentando mudar SourceRect um CroppedBitmap em tempo de execução
-
06-07-2019 - |
Pergunta
Quando tento alterar a propriedade SourceRect um CroppedBitmap em tempo de execução, nada acontece. Não há nenhum erro, eo valor da propriedade na verdade não se mudou.
Eu estou tentando fazer a animação do sprite. I têm um BitmapSource que contém um spritesheet, que é um único mapa de bits que contém uma grade de diferentes posturas para o sprite. Então eu tenho um CroppedBitmap que tem o spritesheet como sua fonte, e uma SourceRect que puxa uma das poses fora do spritesheet. Em tempo de execução, quando eu quero animar, eu estou tentando alterar a propriedade SourceRect do CroppedBitmap, para puxar uma pose diferente fora do bitmap maior; mas, como mencionado acima, o novo valor da propriedade simplesmente não cola. É a coisa mais estranha.
Eis alguns XAML amostra:
<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>
E as tentativas codebehind para fazer isso:
var newRect = new Int32Rect(...);
Debug.WriteLine(" Before: " + image.SourceRect);
Debug.WriteLine("Assigning new value: " + newRect);
image.SourceRect = newRect;
Debug.WriteLine(" After: " + image.SourceRect);
Isso me dá essa saída de depuração:
Before: 240,640,240,320
Assigning new value: 240,0,240,320
After: 240,640,240,320
Por isso é realmente atribuir o novo retângulo (com Y = 0) para a propriedade; não há nenhuma exceção; mas depois, o valor da propriedade simplesmente não mudar (Y ainda é 640).
Todas as ideias sobre por que isso acontece e como corrigi-lo?
Solução
Eu finalmente encontrei a resposta. A partir da documentação para CroppedBitmap :
implementos CroppedBitmap a interface ISupportInitialize a inicialização otimizar em várias propriedades. alterações de propriedade só pode ocorrer durante a inicialização do objeto. Chamada BeginInit para sinalizar que a inicialização começou e EndInit para sinalizar que a inicialização foi concluída. Após a inicialização, alterações de propriedade são ignoradas. (grifo meu)
Apenas por diversão, eu tentei adicionar BeginInit () .. EndInit () chama em meu método, para ver se isso iria torná-lo modificável. Não surpreendentemente, eu tenho um InvalidOperationException ( "Não é possível definir o estado de inicialização mais de uma vez").
Assim CroppedBitmap é efetivamente imutável. (Mas eles ignoraram seu próprio sistema de Freezable, que teria jogado uma exceção para me dizer que eu estava fazendo algo errado, e implementado algo mais surpreendendo vez)
O que significa, no-go sobre como alterar a propriedade SourceRect. Vou precisar para criar uma instância CroppedBitmap separado para cada sub-imagem dentro da spritesheet.
Outras dicas
Aqui está uma forma alternativa de lidar com isso:
Em vez de usar um CroppedBitmap
, use a imagem de origem completo, mas:
- Defina o
image.RenderTransform
para ajustar a área visível. - Definir um
Image.Clip
se necessário, para evitar mostrar partes da imagem que não são desejadas.
Isso significa que você não precisa continuar a fazer nova CroppedBitmaps
, você pode simplesmente ajustar a transformar.
Em meus testes, eu não vi nenhuma diferença na velocidade de fazê-lo de qualquer forma.
Para completar, aqui está como eu ajustar seu código para fazer o que eu estou sugerindo:
<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>
Em seguida, a chamada mais tarde para fazer o equivalente a ajustar o SourceRect
é:
image.RenderTransform = new MatrixTransform(1d, 0d, 0d, 1d, -240d, 0d);
Aqui está uma maneira usando IMultiValueConverter
:
<Image>
<Image.Source>
<MultiBinding Converter="{x:Static local:SourceAndRectToCroppedBitmapConverter.Default}">
<Binding Path="FileName" />
<Binding Path="CropRect" />
</MultiBinding>
</Image.Source>
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Imaging;
public class SourceAndRectToCroppedBitmapConverter : IMultiValueConverter
{
public static readonly SourceAndRectToCroppedBitmapConverter Default = new SourceAndRectToCroppedBitmapConverter();
private static readonly ImageSourceConverter Converter = new ImageSourceConverter();
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] is string text)
{
return new CroppedBitmap((BitmapSource)Converter.ConvertFrom(values[0]), (Int32Rect)values[1]);
}
return new CroppedBitmap((BitmapSource)values[0], (Int32Rect)values[1]);
}
object[] IMultiValueConverter.ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Potencialmente pobre perf.