Search Unity

Question Speed Up Large Get and Set Pixel Operations?

Discussion in 'Scripting' started by JudahMantell, Jan 25, 2023.

  1. JudahMantell

    JudahMantell

    Joined:
    Feb 28, 2017
    Posts:
    476
    I'm working on a function where I have two textures: 1 is an image, and another (of identical size) is a mask, with all clear pixels, except for certain areas. My goal is to use this mask texture to remove everything from the image except for the masked areas.
    The following code works, but is very slow with larger images. I was wondering if anyone has any tips on speeding up this process or even an alternative that would be better.

    Code (CSharp):
    1. // imageTex is the source image
    2. // alphaMask is the mask
    3.  
    4. for (int x = 0; x < imageTex.width; x++)
    5. {
    6.     for (int y = 0; y < imageTex.height; y++)
    7.     {
    8.         Color maskPixel = alphaMask.GetPixel(x, y);
    9.  
    10.         if (maskPixel != Color.clear)
    11.             continue;
    12.  
    13.         imageTex.SetPixel(x, y, Color.clear);
    14.     }
    15. }
    16.  
    17. newBG.Apply();
    I need to be working directly with the textures so I can save them to disk, so I can't think of a way other than Get/Set pixel.
    Thanks!
     
  2. JudahMantell

    JudahMantell

    Joined:
    Feb 28, 2017
    Posts:
    476
    After looking more into it, I wanted to jump back in on my own post and share that changing each Get/SetPixel call to Get/SetPixelS (plural), sped things up significantly.
    For future Unity-nauts who need something similar, this is a good example on how you can migrate to Get/SetPixels for large operations.

    Code (CSharp):
    1. // imageTex is the source image
    2. // alphaMask is the mask
    3. // Get the color data for both textures at once
    4. Color[] imagePixels = imageTex.GetPixels();
    5. Color[] maskPixels = alphaMask.GetPixels();
    6. // Iterate over the pixels in the mask
    7. for (int i = 0; i < maskPixels.Length; i++)
    8. {
    9.     // If the mask pixel is not clear, skip it
    10.     if (maskPixels[i] != Color.clear)
    11.         continue;
    12.     // Otherwise, set the corresponding image pixel to clear
    13.     imagePixels[i] = Color.clear;
    14. }
    15. // Set the color data for the image texture
    16. imageTex.SetPixels(imagePixels);
    17. imageTex.Apply();
     
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    And if you're just blasting pixels INTO a texture, you don't have to keep asking for them back with .GetPixels()... just get them once, keep the array, update the array, call SetPixels / Apply it back.

    Or if you feel brave, pin the color array and pass it into a native C function that can be your own super-ultra-turbo-optimized software blitter / rasterizer, then unpin it and shove it into the texture.

    That's how my KurtMaster2D game does it for all my old MS-DOS games.

    Pinning a texture for manipulation via native code:

    https://forum.unity.com/threads/texture-byte-per-pixel.624343/#post-4185943

    KurtMaster2D is free here:

    Apple iTunes: https://itunes.apple.com/us/app/kurtmaster2d/id1015692678

    Google Play: https://play.google.com/store/apps/details?id=com.plbm.plbm1
     
    HouinKyouma27 and Bunny83 like this.
  4. JudahMantell

    JudahMantell

    Joined:
    Feb 28, 2017
    Posts:
    476
    I'll look into that, thanks for the suggestions!
     
  5. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    Whenever I need to do image manipulation in Unity I usually turn to shaders and rendertextures, so don't forget about these!
     
    Kurt-Dekker likes this.
  6. JudahMantell

    JudahMantell

    Joined:
    Feb 28, 2017
    Posts:
    476
    I appreciate the suggestion! I've looked into those, but the problem is, I need to save the texture to disk, so using shaders won't quite work. And for Render Textures, I'm not quite sure how to best do this.

    I'm also gonna try to switch to GetPixels32 as I heard its even faster as well.
     
  7. SF_FrankvHoof

    SF_FrankvHoof

    Joined:
    Apr 1, 2022
    Posts:
    780
    If you use a Shader to do your 'calculation' (i.e. the checking for Color.clear in your mask) you can render the result to a RenderTexture, then do a GetPixels() on that rendertexture to get the output that you want to write to disk.

    Code (CSharp):
    1. public class SaveRenderTextureToFile {
    2.     [MenuItem("Assets/Save RenderTexture to file")]
    3.     public static void SaveRTToFile()
    4.     {
    5.         RenderTexture rt = Selection.activeObject as RenderTexture;
    6.  
    7.         RenderTexture.active = rt;
    8.         Texture2D tex = new Texture2D(rt.width, rt.height, TextureFormat.RGB24, false);
    9.         tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
    10.         RenderTexture.active = null;
    11.  
    12.         byte[] bytes;
    13.         bytes = tex.EncodeToPNG();
    14.        
    15.         string path = AssetDatabase.GetAssetPath(rt) + ".png";
    16.         System.IO.File.WriteAllBytes(path, bytes);
    17.         AssetDatabase.ImportAsset(path);
    18.         Debug.Log("Saved to " + path);
    19.     }
    20.  
    21.     [MenuItem("Assets/Save RenderTexture to file", true)]
    22.     public static bool SaveRTToFileValidation()
    23.     {
    24.         return Selection.activeObject is RenderTexture;
    25.     }
    26. }
     
  8. JudahMantell

    JudahMantell

    Joined:
    Feb 28, 2017
    Posts:
    476
    Thanks for the example, I appreciate it!
     
  9. unity_BBC92DE73332ECB1B686

    unity_BBC92DE73332ECB1B686

    Joined:
    Jul 4, 2022
    Posts:
    2
    Hi, I am also working on painting pixels on textures. Can you give a full working example of shader for efficiently manipulation of texture pixels at runtime?
    Thanks.
     
    Last edited: Mar 6, 2023