Page 1 of 3

Get more image formats with less overhead

Posted: Fri Feb 23, 2007 11:12 am
by netmaestro
You can use this code to bring images into your program in formats GIF, JPEG, TIFF, BMP, PNG, WMF and ICO all with no decoder necessary. Procedures are provided to load from file or catch from memory. Code may be used any one of three ways with good results: Compile to .dll, compile to lib with Tailbite, or make an include file. I had planned to make this code part of a larger gdiplus library but I have a large project to do in PowerBasic and I won't have time to work on it anytime soon.

Code: Select all

;============================================= 
; Library:         GetImage 
; Author:          Lloyd Gallant (netmaestro) 
; Date:            October 26, 2006 
; Target OS:       Microsoft Windows 
; Target Compiler: PureBasic 4.xx 
; Dependencies:    gdiplus.dll 
; License:         Open Source 
;============================================= 

Global PixelFormatIndexed        = $00010000 ; Indexes into a palette 
Global PixelFormatGDI            = $00020000 ; Is a GDI-supported format 
Global PixelFormatAlpha          = $00040000 ; Has an alpha component 
Global PixelFormatPAlpha         = $00080000 ; Pre-multiplied alpha 
Global PixelFormatExtended       = $00100000 ; Extended color 16 bits/channel 
Global PixelFormatCanonical      = $00200000 
Global PixelFormatUndefined      = 0 
Global PixelFormatDontCare       = 0 
Global PixelFormat1bppIndexed    = (1 | ( 1 << 8) |PixelFormatIndexed |PixelFormatGDI) 
Global PixelFormat4bppIndexed    = (2 | ( 4 << 8) |PixelFormatIndexed |PixelFormatGDI) 
Global PixelFormat8bppIndexed    = (3 | ( 8 << 8) |PixelFormatIndexed |PixelFormatGDI) 
Global PixelFormat16bppGrayScale = (4 | (16 << 8) |PixelFormatExtended) ; $100 
Global PixelFormat16bppRGB555    = (5 | (16 << 8) |PixelFormatGDI) 
Global PixelFormat16bppRGB565    = (6 | (16 << 8) |PixelFormatGDI) 
Global PixelFormat16bppARGB1555  = (7 | (16 << 8) |PixelFormatAlpha |PixelFormatGDI) 
Global PixelFormat24bppRGB       = (8 | (24 << 8) |PixelFormatGDI) 
Global PixelFormat32bppRGB       = (9 | (32 << 8) |PixelFormatGDI) 
Global PixelFormat32bppARGB      = (10 | (32 << 8) |PixelFormatAlpha |PixelFormatGDI |PixelFormatCanonical) 
Global PixelFormat32bppPARGB     = (11 | (32 << 8) |PixelFormatAlpha |PixelFormatPAlpha |PixelFormatGDI) 
Global PixelFormat48bppRGB       = (12 | (48 << 8) |PixelFormatExtended) 
Global PixelFormat64bppARGB      = (13 | (64 << 8) |PixelFormatAlpha  |PixelFormatCanonical |PixelFormatExtended) 
Global PixelFormat64bppPARGB     = (14 | (64 << 8) |PixelFormatAlpha  |PixelFormatPAlpha |PixelFormatExtended) 
Global PixelFormatMax            =  15  

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

Structure StreamObject 
  block.l 
  *bits 
  stream.ISTREAM 
EndStructure 

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 ImageFromMem(Address, Length) 
  
  Protected lib 
  lib = OpenLibrary(#PB_Any, "gdiplus.dll") 
  If Not lib 
    ProcedureReturn 0 
  EndIf 
  
  input.GdiplusStartupInput 
  input\GdiPlusVersion = 1 
  
  CallFunction(lib, "GdiplusStartup", @*token, @input, #Null) 
  If *token 
    stream.streamobject 
    Stream\block = GlobalAlloc_(#GHND, Length) 
    Stream\bits = GlobalLock_(Stream\block) 
    CopyMemory(address, stream\bits, Length) 
    If CreateStreamOnHGlobal_(stream\bits, 0, @Stream\stream) = #S_OK 
      CallFunction(lib, "GdipCreateBitmapFromStream", Stream\stream , @*image) 
     Else 
      CallFunction(lib, "GdiplusShutdown", *token) 
      ProcedureReturn 0 
    EndIf 
    
    If *image 
      CallFunction(lib, "GdipGetImageWidth", *image, @Width.l) 
      CallFunction(lib, "GdipGetImageHeight", *image, @Height.l) 
      CallFunction(lib, "GdipGetImagePixelFormat", *image, @Format.l) 
      
      Select Format 
        Case PixelFormat1bppIndexed: bits_per_pixel = 1 
        Case PixelFormat4bppIndexed: bits_per_pixel = 4 
        Case PixelFormat8bppIndexed: bits_per_pixel = 8 
        Case PixelFormat16bppARGB1555: bits_per_pixel = 16 
        Case PixelFormat16bppGrayScale: bits_per_pixel = 16 
        Case PixelFormat16bppRGB555: bits_per_pixel = 16 
        Case PixelFormat16bppRGB565: bits_per_pixel = 16 
        Case PixelFormat24bppRGB: bits_per_pixel = 24 
        Case PixelFormat32bppARGB: bits_per_pixel = 32 
        Case PixelFormat32bppPARGB: bits_per_pixel = 32 
        Case PixelFormat32bppRGB: bits_per_pixel = 32 
        Case PixelFormat48bppRGB: bits_per_pixel = 48 
        Case PixelFormat64bppARGB: bits_per_pixel = 64 
        Case PixelFormat64bppPARGB: bits_per_pixel = 64 
        Default : bits_per_pixel = 32 
      EndSelect 
      
      If bits_per_pixel < 24 : bits_per_pixel = 24 : EndIf 
      imagenumber = CreateImage(#PB_Any, Width, Height, bits_per_pixel) 
      Retval = ImageID(imagenumber) 
      hDC = StartDrawing(ImageOutput(ImageNumber)) 
      CallFunction(lib, "GdipCreateFromHDC", hdc, @*gfx) 
      CallFunction(lib, "GdipDrawImageRectI", *gfx, *image, 0, 0, Width, Height) 
      StopDrawing()  
      Stream\stream\Release() 
      GlobalUnlock_(Stream\bits) 
      GlobalFree_(Stream\block) 
      CallFunction(lib, "GdipDeleteGraphics", *gfx)  
      CallFunction(lib, "GdipDisposeImage", *image) 
      CallFunction(lib, "GdiplusShutdown", *token) 
      CloseLibrary(0) 
      
      ProcedureReturn imagenumber
    Else 
      ProcedureReturn 0 
    EndIf 
  Else 
    ProcedureReturn 0 
  EndIf 
EndProcedure 

ProcedureDLL ImageFromFile(Filename$) 
  Protected lib 
  lib = OpenLibrary(#PB_Any, "gdiplus.dll") 
  If Not lib 
    ProcedureReturn 0 
  EndIf 
  
  input.GdiplusStartupInput 
  input\GdiPlusVersion = 1 
  
  CallFunction(lib, "GdiplusStartup", @*token, @input, #Null) 
  If *token 
    CallFunction(lib, "GdipCreateBitmapFromFile", StringToBStr(Filename$), @*image) 
    CallFunction(lib, "GdipGetImageWidth", *image, @Width.l) 
    CallFunction(lib, "GdipGetImageHeight", *image, @Height.l) 
    CallFunction(lib, "GdipGetImagePixelFormat", *image, @Format.l) 
    
    Select Format 
      Case PixelFormat1bppIndexed: bits_per_pixel = 1 
      Case PixelFormat4bppIndexed: bits_per_pixel = 4 
      Case PixelFormat8bppIndexed: bits_per_pixel = 8 
      Case PixelFormat16bppARGB1555: bits_per_pixel = 16 
      Case PixelFormat16bppGrayScale: bits_per_pixel = 16 
      Case PixelFormat16bppRGB555: bits_per_pixel = 16 
      Case PixelFormat16bppRGB565: bits_per_pixel = 16 
      Case PixelFormat24bppRGB: bits_per_pixel = 24 
      Case PixelFormat32bppARGB: bits_per_pixel = 32 
      Case PixelFormat32bppPARGB: bits_per_pixel = 32 
      Case PixelFormat32bppRGB: bits_per_pixel = 32 
      Case PixelFormat48bppRGB: bits_per_pixel = 48 
      Case PixelFormat64bppARGB: bits_per_pixel = 64 
      Case PixelFormat64bppPARGB: bits_per_pixel = 64 
      Default : bits_per_pixel = 32 
    EndSelect 
    
    If bits_per_pixel < 24 : bits_per_pixel = 24 : EndIf 
    imagenumber = CreateImage(#PB_Any, Width, Height, bits_per_pixel) 
    Retval = ImageID(imagenumber) 
    hDC = StartDrawing(ImageOutput(ImageNumber)) 
    CallFunction(lib, "GdipCreateFromHDC", hdc, @*gfx) 
    CallFunction(lib, "GdipDrawImageRectI", *gfx, *image, 0, 0, Width, Height) 
    StopDrawing()  
    CallFunction(lib, "GdipDeleteGraphics", *gfx)  
    CallFunction(lib, "GdipDisposeImage", *image) 
    CallFunction(lib, "GdiplusShutdown", *token) 
    CloseLibrary(lib) 
    
    ProcedureReturn imagenumber 
  Else 
    ProcedureReturn 0 
  EndIf 
  
EndProcedure 

Posted: Fri Feb 23, 2007 11:30 am
by srod
Excellent work Lloyd - that is going to come in handy for a future project I have in mind. 8)

Can gdi+ also be used to save images in these various formats?

Posted: Fri Feb 23, 2007 11:36 am
by netmaestro
Thanks! It can, but it's a rather involved process that I haven't had time to fully master. The various encoder parameters are handled differently for each image format and for the simple ones like BMP or PNG it's easy but for GIF or JPEG with options to fill out it's more work.

Posted: Fri Feb 23, 2007 11:52 am
by Derek
Nice, TIFF input will be especially useful. Thanks

Posted: Fri Feb 23, 2007 11:55 am
by Joakim Christiansen
Sweet, that PNG decoder took like 84kb extra, now I can just use this! :D

Great code

Posted: Fri Feb 23, 2007 8:49 pm
by DominiqueB
Thank's for it !

Could you please add some samples to use this code
either as .dll or as library ?

Thank's

Dominique

Posted: Fri Feb 23, 2007 9:32 pm
by netmaestro
This method also overcomes a couple of problems with native decoding. For example, if you pull in a .png using the native decoder it will be unusable in the UpdateLayeredWindow api, and if you CatchImage an .ico its alpha layer doesn't survive. Both issues are solved by using gdiplus.

Posted: Fri Feb 23, 2007 9:35 pm
by Brice Manuel
Very good work, netmaestro! This will be very useful. Thank you!

Posted: Fri Feb 23, 2007 10:13 pm
by Edwin Knoppert
Just be aware it's gdiplus, which is fine for XP but came as distributable dll for 98.

Though, i can only recommend the stuff.

Posted: Sun Oct 07, 2007 11:13 am
by Seymour Clufley
Does this procedure preserve a PNG's transparency?

Posted: Sun Oct 07, 2007 11:50 am
by PB
How do you load a JPG with this? I thought I could just do a command like
MyImage=ImageFromFile(MyJPG$) but then if I use ImageWidth(MyImage)
I get a "#Image object not initialized" error.

Posted: Sun Oct 07, 2007 6:28 pm
by Ollivier
@PB

I never saw this excellent (one more time, it doesn't stop!) work of NetMaestro.

I don't like gdi+ though, I'm sure it's really useful.

That I can tell you is that the return value is not the number of the image, but the handle of the one (=ImageID).

You can use BITMAPINFO structure to get the width of your image.

@seymour

I suppose transparency is preserved.

Posted: Sun Oct 07, 2007 6:31 pm
by r_hyde

Code: Select all

Select Format 
  Case PixelFormat1bppIndexed: bits_per_pixel = 1 
  Case PixelFormat4bppIndexed: bits_per_pixel = 4 
  Case PixelFormat8bppIndexed: bits_per_pixel = 8 
  Case PixelFormat16bppARGB1555: bits_per_pixel = 16 
  Case PixelFormat16bppGrayScale: bits_per_pixel = 16 
  Case PixelFormat16bppRGB555: bits_per_pixel = 16 
  Case PixelFormat16bppRGB565: bits_per_pixel = 16 
  Case PixelFormat24bppRGB: bits_per_pixel = 24 
  Case PixelFormat32bppARGB: bits_per_pixel = 32 
  Case PixelFormat32bppPARGB: bits_per_pixel = 32 
  Case PixelFormat32bppRGB: bits_per_pixel = 32 
  Case PixelFormat48bppRGB: bits_per_pixel = 48 
  Case PixelFormat64bppARGB: bits_per_pixel = 64 
  Case PixelFormat64bppPARGB: bits_per_pixel = 64 
  Default : bits_per_pixel = 32 
EndSelect 
  
If bits_per_pixel < 24 : bits_per_pixel = 24 : EndIf
Why did you go to all that trouble and then fix the bpp at 24 or above?

Posted: Sun Oct 07, 2007 6:45 pm
by Ollivier
It's forseeing and educational! :D

Posted: Sun Oct 07, 2007 9:42 pm
by PB
> You can use BITMAPINFO structure to get the width of your image

Well, that's no good, I was hoping to use the routine as a drop-in replacement
for the LoadImage command, but obviously that can't be done because the
resulting ID isn't directly compatible with the other Image commands. :(