Resizing an image with GDI+ with C++

November 11, 2010 6 Comments by Richard

Resizing images

Resizing images is a fairly common need; there are lots of libraries around that do this.  However, I just needed to create thumbnails on-the-fly, and I certainly didn’t want to triple the size of my binary by linking against ImageMagick just to support resizing common image types.

GDI+ has the capability to do this, but surprisingly I was unable to find a clear example of how to do this just using GDI+ in C++ — lots of examples in C#, but I needed to do it in C++.

In the hopes that the next person to attempt this will simply find this blog post, here is how to do it in 6 easy(ish) steps:

Step 1: Initialize GDI+

If you haven’t already done so, you need to initailize GDI+.  This is easy, and can be done with the following commands (also note the header file you need to include):

// We'll use these headers:
#include 
#include 
// In your header file, class, etc:
ULONG_PTR(m_gdiplusToken);
// Somewhere where it will run once before you need to use GDI:
GdiplusStartupInput gdiplusstartupinput;
GdiplusStartup(&m_gdiplusToken, &gdiplusstartupinput, NULL);

Step 2: Load the image:

std::wstring filename(L"someImage.jpg");
Gdiplus::Bitmap bmp(filename.c_str());

That was hard, wasn’t it?

Step 3: Resize the image:

I wrote a function from this. I adapted it from one I found somewhere, but now I can’t find the link. Note that this will preserve the aspect ratio of the image so that it fits in the specified boundaries but does not stretch. This means that if you specify 128×128, the resulting image might actually be something like 128×96, 102×128, etc.

Gdiplus::Bitmap* GDIPlusImageProcessor::ResizeClone(Bitmap *bmp, INT width, INT height)
{
    UINT o_height = bmp->GetHeight();
    UINT o_width = bmp->GetWidth();
    INT n_width = width;
    INT n_height = height;
    double ratio = ((double)o_width) / ((double)o_height);
    if (o_width > o_height) {
        // Resize down by width
        n_height = static_cast(((double)n_width) / ratio);
    } else {
        n_width = static_cast(n_height * ratio);
    }
    Gdiplus::Bitmap* newBitmap = new Gdiplus::Bitmap(n_width, n_height, bmp->GetPixelFormat());
    Gdiplus::Graphics graphics(newBitmap);
    graphics.DrawImage(bmp, 0, 0, n_width, n_height);
    return newBitmap;
}

And then later after you load the image into bmp:

INT width = height = 128;
Gdiplus::Bitmap *resizedBmp = ResizeClone(&bmp, width, height);

Step 4: Get the encoder:

The Gdiplus::Bitmap object stores the data in raw format so that it can be manipulated. At this point you get to decide what format to save it in. I just wanted to resize it, so I keep it in the original format. I created a simple map to map the supported image filenames to the mimetype, which we can use to find the correct encoder.

Here is what I store in my map:

    m_mtMap[".jpeg"] = "image/jpeg";
    m_mtMap[".jpe"] = "image/jpeg";
    m_mtMap[".jpg"] = "image/jpeg";
    m_mtMap[".png"] = "image/png";
    m_mtMap[".gif"] = "image/gif";
    m_mtMap[".tiff"] = "image/tiff";
    m_mtMap[".tif"] = "image/tiff";
    m_mtMap[".bmp"] = "image/bmp";

I found a utility function for looking up the encoder from the mimetype:

int GDIPlusImageProcessor::GetEncoderClsid(const WCHAR* form, CLSID* pClsid)
{
    UINT num;
    UINT size;
    ImageCodecInfo* pImageCodecInfo=NULL;
    GetImageEncodersSize(&num,&size);
    if(size==0)
        return -1;
    pImageCodecInfo=(ImageCodecInfo*)(malloc(size));
    if(pImageCodecInfo==NULL)
        return -1;
    GetImageEncoders(num,size,pImageCodecInfo);
    for(UINT j=0;j

Step 5: Save the image:

Now that we have a function to get the encoder CLSID, we can save the image. If we want to save it as a file, we can do it like this:

CLSID encId;
std::wstring extension = getExtension(filename);
std::wstring mimetype = getMimeTypeFrom(extension);
if (GetEncoderClsid(mimetype, &encId) > -1) {
    resizedBmp->Save(std::wstring(L"Output") + extension, &encId);
}

Now, in my case this wasn't actually the goal; I needed the byte data for that file. I guess I could have saved it and then opened it and read it back, but since I am using this in a FireBreath plugin for Facebook that can be problematic. You can get the bytes directly like this:

        // If we were able to determine the filetype, save it to a stream
        IStream *buffer;
        CreateStreamOnHGlobal(NULL, true, &buffer);
        resizedBmp->Save(buffer, &encId);

        // Find the size of the resulting buffer
        STATSTG statstg;
        buffer->Stat(&statstg, STATFLAG_DEFAULT);
        ULONG bmpBufferSize = (ULONG)statstg.cbSize.LowPart;
        int length = bmpBufferSize;

        // Seek to the beginning of the stream
        LARGE_INTEGER li = {0};
        buffer->Seek(li, STREAM_SEEK_SET, NULL);

        // Copy the resulting data from the stream into our result object
        char *data = new char[bmpBufferSize];
        res.content_type = content_type;
        ULONG bytesRead;
        buffer->Read(data, bmpBufferSize, &bytesRead);
        buffer->Release();

Step 6: Deinitialize GDI+ properly

You need the token you created in step 1:

    GdiplusShutdown(m_gdiplusToken);

That's it!

Hope it helps someone!

6 Comments

  1. geomike
    6 years ago

    Thanks a bunch man. This really saved me a lot of time.

  2. JanKarloCamero
    6 years ago

    Great stuff!

  3. Bijo
    6 years ago

    Nice code.

    But the

    Gdiplus::Bitmap* newBitmap = new Gdiplus::Bitmap(n_width, n_height, bmp->GetPixelFormat());

    not worked for me with vc++ 6.0

    so I used

    Gdiplus::Bitmap *newBitmap = bmp->Clone(0, 0, n_width, n_height, bmp->GetPixelFormat());

    and it worked fine

    Thanks

  4. Catherine
    6 years ago

    There’s no need to new anything, just define on the stack.

  5. Jimbob
    6 years ago

    Thanks for this!

  6. Mihai Cazacu
    6 years ago

    2016 and this is still useful. Cheers

Post a Comment

Your email is never published or shared. Required fields are marked *