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

new.gif NEW Tutorial:

In this step of the Bitmap Painting tool development, we will add a main menu bar with New, Open and Save options.

Natural Language

MAXScript

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 theBackgroundBitmap = bitmap bitmapX bitmapY color:white

local currentPos = lastPos = [0,0]

 

--NEW MENU CODE STARTS HERE

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" ( )

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

)

--NEW MENU CODE END HERE

 

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 =

(

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

local bitmapX = bitmapY = 512

local theCanvasBitmap = bitmap bitmapX bitmapY color:white

local theBackgroundBitmap = bitmap bitmapX bitmapY color:white

We will define a second bitmap called background. It will not be used yet, but will be needed in the next tutorial to allow for an eraser function!

 

local currentPos = lastPos = [0,0]

 

 

rcMenu CanvasMenu

(

This is a right-click menu definition.

RCMenu User-Interface Items

 

subMenu "File"

(

The first item in the menu will be called File. A typical program implements File, Edit and Help items in its main menu, we will defined these, too.

menuItem new_menu "New"

menuItem open_menu "Open..."

menuItem save_as "Save As..."

Inside the File menu, we will add some menu items to start a new drawing, open an existing one and saving the results to a file on disk.

separator file_menu_1

This is a separator which draws a horizontal line between the menu items.

menuItem quit_tool "Quit"  

This menu item will be used to quit the tool.

)

subMenu "Edit" ( )

We will leave the Edit menu empty for now...

subMenu "Help"

(

menuItem about_tool "About MicroPaint..." 

)

The Help menu will contain only the about item.

 

on new_menu picked do

(

This is the event handler of the File>New menu item. If the user selected it from the menu...

RCMenu Clauses

 

theBackgroundBitmap = theCanvasBitmap = bitmap bitmapX bitmapY color:MicroPaint_CanvasRollout.paperColor.color

...we will define a new bitmap using the new paper color (background color) added to the User Interface, and will assign to the painting canvas and the new background bitmap.

MicroPaint_CanvasRollout.theCanvas.bitmap = theCanvasBitmap

Then, we assign the result to the User Interface bitmap.

)

 

on open_menu picked do

(

This is the event handler of the File>Open menu item. If the user selected it from the menu...

theOpenBitmap= selectBitmap()

...we open the standard Bitmap picker dialog of 3dsmax. It provides options to preview all supported file formats.

if theOpenBitmap != undefined do

(

If the user picked a valid bitmap and did not cancel out...

copy theOpenBitmap theCanvasBitmap

...we copy the bitmap that was opened into the painting canvas...

copy theOpenBitmap theBackgroundBitmap

...and into the background bitmap. The copy method resizes the original to fit the size of the canvas!

close theOpenBitmap

Finally, we close the opened bitmap...

MicroPaint_CanvasRollout.theCanvas.bitmap = theCanvasBitmap

...and assign the painting canvas to the User Interface bitmap.

)

)

 

on save_as picked do

(

This is the event handler of the File>Save As... menu item. If the user selected it from the menu...

theSaveName = getSaveFileName types:"BMP (*.bmp)|*.bmp|Targa (*.tga)|*.tga|JPEG (*.jpg)|*.jpg"

...we open the standard file saving dialog of 3ds Max and provide a list of some file extensions. (You can add any supported file formats like RLA, RPF etc. to this list).

if theSaveName != undefined do

(

If the user specified a valid name and did not cancel out...

theCanvasBitmap.filename = theSaveName

...we set the file name of the painting canvas to the selected name...

save theCanvasBitmap

...and save the bitmap to disk.

)

)

on about_tool picked do messagebox "MicroPaint\nMAXScript Tutorial" title:"About..."

If the user picked the Help>About menu item, we open a message box with some text. You can add your own text to this dialog with version number etc....

 

on quit_tool picked do destroyDialog MicroPaint_CanvasRollout

If the user picked the File>Quit menu item, we destroy the dialog. In the future, we could add a prompt whether the current painting should be saved to disk...

)

 

 

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

To add the paper color (background color of new images), we have to increase the across: parameter to 5.

colorpicker paperColor height:16 modal:false color:white

This colorpicker adds the paper color (background color of new images). Note that the color pickers are modeless and can be kept open all the time. You can also drag and drop colors to copy colors between the Ink and Paper color pickers, or drag and drop from other areas of 3ds Max!

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 pos do

(

if isDrawing do drawStroke lastPos pos

lastPos = pos

)

)

createDialog MicroPaint_CanvasRollout (bitmapx+100) (bitmapy+30) menu:CanvasMenu

We add the RightClick menu defined in the beginning of the script to the dialog.

MicroPaint_CanvasRollout.theCanvas.bitmap = theBackgroundBitmap

After starting the tool, it is a good idea to clear the display, as the bitmap might be still displaying an older version from a previous painting session.

 

)

 

 

 

Result

 

MicroPaint_Step6.gif

 

Previous Tutorial:

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

Next Tutorial:

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

See also

"How To" Tutorials Index Page