Есть ли быстрая альтернатива созданию Texture2D из объекта Bitmap в XNA? - PullRequest
9 голосов
/ 20 мая 2010

Я много осмотрелся, и единственные методы, которые я нашел для создания Texture2D из растрового изображения:

using  (MemoryStream s = new  MemoryStream())
{
   bmp.Save(s, System.Drawing.Imaging.ImageFormat.Png);
   s.Seek(0, SeekOrigin.Begin);
   Texture2D tx = Texture2D.FromFile(device, s);
}

и

Texture2D tx = new Texture2D(device, bmp.Width, bmp.Height,
                        0, TextureUsage.None, SurfaceFormat.Color);
tx.SetData<byte>(rgbValues, 0, rgbValues.Length, SetDataOptions.NoOverwrite);

Где rgbValues ​​- это байтовый массив, содержащий пиксельные данные растрового изображения в 32-битном формате ARGB.

У меня вопрос, есть ли более быстрые подходы, которые я могу попробовать?

Я пишу редактор карт, который должен читать изображения в произвольном формате (фрагменты карты) и преобразовывать их в текстуры Texture2D для отображения. Предыдущая версия редактора, которая была реализацией C ++, сначала преобразовывала изображения в растровые изображения, а затем в текстуры, которые можно рисовать с использованием DirectX. Я попытался использовать тот же подход здесь, однако оба вышеупомянутых подхода значительно слишком медленные. Для загрузки в память всех текстур, требуемых для карты, требуется при первом подходе ~ 250 секунд и при втором подходе ~ 110 секунд на приемлемом компьютере (для сравнения, код C ++ занял приблизительно 5 секунд). Если есть метод для непосредственного редактирования данных текстуры (например, с помощью метода LockBits класса Bitmap), я смогу преобразовать изображения произвольного формата прямо в Texture2D и, надеюсь, сэкономить время обработки.

Любая помощь будет принята с благодарностью.

Спасибо

Ответы [ 4 ]

10 голосов
/ 20 мая 2010

Вы хотите LockBits? Вы получаете LockBits.

В моей реализации я передал GraphicsDevice от вызывающей стороны, чтобы я мог сделать этот метод универсальным и статическим.

public static Texture2D GetTexture2DFromBitmap(GraphicsDevice device, Bitmap bitmap)
{
    Texture2D tex = new Texture2D(device, bitmap.Width, bitmap.Height, 1, TextureUsage.None, SurfaceFormat.Color);

    BitmapData data = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat);

    int bufferSize = data.Height * data.Stride;

    //create data buffer 
    byte[] bytes = new byte[bufferSize];    

    // copy bitmap data into buffer
    Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);

    // copy our buffer to the texture
    tex.SetData(bytes);

    // unlock the bitmap data
    bitmap.UnlockBits(data);

    return tex;
}
9 голосов
/ 13 сентября 2011

они изменили формат с bgra на rgba в XNA 4.0, так что метод дает странные цвета, красный и синий каналы должны быть переключены. Вот метод, который я написал, который очень быстрый! (загружает текстуры размером 1500x 256x256 пикселей примерно за 3 секунды).

    private Texture2D GetTexture(GraphicsDevice dev, System.Drawing.Bitmap bmp)
    {
        int[] imgData = new int[bmp.Width * bmp.Height];
        Texture2D texture = new Texture2D(dev, bmp.Width, bmp.Height);

        unsafe
        {
            // lock bitmap
            System.Drawing.Imaging.BitmapData origdata = 
                bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);

            uint* byteData = (uint*)origdata.Scan0;

            // Switch bgra -> rgba
            for (int i = 0; i < imgData.Length; i++)
            {
                byteData[i] = (byteData[i] & 0x000000ff) << 16 | (byteData[i] & 0x0000FF00) | (byteData[i] & 0x00FF0000) >> 16 | (byteData[i] & 0xFF000000);                        
            }                

            // copy data
            System.Runtime.InteropServices.Marshal.Copy(origdata.Scan0, imgData, 0, bmp.Width * bmp.Height);

            byteData = null;

            // unlock bitmap
            bmp.UnlockBits(origdata);
        }

        texture.SetData(imgData);

        return texture;
    }
3 голосов
/ 20 мая 2010

Я обнаружил, что должен был указать PixelFormat как .Format32bppArgb при использовании LockBits, поскольку вы предлагаете захватывать изображения с веб-камеры.

        BitmapData bmd = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height),
            System.Drawing.Imaging.ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
        int bufferSize = bmd.Height * bmd.Stride;
        //create data buffer 
        byte[] bytes = new byte[bufferSize];
        // copy bitmap data into buffer
        Marshal.Copy(bmd.Scan0, bytes, 0, bytes.Length);

        // copy our buffer to the texture
        Texture2D t2d = new Texture2D(_graphics.GraphicsDevice, bmp.Width, bmp.Height, 1, TextureUsage.None, SurfaceFormat.Color);
        t2d.SetData<byte>(bytes);
        // unlock the bitmap data
        bmp.UnlockBits(bmd);
        return t2d;
1 голос
/ 03 марта 2018

Когда я впервые прочитал этот вопрос, я предположил, что пределом производительности была SetData. Однако, читая комментарии ОП в верхнем ответе, он, похоже, выделяет лот из больших Texture2D.

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

В первый раз, когда каждый файл текстуры необходим (или в «предварительной загрузке» в начале вашего процесса, в зависимости от того, где вы хотите задержку), загрузите каждый файл в массив byte[]. (Храните эти byte[] массивы в LRU Cache - если только вы не уверены, что у вас достаточно памяти для их постоянного хранения.) Затем, когда вам понадобится одна из этих текстур, возьмите одну из пула текстуры (выделение новой, если нет подходящего размера), SetData из вашего байтового массива - альт, у вас есть текстура.

[Я пропустил важные детали, такие как необходимость привязки текстуры к определенному устройству - но вы можете определить любые потребности от параметров до методов, которые вы вызываете. Цель, которую я делаю, состоит в том, чтобы минимизировать обращения к конструктору Texture2D, особенно если у вас много больших текстур.]

Если вам по-настоящему интересно, и вы имеете дело со многими текстурами разных размеров, вы также можете применить к пулу принципы LRU Cache . В частности, отслеживайте общее количество байтов «свободных» объектов, хранящихся в вашем пуле. Если эта сумма превышает установленное вами пороговое значение (возможно, в сочетании с общим количеством «свободных» объектов), то при следующем запросе отбрасываете самые старые бесплатные элементы пула (неправильного размера или других неправильных параметров), чтобы оставаться ниже разрешенного порог "потерянного" пространства кеша.

Кстати, вы могли бы сделать это просто, отслеживая порог и выбрасывая все свободные объекты при превышении порога. Недостатком является кратковременный сбой в следующий раз, когда вы выделите кучу новых текстур, которые вы можете улучшить, если у вас есть информация о том, какие размеры вам следует сохранить. Если этого недостаточно, вам нужно LRU .

...