Faster Drawing of scaled Images

Share your advanced PureBasic knowledge/code with the community.
Mr.L
Enthusiast
Enthusiast
Posts: 146
Joined: Sun Oct 09, 2011 7:39 am

Faster Drawing of scaled Images

Post by Mr.L »

Drawing a large image using the DrawImage command and the optional parameters "Width" and "Height" can be quite slow.
Here is a small Module to draw a scaled image much faster.

Code: Select all

DeclareModule ScaledImage
	; ImageID          -  The ID of the image to draw
	; X, Y             -  The position of the top/left corner of the image in the drawing output  
	; ScaleX, ScaleY   -  scaling factors by wich the image will be resized in x- and y-direction
	; Mode (optional)  -  The resize method. It can be one of the following values: 
	;                     #PB_Image_Raw    -  Resize the image without any interpolation (default)
	;                     #PB_Image_Smooth -  Resize the image with smoothing
	Declare Draw(ImageID, X.d, Y.d, ScaleX.d, ScaleY.d, Flags = #PB_Image_Raw)
EndDeclareModule

Module ScaledImage
	EnableExplicit
	
	Procedure.d Min(a.d, b.d)
		If a < b
			ProcedureReturn a
		EndIf
		ProcedureReturn b
	EndProcedure
	
	Procedure.d Max(a.d, b.d)
		If a > b
			ProcedureReturn a
		EndIf
		ProcedureReturn b
	EndProcedure
	
	Procedure Draw(ImageID, X.d, Y.d, ScaleX.d, ScaleY.d, Mode = #PB_Image_Raw)
		If ScaleX And ScaleY And IsImage(ImageID)
			Protected cropX.d = Max(-Round(X, #PB_Round_Up), 0)
			Protected cropY.d = Max(-Round(Y, #PB_Round_Up), 0)
			Protected cropWidth.d  = Min(OutputWidth()  / ScaleX, ImageWidth(ImageID)  - Max((X + ImageWidth(ImageID))  - OutputWidth()  / ScaleX, 0) - cropX)
			Protected cropHeight.d = Min(OutputHeight() / ScaleY, ImageHeight(ImageID) - Max((Y + ImageHeight(ImageID)) - OutputHeight() / ScaleY, 0) - cropY)
			If cropWidth > 0 And cropHeight > 0
				Protected tmpImage = GrabImage(ImageID, #PB_Any, cropX, cropY, Round(cropWidth + 1, #PB_Round_Up), Round(cropHeight + 1, #PB_Round_Up))
				If IsImage(tmpImage)
					If Int(ImageWidth(tmpImage) * ScaleX) > 0 And Int(ImageHeight(tmpImage) * ScaleY) > 0
						ResizeImage(tmpImage, ImageWidth(tmpImage) * ScaleX, ImageHeight(tmpImage) * ScaleY, Mode)
						If cropX
							X = Max(X - cropX, 0) * ScaleX + Mod(X + cropX, 1)
						EndIf
						If cropY
							Y = Max(Y - cropY, 0) * ScaleY + Mod(Y + cropY, 1)
						EndIf
						DrawImage(ImageID(tmpImage), X * ScaleX, Y * ScaleY)
					EndIf
					FreeImage(tmpImage)
					
					ProcedureReturn #True
				EndIf
			EndIf
		EndIf
		
		ProcedureReturn #False
	EndProcedure
	
	DisableExplicit
EndModule

; ----------------------------------------- TEST -----------------------------------------
CompilerIf #PB_Compiler_IsMainFile
	LoadFont(0, "Consolas", 12)
	OpenWindow(0,0,0,800,600,"ScaledImage", #PB_Window_SystemMenu | #PB_Window_Maximize)
	CanvasGadget(0,0,35, WindowWidth(0), WindowHeight(0) - 35, #PB_Canvas_Keyboard)
	ButtonGadget(1,   0, 0, 200, 35, "DrawScaledImage: ON", #PB_Button_Toggle)
	TextGadget(2, 210, 5, 400, 30, "mouse + left Button = scroll   mouseWheel = zoom")	
	SetActiveGadget(0)
	
	CreateImage(0, 8192, 8192)
	StartDrawing(ImageOutput(0))
	For i = 0 To 2500
		Circle(Random(OutputWidth()), Random(OutputHeight()), Random(100,15), Random($FFFFFF, $101010))
	Next
	StopDrawing()
	
	Global ScrollX.d = -ImageWidth(0) / 2
	Global ScrollY.d = -ImageHeight(0) / 2
	Global Zoom.d = 1.0
	Global DrawMode = 1
	Global MouseX, MouseY, LastX, LastY, LButton = 0
	
	Procedure Redraw()
		If StartDrawing(CanvasOutput(0))
			DrawingFont(FontID(0))
			Protected time = ElapsedMilliseconds()
			Box(0,0,OutputWidth(), OutputHeight(), RGB(128,128,128))
			If DrawMode = 0
				DrawImage(ImageID(0), ScrollX * Zoom, ScrollY * Zoom, ImageWidth(0) * Zoom, ImageHeight(0) * Zoom)
			Else
				ScaledImage::Draw(0, ScrollX, ScrollY, Zoom, Zoom)
			EndIf
			DrawText(10,  10, "ImageSize: " + Str(ImageWidth(0)) + " x " + Str(ImageHeight(0)), #White, #Black)
			DrawText(10,  35, "ScrollX: " + StrD(ScrollX, 2), #White, #Black)
			DrawText(10,  60, "ScrollY: " + StrD(ScrollY, 2), #White, #Black)
			DrawText(10,  85, "Zoom: " + StrD(Zoom,5), #White, #Black)
			DrawText(10, 110, "Redraw time: " + Str(ElapsedMilliseconds() - time) + " ms", #Red, #Black)
			StopDrawing()
		EndIf
	EndProcedure
	
	Procedure Events()
		LastX = MouseX
		LastY = MouseY
		MouseX = GetGadgetAttribute(0, #PB_Canvas_MouseX)
		MouseY = GetGadgetAttribute(0, #PB_Canvas_MouseY)
		
		Select EventType()
			Case #PB_EventType_MouseMove
				If GetGadgetAttribute(0, #PB_Canvas_Buttons)
					ScrollX + (MouseX - LastX) / Zoom
					ScrollY + (MouseY - LastY) / Zoom
					Redraw()
				EndIf
			Case #PB_EventType_MouseWheel
				Protected oldZoom.d = Zoom
				If GetGadgetAttribute(0, #PB_Canvas_WheelDelta) > 0
					Zoom / 0.75
				Else
					Zoom * 0.75
				EndIf
				If oldZoom <> Zoom
					ScrollX + (MouseX / Zoom) * (1 - (Zoom / oldZoom))
					ScrollY + (MouseY / Zoom) * (1 - (Zoom / oldZoom))
					Redraw()
				EndIf
		EndSelect
	EndProcedure
	
	BindGadgetEvent(0, @Events())
	Redraw()
	
	Repeat
		Select WaitWindowEvent()
			Case #PB_Event_CloseWindow
				End
			Case #PB_Event_Gadget
				If EventGadget() = 1
					DrawMode = Bool(Not DrawMode)
					If DrawMode = 0
						SetGadgetText(1, "DrawScaledImage: OFF")
					Else
						SetGadgetText(1, "DrawScaledImage: ON")
					EndIf
					SetActiveGadget(0)
					Redraw()
				EndIf
		EndSelect
	ForEver
CompilerEndIf
Last edited by Mr.L on Mon Jun 10, 2024 9:21 pm, edited 5 times in total.
User avatar
moulder61
Enthusiast
Enthusiast
Posts: 188
Joined: Sun Sep 19, 2021 6:16 pm
Location: U.K.

Re: Faster Drawing of scaled Images

Post by moulder61 »

@Mr L

A small typo. I had to change line 80 to:

SetGadgetText(2, "")

for it to work.

Moulder.
"If it ain't broke, fix it until it is!

This message is brought to you thanks to SenselessComments.com

My PB stuff for Linux: "https://u.pcloud.link/publink/show?code ... z3MR0T3jyV
Mr.L
Enthusiast
Enthusiast
Posts: 146
Joined: Sun Oct 09, 2011 7:39 am

Re: Faster Drawing of scaled Images

Post by Mr.L »

moulder61 wrote: Sat Jun 08, 2024 1:47 pm A small typo. I had to change line 80 to:
SetGadgetText(2, "")
Thanks for pointing that out. This line was a left over from previous test and has to be be removed. I have updated the code in the first post.
User avatar
moulder61
Enthusiast
Enthusiast
Posts: 188
Joined: Sun Sep 19, 2021 6:16 pm
Location: U.K.

Re: Faster Drawing of scaled Images

Post by moulder61 »

@Mr.L

No problem. A lot of the code on the forums doesn't work for me on Linux so I'm used to poking about a bit to see if I can fix it. :)

I meant to mention, some time ago, that your SnakeZ games is a work of art. I can't even begin to understand how you create something like that? I tweaked it a bit to have a permanent high score table courtesy of Mindphazer's Puretris code. Thanks Mindphazer ;)

Respect.

Moulder.
"If it ain't broke, fix it until it is!

This message is brought to you thanks to SenselessComments.com

My PB stuff for Linux: "https://u.pcloud.link/publink/show?code ... z3MR0T3jyV
User avatar
jacdelad
Addict
Addict
Posts: 1993
Joined: Wed Feb 03, 2021 12:46 pm
Location: Riesa

Re: Faster Drawing of scaled Images

Post by jacdelad »

Wow thanks! This is exactly what I need today, I was just about to code this by myself. This works great!!!
Good morning, that's a nice tnetennba!

PureBasic 6.21/Windows 11 x64/Ryzen 7900X/32GB RAM/3TB SSD
Synology DS1821+/DX517, 130.9TB+50.8TB+2TB SSD
User avatar
jacdelad
Addict
Addict
Posts: 1993
Joined: Wed Feb 03, 2021 12:46 pm
Location: Riesa

Re: Faster Drawing of scaled Images

Post by jacdelad »

I took the freedom to adapt it a bit, for using it freely in any program:

Code: Select all

;Originally written by Mr.L: https://www.purebasic.fr/english/viewtopic.php?p=622394#p622394
;Enhanced by Jac de Lad:     https://www.purebasic.fr/english/viewtopic.php?p=622484#p622484
;The code was written and distributed by Mr.L, who also formed all the formulas. I just enhanced it a bit and made it fit to use it with any canvas!
DeclareModule ScaledImage
  Declare Enable(Gadget,Image,Enable.a=#True)
  Declare Redraw(Gadget)
  Declare SetAttribute(Gadget,Attribute,Value)
  Declare SetAttributeF(Gadget,Attribute,Value.f)
  
  Enumeration ScaledImageAttribute
    #SIA_Image
    #SIA_DrawingMode
    #SIA_BackColor
    #SIA_F_MaxZoom
    #SIA_F_MinZoom
    #SIA_UseRelativeZoom
    #SIA_SizeMode
  EndEnumeration
  
EndDeclareModule

Module ScaledImage
  EnableExplicit
  
  Structure ScaledImage
    Image.i
    LastX.l
    LastY.l
    MouseX.l
    MouseY.l
    ScrollX.f
    ScrollY.f
    Zoom.f
    MinZoom.f
    MaxZoom.f
    RelativeFactor.f
    DrawMode.a
    BackColor.l
    RelativeZoom.a
    SizeMode.l
  EndStructure
  
  Procedure Min(a, b)
    If a < b
      ProcedureReturn a
    EndIf
    ProcedureReturn b
  EndProcedure
  
  Procedure Max(a, b)
    If a > b
      ProcedureReturn a
    EndIf
    ProcedureReturn b
  EndProcedure
  
  ;Attributes:
  ;#SIA_Image           -> Image
  ;#SIA_DrawingMode     -> DrawingMode (#True=ScaledImageDrawing, #False=StandardDrawing)
  ;#SIA_BackColor       -> Background color
  ;#SIA_F_MinZoom       -> Minimal zoom factor, 0.0 to disable
  ;#SIA_F_MaxZoom       -> maximal zoom factor, 0.0 to disable
  ;#SIA_UseRelativeZoom -> Use relative zoom, calculated by gadget size
  ;#SIA_SizeMode        -> #PB_Image_RAW (default) or #PB_Image_Smooth
  Procedure SetAttributeF(Gadget,Attribute,Value.f)
    If IsGadget(Gadget) And GadgetType(Gadget)=#PB_GadgetType_Canvas
      Protected *Data.ScaledImage=GetGadgetData(Gadget)
      Select Attribute
        Case #SIA_F_MinZoom
          *Data\MinZoom=Value
        Case #SIA_F_MaxZoom
          *Data\MaxZoom=Value
      EndSelect
    EndIf
  EndProcedure
  
  Procedure SetAttribute(Gadget,Attribute,Value)
    If IsGadget(Gadget) And GadgetType(Gadget)=#PB_GadgetType_Canvas
      Protected *Data.ScaledImage=GetGadgetData(Gadget)
      Protected zX.f,zY.f
      Select Attribute
        Case #SIA_Image
          If IsImage(Value)
            *Data\Image=Value
            zX=GadgetWidth(Gadget)/ImageWidth(*Data\Image)
            zY=GadgetHeight(Gadget)/ImageHeight(*Data\Image)
            If zY<zX
              *Data\RelativeFactor=zY
            Else
              *Data\RelativeFactor=zX
            EndIf
          EndIf
        Case #SIA_DrawingMode
          *Data\DrawMode=Value
        Case #SIA_BackColor
          *Data\BackColor=Value
        Case #SIA_UseRelativeZoom
          *Data\RelativeZoom=Value
          If IsImage(*Data\Image)
            zX=GadgetWidth(Gadget)/ImageWidth(*Data\Image)
            zY=GadgetHeight(Gadget)/ImageHeight(*Data\Image)
            If zY<zX
              *Data\RelativeFactor=zY
            Else
              *Data\RelativeFactor=zX
            EndIf
          EndIf
        Case #SIA_SizeMode
          *Data\SizeMode=Value
      EndSelect
    EndIf
  EndProcedure
  
  Procedure Redraw(Gadget)
    If IsGadget(Gadget) And GadgetType(Gadget)=#PB_GadgetType_Canvas And StartDrawing(CanvasOutput(Gadget))
      Protected *Data.ScaledImage
      *Data=GetGadgetData(Gadget)
      Box(0,0,OutputWidth(), OutputHeight(), *Data\BackColor)
      If IsImage(*Data\Image)
        Protected Zoom.f=*Data\Zoom
        If *Data\RelativeZoom
          Zoom=Zoom**Data\RelativeFactor
        EndIf
        If *Data\DrawMode = 0
          DrawImage(ImageID(*Data\Image), *Data\ScrollX * Zoom, *Data\ScrollY * Zoom, ImageWidth(*Data\Image) * Zoom, ImageHeight(*Data\Image) * Zoom)
        Else
          Protected ImageID = ImageID(*Data\Image)
          Protected cropX = -Min(*Data\ScrollX, 0)
          Protected cropY = -Min(*Data\ScrollY, 0)
          Protected cropWidth  = Min(OutputWidth()  / Zoom, ImageWidth(*Data\Image)  - Max((*Data\ScrollX + ImageWidth(*Data\Image))  - OutputWidth()  / Zoom, 0) - cropX)
          Protected cropHeight = Min(OutputHeight() / Zoom, ImageHeight(*Data\Image) - Max((*Data\ScrollY + ImageHeight(*Data\Image)) - OutputHeight() / Zoom, 0) - cropY)
          If Int(cropWidth * Zoom) > 0 And Int(cropHeight * Zoom) > 0
            Protected tmpImage = GrabImage(*Data\Image, #PB_Any, cropX, cropY, cropWidth, cropHeight)
            If IsImage(tmpImage)
              If Zoom <> 1 Or Zoom <> 1
                ResizeImage(tmpImage, cropWidth * Zoom, cropHeight * Zoom, *Data\SizeMode)
              EndIf
              DrawImage(ImageID(tmpImage), Max(*Data\ScrollX - cropX, 0) * Zoom, Max(*Data\ScrollY - cropY, 0) * Zoom)
              FreeImage(tmpImage)
            EndIf
          EndIf
        EndIf
      EndIf
      StopDrawing()
    EndIf
  EndProcedure
  
  Procedure Events()
    Protected *Data.ScaledImage=GetGadgetData(EventGadget())
    *Data\LastX = *Data\MouseX
    *Data\LastY = *Data\MouseY
    *Data\MouseX = GetGadgetAttribute(EventGadget(), #PB_Canvas_MouseX)
    *Data\MouseY = GetGadgetAttribute(EventGadget(), #PB_Canvas_MouseY)
    Select EventType()
      Case #PB_EventType_Resize
        If *Data\RelativeZoom And IsImage(*Data\Image)
          Protected zX.f=GadgetWidth(EventGadget())/ImageWidth(*Data\Image)
          Protected zY.f=GadgetHeight(EventGadget())/ImageHeight(*Data\Image)
          If zY<zX
            *Data\RelativeFactor=zY
          Else
            *Data\RelativeFactor=zX
          EndIf
        EndIf
        Redraw(EventGadget())
      Case #PB_EventType_RightClick
        *Data\Zoom=1.0
        *Data\DrawMode=#True
        *Data\ScrollX=0
        *Data\ScrollY=0
        Redraw(EventGadget())
      Case #PB_EventType_MouseMove
        If GetGadgetAttribute(EventGadget(), #PB_Canvas_Buttons) = #PB_Canvas_LeftButton
          *Data\ScrollX + (*Data\MouseX - *Data\LastX) / *Data\Zoom
          *Data\ScrollY + (*Data\MouseY - *Data\LastY) / *Data\Zoom
          Redraw(EventGadget())
        EndIf
      Case #PB_EventType_MouseWheel
        Protected oldZoom.d = *Data\Zoom
        If GetGadgetAttribute(EventGadget(), #PB_Canvas_WheelDelta) > 0
          *Data\Zoom / 0.85
          If *Data\MaxZoom>0 And *Data\Zoom>*Data\MaxZoom
            *Data\Zoom=*Data\MaxZoom
          EndIf
        Else
          *Data\Zoom * 0.85
          If *Data\Zoom<*Data\MinZoom
            *Data\Zoom=*Data\MinZoom
          EndIf
        EndIf
        If oldZoom <> *Data\Zoom
          *Data\ScrollX + (*Data\MouseX / *Data\Zoom) * (1 - (*Data\Zoom / oldZoom))
          *Data\ScrollY + (*Data\MouseY / *Data\Zoom) * (1 - (*Data\Zoom / oldZoom))
          Redraw(EventGadget())
        EndIf
    EndSelect
  EndProcedure
  
  Procedure Enable(Gadget,Image,Enable.a=#True)
    If IsGadget(Gadget) And GadgetType(Gadget)=#PB_GadgetType_Canvas
      If Enable
        Protected *Data.ScaledImage
        *Data=AllocateStructure(ScaledImage)
        *Data\Zoom=1.0
        *Data\DrawMode=#True
        *Data\Image=Image
        *Data\BackColor=#White
        *Data\SizeMode=#PB_Image_Raw
        SetGadgetData(Gadget,*Data)
        BindGadgetEvent(Gadget,@Events())
      Else
        If GetGadgetData(Gadget)
          FreeMemory(GetGadgetData(Gadget))
          UnbindGadgetEvent(Gadget,@Events())
        EndIf
      EndIf
    EndIf
  EndProcedure
  
EndModule
Note: It uses the GadgetData to store some data. This is adaptable to using a map or list, but this way it's easier and the GadgetData usually isn't used.

To enable drawing, use ScaledImage::Enable() (this way drawing can also be disabled). Redrawing is done with ScaledImage::Redraw() and ScaledImage::SetAttribute can change the drawing mode or the image. The code can still be optimized (I'll maybe do) or enhanced. I also removed the demo code, this is just the module. All kudos go to Mr.L, who presented us this nice little gimmick!

Edit: I couldn't wait, so I added some more functions: Limiting min/max zoom, using zoom relative to gadget size, automatic redraw on gadget resize, background color...

@Mr.L: I hope this is ok, otherwise I'll delete this post!
Good morning, that's a nice tnetennba!

PureBasic 6.21/Windows 11 x64/Ryzen 7900X/32GB RAM/3TB SSD
Synology DS1821+/DX517, 130.9TB+50.8TB+2TB SSD
Mr.L
Enthusiast
Enthusiast
Posts: 146
Joined: Sun Oct 09, 2011 7:39 am

Re: Faster Drawing of scaled Images

Post by Mr.L »

jacdelad wrote: Mon Jun 10, 2024 10:45 am@Mr.L: I hope this is ok, otherwise I'll delete this post!
How dare you??? :evil:

...just kidding! :D
I like your enhancement and in my opinion this is exactly, what a programmers forum is for.

I have added smooth scrolling, even when zoomed in very much

Code: Select all

;Originally written by Mr.L: https://www.purebasic.fr/english/viewtopic.php?p=622394#p622394
;Enhanced by Jac de Lad:     https://www.purebasic.fr/english/viewtopic.php?p=622484#p622484
;The code was written and distributed by Mr.L, who also formed all the formulas. I just enhanced it a bit and made it fit to use it with any canvas!
DeclareModule ScaledImage
	Declare Enable(Gadget,Image,Enable.a=#True)
	Declare Redraw(Gadget)
	Declare SetAttribute(Gadget,Attribute,Value)
	Declare SetAttributeF(Gadget,Attribute,Value.f)
	
	Enumeration ScaledImageAttribute
		#SIA_Image
		#SIA_DrawingMode
		#SIA_BackColor
		#SIA_F_MaxZoom
		#SIA_F_MinZoom
		#SIA_UseRelativeZoom
		#SIA_SizeMode
	EndEnumeration
	
EndDeclareModule

Module ScaledImage
	EnableExplicit
	
	Structure ScaledImage
		Image.i
		LastX.l
		LastY.l
		MouseX.l
		MouseY.l
		ScrollX.f
		ScrollY.f
		Zoom.f
		MinZoom.f
		MaxZoom.f
		RelativeFactor.f
		DrawMode.a
		BackColor.l
		RelativeZoom.a
		SizeMode.l
	EndStructure
	
	Procedure.f Min(a.f, b.f)
		If a < b
			ProcedureReturn a
		EndIf
		ProcedureReturn b
	EndProcedure
	
	Procedure.f Max(a.f, b.f)
		If a > b
			ProcedureReturn a
		EndIf
		ProcedureReturn b
	EndProcedure
	
	;Attributes:
	;#SIA_Image           -> Image
	;#SIA_DrawingMode     -> DrawingMode (#True=ScaledImageDrawing, #False=StandardDrawing)
	;#SIA_BackColor       -> Background color
	;#SIA_F_MinZoom       -> Minimal zoom factor, 0.0 to disable
	;#SIA_F_MaxZoom       -> maximal zoom factor, 0.0 to disable
	;#SIA_UseRelativeZoom -> Use relative zoom, calculated by gadget size
	;#SIA_SizeMode        -> #PB_Image_RAW (default) or #PB_Image_Smooth
	Procedure SetAttributeF(Gadget,Attribute,Value.f)
		If IsGadget(Gadget) And GadgetType(Gadget)=#PB_GadgetType_Canvas
			Protected *Data.ScaledImage=GetGadgetData(Gadget)
			Select Attribute
				Case #SIA_F_MinZoom
					*Data\MinZoom=Value
				Case #SIA_F_MaxZoom
					*Data\MaxZoom=Value
			EndSelect
		EndIf
	EndProcedure
	
	Procedure SetAttribute(Gadget,Attribute,Value)
		If IsGadget(Gadget) And GadgetType(Gadget)=#PB_GadgetType_Canvas
			Protected *Data.ScaledImage=GetGadgetData(Gadget)
			Protected zX.f,zY.f
			Select Attribute
				Case #SIA_Image
					If IsImage(Value)
						*Data\Image=Value
						zX=GadgetWidth(Gadget)/ImageWidth(*Data\Image)
						zY=GadgetHeight(Gadget)/ImageHeight(*Data\Image)
						If zY<zX
							*Data\RelativeFactor=zY
						Else
							*Data\RelativeFactor=zX
						EndIf
					EndIf
				Case #SIA_DrawingMode
					*Data\DrawMode=Value
				Case #SIA_BackColor
					*Data\BackColor=Value
				Case #SIA_UseRelativeZoom
					*Data\RelativeZoom=Value
					If IsImage(*Data\Image)
						zX=GadgetWidth(Gadget)/ImageWidth(*Data\Image)
						zY=GadgetHeight(Gadget)/ImageHeight(*Data\Image)
						If zY<zX
							*Data\RelativeFactor=zY
						Else
							*Data\RelativeFactor=zX
						EndIf
					EndIf
				Case #SIA_SizeMode
					*Data\SizeMode=Value
			EndSelect
		EndIf
	EndProcedure
	
	Procedure Redraw(Gadget)
		If IsGadget(Gadget) And GadgetType(Gadget)=#PB_GadgetType_Canvas And StartDrawing(CanvasOutput(Gadget))
			Protected *Data.ScaledImage
			*Data=GetGadgetData(Gadget)
			Box(0,0,OutputWidth(), OutputHeight(), *Data\BackColor)
			If IsImage(*Data\Image) And *Data\Zoom > 0
				Protected Zoom.f = *Data\Zoom
				Protected scrollX.f = *Data\ScrollX
				Protected scrollY.f = *Data\ScrollY
				If *Data\RelativeZoom
					Zoom = Zoom * *Data\RelativeFactor
				EndIf
				If *Data\DrawMode = 0
					DrawImage(ImageID(*Data\Image), scrollX * Zoom, scrollY * Zoom, ImageWidth(*Data\Image) * Zoom, ImageHeight(*Data\Image) * Zoom)
				Else
					Protected cropX.f = Max(-Round(scrollX, #PB_Round_Up), 0)
					Protected cropY.f = Max(-Round(scrollY, #PB_Round_Up), 0)
					Protected cropWidth.f  = Min(OutputWidth()  / Zoom, ImageWidth(*Data\Image)  - Max((scrollX + ImageWidth(*Data\Image))  - OutputWidth()  / Zoom, 0) - cropX)
					Protected cropHeight.f = Min(OutputHeight() / Zoom, ImageHeight(*Data\Image) - Max((scrollY + ImageHeight(*Data\Image)) - OutputHeight() / Zoom, 0) - cropY)
					If cropWidth > 0 And cropHeight > 0
						Protected tmpImage = GrabImage(*Data\Image, #PB_Any, cropX, cropY, Round(cropWidth + 1, #PB_Round_Up), Round(cropHeight + 1, #PB_Round_Up))
						If IsImage(tmpImage) And Int(ImageWidth(tmpImage) * Zoom) > 0 And Int(ImageHeight(tmpImage) * Zoom) > 0
							If Zoom <> 1 Or Zoom <> 1
								ResizeImage(tmpImage, ImageWidth(tmpImage) * Zoom, ImageHeight(tmpImage) * Zoom, *Data\SizeMode)
							EndIf
							If cropX
								scrollX = Max(scrollX - cropX, 0) * Zoom + Mod(scrollX + cropX, 1)
							EndIf
							If cropY
								scrollY = Max(scrollY - cropY, 0) * Zoom + Mod(scrollY + cropY, 1)
							EndIf
							DrawImage(ImageID(tmpImage), scrollX * Zoom, scrollY * Zoom)
							FreeImage(tmpImage)
						EndIf
					EndIf
				EndIf
			EndIf
			StopDrawing()
		EndIf
	EndProcedure
	
	Procedure Events()
		Protected *Data.ScaledImage=GetGadgetData(EventGadget())
		*Data\LastX = *Data\MouseX
		*Data\LastY = *Data\MouseY
		*Data\MouseX = GetGadgetAttribute(EventGadget(), #PB_Canvas_MouseX)
		*Data\MouseY = GetGadgetAttribute(EventGadget(), #PB_Canvas_MouseY)
		Select EventType()
			Case #PB_EventType_Resize
				If *Data\RelativeZoom And IsImage(*Data\Image)
					Protected zX.f=GadgetWidth(EventGadget())/ImageWidth(*Data\Image)
					Protected zY.f=GadgetHeight(EventGadget())/ImageHeight(*Data\Image)
					If zY<zX
						*Data\RelativeFactor=zY
					Else
						*Data\RelativeFactor=zX
					EndIf
				EndIf
				Redraw(EventGadget())
			Case #PB_EventType_RightClick
				*Data\Zoom=1.0
				*Data\DrawMode=#True
				*Data\ScrollX=0
				*Data\ScrollY=0
				Redraw(EventGadget())
			Case #PB_EventType_MouseMove
				If GetGadgetAttribute(EventGadget(), #PB_Canvas_Buttons) = #PB_Canvas_LeftButton
					*Data\ScrollX + (*Data\MouseX - *Data\LastX) / *Data\Zoom
					*Data\ScrollY + (*Data\MouseY - *Data\LastY) / *Data\Zoom
					Redraw(EventGadget())
				EndIf
			Case #PB_EventType_MouseWheel
				Protected oldZoom.d = *Data\Zoom
				If GetGadgetAttribute(EventGadget(), #PB_Canvas_WheelDelta) > 0
					*Data\Zoom / 0.85
					If *Data\MaxZoom>0 And *Data\Zoom>*Data\MaxZoom
						*Data\Zoom=*Data\MaxZoom
					EndIf
				Else
					*Data\Zoom * 0.85
					If *Data\Zoom<*Data\MinZoom
						*Data\Zoom=*Data\MinZoom
					EndIf
				EndIf
				If oldZoom <> *Data\Zoom
					*Data\ScrollX + (*Data\MouseX / *Data\Zoom) * (1 - (*Data\Zoom / oldZoom))
					*Data\ScrollY + (*Data\MouseY / *Data\Zoom) * (1 - (*Data\Zoom / oldZoom))
					Redraw(EventGadget())
				EndIf
		EndSelect
	EndProcedure
	
	Procedure Enable(Gadget,Image,Enable.a=#True)
		If IsGadget(Gadget) And GadgetType(Gadget)=#PB_GadgetType_Canvas
			If Enable
				Protected *Data.ScaledImage
				*Data=AllocateStructure(ScaledImage)
				*Data\Zoom=1.0
				*Data\DrawMode=#True
				*Data\Image=Image
				*Data\BackColor=#White
				*Data\SizeMode=#PB_Image_Raw
				SetGadgetData(Gadget,*Data)
				BindGadgetEvent(Gadget,@Events())
			Else
				If GetGadgetData(Gadget)
					FreeMemory(GetGadgetData(Gadget))
					UnbindGadgetEvent(Gadget,@Events())
				EndIf
			EndIf
		EndIf
	EndProcedure
	
EndModule
User avatar
jacdelad
Addict
Addict
Posts: 1993
Joined: Wed Feb 03, 2021 12:46 pm
Location: Riesa

Re: Faster Drawing of scaled Images

Post by jacdelad »

Cool, smooth scrolling!
With your original code, zooming means zooming at where the mouse points. I somehow broke this behaviour...

Another update: Mr.L's latest changes, ScaledImageGadget, automatic centering when the image is <= view, public function deliver return values, optimizations, edit: structure publically available, more zooming with pressed [ctrl]

Code: Select all

;Originally written by Mr.L: https://www.purebasic.fr/english/viewtopic.php?p=622394#p622394
;Enhanced by Jac de Lad:     https://www.purebasic.fr/english/viewtopic.php?p=622513#p622513
;The code was written and distributed by Mr.L, who also formed all the formulas. I just enhanced it a bit and made it fit to be used with any canvas!
DeclareModule ScaledImage
  
  Structure ScaledImage
    Image.i
    LastX.l
    LastY.l
    MouseX.l
    MouseY.l
    ScrollX.f
    ScrollY.f
    Zoom.f
    MinZoom.f
    MaxZoom.f
    RelativeFactor.f
    DrawMode.a
    BackColor.l
    RelativeZoom.a
    SizeMode.l
    CenterImage.a
    ZoomFactor.f
    ZoomMultiplier.f
  EndStructure
  
  Enumeration ScaledImageAttribute
    #SIA_Image
    #SIA_DrawingMode
    #SIA_BackColor
    #SIA_F_MaxZoom
    #SIA_F_MinZoom
    #SIA_UseRelativeZoom
    #SIA_SizeMode
    #SIA_CenterImage
    #SIA_F_ZoomFactor
    #SIA_F_ZoomMultiplier
  EndEnumeration
  
  #SIG_NoImage = -1;Assign no image to ScaledImageGadget
  
  Declare   Enable(Gadget,Image,Enable.a=#True)
  Declare   Redraw(Gadget)
  Declare   SetAttribute(Gadget,Attribute,Value)
  Declare.f SetAttributeF(Gadget,Attribute,Value.f)
  Declare   ScaledImageGadget(Gadget.i,X.l,Y.l,DX.l,DY.l,Image.i=#SIG_NoImage)
  
EndDeclareModule

Module ScaledImage
  EnableExplicit
  
  Procedure.f Min(a.f, b.f)
    If a < b
      ProcedureReturn a
    EndIf
    ProcedureReturn b
  EndProcedure
  
  Procedure.f Max(a.f, b.f)
    If a > b
      ProcedureReturn a
    EndIf
    ProcedureReturn b
  EndProcedure
  
  ;{ Attribute explanations:
  ;#SIA_Image            -> Image
  ;#SIA_DrawingMode      -> DrawingMode (#True=ScaledImageDrawing, #False=StandardDrawing, 2=Toggle)
  ;#SIA_BackColor        -> Background color
  ;#SIA_F_MinZoom        -> Minimal zoom factor, 0.0 to disable
  ;#SIA_F_MaxZoom        -> maximal zoom factor, 0.0 to disable
  ;#SIA_UseRelativeZoom  -> Use relative zoom, calculated by gadget size
  ;#SIA_SizeMode         -> #PB_Image_RAW (default) or #PB_Image_Smooth
  ;#SIA_CenterImage      -> If Zoom<=1.0 -> the image is always centered
  ;#SIA_F_ZoomFactor     -> Factor of zooming when using scroll wheel (default=0.85), values >1.0 reverse the mouse wheel
  ;#SIA_F_ZoomMultiplier -> Multiplier for the zoom factor when zooming while [Ctrl] pressed (default=1.5)
  ;}
  Procedure.f SetAttributeF(Gadget,Attribute,Value.f);For use with #SIA_F_*-attributes
    Protected LastValue.f
    If IsGadget(Gadget) And GadgetType(Gadget)=#PB_GadgetType_Canvas
      Protected *Data.ScaledImage=GetGadgetData(Gadget)
      Select Attribute
        Case #SIA_F_MinZoom
          LastValue=*Data\MinZoom
          *Data\MinZoom=Value
        Case #SIA_F_MaxZoom
          LastValue=*Data\MaxZoom
          *Data\MaxZoom=Value
        Case #SIA_F_ZoomFactor
          LastValue=*Data\ZoomFactor
          *Data\ZoomFactor=Value
        Case #SIA_F_ZoomMultiplier
          LastValue=*Data\ZoomMultiplier
          *Data\ZoomMultiplier=Value
      EndSelect
    EndIf
    ProcedureReturn LastValue
  EndProcedure
  
  Procedure SetAttribute(Gadget,Attribute,Value);For use with #SIA_*-attributes
    Protected LastValue
    If IsGadget(Gadget) And GadgetType(Gadget)=#PB_GadgetType_Canvas
      Protected *Data.ScaledImage=GetGadgetData(Gadget)
      Protected zX.f,zY.f
      Select Attribute
        Case #SIA_Image
          LastValue=*Data\Image
          If IsImage(Value)
            *Data\Image=Value
            zX=GadgetWidth(Gadget)/ImageWidth(*Data\Image)
            zY=GadgetHeight(Gadget)/ImageHeight(*Data\Image)
            If zY<zX
              *Data\RelativeFactor=zY
            Else
              *Data\RelativeFactor=zX
            EndIf
          EndIf
        Case #SIA_DrawingMode
          LastValue=*Data\DrawMode
          If Value=2
            *Data\DrawMode=1-LastValue
          Else
            *Data\DrawMode=Value
          EndIf
        Case #SIA_BackColor
          LastValue=*Data\BackColor
          *Data\BackColor=Value
        Case #SIA_UseRelativeZoom
          LastValue=*Data\RelativeZoom
          *Data\RelativeZoom=Value
          If IsImage(*Data\Image)
            zX=GadgetWidth(Gadget)/ImageWidth(*Data\Image)
            zY=GadgetHeight(Gadget)/ImageHeight(*Data\Image)
            If zY<zX
              *Data\RelativeFactor=zY
            Else
              *Data\RelativeFactor=zX
            EndIf
          EndIf
        Case #SIA_SizeMode
          LastValue=*Data\SizeMode
          *Data\SizeMode=Value
        Case #SIA_CenterImage
          LastValue=*Data\CenterImage
          *Data\CenterImage=Value
      EndSelect
    EndIf
    ProcedureReturn LastValue
  EndProcedure
  
  Procedure Redraw(Gadget)
    If IsGadget(Gadget) And GadgetType(Gadget)=#PB_GadgetType_Canvas And StartDrawing(CanvasOutput(Gadget))
      Protected *Data.ScaledImage
      *Data=GetGadgetData(Gadget)
      Protected oWidth.l=OutputWidth(),oHeight.l=OutputHeight()
      Box(0, 0, oWidth, oHeight, *Data\BackColor)
      If IsImage(*Data\Image) And *Data\Zoom > 0
        Protected Zoom.f = *Data\Zoom
        Protected scrollX.f = *Data\ScrollX
        Protected scrollY.f = *Data\ScrollY
        If *Data\RelativeZoom
          Zoom = Zoom * *Data\RelativeFactor
        EndIf
        Protected iWidth.l=ImageWidth(*Data\Image), iHeight.l=ImageHeight(*Data\Image)
        If *Data\CenterImage And Int(iWidth * Zoom) <= oWidth And Int(iHeight * Zoom) <= oHeight
          scrollX = (oWidth - iWidth * Zoom ) / 2 / Zoom
          scrollY = (oHeight - iHeight * Zoom ) / 2 / Zoom
          *Data\ScrollX = scrollX
          *Data\ScrollY = scrollY
        EndIf
        If *Data\DrawMode = 0
          DrawImage(ImageID(*Data\Image), scrollX * Zoom, scrollY * Zoom, iWidth * Zoom, iHeight * Zoom)
        Else
          Protected cropX.f = Max(-Round(scrollX, #PB_Round_Up), 0)
          Protected cropY.f = Max(-Round(scrollY, #PB_Round_Up), 0)
          Protected cropWidth.f  = Min(oWidth  / Zoom, iWidth  - Max((scrollX + iWidth)  - oWidth  / Zoom, 0) - cropX)
          Protected cropHeight.f = Min(oHeight / Zoom, iHeight - Max((scrollY + iHeight) - oHeight / Zoom, 0) - cropY)
          If cropWidth > 0 And cropHeight > 0
            Protected tmpImage = GrabImage(*Data\Image, #PB_Any, cropX, cropY, Round(cropWidth + 1, #PB_Round_Up), Round(cropHeight + 1, #PB_Round_Up))
            If IsImage(tmpImage) And Int(ImageWidth(tmpImage) * Zoom) > 0 And Int(ImageHeight(tmpImage) * Zoom) > 0
              If Zoom <> 1
                ResizeImage(tmpImage, ImageWidth(tmpImage) * Zoom, ImageHeight(tmpImage) * Zoom, *Data\SizeMode)
              EndIf
              If cropX
                scrollX = Max(scrollX - cropX, 0) * Zoom + Mod(scrollX + cropX, 1)
              EndIf
              If cropY
                scrollY = Max(scrollY - cropY, 0) * Zoom + Mod(scrollY + cropY, 1)
              EndIf
              DrawImage(ImageID(tmpImage), scrollX * Zoom, scrollY * Zoom)
              FreeImage(tmpImage)
            EndIf
          EndIf
        EndIf
      EndIf
      StopDrawing()
    EndIf
  EndProcedure
  
  Procedure Events()
    Protected Gadget=EventGadget(),*Data.ScaledImage=GetGadgetData(Gadget)
    *Data\LastX = *Data\MouseX
    *Data\LastY = *Data\MouseY
    *Data\MouseX = GetGadgetAttribute(Gadget, #PB_Canvas_MouseX)
    *Data\MouseY = GetGadgetAttribute(Gadget, #PB_Canvas_MouseY)
    Select EventType()
      Case #PB_EventType_Resize
        If *Data\RelativeZoom And IsImage(*Data\Image)
          Protected zX.f=GadgetWidth(Gadget)/ImageWidth(*Data\Image)
          Protected zY.f=GadgetHeight(Gadget)/ImageHeight(*Data\Image)
          If zY<zX
            *Data\RelativeFactor=zY
          Else
            *Data\RelativeFactor=zX
          EndIf
        EndIf
        Redraw(Gadget)
      Case #PB_EventType_RightClick
        *Data\Zoom=1.0
        *Data\DrawMode=#True
        *Data\ScrollX=0
        *Data\ScrollY=0
        Redraw(Gadget)
      Case #PB_EventType_MouseMove
        If GetGadgetAttribute(Gadget, #PB_Canvas_Buttons) = #PB_Canvas_LeftButton
          *Data\ScrollX + (*Data\MouseX - *Data\LastX) / *Data\Zoom
          *Data\ScrollY + (*Data\MouseY - *Data\LastY) / *Data\Zoom
          Redraw(Gadget)
        EndIf
      Case #PB_EventType_MouseWheel
        Protected Multiplier.f,oldZoom.d = *Data\Zoom
        If Bool(GetGadgetAttribute(Gadget, #PB_Canvas_Modifiers)&#PB_Canvas_Control<>0)
          Multiplier=*Data\ZoomMultiplier
        Else
          Multiplier=1
        EndIf
        If GetGadgetAttribute(Gadget, #PB_Canvas_WheelDelta) > 0
          *Data\Zoom / *Data\ZoomFactor * Multiplier
          If *Data\MaxZoom>0 And *Data\Zoom>*Data\MaxZoom
            *Data\Zoom=*Data\MaxZoom
          EndIf
        Else
          *Data\Zoom * *Data\ZoomFactor / Multiplier
          If *Data\Zoom<*Data\MinZoom
            *Data\Zoom=*Data\MinZoom
          EndIf
        EndIf
        If oldZoom <> *Data\Zoom
          *Data\ScrollX + (*Data\MouseX / *Data\Zoom) * (1 - (*Data\Zoom / oldZoom))
          *Data\ScrollY + (*Data\MouseY / *Data\Zoom) * (1 - (*Data\Zoom / oldZoom))
          Redraw(Gadget)
        EndIf
    EndSelect
  EndProcedure
  
  Macro SetDefaultValues(MyData)
    MyData\Zoom=1.0
    MyData\DrawMode=#True
    MyData\Image=Image
    MyData\BackColor=#White
    MyData\SizeMode=#PB_Image_Raw
    MyData\ZoomFactor=0.85
    MyData\ZoomMultiplier=1.5
  EndMacro
  
  Procedure Enable(Gadget,Image,Enable.a=#True)
    If IsGadget(Gadget) And GadgetType(Gadget)=#PB_GadgetType_Canvas
      If Enable
        Protected *Data.ScaledImage
        *Data=AllocateStructure(ScaledImage)
        SetDefaultValues(*Data)
        SetGadgetData(Gadget,*Data)
        BindGadgetEvent(Gadget,@Events())
      Else
        If GetGadgetData(Gadget)
          FreeMemory(GetGadgetData(Gadget))
          UnbindGadgetEvent(Gadget,@Events())
        EndIf
      EndIf
      ProcedureReturn #True
    EndIf
    ProcedureReturn #False
  EndProcedure
  
  ;Creates a canvas with already enabled ScaledImage-functionality. The image is NOT drawn on creation (because some flags may have to be set before)!
  Procedure ScaledImageGadget(Gadget.i,X.l,Y.l,DX.l,DY.l,Image.i=#SIG_NoImage);#SIG_NoImage for no assigning no image on creation
    Protected Handle,*Data.ScaledImage
    If Gadget=#PB_Any
      Gadget=CanvasGadget(#PB_Any,X,Y,DX,DY,#PB_Canvas_Keyboard)
      Handle=Gadget
    Else
      Handle=CanvasGadget(Gadget,X,Y,DX,DY,#PB_Canvas_Keyboard)
    EndIf
    *Data=AllocateStructure(ScaledImage)
    SetDefaultValues(*Data)
    SetGadgetData(Gadget,*Data)
    BindGadgetEvent(Gadget,@Events())
    ProcedureReturn Handle
  EndProcedure
  
EndModule
Last edited by jacdelad on Tue Jun 11, 2024 11:10 am, edited 3 times in total.
Good morning, that's a nice tnetennba!

PureBasic 6.21/Windows 11 x64/Ryzen 7900X/32GB RAM/3TB SSD
Synology DS1821+/DX517, 130.9TB+50.8TB+2TB SSD
Mesa
Enthusiast
Enthusiast
Posts: 433
Joined: Fri Feb 24, 2012 10:19 am

Re: Faster Drawing of scaled Images

Post by Mesa »

i can't make this example working.

Code: Select all

;Originally written by Mr.L: https://www.purebasic.fr/english/viewtopic.php?p=622394#p622394
;Enhanced by Jac de Lad:     https://www.purebasic.fr/english/viewtopic.php?p=622513#p622513
;The code was written and distributed by Mr.L, who also formed all the formulas. I just enhanced it a bit and made it fit to be used with any canvas!
DeclareModule ScaledImage
  
  Enumeration ScaledImageAttribute
    #SIA_Image
    #SIA_DrawingMode
    #SIA_BackColor
    #SIA_F_MaxZoom
    #SIA_F_MinZoom
    #SIA_UseRelativeZoom
    #SIA_SizeMode
    #SIA_CenterImage
  EndEnumeration
  
  #SIG_NoImage = -1;Assign no image to ScaledImageGadget
  
  Declare   Enable(Gadget,Image,Enable.a=#True)
  Declare   Redraw(Gadget)
  Declare   SetAttribute(Gadget,Attribute,Value)
  Declare.f SetAttributeF(Gadget,Attribute,Value.f)
  Declare   ScaledImageGadget(Gadget.i,X.l,Y.l,DX.l,DY.l,Image.i=#SIG_NoImage)
  
EndDeclareModule

Module ScaledImage
  EnableExplicit
  
  Structure ScaledImage
    Image.i
    LastX.l
    LastY.l
    MouseX.l
    MouseY.l
    ScrollX.f
    ScrollY.f
    Zoom.f
    MinZoom.f
    MaxZoom.f
    RelativeFactor.f
    DrawMode.a
    BackColor.l
    RelativeZoom.a
    SizeMode.l
    CenterImage.a
  EndStructure
  
  Procedure.f Min(a.f, b.f)
    If a < b
      ProcedureReturn a
    EndIf
    ProcedureReturn b
  EndProcedure
  
  Procedure.f Max(a.f, b.f)
    If a > b
      ProcedureReturn a
    EndIf
    ProcedureReturn b
  EndProcedure
  
  ;Attributes:
  ;#SIA_Image           -> Image
  ;#SIA_DrawingMode     -> DrawingMode (#True=ScaledImageDrawing, #False=StandardDrawing)
  ;#SIA_BackColor       -> Background color
  ;#SIA_F_MinZoom       -> Minimal zoom factor, 0.0 to disable
  ;#SIA_F_MaxZoom       -> maximal zoom factor, 0.0 to disable
  ;#SIA_UseRelativeZoom -> Use relative zoom, calculated by gadget size
  ;#SIA_SizeMode        -> #PB_Image_RAW (default) or #PB_Image_Smooth
  ;#SIA_CenterImage     -> If Zoom<=1.0 -> the image is always centered
  Procedure.f SetAttributeF(Gadget,Attribute,Value.f);For use with #SIA_F_*-attributes
    Protected LastValue.f
    If IsGadget(Gadget) And GadgetType(Gadget)=#PB_GadgetType_Canvas
      Protected *Data.ScaledImage=GetGadgetData(Gadget)
      Select Attribute
        Case #SIA_F_MinZoom
          LastValue=*Data\MinZoom
          *Data\MinZoom=Value
        Case #SIA_F_MaxZoom
          LastValue=*Data\MaxZoom
          *Data\MaxZoom=Value
      EndSelect
    EndIf
    ProcedureReturn LastValue
  EndProcedure
  
  Procedure SetAttribute(Gadget,Attribute,Value);For use with #SIA_*-attributes
    Protected LastValue
    If IsGadget(Gadget) And GadgetType(Gadget)=#PB_GadgetType_Canvas
      Protected *Data.ScaledImage=GetGadgetData(Gadget)
      Protected zX.f,zY.f
      Select Attribute
        Case #SIA_Image
          LastValue=*Data\Image
          If IsImage(Value)
            *Data\Image=Value
            zX=GadgetWidth(Gadget)/ImageWidth(*Data\Image)
            zY=GadgetHeight(Gadget)/ImageHeight(*Data\Image)
            If zY<zX
              *Data\RelativeFactor=zY
            Else
              *Data\RelativeFactor=zX
            EndIf
          EndIf
        Case #SIA_DrawingMode
          LastValue=*Data\DrawMode
          *Data\DrawMode=Value
        Case #SIA_BackColor
          LastValue=*Data\BackColor
          *Data\BackColor=Value
        Case #SIA_UseRelativeZoom
          LastValue=*Data\RelativeZoom
          *Data\RelativeZoom=Value
          If IsImage(*Data\Image)
            zX=GadgetWidth(Gadget)/ImageWidth(*Data\Image)
            zY=GadgetHeight(Gadget)/ImageHeight(*Data\Image)
            If zY<zX
              *Data\RelativeFactor=zY
            Else
              *Data\RelativeFactor=zX
            EndIf
          EndIf
        Case #SIA_SizeMode
          LastValue=*Data\SizeMode
          *Data\SizeMode=Value
        Case #SIA_CenterImage
          LastValue=*Data\CenterImage
          *Data\CenterImage=Value
      EndSelect
    EndIf
    ProcedureReturn LastValue
  EndProcedure
  
  Procedure Redraw(Gadget)
    If IsGadget(Gadget) And GadgetType(Gadget)=#PB_GadgetType_Canvas And StartDrawing(CanvasOutput(Gadget))
      Protected *Data.ScaledImage
      *Data=GetGadgetData(Gadget)
      Protected oWidth.l=OutputWidth(),oHeight.l=OutputHeight()
      Box(0, 0, oWidth, oHeight, *Data\BackColor)
      If IsImage(*Data\Image) And *Data\Zoom > 0
        Protected Zoom.f = *Data\Zoom
        Protected scrollX.f = *Data\ScrollX
        Protected scrollY.f = *Data\ScrollY
        If *Data\RelativeZoom
          Zoom = Zoom * *Data\RelativeFactor
        EndIf
        Protected iWidth.l=ImageWidth(*Data\Image), iHeight.l=ImageHeight(*Data\Image)
        If *Data\CenterImage And Int(iWidth * Zoom) <= oWidth And Int(iHeight * Zoom) <= oHeight
          scrollX = (oWidth - iWidth * Zoom ) / 2 / Zoom
          scrollY = (oHeight - iHeight * Zoom ) / 2 / Zoom
          *Data\ScrollX = scrollX
          *Data\ScrollY = scrollY
        EndIf
        If *Data\DrawMode = 0
          DrawImage(ImageID(*Data\Image), scrollX * Zoom, scrollY * Zoom, iWidth * Zoom, iHeight * Zoom)
        Else
          Protected cropX.f = Max(-Round(scrollX, #PB_Round_Up), 0)
          Protected cropY.f = Max(-Round(scrollY, #PB_Round_Up), 0)
          Protected cropWidth.f  = Min(oWidth  / Zoom, iWidth  - Max((scrollX + iWidth)  - oWidth  / Zoom, 0) - cropX)
          Protected cropHeight.f = Min(oHeight / Zoom, iHeight - Max((scrollY + iHeight) - oHeight / Zoom, 0) - cropY)
          If cropWidth > 0 And cropHeight > 0
            Protected tmpImage = GrabImage(*Data\Image, #PB_Any, cropX, cropY, Round(cropWidth + 1, #PB_Round_Up), Round(cropHeight + 1, #PB_Round_Up))
            If IsImage(tmpImage) And Int(ImageWidth(tmpImage) * Zoom) > 0 And Int(ImageHeight(tmpImage) * Zoom) > 0
              If Zoom <> 1
                ResizeImage(tmpImage, ImageWidth(tmpImage) * Zoom, ImageHeight(tmpImage) * Zoom, *Data\SizeMode)
              EndIf
              If cropX
                scrollX = Max(scrollX - cropX, 0) * Zoom + Mod(scrollX + cropX, 1)
              EndIf
              If cropY
                scrollY = Max(scrollY - cropY, 0) * Zoom + Mod(scrollY + cropY, 1)
              EndIf
              DrawImage(ImageID(tmpImage), scrollX * Zoom, scrollY * Zoom)
              FreeImage(tmpImage)
            EndIf
          EndIf
        EndIf
      EndIf
      StopDrawing()
    EndIf
  EndProcedure
  
  Procedure Events()
    Protected Gadget=EventGadget(),*Data.ScaledImage=GetGadgetData(Gadget)
    *Data\LastX = *Data\MouseX
    *Data\LastY = *Data\MouseY
    *Data\MouseX = GetGadgetAttribute(Gadget, #PB_Canvas_MouseX)
    *Data\MouseY = GetGadgetAttribute(Gadget, #PB_Canvas_MouseY)
    Select EventType()
      Case #PB_EventType_Resize
        If *Data\RelativeZoom And IsImage(*Data\Image)
          Protected zX.f=GadgetWidth(Gadget)/ImageWidth(*Data\Image)
          Protected zY.f=GadgetHeight(Gadget)/ImageHeight(*Data\Image)
          If zY<zX
            *Data\RelativeFactor=zY
          Else
            *Data\RelativeFactor=zX
          EndIf
        EndIf
        Redraw(Gadget)
      Case #PB_EventType_RightClick
        *Data\Zoom=1.0
        *Data\DrawMode=#True
        *Data\ScrollX=0
        *Data\ScrollY=0
        Redraw(Gadget)
      Case #PB_EventType_MouseMove
        If GetGadgetAttribute(Gadget, #PB_Canvas_Buttons) = #PB_Canvas_LeftButton
          *Data\ScrollX + (*Data\MouseX - *Data\LastX) / *Data\Zoom
          *Data\ScrollY + (*Data\MouseY - *Data\LastY) / *Data\Zoom
          Redraw(Gadget)
        EndIf
      Case #PB_EventType_MouseWheel
        Protected oldZoom.d = *Data\Zoom
        If GetGadgetAttribute(Gadget, #PB_Canvas_WheelDelta) > 0
          *Data\Zoom / 0.85
          If *Data\MaxZoom>0 And *Data\Zoom>*Data\MaxZoom
            *Data\Zoom=*Data\MaxZoom
          EndIf
        Else
          *Data\Zoom * 0.85
          If *Data\Zoom<*Data\MinZoom
            *Data\Zoom=*Data\MinZoom
          EndIf
        EndIf
        If oldZoom <> *Data\Zoom
          *Data\ScrollX + (*Data\MouseX / *Data\Zoom) * (1 - (*Data\Zoom / oldZoom))
          *Data\ScrollY + (*Data\MouseY / *Data\Zoom) * (1 - (*Data\Zoom / oldZoom))
          Redraw(Gadget)
        EndIf
    EndSelect
  EndProcedure
  
  Procedure Enable(Gadget,Image,Enable.a=#True)
    If IsGadget(Gadget) And GadgetType(Gadget)=#PB_GadgetType_Canvas
      If Enable
        Protected *Data.ScaledImage
        *Data=AllocateStructure(ScaledImage)
        *Data\Zoom=1.0
        *Data\DrawMode=#True
        *Data\Image=Image
        *Data\BackColor=#White
        *Data\SizeMode=#PB_Image_Raw
        SetGadgetData(Gadget,*Data)
        BindGadgetEvent(Gadget,@Events())
      Else
        If GetGadgetData(Gadget)
          FreeMemory(GetGadgetData(Gadget))
          UnbindGadgetEvent(Gadget,@Events())
        EndIf
      EndIf
      ProcedureReturn #True
    EndIf
    ProcedureReturn #False
  EndProcedure
  
  ;Creates a canvas with already enabled ScaledImage-functionality. The image is NOT drawn on creation (because some flags may have to be set before)!
  Procedure ScaledImageGadget(Gadget.i,X.l,Y.l,DX.l,DY.l,Image.i=#SIG_NoImage);#SIG_NoImage for no assigning no image on creation
    Protected Handle,*Data.ScaledImage
    If Gadget=#PB_Any
      Gadget=CanvasGadget(#PB_Any,X,Y,DX,DY,#PB_Canvas_Keyboard)
      Handle=Gadget
    Else
      Handle=CanvasGadget(Gadget,X,Y,DX,DY,#PB_Canvas_Keyboard)
    EndIf
    *Data=AllocateStructure(ScaledImage)
    *Data\Zoom=1.0
    *Data\DrawMode=#True
    *Data\Image=Image
    *Data\BackColor=#White
    *Data\SizeMode=#PB_Image_Raw
    SetGadgetData(Gadget,*Data)
    BindGadgetEvent(Gadget,@Events())
    ProcedureReturn Handle
  EndProcedure
  
EndModule

; ----------------------------------------- TEST -----------------------------------------
CompilerIf #PB_Compiler_IsMainFile
  
  UseModule ScaledImage
  
  LoadFont(0, "Consolas", 12)
  OpenWindow(0,0,0,800,600,"ScaledImage", #PB_Window_SystemMenu | #PB_Window_Maximize)
  ScaledImageGadget(0,0,35, WindowWidth(0), WindowHeight(0) - 35)
  ButtonGadget(1,   0, 0, 200, 35, "DrawScaledImage: ON", #PB_Button_Toggle)
  TextGadget(2, 210, 5, 400, 30, "mouse + left Button = scroll   mouseWheel = zoom")	
  SetActiveGadget(0)
  
  CreateImage(0, 8192, 8192)
  StartDrawing(ImageOutput(0))
  For i = 0 To 2500
    Circle(Random(OutputWidth()), Random(OutputHeight()), Random(100,15), Random($FFFFFF, $101010))
  Next
  StopDrawing()
  
  ; 	Global ScrollX.d = -ImageWidth(0) / 2
  ; 	Global ScrollY.d = -ImageHeight(0) / 2
  ; 	Global Zoom.d = 1.0
  Global DrawMode = 1
  ; 	Global MouseX, MouseY, LastX, LastY, LButton = 0
  
  
  
  BindGadgetEvent(0, @Events())
  Redraw()
  
  Repeat
    Select WaitWindowEvent()
      Case #PB_Event_CloseWindow
        End
      Case #PB_Event_Gadget
        If EventGadget() = 1
          DrawMode = Bool(Not DrawMode)
          If DrawMode = 0
            SetGadgetText(1, "DrawScaledImage: OFF")
          Else
            SetGadgetText(1, "DrawScaledImage: ON")
          EndIf
          SetActiveGadget(0)
          Redraw()
        EndIf
    EndSelect
  ForEver
CompilerEndIf
User avatar
jacdelad
Addict
Addict
Posts: 1993
Joined: Wed Feb 03, 2021 12:46 pm
Location: Riesa

Re: Faster Drawing of scaled Images

Post by jacdelad »

The original example is not compatible with the changes I made.

Try this:

Code: Select all

; ----------------------------------------- TEST -----------------------------------------
CompilerIf #PB_Compiler_IsMainFile
  EnableExplicit
  UseModule ScaledImage
  Define i.w,*Data.ScaledImage
  
  OpenWindow(0,0,0,800,600,"ScaledImage", #PB_Window_SystemMenu | #PB_Window_Maximize)
  ScaledImageGadget(0,0,35, WindowWidth(0), WindowHeight(0) - 35)
  ButtonGadget(1,   0, 0, 200, 35, "DrawScaledImage: ON", #PB_Button_Toggle)
  TextGadget(2, 210, 5, 400, 30, "mouse + left Button = scroll   mouseWheel = zoom")	
  SetActiveGadget(0)
  
  CreateImage(0, 8192, 8192)
  StartDrawing(ImageOutput(0))
  For i = 0 To 2500
    Circle(Random(OutputWidth()), Random(OutputHeight()), Random(100,15), Random($FFFFFF, $101010))
  Next
  StopDrawing()
  
  SetAttribute(0,#SIA_Image,0)
  Redraw(0)
  
  Repeat
    Select WaitWindowEvent()
      Case #PB_Event_CloseWindow
        End
      Case #PB_Event_Gadget
        If EventGadget() = 1
          *Data=GetGadgetData(0)
          *Data\DrawMode=1-*Data\DrawMode
          If *Data\DrawMode = 0
            SetGadgetText(1, "DrawScaledImage: OFF")
          Else
            SetGadgetText(1, "DrawScaledImage: ON")
          EndIf
          SetActiveGadget(0)
          Redraw(0)
        EndIf
    EndSelect
  ForEver
CompilerEndIf
in addition to the module right above your post.
Good morning, that's a nice tnetennba!

PureBasic 6.21/Windows 11 x64/Ryzen 7900X/32GB RAM/3TB SSD
Synology DS1821+/DX517, 130.9TB+50.8TB+2TB SSD
Mr.L
Enthusiast
Enthusiast
Posts: 146
Joined: Sun Oct 09, 2011 7:39 am

Re: Faster Drawing of scaled Images

Post by Mr.L »

jacdelad wrote: Tue Jun 11, 2024 4:34 am With your original code, zooming means zooming at where the mouse points. I somehow broke this behaviour...
That's strange. With your changed code, zooming to the cursor doesn't seem to be broken for me.
Post Reply