繁体   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