Invert (mirror) a bitmap inplace

http://www.codeguru.com/cpp/g-m/bitmap/specialeffects/article.php/c1739/Invert-mirror-a-bitmap-inplace.htm

Posted by Roger Onslow on August 5th, 1998

It is possible to invert a bitmap (laterally or vertically) IN-PLACE - ie. without creating any intermediate or temporary bitmaps. You can modify the original bitmap directly. This can be very useful if you are wanting to reflect a LARGE bitmap, and don't want to allocate the extra storage. The operation requires 50% more StretchBlt operations, but does not have the overhead of creating the extra bitmaps and device contexts. Compare this to GetInvertedBitmap presented elsewhere which needs to make a copy of the original bitmap.

The Windows SDK appears to provide very few functions for manipulating the contents of a bitmap. Sure, you can draw on a bitmap using GDI functions. And you can copy a rectangular part of a bitmap onto another bitmap with BitBlt or StretchBlt, but that's about it.

However, the BitBlt and StretchBlt routines are quite powerful little beasts, and you can do all sorts of manipulations using them.

For example, you can use StretchBlt to copy a bitmap onto another bitmap, but reflect it in the x or y axis along the way. And if you do both at once, you can copy the image flipped upside down. To do this, you specify a negative value for the destination width (for a y-axis reflection) or destination height (or a x-axis reflection).

You can also use the various raster operations (or ROPs) to achieve various interesting effects by specifying how the source and destination bits interact with each other.

All these operations are good for copying from one bitmap to another ( including copy from a bitmap to the screen - which is just another bitmap anyway). But what about actually CHANGING a bitmap itself?

Well, you could make a second bitmap, do your magic with it, and then copy the result back. However, that means you need an extra BitBlt to copy back, AND you need extra storage for the temporary bitmap.

A better solution is to do all the changes in place on the original bitmap. This is not always possible, but when it is, you get savings in both memory and processing.

In this article we will look at using StretchBlt to do x and y axis reflections in-place, WITHOUT needing another bitmap.

Before leaping straight into some code, lets go back to one of the simplest functions a beginning program learns how to write - the function to swap the values of two variables.

void Swap (int& a, int &b) {
     int temp = a;
     a = b;
     b = temp;
}

This function swaps two integer variables. Note that this requires the use of a temporary variable, which I have given the unoriginal name 'temp'. We make a copy of 'a' in 'temp', overwrite 'a' with 'b' (but we still have a copy in 'temp') and then finally clobber 'b' with the saved value in 'temp'. All very simple - and a temporary int is not a problem.

But what if a and b are large structures or blocks of memory instead? Then a temporary could be an expensive thing indeed? But how can we swap the values without it?

Elementary, my dear Watson, we use logic!

One of the basic primitive operations that you can do to a lump of bits is called an exclusive-or ('XOR') operation. It is a cousin of the more familiar 'AND' and 'OR' operations that we all know and love. When you combine two values with an 'XOR', you get a 1-bit for each bit that is different and a '0' bit for each that is the same. That is, it sets a bit if and only if one or the other (but not both) of the input bits are set.

Here are some 'truth tables' that might clear it up. Compare the table for XOR with those for AND and OR.

XOR     0       1         AND   0       1          OR   0       1
0       0       1           0   0       0           0   0       1
1       1       0           1   0       1           1   1       1

By looking at the truth table for XOR, you can see that it effectively toggles (or swaps) the state of a bit of one input when the corresponding bit of the other input is 1.

Also not that '(A XOR B) XOR B)' returns A, regardless of B. The first XOR with B swaps the state of some of the bits, and then the second XOR swaps the same states back again.

In C (and C++) the XOR operator is '^', so 'a ^ b' will do a bit-wise XOR operation between a and b and return the result. There is also a XOR-assignment operator '^=', so 'a ^= b' will do a bit-wise XOR of b ont a, replacing the original value of a.

Now comes some magic, we can use XOR's bit state swapping abilities to do our entire integer swapping WITHOUT a temporary.

void InPlaceSwap (int& a, int &b) {
     a ^= b;
     b ^= a;
     a ^= b;
}

Let's look at some bits to see this in action. We'll start off with a=12 and b = 10 (in binary a=1100, b=1010). Watch what happens to the bits as each statement of InPlaceSwap executes:

                  a     b      Algebra
               1100  1010
    a ^= b;    0110  1010      a' = a^b
    b ^= a;    0110  1100      b' = b^a' = b^a^b = a
    a ^= b;    1010  1100      a" = a'^b' = a^b^a = b

The end result is that a and b are swapped.

You can use the same technique to swap to large blocks of memory by XOR-ing their bits using the same algorithm. In fact, you can swap chunks of bits in a BITMAP using this algorithm - by using BitBlt with the SRCINVERT raster op, which does an XOR operation.

void SwapBlt (CDC* pDC1, int x1, int y1,
                 int nWidth, int nHeight,
                 CDC* pDC2, int x2, int y2
                 ) {
     if (! nWidth || ! nHeight) return;
     pDC1->BitBlt(x1,y1, nWidth,nHeight, pDC2, x2,y2, SRCINVERT);
     pDC2->BitBlt(x2,y2, nWidth,nHeight, pDC1, x1,y1, SRCINVERT);
     pDC1->BitBlt(x1,y1, nWidth,nHeight, pDC2, x2,y2, SRCINVERT);
}

The SwapBlt function above uses exactly the same algorithm as the SwapInPlace function previously to swap the rectangle of size nWidth x nHeight at [x1, y1] with the one at [x2,y2]. Each BitBlt simply does an XOR of two chunks of bitmap memory.

By creating a compatible device context and selecting a bitmap into it, you can use SwapBlt to swap the left hand side and right hand side of the bitmap image as shown below.

First we need a helper function to give us the actual width and height of the bitmap.

void GetWidthAndHeight(CBitmap* pBitmap, int* pw, int* ph) const {
     if (! pBitmap && ! pBitmap->GetSafeHandle()) {     // no bitmap yet
          if (pw) *pw = 0;
          if (ph) *ph = 0;
     } else {
          BITMAP bm;
          pBitmap->GetObject(sizeof(bm), &bm);
          if (pw) *pw = bm.bmWidth;
          if (ph) *ph = bm.bmHeight;
     }
}

Here's the swapper:

void SwapLeftAndRightHalves (CBitmap* pBitmap) {
     if (!pBitmap || ! pBitmap->GetSafeHandle()) return;
     // create DC and select bitmap into it
     CDC dc;
     CDC* pDC = &dc
     pDC->CreateCompatibleDC( NULL );
     CBitmap* pBmpOldImage = pDC->SelectObject(pBitmap);
     // get bitmap size
     int nWidth,nHeight;
     ::GetWidthAndHeight(pBitmap,&nWidth,&nHeight);
     // do the swap
     int nHalfWidth = nWidth/2;
     if (nHalfWidth < 1) return;
     ::SwapBlt(pDC,0,0,nHalfWidth,nHeight,pDC,nWidth-nHalfWidth,0);
     // reselect old bitmap into DC
     pDC->SelectObject(pBmpOldImage);
}

This function simply works out the width of the two halves, by halving the bitmap width. Then it swaps the left and right sides by passing the same DC for both arguments of SwapBlt, and by giving positions and sizes for the left and right hand sides.

This is the first step toward doing a reflection. Now if only we could reflect each half we would be done. That is exactly what we can do - with a little bit of recursive programming.

void MirrorLeftAndRightHalves (CBitmap* pBitmap) {
     if (!pBitmap || ! pBitmap->GetSafeHandle()) return;
     // create DC and select bitmap into it
     CDC dc;
     CDC* pDC = &dc
     pDC->CreateCompatibleDC( NULL );
     CBitmap* pBmpOldImage = pDC->SelectObject(pBitmap);
     // get bitmap size
     int nWidth,nHeight;
     ::GetWidthAndHeight(pBitmap,&nWidth,&nHeight);
     // do the mirror
     ::RecursiveMirrorBlt(pDC,0,0,nWidth,nHeight);
     // reselect old bitmap into DC
     pDC->SelectObject(pBmpOldImage);
}
 
BOOL RecursiveMirrorBlt (CDC* pDC1, int x1, int y1,
                               int nWidth, int nHeight
                               ) {
     int nHalfWidth = nWidth/2;
     if (nHalfWidth < 1) return;
     int x2 = x1+nWidth-nHalfWidth;
     ::SwapBlt(pDC,x1,y1,nHalfWidth,nHeight,pDC,x2,y1);
     RecursiveMirrorBlt(pDC,x1,y1,nHalfWidth,nHeight);
     RecursiveMirrorBlt(pDC,x2,y1,nHalfWidth,nHeight);
}

The 'MirrorLeftAndRightHalves' function is similar to our earlier 'SwapLeftAndRightHalves'. However, instead of calling SwapBlt it calls 'RecursiveMirrorBlt'. This helper function works out the width of the two halves and calls SwapBlt to swap them. Then it calls itself recursively to mirror each half. The recursion stops when we get down to a single pixel wide column to mirror, at which stage we have successfully mirrored the bitmap image.

Now, this recursive method is not very efficient - it involves move BiBlt operations than are necessary. We could expand this out to an iterative method, but there is an even easier way of achieving the same result without looping or recursion.

The magic here is to write a version of SwapBlt the also does the reflection at the same time.

Lets see if this will work with some test data nd some pseudo-code

                          a     b
                       1100  1010
     a ^= reverse(b);  1001  1010
     b ^= reverse(a);  1001  0011
     a ^= reverse(b);  0101  0011

We do indeed end up with a = reverse(b) and b = reverse(a). Overall we have a mirror of the original bit pattern.

We can do this reverse and XOR using StretchBlt with a negative value for the width. The function to do this is very much like SwapBlt

void SwapYInvertBlt (CDC* pDC1, int x1, int y1,
                          int nWidth, int nHeight,
                          CDC* pDC2, int x2, int y2
                          ) {
     if (! nWidth || ! nHeight) return;
     pDC1->StretchBlt (
          x1,y1, nWidth,nHeight,
          pDC2,
          x2+nWidth-1,y2, -nWidth, nHeight,
          SRCINVERT
          );
     pDC2->StretchBlt (
          x2,y2, nWidth,nHeight,
          pDC1,
          x1+nWidth-1,y1, -nWidth, nHeight,
          SRCINVERT
          );
     pDC1->StretchBlt (
          x1,y1, nWidth,nHeight,
          pDC2,
          x2+nWidth-1,y2, -nWidth, nHeight,
          SRCINVERT
          );
}

Notice the negative sign in each StretchBlt for the width. And also notice that the destination x value is adjusted to take into account the negative width.

Now we can rewrite our 'SwapLeftAndRightHalves' function to not just swap, but to actually do the mirror.

void MirrorLeftAndRightHalves (CBitmap* pBitmap) {
     if (!pBitmap || ! pBitmap->GetSafeHandle()) return;
     // create DC and select bitmap into it
     CDC dc;
     CDC* pDC = &dc
     pDC->CreateCompatibleDC( NULL );
     CBitmap* pBmpOldImage = pDC->SelectObject(pBitmap);
     // get bitmap size
     int nWidth,nHeight;
     ::GetWidthAndHeight(pBitmap,&nWidth,&nHeight);
     // do the swap
     int nHalfWidth = nWidth/2;
     if (nHalfWidth < 1) return;
     ::SwapYInvertBlt(pDC,0,0,nHalfWidth,nHeight,pDC,nWidth-nHalfWidth,0);
     // reselect old bitmap into DC
     pDC->SelectObject(pBmpOldImage);
}

This require only 3 StretchBlt operations, each on a half of the bitmap, so is very efficient.

Of course the same technique can be used to mirror top and bottom.

We can use these techniques to derive from class CBitmap and add in-place inverting functionality to it. Here, I have single function 'Invert' which takes a BOOL value indicating whether we want left/right or top/bottom mirroring. Also note that some of the functions above now appears as member functions of this class

//MyBitmap.h
class CMyBitmap : public CBitmap {
public:
     void GetWidthAndHeight (int* pw, int* ph) const;
public:
     void Invert (BOOL bLateral);
};
// Global helpers
void SwapBlt (CDC* pDC1, int x1, int y1, int dx, int dy, CDC* pDC2, int x2, int y2);
void SwapXReflectBlt (CDC* pDC1, int x1, int y1, int dx, int dy, CDC* pDC2, int x2, int y2);
void SwapYReflectBlt (CDC* pDC1, int x1, int y1, int dx, int dy, CDC* pDC2, int x2, int y2);
 
 
//MyBitmap.cpp
#include "stdafx.h"
#include "MyBitmap.h"
 
void CMyBitmap::GetWidthAndHeight(int* pw, int* ph) const {
     if (! GetSafeHandle()) {   // no bitmap yet
          if (pw) *pw = 0;
          if (ph) *ph = 0;
     } else {
          BITMAP bm; GetObject(sizeof(bm), &bm);
          if (pw) *pw = bm.bmWidth;
          if (ph) *ph = bm.bmHeight;
     }
}
 
void CMyBitmap::Invert (BOOL bLateral) {
     // must have a bitmap
     if (! GetSafeHandle()) return;
     // create DC and select this into it
     CDC dc; CDC* pDC = &dc
     pDC->CreateCompatibleDC( NULL );
     int nWidth,nHeight; GetWidthAndHeight(&nWidth,&nHeight);
     CBitmap* pBmpOldImage = pDC->SelectObject(this);
     // do the reflection
     if (bLateral) {
          int nHalfWidth = nWidth/2; if (nHalfWidth < 1) return;
          SwapYInvertBlt(pDC,0,0,nHalfWidth,nHeight,pDC,nWidth-nHalfWidth,0);
     } else {
          int nHalfHeight = nHeight/2; if (nHalfHeight < 1) return;
          SwapXReflectBlt(pDC,0,0,nWidth,nHalfHeight,pDC,0,nHeight-nHalfHeight);
     }
     pDC->SelectObject(pBmpOldImage);
}
 
// swap two rectangles in two (different or same) DC's (ie. bitmaps)
void SwapBlt (CDC* pDC1, int x1, int y1, int nWidth, int nHeight, CDC*pDC2, int x2, int y2) {
     if (nWidth && nHeight) {   // ignore degenerate cases
          pDC1->BitBlt( x1,y1, nWidth,nHeight, pDC2, x2,y2, SRCINVERT );
          pDC2->BitBlt( x2,y2, nWidth,nHeight, pDC1, x1,y1, SRCINVERT );
          pDC1->BitBlt( x1,y1, nWidth,nHeight, pDC2, x2,y2, SRCINVERT );
     }
}
// swap two rectangles in two (different or same) DC's (ie. bitmaps) and reflect in X asis
void SwapXReflectBlt (CDC* pDC1, int x1, int y1, int nWidth, int nHeight, CDC* pDC2, int x2, int y2) {
     if (nWidth && nHeight) {   // ignore degenerate cases
          pDC1->StretchBlt( x1,y1, nWidth,nHeight, pDC2, x2,y2+nHeight-1,nWidth, -nHeight, SRCINVERT );
          pDC2->StretchBlt( x2,y2, nWidth,nHeight, pDC1, x1,y1+nHeight-1,nWidth, -nHeight, SRCINVERT );
          pDC1->StretchBlt( x1,y1, nWidth,nHeight, pDC2, x2,y2+nHeight-1,nWidth, -nHeight, SRCINVERT );
     }
}
// swap two rectangles in two (different or same) DC's (ie. bitmaps) and reflect in Y axis
void SwapYInvertBlt (CDC* pDC1, int x1, int y1, int nWidth, int nHeight,CDC* pDC2, int x2, int y2) {
     if (nWidth && nHeight) {   // ignore degenerate cases
          pDC1->StretchBlt( x1,y1, nWidth,nHeight, pDC2, x2+nWidth-1,y2,-nWidth, nHeight, SRCINVERT );
          pDC2->StretchBlt( x2,y2, nWidth,nHeight, pDC1, x1+nWidth-1,y1,-nWidth, nHeight, SRCINVERT );
          pDC1->StretchBlt( x1,y1, nWidth,nHeight, pDC2, x2+nWidth-1,y2,-nWidth, nHeight, SRCINVERT );
     }
}

原文地址:https://www.cnblogs.com/shangdawei/p/3071172.html