Strange bug in my own bitmap drawing function

EDIT: Thanks Mmozeiko! Bug found, not nearly as insidious as my ego would have hoped, turns out that I had 3 byte pixels :p.

Hey everyone, as stated above, I'm having an odd bug (that I'm sure is a very simple fix) that smells like some kind of flag being set somewhere so I was wondering if anyone has heard of it.

In my function I run through each pixel in the buffer and use a transformation into the bitmap space to poll for the color and covering area of a pixel in the bitmap. Here is where the weirdness occurs: I access the bitmap using the following syntax

Color = *(BitmapAccess + BitmapPixelOffset);

and I get the same error every time: error reading from an invalid memory location. I have done some things to narrow it down. In addition, this is pretty far in, but still with a good deal to go relatively (its a small image because I wanted to see about extreme scaling up) .

1.) The memory location pointed to by the operation alone is valid. I looked through the memory debugger, The BitmapAccess pointer is correct, the offset is correct, and the location pointed to is correct as well.

2.) I've done a number of things to try to figure out if I'm doing something stupid like accidentally forget that I cast BitmapAccess to a char or something like that. Nothing I have found so far (I'm really hoping you all can help me prove this point false because I don't want to be right about 3).

3.) This is the one that makes me think that its possible that I'm getting something big and fundamental wrong: every time, without fail, the invalid pointer is the address exactly the next one after the allocated memory block ends. Regardless the amount of space to the end of the struct. EDIT: I should also say here that its also occurring at the same pixeloffset every time as well, 6130 (not that the number actually matters in this context but to solidify the meaning of my statement).

I don't know, does anyone have any ideas off the top of their head? I'm happy to post the source as well if that would help, just keep in mind I haven't even been able to do a first pass debug on the actual useful stuff because of this one, so don't judge me too much :D

Edited by Ruy Calderon on
Show us more code. How BitmapAccess and BitmapPixelOffset is declared and how values are calculated/allocated and assigned to these variables?
Sure, here is the whole function for context but I'll excise the parts you requested and put them on top.

**Quick note here BufferPixelColumn and BufferPixelRow are the for loop indices**

EDIT: sorry bold tags don't seem to work inside of code blocks

1
2
3
4
5
6
7
uint32_t * BitmapAccess = (uint32_t *) Bitmap->Image;

BitmapPixelDistance.X = ((real32)BufferPixelColumn - SubPixelBufferOffset.X) * BitmapToBufferRatio.X;

BitmapPixelDistance.Y = ((real32)BufferPixelRow - SubPixelBufferOffset.Y) * BitmapToBufferRatio.Y;

SubPixelBitmapOffset = BitmapPixelDistance - Floor_realTOreal(BitmapPixelDistance);


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
internal_function void
DrawBitmap(game_VisualBuffer * Buffer, 
           v2 DrawTopLeft_Pixels, 
           v2 DrawBottomRight_Pixels, 
           BitmapImage * Bitmap)
{
	//BIG TODO!!!!: STILL WORKING THIS OUT!!
	real32 BufferPixelWidth = Floor_realTOreal(DrawBottomRight_Pixels.X - DrawTopLeft_Pixels.X);
	real32 BufferPixelHeight = Floor_realTOreal(DrawBottomRight_Pixels.Y - DrawTopLeft_Pixels.Y);

	real32 BitmapPixelWidth = (real32)Bitmap->ImageData.Width;
	real32 BitmapPixelHeight = (real32)Bitmap->ImageData.Height;

	v2 BufferPixelStart = DrawTopLeft_Pixels;
	v2 BufferPixelEnd = DrawBottomRight_Pixels;
	
	if((int)DrawTopLeft_Pixels.X < 0)
	{
		//subtract one here because we need to map the -1 pixel to pixel 0 when calculating what pixel to start from
		BufferPixelStart.X = -(DrawTopLeft_Pixels.X);
	}

	if((int)DrawBottomRight_Pixels.X > Buffer->Width)
	{
		BufferPixelEnd.X = BufferPixelEnd.X - (real32)((int)DrawBottomRight_Pixels.X - Buffer->Width);
	}

	if((int)DrawTopLeft_Pixels.Y < 0)
	{
		BufferPixelStart.Y = -(DrawTopLeft_Pixels.Y);
	}

	if((int)DrawBottomRight_Pixels.Y > Buffer->Height)
	{
		BufferPixelEnd.Y = BufferPixelEnd.Y - (real32)((int)DrawBottomRight_Pixels.Y - Buffer->Height);
	}

	uint32_t * BufferAccess = (uint32_t *) Buffer->Memory;
	BufferAccess += (int)BufferPixelStart.Y*Buffer->Width + (int)BufferPixelStart.X;
	int BufferRowOffset = (Buffer->Width - (int)(BufferPixelEnd.X - BufferPixelStart.X));

	[b]uint32_t * BitmapAccess = (uint32_t *) Bitmap->Image;[/b]

	v2 BitmapToBufferRatio = V2(BitmapPixelWidth/BufferPixelWidth,BitmapPixelHeight/BufferPixelHeight);
	v2 BufferToBitmapRatio = 1.0f/BitmapToBufferRatio;
	v2 SubPixelBufferOffset = V2(DrawTopLeft_Pixels.X - Floor_realTOreal(DrawTopLeft_Pixels.X),DrawTopLeft_Pixels.Y - Floor_realTOreal(DrawTopLeft_Pixels.Y));

	int BitmapPixelPointerOffset = 0;
	v2 BitmapPixelDistance = V2(0.0f,0.0f);
	v2 SubPixelBitmapOffset = V2(0.0f,0.0f);

	for(uint32_t BufferPixelRow = 0;
		BufferPixelRow < (uint32_t)(BufferPixelEnd.Y - BufferPixelStart.Y) + 1;
		BufferPixelRow++)
	{
		for(uint32_t BufferPixelColumn = 0;
			BufferPixelColumn < (uint32_t)(BufferPixelEnd.X-BufferPixelStart.X) + 1;
			BufferPixelColumn++)
		{
			if(BufferPixelRow + (uint32_t)BufferPixelStart.Y <= (uint32_t)Buffer->Height || BufferPixelColumn + (uint32_t)BufferPixelStart.X <= (uint32_t)Buffer->Width)
			{
				uint32_t BufferPixel = *(BufferAccess + 1);
				uint32_t Rbuf = (BufferPixel & (Bitmap->ImageData.RedMask));
				uint32_t Gbuf = (BufferPixel & (Bitmap->ImageData.GreenMask))>>8;
				uint32_t Bbuf = (BufferPixel & (Bitmap->ImageData.BlueMask))>>16;
				uint32_t Abuf = (BufferPixel & (Bitmap->ImageData.AlphaMask))>>24;
				v3 BufferColor = V3((real32)Rbuf/255.0f,(real32)Gbuf/255.0f,(real32)Bbuf/255.0f);

				//need to use inverse in case there are multiple bitmap pixels to walk

				[b]BitmapPixelDistance.X = ((real32)BufferPixelColumn - SubPixelBufferOffset.X) * BitmapToBufferRatio.X;
				BitmapPixelDistance.Y = ((real32)BufferPixelRow - SubPixelBufferOffset.Y) * BitmapToBufferRatio.Y;
				SubPixelBitmapOffset = BitmapPixelDistance - Floor_realTOreal(BitmapPixelDistance);[/b]
				

				v3 NewBufferPixelColor = V3(0.0f,0.0f,0.0f);
				

				real32 FirstBitmapPixelArea = SubPixelBitmapOffset.X * SubPixelBitmapOffset.Y;
				real32 SecondBitmapPixelArea = (1.0f - SubPixelBitmapOffset.X) * SubPixelBitmapOffset.Y;
				real32 ThirdBitmapPixelArea = SubPixelBitmapOffset.X * (1.0f - SubPixelBitmapOffset.Y);
				real32 FourthBitmapPixelArea = (1.0f - SubPixelBitmapOffset.X) * (1.0f - SubPixelBitmapOffset.Y);
				real32 BufferPixelArea = 0.0f;

				BitmapPixelPointerOffset = (uint32_t)((BitmapPixelDistance.Y + 0.0f) * (BitmapPixelWidth - 1.0f)) + (uint32_t)(BitmapPixelDistance.X + 0.0f);

				uint32_t BitmapPixel = *(BitmapAccess + BitmapPixelPointerOffset);
				uint32_t Rbit = (BitmapPixel & (Bitmap->ImageData.RedMask));
				uint32_t Gbit = (BitmapPixel & (Bitmap->ImageData.GreenMask))>>8;
				uint32_t Bbit = (BitmapPixel & (Bitmap->ImageData.BlueMask))>>16;
				uint32_t Abit = (BitmapPixel & (Bitmap->ImageData.AlphaMask))>>24;
				v4 BitmapPixelColor = V4((real32)Rbit/255.0f,(real32)Gbit/255.0f,(real32)Bbit/255.0f,(real32)Abit/255.0f);

				BufferPixelArea += FirstBitmapPixelArea * (1.0f - (BitmapPixelColor.A));
				NewBufferPixelColor += V3(BitmapPixelColor.R,BitmapPixelColor.G,BitmapPixelColor.B) * BitmapPixelColor.A;

				
				if((uint32_t)(BitmapPixelDistance.Y + 0.0f) <= BitmapPixelHeight || (uint32_t)(BitmapPixelDistance.X + 1.0f)<= BitmapPixelWidth)
				{
					BufferPixelArea += SecondBitmapPixelArea;
				}
				else
				{
					BitmapPixelPointerOffset = ((uint32_t)(BitmapPixelDistance.Y + 0.0f) + 1) * (uint32_t)(BitmapPixelDistance.X + 1.0f);
					BitmapPixel = *(BitmapAccess + BitmapPixelPointerOffset);
					Rbit = (BitmapPixel & (Bitmap->ImageData.RedMask));
					Gbit = (BitmapPixel & (Bitmap->ImageData.GreenMask))>>8;
					Bbit = (BitmapPixel & (Bitmap->ImageData.BlueMask))>>16;
					Abit = (BitmapPixel & (Bitmap->ImageData.AlphaMask))>>24;
					BitmapPixelColor = V4((real32)Rbit/255.0f,(real32)Gbit/255.0f,(real32)Bbit/255.0f,(real32)Abit/255.0f);
					BufferPixelArea += SecondBitmapPixelArea * (1.0f - (BitmapPixelColor.A));
					NewBufferPixelColor += (V3(BitmapPixelColor.R,BitmapPixelColor.G,BitmapPixelColor.B) * BitmapPixelColor.A);
				}

				if((uint32_t)(BitmapPixelDistance.Y + 1.0f) <= BitmapPixelHeight || (uint32_t)(BitmapPixelDistance.X + 0.0f)<= BitmapPixelWidth)
				{
					BufferPixelArea += ThirdBitmapPixelArea;
				}
				else
				{
					BitmapPixelPointerOffset = ((uint32_t)(BitmapPixelDistance.Y + 1.0f) + 1) * (uint32_t)(BitmapPixelDistance.X + 0.0f);
					BitmapPixel = *(BitmapAccess + BitmapPixelPointerOffset);
					Rbit = (BitmapPixel & (Bitmap->ImageData.RedMask));
					Gbit = (BitmapPixel & (Bitmap->ImageData.GreenMask))>>8;
					Bbit = (BitmapPixel & (Bitmap->ImageData.BlueMask))>>16;
					Abit = (BitmapPixel & (Bitmap->ImageData.AlphaMask))>>24;
					BitmapPixelColor = V4((real32)Rbit/255.0f,(real32)Gbit/255.0f,(real32)Bbit/255.0f,(real32)Abit/255.0f);

					BufferPixelArea += ThirdBitmapPixelArea * (1.0f - (BitmapPixelColor.A));
					NewBufferPixelColor += (V3(BitmapPixelColor.R,BitmapPixelColor.G,BitmapPixelColor.B) * BitmapPixelColor.A);
				}

				if((uint32_t)(BitmapPixelDistance.Y + 1.0f) <= BitmapPixelHeight || (uint32_t)(BitmapPixelDistance.X + 1.0f)<= BitmapPixelWidth)
				{
					BufferPixelArea += FourthBitmapPixelArea;
				}
				else
				{
					BitmapPixelPointerOffset = ((uint32_t)(BitmapPixelDistance.Y + 1.0f) + 1) * (uint32_t)(BitmapPixelDistance.X + 1.0f);
					BitmapPixel = *(BitmapAccess + BitmapPixelPointerOffset);
					Rbit = (BitmapPixel & (Bitmap->ImageData.RedMask));
					Gbit = (BitmapPixel & (Bitmap->ImageData.GreenMask))>>8;
					Bbit = (BitmapPixel & (Bitmap->ImageData.BlueMask))>>16;
					Abit = (BitmapPixel & (Bitmap->ImageData.AlphaMask))>>24;
					BitmapPixelColor = V4((real32)Rbit/255.0f,(real32)Gbit/255.0f,(real32)Bbit/255.0f,(real32)Abit/255.0f);

					BufferPixelArea += FourthBitmapPixelArea * (1.0f - (BitmapPixelColor.A));
					NewBufferPixelColor += (V3(BitmapPixelColor.R,BitmapPixelColor.G,BitmapPixelColor.B) * BitmapPixelColor.A);
				}

				NewBufferPixelColor += BufferColor * BufferPixelArea;

				*(BufferAccess++) = ((uint32_t)(NewBufferPixelColor.R * 255.0f)) | ((uint32_t)(NewBufferPixelColor.G * 255.0f) << 8) | ((uint32_t)(NewBufferPixelColor.B * 255.0f) << 16);
			}
		}
		BufferAccess += BufferRowOffset;
	}
}

Edited by Ruy Calderon on
Hm, hard to see what is the problem.
Use the debugger - put assert before statement that crashes:
1
2
Assert(BitmapPixelOffset >= 0 && BitmapPixelOffset < Width * Height);
Color = *(BitmapAccess + BitmapPixelOffset);

When assert will fail, examine values of X and Y coordinates that were used to calculate BitmapPixelOffset and then figure out why they are out of bounds.
That's the very thing that is so strange about the bug! That was the first thing I did and it is completely valid, the assert never fires! To be specific, the image is 144px by 48px (random numbers I know but its like 4x4 pixel test assets). This is occurring, every time, on row(Y)=43 column(X)=82, or something like that, either way very far (in absolute terms, if not relative) behind the end of the file. Not to mention the other interesting part, the distance it has to travel to get to the end of the allocated block is not constant. That changes from allocation to allocation ie; run to run.

I don't know I'm kind of fried trying to look through the google to find some indication of whether I'm hitting some silly hack a microsoft intern put into visual studio 5 years ago. I'm resigned to more extreme methods of teasing out the problem if I have to I was just kind of hoping someone here could find a dumb mistake or give me a quick and easy "oh thats obvious, in windows you can only access an image 6310 times consecutively in the same function if it starts with a D, then it just gives up and takes you to the end of the block. To stop that set this bit to true in the virtual alloc"

Edited by Ruy Calderon on Reason: Addendum
What does "the distance it has to travel to get to the end of the allocated block is not constant" mean? What travelling are you talking about? Does offset is different for same x and y? In that case I can imagine Bitmap->ImageData.Width being wrong.

And how is Bitmap->Image allocated? Are you sure you can access last pixel of it?
1
uint32_t test = *((uint32_t*)Bitmap->Image + 47*144 + 143);

Are you sure pixels are laid out in memory as 4 bytes per pixel and 144 pixels per row without any padding or alignment per row?

Edited by Mārtiņš Možeiko on
Hahaha!! Yes, that is the very problem... I looked at the old version of the function again and found that it was written for a 24 bit pixel. Sweet Jesus that is such a relief. Thank you very much, Mmozeiko, for your help and patience. I should know by now, I'm not anywhere near good enough to start blaming Windows for bugs :lol: .

Edited by Ruy Calderon on
No problem.
When debugging remember to verify every single assumption you made if you find that something doesn't work as expected. Because [at least] one of assumptions will be wrong and that will be the bug :)