Как создать текстуру XNA из GDI-изображения с исходным прямоугольником?

8

Texture2D в XNA может содержать только так много. В зависимости от того, какой профиль я использую, этот предел составляет 2048 или 4096 пикселей в ширину. Насколько я могу судить по моим исследованиям (кто-то поправит меня, если я ошибаюсь) GDI-изображения не имеют никаких ограничений, кроме того, что они не превышают память пользователя.

Я ищу наиболее эффективный способ взять изображение GDI и исходный прямоугольник (в пределах ограничений размера XNA) и создать новый Texture2D из этих данных во время выполнения. Процесс также должен учитывать предварительно умноженную альфа в XNA 4.0!

ИЛИ...

Способ взять Texture2D.FromStream и адаптировать его так, чтобы он тоже занимал исходный прямоугольник, так как это было бы еще лучше для моих нужд.

Требования следующие:

  • Нет конвейера контента, мне нужно иметь возможность загружать их во время выполнения.
  • Если возможно, используйте только профиль Reach!
  • Заботьтесь только о Windows. Не беспокойтесь о портативности.

TL; DR:

Мне нужен такой метод, как:

Texture2D Texture2DHelper.FromStream(GraphicsDevice, Stream, SourceRectangle);

Или

Texture2D Texture2DHelper.FromImage(Image, SourceRectangle)

Желательно первое.


Большая фотография:

На мгновение игнорируя исходный прямоугольник (чтобы упростить визуализацию), я попытался загрузить целую текстуру в GDI и передать данные в Texture2D, используя полностью грубый метод:

using (System.Drawing.Image image = System.Drawing.Image.FromFile(path))
{
    int w = image.Width;
    int h = image.Height;
    System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(image);
    uint[] data = new uint[w * h];
    for (int i=0; i!=bitmap.Width; ++i)
    {
        for (int j=0; j!=bitmap.Height; ++j)
        {
            System.Drawing.Color pixel = bitmap.GetPixel(i, j);
            Microsoft.Xna.Framework.Color color = Color.FromNonPremultiplied(pixel.R, pixel.G, pixel.B, pixel.A);
            data[i + j * w] = color.PackedValue;
        }
    }
    _texture = new Texture2D(device, w, h);
    _texture.SetData(data);
}

И результат был действительно медленным. Это заняло много много раз дольше, чем просто:

Texture2D.FromStream(GraphicsDevice, new FileStream(path, FileMode.Open));

Я также думал об использовании Bitmap.LockBits и Block Copy, но при этом исключалась бы предварительно умноженная альфа-коррекция, и я не знаю, как применить ее после копирования.

Я испытываю желание думать, что лучшим решением было бы работать непосредственно с Texture2D.FromStream ... Я не слишком знаком с тем, как работает Stream, но я видел некоторые методы в .NET, которые принимают Stream и Rectangle. Как бы я достиг чего-то подобного? Либо совершенно новый метод FromStream, либо обертка вокруг любого потока, который обеспечивает функциональность «Прямоугольник».

Дэвид Гувея
источник

Ответы:

4

Хорошо, удалось найти решение этой проблемы в стиле GDI! Я обернул это в статический вспомогательный класс:

public static class TextureLoader
{
    public static Texture2D FromFile(GraphicsDevice device, string path, Microsoft.Xna.Framework.Rectangle? sourceRectangle = null)
    {
        // XNA 4.0 removed FromFile in favor of FromStream
        // So if we don't pass a source rectangle just delegate to FromStream
        if(sourceRectangle == null)
        {
            return Texture2D.FromStream(device, new FileStream(path, FileMode.Open));
        }
        else
        {
            // If we passed in a source rectangle convert it to System.Drawing.Rectangle
            Microsoft.Xna.Framework.Rectangle xnaRectangle = sourceRectangle.Value;
            System.Drawing.Rectangle gdiRectangle = new System.Drawing.Rectangle(xnaRectangle.X, xnaRectangle.Y, xnaRectangle.Width, xnaRectangle.Height);

            // Start by loading the entire image
            Image image = Image.FromFile(path);

            // Create an empty bitmap to contain our crop
            Bitmap bitmap = new Bitmap(gdiRectangle.Width, gdiRectangle.Height, image.PixelFormat);

            // Draw the cropped image region to the bitmap
            Graphics graphics = Graphics.FromImage(bitmap);
            graphics.DrawImage(image, new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height), gdiRectangle.X, gdiRectangle.Y, gdiRectangle.Width, gdiRectangle.Height, GraphicsUnit.Pixel);

            // Save the bitmap into a memory stream
            MemoryStream stream = new MemoryStream();
            bitmap.Save(stream, ImageFormat.Png);

            // Finally create the Texture2D from stream
            Texture2D texture = Texture2D.FromStream(device, stream);

            // Clean up
            stream.Dispose();
            graphics.Dispose();
            bitmap.Dispose();
            image.Dispose();

            return texture;
        }
    }
}

В случае, если вы не заметили, что Texture2D.FromFile был удален из XNA 4.0, поэтому я воспользовался возможностью повторить старое поведение, когда не прошел исходный прямоугольник.

И самое приятное то, что, в конце концов, он все еще полагается на Texture2D.FromStream, вся предварительно умноженная альфа-фаза автоматизирована! Работает относительно быстро тоже. Хорошо, это не правда! Вы все еще должны сделать предварительные альфа-вычисления. Я просто плохо помню, так как у меня уже был код для этого .

Но все еще задаюсь вопросом, есть ли способ сделать это непосредственно из Stream без необходимости проходить через GDI. Ум каким-то образом побрел к украшению Стрима, но никуда не пришел. :)

И, просматривая мой старый код, я нашел комментарий о том, что Texture2D.FromStream содержит ошибки и не может прочитать все форматы, которые есть в документации, поэтому я все равно использую GDI. Так что я пока останусь с этим методом.

Дэвид Гувея
источник