I just have to say, GDI+ is really awesome. It makes graphics programming so much more fun and simple. Granted, you aren't going to be using it for making Quake 18 -- you'd still use DirectX or OpenGL for the truly hardcore graphics programming. But for your everyday graphics needs, GDI+ really does rock.
For instance, I found myself needing to convert an image from one format to another on disk. So I had an 8-bit GIF file that needed to be converted to a BMP. Turns out that this is blindingly simple to do.
The first step is to get the image that you want to save. In my case, I have a handle to a device-independant bitmap (a DIB, in Win32 parlance). So to get my image, I use the Bitmap constructor like this:
Bitmap *bmp = new Bitmap( hDIB, nil );
I can get away with passing in nil as the HPALETTE parameter because DIBs already have their palette information stored locally. Ta da! I've gotten 1/3 of the problem done. The next problem to tackle is getting a CLSID which represents the target encoding I want to use. This part is a little more tricky, but still isn't terribly bad. There is an APIs which gets you the entire list of supported encoders in the system, and you can iterate over that list to find the one you're looking for.
static 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
}
The code may look scary, but it's really quite trivial. Basically, you get an array of the encoders that the system supports, and loop over each one. Check the MIME type for that encoder and use that to retrieve the CLSID of the one you want. So, back to our handy conversion function, you do this:
CLSID enc; if (-1 == GetEncoderClsid( L"image/bmp", &enc )) return FALSE;
And now another 1/3 of the problem is solved! If we can't find the encoder, then we're not going to be able to save in that format, so we can just bail out. BMP isn't the only format supported by default, either. You can do gif, jpeg, tiff or png as well (there are probably others installed, to be honest).
So the final step is saving the file out.
bmp->Save( pathToFile, &enc );
Boy, wasn't that just painfully tough? ;-) Now we're done! Granted, since this is C++, there's a wee bit of cleanup left (in the form of a call to delete bmp, but that hardly counts, right?
So, excluding our little helper function, which we'd tuck away in some header file anyhow, here's the entire conversion function:
BOOL SaveFileAs( HBITMAP hDIB, const wchar_t *pathToFile, const wchar_t *mimeType )
{
Bitmap *bmp = new Bitmap( hDIB, nil );
CLSID enc;
if (-1 == GetEncoderClsid( mimeType, &enc )) return FALSE;
return (bmp->Save( pathToFile, &enc ) == Ok);
}
This is a far cry from how amazingly difficult this would be to do in traditional GDI programming. You'd basically be stuck converting to the various file types yourself, which would mean you'd have to grab the raw bitmap data and do the encoding conversion yourself. So you'd need to know the format of a JPEG, or the compression algorithm used by GIF, etc. Horribly painful, and now it's a trivial few lines of code.
dim orig as FolderItem = getThePictureFile()
dim pic as Picture = orig.openAsPicture()
dim dest as FolderItem = orig.parent.child( orig.name + ".bmp" )
dest.saveAsPicture( pic, FolderItem.SaveAsWindowsBMP )
;-)
Now do that for PNG or TIFF. ;-)
NSData *start = [NSData dataWithContentsOfFile: orig];
NSBitmapImageRep *image = [NSBitmapImageRep imageRepWithData: orig];
NSData *end = [image representationUsingType: NSPNGFileType properties: nil];
[end writeToFile: [[orig stringByDeletingPathExtension] stringByAppendingPathExtension: @"png"] atomically: YES];
This is interesting, seeing how it works in different languages.
@Asher -- btw, I meant to append "on Windows" to the end of my last comment.
@John -- I agree, that is quite interesting.
Aaron, little tip to keep Stroustrup happy: It's much safer to use an auto_ptr instead of calling delete. For those who don't know what I'm talking about: An auto_ptr is a stack object that takes ownership of a pointer and calls delete on it when it goes out of scope.
So, essentially it lets you use a heap object as a stack object. The advantage is that when something throws an exception, an auto_ptr will automatically go out of scope and delete bmp, while otherwise you'd have to write a catch clause and a second delete statement just to be exception-safe.
Say, Aaron, I suppose Windows doesn't let you declare a Bitmap object as a stack object? That would make the code much easier.