Faster resize image

Just starting out? Need help? Post your questions and find answers here.
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 »

Documentation on gdiplus is notoriously scarce. Best way to get help on a command that I've found is to google the function name and you'll find examples in VB, Delphi etc. that shows you how to use it. For a complete listing of available commands, you can examine the library functions of gdiplus.dll in a loop.

For obtaining a gdiplus image from a PB image, once you have the dll open and a successful startup of gdiplus, you can execute this command:

Code: Select all

CallFunction(0, "GdipCreateBitmapFromHBITMAP", ImageID(#pbimg), 0, @*image)
and *image will point to a valid gdiplus image.

To make a homemade CatchImage for an IncludeBinary-ed image, here's a snippet from my TransparentClock program:

Code: Select all

  ; Load Clock Base Image 
  Length = ?baseend-?base 
  g1 = GlobalAlloc_(#GHND, Length) 
  *buf1 = GlobalLock_(g1) 
  CopyMemory(?base, *buf1, Length) 
  If CreateStreamOnHGlobal_(*buf1, 1, @stream1.ISTREAM) = #S_OK 
    CallFunction(0, "GdipCreateBitmapFromStream", stream1 , @*base) 
  Else 
    MessageRequester("Error","Problem creating images", #MB_ICONERROR) 
    End 
  EndIf
Last edited by netmaestro on Sun Nov 19, 2006 8:25 am, edited 2 times in total.
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 »

Got around to doing a speed test on CreateFileThumbnail. Time to load, resize and save 100 JPEG's from 1280*1024 to 100*75: 4.5 seconds. 100 PNG's took 7.9 seconds. Fwiw, I think these times for this quality of resampling are pretty decent.

(AMD 3800 X2, 2g Ram, NVidia 7300GT, Holley 4bbl carbs)
dige
Addict
Addict
Posts: 1405
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Post by dige »

@netmaestro:
I guess its better to init gdi plus only once, is'nt it?

Code: Select all

    input.GdiplusStartupInput 
    input\GdiPlusVersion = 1 
    Startup( @*token, @input, #Null) 
did you have some gdi information about loseless jpeg rotation?
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3942
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Post by wilbert »

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 »

@dige, I don't know what "lossless jpeg rotation" means. Could you clarify?
mskuma
Enthusiast
Enthusiast
Posts: 573
Joined: Sat Dec 03, 2005 1:31 am
Location: Australia

Post by mskuma »

It means opening a JPEG, rotating it (e.g.) 90 degrees and saving it out without recompressing it, i.e. rearrange the data internally.
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 »

Ok, thanks. Those "standard" flips and rotations are highly optimized in gdiplus for speed and quality. They are all accomplished with one command:

Code: Select all

CallFunction(0, "GdipImageRotateFlip", *image, RotationConstant)
where 'RotationConstant' is one of the following values:

Code: Select all

#RotateNoneFlipNone = 0
#Rotate90FlipNone   = 1
#Rotate180FlipNone  = 2
#Rotate270FlipNone  = 3
#RotateNoneFlipX    = 4
#Rotate90FlipX      = 5
#Rotate180FlipX     = 6
#Rotate270FlipX     = 7
#RotateNoneFlipY    = #Rotate180FlipX
#Rotate90FlipY      = #Rotate270FlipX
#Rotate180FlipY     = #RotateNoneFlipX
#Rotate270FlipY     = #Rotate90FlipX
#RotateNoneFlipXY   = #Rotate180FlipNone
#Rotate90FlipXY     = #Rotate270FlipNone
#Rotate180FlipXY    = #RotateNoneFlipNone
#Rotate270FlipXY    = #Rotate90FlipNone
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 »

For more interesting rotations, here's a demo program to show the method. You can use the techniques shown here to build a really high-quality needle guage, clock, dial, etc. The "wow, cool!" possibilities are unlimited and PureBasic makes it all so easy.

You need this image to run the test: http://www.lloydsplace.com/wheeldisc.png

** Right-click the link and select "Save Target As" **

Code: Select all

;========================================================
;  Program:     GDIPlus Rotation Demo
;  Author:      netmaestro
;  Date:        November 12, 2006
;  Update:      February 8, 2020
;               Updated to use prototypes as
;               CallFunction no longer supports floats
;========================================================

#background = 0 ; image# for background

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

Prototype GdiplusStartup( *token, *input, mode )
Prototype GdipCreateBitmapFromFile(filename.p-bstr, *image)
Prototype GdipCreateFromHDC( hdc, *gfx)
Prototype GdipRotateWorldTransform( *gfx, angle.f, mode)
Prototype GdipResetWorldTransform( *gfx)
Prototype GdipTranslateWorldTransform( *gfx, wmidf.f, hmidf.f, mode)
Prototype GdipDrawImageRectI( *gfx, *image, x, y, Width, Height )
Prototype GdipDeleteGraphics( *gfx )
Prototype GdipDisposeImage( *image )
Prototype GdiplusShutdown( *token )

OpenLibrary(0, "gdiplus.dll")
 
Global GdiplusStartup.GdiplusStartup                           = GetFunction( 0, "GdiplusStartup" )         
Global GdipCreateBitmapFromFile.GdipCreateBitmapFromFile       = GetFunction( 0, "GdipCreateBitmapFromFile")
Global GdipCreateFromHDC.GdipCreateFromHDC                     = GetFunction( 0, "GdipCreateFromHDC" )     
Global GdipDrawImageRectI.GdipDrawImageRectI                   = GetFunction( 0, "GdipDrawImageRectI" )     
Global GdipRotateWorldTransform.GdipRotateWorldTransform       = GetFunction( 0, "GdipRotateWorldTransform" )     
Global GdipTranslateWorldTransform.GdipTranslateWorldTransform = GetFunction( 0, "GdipTranslateWorldTransform" )     
Global GdipResetWorldTransform.GdipResetWorldTransform         = GetFunction( 0, "GdipResetWorldTransform" )
Global GdipDeleteGraphics.GdipDeleteGraphics                   = GetFunction( 0, "GdipDeleteGraphics" )     
Global GdipDisposeImage.GdipDisposeImage                       = GetFunction( 0, "GdipDisposeImage" )       
Global GdiplusShutdown.GdiplusShutdown                         = GetFunction( 0, "GdiplusShutdown" ) 

Procedure InitGDIPlus()
  input.GdiplusStartupInput
  input\GdiPlusVersion = 1
  GdiplusStartup( @*token, @input, #Null)
  ProcedureReturn *token
EndProcedure

Procedure ShutDownGDIPlus(*token)
  GdiplusShutdown(*token)
  CloseLibrary(0)
EndProcedure

Procedure RotateImage(*image, bkgimage, angle.f)
  width=ImageWidth(bkgimage)
  height=ImageHeight(bkgimage)
  wmidf.f = width/2
  hmidf.f = height/2
  wmid.l = height/2
  hmid.l = height/2 
  hdc=StartDrawing(ImageOutput(bkgimage))
    Box(0,0,width,height,GetSysColor_(#COLOR_BTNFACE))
    GdipCreateFromHDC( hdc, @*surface)
    GdipRotateWorldTransform( *surface, angle, 1)
    GdipTranslateWorldTransform( *surface, wmidf, hmidf, 1)
    GdipDrawImageRectI( *surface, *image, wmid, hmid, -width, -height)
    GdipResetWorldTransform( *surface)                                     
  StopDrawing()
  GdipDeleteGraphics( *surface) 
EndProcedure

*token = InitGDIPlus()

GdipCreateBitmapFromFile("wheeldisc.png", @*wheeldisc)
CreateImage(0,256,256,24)

OpenWindow(0,0,0,300,320,"Rotation Demo",#PB_Window_ScreenCentered|#PB_Window_SystemMenu)

ImageGadget(0,15,15,0,0,0)
TrackBarGadget(1,20,285,260,20,0,300)
SetGadgetState(1,150)

Repeat
  EventID = WaitWindowEvent(1)
  angle.f+inc.f:If angle >=360:angle=0:EndIf
  RotateImage(*wheeldisc, #background, angle)

  SetGadgetState(0,ImageID(#background))
  inc.f = (GetGadgetState(1)-150)/100
  If inc>=-0.02 And inc<=0.02:inc=0:EndIf
Until EventID = #PB_Event_CloseWindow

ShutDownGDIPlus(*token) 
Last edited by netmaestro on Sat Feb 08, 2020 8:18 am, edited 2 times in total.
dige
Addict
Addict
Posts: 1405
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Post by dige »

Very very cooool! thx netmeastro!!!

"GdipImageRotateFlip" seems not the right one for loseless
Jpeg - Rotation, because you have to load, rotate and save
the Image again...

I'll check the API Link from wilbert ... the command should
sound like "TransformJpeg90" or so ...
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 »

You're most welcome. For the lossless jpeg rotation, I'm sorry it's just not a concept I'm familiar with. A person could load, flip and save images using code I've posted in this thread, but for anything more than what's here I'm afraid I'm in the dark. If you find what you're looking for, I'd appreciate your posting it. I'm always up for learning something new. It's fun, isn't it?
Hurga
Enthusiast
Enthusiast
Posts: 148
Joined: Thu Jul 17, 2003 2:53 pm
Contact:

Post by Hurga »

Nice code, but i thought about how to do it nativly in PB. (Just my tick)

(Have no time to test the idea, so no code, only the idea)

Could that work?

load the big image into mem (with #PB_IMAGE_MEMORY)
create the thumbnail into mem

calc the x and y ratio between both images (if big = 1000x1000 and the thumbnail ist 100 x 100, the ration is 10
step through the big image (in this case take every 10th pixel)
write it to the thumbnail image.

If I find time, maybe i´ll give it a try...
dige
Addict
Addict
Posts: 1405
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Post by dige »

jpeg, png etc. are compressed images ...
Baldrick
Addict
Addict
Posts: 860
Joined: Fri Jul 02, 2004 6:49 pm
Location: Australia

Post by Baldrick »

Last week while working on a card access control system I discovered the man in charge of the system was having to manually resize all card images from his digital camera to allow the small size needed for the photo software package of this system. With a bit of sniffing around this forum I came up with this thread & have now made a little utility to quickly resize images for this man ( which he does 30 - 40 images at a time ). Some of you might find it handy so here is a download link for the source.

Click here to download source

It pretty much uses Netmaestro's 1st snippet almost unaltered which served my purposes very nicely :)
Pantcho!!
Enthusiast
Enthusiast
Posts: 538
Joined: Tue Feb 24, 2004 3:43 am
Location: Israel
Contact:

Post by Pantcho!! »

Yeah it's nice to help people :)

I did a resize program because one of my fellow students were taking pictures of the lectures in class and has to manual resize them so i create a little code that will resize it as he wish and also the file name match to the class name.

The coolest thing is that you can code this full program in little less then 2 hours or so unlike other programming languages :)
User avatar
Joakim Christiansen
Addict
Addict
Posts: 2452
Joined: Wed Dec 22, 2004 4:12 pm
Location: Norway
Contact:

Post by Joakim Christiansen »

Hmm, can't get netmaestro's function to work here on PB 4.30.
Invalid memory access on line 111 :S

EDIT:
This helps fixing the memory bug, but the code still doesn't work.

Code: Select all

  Protected *info.ImageCodecInfo
 
  GetImageEncodersSize( @num, @Size )
  *info = AllocateMemory(Size)
  GetImageEncoders( num, Size, *info )
 
  For i = 0 To num - 1
    If *info\MimeType = encoder2use
      clsid = *info\clsid
      Break
    EndIf
    *info + SizeOf(ImageCodecInfo)
  Next

Code: Select all

;; netmaestro's gdiplus Thumbnail File creator
;
;----------------------------------------------------------------------------
;                     GDIPlus Initialization Section
;----------------------------------------------------------------------------
;
CompilerIf Defined(GdiplusStartupInput, #PB_Structure) = 0
  Structure GdiplusStartupInput
    GdiPlusVersion.l
    *DebugCallback
    SuppressBackgroundThread.l
    SuppressExternalCodecs.l
  EndStructure
CompilerEndIf 

CompilerIf Defined(EncoderParameter, #PB_Structure) = 0
  Structure EncoderParameter
    count.l
    guid.GUID
    numberOfValues.l
    type.l
    *value
  EndStructure
CompilerEndIf

CompilerIf  Defined(ImageCodecInfo, #PB_Structure) = 0
  Structure ImageCodecInfo
    clsid.CLSID
    formatID.GUID
    codecName.s
    dllName.s
    formatDescription.s
    filenameExtension.s
    mimeType.s
    flags.l
    version.l
    sigCount.l
    sigSize.l
    *sigPattern
    *sigMask
  EndStructure
CompilerEndIf

Prototype GdiplusStartup( *token, *input, mode )
Prototype GdipCreateBitmapFromFile( Filename, *image )
Prototype GdipSaveImageToFile ( *image, Filename, encoder, params)
Prototype GdipGetImageEncodersSize( *num, *Size )
Prototype GdipGetImageEncoders( *num, *Size, *info.ImageCodecInfo)
Prototype GdipGetImageThumbnail(*image, tn_xsize, tn_ysize, *tn_image, *callbackproc, *callbackdata)
Prototype GdipDisposeImage( *image )
Prototype GdiplusShutdown( *token )

OpenLibrary(0, "gdiplus.dll")

Global Startup.GdiplusStartup                        = GetFunction( 0, "GdiplusStartup" )         
Global CreateBitmapFromFile.GdipCreateBitmapFromFile = GetFunction( 0, "GdipCreateBitmapFromFile" )
Global SaveImageToFile.GdipSaveImageToFile           = GetFunction( 0, "GdipSaveImageToFile" )
Global GetImageEncodersSize.GdipGetImageEncodersSize = GetFunction( 0, "GdipGetImageEncodersSize" )
Global GetImageEncoders.GdipGetImageEncoders         = GetFunction( 0, "GdipGetImageEncoders" )
Global GetImageThumbnail.GdipGetImageThumbnail       = GetFunction( 0, "GdipGetImageThumbnail" )
Global DisposeImage.GdipDisposeImage                 = GetFunction( 0, "GdipDisposeImage" )       
Global Shutdown.GdiplusShutdown                      = GetFunction( 0, "GdiplusShutdown" ) 
;     
;-----------------------------------------------------------------------------
;                      End GDIPlus Initialization Section
;-----------------------------------------------------------------------------

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 SaveThumbnail(*image, decoder, filename.s)
 
  Protected encoder2use.s
  Select decoder
    Case 0
      encoder2use = "image/bmp"
    Case 1
      encoder2use = "image/jpeg"
    Case 2
      encoder2use = "image/gif"
    Case 3
      encoder2use = "image/tiff"
    Case 4
      encoder2use = "image/png"
    Default
      ProcedureReturn 0
  EndSelect
 
  Dim info.ImageCodecInfo(0)
 
  GetImageEncodersSize( @num, @Size )
  ReDim info.ImageCodecInfo(Size/SizeOf(ImageCodecInfo))
  GetImageEncoders( num, Size, @info(0) )
 
  For i = 0 To num - 1
    If PeekS(@info(i)\MimeType, -1, #PB_Unicode) = encoder2use
      clsid.l = info(i)\clsid
      Break
    EndIf
  Next
 
  If clsid
    If SaveImageToFile( *image, StringToBStr(filename), clsid, 0) = 0
      ProcedureReturn 1
    Else
      ProcedureReturn 0
    EndIf
  Else
    ProcedureReturn 0
  EndIf
 
EndProcedure

ProcedureDLL CreateFileThumbnail( xsize, ysize, Filename.s )
  Select LCase(GetExtensionPart(filename))
    Case "bmp" : encoder.l = 0
    Case "jpg" : encoder.l = 1
    Case "gif" : encoder.l = 2
    Case "tif" : encoder.l = 3
    Case "png" : encoder.l = 4
    Default : encoder.l = 0
  EndSelect
  input.GdiplusStartupInput
  input\GdiPlusVersion = 1
  Startup( @*token, @input, #Null)
  CreateBitmapFromFile( StringToBStr(Filename), @*image)
  GetImageThumbnail(*image, 382,202, @*tn_image, #Null, #Null)
  SaveThumbnail( *tn_image, encoder, "tn_"+filename )   
  DisposeImage( *image )
  DisposeImage( *tn_image )
  Shutdown( *token )
EndProcedure

CreateFileThumbnail( 100, 75, OpenFileRequester("","","",0)) ; input image can be any size; parameters are for thumbnail-out size

CloseLibrary(0) 
I like logic, hence I dislike humans but love computers.
Post Reply