March 29, 2024, 03:23:54 AM

News:

IonicWind Snippit Manager 2.xx Released!  Install it on a memory stick and take it with you!  With or without IWBasic!


Load .PNG resources from DLL

Started by fasecero, September 08, 2018, 02:53:28 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

fasecero

I liked a lot the idea of using a dll specifically for resources. But after adding several .BMP's the size of the dll increases a lot. The BMP format takes up a lot of space. After researching about the subject and gathering information from codes scattered over the internet plus an old example of Sapero on IStream, I managed to make it work :)

PNG and BMP are almost the same quality but PNG takes much less space. When loading a PNG file you have to specify a background color if the .PNG has transparency.

.rc format
1001 BITMAP "C:\\Example\\image.bmp"
1002 PNG "C:\\Example\\graphic.png"


Dll


$INCLUDE "windowssdk.inc"
$INCLUDE "gdiplus.inc"
$include "olectl.inc"

EXPORT load_bitmap
EXPORT Load_PNG

GdiplusStartupInput temp
INT_PTR _g_gdiptoken
IStream stream = 0

EXTERN _hinstance as uint

SUB load_bitmap(INT imgno, INT w, INT h), UINT
RETURN LoadImageA(_hinstance, MAKEINTRESOURCE(imgno), IMAGE_BITMAP, w,h, 0)
ENDSUB

SUB Load_PNG(INT imgno, INT ARGB_backcolor), UINT
temp.GdiplusVersion = 1
pointer newBmp = NULL
pointer bit = 0

IF GdiplusStartup(&_g_gdiptoken, temp, 0) = Ok  THEN
HRSRC hResource = FindResource(_hinstance, MAKEINTRESOURCE(imgno), "PNG")

IF hResource THEN
DWORD imageSize = SizeofResource(_hinstance, hResource)

IF imageSize THEN
pointer pResourceData = LockResource(_LoadResource(_hinstance, hResource))

IF pResourceData THEN
pointer m_hBuffer  = GlobalAlloc(GMEM_MOVEABLE, imageSize)

IF m_hBuffer THEN
LPVOID ptr = GlobalLock(m_hBuffer)

IF ptr THEN
memcpy(ptr, pResourceData, imageSize)
GlobalUnlock(m_hBuffer)
ENDIF

HRESULT hr = CreateStreamOnHGlobal(m_hBuffer, TRUE, &stream)

IF hr = S_OK THEN
IF GdipLoadImageFromStream(stream, &newBmp) = Ok THEN
GdipCreateHBITMAPFromBitmap(newBmp, &bit, ARGB_backcolor)
stream->Release()
GdipDisposeImage(newBmp)
ENDIF
ENDIF

GlobalUnlock(m_hBuffer)
GlobalFree(m_hBuffer)
ENDIF
ENDIF
ENDIF
ENDIF

GdiplusShutdown(_g_gdiptoken)
ENDIF

RETURN bit + 0
ENDSUB

SUB MAKEINTRESOURCE(n as WORD),POINTER
pointer pReturn = 0

_asm
lea eax,[ebp-4]
mov ebx,[ebp+8]
mov [eax],ebx
_endasm

RETURN pReturn
ENDSUB


Test

$include "windowssdk.inc"

$use "cfImages.lib"
DECLARE "cfImages.dll",load_bitmap(imgno:int,w:int,h:int),uint
DECLARE "cfImages.dll",Load_PNG(imgno:int,ARGB_backcolor:int),uint ' backcolor format 0XAARRGGBB (alpha, red, green, blue)

WINDOW win
INT hInt
UINT hBitmap2

OPENWINDOW win,0,0,800,600,@MINBOX|@MAXBOX|@SIZE|@NOAUTODRAW,0,"Window",&win_handler
hBitmap2=Load_PNG(1002,0xFFFFFFFF)  ' load a PNG resource
setcaption win,str$(hBitmap2)
SHOWIMAGE win,hBitmap2,@IMGBITMAP,50,50,256,256

WAITUNTIL ISWINDOWCLOSED(win)
END

SUB win_handler(),INT
SELECT @MESSAGE
CASE @IDCREATE
CENTERWINDOW win
CASE @IDCLOSEWINDOW
CLOSEWINDOW win
ENDSELECT
RETURN 0
ENDSUB

Brian

Fasecero,

That's great. PNG's seem to be about half the size of similar bitmaps, and the transparency allows you to draw images to the screen that are irregular in shape without bleeding. Brilliant!

Brian

Andy

Nice solution,

I wonder if I should make my bitmaps PNG's to reduce the size of my exe file that has all the bitmaps stored in it.

Andy.
:)
Day after day, day after day, we struck nor breath nor motion, as idle as a painted ship upon a painted ocean.

Brian

Fasecero,

Been messing with your code for the PNGs. The only drawback I see is that you have to draw your image on screen at the exact size the image is. You can't resize the image larger (doesn't do anything), and if you try to make it smaller, it chops off the parts it can't display

Brian

fasecero

Andy, if you decide to do it the function should work (I think) for an exe without needing any change, just copy all the code to your exe and you are ready to go. It would be convenient if if you have many images, or just a few but with large size.

Brian, here is an update that allows you to change the image size. I also like the transparency bit and I'm reading more about it to see if I can go one step further and draw translucent images, but some articles are like reading Egyptian hieroglyphs :D, so we'll see.


$INCLUDE "windowssdk.inc"
$INCLUDE "gdiplus.inc"
$include "olectl.inc"

EXPORT load_bitmap
EXPORT Load_PNG

GdiplusStartupInput temp
INT_PTR _g_gdiptoken
IStream stream = 0

EXTERN _hinstance as uint

SUB load_bitmap(INT imgno, INT w, INT h), UINT
RETURN LoadImageA(_hinstance, MAKEINTRESOURCE(imgno), IMAGE_BITMAP, w,h, 0)
ENDSUB

SUB Load_PNG(INT imgno, INT width, INT height, INT ARGB_backcolor), UINT
temp.GdiplusVersion = 1
pointer newBmp = NULL
pointer bit = 0

IF GdiplusStartup(&_g_gdiptoken, temp, 0) = Ok  THEN
HRSRC hResource = FindResource(_hinstance, MAKEINTRESOURCE(imgno), "PNG")

IF hResource THEN
DWORD imageSize = SizeofResource(_hinstance, hResource)

IF imageSize THEN
pointer pResourceData = LockResource(_LoadResource(_hinstance, hResource))

IF pResourceData THEN
pointer m_hBuffer  = GlobalAlloc(GMEM_MOVEABLE, imageSize)

IF m_hBuffer THEN
LPVOID ptr = GlobalLock(m_hBuffer)

IF ptr THEN
memcpy(ptr, pResourceData, imageSize)
ENDIF

HRESULT hr = CreateStreamOnHGlobal(m_hBuffer, TRUE, &stream)

IF hr = S_OK THEN
IF GdipLoadImageFromStream(stream, &newBmp) = Ok THEN
'GdipCreateHBITMAPFromBitmap(newBmp, &bit, ARGB_backcolor)
bit = BitmapResize(newBmp, width, height, ARGB_backcolor)
GdipDisposeImage(newBmp)
ENDIF

stream->Release()
ENDIF

GlobalUnlock(m_hBuffer)
GlobalFree(m_hBuffer)
ENDIF
ENDIF
ENDIF
ENDIF

GdiplusShutdown(_g_gdiptoken)
ENDIF

RETURN bit + 0
ENDSUB

SUB BitmapResize(pointer oldImage, INT width, INT height, INT ARGB_backcolor), uint
float dimx, dimy
pointer GC
pointer newBmp = NULL
pointer newGC
pointer bit = 0
pointer imageattr

IF GdipGetImageDimension(oldImage, &dimx, &dimy) = ok THEN
IF GdipGetImageGraphicsContext(oldImage, &GC) = ok THEN
IF GdipCreateBitmapFromGraphics(width, height, GC, &newBmp) = ok THEN
IF GdipGetImageGraphicsContext(newBmp, &newGC) = ok THEN
IF GdipCreateImageAttributes(&imageattr) = ok THEN

IF GdipDrawImageRectRectI(newGC, oldImage, 0, 0, width, height, 0, 0, dimx, dimy, UnitPixel, imageattr, 0, 0) = ok THEN
GdipCreateHBITMAPFromBitmap(newBmp, &bit, ARGB_backcolor)
ENDIF

GdipDisposeImageAttributes(imageattr)
ENDIF

GdipDeleteGraphics(newGC)
ENDIF

GdipDisposeImage(newBmp)
ENDIF

GdipDeleteGraphics(GC)
ENDIF
ENDIF

RETURN bit + 0
ENDSUB

SUB MAKEINTRESOURCE(n as WORD),POINTER
pointer pReturn = 0

_asm
lea eax,[ebp-4]
mov ebx,[ebp+8]
mov [eax],ebx
_endasm

RETURN pReturn
ENDSUB



$include "windowssdk.inc"

$use "cfImages.lib"
DECLARE "cfImages.dll",load_bitmap(imgno:int,w:int,h:int),uint
DECLARE "cfImages.dll",Load_PNG(imgno:int,width:int,height:int,ARGB_backcolor:int),uint ' backcolor format 0XAARRGGBB (alpha, red, green, blue)

WINDOW win
INT hInt
UINT hBitmap1,hBitmap2,hBitmap3

OPENWINDOW win,0,0,600,400,@MINBOX|@MAXBOX|@SIZE|@NOAUTODRAW,0,"Window",&win_handler
SETWINDOWCOLOR win, RGB(200,255,200)

WAITUNTIL ISWINDOWCLOSED(win)
END

SUB win_handler(),INT
SELECT @MESSAGE
CASE @IDCREATE
CENTERWINDOW win
hBitmap1=Load_PNG(1002,64,64,0xFFC8FFC8)
hBitmap2=Load_PNG(1002,128,128,0xFFC8FFC8)
hBitmap3=Load_PNG(1002,256,256,0xFFC8FFC8)

CASE @IDPAINT
SHOWIMAGE win,hBitmap1,@IMGBITMAP,50,50,64,64
SHOWIMAGE win,hBitmap2,@IMGBITMAP,150,50,128,128
SHOWIMAGE win,hBitmap3,@IMGBITMAP,300,50,256,256

CASE @IDCLOSEWINDOW
CLOSEWINDOW win
ENDSELECT

RETURN 0
ENDSUB



Brian

That works a treat, Fasecero! Many thanks

Brian

fasecero

September 10, 2018, 08:03:15 AM #6 Last Edit: September 10, 2018, 10:49:17 AM by fasecero
Thank you!

I was able to achieve the goal of drawing translucent images, more or less. Currently it has some issues with the @NOAUTODRAW flag so I had to remove it. To work the png needs a black background color and the SHOW_ALPHABLEND_IMAGE subroutine, this function will replace the black color of the bitmap with whatever is behind, so we can truly draw transparent images. Also we can modify the transparency of the "opaque" zone with an alpha value of 1 for totally transparent and 255 for the default completely opaque. Check the improvised example below (using a 256x256 png file) to appreciate how well the images blend together.


  • EDIT: Just in case I wasn't clear... you don't have to manually edit the PNG and paint it black, leave as is. Simply use the value 0xFF000000 (black color) in the Load_PNG function.


$include "windowssdk.inc"

$use "cfImages.lib"
DECLARE "cfImages.dll",load_bitmap(imgno:int,w:int,h:int),uint
DECLARE "cfImages.dll",Load_PNG(imgno:int,width:int,height:int,ARGB_backcolor:int),uint ' backcolor format 0XAARRGGBB (alpha, red, green, blue)

WINDOW win
INT hInt
UINT hBitmap1
UINT hBlend21, hBlend22, hBlend23

OPENWINDOW win,0,0,600,400,@MINBOX|@MAXBOX|@SIZE,0,"Window",&win_handler
SETWINDOWCOLOR win, RGB(200,255,200)

hBitmap1=Load_PNG(1002,64,64,0xFFC8FFC8)
hBlend21=Load_PNG(1002,128,128,0xFF000000) ' alpha blend images needs a BLACK background color
hBlend22=Load_PNG(1002,200,200,0xFF000000)
hBlend23=Load_PNG(1002,256,256,0xFF000000)

SHOWIMAGE win,hBitmap1,@IMGBITMAP,50,50,64,64
SHOW_ALPHABLEND_IMAGE(win, hBlend21, 200, 150, 50, 128, 128) ' alpha: 1 to 255 / I'm using 200 here
SHOW_ALPHABLEND_IMAGE(win, hBlend22, 200, 190, 50, 200, 200)
SHOW_ALPHABLEND_IMAGE(win, hBlend23, 200, 300, 50, 256, 256)

WAITUNTIL ISWINDOWCLOSED(win)
END

SUB win_handler(),INT
SELECT @MESSAGE
CASE @IDCREATE
CENTERWINDOW win

CASE @IDCLOSEWINDOW
CLOSEWINDOW win
ENDSELECT

RETURN 0
ENDSUB

SUB SHOW_ALPHABLEND_IMAGE(WINDOW win, UINT hbit, INT alpha, INT x, INT y, INT width, INT height) ' alpha: 1 to 255
INT hDC = GetHDC(win)
HDC hdcMem = CreateCompatibleDC(hDC)
    HBITMAP hbmOld = SelectObject(hdcMem, hbit)

BITMAP bm
GetObject(hbit, LEN(bm), &bm)

    BLENDFUNCTION blender
blender.BlendOp = AC_SRC_OVER
blender.BlendFlags = 0
blender.SourceConstantAlpha = alpha
blender.AlphaFormat = AC_SRC_ALPHA
    AlphaBlend(hDC, x, y, width, height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, blender)

    SelectObject(hdcMem, hbmOld)
    DeleteDC(hdcMem)
ReleaseHDC win, hDC
ENDSUB

LarryMc

LarryMc
Larry McCaughn :)
Author of IWB+, Custom Button Designer library, Custom Chart Designer library, Snippet Manager, IWGrid control library, LM_Image control library

fasecero

Thanks Larry!

one last thing I want to share is the possibility of using this code to assign a background image to a window. In this example I'm using a .jpg of only 59KB! with an alpha value of 150. The child controls have to process the WM_CTLCOLORSTATIC message to become transparent to the background. I think that with the right layout, an appropriate background and with a good alpha value you can create a more attractive interface.


1020 PNG "C:\\Example\\background.jpg"



$INCLUDE "windowssdk.inc"
$INCLUDE "commctrl.inc"
$INCLUDE "uxtheme.inc"

$USE "Uxtheme.lib"

$use "cfImages.lib"
DECLARE "cfImages.dll",load_bitmap(imgno:int,w:int,h:int),uint
DECLARE "cfImages.dll",Load_PNG(imgno:int,width:int,height:int,ARGB_backcolor:int),uint ' backcolor format 0XAARRGGBB (alpha, red, green, blue)

' variables
INT guiFont
INT subclassID = 12345
INT hBackground

' interface
CONST STATIC_1 = 1
CONST GROUP_2 = 2
CONST RADIO_3 = 3
CONST RADIO_4 = 4
CONST CHECK_5 = 5
CONST CHECK_7 = 7
CONST BUTTON_8 = 8
CONST RADIO_9 = 9
WINDOW w1
OPENWINDOW w1,0,0,428,484,@CAPTION,NULL,"Simple Window",&w1_handler
AfterWindowCreated()
CONTROL w1,@STATIC,"Background Demo...",52,70,220,23,0x5000010B,STATIC_1
CONTROL w1,@GROUPBOX," Please select an option ",81,112,193,129,0x50000007,GROUP_2
CONTROL w1,@RADIOBUTTON,"Go to DOS",121,138,100,20,0x50000009,RADIO_3
CONTROL w1,@RADIOBUTTON,"Go to Windows",121,167,131,20,0x50000009,RADIO_4
CONTROL w1,@CHECKBOX,"Checkbox 1",41,258,232,23,0x50000003,CHECK_5
CONTROL w1,@CHECKBOX,"Checkbox 2",40,289,239,25,0x50000003,CHECK_7
CONTROL w1,@SYSBUTTON,"Close",289,326,106,34,0x50000000,BUTTON_8
CONTROL w1,@RADIOBUTTON,"Go to Bahamas",121,196,128,23,0x50000009,RADIO_9
AfterUIInitialized()

' main loop
WAITUNTIL w1 = 0
END

' window procedure
SUB w1_handler
SELECT @MESSAGE
CASE @IDCREATE
CENTERWINDOW w1

CASE @IDCLOSEWINDOW
OnClose()
CLOSEWINDOW w1

CASE @IDCONTROL
SELECT @CONTROLID
CASE RADIO_3
IF @NOTIFYCODE = 0
/*button clicked*/
ENDIF
CASE RADIO_4
IF @NOTIFYCODE = 0
/*button clicked*/
ENDIF
CASE CHECK_5
IF @NOTIFYCODE = 0
/*button clicked*/
ENDIF
CASE CHECK_7
IF @NOTIFYCODE = 0
/*button clicked*/
ENDIF
CASE BUTTON_8
IF @NOTIFYCODE = 0
/*button clicked*/
_SendMessage(w1.hwnd, WM_CLOSE, 0, 0)
ENDIF
CASE RADIO_9
IF @NOTIFYCODE = 0
/*button clicked*/
ENDIF
ENDSELECT
ENDSELECT
ENDSUB

SUB subclassProc(hWnd:INT,uMsg:INT,wParam:INT,lParam:INT,uIdSubclass:UINT_PTR,dwRefData:DWORD_PTR),INT
SELECT uMsg
CASE WM_CTLCOLORSTATIC ' this set some basic controls as transparent
SELECT GetDlgCtrlID(lParam)
CASE GROUP_2
SetBkMode(wParam, OPAQUE)
RETURN GetStockObject(NULL_PEN)
DEFAULT
SetBkMode(wParam, TRANSPARENT)
RETURN GetStockObject(NULL_PEN)
ENDSELECT

DEFAULT
RETURN DefSubclassProc(hWnd, uMsg, wParam, lParam)
ENDSELECT

RETURN 0
ENDSUB


' -------------------------------------------------------------------------------------------------------------------------------------------------------
' EVENTS
' -------------------------------------------------------------------------------------------------------------------------------------------------------

SUB AfterWindowCreated()
SetWindowSubclass(w1.hwnd, &subclassProc, subclassID, 0)
RemoveCaptionTextAndIcon(w1)

hBackground = Load_PNG(1020,500,750,0xFF000000)
SHOW_ALPHABLEND_IMAGE(w1, hBackground, 150,0, 0, 428, 484)
ENDSUB

SUB AfterUIInitialized()
guiFont = CreateGUIFont()
AplyFontToChildControls(w1, guiFont) ' aply the font to all the controls

SETSTATE(w1, RADIO_3, 1)
SETSTATE(w1, CHECK_5, 1)
ENDSUB

SUB OnClose()
DeleteGuiFont(guiFont)
RemoveWindowSubclass(w1.hwnd, &subclassProc, subclassID)
ENDSUB

' -------------------------------------------------------------------------------------------------------------------------------------------------------
' CAPTION
' -------------------------------------------------------------------------------------------------------------------------------------------------------

SUB RemoveCaptionTextAndIcon(WINDOW w)
WTA_OPTIONS options

options.dwFlags = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON | WTNCA_NOSYSMENU
options.dwMask = WTNCA_VALIDBITS

SetWindowThemeAttribute(w.hwnd, WTA_NONCLIENT, options, LEN(options))
ENDSUB

' -------------------------------------------------------------------------------------------------------------------------------------------------------
' FONT
' -------------------------------------------------------------------------------------------------------------------------------------------------------

SUB CreateGUIFont(), INT
RETURN CreateFont(15, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Segoe UI")
ENDSUB

SUB DeleteGuiFont(HFONT font)
IF font THEN DeleteObject(font)
ENDSUB

SUB AplyFontToChildControls(WINDOW w, HFONT font)
EnumChildWindows(w.hwnd, &EnumChildProc, font)
ENDSUB

SUB EnumChildProc(HWND hwnd, LPARAM lParam) ' aux func
_SendMessage(hwnd, WM_SETFONT, lParam, TRUE)
RETURN TRUE
ENDSUB

' -------------------------------------------------------------------------------------------------------------------------------------------------------
' BACKGROUND
' -------------------------------------------------------------------------------------------------------------------------------------------------------

SUB SHOW_ALPHABLEND_IMAGE(WINDOW win, UINT hbit, INT alpha, INT x, INT y, INT width, INT height) ' alpha: 1 to 255
INT hDC = GetHDC(win)
HDC hdcMem = CreateCompatibleDC(hDC)
    HBITMAP hbmOld = SelectObject(hdcMem, hbit)

BITMAP bm
GetObject(hbit, LEN(bm), &bm)

    BLENDFUNCTION blender
blender.BlendOp = AC_SRC_OVER
blender.BlendFlags = 0
blender.SourceConstantAlpha = alpha
blender.AlphaFormat = AC_SRC_ALPHA
    AlphaBlend(hDC, x, y, width, height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, blender)

    SelectObject(hdcMem, hbmOld)
    DeleteDC(hdcMem)
ReleaseHDC win, hDC
ENDSUB



LarryMc

LarryMc
Larry McCaughn :)
Author of IWB+, Custom Button Designer library, Custom Chart Designer library, Snippet Manager, IWGrid control library, LM_Image control library

Egil






fasecero,

Brilliant idea and great work.
When I return home I'll play around with your code.

Thanks for sharing!


Egil








Support Amateur Radio  -  Have a ham  for dinner!

Brian

That's pretty neat, Fasecero! It will pass my day on looking at it . . .

Brian

fasecero


Andy

Fasecero,

This is really great code!  8)

I was just wondering is it at all possible to incorporate the IWB LOADIMAGE function (or something similar) so that we could also load png files from a local folder as well as a resource file?

Thanks,
Andy.
:)


Day after day, day after day, we struck nor breath nor motion, as idle as a painted ship upon a painted ocean.

fasecero

Yes, gdiplus has a function called GdipLoadImageFromFile specifically for that. Actually it also has a specific function to load resources, but I could never make it work. The "type" of resource name that it requires (instead of the custom "PNG") was not accepted by the compiler.

Add the following code to the dll and rename the function as you wish

EXPORT Load_PNG_FromFile

SUB Load_PNG_FromFile(STRING filename, INT width, INT height, INT ARGB_backcolor), UINT
temp.GdiplusVersion = 1
pointer newBmp = NULL
pointer bit = 0

IF GdiplusStartup(&_g_gdiptoken, temp, 0) = Ok  THEN
IF GdipLoadImageFromFile(S2W(filename), &newBmp) = ok THEN
bit = BitmapResize(newBmp, width, height, ARGB_backcolor)
GdipDisposeImage(newBmp)
ENDIF

GdiplusShutdown(_g_gdiptoken)
ENDIF

RETURN bit + 0
ENDSUB


Example

hBackground = Load_PNG_FromFile(GETSTARTPATH + "//background.jpg", 500,750,0xFF000000)





Brian

Fasecero,

That's another great addition to the PNG library. I've added it to the DLL code and recompiled - works a treat

To make it all complete, what would be the code to load a PNG that has been embedded into an exe? Had a look, but it doesn't make much sense to me!

Thanks again,

Brian

fasecero

I have not tested it, but it should work without making any changes. Make a new .exe project, add some resources and paste all the dll code without the EXPORT sentences. Then use the functions as normally.