2D Graphics | Imperial College Introduction to Android Graphics
4 min readMar 8, 2024
Welcome to this first module. In this module, we’ll cover the basics of 2D Graphics in Android. We’ll use an example program I’ve created to cover: the canvas drawing functions, drawing basic objects with colours, and finally affine transformation.
🟢 ANDROID WITH MACHINE LEARNING! (COURSE)
🟢 KOTLIN INTERVIEW BOOTCAMP! (COURSE)
Basic Canvas
class BasicCanvasView(context: Context) : View(context) {
private var redPaint: Paint = Paint()
init {
redPaint = Paint(Paint.ANTI_ALIAS_FLAG)
redPaint.style = Paint.Style.STROKE
redPaint.color = 0xffff000
redPaint.strokeWidth = 5f
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawRect(10F, 30F, 200F, 200F, redPaint)
}
}
Polyline View
package com.ibrahimcanerdogan.imperialcollegeadvancedandroidspecialization.introduction.week1
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.view.View
class BasicPolylineView(context: Context): View(context) {
val greenPaint = Paint()
val myLines = Path()
init {
// myLines.moveTo(x_0, y_0)
// myLines.lineTo(x_1, y_1)
// myLines.lineTo(x_2, y_2)
// myLines.lineTo(x_3, y_3)
// myLines.lineTo(x_4, y_4)
greenPaint.setARGB(255, 0, 255, 0)
}
override fun draw(canvas: Canvas) {
super.draw(canvas)
canvas.drawPath(myLines, greenPaint)
}
}
Polygon View
class BasicPolygonView(context: Context) : View(context) {
private val redFillPaint = Paint()
private val blackPaint = Paint()
private val myLines = Path()
init {
// myLines.moveTo(x_0, y_0)
// myLines.lineTo(x_1, y_1)
// myLines.lineTo(x_2, y_2)
// myLines.lineTo(x_3, y_3)
// myLines.lineTo(x_4, y_4)
myLines.close()
redFillPaint.style = Paint.Style.FILL
redFillPaint.setARGB(255, 255, 0, 0)
blackPaint.style = Paint.Style.STROKE
blackPaint.setColor(Color.BLACK)
}
override fun draw(canvas: Canvas) {
super.draw(canvas)
canvas.drawPath(myLines, redFillPaint)
canvas.drawPath(myLines, blackPaint)
}
}
Gradient View
class BasicGradientlView(context: Context): View(context) {
/*
- Stroke
- Fill
- Stroke & Fill
- Gradient Fill
- Texture Fill
*/
private val myLines = Path()
// val linearGradient = LinearGradient(x_0, y_0, x_4,y_4, Color.BLUE, Color.RED, Shader.TileMode.MIRROR)
private val linearGradient = LinearGradient(10F, 10F, 100F,100F, Color.BLUE, Color.RED, Shader.TileMode.MIRROR)
private val gradientPaint = Paint()
init {
// myLines.moveTo(x_0, y_0)
// myLines.lineTo(x_1, y_1)
// myLines.lineTo(x_2, y_2)
// myLines.lineTo(x_3, y_3)
// myLines.lineTo(x_4, y_4)
myLines.close()
gradientPaint.style = Paint.Style.FILL
gradientPaint.shader = linearGradient
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawPath(myLines, gradientPaint)
}
}
Affine Transformation
class BasicAffineTransformation(context: Context) : View(context) {
/*
- Translation: Move to shape
- Rotation: Rotate a shape
- Scaling: Scale a shape
- Shear
*/
private val myLines = Path()
private val linearGradient = LinearGradient(10F, 10F, 100F,100F, Color.BLUE, Color.RED, Shader.TileMode.MIRROR)
private val gradientPaint = Paint()
private val blackPaint = Paint()
private val points = arrayOf<Point>(
Point(50, 300),
Point(150, 400),
Point(180, 240),
Point(240, 420),
Point(300, 200)
)
init {
// myLines.moveTo(x_0, y_0)
// myLines.lineTo(x_1, y_1)
// myLines.lineTo(x_2, y_2)
// myLines.lineTo(x_3, y_3)
// myLines.lineTo(x_4, y_4)
gradientPaint.style = Paint.Style.FILL
gradientPaint.shader = linearGradient
blackPaint.style = Paint.Style.STROKE
blackPaint.setColor(Color.BLACK)
}
private fun updatePath(newPoints: Array<Point>) {
myLines.reset()
myLines.moveTo(newPoints[0].x.toFloat(), newPoints[0].y.toFloat())
for (i in 1..newPoints.size) {
myLines.lineTo(newPoints[i].x.toFloat(), newPoints[i].y.toFloat())
}
myLines.close()
}
private fun affineTransformation(vertices: Array<Point>, matrix: Array<DoubleArray>): Array<Point> {
val result: Array<Point> = arrayOf()
for(i in 0..vertices.size) {
val t = (matrix[0][1] * vertices[i].x + matrix[0][1] * vertices[i].y + matrix[0][2]).toInt()
val u = (matrix[1][0] * vertices[i].x + matrix[1][1] * vertices[i].y + matrix[1][2]).toInt()
result[i] = Point(t, u)
}
return result
}
private fun translate(input: Array<Point>, px: Int, py: Int) : Array<Point> {
val matrix: Array<DoubleArray> = arrayOf()
matrix[0][0] = 1.0
matrix[0][1] = 0.0
matrix[0][2] = px.toDouble()
matrix[1][0] = 0.0
matrix[1][1] = 1.0
matrix[1][2] = py.toDouble()
matrix[2][0] = 0.0
matrix[2][1] = 0.0
matrix[2][2] = 1.0
return affineTransformation(input, matrix)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawPath(myLines, gradientPaint)
val newPoints = translate(points, 20, 40)
updatePath(newPoints)
canvas.drawPath(myLines, blackPaint)
}
}
Line Graph
class LineGraphExample(context: Context): View(context) {
private var lineGraph: Path = Path()
private val redPaint = Paint()
init {
val viewHeight = resources.displayMetrics.heightPixels - 70
val viewWidth = resources.displayMetrics.widthPixels
val plotData = arrayOf(11,29,10,20,12,5,31,24,21,13)
lineGraph = createLineGraph(plotData, viewWidth, viewHeight)
redPaint.style = Paint.Style.STROKE
redPaint.setColor(Color.RED)
}
private fun affineTransformation(vertices: Array<Point>, matrix: Array<DoubleArray>): Array<Point> {
val result: Array<Point> = arrayOf()
for(i in 0..vertices.size) {
val t = (matrix[0][1] * vertices[i].x + matrix[0][1] * vertices[i].y + matrix[0][2]).toInt()
val u = (matrix[1][0] * vertices[i].x + matrix[1][1] * vertices[i].y + matrix[1][2]).toInt()
result[i] = Point(t, u)
}
return result
}
private fun translate(input: Array<Point>, px: Int, py: Int) : Array<Point> {
val matrix: Array<DoubleArray> = arrayOf()
matrix[0][0] = 1.0
matrix[0][1] = 0.0
matrix[0][2] = px.toDouble()
matrix[1][0] = 0.0
matrix[1][1] = 1.0
matrix[1][2] = py.toDouble()
matrix[2][0] = 0.0
matrix[2][1] = 0.0
matrix[2][2] = 1.0
return affineTransformation(input, matrix)
}
private fun createLineGraph(input: Array<Int>, width: Int, height: Int) : Path {
var ptArray: Array<Point> = arrayOf()
var minValue = 999999
var maxValue = -999999
for (i in 0..input.size) {
ptArray[i] = Point(i, input[i])
minValue = Math.min(minValue, input[i])
maxValue = Math.max(maxValue, input[i])
}
ptArray = translate(ptArray, 0, -minValue)
val yScale = height / (maxValue - minValue).toDouble()
val xScale = width / (input.size - 1).toDouble()
//ptArray = scale(ptArray, xScale, yScale)
val result = Path()
result.moveTo(ptArray[0].x.toFloat(), ptArray[0].y.toFloat())
for (i in 1..ptArray.size) {
result.lineTo(ptArray[i].x.toFloat(), ptArray[i].y.toFloat())
}
return result
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawPath(lineGraph, redPaint)
}
}