How To ... Develop a Bitmap Painting Tool - Strokes Support

How To > Develop a Bitmap Painting Tool- Strokes Support

In this step of the Bitmap Painting tool development, we will add some better stroke drawing code to avoid the dotted appearance.

NATURAL LANGUAGE

We will extend the existing MacroScript by adding a new function.

The new function will be passed the old and the new position of the mouse and a line will be drawn between the two points to define a continuous stroke.

SCRIPT:

macroScript MicroPaint category: "HowTo"
(
global MicroPaint_CanvasRollout
try (destroyDialog MicroPaint_CanvasRollout) catch()
local isDrawing = false
local bitmapX = bitmapY = 512
local theCanvasBitmap = bitmap bitmapX bitmapY color:white
local currentPos = lastPos = [0,0]
 
rollout MicroPaint_CanvasRollout "MicroPaint"
(
  bitmap theCanvas pos:[0,0] width:bitmapX height:bitmapY bitmap:theCanvasBitmap
  fn paintBrush pos = ( setPixels theCanvasBitmap pos #(color 0 0 0) )
  fn drawStroke lastPos pos =
  (
    currentPos = lastPos
    deltaX = pos.x - lastPos.x
    deltaY = pos.y - lastPos.y
    maxSteps = amax #(abs(deltaX),abs(deltaY))
    deltaStepX = deltaX / maxSteps
    deltaStepY = deltaY / maxSteps
    for i = 0 to maxSteps do
    (
      paintBrush currentPos
      currentPos += [deltaStepX, deltaStepY]
    ) 
    theCanvas.bitmap = theCanvasBitmap
  )
  on MicroPaint_CanvasRollout lbuttondown pos do
  (
    lastPos = pos
    isDrawing = true
    drawStroke lastPos pos
  )
  on MicroPaint_CanvasRollout lbuttonup pos do isDrawing = false
  on MicroPaint_CanvasRollout mousemove pos do
  (
    if isDrawing do drawStroke lastPos pos
    lastPos = pos
  )
)
createDialog MicroPaint_CanvasRollout bitmapX bitmapY
)

Step-By-Step

--Code in italic has no changes since the previous version. macroScript MicroPaint category:"HowTo"
(
global MicroPaint_CanvasRollout
try(destroyDialog CanvasRollout)catch()
local isDrawing = false
local bitmapX = bitmapY = 512
local theCanvasBitmap = bitmap bitmapX bitmapY color:white local currentPos = lastPos = [0,0]

The first change we will have to do is to define the current and the last known position variables that will be used to keep track of the current stroke. We initialize both to [0,0].

rolloutMicroPaint_CanvasRollout "MicroPaint" ( bitmap theCanvas pos:[0,0] width:400 height:400 bitmap:theCanvasBitmap fn paintBrush pos = ( setPixels theCanvasBitmap pos #(black) )

We will simplify the paintbrush function. It will only draw the point, but will not update the canvas. This is necessary because the stroke will contain a large number of brush points, but will have to be updated just once after the stroke to maintain interactivity.

fn drawStroke lastPos pos =
(

This will be the stroke drawing function called by the mouse handlers. Two arguments are passed to it - the last mouse position (beginning of the stroke) and the current mouse position (the end of the stroke).

currentPos = lastPos

The current position variable is initialized to the beginning of the stroke. It will be used to draw the line from the start to the end point of the stroke.

deltaX = pos.x - lastPos.x
deltaY = pos.y - lastPos.y

To draw a straight line from the last to the new position, we must know how far these two points are from each other along both X and Y.

maxSteps = amax #(abs(deltaX),abs(deltaY))

Then, we compare the absolute values along X and Y to see which one is larger. MaxSteps will be equal to either the number of pixels between the two along X or along Y.

deltaStepX = deltaX / maxSteps
deltaStepY = deltaY / maxSteps

Now, we calculate the increment per step we will need for the X and Y axis. If maxSteps is equal to the distance along X, then deltaStepX will be +1 or -1, while deltaStepY will be a number less than 1.0. If maxSteps is equal to the distance along Y, then deltaStepY will be +1 or -1, and deltaStepX will be < 1.0.

for i = 0 to maxSteps do
(

Now, we loop from 0 to the number of steps,

paintBrush currentPos

paint a black dot at the current position by calling our custom paintbrush function,

currentPos += [deltaStepX, deltaStepY]

and then increment the current position with the deltaStep values until the current mouse position is reached.

) 
theCanvas.bitmap = theCanvasBitmap

Finally, when the stroke is done, we update the bitmap in the rollout.

) on MicroPaint_CanvasRollout lbuttondown pos do
(  
lastPos = pos

If the user pressed the mouse button, we will have to reset the stroke to the currently clicked point.

isDrawing = true drawStroke lastPos pos

Instead of calling the PaintBrush function, we call the drawStroke function and pass the last and the current position (which are equal at this point) to it. This will create a single dot at the clicked point (the stroke has length 0, both deltaX and deltaY are 0, thus maxSteps is 0, but since the for i loop starts counting at 0, we get at least one iteration to draw the point).

)
on MicroPaint_CanvasRollout lbuttonup pos do isDrawing = false
on MicroPaint_CanvasRollout mousemove pos do ( if isDrawing do drawStroke lastPos pos

Instead of calling the PaintBrush function, we call the drawStroke function again. At this point, the lastPos and the pos values are different and require a longer stroke to be drawn.

lastPos = pos

After the stroke is drawn, we record the current mouse position into the lastPos variable to be used as the starting point of the next stroke.

)
)
createDialog MicroPaint_CanvasRollout bitmapX bitmapY
)

Result

Previous Tutorial:

How To ... Develop a Bitmap Painting Tool - Basic Utility

Next Tutorial:

How To ... Develop a Bitmap Painting Tool - Brush Size and Color

See Also