Saving+a+PNG+image

//by Richard Russell, November 2010//

This article describes how to save an image as a PNG file. It relies on the presence of the //GDI Plus// library so will work only on Windows XP (or later) or if you have specifically installed **GDIPLUS.DLL** on the target computer. Microsoft permits you to redistribute this file so you can include it with your program if necessary.

To begin with you need to have a **handle** to the bitmap you want to save (see below if instead the bitmap is in the form of a **DIB**). One way of obtaining a handle is to load the image from a file (e.g. a BMP file):

code format="bb4w" bmpfile$ = "\Windows\Soap Bubbles.bmp" SYS "LoadImage", 0, bmpfile$, 0, 0, 0, 16 TO hbitmap% code Here the image is loaded at its original size. You can alternatively scale the image to different dimensions:

code format="bb4w" bmpfile$ = "\Windows\Soap Bubbles.bmp" SYS "LoadImage", 0, bmpfile$, 0, dx%, dy%, 16 TO hbitmap% code Where **dx%** and **dy%** are the wanted width and height of the image respectively (the scaling quality is not particularly good so for best results you might prefer to scale the image using a third-party program).

There are a number of other ways in which you might obtain a bitmap handle, which are outside the scope of this article. You can easily obtain a handle to whatever is displayed in your program's output window:

code format="bb4w" SYS "GetCurrentObject", @memhdc%, 7 TO hbitmap% code but this ordinarily returns the //entire// 1920 x 1440 bitmap which is probably not what you want. To save just a region of your program's output window the easiest way is probably to save it first as a BMP file (using ***GSAVE**) then load it using LoadImage as shown above.

Once you've got a handle to the bitmap you simply save it as a PNG file as follows:

code format="bb4w" PROCsavepng(hbitmap%, filename$) code Here **filename$** is the name of the PNG file to create.

One you've saved the file you should delete the bitmap handle:

code format="bb4w" SYS "DeleteObject", hbitmap% code Finally, here's the code for **PROCsavepng** itself:

code format="bb4w" DEF PROCsavepng(hbitmap%, filename$) LOCAL gdiplus%, ole32% SYS "LoadLibrary", "GDIPLUS.DLL" TO gdiplus% IF gdiplus% = 0 ERROR 100, "Couldn't load GDIPLUS.DLL" SYS "GetProcAddress", gdiplus%, "GdiplusStartup" TO `GdiplusStartup` SYS "GetProcAddress", gdiplus%, "GdiplusShutdown" TO `GdiplusShutdown` SYS "GetProcAddress", gdiplus%, "GdipCreateBitmapFromHBITMAP" TO `GdipCreateBitmapFromHBITMAP` SYS "GetProcAddress", gdiplus%, "GdipDisposeImage" TO `GdipDisposeImage` SYS "GetProcAddress", gdiplus%, "GdipSaveImageToFile" TO `GdipSaveImageToFile` SYS "LoadLibrary", "OLE32.DLL" TO ole32% SYS "GetProcAddress", ole32%, "CLSIDFromString" TO `CLSIDFromString` LOCAL tSI{}, lRes%, lGDIP%, lBitmap%, tPngEncoder%, guid%, filename% DIM tPngEncoder% LOCAL 15, guid% LOCAL 79, filename% LOCAL 2*LEN(filename$)+1 DIM tSI{GdiplusVersion%, DebugEventCallback%, \ \      SuppressBackgroundThread%, SuppressExternalCodecs%} REM Initialize GDI+ tSI.GdiplusVersion% = 1 SYS `GdiplusStartup`, ^lGDIP%, tSI{}, 0 TO lRes% IF lRes% ERROR 100, "GDI+ error "+STR$(lRes%) REM Create the GDI+ bitmap from the image handle SYS `GdipCreateBitmapFromHBITMAP`, hbitmap%, 0, ^lBitmap% TO lRes% IF lRes% ERROR 100, "GDI+ error "+STR$(lRes%) REM Initialize the encoder GUID SYS "MultiByteToWideChar", 0, 0, "{557CF406-1A04-11D3-9A73-0000F81EF32E}", -1, guid%, 40 SYS `CLSIDFromString`, guid%, tPngEncoder% REM Save the image SYS "MultiByteToWideChar", 0, 0, filename$, -1, filename%, LEN(filename$)+1 SYS `GdipSaveImageToFile`, lBitmap%, filename%, tPngEncoder%, 0 TO lRes% IF lRes% ERROR 100, "GDI+ error "+STR$(lRes%) REM Destroy the bitmap SYS `GdipDisposeImage`, lBitmap% REM Shutdown GDI+ SYS `GdiplusShutdown`, lGDIP% SYS "FreeLibrary", gdiplus% ENDPROC code

If, instead of a bitmap **handle**, you have a bitmap (DIB) stored in memory you can use this alternative routine: code format="bb4w" DEF PROCsavepngdib(dib%, bmi%, filename$) LOCAL gdiplus%, ole32% SYS "LoadLibrary", "GDIPLUS.DLL" TO gdiplus% IF gdiplus% = 0 ERROR 100, "Couldn't load GDIPLUS.DLL" SYS "GetProcAddress", gdiplus%, "GdiplusStartup" TO `GdiplusStartup` SYS "GetProcAddress", gdiplus%, "GdiplusShutdown" TO `GdiplusShutdown` SYS "GetProcAddress", gdiplus%, "GdipCreateBitmapFromGdiDib" TO `GdipCreateBitmapFromGdiDib` SYS "GetProcAddress", gdiplus%, "GdipDisposeImage" TO `GdipDisposeImage` SYS "GetProcAddress", gdiplus%, "GdipSaveImageToFile" TO `GdipSaveImageToFile` SYS "LoadLibrary", "OLE32.DLL" TO ole32% SYS "GetProcAddress", ole32%, "CLSIDFromString" TO `CLSIDFromString` LOCAL tSI{}, lRes%, lGDIP%, lBitmap%, tPngEncoder%, guid%, filename% DIM tPngEncoder% LOCAL 15, guid% LOCAL 79, filename% LOCAL 2*LEN(filename$)+1 DIM tSI{GdiplusVersion%, DebugEventCallback%, \ \      SuppressBackgroundThread%, SuppressExternalCodecs%} REM Initialize GDI+ tSI.GdiplusVersion% = 1 SYS `GdiplusStartup`, ^lGDIP%, tSI{}, 0 TO lRes% IF lRes% ERROR 100, "GDI+ error "+STR$(lRes%) REM Create the GDI+ bitmap from the DIB SYS `GdipCreateBitmapFromGdiDib`, bmi%, dib%, ^lBitmap% TO lRes% IF lRes% ERROR 100, "GDI+ error "+STR$(lRes%) REM Initialize the encoder GUID SYS "MultiByteToWideChar", 0, 0, "{557CF406-1A04-11D3-9A73-0000F81EF32E}", -1, guid%, 40 SYS `CLSIDFromString`, guid%, tPngEncoder% REM Save the image SYS "MultiByteToWideChar", 0, 0, filename$, -1, filename%, LEN(filename$)+1 SYS `GdipSaveImageToFile`, lBitmap%, filename%, tPngEncoder%, 0 TO lRes% IF lRes% ERROR 100, "GDI+ error "+STR$(lRes%) REM Destroy the bitmap SYS `GdipDisposeImage`, lBitmap% REM Shutdown GDI+ SYS `GdiplusShutdown`, lGDIP% SYS "FreeLibrary", gdiplus% ENDPROC code

You would call it as follows: code format="bb4w" PROCsavepngdib(dibits%, bmi{}, filename$) code where **dibits%** is the address of the bitmap data and **bmi{}** is a BITMAPINFO structure containing the dimensions etc. and (optionally) colour palette for the bitmap.