Parece que a resposta certa para isso é pular o ContentPipeline e usar Texture2D.FromStream para carregar as texturas em tempo de execução. Esse método funciona bem em um PC e, mesmo com um pequeno impacto no desempenho, isso é algo que eu posso otimizar quando estiver mais próximo da data de lançamento. Por enquanto, ter a capacidade de modificar dinamicamente o conteúdo do editor e do jogo é exatamente o que eu preciso. Depois que o conteúdo estiver congelado, eu posso otimizar isso retornando ao ContentPipeline.
Como você escolheu essa rota, devo avisá-lo de que não é tão simples quanto usar apenas Texture2D.FromStream
por dois motivos:
Problema # 1 - Falta de suporte alfa pré-multiplicado
O XNA4 agora lida com texturas com cores no formato alfa pré-multiplicado por padrão. Quando você carrega uma textura através do pipeline de conteúdo, esse processamento é feito automaticamente para você. Infelizmente Texture2D.FromStream
, não faz o mesmo, portanto, qualquer textura que exija algum grau de transparência será carregada e renderizada incorretamente. Abaixo está uma captura de tela para ilustrar o problema:
Portanto, para obter os resultados corretos, você mesmo precisa fazer o processamento. O método que mostrarei usa a GPU para fazer o processamento, então é bem rápido. Foi baseado neste ótimo artigo . Claro que você também pode instruir SpriteBatch
para renderizar no antigo modo NonPremultiplyAlpha, mas eu realmente não recomendo fazer isso.
Problema # 2 - Formatos não suportados
O pipeline de conteúdo suporta mais formatos que Texture2D.FromStream
. Em particular, Texture2D.FromStream
suporta apenas png, jpg e gif. Por outro lado, o pipeline de conteúdo suporta bmp, dds, dib, hdr, jpg, pfm, png, ppm e tga. Se você tentar carregar um formato usuportado, Texture2D.FromStream
receberá um InvalidOperationException
pouco de informações adicionais.
Eu realmente precisava de suporte bmp no meu mecanismo, então, para esse caso em particular, encontrei uma solução alternativa que parece funcionar bem. Não conheço nenhum dos outros formatos. O problema com o meu método é que você precisa adicionar uma referência ao System.Drawing
assembly ao seu projeto, porque ele usa GDIs Image.FromStream
que suportam mais formatos do que Texture2D.FromStream
.
Se você não se importa com o suporte a bmp, pode facilmente largar essa parte da minha solução e fazer o processamento alfa pré-multiplicado.
Solução - Versão Simples (Mais Lenta)
Primeiro de tudo, aqui está a solução mais simples , se você não se importa com o suporte a bmps. Neste exemplo, o estágio de processamento é realizado inteiramente na CPU. É um pouco mais lento que a alternativa mostrada abaixo (fiz benchmark de ambas as soluções), mas é mais fácil de entender:
public static Texture2D FromStream(GraphicsDevice graphicsDevice, Stream stream)
{
Texture2D texture = Texture2D.FromStream(graphicsDevice, stream);
Color[] data = new Color[texture.Width * texture.Height];
texture.GetData(data);
for (int i = 0; i != data.Length; ++i)
data[i] = Color.FromNonPremultiplied(data[i].ToVector4());
texture.SetData(data);
return texture;
}
Se você se preocupa com bmps, o que você precisa fazer é carregar a imagem com o GDI primeiro e depois converter em PNG internamente antes de passá-la para Texture2D.FromStream
. Aqui está o código que faz isso:
// Load image using GDI because Texture2D.FromStream doesn't support BMP
using (Image image = Image.FromStream(stream))
{
// Now create a MemoryStream which will be passed to Texture2D after converting to PNG internally
using (MemoryStream ms = new MemoryStream())
{
image.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
ms.Seek(0, SeekOrigin.Begin);
texture = Texture2D.FromStream(_graphicsDevice, ms);
}
}
Solução - Versão complexa (mais rápida)
Por fim, a abordagem usada em meus projetos é usar a GPU para fazer o processamento. Neste método, você precisa criar um destino de renderização, configurar corretamente alguns estados de mesclagem e desenhar a imagem duas vezes com um SpriteBatch. No final, reviso todo o RenderTarget2D e clono o conteúdo em um objeto Texture2D separado porque o RenderTarget2D é volátil e não sobrevive a coisas como alterar o tamanho do backbuffer para que seja mais seguro fazer uma cópia.
O engraçado é que, mesmo com tudo isso, nos meus testes essa abordagem foi executada cerca de três vezes mais rápido que a da CPU. Portanto, é definitivamente mais rápido do que passar por cima de cada pixel e calcular a cor você mesmo. O código é um pouco longo, então eu o coloquei em um pastebin:
http://pastie.org/3651642
Basta adicionar essa classe ao seu projeto e usá-la da maneira mais simples:
TextureLoader textureLoader = new TextureLoader(GraphicsDevice);
Texture2D texture = textureLoader.FromFile("Content/texture.png");
Nota: Você só precisa criar uma TextureLoader
instância para o jogo inteiro. Também estou usando a correção BMP, mas você pode removê-la se não precisar e obter um monte de desempenho ou apenas deixar o needsBmp
parâmetro como falso.