在内存中创建 GDI+ 位图,然后另存为 png
Creating GDI+ bitmaps in memory and then saving as png
我是 C++ 的新手,在使用 GDI+ 库编写函数以在内存中创建新位图时遇到了问题(所以不是 opening/reading 现有位图);然后在位图上绘制;在将其保存为 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);
}
我遇到的问题如下:
'saving' 代码无法编译并给出错误消息“'GetEncoderClsid' 未在此范围内声明”。但是,我直接从 Microsoft 网站 here 得到了这个。我不认为这是转换为 png 的正确方法,但我不知道其他方法?
代码编译后运行(通过注释掉保存的代码),然后它在"Bitmap *myBitmap = new Bitmap(width, height, PixelFormatCanonical);"行崩溃并给出一条错误消息说我的可执行文件有停止工作。
我添加了 'gdi32' 链接器库以及“-lgdiplus”作为链接器选项。此外,我已经使用 this website 来帮助处理 gdi 的东西,尽管位图部分只涉及加载现有位图(而不是在内存中创建新位图)
我完全不知道该怎么做,所以非常感谢任何关于此事的帮助或建议。
对于 1.: GetEncoderClsid
不是库函数,它是定义的示例助手 here。如果您想使用它,请从站点复制代码。 (顺便说一下,您的 link 明确说明了这一点。)
对于 2.: 您需要在创建任何 GDI+ 对象或调用任何 GDI+ 函数之前调用 GdiplusStartup
。请参阅其文档 here.
更准确地说 GdiplusShutdown
,因为这似乎会给您带来新的问题:要求在调用 GdiplusShutdown
之前销毁所有 GDI+ 对象。这意味着具有静态存储的对象(例如 pen
)必须在调用 GdiplusShutdown
之前超出范围。如果您按照现在的方式在 DrawBitmap
中调用它,情况就不同了。在 main
中调用 GdiplusStartup
和 GdiplusShutdown
,或者在 GdiplusStartup
和 GdiplusShutdown
之间的代码周围添加额外的括号 {}
,因为它们引入了新的作用域当达到 }
时 pen
将被销毁。
编辑:Nr. 2 通过编辑修复了问题。剩下的问题和代码的改进见@IInspectable的回答。
核心问题是将错误的像素格式传递给 Bitmap constructor. PixelFormatCanonical
is not one of the supported pixel formats. It's a bit mask used to determine whether a pixel format is canonical (see IsCanonicalPixelFormat)。您必须使用真正的像素格式,例如默认的 PixelFormat32bppARGB
.
以下代码产生所需的输出:
首先,一个用于 GDI+ 初始化的小帮手 class。这确保了 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
错误。
我是 C++ 的新手,在使用 GDI+ 库编写函数以在内存中创建新位图时遇到了问题(所以不是 opening/reading 现有位图);然后在位图上绘制;在将其保存为 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);
}
我遇到的问题如下:
'saving' 代码无法编译并给出错误消息“'GetEncoderClsid' 未在此范围内声明”。但是,我直接从 Microsoft 网站 here 得到了这个。我不认为这是转换为 png 的正确方法,但我不知道其他方法?
代码编译后运行(通过注释掉保存的代码),然后它在"Bitmap *myBitmap = new Bitmap(width, height, PixelFormatCanonical);"行崩溃并给出一条错误消息说我的可执行文件有停止工作。
我添加了 'gdi32' 链接器库以及“-lgdiplus”作为链接器选项。此外,我已经使用 this website 来帮助处理 gdi 的东西,尽管位图部分只涉及加载现有位图(而不是在内存中创建新位图)
我完全不知道该怎么做,所以非常感谢任何关于此事的帮助或建议。
对于 1.: GetEncoderClsid
不是库函数,它是定义的示例助手 here。如果您想使用它,请从站点复制代码。 (顺便说一下,您的 link 明确说明了这一点。)
对于 2.: 您需要在创建任何 GDI+ 对象或调用任何 GDI+ 函数之前调用 GdiplusStartup
。请参阅其文档 here.
更准确地说 GdiplusShutdown
,因为这似乎会给您带来新的问题:要求在调用 GdiplusShutdown
之前销毁所有 GDI+ 对象。这意味着具有静态存储的对象(例如 pen
)必须在调用 GdiplusShutdown
之前超出范围。如果您按照现在的方式在 DrawBitmap
中调用它,情况就不同了。在 main
中调用 GdiplusStartup
和 GdiplusShutdown
,或者在 GdiplusStartup
和 GdiplusShutdown
之间的代码周围添加额外的括号 {}
,因为它们引入了新的作用域当达到 }
时 pen
将被销毁。
编辑:Nr. 2 通过编辑修复了问题。剩下的问题和代码的改进见@IInspectable的回答。
核心问题是将错误的像素格式传递给 Bitmap constructor. PixelFormatCanonical
is not one of the supported pixel formats. It's a bit mask used to determine whether a pixel format is canonical (see IsCanonicalPixelFormat)。您必须使用真正的像素格式,例如默认的 PixelFormat32bppARGB
.
以下代码产生所需的输出:
首先,一个用于 GDI+ 初始化的小帮手 class。这确保了 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
错误。