Page 1 of 3

Faster resize image

Posted: Sat Dec 06, 2003 12:43 pm
by Blade
Is there a faster way to resize an image than ResizeImage() ?

I wish to generate thumbnails of all images inside a directory, by it take ages to compute even in my 2400+ Athlon!

The ResizeImage quality is photoshop-like (too good) perhaps a simple stretch could be enough...

p.s.
using "StretchBlt_" seems to badly reduce the images to 256 colors...

Re: Faster resize image

Posted: Sat Nov 11, 2006 4:45 am
by PB
> Is there a faster way to resize an image than ResizeImage() ?

I need this, too. My images are all 1280x1024 and I have to load them, then
resize them to a new image thumbnail, which is slow because I have to copy
them first and then resize the copy (I need to retain the original image).

Using #PB_Image_Raw is no good because the resized images are 386x202
and having a raw resize makes the quality basically useless. Does anyone
have any API examples that could do what I want, faster? Here's the code
I'm currently using. Surely it can be done faster than that?

Code: Select all

LoadImage(#Img_ScreenShot,shotname$)
CopyImage(#Img_ScreenShot,#Img_ScreenShotThumb)
SetGadgetState(#shotview,ResizeImage(#Img_ScreenShotThumb,386,202))
Here's what my thumbnails look like without and with smoothing, and why
I don't want to use #PB_Image_Raw:

Image

Image

Re: Faster resize image

Posted: Sat Nov 11, 2006 5:17 am
by PB
Okay, I re-coded the routine so that it loads the original, resizes that raw, and
shows that as the thumbnail. Ugly, but I guess I can live with it. (Maybe I'll
put an option in my app where the user can select if they want smoothing or
not, and leave it up to them to live with the slower speed?).

I will then only show the fullsize image (by reloading it) if the user selects the
thumbnail, rather than my first approach of loading it, copying it, and resizing
the copy. Just goes to show that a bit of re-coding can speed things up too. :oops:

(But a faster ResizeImage would still be very nice).

Posted: Sat Nov 11, 2006 6:00 am
by netmaestro
Blade and PB, I may have something for you. I thought your challenge looked interesting so I put something together using gdiplus. Feed it a 1280*1024 image and run the test. Here it's creating 50 thumbnails 2.5 to 5 times faster than ResizeImage (depending on format) and the quality is photoshop-level imho:

Code: Select all

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

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  

Prototype GdiplusStartup( *token, *input, mode )
Prototype GdipCreateBitmapFromFile( Filename, *image )
Prototype GdipGetImageWidth( *image, *Width )
Prototype GdipGetImageHeight( *image, *Height )
Prototype GdipGetImagePixelFormat( *image, *Format )
Prototype GdipCreateFromHDC( hdc, *gfx)
Prototype GdipDrawImageRectI( *gfx, *image, x, y, Width, Height ) 
Prototype GdipDeleteGraphics( *gfx ) 
Prototype GdipDisposeImage( *image )
Prototype GdiplusShutdown( *token ) 

OpenLibrary(0, "gdiplus.dll")

Global Startup.GdiplusStartup                        = GetFunction( 0, "GdiplusStartup" )         
Global CreateBitmapFromFile.GdipCreateBitmapFromFile = GetFunction( 0, "GdipCreateBitmapFromFile" )
Global GetImageWidth.GdipGetImageWidth               = GetFunction( 0, "GdipGetImageWidth" )               
Global GetImageHeight.GdipGetImageHeight             = GetFunction( 0, "GdipGetImageHeight" )     
Global GetImagePixelFormat.GdipGetImagePixelFormat   = GetFunction( 0, "GdipGetImagePixelFormat" )
Global CreateFromHDC.GdipCreateFromHDC               = GetFunction( 0, "GdipCreateFromHDC" )      
Global DrawImageRectI.GdipDrawImageRectI             = GetFunction( 0, "GdipDrawImageRectI" )     
Global DeleteGraphics.GdipDeleteGraphics             = GetFunction( 0, "GdipDeleteGraphics" )     
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 LoadThumbnail(ImageNumber, xsize, ysize, Filename.s)
  input.GdiplusStartupInput
  input\GdiPlusVersion = 1
  Startup( @*token, @input, #Null)

  CreateBitmapFromFile( StringToBStr(Filename), @*image)
  
  GetImageWidth( *image, @Width.l )
  GetImageHeight( *image, @Height.l )
  GetImagePixelFormat( *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

  Retval = CreateImage(ImageNumber, xsize, ysize, bits_per_pixel)
  hDC = StartDrawing(ImageOutput(ImageNumber))
    CreateFromHDC( hdc, @*gfx )
    DrawImageRectI( *gfx, *image, 0, 0, xsize, ysize )
  StopDrawing()  
  DeleteGraphics( *gfx )  
  
  DisposeImage( *image )
  Shutdown( *token )
  
  ProcedureReturn Retval
EndProcedure

tt = ElapsedMilliseconds()
For i = 1 To 50
  LoadThumbnail(0, 382, 202, "desktop.png") ; input image can be any size; parameters are for thumbnail-out size
Next
MessageRequester("Time to create 50 Thumbnails:",Str((ElapsedMilliseconds()-tt)/1000)+" seconds")

CloseLibrary(0)

OpenWindow(0,0,0,382,202,"",$CF0001)
CreateGadgetList(WindowID(0))
ImageGadget(0,0,0,0,0,ImageID(0))
Repeat:Until WaitWindowEvent() = #WM_CLOSE
I just tested against this code:

Code: Select all

UsePNGImageDecoder()
tt = ElapsedMilliseconds()
For i = 1 To 50
  LoadImage(0, "desktop.png")
  ResizeImage(0, 382,202,#PB_Image_Smooth)
Next
MessageRequester("Time to create 50 Thumbnails:",Str((ElapsedMilliseconds()-tt)/1000)+" seconds")

CloseLibrary(0)

OpenWindow(0,0,0,382,202,"",$CF0001)
CreateGadgetList(WindowID(0))
ImageGadget(0,0,0,0,0,ImageID(0))
Repeat:Until WaitWindowEvent() = #WM_CLOSE
PNG results: Using ResizeImage() the 50 images took 12 seconds vs. 5 for the gdiplus version.

JPEG results: ResizeImage() took 5 seconds, gdiplus took 1 second.

It's also beating ResizeImage with #PB_Image_Raw by a significant margin.

Posted: Sat Nov 11, 2006 8:04 am
by rsts
This is one mighty nice routine and extremely timely. I'm resizing images of undetermined size down to thumbs and this speeds it up tremendously.

No timing tests yet, but using resize in my program, it can take several seconds and is noticeably slow. This external routine rendered the same image in much less time.

I'll get a timing test after I insert this routine into my program.

Pure genius.

cheers

Posted: Sat Nov 11, 2006 8:10 am
by netmaestro
Pure genius.
Oh, the wanton abuse I have to put up with... :D

I hope it pans out for you. Just remember that WinXP is the only OS that natively includes gdiplus.dll. For other target OS's you must distribute the dll with your app. (MS says it's redistributable)

Posted: Sat Nov 11, 2006 8:41 am
by rsts
netmaestro wrote:
I hope it pans out for you. Just remember that WinXP is the only OS that natively includes gdiplus.dll. For other target OS's you must distribute the dll with your app. (MS says it's redistributable)
And thanks for pointing that out. I always tend to add these things without thinking out the platform considerations. Got into trouble on some transparent windows code too. :oops: Wouldn't want to mess up all them other Windows users.

Guess I better have a look at Flype's lib:)

Thanks again

Posted: Sat Nov 11, 2006 9:19 am
by omit59
This is going to be a great help for me, I tried to find this kind of solution earlier this year!
Look->http://www.purebasic.fr/english/viewtop ... size+image

Thanks a ot Netmaestro!

Timo

Posted: Sat Nov 11, 2006 12:07 pm
by SoulReaper
Thanks netmaestro your a shining star and pure genius :lol: :wink:

Posted: Sat Nov 11, 2006 2:43 pm
by Sparkie
Nice work Netmaestro :)

Try using GdipGetImageThumbnail and see if the results are satisfactory. I use it in one of my apps and a folder with 89 jpgs (mix of 640 x 480 and 1600 x 1200) displays 100 x 75 sized thumbs in 441 ms.

Posted: Sat Nov 11, 2006 3:03 pm
by netmaestro
Thanks Sparkie, I modified the procedure to do the resizing with GdipGetImageThumbnail, but it didn't change the speed any. It's going to be slower than the task you describe as it has to make a PB image out of the thumbnail, if it could just do a GdipSaveImageToFile without creating an HBITMAP it would go much faster. I can write a version that does that if that's generally desired. Or you can if you have time. Your coding is better than mine.

[useless code removed]

Posted: Sat Nov 11, 2006 3:15 pm
by Sparkie
I'll see if I can cleanly rip out the GDI+ code from my app and post it here. :)

Posted: Sat Nov 11, 2006 4:18 pm
by Trond
I know you didn't want to use the raw resize mode of PB, but in case someone else does, check the speed differences in this code:

Code: Select all

OpenWindow(0, 0, 0, 258, 72, "", #PB_Window_ScreenCentered | #PB_Window_SystemMenu) 
CreateGadgetList(WindowID(0)) 
  ResX = 1024 
  ResY = 768 
  Factor = 32 
  ScaledX = ResX/Factor 
  ScaledY = ResY/Factor 
  
  CreateImage(0, ScaledX, ScaledY, #PB_Image_DisplayFormat) 
  CreateImage(1, ScaledX, ScaledY, #PB_Image_DisplayFormat) 
  CreateImage(2, ResX, ResY, #PB_Image_DisplayFormat) 
  CreateImage(3, ResX, ResY, #PB_Image_DisplayFormat) 
  
  ; Custom 
  timer = GetTickCount_() 
  StartDrawing(ImageOutput(0)) 
    hDC = CreateDC_("DISPLAY", 0, 0, 0) 
    HalfFactor = Factor/2 
    DrawX = HalfFactor 
    DrawY = DrawX 
    For I = 0 To ScaledX-1 
      For J = 0 To ScaledY-1 
        Plot(I, J, GetPixel_(hDC, DrawX, DrawY)) 
        DrawY + Factor 
      Next 
      DrawY = HalfFactor 
      DrawX + Factor 
    Next 
    DeleteDC_(hDC) 
  StopDrawing() 
  CustomTime = GetTickCount_()-timer 
  
  ; API 
  timer = GetTickCount_() 
  ImghDC = StartDrawing(ImageOutput(1)) 
    hDC = CreateDC_("DISPLAY", 0, 0, 0) 
    ;SetStretchBltMode_(hDC, #HALFTONE) 
    ;SetBrushOrgEx_(hDC, 0, 0, 0) 
    StretchBlt_(ImghDC, 0, 0, ScaledX, ScaledY, hDC, 0, 0, ResX, ResY, #SRCCOPY) 
    DeleteDC_(hDC) 
  StopDrawing() 
  ApiTime = GetTickCount_()-timer 
  
  ; PB raw 
  timer = GetTickCount_() 
  ImghDC = StartDrawing(ImageOutput(2)) 
    hDC = CreateDC_("DISPLAY", 0, 0, 0) 
    BitBlt_(ImghDC, 0, 0, ResX, ResY, hDC, 0, 0, #SRCCOPY) 
    DeleteDC_(hDC) 
  StopDrawing() 
  ResizeImage(2, ScaledX, ScaledY, #PB_Image_Raw) 
  RawTime = GetTickCount_()-timer 
  
  ; PB smooth 
  timer = GetTickCount_() 
  ImghDC = StartDrawing(ImageOutput(3)) 
    hDC = CreateDC_("DISPLAY", 0, 0, 0) 
    BitBlt_(ImghDC, 0, 0, ResX, ResY, hDC, 0, 0, #SRCCOPY) 
    DeleteDC_(hDC) 
  StopDrawing() 
  ResizeImage(3, ScaledX, ScaledY, #PB_Image_Smooth) 
  SmoothTime = GetTickCount_()-timer 
  
  Frame3DGadget(100, 10, 10, 10+32+10, 10+32+10, Str(CustomTime)) 
  ImageGadget(0, 20, 20+(32-24), 32, 24, ImageID(0)) 
  Frame3DGadget(101, GadgetX(100)+GadgetWidth(100)+10, 10, 10+32+10, 10+32+10, Str(ApiTime)) 
  ImageGadget(1, GadgetX(100)+GadgetWidth(100)+20, 20+(32-24), 32, 24, ImageID(1)) 
  Frame3DGadget(102, GadgetX(101)+GadgetWidth(101)+10, 10, 10+32+10, 10+32+10, Str(RawTime)) 
  ImageGadget(2, GadgetX(101)+GadgetWidth(101)+20, 20+(32-24), 32, 24, ImageID(2)) 
  Frame3DGadget(103, GadgetX(102)+GadgetWidth(102)+10, 10, 10+32+10, 10+32+10, Str(SmoothTime)) 
  ImageGadget(3, GadgetX(102)+GadgetWidth(102)+20, 20+(32-24), 32, 24, ImageID(3)) 
  
Repeat 
  Select WaitWindowEvent() 
    Case #PB_Event_CloseWindow 
      Break 
  EndSelect 
ForEver

Posted: Sat Nov 11, 2006 6:25 pm
by netmaestro
Here's a version that creates a file thumbnail based on a filename. You can easily write a program to examine a dir for images and send this procedure each image to have a thumbnail created in the same folder. As it isn't creating a HBITMAP and drawing on it, it's even faster:

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.i 
    dllName.i 
    formatDescription.i 
    filenameExtension.i 
    mimeType.i 
    flags.l 
    version.l 
    sigCount.l 
    sigSize.l 
    *sigPattern.byte 
    *sigMask.byte
  EndStructure 
CompilerEndIf 

Prototype GdiplusStartup( *token, *input, mode ) 
Prototype GdipCreateBitmapFromFile( Filename.p-unicode, *image ) 
Prototype GdipSaveImageToFile ( *image, Filename.p-unicode, *encoder.CLSID, 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 
;----------------------------------------------------------------------------- 

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 
  
  GetImageEncodersSize( @num, @Size ) 
  *mem = AllocateMemory(size)
  *ptr.ImageCodecInfo = *mem
  
  GetImageEncoders( num, Size, *ptr ) 
  For i = 0 To num - 1 
    If PeekS(*ptr\MimeType, -1, #PB_Unicode) = encoder2use 
      *clsid.CLSID = *ptr\clsid 
      Break 
    Else
      *ptr + SizeOf(ImageCodecInfo)
    EndIf 
  Next 

  If *clsid 
    result = SaveImageToFile( *image, filename, *clsid, 0) 
    FreeMemory(*mem)
    If result = #S_OK 
      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( Filename, @*image)
  GetImageThumbnail(*image, xsize,ysize, @*tn_image, #Null, #Null) 
  file2save$ = GetPathPart(filename)+"tn_"+GetFilePart(filename)
  SaveThumbnail( *tn_image, encoder, file2save$)    
  DisposeImage( *image ) 
  DisposeImage( *tn_image ) 
  Shutdown( *token ) 
EndProcedure 

filename$ =  OpenFileRequester("","","",0)

CreateFileThumbnail( 100, 75, filename$) ; input image can be any size; parameters are for thumbnail-out size 

CloseLibrary(0) 

Posted: Sat Nov 11, 2006 8:59 pm
by dige
@netmaestro: thx that very nice gdiplus code!
Where can I find information about gdiplus? I know that
it should be possible to do a lossless jpeg rotation...

And how to change the gdi resize code to use it without
file loading. ( create a gdi image from a pb image )
could you help?