Miko Kronn
Miko Kronn

Reputation: 2454

Why do I keep getting exceptions?

There is some code:

struct BitmapDataAccessor
{
    private readonly byte[] data;
    private readonly int[] rowStarts;
    public readonly int Height;
    public readonly int Width;
    public readonly int Padding;

    public BitmapDataAccessor(byte[] data, int width, int height, int padding)
    {
        this.data = data;
        this.Height = height;
        this.Width = width + (4-padding)%4;
        this.Padding = padding;
        rowStarts = new int[Height];
        for (int y = 0; y < Height; y++)
            rowStarts[y] = y * Width;
    }

    public byte this[int x, int y, int color] // Maybe use an enum with Red = 0, Green = 1, and Blue = 2 members?
    {
        get { return data[(rowStarts[y] + x) * 3 + color]; }
        set { data[(rowStarts[y] + x) * 3 + color] = value; }
    }

    public byte[] Data
    {
        get { return data; }
    }
}

public static byte[, ,] Bitmap2Byte(Bitmap obraz)
{
    int h = obraz.Height;
    int w = obraz.Width;

    byte[, ,] wynik = new byte[w, h, 3];

    BitmapData bd = obraz.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

    int bytes  = Math.Abs(bd.Stride) * h;

    byte[] rgbValues = new byte[bytes];
    IntPtr ptr = bd.Scan0;

    System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);

    int padding = Math.Abs(bd.Stride) - (((w * 24) + 7) / 8);

    BitmapDataAccessor bda = new BitmapDataAccessor(rgbValues, w, h, padding);

    for (int i = 0; i < h; i++)
    {
        for (int j = 0; j < w; j++)
        {
            wynik[j, i, 0] = bda[j, i, 2];
            wynik[j, i, 1] = bda[j, i, 1];
            wynik[j, i, 2] = bda[j, i, 0];
        }
    }

    obraz.UnlockBits(bd);
    return wynik;
}

And this is test I run:

private void btnLoad_Click(object sender, EventArgs e)
{
    string s = "";
    for (int i = 400; i < 409; i++)
    {
        Bitmap bmp = new Bitmap(i, i);
        bool ok = true;
        try
        {
            byte[,,] tab = Grafika.Bitmap2Byte(bmp);
        }
        catch
        {
            ok = false;
        }

        s += i + ": ";
        if (ok) s += "OK";
        else s += "NOT OK";
        s += "\n";
    }

    StreamWriter w = StreamWriter(@"C:\lalala.txt");
    w.Write(s);
    w.Close();
    return;
}

Output:

400: OK
401: NOT OK
402: NOT OK
403: OK
404: OK
405: NOT OK
406: NOT OK
407: OK
408: OK

Why do I get exceptions for w % 4 ==1 or w % 4 == 2?

EDIT:
I know what the exception is. It is 'Index out of bounds' and it happens in line
get { return data[(rowStarts[y] + x) * 3 + color]; }.
Unfortunately I don't know how to avoid this and why is it happening only for w % 4 ==1 or w % 4 == 2. That is why I posted my question. I don't need help with identyfying the exception or handling it. I just need to fix BitmapDataAccessor and/or Bitmap2Byte to make it work.

EDIT 2: Thanks to max's answer Bitmap2Byte doesn't throw Exceptions. But this makes my other function Byte2Bitmap Throw them:

    public static Bitmap Byte2Bitmap(byte[, ,] tablica)
    {
        if (tablica.GetLength(2) != 3)
        {
            throw new NieprawidlowyWymiarTablicyException();
        }

        int w = tablica.GetLength(0);
        int h = tablica.GetLength(1);


        int padding = w % 4;
        int ww=w;

        if (padding != 0)
            ww += 4 - padding;

        int bytes = 3 * ww * h;
        byte[] rgbValues = new byte[bytes];

        int counter = -3;

        for (int j = 0; j < h; j++)
        {
            for (int i = 0; i < w; i++)
            {
                counter += 3;
                rgbValues[counter] = tablica[i, j, 2];
                rgbValues[counter + 1] = tablica[i, j, 1];
                rgbValues[counter + 2] = tablica[i, j, 0];                    
            }                

            if(padding!=0)
                counter+= padding;
        }


        Bitmap obraz = new Bitmap(w, h, PixelFormat.Format24bppRgb);
        BitmapData bd = obraz.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);            
        IntPtr ptr = bd.Scan0;

        System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);

        obraz.UnlockBits(bd);

        return obraz;
    }

And my output to show for which widths there is exception:

380: OK
381: NOT OK: Nastąpiła próba odczytu lub zapisu pamięci chronionej. Często wskazuje to, że inna pamięć jest uszkodzona.
382: OK
383: OK
384: OK
385: NOT OK: Nastąpiła próba odczytu lub zapisu pamięci chronionej. Często wskazuje to, że inna pamięć jest uszkodzona.
386: OK
387: OK
388: OK
389: NOT OK: Nastąpiła próba odczytu lub zapisu pamięci chronionej. Często wskazuje to, że inna pamięć jest uszkodzona.
390: OK
391: OK
392: OK
393: OK
394: NOT OK: Nastąpiła próba odczytu lub zapisu pamięci chronionej. Często wskazuje to, że inna pamięć jest uszkodzona.
395: OK
396: OK
397: NOT OK: Nastąpiła próba odczytu lub zapisu pamięci chronionej. Często wskazuje to, że inna pamięć jest uszkodzona.
398: OK
399: OK
400: OK
401: NOT OK: Nastąpiła próba odczytu lub zapisu pamięci chronionej. Często wskazuje to, że inna pamięć jest uszkodzona.
402: OK
403: OK
404: OK
405: NOT OK: Nastąpiła próba odczytu lub zapisu pamięci chronionej. Często wskazuje to, że inna pamięć jest uszkodzona.
406: NOT OK: Nastąpiła próba odczytu lub zapisu pamięci chronionej. Często wskazuje to, że inna pamięć jest uszkodzona.
407: OK
408: OK
409: NOT OK: Nastąpiła próba odczytu lub zapisu pamięci chronionej. Często wskazuje to, że inna pamięć jest uszkodzona.
410: OK
411: OK
412: OK
413: OK
414: NOT OK: Nastąpiła próba odczytu lub zapisu pamięci chronionej. Często wskazuje to, że inna pamięć jest uszkodzona.
415: OK
416: OK
417: NOT OK: Nastąpiła próba odczytu lub zapisu pamięci chronionej. Często wskazuje to, że inna pamięć jest uszkodzona.
418: OK
419: OK
420: OK
421: NOT OK: Nastąpiła próba odczytu lub zapisu pamięci chronionej. Często wskazuje to, że inna pamięć jest uszkodzona.
422: NOT OK: Nastąpiła próba odczytu lub zapisu pamięci chronionej. Często wskazuje to, że inna pamięć jest uszkodzona.
423: OK
424: OK
425: NOT OK: Nastąpiła próba odczytu lub zapisu pamięci chronionej. Często wskazuje to, że inna pamięć jest uszkodzona.
426: OK
427: OK
428: OK

Exception occurs in line:
System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);
and its text means: There was try to read or write to/from protected memory...
I think this is because bytes is not calculated correctly. How can I do this right? I don't know how to get Exception message in English. Sorry about that.

Upvotes: 1

Views: 955

Answers (3)

max
max

Reputation: 34407

Try to calculate padding as

int padding = Math.Abs(bd.Stride) - (w * 3);

(at least it is more readable)

And fix for BitmapDataAccessor:

public BitmapDataAccessor(byte[] data, int width, int height, int padding)
{
    this.data = data;
    this.Height = height;
    // width in bytes, not pixels
    this.Width = width * 3 + padding;
    this.Padding = padding;
    rowStarts = new int[Height];
    for(int y = 0; y < Height; y++)
        rowStarts[y] = y * Width;
}

public byte this[int x, int y, int color]
{
    // row offsets already in bytes, so don't myltiply them by 3
    get { return data[rowStarts[y] + x * 3 + color]; }
    set { data[rowStarts[y] + x * 3 + color] = value; }
}

Notes about 2nd problem:

Similar problem here - you calculate padding for pixels, not bytes.

int ww = w * 3;
int padding = (4 - (ww % 4)) % 4;
int bytes = (ww + padding) * h;
byte[] rgbValues = new byte[bytes];

int rowOffset = 0;
for (int j = 0; j < h; j++)
{
    int pixelOffset = rowOffset;
    for (int i = 0; i < w; i++)
    {
        rgbValues[pixelOffset + 0] = tablica[i, j, 2];
        rgbValues[pixelOffset + 1] = tablica[i, j, 1];
        rgbValues[pixelOffset + 2] = tablica[i, j, 0];                    
        pixelOffset += 3;
    }
    rowOffset += ww + padding;
}

Upvotes: 3

Ichibann
Ichibann

Reputation: 4451

Padding is not numer or pixels. It's number of bytes.
But still, I don't know how to fix your code. Maybe somebody else could do that?

Upvotes: 1

izb
izb

Reputation: 51771

It would be more useful instead of simply collecting 'ok' or 'not ok', to actually inspect the exception being thrown which will give you more information about what was actually not ok.

Try adding

Console.WriteLine(ex.ToString());

to the catch block.

Upvotes: 3

Related Questions