Faster resize image
-
- Enthusiast
- Posts: 362
- Joined: Wed Aug 06, 2003 2:49 pm
- Location: Venice - Italy, Japan when possible.
- Contact:
Faster resize image
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...
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
> 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?
Here's what my thumbnails look like without and with smoothing, and why
I don't want to use #PB_Image_Raw:


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))
I don't want to use #PB_Image_Raw:


I compile using 5.31 (x86) on Win 7 Ultimate (64-bit).
"PureBasic won't be object oriented, period" - Fred.
"PureBasic won't be object oriented, period" - Fred.
Re: Faster resize image
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.
(But a faster ResizeImage would still be very nice).
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.

(But a faster ResizeImage would still be very nice).
I compile using 5.31 (x86) on Win 7 Ultimate (64-bit).
"PureBasic won't be object oriented, period" - Fred.
"PureBasic won't be object oriented, period" - Fred.
- netmaestro
- PureBasic Bullfrog
- Posts: 8451
- Joined: Wed Jul 06, 2005 5:42 am
- Location: Fort Nelson, BC, Canada
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:
I just tested against this code:
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.
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
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
JPEG results: ResizeImage() took 5 seconds, gdiplus took 1 second.
It's also beating ResizeImage with #PB_Image_Raw by a significant margin.
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
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
- netmaestro
- PureBasic Bullfrog
- Posts: 8451
- Joined: Wed Jul 06, 2005 5:42 am
- Location: Fort Nelson, BC, Canada
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.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)

Guess I better have a look at Flype's lib:)
Thanks again
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
Look->http://www.purebasic.fr/english/viewtop ... size+image
Thanks a ot Netmaestro!
Timo
-
- Enthusiast
- Posts: 372
- Joined: Sun Apr 03, 2005 2:14 am
- Location: England
- netmaestro
- PureBasic Bullfrog
- Posts: 8451
- Joined: Wed Jul 06, 2005 5:42 am
- Location: Fort Nelson, BC, Canada
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]
[useless code removed]
Last edited by netmaestro on Sat Nov 11, 2006 7:49 pm, edited 1 time in total.
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
- netmaestro
- PureBasic Bullfrog
- Posts: 8451
- Joined: Wed Jul 06, 2005 5:42 am
- Location: Fort Nelson, BC, Canada
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)
Last edited by netmaestro on Wed May 13, 2009 11:08 pm, edited 3 times in total.