Selector module; select rectangle on canvas using mouse
Posted: Wed Feb 25, 2015 2:39 am
This module allows you to select an area on a canvas gadget using the mouse.
Once an area has been selected you can move the selection by dragging it.
It can also be resized by dragging the adjustment square in the lower right corner.
You can retrieve an image of the selected area if desired.
Other information about the selection is also available such as x, y coordinates.
I have tested with Windows and Linux.
Usage example follows the module code.
Edit: 6/25/2015
Fixed contrast problem when selecting on a gray background.
Module code.
Usage example.
Once an area has been selected you can move the selection by dragging it.
It can also be resized by dragging the adjustment square in the lower right corner.
You can retrieve an image of the selected area if desired.
Other information about the selection is also available such as x, y coordinates.
I have tested with Windows and Linux.
Usage example follows the module code.
Edit: 6/25/2015
Fixed contrast problem when selecting on a gray background.
Module code.
Code: Select all
; select a rectangular area on a canvas using the mouse.
;
; Filename : Selector_Module.pbi
; Module name : Selector
; Author : BasicallyPure
; original date : 2/24/2015
;
; OS platforms : cross platform, tested on Windows 7 and Linux
;
; Compiler : tested using Windows PureBasic 5.31 (x86),
; : Linux PureBasic 5.30 (x64)
;
; License : free
;
; forum topic : http://www.purebasic.fr/english/viewtopic.php?f=12&t=61736
;
; last revision : 6/25/2015
;
;
; EnableExplicit
DeclareModule Selector
; Usage:
; 1. INITIALIZE_SELECTOR(canvas, scrollArea = -1)
; Call the INITIALIZE_SELECTOR() procedure to activate the selector.
; Repeat a call to INITIALIZE_SELECTOR() if the canvas is resized or when
; switching to another canvas. If your are using Windows and your canvas
; is inside a ScrollAreaGadget, provide the scroll area number as the
; second parameter in the procedure.
;
; 2. The following custom events are generated by mouse activity on the canvas gadget.
; a. #SelectionComplete : when a valid selection has been completed on the canvas.
; b. #SelectionCancelled : when a selection has been cancelled by mouse or program.
; c. #SelectionAltered : when the selection has been moved or resized.
;
; 3. Use these additional module procedures in your program as needed.
; a. CANCEL_SELECTION() ; removes an active selection programatically. Also a selection
; may be automatically cancelled by clicking outside the selection rectangle.
;
; b. CAPTURE_SELECTION(image = #PB_Any)
; This procedure creates a new image using the selected area. If #PB_Any is used or
; the image parameter is omitted the automatically generated image number will be
; returned. If an image number is provided then the ImageID will be returned.
;
; c. DEACTIVATE_SELECTOR() ; the selector feature will be deactivated.
;
; 4. Global sturctured variable 'Selector::selection\xxx' is available to determine selection
; details; see comments in structure 'SelType' below.
;
#MaskPattern = $888888
Enumeration #PB_Event_FirstCustomValue + 47
#SelectionComplete
#SelectionCancelled
#SelectionAltered
EndEnumeration
Declare CANCEL_SELECTION()
Declare INITIALIZE_SELECTOR(canvas, scrollArea = -1)
Declare DEACTIVATE_SELECTOR()
Declare CAPTURE_SELECTION(image = #PB_Any)
Structure SelType
canvas.i ; canvas number or -1 if selector is deactivated
sFlag.i ; if #True an area is selected
left.i ; left edge 'x' value of selected area
right.i ; right edge 'x' value of selected area
top.i ; top edge 'y' value of selected area
bottom.i ; bottom edge 'y' value of selected area
limLeft.i ; left boundary limit of selection area
limRight.i ; right boundary limit of selection area
limTop.i ; top boundary limit of selection area
limBottom.i ; bottom boundary limit of selection area
EndStructure
Global selection.SelType
selection\canvas = -1
EndDeclareModule
Module Selector
Declare SELECT_AREA(canvasEvent)
Declare MASK_SELECTION_RECTANGLE()
Declare CANVAS_EVENT_HANDLER()
Global canvas.i, userCursor, scroll, mouseDown
#SelectorCursor = #PB_Cursor_Cross
Enumeration
#CanvasMouseUP
#CanvasMouseDown
#CanvasMouseMove
EndEnumeration
Procedure INITIALIZE_SELECTOR(canvas, scrollArea = -1)
; call this procedure to activate the selector, or
; any time the canvas size is changed, or if you switch to
; another canvas gadget. For Windows only, Provide the
; scrollArea number if the canvas is inside a ScrollAreaGadget.
Protected result = #False, scrollError = #False
scroll = scrollArea
If scroll <> -1
If IsGadget(scroll)
If GadgetType(scroll) <> #PB_GadgetType_ScrollArea
scrollError = #True
EndIf
Else
scrollError = #True
EndIf
EndIf
If IsGadget(canvas) And scrollError = #False
If GadgetType(canvas) = #PB_GadgetType_Canvas
With selection
If canvas <> \canvas
userCursor = GetGadgetAttribute(canvas, #PB_Canvas_Cursor)
If userCursor <> -1
SetGadgetAttribute(canvas,#PB_Canvas_Cursor, #SelectorCursor)
EndIf
If IsGadget(\canvas)
UnbindGadgetEvent(\canvas, @CANVAS_EVENT_HANDLER())
EndIf
BindGadgetEvent(canvas, @CANVAS_EVENT_HANDLER())
EndIf
\canvas = canvas
\sFlag = #False
\left = 0
\right = 0
\top = 0
\bottom = 0
\limLeft = 0
\limTop = 0
\limRight = GadgetWidth(canvas)
\limBottom = GadgetHeight(canvas)
EndWith
mouseDown = #False
result = #True
EndIf
EndIf
If scrollError = #True
MessageRequester("INITIALIZE_SELECTOR()","Error! Parameter is not a valid ScrollArea number.")
ElseIf result = #False
MessageRequester("INITIALIZE_SELECTOR()","Error! Parameter is not a valid canvas number.")
EndIf
ProcedureReturn result
EndProcedure
Procedure SELECT_AREA(canvasEvent)
; Uses the mouse to select an area on the canvas gadget.
Static.i x, y, lenX, lenY, lastX, lastY, boxSet, drag
Static.i WIC = #PB_Window_InnerCoordinate
Protected.i msx, msy, ox, oy
Protected.i Win = GetActiveWindow()
With selection
Select canvasEvent
Case #CanvasMouseDown
x = WindowMouseX(Win) - GadgetX(\canvas,#PB_Gadget_WindowCoordinate)
y = WindowMouseY(Win) - GadgetY(\canvas,#PB_Gadget_WindowCoordinate)
If \sFlag
If boxSet = #True And x > \right-9 And x < \right And y > \bottom-9 And y < \bottom
; adjust the rectangle
lastX = \left : lastY = \top : lenX = \right-\left : lenY = \bottom-\top ;<-- added 6/22/15
x = lastX
y = lastY
drag = #False
ElseIf x > \left And x < \right And y > \top And y < \bottom
drag = #True ; drage selection rectangle is active
lastX = \left : lastY = \top : lenX = \right-\left : lenY = \bottom-\top ;<-- added 6/22/15
; optional for windows
; moves cursor to center of selected area on mouse button down
CompilerIf #PB_Compiler_OS = #PB_OS_Windows
If scroll > -1 ; use this if the canvas is inside a ScrollArea gadget
ox = \left + Abs(lenX / 2) + WindowX(Win, WIC) + GadgetX(scroll) + 1
oy = \top + Abs(lenY / 2) + WindowY(Win, WIC) + GadgetY(scroll) + 1
SetCursorPos_(ox-GetGadgetAttribute(scroll,#PB_ScrollArea_X)+1,
oy-GetGadgetAttribute(scroll,#PB_ScrollArea_Y)+1)
Else ; use this if the canvas is NOT inside a ScrollArea gadget
ox = \left + Abs(lenX / 2) + WindowX(Win, WIC) + GadgetX(\canvas)
oy = \top + Abs(lenY / 2) + WindowY(Win, WIC) + GadgetY(\canvas)
SetCursorPos_(ox,oy)
EndIf
CompilerEndIf
Else ; prepare for selection removal on mouse up
MASK_SELECTION_RECTANGLE()
\sFlag = #False
\left = x : \right = x
\top = y : \bottom = y
EndIf
Else
\left = x : \right = x
\top = y : \bottom = y
EndIf
Case #CanvasMouseMove
Delay(10)
If mouseDown
StartDrawing(CanvasOutput(\canvas))
DrawingMode(#PB_2DDrawing_XOr | #PB_2DDrawing_Outlined)
If \sFlag = #True ; erase previous selection rectangle
Box(lastX,lastY,lenX,lenY,#MaskPattern)
DrawingMode(#PB_2DDrawing_XOr)
If boxSet = #True
boxSet = #False
Box(\right - 8, \bottom - 8, 7, 7, #MaskPattern)
EndIf
Else ; reset the lastX and lastY values
\sFlag = #True
lastX = x
lastY = y
boxSet = #False
EndIf
msx = WindowMouseX(Win) - GadgetX(\canvas,#PB_Gadget_WindowCoordinate)
msy = WindowMouseY(Win) - GadgetY(\canvas,#PB_Gadget_WindowCoordinate)
; check if mouse cursor is in bounds
If msx >= \limLeft And msx < \limRight And msy >= \limTop And msy < \limBottom
If drag = #False ; calculate size of next selection outline
lenX = msx - x
lenY = msy - y
Else ; drag the selection outline
x = msx - lenX >> 1
y = msy - lenY >> 1
; prevent out of bounds dragging
If x < \limLeft : x = \limLeft : EndIf ; check left side
If y < \limTop : y = \limTop : EndIf ; check top side
If x > \limRight -1 - lenX ; check right side
x = \limRight -1 - lenX
EndIf
If y > \limBottom -1 - lenY ; check bottom side
y = \limBottom -1 - lenY
EndIf
EndIf
EndIf
; draw the selection rectangle
DrawingMode(#PB_2DDrawing_XOr | #PB_2DDrawing_Outlined)
Box(x,y,lenX,lenY,#MaskPattern)
lastX = x
lastY = y
StopDrawing()
\left = x
\top = y
\right = x + lenX
\bottom = y + lenY
PostEvent(#SelectionAltered)
EndIf
Case #CanvasMouseUP ; mouse button was released
drag = #False
If \left > \right ; fix left/right crossover
Swap \left, \right : x + lenX : lastX = x : lenX * -1
EndIf
If \top > \bottom ; fix top/bottom crossover
Swap \top, \bottom : y + lenY : lastY = y : lenY * -1
EndIf
If \left = \right And \top = \bottom ; single point so no selection
\sFlag = #False
boxSet = #False
PostEvent(#SelectionCancelled)
Else ; a valid selection has been made
; draw the selection resize box
If boxSet = #False
StartDrawing(CanvasOutput(\canvas))
DrawingMode(#PB_2DDrawing_XOr)
Box(\right - 8, \bottom - 8, 7, 7, #MaskPattern)
StopDrawing()
boxSet = #True
EndIf
PostEvent(#SelectionComplete) ; custom event
EndIf
EndSelect
EndWith
EndProcedure
Procedure MASK_SELECTION_RECTANGLE()
; toggle, hide or unhide the selection rectangle
With selection
If \sFlag
StartDrawing(CanvasOutput(\canvas))
DrawingMode(#PB_2DDrawing_XOr | #PB_2DDrawing_Outlined)
Box(\left, \top, \right-\left, \bottom-\top, #MaskPattern)
DrawingMode(#PB_2DDrawing_XOr)
Box(\right - 8, \bottom - 8, 7, 7, #MaskPattern)
StopDrawing()
EndIf
EndWith
EndProcedure
Procedure CANCEL_SELECTION()
; erase the selection rectangle
With selection
If \sFlag
MASK_SELECTION_RECTANGLE()
\sFlag = #False
\top = \bottom
\left = \right
PostEvent(#SelectionCancelled)
EndIf
EndWith
EndProcedure
Procedure CAPTURE_SELECTION(image = #PB_Any)
; creates an image of the selected canvas area
; image: the image number to create or #PB_Any (default)
; Returns the ImageID(img) Or the automatically
; generated image number if #PB_Any is used.
With selection
MASK_SELECTION_RECTANGLE() ; temporarily hide the rectangle
StartDrawing(CanvasOutput(\canvas))
image = GrabDrawingImage(image,\left,\top,\right - \left + 1,\bottom - \top + 1)
StopDrawing()
MASK_SELECTION_RECTANGLE() ; restore the rectangle
EndWith
ProcedureReturn image
EndProcedure
Procedure DEACTIVATE_SELECTOR()
With selection
If \canvas <> -1
UnbindGadgetEvent(\canvas, @CANVAS_EVENT_HANDLER())
If \sFlag : CANCEL_SELECTION() : EndIf
If userCursor <> -1
SetGadgetAttribute(\canvas,#PB_Canvas_Cursor, userCursor)
EndIf
\canvas = -1
EndIf
EndWith
EndProcedure
Procedure CANVAS_EVENT_HANDLER()
Select EventType()
Case #PB_EventType_LeftButtonDown
mouseDown = #True
SELECT_AREA(#CanvasMouseDown)
Case #PB_EventType_LeftButtonUp
mouseDown = #False
SELECT_AREA(#CanvasMouseUP)
Case #PB_EventType_MouseMove
SELECT_AREA(#CanvasMouseMove)
EndSelect
EndProcedure
EndModule
Code: Select all
; demo program for 'Selector_Module.pbi'
; Author: BasicallyPure
; 2/24/2015
EnableExplicit
XIncludeFile "Selector_Module.pbi"
;UseModule Selector
UseJPEGImageDecoder()
UsePNGImageDecoder()
Declare UPDATE_STATUS()
Declare DISPLAY_SELECTION()
Declare REMOVE_DISPLAY()
Declare LOAD_IMAGE()
Declare CANVAS_CALLBACK()
#WinMain = 0
#WinShow = 1
#StatusBar = 0
#DispImg = 1
#Font = 1
; gadgets
Enumeration
#ScrollArea
#Canvas
#ButtonCancel
#ButtonDeactivate
#ButtonActivate
EndEnumeration
If OpenWindow(#WinMain,0,0,770,500,"Rectangle Selector Demo",
#PB_Window_ScreenCentered | #PB_Window_SystemMenu) = 0
End
EndIf
;{- build GUI
LoadFont(#Font,"Arial",10)
SetGadgetFont(#PB_Default, FontID(#Font))
CreateStatusBar(#StatusBar,WindowID(#WinMain))
AddStatusBarField(#PB_Ignore)
AddStatusBarField(#PB_Ignore)
AddStatusBarField(#PB_Ignore)
AddStatusBarField(#PB_Ignore)
AddStatusBarField(#PB_Ignore)
AddStatusBarField(#PB_Ignore)
AddStatusBarField(#PB_Ignore)
ResizeWindow(#WinMain,#PB_Ignore,#PB_Ignore,#PB_Ignore,WindowHeight(#WinMain)+StatusBarHeight(#StatusBar))
ScrollAreaGadget(#ScrollArea,120,10,644,484,640,480)
CanvasGadget(#Canvas,0,0,640,480,#PB_Canvas_ClipMouse)
CloseGadgetList()
SetGadgetAttribute(#Canvas,#PB_Canvas_Cursor,#PB_Cursor_Hand)
BindGadgetEvent(#Canvas,@CANVAS_CALLBACK())
StartDrawing(CanvasOutput(#Canvas))
Box(0,0,640,480,$B8CE8C)
DrawText(10,10,"Right click to load image",0,$B8CE8C)
DrawText(10,30,"Left click and drag mouse to select area" ,0,$B8CE8C)
DrawText(10,50,"To cancel selection, click outside selected",0,$B8CE8C)
DrawText(10,70,"rectangle or use 'Cancel' button at left" ,0,$B8CE8C)
StopDrawing()
ButtonGadget(#ButtonDeactivate,10,070,100,50,"DEACTIVATE SELECTOR",#PB_Button_MultiLine)
ButtonGadget(#ButtonActivate ,10,010,100,50,"ACTIVATE SELECTOR" ,#PB_Button_MultiLine)
ButtonGadget(#ButtonCancel ,10,130,100,50,"CANCEL SELECTION" ,#PB_Button_MultiLine)
DisableGadget(#ButtonDeactivate,1)
DisableGadget(#ButtonCancel,1)
;}
;{- event loop
Repeat
Select WaitWindowEvent()
Case #PB_Event_CloseWindow : Break
Case #PB_Event_RightClick : LOAD_IMAGE()
; *** custom module events ***
Case Selector::#SelectionComplete : DISPLAY_SELECTION() : UPDATE_STATUS()
Case Selector::#SelectionCancelled : REMOVE_DISPLAY() : UPDATE_STATUS()
Case Selector::#SelectionAltered : UPDATE_STATUS()
; *** end custom events ***
Case #PB_Event_Gadget
Select EventGadget()
Case #ButtonCancel
Selector::CANCEL_SELECTION() ; module procedure call
DISPLAY_SELECTION()
Case #ButtonDeactivate
Selector::DEACTIVATE_SELECTOR() ; module procedure call
DisableGadget(#ButtonDeactivate,1)
DisableGadget(#ButtonActivate,0)
UPDATE_STATUS()
Case #ButtonActivate
Selector::INITIALIZE_SELECTOR(#Canvas, #ScrollArea) ; module procedure call
DisableGadget(#ButtonActivate,1)
DisableGadget(#ButtonDeactivate,0)
UPDATE_STATUS()
EndSelect
EndSelect
ForEver
;}
End
Procedure UPDATE_STATUS()
; update the information displayed in the status bar
Protected text.s, lenX.i, lenY.i, i.i
With Selector::selection
lenX = \right - \left + 1
lenY = \bottom - \top + 1
If IsStatusBar(#StatusBar)
If \canvas <> -1
StatusBarText(#StatusBar,0,"Size = " + Str(lenX) + " x " + Str(lenY))
StatusBarText(#StatusBar,1,"x/y ratio = " + StrF(lenX / lenY,5))
StatusBarText(#StatusBar,2,"x_Left = " + Str(\left))
StatusBarText(#StatusBar,3,"y_Top = " + Str(\top))
StatusBarText(#StatusBar,4,"x_Right = " + Str(\right))
StatusBarText(#StatusBar,5,"y_Bottom = " + Str(\bottom))
If \sFlag : text = "True" : Else : text = "False" : EndIf
StatusBarText(#StatusBar,6,"Selection = " + text)
Else
For i = 0 To 6
StatusBarText(#StatusBar,i,"")
Next i
EndIf
EndIf
EndWith
EndProcedure
Procedure DISPLAY_SELECTION()
Protected w, h
Static imgGad, image = #DispImg
With Selector::selection
If \sFlag
w = \right - \left + 1
h = \bottom - \top + 1
If IsWindow(#WinShow) = 0
OpenWindow(#WinShow,20,20,w,h,"selection",#PB_Window_TitleBar|#PB_Window_NoActivate)
imgGad = ImageGadget(#PB_Any,1,1,w,h,0)
EndIf
; capture the image within the selected area
Selector::CAPTURE_SELECTION(image) ; <--- module procedure call
If IsImage(image)
SetGadgetState(imgGad,ImageID(image))
ResizeWindow(#WinShow,#PB_Ignore,#PB_Ignore,w+1,h+1)
EndIf
DisableGadget(#ButtonCancel,0)
EndIf
EndWith
EndProcedure
Procedure REMOVE_DISPLAY()
Static image = #DispImg
If IsWindow(#WinShow)
CloseWindow(#WinShow)
If IsImage(image) : FreeImage(image) : EndIf
EndIf
DisableGadget(#ButtonCancel,1)
EndProcedure
Procedure LOAD_IMAGE()
Protected f$, p$, img
CompilerIf #PB_Compiler_OS = #PB_OS_Windows
p$ = GetHomeDirectory() + "My Pictures\"
CompilerElse
p$ = GetHomeDirectory() + "Pictures/"
CompilerEndIf
f$ = OpenFileRequester("choose an image",p$,"IMAGES|*.bmp;*.jpeg;*.jpg;*.jpe;*.png",0)
If f$
img = LoadImage(#PB_Any,f$)
EndIf
If img
With Selector::selection
If \sFlag ; check for active selection
Selector::CANCEL_SELECTION()
REMOVE_DISPLAY()
\left = 0
\right = 0
\top = 0
\bottom = 0
UPDATE_STATUS()
EndIf
ResizeGadget(#Canvas,0,0,ImageWidth(img),ImageHeight(img))
;must do after canvas is resized if selector is active
If \canvas <> -1
Selector::INITIALIZE_SELECTOR(#Canvas, #ScrollArea)
EndIf
SetGadgetAttribute(#ScrollArea,#PB_ScrollArea_InnerWidth,ImageWidth(img))
SetGadgetAttribute(#ScrollArea,#PB_ScrollArea_InnerHeight,ImageHeight(img))
EndWith
StartDrawing(CanvasOutput(#Canvas))
DrawImage(ImageID(img),0,0)
StopDrawing()
EndIf
CompilerIf #PB_Compiler_OS = #PB_OS_Linux
While WindowEvent() : Wend
CompilerEndIf
EndProcedure
Procedure CANVAS_CALLBACK()
; triggers a '#PB_Event_RightClick' just like
; right clicking on the main window.
If EventType() = #PB_EventType_RightButtonUp
PostEvent(#PB_Event_RightClick)
EndIf
EndProcedure