Search Unity

  1. Unity 2020.1 has been released.
    Dismiss Notice
  2. We are looking for feedback on the experimental Unity Safe Mode which is aiming to help you resolve compilation errors faster during project startup.
    Dismiss Notice
  3. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

Texture.setPixels()

Discussion in 'Scripting' started by spencer_white, Sep 14, 2016.

  1. spencer_white

    spencer_white

    Joined:
    Nov 9, 2015
    Posts:
    37
    First off, let me say this, it makes no sense why Unity uses a one dimensional array to set pixels. It should be that you use a two dimensional array, but I'll have to work with Unity's system.

    Anyway, I tried making something where I would pass in a 2 dimensional array, like
    Code (CSharp):
    1. Color[,] newImage = new Color[,] {
    2.             { Color.red, Color.blue },
    3.             { Color.green, Color.black }
    4.         };
    And it would create a custom texture that looked like that; Top left is red, Top Right is blue, Bottom Left is green, and Bottom Right is black.

    So, I wrote this method up.
    Code (CSharp):
    1. void changeImage(Color[,] image) {
    2.  
    3.         int width = image.GetLength(0);
    4.         int height = image.GetLength(1);
    5.  
    6.         Renderer renderer = GetComponent<Renderer>();
    7.  
    8.         Texture2D newTexture = new Texture2D(width, height);
    9.        
    10.         Color[] imageOneD = new Color[width * height];
    11.        
    12.         for (int x = 0; x < width; x++) { //then, while Y is 0, x will go to 0 and 1
    13.             for (int y = 0; y < height; y++) {
    14.                 imageOneD[(width * y) + x] = image[x, y]; //doing (0, 1), then (1, 1)...WHY!?!?
    15.                 print(image[x, y]);
    16.                 print(imageOneD[width * y + x] + " ");
    17.             }
    18.         }
    19.  
    20.         renderer.material.shader = Shader.Find("Unlit/Texture");
    21.  
    22.         newTexture.filterMode = FilterMode.Point;
    23.         newTexture.SetPixels(0, 0, width, height, imageOneD);
    24.         newTexture.Apply();
    25.  
    26.         renderer.material.mainTexture = newTexture;
    27.     }
    So, what I am doing is flattening the 2 dimensional array into a 1 dimensional array so that I can use it with texture.setPixels, but when I look at the object I am using it on, it looks like this:

    blue black
    red green,

    when it should be:

    red green
    blue black

    So...What happened? Well, it is switched. The first row is on the bottom, and the second row is on the top. Can someone help?
     
  2. spencer_white

    spencer_white

    Joined:
    Nov 9, 2015
    Posts:
    37
    Edit: Ok, so, I gave it a new image array for testing, I gave it this:

    { Color.red, Color.blue, Color.gray },
    { Color.green, Color.black, Color.white }

    And, the output is a 2x3 image with gray and white on top...So...It must be rotated weirdly. I don't want to have to rotate the actual gameObject, so, can someone help?
     
  3. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Its because a texture's rectangle starts in the bottom left corner and goes to the top right corner.

    When your passing in that array:
    Your probably wanting red,blue,gray to be on top. But since its the first Colors in your array they start at the bottom left and tile their way up to the top.

    You either need to pass your array in to match the bottom->top drawing of a texture or flip it like this:
    Code (CSharp):
    1. for (int x = 0; x < width; x++) { //then, while Y is 0, x will go to 0 and 1
    2.             for (int y = 0; y < height; y++) {
    3.                 // Read from the end of the image array to get the
    4.                 // bottom first
    5.                 imageOneD[(width * y) + x] = image[x, (height-1-y]; //doing (0, 1), then (1, 1)...WHY!?!?
    6.                 print(image[x, y]);
    7.                 print(imageOneD[width * y + x] + " ");
    8.             }
    9.         }
    Another side note:
    Code (CSharp):
    1. newTexture.SetPixels(0, 0, width, height, imageOneD);
    is the same as
    Code (CSharp):
    1. newTexture.SetPixels( imageOneD);
    You only need to specify a start x,y and a width/height if your setting a partial block of the texture. SetPixels assumes your setting the whole thing.
     
  4. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Just noticed your loop is inverted as well thats why you are getting the 3x2 flipped into a 2x3:
    Code (CSharp):
    1. for (int y=0;y<height;y++)
    2. {
    3.          for (int x=0;x < width;x++)
    4.          {
    5.                   imageOneD[width*y]+x] = image[x,(height-1-y)];
    6.           }
    7. }
    This code reads the entire last row of your array and copies it into the "first row" of the OneD array. That way your bottom row is the bottom row of the image.
     
  5. spencer_white

    spencer_white

    Joined:
    Nov 9, 2015
    Posts:
    37
    Thanks, but, this doesn't work how I would expect... It's still a 2x3 image (Should be 3x2, with red, blue, and grey on top), but now, it's still a 2x3, but with grey and white on bottom now.

    This is the code so far:

    Code (CSharp):
    1. for (int y = 0; y < height; y++) {
    2.             for (int x = 0; x < width; x++) {
    3.                 imageOneD[(width * y) + x] = image[x, height - 1 - y];
    4.                 print(image[x, y]);
    5.                 print(imageOneD[width * y + x] + " ");
    6.             }
    7.         }
     
  6. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Its probably this:
    Code (CSharp):
    1. int width = image.GetLength(0);
    2. int height = image.GetLength(1);
    GetLength(0) returns the rows -- in your case height
    GetLength(1) returns the columsn in your case that would be width. Try:
    Code (CSharp):
    1. int height = image.GetLength(0);
    2. int width = image.GetLength(1);
     
  7. spencer_white

    spencer_white

    Joined:
    Nov 9, 2015
    Posts:
    37
    I'm sorry, I've been trying to debug this for a few, but whenever I do what you just said, it always gives me an index out of range on this line:

    Code (CSharp):
    1. imageOneD[(width * y) + x] = image[x, y];
    I can't say I know why, though. I used a try catch, and they x, y of the error is 2, 0. If I do what you just said, (Width * y) + x is (3 * 0) + 2...Which should NOT be an index out of range.
     
  8. spencer_white

    spencer_white

    Joined:
    Nov 9, 2015
    Posts:
    37
    Though, you were not wrong. After printing off the values, I found out that the width and height are correct now...Just need to find out why it errors.
     
  9. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    So I was both right and wrong about the GetLength calls. GetLength(0) does in fact get the length of first index of the array (what we were calling x). GetLength(1) does return the second index. (y).

    The problem was in how you initialized the array.
    After looking up initializing arrays (never done it really) C# does it column by column so your initalization makes 2 Columns (x value) with 3 items each (y value) so its a 2x3 array if your thinking of it in terms of Color[2,3]
    creates an array that looks like this:
    RED (0,0) GREEN (1,0)
    BLUE(0,1) BLACK (1,1)
    GRAY(0,2) WHITE (1,2)

    Which is why it was working when you had the width/height backwards.. because your initalization was "backwards" too.


    So to create this setup of colors:
    RED (0,0) BLUE (1,0) GRAY(2,0)
    GREEN(0,1) BLACK(1,1) WHITE (2,1) you need this:
    Code (CSharp):
    1. Color[,] image = new Color[,] { { Color.red, Color.green}, { Color.blue, Color.black }, {Color.gray, Color.white } };
    This sets up a 3x2 array. The first row (y value =0) takes the first item from each set. The 2nd row (y=1) takes the 2nd item from each set. If you initalize your colors like above and flip the width/height back to the original way you had it :
    Code (CSharp):
    1. int width = image.GetLength(0);
    2.         int height = image.GetLength(1);
    Then the loop I wrote should work. Its all about making sure you understand the implementation of how the 2D array is setting things up.. and how you read it back out.
     
  10. spencer_white

    spencer_white

    Joined:
    Nov 9, 2015
    Posts:
    37
    Thanks, this works great! On thing I will try to do is write something to rotate it, because, when I initialize the array, I want it to look like the final image.
     
  11. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Well if you initialize it like this:
    Code (CSharp):
    1. { Color.red, Color.blue, Color.gray },
    2. { Color.green, Color.black, Color.white }
    Then i think this loop will create the texture to look like your initialization
    Red,blue,gray across the top, then green,black,white across the bottom.
    if
    Code (CSharp):
    1. int width = image.GetLength(0);
    2. int height = image.GetLength(1);
    3.  
    4. int count =0;
    5. for (int x=0;x<width;x++)
    6.      for (int y=0;y<height;y++)
    7.               imageOneD[count++] = image[width-1-x,y];
    That is assumine imageOneD is applied to a 3x2 texture. (Note: the image your passing in is a 2x3.. but that doesn't matter. we are flipping it around to work into a 3x2)
     
    biggs64 likes this.
  12. moodymagyar

    moodymagyar

    Joined:
    Oct 23, 2017
    Posts:
    27
    WOW! I know I'm way late to this post, but, I just want to say I am really impressed by the amount of effort that went into answering this question. Very cool to see.
     
    JohnnyFactor and biggs64 like this.
  13. JohnnyFactor

    JohnnyFactor

    Joined:
    May 18, 2018
    Posts:
    183
    It's still ridiculous that we have to write up an array converter for a basic feature, especially when Unity suggests using this instead of looping GetPixel.
     
unityunity