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

# How To ... Develop a Bitmap Painting Tool - Erase Changes

How To > Develop a Bitmap Painting Tool- Erase Changes

In this step of the Bitmap Painting tool development, we will add an eraser option to the existing brushes. When the user is drawing with the right mouse button pressed, the background image will be revealed.This mode will support all available brushes including the Airbrush mode, allowing the user to erase smoothly.

NATURAL LANGUAGE
We will add a new flag variable to control the Erase mode
We will change the paintbrush function to support reading pixels from the background image
We will implement right mouse button handlers to enable erasing existing pixels with the original pixels from the background
We will add a commit changes option to the Edit menu top copy the foreground painting canvas into the background and "bake in" all changes.

SCRIPT:
    macroScript MicroPaint category: "HowTo"
    (
    global MicroPaint_CanvasRollout
    try (destroyDialog MicroPaint_CanvasRollout) catch()
    local isErasing =isDrawing = false
    local bitmapX = bitmapY = 512
    local theCanvasBitmap = bitmap bitmapX bitmapY color:white
    local theBackgroundBitmap = bitmap bitmapX bitmapY color:white
    local currentPos = lastPos = [0,0]

    rcMenu CanvasMenu
    (
      subMenu "File"
      (
        menuItem new_menu "New"
        menuItem open_menu "Open..."
        menuItem save_as "Save As..."
        separator file_menu_1
        menuItem quit_tool "Quit"
      )
      subMenu "Edit"
      (
         menuItem commit_menu "Commit Changes"
      )
      on commit_menu picked do copy theCanvasBitmap theBackgroundBitmap

      subMenu "Help"
      (
        menuItem about_tool "About MicroPaint..."
      )

      on new_menu picked do
      (
        theBackgroundBitmap = theCanvasBitmap = bitmap bitmapX bitmapY color:MicroPaint_CanvasRollout.paperColor.color
        MicroPaint_CanvasRollout.theCanvas.bitmap = theCanvasBitmap
      )

      on open_menu picked do
      (
        theOpenBitmap= selectBitmap()
        if theOpenBitmap != undefined do
        (
          copy theOpenBitmap theCanvasBitmap
          copy theOpenBitmap theBackgroundBitmap
          close theOpenBitmap
          MicroPaint_CanvasRollout.theCanvas.bitmap = theCanvasBitmap
        )
      )

      on save_as picked do
      (
        theSaveName = getSaveFileName types:"BMP (*.bmp)|*.bmp|Targa (*.tga)|*.tga|JPEG (*.jpg)|*.jpg"
        if theSaveName != undefined do
        (
          theCanvasBitmap.filename = theSaveName
          save theCanvasBitmap
        )
      )
      on about_tool picked do messagebox "MicroPaint\nMAXScript Tutorial" title:"About..."
      on quit_tool picked do destroyDialog MicroPaint_CanvasRollout
    )

    rollout MicroPaint_CanvasRollout "MicroPaint"
    (
      bitmap theCanvas pos:[0,0] width:bitmapX height:bitmapY bitmap:theCanvasBitmap
      colorpicker inkColor height:16 modal:false color:black across:5
      colorpicker paperColor height:16 modal:false color:white
      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 =
      (
        if isErasing then
          thePaintColor = (getPixels theBackgroundBitmap pos 1)[1]
        else
          thePaintColor = inkColor.color
        if thePaintColor == undefined do thePaintColor = white

        case BrushShape.selection of
        (
          1: (
            if distance pos currentPos <= BrushSize.value/2 do
              setPixels theCanvasBitmap pos #(thePaintColor)
          )
          2: setPixels theCanvasBitmap pos #(thePaintColor)
          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) + (thePaintColor * (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
        isErasing = false
        drawStroke lastPos pos
      )
      on MicroPaint_CanvasRollout rbuttondown pos do
      (
        lastPos = pos
        isErasing = isDrawing = true
        drawStroke lastPos pos
      )
      on MicroPaint_CanvasRollout lbuttonup pos do isErasing =isDrawing = false
      on MicroPaint_CanvasRollout rbuttonup pos do isErasing =isDrawing = false

      on MicroPaint_CanvasRollout mousemove pos do
      (
        if isDrawing do drawStroke lastPos pos
        lastPos = pos
      )
    )
    createDialog MicroPaint_CanvasRollout (bitmapx+100) (bitmapy+30) menu:CanvasMenu
    MicroPaint_CanvasRollout.theCanvas.bitmap = theBackgroundBitmap
    )

## Step-By-Step

    --Code in green has not changes since the previous version! macroScript MicroPaint category:"HowTo"
    (
    global MicroPaint_CanvasRollout
    try(destroyDialog MicroPaint_CanvasRollout)catch() local isErasing = isDrawing = false

We add a new variable isErasing which will be set to true when the right mouse button is pressed.

    local bitmapX = bitmapY = 512
    local theCanvasBitmap = bitmap bitmapX bitmapY color:white
    local theBackgroundBitmap = bitmap bitmapX bitmapY color:white
    local currentPos = lastPos = [0,0]

    rcMenu CanvasMenu
    (
    subMenu "File"
    (
    menuItem new_menu "New"
    menuItem open_menu "Open..."
    menuItem save_as "Save As..."
    separator file_menu_1
    menuItem quit_tool "Quit"
    )
    subMenu "Edit"
    ( menuItem commit_menu "Commit Changes"

We add a new menu item to the Edit dialog. It will be used to commit the editing changes and make them "unerasable".

    ) on commit_menu picked do copy theCanvasBitmap theBackgroundBitmap

This is the event handler for the commit changes option. When selected, the foreground image we are painting on will be copied into the background image, thus "baking" all the changed pixels into it. Using the eraser from this point on will reveal the new pixels.

    subMenu "Help"
    (
    menuItem about_tool "About MicroPaint..."
    )

    on new_menu picked do
    (
    theBackgroundBitmap = theCanvasBitmap = bitmap bitmapX bitmapY color:MicroPaint_CanvasRollout.paperColor.color
    MicroPaint_CanvasRollout.theCanvas.bitmap = theCanvasBitmap
    )

    on open_menu picked do
    (
    theOpenBitmap= selectBitmap()
    if theOpenBitmap != undefined do
    (
    copy theOpenBitmap theCanvasBitmap
    copy theOpenBitmap theBackgroundBitmap
    close theOpenBitmap
    MicroPaint_CanvasRollout.theCanvas.bitmap = theCanvasBitmap
    )
    )

    on save_as picked do
    (
    theSaveName = getSaveFileName types:"BMP (*.bmp)|*.bmp|Targa (*.tga)|*.tga|JPEG (*.jpg)|*.jpg"
    if theSaveName != undefined do
    (
    theCanvasBitmap.filename = theSaveName
    save theCanvasBitmap
    )
    )

    on about_tool picked do messagebox "MicroPaint\nMAXScript Tutorial" title:"About..."
    on quit_tool picked do destroyDialog MicroPaint_CanvasRollout
    )

    rollout MicroPaint_CanvasRollout "MicroPaint"
    (
    bitmap theCanvas pos:[0,0] width:bitmapX height:bitmapY bitmap:theCanvasBitmap
    colorpicker inkColor height:16modal:false color:black across:5
    colorpicker paperColor height:16 modal:false color:white
    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 =
    ( if isErasing then

If the eraser mode is active (that means, the user is holding down the right mouse button), then

    thePaintColor = (getPixels theBackgroundBitmap pos 1)[1]

we get the color of the pixel from the background as the ink color.

    else
    thePaintColor = inkColor.color

otherwise we use the ink color defined by the User Interface.

    if thePaintColor == undefined do thePaintColor = white

If the pixel was outside the bitmap, it might contain undefined. To avoid errors, we reset it back to white.

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

In both cases, we replace the explicit use of the InkColor.color with thepaintColor which contains the correct color to paint with.

    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) + (thePaintColor * (1.0 - theFactor))

Again, we replace the explicit use of the InkColor.color with thePaintColor which contains the correct color to paint with.

    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 isErasing = false

The isErasing flag is set to false whenever the user presses the left mouse button.

    drawStroke lastPos pos
    ) on MicroPaint_CanvasRollout rbuttondown pos do
    (
    lastPos = pos
    isErasing = isDrawing = true
    drawStroke lastPos pos
    )

This new handler is called whenever the right mouse button is pressed. It is identical to the left mouse button handler except for the isErasing flag which is set to true this time.

    on MicroPaint_CanvasRollout lbuttonup pos do isErasing = isDrawing = false
    on MicroPaint_CanvasRollout rbuttonup pos do isErasing = isDrawing = false

Both the lbuttonup and the new rbuttonup handlers will stop both drawing and erasing by setting both flags to false.

    on MicroPaint_CanvasRollout mousemove pos do
    (
    if isDrawing do drawStroke lastPos pos
    lastPos = pos
    )
    )
    createDialog MicroPaint_CanvasRollout (bitmapx+100) (bitmapy+30) menu:CanvasMenu
    MicroPaint_CanvasRollout.theCanvas.bitmap = theBackgroundBitmap
    )

Previous Tutorial:

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

NextTutorial:

How To ... Develop a Bitmap Painting Tool - Unwrap UV Coordinates

#### See Also

- "How To" Tutorials Index Page