The CS109UI moduleProgramming Practice TutorialsImage processingDrawing images

Drawing images

To draw something, you first need a canvas to draw on. We will use java.awt.image.BufferedImage, a bitmapped image that you create by providing the desired width and height in pixels.

Then we use a small package I've written to draw to this image. (You could of course use standard java.awt functions for doing this. But the java.awt interface is somewhat old-fashioned, and using the cs109ui package has the advantage that you can later reuse the exact same drawing code in Javascript and on Android.)

When you are done drawing, you can save the image to a file using the javax.imageio.ImageIO.write function.

Here is a complete example that shows some drawing functions (drawing.kts):

import java.awt.image.BufferedImage
import org.otfried.cs109ui.ImageCanvas
import org.otfried.cs109.Color
import org.otfried.cs109.DrawStyle

// Size of image
val w = 500
val h = 500

// create an image
val image = BufferedImage(w, h, BufferedImage.TYPE_INT_RGB)

// get ImageCanvas for the image
val g = ImageCanvas(image)

// clear background
g.clear(Color.WHITE)

// draw two filled circles
g.setColor(Color.RED)
g.drawCircle(50.0, 50.0, 20.0)  // FILL is the default
g.setColor(Color.ORANGE)
g.drawCircle(250.0, 400.0, 20.0)

// draw an unfilled circle with a pen of width 3
g.setColor(Color.MAGENTA)
g.setLineWidth(3.0)
g.drawCircle(415.0, 50.0, 15.0, DrawStyle.STROKE)
  
// draw a filled and an unfilled Rectangle
g.setColor(Color.CYAN)
g.drawRectangle(20.0, 400.0, 50.0, 20.0, DrawStyle.FILL)
g.drawRectangle(400.0, 400.0, 50.0, 20.0, DrawStyle.STROKE)

// draw a line
g.setLineWidth(1.0)   // reset to default
g.setColor(Color(0, 0, 255)) // same as Color.BLUE
g.beginShape()
g.moveTo(50.0, 50.0)
g.lineTo(250.0, 400.0)
g.drawShape(DrawStyle.STROKE)

// draw a non-convex quadrilateral:
g.save()              // save current coordinate system
g.translate(360.0, 260.0) // move origin to here
g.rotate(-30.0)           // rotate 30 degrees counter-clockwise
g.beginShape()
g.moveTo(0.0, 0.0)
g.lineTo(30.0, -40.0)
g.lineTo(60.0, 0.0)
g.lineTo(30.0, -100.0)
g.closePath()
g.drawShape()
g.restore()           // restore current coordinate system

// draw some text
g.setColor(Color(0, 128, 0)) // a darker green
g.setFont(20.0, "Batang")
g.drawText("Hello World!", 155.0, 225.0)
g.drawText("안녕 하세요", 175.0, 245.0)

// done with drawing
g.done()

// write image to a file
javax.imageio.ImageIO.write(image, "png", java.io.File("drawing.png"))

The generated drawing looks like this:

The resulting drawing

Colors

Colors are specified using an org.otfried.cs109.Color object. There are some predefined colors:

To create another color, provide its red, green, and blue components, as integers between 0 and 255:
val color = Color(0, 200, 0)  // slightly darker green
You can also construct a color from a 24-bit integer:
val color = Color(0x00c800)  // slightly darker green

Drawing style

Every shape can be drawn in two ways: either as a filled shape, or by drawing with a "pen" along the outline of the shape (or both).

You select the drawing style with one of the constants

Text font and alignment

To draw text, you need to set a font on the ImageCanvas using setFont. You can just provide a point size like this:

  canvas.setFont(16.0)
or set a different typeface by using one of the standard names "Monospaced", "Serif", or "SansSerif", or the name of a real font installed on your system:
  canvas.setFont(16.0, "Serif")

When calling drawText, you have the choice to align the text at its left edge, the right edge, or its horizontal center. The alignment is selected with the constants

ImageCanvas

The org.otfried.cs109ui.ImageCanvas object does all the drawing. You can use its width and height properties to inquire the size of the canvas (but of course you can also check the original BufferedImage).

The basic methods of ImageCanvas are:

More complicated shapes

To draw shapes other than rectangles and circles, you need to use the shape methods of ImageCanvas:

You describe the shape by walking along the outline. First, call beginShape(), then moveTo(x, y) to go to the starting point of the outline. Now make a sequence of lineTo(x, y) calls to describe the boundary of the shape. You can complete the outline by calling closePath(), this returns to the starting point (which you set when you called moveTo(x, y)). For filled shapes you normally want to call closePath, for outlined (stroked) shapes it depends if you want the shape to be a closed curve or not.

Finally, call drawShape to draw the shape.

Transformations

You can modify the coordinate system to make it easier to specify your objects:

Often you only want to modify the coordinate system temporarily, and return to the original coordinate system when you are done with some part of the drawing (see the quadrilateral in the code above). This is achieved by calling save() before changing the coordinate system, and restore() when you want to return to the previous settings.

Transparency

Normally, shapes that are drawn later cover shapes that have been drawn before (the so-called "painter's model" of computer graphics).

However, you can call setAlpha(alpha) to set a transparency for future drawing operations. When alpha == 255, drawing is opaque and completely covers what was drawn before. When alpha == 128, you are drawing with 50% transparency: The result is a blend of half the original color and half the newly drawn color.

Measuring text

The method textWidth(text: String) returns the width of the text if you were to draw it now with the current transformation and font settings.

The CS109UI moduleProgramming Practice TutorialsImage processingDrawing images