Get more image formats with less overhead

Share your advanced PureBasic knowledge/code with the community.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Get more image formats with less overhead

Post 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 
Last edited by netmaestro on Thu Sep 29, 2011 3:57 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 »

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?
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! 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.
BERESHEIT
Derek
Addict
Addict
Posts: 2354
Joined: Wed Apr 07, 2004 12:51 am
Location: England

Post by Derek »

Nice, TIFF input will be especially useful. Thanks
User avatar
Joakim Christiansen
Addict
Addict
Posts: 2452
Joined: Wed Dec 22, 2004 4:12 pm
Location: Norway
Contact:

Post by Joakim Christiansen »

Sweet, that PNG decoder took like 84kb extra, now I can just use this! :D
I like logic, hence I dislike humans but love computers.
DominiqueB
Enthusiast
Enthusiast
Posts: 103
Joined: Fri Apr 25, 2003 4:00 pm
Location: France

Great code

Post 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
Dominique

Windows 10 64bits. Pure basic 32bits
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 »

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.
BERESHEIT
Brice Manuel

Post by Brice Manuel »

Very good work, netmaestro! This will be very useful. Thank you!
Edwin Knoppert
Addict
Addict
Posts: 1073
Joined: Fri Apr 25, 2003 11:13 pm
Location: Netherlands
Contact:

Post 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.
Seymour Clufley
Addict
Addict
Posts: 1264
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Post by Seymour Clufley »

Does this procedure preserve a PNG's transparency?
PB
PureBasic Expert
PureBasic Expert
Posts: 7581
Joined: Fri Apr 25, 2003 5:24 pm

Post 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.
Ollivier
Enthusiast
Enthusiast
Posts: 281
Joined: Mon Jul 23, 2007 8:30 pm
Location: FR

Post 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.
r_hyde
Enthusiast
Enthusiast
Posts: 155
Joined: Wed Jul 05, 2006 12:40 am

Post 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?
Ollivier
Enthusiast
Enthusiast
Posts: 281
Joined: Mon Jul 23, 2007 8:30 pm
Location: FR

Post by Ollivier »

It's forseeing and educational! :D
PB
PureBasic Expert
PureBasic Expert
Posts: 7581
Joined: Fri Apr 25, 2003 5:24 pm

Post 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. :(
Post Reply