The Allegro Wiki is migrating to github at https://github.com/liballeg/allegro_wiki/wiki

StretchDIBits

From Allegro Wiki
Jump to: navigation, search

Background

Even though Allegro uses top-down DIBs, the StretchDIBits function always works from the bottom-up. (This is in contradiction of Microsoft's documentation.) So the source bitmap's bottom left corner is (0,0). However, the destination is top-down with (0,0) being the top left. This is why Allegro (prior to 4.2.2) used the formula: bottom_up_src_y = bitmap->h - src_y - src_h to convert the incoming src_y parameter from top-down to bottom-up.

The Problem Child

And if that's not confusing enough, there is one problem case to complicate things. When blitting a rectangle that touches the bottom, left corner of the bitmap, StretchDIBits looks at it top-down! Take a look at the image below. It looks harmless enough. We just want to blit the lower 320x180 rectangle from the bitmap on the left to the same spot on the bitmap to the right. But due to the different behavior, it gets shifted down by 20 pixels.

StretchDIBits-bug.png

To be clear, consider the formula: bottom_up_src_y = 200 - 20 - 180 = 0. When that value is 0 (and the x value is 0 as well), the blit goes awry. Because Windows inexplicably now treats it as a top-down bitmap, the formula won't work. However, if we don't use the formula, Windows will treat it as a bottom-up bitmap! So it still would go in the wrong place.

The Solution

It sounds like there is no solution, but thanks to StretchDIBits' ability to perform mirror (flip) blits, it actually can be worked around. Since Windows treats our source bitmap as bottom-up, a negative source height means we move down from the source. So if we start at the top (near line 180 in bottom-up terms) and copy src_h pixels downward, we can blit them at the bottom of the destination and move up. Because the destination bitmap is top-down, a negative height means we move up. Confused? I hope so. The following image might help.

StretchDIBits-fix.png

You might think since we are copying down, but drawing up that everything would be flipped. It's true, but we are just reversing a flip. Keep in mind that to start with, Windows is copying up, but drawing down. It performs an invisible flip. Now we are copying down, but drawing up.

The following function in src/win/gdi.c is what does the trick:

<HighlightSyntax language="cpp">

StretchDIBits(dc, 
  dest_x, dest_h+dest_y-1, dest_w, -dest_h, src_x, bitmap->h-src_y+1, src_w, -src_h, 
  pixels, bi, DIB_RGB_COLORS, SRCCOPY);

</HighlightSyntax>

Admittedly, I'm not sure why we need to add 1 to the src_y. For this example, that implies we are starting at row "181," which correlates with row 19 in the bottom-down view. I would think we would want to start with row 20, but that made everything off by one.

I don't know if this flipped method is drawn more slowly or not. If so, it could be special cased like:

<HighlightSyntax language="cpp">

  int bottom_up_src_y = bitmap->h - src_y - src_h;
  if (bottom_up_src_y == 0 && src_x == 0 && src_h != bitmap->h)
  {

StretchDIBits(dc, dest_x, dest_h+dest_y-1, dest_w, -dest_h,

src_x, bitmap->h-src_y+1, src_w, -src_h, pixels, bi, DIB_RGB_COLORS, SRCCOPY);

  }
  else
  {
  

StretchDIBits(dc, dest_x, dest_y, dest_w, dest_h, src_x, bottom_up_src_y, src_w, src_h, pixels, bi, DIB_RGB_COLORS, SRCCOPY);

  }

</HighlightSyntax>