Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
462 views
in Technique[技术] by (71.8m points)

c++ - Creating GDI+ bitmaps in memory and then saving as png

I am new to C++ and been having trouble with writing a function using the GDI+ library to create a new bitmap in memory ( so not opening/reading an existing bitmap); then drawing on the bitmap; before saving it to png. In particular, I am having problems with the bitmap creation and saving code. I am constrained to using codeblocks and I can't use visual studios, even if I wanted to. The code is as follows:

#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);
}

The issues I am having are as follows:

  1. The 'saving' code does not compile and gives the error message "'GetEncoderClsid' was not declared in this scope". However, I got this direct from the Microsoft website here. I don't think this is the proper way of converting to png but I dont know an alternative way?

  2. When the code is compiled and run (by commenting out the saving code), it then crashes on the line "Bitmap *myBitmap = new Bitmap(width, height, PixelFormatCanonical);" and gives an error message saying my executable has stopped working.

I have added the 'gdi32' linker library and also '-lgdiplus' as a linker option. Also, I have used this website to help with the gdi stuff although the section on bitmaps only deals with loading existing bitmaps (not creating new ones in memory)

I am totally lost on what to do, so any help or advice on this matter is much appreciated.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The core issue is passing the wrong pixel format to the 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). You'll have to use a real pixel format, like the default PixelFormat32bppARGB.

The following code produces the desired output:

First up, a small helper class for GDI+ initialization. This ensures, that the d'tor (i.e. the call to GdiplusShutdown) is executed after all other objects have been destroyed. With respect to order of destruction, it serves the same purpose as the additional scope in the OP. In addition, it also allows for exceptions to be thrown.

#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_;
};

This code was taken from the MSDN sample 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
}

Finally, the GDI+ rendering code. It uses objects with automatic storage duration throughout, making it more compact and safer.

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;
}

Note: It looks like GetEncoderClsid shouldn't be required, since those are well-known constants. However, trying to pass the appropriate WIC CLSID (CLSID_WICPngEncoder) to Bitmap::Save only produced a FileNotFound error.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...