Using gdi+ to save bitmaps as png's

Just starting out? Need help? Post your questions and find answers here.
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Using gdi+ to save bitmaps as png's

Post by srod »

Hi,

I'm trying to get gdi+ to save a 32-bit bitmap with an alpha channel to 32-bit png format.

Saving as png is no problem, preserving the alpha channel on the other hand... :? It seems that 'basic' gdi+ will not recognise the alpha channel in a 32-bit bitmap, which kind of makes sense, after all bitmaps are not really supposed to have alpha transparency. I suspect that I need to use image attributes, but with so little available documentation on gdi+ I'm a little stuck.

Anyone know how I should proceed?

I have some code. The following creates a normal 32-bit gdi bitmap, shoves a white box in the middle of it. Converts it to a gdi+ bitmap, sets all black pixels to have full transparency and uses gdi+ to save it in png format.

You'll need SFSxOI's gdi wrappers and Freak's macro framework to run the code : http://www.purebasic.fr/english/viewtopic.php?t=26459

Code: Select all

XIncludeFile "gdiplus_11.pbi" 

*token = Gdiplus_New() 
If *token 

;Let's create our 'main' image.
  mainImage = CreateImage(#PB_Any, 300, 300, 32)
;Only proceed if the image creation succeeded.
  If mainImage
    hdc = StartDrawing(ImageOutput(mainImage))
    ;Chuck a white box in the middle.
      Box(40,40,220,220,#White)
    ;Now steam into gdi+ and attempt to convert to png with alpha channel.
      If GdipCreateFromHDC(hdc, @*graphics) = #Ok 
        ;Create a gdi+ bitmap based on our gdi one.
          GdipCreateBitmapFromHBITMAP(ImageID(mainImage), 0, @*image)
        ;Set the alpha values of all 'black' pixels.
          For row = 0 To ImageHeight(mainImage)-1
            For col = 0 To ImageWidth(mainImage) - 1
              GetPixel(*image, col, row, @color)
              ;If color is black then set to transparent.
                If color&$ffffff = 0
                  color=0
                  SetPixel(*image, col, row, color)
                EndIf
            Next
          Next
          Gdiplus_ImageSaveToFile(*image, "test.png", #Png_Encoder)
          GdipDisposeImage(*imgage)
      EndIf
    StopDrawing()
    FreeImage(mainImage)  
    GdipDeleteGraphics(*graphics) 
  EndIf

EndIf
Gdiplus_Del(*token) 
   
End
Whilst the code creates a 32-bit png (as opposed to PB's 24-bit png) you'll see that the saved png has reset all of the alpha values to 255 (fully opaque!) You can see the lack of transparency by simply loading the resulting png in MS image viewer.

Any help much appreciated.
I may look like a mule, but I'm not a complete ass.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

Change the input filename to match a PNG of yours, hopefully with alpha transparency, set the output filename as desired and give this a try. You can check the result with Windows Picture and Fax viewer:

Code: Select all

;========================================================================= 
; Demo:             DoubleItUp 
; Author:           Lloyd Gallant (netmaestro) 
; Date:             August 8, 2007 
; 
; Takes a PNG image off your disk, creates a new image twice as wide 
; and then draws the original image twice, side by side 
; then saves the result preserving the alpha channel 
; 
;========================================================================= 


Procedure StringToBStr (string$) ; By Zapman Inspired by Fr34k 
  Protected Unicode$ = Space(Len(String$)* 2 + 2) 
  Protected bstr_string.l 
  PokeS(@Unicode$, String$, -1, #PB_Unicode) 
  bstr_string = SysAllocString_(@Unicode$) 
  ProcedureReturn bstr_string 
EndProcedure 

ProcedureDLL DoubleItUp(filenamein.s, filenameout.s) 

  CompilerIf Defined(GdiplusStartupInput, #PB_Structure) = 0 
    Structure GdiplusStartupInput 
      GdiPlusVersion.l 
      *DebugEventCallback.Debug_Event 
      SuppressBackgroundThread.l 
      SuppressExternalCodecs.l 
    EndStructure 
  CompilerEndIf  

  Protected PixelFormat32bppARGB = $26200A ; Correct pixel format for PNG 
  
  If Not OpenLibrary(0, "gdiplus.dll") 
    ProcedureReturn 0 
  EndIf 

  input.GdiplusStartupInput 
  input\GdiPlusVersion = 1 
  
  CallFunction(0, "GdiplusStartup", @*token, @input, #Null) 
  
  If *token 
  
    ; bring in your two images 
    CallFunction(0, "GdipCreateBitmapFromFile", StringToBStr(filenamein), @*image1) 
    CallFunction(0, "GdipCreateBitmapFromFile", StringToBStr(filenamein), @*image2) 
    
    ; see how big they are 
    CallFunction(0, "GdipGetImageWidth", *image1, @width) 
    CallFunction(0, "GdipGetImageHeight", *image1, @height) 
    
    ; create a third image sized to hold both 
    CallFunction(0, "GdipCreateBitmapFromScan0", width*2,height,0,PixelFormat32bppARGB,0,@*imageout) 
    
    ; kind of a gdiplus StartDrawing(ImageOutput) 
    CallFunction(0, "GdipGetImageGraphicsContext", *imageout, @*surface) 
    
    ; draw the images 
    CallFunction(0, "GdipDrawImageRectI", *surface, *image1, 0,0,width,height) 
    CallFunction(0, "GdipDrawImageRectI", *surface, *image2, width,0,width,height) 
  
    ; kind of a gdiplus StopDrawing() 
    CallFunction(0, "GdipDeleteGraphics", *surface)  
        
    ; clsid for png image format 
    DataSection 
      clsid_png: 
      Data.l $557cf406 
      Data.w $1a04 
      Data.w $11d3 
      Data.b $9a,$73,$00,$00,$f8,$1e,$f3,$2e 
    EndDataSection 
    
    result = CallFunction(0, "GdipSaveImageToFile", *imageout, StringToBStr(filenameout), ?clsid_png, 0) 
    CallFunction(0, "GdipDisposeImage", *image1) 
    CallFunction(0, "GdipDisposeImage", *image2) 
    CallFunction(0, "GdipDisposeImage", *imageout) 
    CallFunction(0, "GdiplusShutdown", *token) 
    CloseLibrary(0) 
    
    If result = 0 
      ProcedureReturn 1 
    Else 
      ProcedureReturn 0 
    EndIf 
  Else 
    ProcedureReturn 0 
  EndIf 
EndProcedure 

Debug DoubleItUp("c:\back.png", "c:\test.png") 
This technique can be used to create one large PNG image from several smaller ones and save the result, simply bring in and draw more and different images on the output.

- Ribbit -
Last edited by netmaestro on Thu Aug 09, 2007 2:19 am, edited 2 times in total.
BERESHEIT
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

Netmaestro you're a bloody genius! :)

I thought of doing it this way but didn't know how to create a gdi+ bitmap of the required pixel format! The missing ingredient : GdipCreateBitmapFromScan0().

God I wish there was a decent help manual for gdi+ ! :)

You've also opened up the way for saving a 32-bit bitmap with an alpha channel in png format which preserves the alpha. I'll hack this up in a minute!

Excellent. First class in fact.

Thanks.
I may look like a mule, but I'm not a complete ass.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

Thanks for the kind words.

Sorry I had the wrong pixel format, I actually had 32bppPARGB instead of 32bppARGB, but it still worked OK. I fixed it now with the right one.
BERESHEIT
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

I didn't even notice and inadvertently used the correct one! :)

You've opened up some nice possibilities here.

One question, where did you get the clsid from because it differs from the one in the gdi+ wrappers I'm using?
I may look like a mule, but I'm not a complete ass.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

I called GdipGetImageEncoders, debugged which index the image/png encoder was going into, debugged the members of the clsid structure of that encoder and then made a datasection with the displayed values. It may be safer to traverse the encoders every time and get the clsid from the returned structure in case they differ from machine to machine rather than hardcoding it.
BERESHEIT
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

Wonder why it's listed differently in the gdi+ wrappers I'm using?

Oh well it all works; including saving bitmaps with alpha transparency in pgn format which I've just finished. Just need to tidy it up.
I may look like a mule, but I'm not a complete ass.
Post Reply