Software Generating of Mip Maps

Started by
18 comments, last by AhmedSaleh 11 months, 4 weeks ago

I'm tyring to do mip maps generation, and would like to average a 2x2 block. The current code crashes as out of bounds exception.

 for (int mip = 0; mip < levelCount; mip++)
                        {
                            uint nWidth = Math.Max((width) / ((uint)1 << mip), 1);
                            uint nHeight = Math.Max((height) / ((uint)1 << mip), 1);

                            byte[] imageData = new byte[nWidth * nHeight * formatInfo.BPP];

                            for (int y = 0; y < nWidth; y ++)
                            {
                                for (int x = 0; x < nHeight; x ++)
                                {
                                   int dst_index_x = x;
                                   int dst_index_y = y;
                                   int src_index_x = dst_index_x;
                                   int src_index_y = dst_index_y ;

                                     float filtered_1 = (data[src_index_x + src_index_y]);
                                     float filtered_2 = (data[src_index_x + 1]   + data[src_index_x + src_index_y]);
                                     float filtered_3 = (data[src_index_x] +  data[src_index_x + 1 + src_index_y ]);
                                     float filtered_4 = (data[src_index_x + 1] +  data[(src_index_x + 1) + (src_index_y + 1)]);

                                   float filtered = (filtered_1 + filtered_2 + filtered_3 + filtered_4)*0.25f;

                                    imageData[x + y* nWidth] = (byte)filtered;

                                }  
                            }
Game Programming is the process of converting dead pictures to live ones .
Advertisement

AhmedSaleh said:
The current code crashes as out of bounds exception.

It looks like it would indeed.

when x == nWidth - 1 and y == nHeight - 1, what pixel do you expect to address with data[(src_index_x + 1) + (src_index_y + 1)] ?

For simplicity use a very small area for example 2x2 or 3x2 or 3x3 pixels . In that way you can easily draw things at paper and count pixels manually to see what pixels you access (or try to access)..

It looks like your code is missing a few things:

  • Not multiplying by the image row size (aka pitch) when calculating a 1D index from an (x,y) pixel position. It should be something like: data[y*rowPitch + x*bytesPerPixel], where rowPitch = width*bytesPerPixel. Your code is doing data[y + x], which doesn't work if y is not 0.
  • Not dividing the source indices by 2 to get destination indices (or multiplying destination indices by 2 to get source indices). If destination indices are in range [0,N-1], then source indices should be in range [0,2*N-1].
  • It assumes that there is only 1 color channel, or 1 byte per pixel. You need to introduce a concept of a pixel size and/or channel count to handle normal RGB images.
  • You should round to the nearest integer, rather than simple cast. You can do that by adding 0.5 before casting.
  • If the image data is in sRGB color space (most color images), then you need to convert each pixel to a linear color space inside in the loops before doing the filtering. Then, after computing filtered value, you need to convert back to sRGB space before converting to byte. This is necessary for proper filtering of the mip maps. Believe it or not, professional software like Photoshop doesn't do this correctly.
  • You will get the best quality result if you downsample directly from the original full-size image, rather than the previous mip. Using the previous mip causes drift in the pixel values and can result in a blurrier image. The downside is that it's slower and the code becomes a fair bit more complex to handle filter kernels of any size, because your 2x2 kernel may have a much larger footprint in the original image.

@aressera

Many thanks, appreciate it.

I'm not sure about number 3, which is assuming one color channel. How should I do that ?

I'm modifying the code after your suggestions. Most textures that I get are HDR float or SDR, Please note that also.

for (int mip = 0; mip < levelCount; mip++)
                       {
                           uint nWidth = Math.Max((width) / ((uint)1 << mip), 1);
                           uint nHeight = Math.Max((height) / ((uint)1 << mip), 1);

                           byte[] imageData = new byte[nWidth * nHeight * formatInfo.BPP];

                           for (int y = 0; y < nWidth; y ++)
                           {
                               for (int x = 0; x < nHeight; x ++)
                               {
                                  int dst_index_x = x;
                                  int dst_index_y = y;
                                  int src_index_x = dst_index_x*2;
                                  int src_index_y = dst_index_y*2 ;
                                  int row_pitch = nWidth * 8; // 8 Bytes Per Pixel 64Bit Image
          int BPP = 8;
                                    float filtered_1 = (data[src_index_x*BPP + src_index_y*row_pitch]);
                                    float filtered_2 = (data[src_index_x*BPP + 1]   + data[src_index_x*BPP + src_index_y*row_pitch]);
                                    float filtered_3 = (data[src_index_x*BPP   ] +  data[src_index_x*BPP + 1 + src_index_y*row_pitch ]);
                                    float filtered_4 = (data[src_index_x*BPP + 1] +  data[(src_index_x*BPP + 1) + (src_index_y*row_pitch + 1)]);

                                  float filtered = (filtered_1 + filtered_2 + filtered_3 + filtered_4)*0.25f;

                                   imageData[x + y* nWidth] = (byte)(0.5f+filtered);

                               }  
                           }
Game Programming is the process of converting dead pictures to live ones .

This is the best that I could, but still its broken

                for (int mip = 0; mip < levelCount; mip++)
                        {
                            uint nWidth = Math.Max((width) / ((uint)1 << mip), 1);
                            uint nHeight = Math.Max((height) / ((uint)1 << mip), 1);

                            byte[] imageData = new byte[nWidth * nHeight * formatInfo.BPP];

                            for (int y = 0; y < nWidth; y ++)
                            {
                                for (int x = 0; x < nHeight; x ++)
                                {
                                   int dst_index_x = x;
                                   int dst_index_y = y;
                                   int src_index_x = dst_index_x * 2;
                                   int src_index_y = dst_index_y * 2;
                                   uint bpp = (uint)formatInfo.BPP >> 2;
                                   float filtered = 0;
                                   for (int i = 0; i < bpp; i++)
                                   {
                                       uint row_pitch = (uint)(bpp * width); // 8 Bytes Per Pixel 64Bit Image
                                       float filtered_1 = (data[src_index_x * bpp + src_index_y * row_pitch]);
                                       float filtered_2 = (data[src_index_x * bpp + 1] + data[src_index_x * bpp + src_index_y * row_pitch]);
                                       float filtered_3 = (data[src_index_x * bpp] + data[src_index_x * bpp + 1 + src_index_y * row_pitch]);
                                       float filtered_4 = (data[src_index_x * bpp + 1] + data[(src_index_x * bpp + 1) + (src_index_y * row_pitch + 1)]);

                                       filtered = (filtered_1 + filtered_2 + filtered_3 + filtered_4) * 0.25f;
                                       imageData[x * bpp + y * nWidth * bpp * i] = (byte)(0.5f + filtered);

                                   }

                               }
                           }

Game Programming is the process of converting dead pictures to live ones .
for (int i = 0; i < bpp; i++) {
 ....
 imageData[x * bpp + y * nWidth * bpp * i]  // Really? bpp*bpp ?

For debugging stuff like this, add code like

constexpr SIZE = nWidth * nHeight * formatInfo.BPP;
byte[] imageData = new byte[SIZE];

byte read(byte[] imageData, uint offset) {
    if (offset >= SIZE) { printf("Read: Offset too large\n"); return 0; }
    printf("Reading imageData[%d]\n", offset);
    return imageData[offset];
}

void write(byte[] imageData, byte value, uint offset) {
    if (offset >= SIZE) { printf("Write: Offset too large\n"); return; }
    printf("Writing imageData[%d]\n", offset);
    imageData[offset] = value; // This isn't quite needed as you're not interested in the values afterwards so you can leave it out.
}

Replace the array access by read and write functions.

Inside the inner loop before the "rowPitch" assignment, insert

printf("\n");
printf("y = %d\n", y);
printf("x = %d\n", x);
printf("bpp = %d\n", bpp);
printf("width = %d\n", width);
printf("dst_index_x = %d\n", dst_index_x);
printf("dst_index_y = %d\n", dst_index_y);
printf("src_index_x = %d\n", src_index_x);
printf("src_index_y = %d\n", src_index_y);
printf("i = %d\n", i);

Try 1 mip value at a time, make your image small (less than 10 width/height if possible).

Run the code, you'll get a lot of output but no crash if the crash is in the inner loop. Afterwards you can exactly read what was done and what access was out of bounds.

The above is called “printf debugging”, as you use printf to get additional information about what is happening to find the cause. An alternative to that is using a debugger. The latter is useful if you mostly know the precise circumstances of the problem.

Some debuggers can stop execution when a crash is about to happen. If you have one of those, that would also be a good option to use.

@Alberth Please note that I sometimes work with 64bit images.. it's not that easy :'(

Game Programming is the process of converting dead pictures to live ones .

This is my trial so far

for (int mip = 0; mip < levelCount; mip++)
                        {
                            uint nWidth = (Math.Max((width) / ((uint)1 << mip), 1));
                            uint nHeight = (Math.Max((height) / ((uint)1 << mip), 1));

                            byte[] imageData = new byte[nWidth * nHeight * formatInfo.BPP];
                            uint bpp = (uint)formatInfo.BPP;

                            for (int y = 0; y < nHeight; y++)
                            {
                                for (int x = 0; x < nWidth; x++)
                                {

                                    int dst_index_x = x;
                                    int dst_index_y = y;
                                    int src_index_x = dst_index_x * 2;
                                    int src_index_y = dst_index_y * 2;
                                    float filtered = 0;
                                    for (int i = 0; i < bpp; i++)
                                    {
                                        uint row_pitch = (uint)(bpp * width); // 8 Bytes Per Pixel 64Bit Image
                                        float filtered_1 = (data[src_index_x  + src_index_y * row_pitch]);
                                        float filtered_2 = (data[src_index_x  + i] + data[src_index_x  + src_index_y * row_pitch]);
                                        float filtered_3 = (data[src_index_x] + data[src_index_x  + i + src_index_y * row_pitch]);
                                        float filtered_4 = (data[src_index_x + i] + data[(src_index_x + i) + (src_index_y * row_pitch + i)]);

                                        filtered = (filtered_1 + filtered_2 + filtered_3 + filtered_4) * 0.25f;
                                        imageData[(x + y * nWidth) * bpp + i] = (byte)(filtered + 0.5f);
                                    }
                                }
                            }
Game Programming is the process of converting dead pictures to live ones .

AhmedSaleh said:
Please note that I sometimes work with 64bit images.. it's not that easy

No doubt it's not trivial, but that doesn't mean crowd-sourcing bugfixes is a good way to debug code.

The idea here is that you learn how to fish for bugs yourself rather than relying on others to give you a bug fish every day. Becoming better at bug-hunting makes you understand coding better, you'll grow as programmer.

We can discuss how to debug code, but please don't assume anyone will fix your bugs for free, it's not going to work eventually.

Alberth said:
We can discuss how to debug code, but please don't assume anyone will fix your bugs for free, it's not going to work eventually.

It's never going to work.

Anyways my advice for @ahmedsaleh you do NOT need to generate the bottom most level of mipmap pyramid - i.e. level 0.

My current blog on programming, linux and stuff - http://gameprogrammerdiary.blogspot.com

This topic is closed to new replies.

Advertisement