Page 1 of 1

Using gdi+ to save bitmaps as png's

Posted: Wed Aug 08, 2007 12:38 pm
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.

Posted: Wed Aug 08, 2007 8:10 pm
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 -

Posted: Wed Aug 08, 2007 9:42 pm
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.

Posted: Wed Aug 08, 2007 9:58 pm
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.

Posted: Wed Aug 08, 2007 10:00 pm
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?

Posted: Wed Aug 08, 2007 10:57 pm
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.

Posted: Wed Aug 08, 2007 11:08 pm
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.