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

new.gif NEW Tutorial:

In this step of the Bitmap Painting tool development, we will add an eraser option to the existing brishes. 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 soothly

Natural Language

MAXScript

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: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

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