【问题标题】:Creating GDI+ bitmaps in memory and then saving as png在内存中创建 GDI+ 位图,然后保存为 png
【发布时间】:2017-01-25 21:15:09
【问题描述】:

我是 C++ 新手,在使用 GDI+ 库编写函数以在内存中创建新位图时遇到问题(因此不能打开/读取现有位图);然后在位图上绘图;在将其保存为 png 之前。特别是,我在位图创建和保存代码方面遇到了问题。我被限制使用代码块,我不能使用视觉工作室,即使我想。代码如下:

#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' 未在此范围内声明”。但是,我是直接从 Microsoft 网站 here 获得的。我认为这不是转换为 png 的正确方法,但我不知道另一种方法?

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

我添加了“gdi32”链接器库和“-lgdiplus”作为链接器选项。此外,我使用this website 来帮助处理 gdi 的内容,尽管关于位图的部分仅处理加载现有位图(而不是在内存中创建新位图)

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

【问题讨论】:

  • drawImage 是如何定义的?你怎么称呼(用什么参数)DrawBitmap
  • 题外话:您正在使用new 分配一个新对象,但在不再需要它时您永远不会delete。这很糟糕,一旦您离开DrawBitmap,就会导致内存泄漏。
  • 感谢您的删除建议。我来自使用垃圾收集器的编程语言。所以我仍在学习如何进行这种类型的内存管理(所以我现在将在函数结束时将其删除)。至于您的 drawImage 查询,此代码是非常典型的代码,以使其特定于问题,但我在代码块中创建了一个类,这是 .cpp 代码。主库通过以下方式调用它:drawImage di; di.DrawBitmap(250, 250); (以#include "drawImage.h" 作为包含语句)
  • Image::Save 有返回值。
  • 我不知道PixelFormatCanonical是什么。只需删除它或使用来自here 的值。例如Bitmap myBitmap(width, height);

标签: c++ codeblocks gdi+


【解决方案1】:

核心问题是将错误的像素格式传递给Bitmap constructorPixelFormatCanonical 不是受支持的 pixel formats 之一。它是用于确定像素格式是否规范的位掩码(请参阅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 示例 Retrieving the Class Identifier for an Encoder

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 错误。

【讨论】:

    【解决方案2】:

    对于 1.:GetEncoderClsid 不是库函数,它是定义的示例帮助程序 here。如果您想使用它,请从网站复制代码。 (顺便说一句,您的链接明确说明了这一点。)

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

    更准确地说是GdiplusShutdown,因为这似乎给您带来了新问题:要求在调用GdiplusShutdown 之前销毁所有GDI+ 对象。这意味着具有静态存储的对象(例如pen)必须在调用GdiplusShutdown 之前超出范围。如果您像现在这样在DrawBitmap 中调用它,情况并非如此。要么在 main 中调用 GdiplusStartupGdiplusShutdown,要么在 GdiplusStartupGdiplusShutdown 之间的代码周围添加额外的括号 {},因为这些引入了一个新的范围,并且 pen 将在 } 被销毁时被销毁到达。

    编辑:编号。 2 已通过编辑在问题中修复。剩下的问题和代码的改进见@IInspectable的回答。

    【讨论】:

    • 非常感谢并道歉,因为我应该更彻底地抓住这一点。我不会将此标记为答案,因为它仅部分回答了问题。另外,我还不知道(因为代码崩溃)这是否会成功地将位图转换为 png 然后保存。但再次感谢您,我们非常感谢您的回复。
    • 非常感谢 Eichhornchen 和 Ilnspectable。您的帮助非常宝贵。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-09
    • 1970-01-01
    • 2013-01-29
    • 1970-01-01
    • 2010-12-07
    • 2018-12-25
    相关资源
    最近更新 更多