Workaround for Texture2D.GetData method
Asked Answered
M

3

6

I’m converting a game from XNA to iOS with Monogame.
In the code snippet below, smallDeform is a Texture2D on which I call the GetData method.

smallDeform = Game.Content.Load<Texture2D>("Terrain/...");
smallDeform.GetData(smallDeformData, 0, smallDeform.Width * smallDeform.Height);

I’m having some problem with Monogame because the feature in iOS has not been implemented yet because it returns this exception.

#if IOS 
   throw new NotImplementedException();
#elif ANDROID

I tried to serialize the data in a XML file from Windows to load the whole file from iOS. The serialized files weight more than 100MB each, that is quite unacceptable to parse.

Basically, I’m looking for a workaround to get the data (for instance a uint[] or Color[]) from a texture without using the GetData method.

PS: I'm on Mac, so I can't use the Monogame SharpDX library.

Thanks in advance.

Mistaken answered 28/11, 2013 at 21:31 Comment(0)
M
1

I answer my own question, if someone will have the same problem.

Following craftworkgame's suggestion, I saved my data using byte streams, but due to the absence of File class, I had to use WinRT functions:

private async void SaveColorArray(string filename, Color[] array)
{
    StorageFile sampleFile = await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFileAsync(filename + ".dat", CreationCollisionOption.ReplaceExisting);
    IRandomAccessStream writeStream = await sampleFile.OpenAsync(FileAccessMode.ReadWrite);
    IOutputStream outputSteam = writeStream.GetOutputStreamAt(0);
    DataWriter dataWriter = new DataWriter(outputSteam);
    for (int i = 0; i < array.Length; i++)
    {
        dataWriter.WriteByte(array[i].R);
        dataWriter.WriteByte(array[i].G);
        dataWriter.WriteByte(array[i].B);
        dataWriter.WriteByte(array[i].A);
    }

    await dataWriter.StoreAsync();
    await outputSteam.FlushAsync();
}

protected async Task<Color[]> LoadColorArray(string filename)
{
    StorageFile sampleFile = await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFileAsync(filename + ".dat", CreationCollisionOption.OpenIfExists);
    IRandomAccessStream readStream = await sampleFile.OpenAsync(FileAccessMode.Read);
    IInputStream inputSteam = readStream.GetInputStreamAt(0);
    DataReader dataReader = new DataReader(inputSteam);
    await dataReader.LoadAsync((uint)readStream.Size);

    Color[] levelArray = new Color[dataReader.UnconsumedBufferLength / 4];
    int i = 0;
    while (dataReader.UnconsumedBufferLength > 0)
    {
        byte[] colorArray = new byte[4];
        dataReader.ReadBytes(colorArray);
        levelArray[i++] = new Color(colorArray[0], colorArray[1], colorArray[2], colorArray[3]);
    }

    return levelArray;
}
Mistaken answered 29/11, 2013 at 17:52 Comment(0)
S
1

Serializing the data is actually a pretty clever workaround but XML is far to heavy for the job. What I would do is use a custom made, but simple, binary format so that you can control the size of the output.

Here are a couple of methods I whipped up that should work, but please note that I made them specifically to answer this question and they have not been tested.

To save the data..

    private void SaveTextureData(Texture2D texture, string filename)
    {
        int width = texture.Width;
        int height = texture.Height;
        Color[] data = new Color[width * height];
        texture.GetData<Color>(data, 0, data.Length);

        using (BinaryWriter writer = new BinaryWriter(File.Open(filename, FileMode.Create)))
        {
            writer.Write(width);
            writer.Write(height);
            writer.Write(data.Length);

            for (int i = 0; i < data.Length; i++)
            {
                writer.Write(data[i].R);
                writer.Write(data[i].G);
                writer.Write(data[i].B);
                writer.Write(data[i].A);
            }
        }
    }

And to load the data..

    private Texture2D LoadTextureData(string filename)
    {
        using (BinaryReader reader = new BinaryReader(File.Open(filename, FileMode.Open)))
        {                
            var width = reader.ReadInt32();
            var height = reader.ReadInt32();
            var length = reader.ReadInt32();
            var data = new Color[length];

            for (int i = 0; i < data.Length; i++)
            {
                var r = reader.ReadByte();
                var g = reader.ReadByte();
                var b = reader.ReadByte();
                var a = reader.ReadByte();
                data[i] = new Color(r, g, b, a);
            }

            var texture = new Texture2D(GraphicsDevice, width, height);
            texture.SetData<Color>(data, 0, data.Length);
            return texture;
        }
    }

Binary data is far smaller than XML data. It's about as small as you're going to get without compression. Just be aware that binary formats are pretty rigid when it comes to change. If you need to change the format it'll be easier in most cases to write out new files.

If you need to make changes to the methods, be careful to keep them in sync. Every Write should be matched with an identical Read in the exact same order and data type.

I'm interested to know how much smaller the files become. Let me know how it goes?

Supervise answered 29/11, 2013 at 1:2 Comment(4)
Yap, this is another way that I've tried, but the problem is that File class does not seem to exist in Monogame, unfortunately. I should find another way to save on file.Mistaken
I've found a different solution, but +1 for your help.Mistaken
I dont get it, there is texture.GetData call, which isnt implemented in iOS. @Mistaken can you please share your solution? I cant figure it out.Prosimian
@Kostkac my solution is in the other answerMistaken
M
1

I answer my own question, if someone will have the same problem.

Following craftworkgame's suggestion, I saved my data using byte streams, but due to the absence of File class, I had to use WinRT functions:

private async void SaveColorArray(string filename, Color[] array)
{
    StorageFile sampleFile = await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFileAsync(filename + ".dat", CreationCollisionOption.ReplaceExisting);
    IRandomAccessStream writeStream = await sampleFile.OpenAsync(FileAccessMode.ReadWrite);
    IOutputStream outputSteam = writeStream.GetOutputStreamAt(0);
    DataWriter dataWriter = new DataWriter(outputSteam);
    for (int i = 0; i < array.Length; i++)
    {
        dataWriter.WriteByte(array[i].R);
        dataWriter.WriteByte(array[i].G);
        dataWriter.WriteByte(array[i].B);
        dataWriter.WriteByte(array[i].A);
    }

    await dataWriter.StoreAsync();
    await outputSteam.FlushAsync();
}

protected async Task<Color[]> LoadColorArray(string filename)
{
    StorageFile sampleFile = await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFileAsync(filename + ".dat", CreationCollisionOption.OpenIfExists);
    IRandomAccessStream readStream = await sampleFile.OpenAsync(FileAccessMode.Read);
    IInputStream inputSteam = readStream.GetInputStreamAt(0);
    DataReader dataReader = new DataReader(inputSteam);
    await dataReader.LoadAsync((uint)readStream.Size);

    Color[] levelArray = new Color[dataReader.UnconsumedBufferLength / 4];
    int i = 0;
    while (dataReader.UnconsumedBufferLength > 0)
    {
        byte[] colorArray = new byte[4];
        dataReader.ReadBytes(colorArray);
        levelArray[i++] = new Color(colorArray[0], colorArray[1], colorArray[2], colorArray[3]);
    }

    return levelArray;
}
Mistaken answered 29/11, 2013 at 17:52 Comment(0)
F
1

You can get Texture2D.GetData working on iOS if you're willing to build your own version of MonoGame. See the fix here: https://github.com/scottlerch/MonoGame/commit/24c1c3d5398f4ed9dbd9b60c0bbdbc096311632c).

It's from discussions about this same issue here: https://monogame.codeplex.com/discussions/442667

The fix has been confirmed to work on iPad1 v5.1.1+ and an iPhone4 v6.1+.

Folder answered 19/5, 2014 at 21:49 Comment(1)
Good to know, but it would have been more useful a few months ago :)Mistaken

© 2022 - 2024 — McMap. All rights reserved.