Texture2D.SetPixels : "Array size must be at least width*height" but values seems good

Hello,
I am trying to recover a Texture2D to add white pixels at the top and bottom to have several Texture2D of the same size.
The problem I have is that an error occurs when I try to set the pixels of the Texture2D in the new except when yTopDiff is equal to 0 while I check that
textureSpace.height - yBotDiff - yTopDiff * textureSpace.width = number of pixels of measurePixels.

For this example:
yTopDiff = 58
textureSpace.width = 688
textureSpace.height = 300
yBotDiff = 15
textureSpace.height -yBotDiff=285
measurePixels = 156176 Color


Screenshots of code are not a thing. If you post a code snippet, ALWAYS USE CODE TAGS:

How to use code tags: Using code tags properly

Your terminating conditions for x seem wrong: you are doing <= width

Not sure if that’s the actual problem.

Either way, you’re doing a lot of whacky math with diffs and deltas… I think the only way you will track this down is to start debugging the actual numerics directly.

You must find a way to get the information you need in order to reason about what the problem is.

What is often happening in these cases is one of the following:

  • the code you think is executing is not actually executing at all
  • the code is executing far EARLIER or LATER than you think
  • the code is executing far LESS OFTEN than you think
  • the code is executing far MORE OFTEN than you think
  • the code is executing on another GameObject than you think it is
  • you’re getting an error or warning and you haven’t noticed it in the console window

To help gain more insight into your problem, I recommend liberally sprinkling Debug.Log() statements through your code to display information in realtime.

Doing this should help you answer these types of questions:

  • is this code even running? which parts are running? how often does it run? what order does it run in?
  • what are the values of the variables involved? Are they initialized? Are the values reasonable?
  • are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

Knowing this information will help you reason about the behavior you are seeing.

You can also put in Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene

You could also just display various important quantities in UI Text elements to watch them change as you play the game.

If you are running a mobile device you can also view the console output. Google for how on your particular mobile target.

Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

Here’s an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

You’re not even using measurePixels, btw. I’m not sure what the goal of this code is (it seems to be attempting to put white bars on top and bottom of the texture?) but there’s no reason ever to combine SetPixel with SetPixels. SetPixel is agonizingly slow if you’re using it more than, like, twice; if you’re going to be calling Setpixels, then what you want to be doing is reading out the entire texture, modifying the array you have read out, and then applying that whole array back with SetPixels.

This is part of the problem. You’re reading out the entire texture into measurePixels, and then trying to apply it back to only a subset of the texture (via your yTopDiff/yBotDiff math). There’s too many pixels in that array for the space you’re trying to put them into, so SetPixels assumed you’ve done something wrong, which you have.

Basically:

  1. Trash your SetPixel calls.
  2. Replace them with modifications to the measurePixels array. Something like
measurePixels[y * width + x] = Color.white;
  1. Move your SetPixels to the end of the whole process (just before Apply), and use it to set the entire texture, not just the subset.
public void ResizeAndFillAll(List<Texture2D> textures, int yTopMax, int yBotMax)
    {
        int textureId = 0;
        foreach (Texture2D texture in textures)
        {
            int yTopDiff = yTopMax - yTopsDiff[textureId];
            int yBotDiff = yBotMax - yBotsDiff[textureId];
            Texture2D textureSpace = new Texture2D(texture.width, texture.height + yTopDiff + yBotDiff);
            Color[] measurePixels = texture.GetPixels(0, 0, texture.width, texture.height);

            for (int y = 0; y <= yTopDiff-1; y++)
            {
                for (int x = 0; x <= textureSpace.width; x++)
                {
                    textureSpace.SetPixel(x, y, Color.white);
                }
            }
          
            textureSpace.SetPixels(0, yTopDiff, textureSpace.width, textureSpace.height - yBotDiff, measurePixels);

            for (int y = textureSpace.height - yBotDiff+1; y <= textureSpace.height; y++)
            {
                for (int x = 0; x <= textureSpace.width; x++)
                {
                    textureSpace.SetPixel(x, y, Color.white);
                }
            }

            textureSpace.Apply();
            Debug.Log("texture id : " + textureId);
              
            textureId++;
        }
    }

Here is the the with the code Tags, I did not know that existed

I checked everything you told me to check and I didn’t see any error (my x <= width allow me to complete a full line of white pixel).
Actually, as long as my SetPixels has an area of width and height and an array that equals width*height, shouldn’t that work?

Oh, hang on, I misunderstood something about your code. It’s still the wrong approach and you should still not be using SetPixel here, but in a different way than I thought.

Since this is in a loop and ‘texture’ doesn’t change, start by putting that GetPixels before the loop.
Now, inside the loop, you’ll want to manually create a new Color[ ] array, whose size will be your desired size.
Then write your Color.white’s and your old pixels into the appropriate place in the new array.
Use SetPixels on the new array.

I’d advise putting all your pixel mods into one nested for loop, looking something like this:

int newTextureHeight = texture.height + yTopDiff + yBotDiff;
int width = texture.width;
Color[] newTex = new Color[newTexutreHeight * newTextureWidth];

for (int y=0; y<newTextureHeight; y++) {
   for (int x=0; x<newTextureWidth; x++) {
      int index = y * width + x;
      if (y < yTopDiff) newTex[index] = Color.white;
      else if (y > texture.height + yTopDiff) newTex[index] = Color.white;
      else {
         int mpIndex = (y - yTopDiff) * width + x;
         newTex[index] = measurePixels[mpIndex];
      }
   }
}

BTW, while typing this, I noticed there were some <='s in your for loops, which is incorrect. A texture of 512x512 goes 0 to 511. Use < instead.

I’m honestly unsure of the source of your exception, as it does look like the math is about right, but it’s probably an off-by-one error somehow. I’m also a bit surprised you didn’t get an exception on your SetPixel calls in your loops with <=, because that certainly looks like it’d be attempting to write a pixel one off the edge of the texture. Anyway, if you take my advice, you’d be dumping SetPixel entirely and using different math, so the question is kinda moot.

Even if I haven’t finished yet, I understood what you said (even if your way of coding was a bit hard to understand for a newbie like me x)) and I manage to display the result which is almost as wanted.
For the setPixel, it also seemed weird to me, so I tried with an x greater than the coordinates of my new 2D texture (like 500 on a width of 288) and it didn’t give me an error.
Anyway, thank you for your answer, it helped me a lot ! :slight_smile:

If someone needs a generic texture resampler that can either crop the image to a new resolution of letterbox it, I’ve posted a solution on UA. The optimised version is here on my dropbox. It performs a billinear rescaling of the texture to match the target size, however the texture is never distorted. Both methods either crop equally from the top / bottom ot left / right or put a letter box equally instead.

Of course if no scaling is necessary it could be simplified a bit. That means the bilinear filtering is not necessary and the target texture size has to match either the original width or height. ResampleAndLetterbox is probably the most useful method for arranging arbitrary images so they all have the same size. ResampleAndCrop is probably more useful for something like profile pictures.

SetPixel does use the texture wrapmode AFAIK. So if the texture is set to “Clamp”, it would just overwrite the last pixel row. It it’s set to Repeat it would write the pixels that are out of bounds on the right side to the left side of the texture.