2

I'm trying to optimize the following code as looping that many times to get every pixel of a texture is extremely time consuming!

I have 3 textures, generated elsewhere that need to be merged and then saved to disk.

I've tried using SetPixels and GetPixels but that isn't particularly useful as using it multiple times just replaces the whole texture rather than only replacing non transparent pixels. The following is my function so far:

public void savePNG()
{

    stationWidth = PlayerPrefs.GetInt("Station Width");
    stationHeight = PlayerPrefs.GetInt("Station Height");

    Debug.Log(stationWidth + ", " + stationHeight);

    Texture2D spaceStation = new Texture2D(stationWidth*256, stationHeight*256, TextureFormat.ARGB32, false);
    spaceStation = new Texture2D(stationWidth*256, stationHeight*256, TextureFormat.ARGB32, false);

    Color Test;

    floorTex = new Texture2D(stationWidth*256, stationHeight*256, TextureFormat.ARGB32, false);
    wallTex = new Texture2D(stationWidth*256, stationHeight*256, TextureFormat.ARGB32, false);
    roomTex = new Texture2D(stationWidth*256, stationHeight*256, TextureFormat.ARGB32, false);

    floorTex.LoadImage(File.ReadAllBytes(Application.dataPath + "/../floorTexture.png"), false);
    wallTex.LoadImage(File.ReadAllBytes(Application.dataPath + "/../wallTexture.png"), false);
    roomTex.LoadImage(File.ReadAllBytes(Application.dataPath + "/../roomTexture.png"), false);


    for(int row = 0; row < stationWidth*256; row ++)
    {
        for(int col = 0; col < stationHeight*256; col ++)
        {

            Test = floorTex.GetPixel(row,col);

            if(Test.a != 0.0f)
            {
                spaceStation.SetPixel(row, col, floorTex.GetPixel(row,col));
            }
            else
            {
                spaceStation.SetPixel(row, col, Color.white);
            }

            Test = wallTex.GetPixel(row,col);

            if(Test.a != 0.0f)
            {
                spaceStation.SetPixel(row, col, wallTex.GetPixel(row,col));
            }

            Test = roomTex.GetPixel(row,col);

            if(Test.a != 0.0f)
            {
                spaceStation.SetPixel(row, col, roomTex.GetPixel(row,col));
            }

        }
    }

    spaceStation.Apply();

    byte[] bytes = spaceStation.EncodeToPNG();
    File.WriteAllBytes(Application.dataPath + "/../SpaceStation.png", bytes);

}

I'm not sure where to go from here, any ideas or suggestions would be greatly appreciated!

Additional

The GetPixels and SetPixels code I was trying looked like this:

    spaceStation.SetPixels(floorTex.GetPixels(0, 0, stationWidth*256, stationHeight*256));
    spaceStation.SetPixels(wallTex.GetPixels(0 ,0, stationWidth*256, stationHeight*256));
    spaceStation.SetPixels(roomTex.GetPixels(0, 0, stationWidth*256, stationHeight*256));

Each SetPixels overwrote the previous ones completely where I would want transparent pixels not to overwrite any pixel at all so the details would show through

Al Wyvern
  • 199
  • 1
  • 15
  • `GetPixels` and `SetPixels` are capable of any sort of effect. Show the code that uses those and is causing you problems. – Ben Voigt May 07 '16 at 20:50
  • added the `SetPixels` code that didn't work for me – Al Wyvern May 07 '16 at 21:02
  • 1
    Ahh well, you still have to run your algorithm. If you're just wanting to draw with transparency, there should be a function for that. For other effects, loop over the array returned from `GetPixels`, change the array one pixel at a time, then apply all at once using `SetPixels`. – Ben Voigt May 07 '16 at 21:18
  • That seems to be the best idea alright, I just used color arrays and did all my editing there, made it about 80% faster :) will post my answer. I've been told I could get even faster using `Graphics.Blit` but I don't have any experience with shaders at all so I'm gonna pass on that, 80% is a decent saving of time at any rate – Al Wyvern May 07 '16 at 23:02

2 Answers2

1

I used color arrays instead of setting each pixel individually, only had to call GetPixels 3 times and SetPixels once. Function is now about 80% faster which is pretty decent all-round. I've been told that I could get even better results using Graphics.Blit and a shader but I really don't know anything about shaders so I'm not going to take that approach at the moment. Here's how the code looks now:

public void savePNG()
{
    stationWidth = PlayerPrefs.GetInt("Station Width");
    stationHeight = PlayerPrefs.GetInt("Station Height");

    Texture2D spaceStation = new Texture2D(stationWidth*256, stationHeight*256, TextureFormat.ARGB32, false);
    spaceStation = new Texture2D(stationWidth*256, stationHeight*256, TextureFormat.ARGB32, false);

    Color[] mainArray = new Color[(stationWidth*256)*(stationHeight*256)];
    Color[] wallArray = new Color[(stationWidth*256)*(stationHeight*256)];
    Color[] roomArray = new Color[(stationWidth*256)*(stationHeight*256)];

    floorTex = new Texture2D(stationWidth*256, stationHeight*256, TextureFormat.ARGB32, false);
    wallTex = new Texture2D(stationWidth*256, stationHeight*256, TextureFormat.ARGB32, false);
    roomTex = new Texture2D(stationWidth*256, stationHeight*256, TextureFormat.ARGB32, false);

    floorTex.LoadImage(File.ReadAllBytes(Application.dataPath + "/../floorTexture.png"), false);
    wallTex.LoadImage(File.ReadAllBytes(Application.dataPath + "/../wallTexture.png"), false);
    roomTex.LoadImage(File.ReadAllBytes(Application.dataPath + "/../roomTexture.png"), false);


    mainArray = floorTex.GetPixels(0,0,stationWidth*256, stationHeight*256);
    wallArray = wallTex.GetPixels(0,0,stationWidth*256, stationHeight*256);
    roomArray = roomTex.GetPixels(0,0,stationWidth*256, stationHeight*256);

    for(int i = 0; i < mainArray.Length; i++)
    {
        if(wallArray[i].a != 0.0f)
        {
            mainArray[i] = wallArray[i];
        }
        if(roomArray[i].a != 0.0f)
        {
            mainArray[i] = roomArray[i];
        }
    }

    spaceStation.SetPixels(mainArray);

    spaceStation.Apply();

    byte[] bytes = spaceStation.EncodeToPNG();
    File.WriteAllBytes(Application.dataPath + "/../SpaceStation.png", bytes);

}
Al Wyvern
  • 199
  • 1
  • 15
-1

I really don't know the performance of the (get/set)Pixel methods of Texture2D object. But in .NET world we prefer the System.Drawing.Bitmap object and System.Drawing.Imaging.BitmapData classes and BitmapData's Scan0 property to access the pixel values in a fast way.

There already is a beautiful answer given there in SO that demonstrates the usage.

Community
  • 1
  • 1
tafa
  • 7,146
  • 3
  • 36
  • 40