Skip to content

Instantly share code, notes, and snippets.

@superwills
Last active September 7, 2021 17:56
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save superwills/2f98fc72f07e61f9c04e56036a29f4b3 to your computer and use it in GitHub Desktop.
Save superwills/2f98fc72f07e61f9c04e56036a29f4b3 to your computer and use it in GitHub Desktop.
GDI+, Gdiplus Image Loading and Saving PNG or JPG
#include <windows.h>
#include <gdiplus.h>
#include <stdio.h>
using namespace Gdiplus;
#pragma comment( lib, "gdiplus.lib" )
#pragma warning( disable : 4018 )
#pragma warning( disable : 4996 )
LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM );
INT WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow )
{
// attach a console
AllocConsole();
AttachConsole( GetCurrentProcessId() );
freopen( "CON", "w", stdout );
// Start up GDI+.
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup( &gdiplusToken, &gdiplusStartupInput, NULL );
WNDCLASS wndClass;
wndClass.style = CS_HREDRAW | CS_VREDRAW;
wndClass.lpfnWndProc = WndProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = hInstance;
wndClass.hIcon = LoadIcon( NULL, IDI_APPLICATION );
wndClass.hCursor = LoadCursor( NULL, IDC_ARROW );
wndClass.hbrBackground = ( HBRUSH )GetStockObject( WHITE_BRUSH );
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = TEXT( "GDIPLUSWINDOW" );
if( !RegisterClass( &wndClass ) ) { puts( "problem registering wndclass" ); }
HWND hWnd = CreateWindow(
TEXT( "GDIPLUSWINDOW" ), // window class name
TEXT( "Welcome to GDI+!" ), // window caption
WS_OVERLAPPEDWINDOW, // window style
20, // initial x position
20, // initial y position
640, // initial width
480, // initial height
NULL, // parent window handle
NULL, // window menu handle
hInstance, // program instance handle
NULL ); // creation parameters
ShowWindow( hWnd, iCmdShow );
UpdateWindow( hWnd );
MSG msg;
while( GetMessage( &msg, NULL, 0, 0 ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
GdiplusShutdown( gdiplusToken );
return msg.wParam;
}
void MessageBox( TCHAR* fmt, TCHAR* title, int options, va_list args )
{
TCHAR buf[ 1024 ];
int index = wvsprintf( buf, fmt, args );
buf[index] = '\n';
buf[index+1] = 0;
wprintf( buf );
MessageBox( HWND_DESKTOP, buf, title, options );
}
void info( TCHAR* fmt, ... )
{
va_list list;
va_start( list, fmt );
MessageBox( fmt, TEXT( "Info" ), MB_OK|MB_ICONINFORMATION, list );
}
void error( TCHAR* fmt, ... )
{
va_list list;
va_start( list, fmt );
MessageBox( fmt, TEXT( "Error" ), MB_OK|MB_ICONERROR, list );
}
TCHAR* GetStatusString( Gdiplus::Status status )
{
TCHAR* statuses[] = {
TEXT( "Ok: Indicates that the method call was successful." ),
TEXT( "GenericError: Indicates that there was an error on the method call, which is identified as something other than those defined by the other elements of this enumeration." ),
TEXT( "InvalidParameter: Indicates that one of the arguments passed to the method was not valid." ),
TEXT( "OutOfMemory: Indicates that the operating system is out of memory and could not allocate memory to process the method call. For an explanation of how constructors use the OutOfMemory status, see the Remarks section at the end of this topic." ),
TEXT( "ObjectBusy: Indicates that one of the arguments specified in the API call is already in use in another thread." ),
TEXT( "InsufficientBuffer: Indicates that a buffer specified as an argument in the API call is not large enough to hold the data to be received." ),
TEXT( "NotImplemented: Indicates that the method is not implemented." ),
TEXT( "Win32Error: Indicates that the method generated a Win32 error." ),
TEXT( "WrongState: Indicates that the object is in an invalid state to satisfy the API call. For example, calling Pen::GetColor from a pen that is not a single, solid color results in a WrongState status." ),
TEXT( "Aborted: Indicates that the method was aborted." ),
TEXT( "FileNotFound: Indicates that the specified image file or metafile cannot be found." ),
TEXT( "ValueOverflow: Indicates that the method performed an arithmetic operation that produced a numeric overflow." ),
TEXT( "AccessDenied: Indicates that a write operation is not allowed on the specified file." ),
TEXT( "UnknownImageFormat: Indicates that the specified image file format is not known." ),
TEXT( "FontFamilyNotFound: Indicates that the specified font family cannot be found. Either the font family name is incorrect or the font family is not installed." ),
TEXT( "FontStyleNotFound: Indicates that the specified style is not available for the specified font family." ),
TEXT( "NotTrueTypeFont: Indicates that the font retrieved from an HDC or LOGFONT is not a TrueType font and cannot be used with GDI+." ),
TEXT( "UnsupportedGdiplusVersion: Indicates that the version of GDI+ that is installed on the system is incompatible with the version with which the application was compiled." ),
TEXT( "GdiplusNotInitialized: Indicates that the GDI+API is not in an initialized state. To function, all GDI+ objects require that GDI+ be in an initialized state. Initialize GDI+ by calling GdiplusStartup." ),
TEXT( "PropertyNotFound: Indicates that the specified property does not exist in the image." ),
TEXT( "PropertyNotSupported: Indicates that the specified property is not supported by the format of the image and, therefore, cannot be set." ),
TEXT( "ProfileNotFound: Indicates that the color profile required to save an image in CMYK format was not found." ),
TEXT( "INVALID STATUS CODE" )
};
if( status < 0 || status > Gdiplus::Status::PropertyNotSupported + 1 ) // ProfileNotFound may not be there
status = (Gdiplus::Status)( Gdiplus::Status::PropertyNotSupported + 2 ); // gives last error (INVALID STATUS CODE)
return statuses[ status ];
}
struct ImageEncoders
{
private:
UINT byteSize; // byteSize of encoders on system
UINT NumberOfEncoders; // number of encoders on system
ImageCodecInfo* imageCodecs;
public:
enum ImageFormat { BMP, JPG, GIF, TIF, PNG };
ImageEncoders()
{
// How many encoders do we have on the system?
Gdiplus::GetImageEncodersSize( &NumberOfEncoders, &byteSize );
if( !byteSize || !NumberOfEncoders )
{
error( TEXT( "ERROR: There are no image encoders available, num=%d, size=%d" ),
NumberOfEncoders, byteSize );
return;
}
// Allocate space to get the ImageCodeInfo descriptor for each codec.
imageCodecs = ( ImageCodecInfo* )malloc( byteSize );
Gdiplus::GetImageEncoders( NumberOfEncoders, byteSize, imageCodecs );
wprintf( TEXT( "CODECS:\n" ) );
// Print the codecs we know
for( int i = 0; i < NumberOfEncoders; i++ )
{
wprintf( TEXT( " * Codec %d = Ext:%s Description:%s\n" ),
i, imageCodecs[i].FilenameExtension, imageCodecs[i].FormatDescription );
}
}
// File types lists look like *.jpg;*.jpeg;*.jfif
bool InFileTypesList( const TCHAR* ext, const TCHAR* filetypesList )
{
TCHAR* dup = wcsdup( filetypesList ); // We have to form a writeable copy of the FileTypesList
TCHAR* delimiters = TEXT( "*.;" );
TCHAR* tok = wcstok( dup, delimiters ); // 1st call is on dup, subsequent on NULL.
// So we want this call outside the while loop anyway.
bool in = 0;
do
{
if( ! wcsicmp( ext, tok ) )
in = 1;
else // Pull the next token
tok = wcstok( NULL, delimiters ); // wcstok retains state, so you pass NULL to use last tokenized string
} while( tok && !in );
free( dup ); // release the manipulatable duplicate.
return in;
}
CLSID GetCLSIDForExtension( const TCHAR* ext )
{
CLSID clsid = CLSID_NULL; // Start with assuming invalid clsid.
// Use a case-insensitive comparison
for( int i = 0; i < NumberOfEncoders; i++ )
{
if( InFileTypesList( ext, imageCodecs[ i ].FilenameExtension ) )
{
wprintf( TEXT("%s is type %s\n"), ext, imageCodecs[i].FormatDescription );
clsid = imageCodecs[ i ].Clsid; // Found a CLSID for this extension
break;
}
}
return clsid;
}
CLSID GetCLSIDByMime( const TCHAR* mimetype )
{
CLSID clsid = CLSID_NULL;
for( int i = 0; i < NumberOfEncoders; i++ )
{
// Straight comparison with listed mime type.
if( !wcsicmp( mimetype, imageCodecs[ i ].MimeType ) )
{
clsid = imageCodecs[ i ].Clsid;
break;
}
}
return clsid;
}
~ImageEncoders()
{
free( imageCodecs );
}
static bool Save( Image* im, TCHAR* filename )
{
ImageEncoders encoders;
// Extract the extension
TCHAR* dotLocation = wcsrchr( filename, TEXT('.') );
if( dotLocation ) // found.
{
CLSID clsid = encoders.GetCLSIDForExtension( dotLocation+1 );
if( clsid != CLSID_NULL )
{
Gdiplus::Status status = im->Save( filename, &clsid );
if( status != Gdiplus::Status::Ok )
{
error( TEXT( "ImageEncoders::Save( %s ): Failed to save: %s" ), filename, GetStatusString( status ) );
return 0;
}
else
wprintf( TEXT( "%s saved successfully\n" ), filename );
return 1;
}
}
error( TEXT( "ImageEncoders::Save( %s ): Failed to save; invalid extension" ), filename );
return 0;
}
};
LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
HDC hdc;
PAINTSTRUCT ps;
switch( message )
{
case WM_PAINT:
{
// "Start painting": The HDC "handle to a device context"
// is the 'surface' on which you draw.
// You don't draw directly to a window. Rather you draw to a "device context".
// The reason its this way is it makes it so you can draw to ANY SURFACE.
hdc = BeginPaint( hWnd, &ps );
#pragma region painting code
// Create a Graphics object for our window's hdc
Graphics g( hdc );
// Create a pen.
Pen bluePen( Color( 128, 0, 0, 255 ) );
bluePen.SetWidth( 8.0 );
g.DrawLine( &bluePen, 0, 0, 100, 100 );
g.DrawEllipse( &bluePen, 0, 0, 40, 40 );
g.DrawEllipse( &bluePen, 0, 0, 50, 50 );
SolidBrush blueBrush( Color( 120, 0, 0, 255 ) );
g.FillRectangle( &blueBrush, 60, 60, 190, 180 );
SolidBrush redBrush( Color( 123, 255, 0, 0 ) );
Font arialFont( TEXT( "Arial" ), 12.0 );
PointF pTextPos( 20, 20 );
g.DrawString( TEXT( "This is GDI+!" ),
strlen( "This is GDI+!" ),
&arialFont, pTextPos, &redBrush );
g.FillRectangle( &redBrush, 30, 30, 100, 180 );
TCHAR* imageFilename = TEXT( "picture.jpg" );
Image *im = new Image( imageFilename );
if( im->GetLastStatus() != Gdiplus::Ok )
{
error( TEXT( "Image `%s` not provided, please copy one into the src folder" ), imageFilename );
system( "start ." ); // opens current folder in windows explorer
}
else
{
g.DrawImage( im, 50.f, 50.f );
// Test saving as PNG and JPG
ImageEncoders::Save( im, TEXT( "COPY.png" ) );
ImageEncoders::Save( im, TEXT( "COPY.jpg" ) );
}
#pragma endregion
// "Wrap it up"
EndPaint( hWnd, &ps );
}
return 0;
case WM_KEYDOWN:
switch( wParam )
{
case VK_ESCAPE:
PostQuitMessage( 0 );
break;
}
return 0;
case WM_DESTROY:
PostQuitMessage( 0 );
return 0;
default:
return DefWindowProc( hWnd, message, wParam, lParam );
}
} // WndProc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment