在内存中创建GDI+位图,然后保存为PNG

3

我刚学习C++,在使用GDI+库编写一个函数创建新位图(不是打开/读取现有位图),然后在位图上绘制后保存为png时遇到了问题。特别是在位图创建和保存代码方面,我遇到了困难。我受限于使用codeblocks,并且即使我想要也不能使用visual studios。下面是代码:

#include "drawImage.h"
#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
#include <stdio.h>
#include <iostream>
using namespace std;
using namespace Gdiplus;

drawImage::drawImage(){}

void drawImage::DrawBitmap(int width, int height){
  GdiplusStartupInput gdiplusStartupInput;
  ULONG_PTR           gdiplusToken;
  GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

  {
    //Create a bitmap
    Bitmap myBitmap(width, height, PixelFormatCanonical);
    Graphics g(&myBitmap);
    Pen blackpen(Color(255,0,0,0), 3);

    //draw on bitmap
    int x1 = 1;
    int x2 = 200;
    int y1 = 1;
    int y2 = 200;
    g.DrawLine(&blackpen, x1,y1,x2,y2);

    // Save bitmap (as a png)
    CLSID pngClsid;
    GetEncoderClsid(L"image/png", &pngClsid);
    myBitmap.Save(L"C:\\test\\test.png", &pngClsid, NULL);
  }

  GdiplusShutdown(gdiplusToken);
}

我遇到的问题如下:
  1. “保存”代码无法编译并显示错误信息“'GetEncoderClsid' was not declared in this scope”。然而,我是从微软官网这里获取的。我不认为这是转换为png的正确方法,但我不知道有什么替代方法?

  2. 当编译并运行代码(注释掉保存代码)时,它会在“Bitmap *myBitmap = new Bitmap(width, height, PixelFormatCanonical);”这一行崩溃,并显示一个错误消息,指出我的可执行文件已停止工作。

我已添加“gdi32”链接库,并将“-lgdiplus”作为链接器选项。此外,我使用了这个网站来帮助理解gdi内容,尽管位图部分只涉及加载现有位图(而不是在内存中创建新位图)。

我完全不知道该怎么做,所以非常感谢您对此事提供任何帮助或建议。


你是否调用了 GdiplusStartup 函数? - user4407569
Bitmap *myBitmap = new Bitmap(width, height, PixelFormatCanonical); 这种写法很不寻常。为什么不利用 C++ 中内置的自动垃圾回收机制,即 Bitmap myBitmap(width, height, PixelFormatCanonical); - IInspectable
我忘了提到@IInspectable所说的。对于“Graphics”也是如此。它有一个从“Image*”构造函数。因此,“Graphics g(&myBitmap)”也是完全可以的,这也使得“delete g”变得过时。 - user4407569
1
Image::Save有一个返回值。 - IInspectable
1
我不知道PixelFormatCanonical是什么。只需将其删除或使用此处中的值。例如:Bitmap myBitmap(width, height); - Barmak Shemirani
显示剩余17条评论
2个回答

8
核心问题是向位图构造函数传递错误的像素格式。PixelFormatCanonical不是受支持的像素格式之一。它是一个位掩码,用于确定像素格式是否为规范格式(请参阅IsCanonicalPixelFormat)。您需要使用真实的像素格式,例如默认的PixelFormat32bppARGB
以下代码可产生所需的输出:
首先是一个小的GDI+初始化帮助类。这确保在所有其他对象被销毁后执行d'tor(即调用GdiplusShutdown)。就销毁顺序而言,它具有与OP中的附加作用域相同的目的。此外,它还允许抛出异常。
#include <windows.h>
#include <gdiplus.h>
using namespace Gdiplus;
#include <stdexcept>
using std::runtime_error;

struct GdiplusInit {
    GdiplusInit() {
        GdiplusStartupInput inp;
        GdiplusStartupOutput outp;
        if ( Ok != GdiplusStartup( &token_, &inp, &outp ) )
            throw runtime_error( "GdiplusStartup" );
    }
    ~GdiplusInit() {
        GdiplusShutdown( token_ );
    }
private:
    ULONG_PTR token_;
};

这段代码摘自MSDN示例获取编码器的类标识符
int GetEncoderClsid( const WCHAR* format, CLSID* pClsid )
{
    UINT  num = 0;          // number of image encoders
    UINT  size = 0;         // size of the image encoder array in bytes

    ImageCodecInfo* pImageCodecInfo = NULL;

    GetImageEncodersSize( &num, &size );
    if ( size == 0 )
        return -1;  // Failure

    pImageCodecInfo = (ImageCodecInfo*)( malloc( size ) );
    if ( pImageCodecInfo == NULL )
        return -1;  // Failure

    GetImageEncoders( num, size, pImageCodecInfo );

    for ( UINT j = 0; j < num; ++j )
    {
        if ( wcscmp( pImageCodecInfo[j].MimeType, format ) == 0 )
        {
            *pClsid = pImageCodecInfo[j].Clsid;
            free( pImageCodecInfo );
            return j;  // Success
        }
    }

    free( pImageCodecInfo );
    return -1;  // Failure
}

最后是GDI+渲染代码。它在整个过程中都使用具有自动存储期的对象,使其更加紧凑和安全。
void drawImage( int width, int height ) {
    GdiplusInit gdiplusinit;

    //Create a bitmap
    Bitmap myBitmap( width, height, PixelFormat32bppARGB );
    Graphics g( &myBitmap );
    Pen blackpen( Color( 255, 0, 0, 0 ), 3 );

    //draw on bitmap
    g.DrawLine( &blackpen, 1, 1, 200, 200 );

    // Save bitmap (as a png)
    CLSID pngClsid;
    int result = GetEncoderClsid( L"image/png", &pngClsid );
    if ( result == -1 )
        throw runtime_error( "GetEncoderClsid" );
    if ( Ok != myBitmap.Save( L"C:\\test\\test.png", &pngClsid, NULL ) )
        throw runtime_error( "Bitmap::Save" );
}

int main()
{
    drawImage( 200, 200 );
    return 0;
}

注意:看起来似乎不需要使用GetEncoderClsid,因为那些是知名的常量。然而,尝试将相应的WIC CLSID (CLSID_WICPngEncoder) 传递给 Bitmap::Save 只会产生一个 FileNotFound 错误。

4

对于1.: GetEncoderClsid不是一个库函数,它是一个在这里定义的示例助手。如果您想使用它,请从该网站复制代码。(顺便说一下,您的链接明确指出了这一点。)

对于2.:在创建任何GDI+对象或调用任何GDI+函数之前,您需要调用GdiplusStartup。请参阅其文档这里

更加精确地说,关于GdiplusShutdown,因为它似乎给你带来了新的问题:在调用GdiplusShutdown之前,必须销毁所有的GDI+对象。这意味着具有静态存储(例如pen)的对象必须在调用GdiplusShutdown之前超出作用域。如果你现在在DrawBitmap中调用它,那就不是这种情况。要么在main中调用GdiplusStartupGdiplusShutdown,要么在GdiplusStartupGdiplusShutdown之间添加额外的括号{},因为它们引入了一个新的作用域,当达到}时,pen将被销毁。

编辑:第二个问题已经通过编辑在问题中得到解决。对于剩下的问题和代码改进,请参见@IInspectable的答案。


非常感谢并表示歉意,我应该更加仔细地检查并发现这个问题。我不会将其标记为答案,因为它只部分回答了问题。此外,由于代码崩溃,我还不确定它是否能成功将位图转换为 PNG 并保存。但再次感谢您的回复,非常感激。 - greenbeast
非常感谢你,Eichhornchen和Ilnspectable。你们的帮助是无价的。 - greenbeast

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接