How To - Practical Examples > How To ... Develop a Bitmap Painting Tool In Nine Easy Steps >

# How To ... Develop a Bitmap Painting Tool - Smooth Brushes

How To > Develop a Bitmap Painting Tool- Smooth Brushes

In this step of the Bitmap Painting tool development, we will add optional smooth falloff to the brush.

NATURAL LANGUAGE
We will add a new item to the brushes listbox.
We will extend the existing paintbrush function with a new case calculating the new color using the existing one and a falloff based on the pixel's distance to the center of the brush.

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
      colorpicker inkColor height:16 modal:false color:black across:4
      checkbutton airBrush "AirBrush"width:50
      spinner AirBrushSpeed "Speed" range:[0.1,50,10] fieldwidth:30
      spinner BrushSize "Size" range:[1,50,10] type:#integer fieldwidth:40
      listbox BrushShape items:#("Circle","Box","Circle Smooth") pos:[bitmapX+5,0] width:90

      fn paintBrush pos =
      (
        case BrushShape.selection of
        (
          1: (
            if distance pos currentPos <= BrushSize.value/2 do
              setPixels theCanvasBitmap pos #(inkColor.color)
          )
          2: setPixels theCanvasBitmap pos #(inkColor.color)
          3: (
            theFactor = (distance pos currentPos) / (BrushSize.value/2.0)
            if theFactor <= 1.0 do
            (
              theFactor = sin ( 90.0 * theFactor)
              thePixels = getPixels theCanvasBitmap pos 1
              if thePixels[1] != undefined do
              (
                thePixels[1] = (thePixels[1] * theFactor) + (inkColor.color * (1.0 - theFactor))
                setPixels theCanvasBitmap pos thePixels
              )
            )
          )--end case 3
        )--end case
      )--end fn

      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
        (
          if airBrush.checked then
          (
            for b = 1 to (BrushSize.value / AirBrushSpeed.value) do
            paintBrush (currentPos + (random [-BrushSize.value/2,-BrushSize.value/2] [BrushSize.value/2,BrushSize.value/2] ))
          )
          else
            for b = -BrushSize.value/2 to BrushSize.value/2 do
              for c = -BrushSize.value/2 to BrushSize.value/2 do
                paintBrush (currentPos + [c,b])
          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 posdo
      (
        if isDrawing do drawStroke lastPos pos
        lastPos = pos
      )
    )
    createDialog MicroPaint_CanvasRollout (bitmapx+100) (bitmapy+30)
    )

## Step-By-Step

    --Code in italic has not 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]

    rollout MicroPaint_CanvasRollout "MicroPaint"
    (
    bitmap theCanvas pos:[0,0] width:bitmapX height:bitmapY bitmap:theCanvasBitmap
    colorpicker inkColor height:16 modal:false color:black across:4
    checkbutton airBrush "AirBrush" width:50
    spinner AirBrushSpeed "Speed" range:[0.1,50,10] fieldwidth:40
    spinner BrushSize "Size" range:[1,50,10] type:#integer fieldwidth:40
     listbox BrushShape items:#("Circle", "Box", "Circle Smooth") pos:[bitmapX+5,0] width:90

We add a new brush to our "toolbox" - "Circle Smooth"

    fn paintBrush pos =
    (
    case BrushShape.selection of
    (
    1: (
    if distance pos currentPos <= BrushSize.value/2 do
    setPixels theCanvasBitmap pos #(inkColor.color)
    )
    2: setPixels theCanvasBitmap pos #(inkColor.color)

    3: (

When the "Circle Smooth" brush is selected,

    theFactor =(distance pos currentPos)/ (BrushSize.value/2.0)

We calculate the distance from the pixel to be drawn to the center of the brush and divide by the radius of the brush.

    if theFactor <= 1.0 do (

If the result is less than 1.0, the pixel lies inside the circle.

    theFactor = sin ( 90.0 * theFactor)

In that case we multiply the value by 90 and use the Sine function to calculate a nice falloff curve. Note that remarking this line would give you a linear falloff curve instead of the sine-based falloff curve!

    thePixels = getPixels theCanvasBitmap pos 1

Next we read a single pixel from the position we are about to write to.

    if thePixels[1] != undefined do (

If the single pixel in the array is a valid color,

    thePixels[1] = (thePixels[1] * theFactor) + (inkColor.color * (1.0 - theFactor))

we blend the color of the background with the brush color based on the distance factor. If the factor is 0.5, the two colors will be blended 50-50%. If the factor is 1 (at the edge of the circle, the background color will be used 100% and the ink will be 0%.

    setPixels theCanvasBitmap pos thePixels

Finally, we write the color back into the bitmap.

    )  ) )   ) )

    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
    (
    if airBrush.checked then
    (
    for b = 1 to (BrushSize.value / AirBrushSpeed.value) do
    paintBrush (currentPos + (random [-BrushSize.value/2,-BrushSize.value/2] [BrushSize.value/2,BrushSize.value/2] ))
    )
    else
    for b = -BrushSize.value/2 to BrushSize.value/2 do
    for c = -BrushSize.value/2 to BrushSize.value/2 do
    paintBrush (currentPos + [c,b])
    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+100) (bitmapy+30)
    )

Result

Previous Tutorial:

How To ... Develop a Bitmap Painting Tool - Airbrush and Shapes

Next Tutorial:

How To ... Develop a Bitmap Painting Tool - Load and Save

#### See Also

- "How To" Tutorials Index Page