0

I'm trying to generate the mipmap of an image. The pixels are stored as a byte[] and the format is {r,g,b,a,r,g,b,a,r,g,b,a ... }

What this is trying to do is get each group of four pixels in the image and find the average of those four pixels, then put that into a new image.

The result of creating all the mipmaps for a sample texture are here: https://i.stack.imgur.com/25Ad7.jpg

If there is a way to create the mipmaps without using my own algorithm, and without directx or anything (i'm not using the mipmaps for rendering, i'm saving them to a file) that would be good

public static byte[] mipmap(byte[] inPixels, int width, int height)
{
    // add one to width and height incase they are 1
    byte[] outPixels = new byte[((width + 1) / 2) * ((height + 1) / 2) * 4];
    for (int y = 0; y < height; y += 2)
    {
        for (int x = 0; x < width; x += 2)
        {
            // get the four red values
            int[] r = new int[4];
            r[0] = (int)inPixels[x + y * width + 0]; // top left
            r[1] = (int)inPixels[(x + 1) + y * width + 0]; // top right
            r[2] = (int)inPixels[(x + 1) + (y + 1) * width + 0]; // bottom right
            r[3] = (int)inPixels[x + (y + 1) * width + 0]; // bottom left

            // get the four green values
            int[] g = new int[4];
            g[0] = (int)inPixels[x + y * width + 1]; // top left
            g[1] = (int)inPixels[(x + 1) + y * width + 1]; // top right
            g[2] = (int)inPixels[(x + 1) + (y + 1) * width + 1]; // bottom right
            g[3] = (int)inPixels[x + (y + 1) * width + 1]; // bottom left

            // get the four blue values
            int[] b = new int[4];
            b[0] = (int)inPixels[x + y * width + 2]; // top left
            b[1] = (int)inPixels[(x + 1) + y * width + 2]; // top right
            b[2] = (int)inPixels[(x + 1) + (y + 1) * width + 2]; // bottom right
            b[3] = (int)inPixels[x + (y + 1) * width + 2]; // bottom left

            // get the four alpha values
            int[] a = new int[4];
            a[0] = (int)inPixels[x + y * width + 3]; // top left
            a[1] = (int)inPixels[(x + 1) + y * width + 3]; // top right
            a[2] = (int)inPixels[(x + 1) + (y + 1) * width + 3]; // bottom right
            a[3] = (int)inPixels[x + (y + 1) * width + 3]; // bottom left

            // the index in the new image, we divide by 2 because the image is half the size of the original image
            int index = (x + y * width) / 2;
            outPixels[index + 0] = (byte)((r[0] + r[1] + r[2] + r[3]) / 4);
            outPixels[index + 1] = (byte)((g[0] + g[1] + g[2] + g[3]) / 4);
            outPixels[index + 2] = (byte)((b[0] + b[1] + b[2] + b[3]) / 4);
            outPixels[index + 3] = (byte)((a[0] + a[1] + a[2] + a[3]) / 4);
        }
    }
    return outPixels;
}
Marc
  • 3,905
  • 4
  • 21
  • 37
Tom Tetlaw
  • 83
  • 1
  • 10
  • 21

1 Answers1

0

I think the problem lies here:

inPixels[x + y * width + 0]

normally this runs correct when one array element is one pixel, only one element is one channel of one pixel. So each pixel starts a (x + (y * width)) * 4 so it should be something like this:

inPixels[((x + y * width) * 4) + 0]

I wrote some code for optimalization maybe you get some extra ideas, but i did not test it:

public static byte[] mipmap(byte[] inPixels, int width, int height)
{
    // add one to width and height incase they are 0
    byte[] outPixels = new byte[((width / 2) * (height / 2)) * 4];

    // the offsets of the neighbor pixels (with *4 for the channel)
    // this will automatically select a channel of a pixel one line down.
    int[] neighborOffsets = new int[] { 0 * 4, 1 * 4, width * 4, (width + 1) * 4 };

    // an 'offset for output buffer'
    int outputOffset = 0;

    // an 'offset for input buffer'
    int inputOffset = 0;

    for (int y = 0; y < height / 2; y++)
    {
        for (int x = 0; x < width / 2; x++)
        {

            // calculate the average of each channel
            for (int channelIndex = 0; channelIndex < 4; channelIndex++)
            {
                int totalValue = 0;

                for (int offset = 0; offset < 4; offset++)
                    totalValue = (int)inPixels[inputOffset + neighborOffsets[offset] + channelIndex];

                // write it to the ouput buffer and increase the offset.
                outPixels[outputOffset++] = (byte)(totalValue / 4);

            }
            // set the input offset on the next pixel. The next pixel is current + 2.
            inputOffset += 2 * 4; // *4 for the channelcount (*4 will be optimized by the compiler)
        }
        inputOffset += width * 4; // skip an extra line. *4 for the channelcount (*4 will be optimized by the compiler)
    }

    return outPixels;
}

There maybe some little mistakes in it, but it probably run 4 times as fast. Try to avoid redundancy on multiplications.

Jeroen van Langen
  • 21,446
  • 3
  • 42
  • 57
  • I tried yours (needed to add the +1's to handle the case in the comment above or I get the exception) and I get this: http://imgur.com/BHeCl7u – Tom Tetlaw Sep 16 '13 at 10:53
  • I thinking what that could be. I don't know why it gets transparent, because (4*255)/4 = 255 ofcourse. Hard to find without running code. – Jeroen van Langen Sep 16 '13 at 11:01
  • Are you using the BitmapData class for getting the bytes? If your bitmap has a 'strange' width. It can be wrong aligned to the bitmap. Because the BitmapData uses a Stride for scanlines. more info: http://stackoverflow.com/questions/2004602/any-can-explain-the-function-of-stride-in-bitmapdata But i don't know if that is involved here. – Jeroen van Langen Sep 16 '13 at 11:10
  • Can you update you post as it is right now? Like to see the BitmapData.Lockbits also. – Jeroen van Langen Sep 16 '13 at 11:22