簡體   English   中英

MFC C++中如何將CImage轉換為Base64字符串

[英]How to convert CImage to Base64 string in MFC C++

我正在將一些代碼從 C# 移植到 C++ 和 MFC,有一件事阻止了我。 原始代碼生成一個圖像,然后將其編碼為 base64 字符串,以在生成 HTML 文件時用於嵌入圖像。

原代碼首先將其轉換為字節數組

private byte[] AsBytes(System.Drawing.Image image)
{
      using (var ms = new System.IO.MemoryStream())
      {
           image.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
           return ms.ToArray();
      }
}

到 Base64 的轉換是一個簡單的調用,對於 MFC 的Convert.ToBase64String(pictureAsBytes)Base64Encode ,但它看起來不太好完成這項工作。 問題是從 CImage 到 CByteArray(或其他有用的東西)。

我的代碼讓人很頭疼,但它看起來像

AsBytes(CImage &image, CByteArray &bytes)
{
    int pitch = image.GetPitch();
    int size = abs(pitch) * image.GetHeight();
    const BYTE *src = (BYTE *)image.GetBits();

    if(pitch < 0)
    {
        src -= size;
    }
    
    BYTE *pBitmapData = new BYTE[size];
    memcpy(pBitmapData, src, size * sizeof(BYTE));

    for(int i = 0; i < size; i++)
    {
        bytes.Add(pBitmapData[i]);
    }
}

經過一些修復后,我的 AsBytes 變成了

bool AsBytes(CImage &image, CByteArray &bytes)
{
    IStream *pStream = NULL;
    HRESULT hr = CreateStreamOnHGlobal(0, TRUE, &pStream);

    if( SUCCEEDED(hr) )
    {
        hr = image.Save(pStream, Gdiplus::ImageFormatPNG);
        if( SUCCEEDED(hr) )
        {
            // Get size
            ULARGE_INTEGER liSize;
            IStream_Size(pStream, &liSize);
            // Assume no huge files
            int size = liSize.QuadPart;
            BYTE *result = new BYTE[size];

            // Set to start
            LARGE_INTEGER offset;
            offset.HighPart = 0;
            offset.LowPart = 0;
            offset.QuadPart = 0;
            hr = pStream->Seek(offset, STREAM_SEEK_SET, 0);

            ULONG read;
            hr = pStream->Read(result, size, &read);
            if( SUCCEEDED(hr) )
            {
                bytes.SetSize(read * sizeof(BYTE));
                memcpy(bytes.GetData(), result, read * sizeof(BYTE));
            }

            delete [] result;
            pStream->Release();

            return true;
        }
    }
    pStream->Release();
    return false;
}

它現在使用 stream 來轉換它。

要將CImage object 序列化為 stream 字節,您可以使用其接受IStream接口的CImage::Save重載,並將 memory stream 傳遞給它。由於我們不知道提前產生的大小,我們必須湊合使用根據需要增長的 stream。 SHCreateMemStream可以為此目的使用默認值構造。

以下實現將編碼為 PNG 數據的CImage序列化為std::vector<uint8_t>

#include <atlimage.h>
#include <comdef.h>
#include <Shlwapi.h>
#include <vector>

std::vector<uint8_t> as_bytes(CImage const& img)
{
    // Serialize image to memory stream
    CComPtr<IStream> stream {};
    stream.Attach(::SHCreateMemStream(nullptr, 0));
    _com_util::CheckError(img.Save(stream, Gdiplus::ImageFormatPNG));

    // Find size in bytes
    ULARGE_INTEGER size {};
    _com_util::CheckError(stream->Seek({}, STREAM_SEEK_CUR, &size));
    if (size.HighPart != 0)
    {
        throw std::runtime_error("Images larger than 4GiB not supported");
    }

    // Read memory stream into vector
    std::vector<uint8_t> bytes(size.QuadPart);

    _com_util::CheckError(stream->Seek({}, STREAM_SEEK_SET, nullptr));
    ULONG bytes_read { 0 };
    _com_util::CheckError(stream->Read(bytes.data(),
                                       static_cast<ULONG>(bytes.size()),
                                       &bytes_read));

    return bytes;
}

該代碼使用 C++ 異常來報告錯誤,使 function 使用起來更自然。 該實現留下了一些改進空間,特別是額外的bytes分配和副本。 一種可能的替代方法是使用 class 實現IStream接口,該接口在內部直接寫入vector 這將允許在不復制數據的情況下實現。

為了完整起見,這里有一個不依賴於 ATL 實現的 base64 編碼器。 它改用CryptBinaryToStringA

#include <wincrypt.h>

#include <string>
#include <vector>

#pragma comment(lib, "Crypt32.lib")

std::string to_base64(std::vector<uint8_t> const& bytes)
{
    // Return empty string on empty input
    if (bytes.empty())
    {
        return {};
    }

    // Change as desired
    auto const flags { CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF };

    // Request required character count (including NUL character)
    DWORD chars_required {};
    if (!::CryptBinaryToStringA(bytes.data(), static_cast<DWORD>(bytes.size()), flags,
                                nullptr, &chars_required)
        || chars_required < 1)
    {
        throw std::runtime_error { "CryptBinaryToStringA() failed" };
    }

    // Create a sufficiently sized string and have the API write into it
    std::string base64(chars_required - 1, 0);
    if (!::CryptBinaryToStringA(bytes.data(), static_cast<DWORD>(bytes.size()), flags,
                                base64.data(), &chars_required))
    {
        throw std::runtime_error { "CryptBinaryToStringA() failed" };
    }

    return base64;
}

這需要 C++17 來編譯std::string::data()調用以返回非const指針。 請注意,用另一個 NUL 終止符覆蓋std::string中尾隨的 NUL 終止符也是 C++<something> 的良好定義。

有了它,你就有了一個很好的命令行實用程序,它可以對圖像進行 base64 編碼:

int wmain(int argc, wchar_t const* argv[])
{
    if (argc != 2)
    {
        return -1;
    }

    std::wstring const src { argv[1] };
    CImage src_img {};
    _com_util::CheckError(src_img.Load(src.c_str()));

    auto const bytes = as_bytes(src_img);
    auto const base64 = to_base64(bytes);

    printf("%s", base64.c_str());

    return 0;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM