Search code examples
c++winapimfcgdi

Anti aliasing in MFC


I'm trying to implement anti-aliasing in my MFC app, I'm using the technique described in this tutorial.

  • Create a bitmap (2x, 4x, 8x) the size of the original bitmap.
  • Draw on the resized bitmap (I'm only using simple figures (lines, circles and etc)).
  • Set StretchBlt Mode to HalfTone.
  • And Resize with StretchBlt to the original size.

Using this way, drawing in the resized bitmap it works, but I want to create a more generic function that receives a bitmap with the drawing already made and return with the anti-aliasing, I tried this:

static HBITMAP AntiAliasing(HBITMAP hBitmap)
{
    int escala = 4;

    HBITMAP bmp = __copia(hBitmap); //  Copy the bitmap.

    HDC hMemDC = CreateCompatibleDC(NULL);
    HBITMAP bmpAntigo1 = (HBITMAP)::SelectObject(hMemDC, bmp);

    BITMAP bitmap;
    ::GetObject(hBitmap, sizeof(BITMAP), &bitmap);

    //  Create a bitmap (2x, 4x, 8x) the size of the original bitmap.
    HDC hDCDimensionado = ::CreateCompatibleDC(hMemDC);
    HBITMAP bmpDimensionado = ::CreateCompatibleBitmap(hDCDimensionado, 
                                                        bitmap.bmWidth * escala, 
                                                        bitmap.bmHeight * escala);

    HBITMAP hBmpVelho = (HBITMAP)::SelectObject(hDCDimensionado, bmpDimensionado);

    //  I also tried with {BLACKONWHITE, HALFTONE, WHITEONBLACK}
    int oldStretchBltMode2 = ::SetStretchBltMode(hDCDimensionado, COLORONCOLOR);    

    //  Resize the  bitmap to the new size.
    ::StretchBlt(hDCDimensionado,
        0, 0, bitmap.bmWidth * escala, bitmap.bmHeight * escala, 
        hMemDC, 
        0, 0, bitmap.bmWidth, bitmap.bmHeight, 
        SRCCOPY);

    /*
    *   Here the bitmap has lost his colors and became black and white.
    */

    ::SetStretchBltMode(hDCDimensionado, oldStretchBltMode2);

    //  Set StretchBltMode to halfTone so can mimic the anti aliasing effect.
    int oldStretchBltMode = ::SetStretchBltMode(hMemDC, HALFTONE);

    //  resize to the original size.
    ::StretchBlt(hMemDC, 
        0, 0, bitmap.bmWidth, bitmap.bmHeight, 
        hDCDimensionado, 
        0, 0, escala * bitmap.bmWidth, escala * bitmap.bmHeight, 
        SRCCOPY);

    ::SetStretchBltMode(hMemDC, oldStretchBltMode);
    ::SelectObject(hMemDC, bmpAntigo1);
    ::DeleteDC(hMemDC);
    ::SelectObject(hDCDimensionado, hBmpVelho);
    DeleteDC(hDCDimensionado);

    return bmp;
}

But this function doesn't work, the result loses its colors (all drawings became black) and there isn't anti aliasing.

Any help will be appreciated!


Solution

  • From documentation for CreateCompatibleBitmap:

    Note: When a memory device context is created, it initially has a 1-by-1 monochrome bitmap selected into it. If this memory device context is used in CreateCompatibleBitmap, the bitmap that is created is a monochrome bitmap. To create a color bitmap, use the HDC that was used to create the memory device context, as shown in the following code:

    Change the code and supply hdc for the desktop as show below:

    HDC hdc = ::GetDC(0);
    HBITMAP bmpDimensionado = ::CreateCompatibleBitmap(hdc, ...)
    ::ReleaseDC(0, hdc);
    


    This will show the image, however this method will not produce the desired effect because it simply magnifies each pixel to larger size and reduces it back to the original pixel. There is no blending with neighboring pixels.

    Use other methods such Direct2D with Gaussian blur effect, or use GDI+ instead with interpolation mode:

    Gdiplus::GdiplusStartup...
    
    void foo(HDC hdc)
    {
        Gdiplus::Bitmap bitmap(L"file.bmp");
        if(bitmap.GetLastStatus() != 0)
            return 0;
        auto w = bitmap.GetWidth();
        auto h = bitmap.GetHeight();
    
        auto maxw = w * 2;
        auto maxh = h * 2;
        Gdiplus::Bitmap membmp(maxw, maxh);
        Gdiplus::Graphics memgr(&membmp);
        memgr.SetInterpolationMode(Gdiplus::InterpolationModeHighQualityBilinear);
        memgr.DrawImage(&bitmap, 0, 0, maxw, maxh);
    
        Gdiplus::Graphics gr(hdc);
        gr.SetInterpolationMode(Gdiplus::InterpolationModeHighQualityBilinear);
        gr.DrawImage(&membmp, 0, 0, w, h);
    }
    


    If target window is at least Vista, use GDI+ version 1.1 with blur effect. See also How to turn on GDI+ 1.1 in MFC project

    #define GDIPVER 0x0110 //add this to precompiled header file
    
    void blur(HDC hdc)
    {
        Gdiplus::Graphics graphics(hdc);
        Gdiplus::Bitmap bitmap(L"file.bmp");
        if(bitmap.GetLastStatus() != 0)
            return;
    
        Gdiplus::Blur blur;
        Gdiplus::BlurParams blur_param;
        blur_param.radius = 3; //change the radius for different result
        blur_param.expandEdge = TRUE;
        blur.SetParameters(&blur_param);
        bitmap.ApplyEffect(&blur, NULL);
    
        graphics.DrawImage(&bitmap, 0, 0);
    }