views:

182

answers:

1

I asked a question recently, How can I create an Image in GDI+ from a Base64-Encoded string in C++?, which got a response that led me to the answer.

Now I need to do the opposite - I have an Image in GDI+ whose image data I need to turn into a Base64-Encoded string. Due to its nature, it's not straightforward.

The crux of the issue is that an Image in GDI+ can save out its data to either a file or an IStream*. I don't want to save to a file, so I need to use the resulting stream. Problem is, this is where my knowledge breaks down.

This first part is what I figured out in the other question

// Initialize GDI+.
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

// I have this decode function from elsewhere
std::string decodedImage = base64_decode(Base64EncodedImage);

// Allocate the space for the stream
DWORD imageSize = decodedImage.length();
HGLOBAL hMem = ::GlobalAlloc(GMEM_MOVEABLE, imageSize);
LPVOID pImage = ::GlobalLock(hMem);    
memcpy(pImage, decodedImage.c_str(), imageSize);

// Create the stream
IStream* pStream = NULL;
::CreateStreamOnHGlobal(hMem, FALSE, &pStream);

// Create the image from the stream
Image image(pStream);

// Cleanup
pStream->Release();
GlobalUnlock(hMem);
GlobalFree(hMem);

(Base64 code)

And now I'm going to perform an operation on the resulting image, in this case rotating it, and now I want the Base64-equivalent string when I'm done.

// Perform operation (rotate)
image.RotateFlip(Gdiplus::Rotate180FlipNone);

IStream* oStream = NULL;

CLSID tiffClsid;
GetEncoderClsid(L"image/tiff", &tiffClsid);  // Function defined elsewhere
image.Save(oStream, &tiffClsid);

// And here's where I'm stumped. 

(GetEncoderClsid)

So what I wind up with at the end is an IStream* object. But here's where both my knowledge and Google break down for me. IStream shouldn't be an object itself, it's an interface for other types of streams. I'd go down the road from getting string->Image in reverse, but I don't know how to determine the size of the stream, which appears to be key to that route.

How can I go from an IStream* to a string (which I will then Base64-Encode)? Or is there a much better way to go from a GDI+ Image to a string?

+1  A: 

Got it

std::string RotateImage(const std::string &Base64EncodedImage)
{
    // Initialize GDI+.
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    std::string decodedImage = base64_decode(Base64EncodedImage);

    DWORD imageSize = decodedImage.length();
    HGLOBAL hMem = ::GlobalAlloc(GMEM_MOVEABLE, imageSize);
    LPVOID pImage = ::GlobalLock(hMem);
    memcpy(pImage, decodedImage.c_str(), imageSize);

    IStream* pStream = NULL;
    ::CreateStreamOnHGlobal(hMem, FALSE, &pStream);

    Image image(pStream);

    image.RotateFlip(Gdiplus::Rotate180FlipNone);

    pStream->Release();
    GlobalUnlock(hMem);
    GlobalFree(hMem);

    IStream* oStream = NULL;
    CreateStreamOnHGlobal(NULL, TRUE, (LPSTREAM*)&oStream);

    CLSID tiffClsid;
    GetEncoderClsid(L"image/tiff", &tiffClsid);
    image.Save(oStream, &tiffClsid);

    ULARGE_INTEGER ulnSize;
    LARGE_INTEGER lnOffset;
    lnOffset.QuadPart = 0;
    oStream->Seek(lnOffset, STREAM_SEEK_END, &ulnSize);
    oStream->Seek(lnOffset, STREAM_SEEK_SET, NULL);

    char *pBuff = new char[(unsigned int)ulnSize.QuadPart];
    ULONG ulBytesRead;
    oStream->Read(pBuff, (ULONG)ulnSize.QuadPart, &ulBytesRead);

    std::string rotated_string = base64_encode((const unsigned char*)pBuff, ulnSize.QuadPart);

    return rotated_string;
}

The trick, as inspired by what I got from this article, is knowing the method to finding out the size of the stream, and having it read it into a character array. Then I can feed that array to the base64_encode function and voilà.

Schnapple