Skip to content

Instantly share code, notes, and snippets.

@saulmaos
Created February 22, 2023 06:19
Show Gist options
  • Save saulmaos/8b902999498c58455277bd125cd77b22 to your computer and use it in GitHub Desktop.
Save saulmaos/8b902999498c58455277bd125cd77b22 to your computer and use it in GitHub Desktop.
Draw Homer with Compose
package com.recodigo.composeapp
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.drawscope.Stroke
import com.recodigo.composeapp.ui.theme.ComposeAppTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeAppTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
DrawHomer()
}
}
}
}
}
const val STROKE_WIDTH = 5f
@Composable
fun DrawHomer() {
Canvas(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFF1ECBEF))
) {
val squaresPerLine = 10
val canvasWidth = size.width // 1440
val squareSize = canvasWidth / squaresPerLine
val canvasHeight = size.height // 2788
val numberOfHorizontalLines = (canvasHeight / squareSize).toInt()
val canvasWidthHalf = canvasWidth / 2
val canvasWidthThird = canvasWidthHalf - 240f //canvasWidth / 3
val canvasWidthTwoThird = canvasWidthHalf + 240f//canvasWidth / 3 * 2
val canvasHeightHalf = canvasHeight / 2 + 50f
val canvasHeightThird = canvasHeightHalf - 464.6667f //canvasHeight / 3
val homerSkinColor = Color(0xFFffd90f)
val homerBeardColor = Color(0xFFbeae7d)
// draw background reference lines
drawBackgroundReferenceLines(
canvasWidthHalf,
canvasHeight,
canvasWidthThird,
canvasWidthTwoThird,
canvasHeightHalf,
canvasWidth,
canvasHeightThird
)
val beardBackgroundPath = getBeardBackgroundPath(
canvasWidthHalf,
canvasHeightThird,
canvasHeightHalf,
canvasWidthTwoThird
)
val noseBackgroundPath = getNoseBackgroundPath(canvasWidthHalf, canvasHeightThird)
val noseAndMouthBorderPath = getNoseAndMouthBorderPath(
noseBackgroundPath,
canvasWidthHalf,
canvasHeightThird,
canvasHeightHalf,
canvasWidthTwoThird
)
val skinBackgroundPath = getSkinBackgroundPath(
canvasWidthHalf,
canvasWidthTwoThird,
canvasHeightHalf,
canvasHeightThird
)
drawPath(
path = skinBackgroundPath,
color = homerSkinColor,
style = Fill
)
drawPath(
path = skinBackgroundPath,
Color.Black,
style = Stroke(STROKE_WIDTH)
)
val clotheBackgroundPath = getClotheBackgroundPath(
canvasWidthHalf,
canvasWidthThird,
canvasWidthTwoThird,
canvasHeightHalf,
canvasHeightThird
)
drawPath(
path = clotheBackgroundPath,
Color.White,
style = Fill
)
drawPath(
path = getClotheBorderPath(
clotheBackgroundPath,
canvasWidthHalf,
canvasWidthThird,
canvasWidthTwoThird,
canvasHeightHalf,
),
color = Color.Black,
style = Stroke(STROKE_WIDTH)
)
// right eye
drawMyCircle(canvasWidthHalf + 150f, canvasHeightThird, 100f)
drawCircle(Color.Black, radius = 10f, Offset(canvasWidthHalf + 150f, canvasHeightThird))
// left eye
drawMyCircle(canvasWidthHalf, canvasHeightThird, 100f)
drawCircle(Color.Black, radius = 10f, Offset(canvasWidthHalf, canvasHeightThird))
drawPath(
path = noseBackgroundPath,
color = homerSkinColor,
style = Fill
)
drawPath(
path = beardBackgroundPath,
color = homerBeardColor,
style = Fill
)
drawPath(
path = noseAndMouthBorderPath,
Color.Black,
style = Stroke(STROKE_WIDTH)
)
val earBackgroundPath = getEarBackgroundPath(
canvasWidthThird,
canvasHeightThird
)
drawPath(
path = earBackgroundPath,
color = homerSkinColor,
style = Fill
)
val earBorderPath = getEarBorderPath(
earBackgroundPath,
canvasWidthThird,
canvasHeightThird
)
drawPath(
path = earBorderPath,
Color.Black,
style = Stroke(STROKE_WIDTH)
)
drawPath(
path = getHairBorderPath(canvasWidthThird, canvasHeightThird, canvasWidthHalf),
Color.Black,
style = Stroke(STROKE_WIDTH)
)
drawGrid(squaresPerLine, squareSize, canvasHeight, numberOfHorizontalLines, canvasWidth)
}
}
private fun DrawScope.drawGrid(
squaresPerLine: Int,
squareSize: Float,
canvasHeight: Float,
numberOfHorizontalLines: Int,
canvasWidth: Float
) {
(1 until squaresPerLine).forEach {
val xPos = it * squareSize
drawLine(
start = Offset(xPos, 0f),
end = Offset(xPos, canvasHeight),
color = Color.Blue
)
}
(1..numberOfHorizontalLines).forEach {
val yPos = it * squareSize
drawLine(
start = Offset(0f, yPos),
end = Offset(canvasWidth, yPos),
color = Color.Blue
)
}
}
private fun getNoseAndMouthBorderPath(
path: Path,
canvasWidthHalf: Float,
canvasHeightThird: Float,
canvasHeightHalf: Float,
canvasWidthTwoThird: Float
) = path.apply {
quadraticBezierTo(
canvasWidthHalf - 80, canvasHeightThird + 140f,
canvasWidthHalf - 100, canvasHeightThird + 200f
)
quadraticBezierTo(
canvasWidthHalf - 130, canvasHeightThird + 280f,
canvasWidthHalf - 70, canvasHeightThird + 330f
)
quadraticBezierTo(
canvasWidthHalf + 10, canvasHeightHalf - 80,
canvasWidthHalf + 110f, canvasHeightHalf - 130f
)
quadraticBezierTo(
canvasWidthTwoThird - 100f, canvasHeightThird + 310f,
canvasWidthTwoThird - 100f, canvasHeightThird + 300f
)
quadraticBezierTo(
canvasWidthTwoThird - 50f, canvasHeightThird + 285f,
canvasWidthTwoThird - 50f, canvasHeightThird + 265f
)
moveTo(canvasWidthHalf + 190f, canvasHeightThird + 130)
quadraticBezierTo(
canvasWidthTwoThird - 10, canvasHeightThird + 150,
canvasWidthTwoThird - 10, canvasHeightThird + 220
)
cubicTo(
canvasWidthTwoThird + 50, canvasHeightThird + 270,
canvasWidthHalf + 40, canvasHeightThird + 290,
canvasWidthHalf - 50, canvasHeightThird + 250
)
moveTo(canvasWidthHalf - 30, canvasHeightThird + 235)
quadraticBezierTo(
canvasWidthHalf - 60, canvasHeightThird + 250,
canvasWidthHalf - 60, canvasHeightThird + 260
)
}
private fun getBeardBackgroundPath(
canvasWidthHalf: Float,
canvasHeightThird: Float,
canvasHeightHalf: Float,
canvasWidthTwoThird: Float
) = Path().apply {
arcTo(
rect = Rect(
canvasWidthHalf + 140f, canvasHeightThird + 70f,
canvasWidthHalf + 200f, canvasHeightThird + 140
),
startAngleDegrees = 20f,
sweepAngleDegrees = 40f,
forceMoveTo = false
)
lineTo(canvasWidthHalf + 70f, canvasHeightThird + 140f)
quadraticBezierTo(
canvasWidthHalf - 80, canvasHeightThird + 140f,
canvasWidthHalf - 100, canvasHeightThird + 200f
)
quadraticBezierTo(
canvasWidthHalf - 130, canvasHeightThird + 280f,
canvasWidthHalf - 70, canvasHeightThird + 330f
)
quadraticBezierTo(
canvasWidthHalf + 10, canvasHeightHalf - 80,
canvasWidthHalf + 110f, canvasHeightHalf - 130f
)
quadraticBezierTo(
canvasWidthTwoThird - 100f, canvasHeightThird + 310f,
canvasWidthTwoThird - 100f, canvasHeightThird + 300f
)
quadraticBezierTo(
canvasWidthTwoThird - 50f, canvasHeightThird + 285f,
canvasWidthTwoThird - 50f, canvasHeightThird + 265f
)
cubicTo(
canvasWidthTwoThird - 50, canvasHeightThird + 250,
canvasWidthTwoThird + 30, canvasHeightThird + 260,
canvasWidthTwoThird - 10, canvasHeightThird + 220
)
quadraticBezierTo(
canvasWidthTwoThird - 10, canvasHeightThird + 150,
canvasWidthHalf + 190f, canvasHeightThird + 130
)
close()
}
private fun getNoseBackgroundPath(canvasWidthHalf: Float, canvasHeightThird: Float) =
Path().apply {
moveTo(canvasWidthHalf + 70f, canvasHeightThird + 70f)
lineTo(canvasWidthHalf + 170f, canvasHeightThird + 70f)
arcTo(
rect = Rect(
canvasWidthHalf + 140f, canvasHeightThird + 70f,
canvasWidthHalf + 200f, canvasHeightThird + 140
),
startAngleDegrees = -90f,
sweepAngleDegrees = 180f,
forceMoveTo = false
)
lineTo(canvasWidthHalf + 70f, canvasHeightThird + 140f)
}
private fun getHairBorderPath(
canvasWidthThird: Float,
canvasHeightThird: Float,
canvasWidthHalf: Float
) = Path().apply {
moveTo(canvasWidthThird - 10, canvasHeightThird + 70)
lineTo(canvasWidthThird - 30, canvasHeightThird - 30)
lineTo(canvasWidthThird + 30, canvasHeightThird + 60)
lineTo(canvasWidthThird + 50, canvasHeightThird - 40)
lineTo(canvasWidthThird + 70, canvasHeightThird + 50)
arcTo(
rect = Rect(
canvasWidthHalf - 100,
canvasHeightThird - 415,
canvasWidthHalf + 30,
canvasHeightThird - 285
),
startAngleDegrees = 160f,
sweepAngleDegrees = 180f,
forceMoveTo = true
)
arcTo(
rect = Rect(
canvasWidthHalf - 140,
canvasHeightThird - 415,
canvasWidthHalf - 10,
canvasHeightThird - 285
),
startAngleDegrees = 160f,
sweepAngleDegrees = 180f,
forceMoveTo = true
)
}
private fun DrawScope.drawBackgroundReferenceLines(
canvasWidthHalf: Float,
canvasHeight: Float,
canvasWidthThird: Float,
canvasWidthTwoThird: Float,
canvasHeightHalf: Float,
canvasWidth: Float,
canvasHeightThird: Float
) {
// width - 1 third
drawLine(
start = Offset(canvasWidthThird, 0f),
end = Offset(canvasWidthThird, canvasHeight),
color = Color.Magenta,
strokeWidth = 4f
)
// width - Half
drawLine(
start = Offset(canvasWidthHalf, 0f),
end = Offset(canvasWidthHalf, canvasHeight),
color = Color.Magenta,
strokeWidth = 4f
)
//width - 2 thirds
drawLine(
start = Offset(canvasWidthTwoThird, 0f),
end = Offset(canvasWidthTwoThird, canvasHeight),
color = Color.Magenta,
strokeWidth = 4f
)
// height - half
drawLine(
start = Offset(0f, canvasHeightHalf),
end = Offset(canvasWidth, canvasHeightHalf),
color = Color.Blue,
strokeWidth = 4f
)
// height - 1 Third
drawLine(
start = Offset(0f, canvasHeightThird),
end = Offset(canvasWidth, canvasHeightThird),
color = Color.Blue,
strokeWidth = 4f
)
}
private fun getEarBorderPath(
path: Path,
canvasWidthThird: Float,
canvasHeightThird: Float
) = path.apply {
moveTo(canvasWidthThird + 20, canvasHeightThird + 120)
quadraticBezierTo(
canvasWidthThird + 50, canvasHeightThird + 110,
canvasWidthThird + 80, canvasHeightThird + 125
)
moveTo(canvasWidthThird + 60, canvasHeightThird + 115)
quadraticBezierTo(
canvasWidthThird + 20, canvasHeightThird + 125,
canvasWidthThird + 30, canvasHeightThird + 150
)
}
private fun getEarBackgroundPath(
canvasWidthThird: Float,
canvasHeightThird: Float
) = Path().apply {
arcTo(
rect = Rect(
canvasWidthThird,
canvasHeightThird + 80,
canvasWidthThird + 100,
canvasHeightThird + 180f
),
startAngleDegrees = 30f,
sweepAngleDegrees = 270f,
forceMoveTo = false
)
}
private fun getSkinBackgroundPath(
canvasWidthHalf: Float,
canvasWidthTwoThird: Float,
canvasHeightHalf: Float,
canvasHeightThird: Float
) = Path().apply {
moveTo(canvasWidthHalf + 150f + 50f, canvasHeightThird - 125f)
lineTo(canvasWidthHalf + 150f + 20f, canvasHeightThird - 220f)
cubicTo(
canvasWidthHalf + 120f, canvasHeightThird - 450f,
canvasWidthHalf - 265f, canvasHeightThird - 430f,
canvasWidthHalf - 250f, canvasHeightThird - 150f
)
lineTo(canvasWidthHalf - 205f, canvasHeightThird + 100)
quadraticBezierTo(
canvasWidthHalf - 190f, canvasHeightThird + 170,
canvasWidthHalf - 220f, canvasHeightThird + 350f
)
lineTo(canvasWidthHalf - 220f, canvasHeightHalf + 50f)
lineTo(canvasWidthHalf + 140f, canvasHeightHalf + 50f)
lineTo(canvasWidthHalf + 140f, canvasHeightHalf - 100f)
lineTo(canvasWidthHalf + 110f, canvasHeightHalf - 130f)
lineTo(canvasWidthHalf + 190f, canvasHeightThird + 140)
lineTo(canvasWidthHalf + 195f, canvasHeightThird + 130)
lineTo(canvasWidthHalf + 198f, canvasHeightThird + 125)
lineTo(canvasWidthTwoThird - 30, canvasHeightThird - 80)
quadraticBezierTo(
canvasWidthTwoThird - 20, canvasHeightThird - 130,
canvasWidthTwoThird - 60, canvasHeightThird - 130
)
close()
}
private fun getClotheBorderPath(
path: Path,
canvasWidthHalf: Float,
canvasWidthThird: Float,
canvasWidthTwoThird: Float,
canvasHeightHalf: Float,
) = path.apply {
arcTo(
rect = Rect(
canvasWidthThird - 200f,
canvasHeightHalf - 30f,
canvasWidthThird,
canvasHeightHalf + 160f
),
startAngleDegrees = 275f,
sweepAngleDegrees = 75f,
forceMoveTo = true
)
moveTo(canvasWidthThird - 75f, canvasHeightHalf - 50f)
quadraticBezierTo(
canvasWidthThird + 40, canvasHeightHalf + 20,
canvasWidthHalf - 80, canvasHeightHalf + 50
)
moveTo(canvasWidthHalf + 140f, canvasHeightHalf) // doblez
lineTo(canvasWidthHalf + 180f, canvasHeightHalf - 50) // doblez
moveTo(canvasWidthTwoThird - 50f, canvasHeightHalf + 20f)
lineTo(canvasWidthTwoThird - 80f, canvasHeightHalf - 20f)
}
private fun getClotheBackgroundPath(
canvasWidthHalf: Float,
canvasWidthThird: Float,
canvasWidthTwoThird: Float,
canvasHeightHalf: Float,
canvasHeightThird: Float
) = Path().apply {
moveTo(canvasWidthThird, canvasHeightThird + 300f)
quadraticBezierTo(
canvasWidthThird + 10f, canvasHeightHalf - 100f,
canvasWidthHalf, canvasHeightHalf - 50f
)
lineTo(canvasWidthHalf + 100f, canvasHeightHalf + 50f)
lineTo(canvasWidthHalf + 140f, canvasHeightHalf) // doblez
quadraticBezierTo(
canvasWidthHalf + 115f, canvasHeightHalf - 55f,
canvasWidthHalf + 110f, canvasHeightHalf - 50f
)
quadraticBezierTo(
canvasWidthHalf + 100f, canvasHeightHalf - 90f,
canvasWidthHalf + 110f, canvasHeightHalf - 130f // cuello derecha
)
lineTo(canvasWidthTwoThird - 40f, canvasHeightHalf - 80f)
lineTo(canvasWidthTwoThird, canvasHeightHalf + 30f)
lineTo(canvasWidthTwoThird - 50f, canvasHeightHalf + 20f)
lineTo(canvasWidthTwoThird - 30f, canvasHeightHalf + 50f)
lineTo(canvasWidthThird - 200f, canvasHeightHalf + 50f) // bottom
arcTo(
rect = Rect(
canvasWidthThird - 200f,
canvasHeightHalf - 30f,
canvasWidthThird,
canvasHeightHalf + 160f
),
startAngleDegrees = 190f,
sweepAngleDegrees = 90f,
forceMoveTo = false
)
lineTo(canvasWidthThird - 75f, canvasHeightHalf - 50f)
lineTo(canvasWidthThird - 95f, canvasHeightHalf - 65f)
quadraticBezierTo(
canvasWidthThird - 50f, canvasHeightThird + 230f,
canvasWidthThird + 33f, canvasHeightThird + 250f
)
lineTo(canvasWidthHalf - 218f, canvasHeightThird + 335f)
}
private fun DrawScope.drawMyCircle(xPos: Float, yPos: Float, radius: Float) {
drawCircle(
color = Color.White,
radius = radius,
center = Offset(xPos, yPos),
)
drawCircle(
color = Color.Black,
radius = radius,
center = Offset(xPos, yPos),
style = Stroke(STROKE_WIDTH)
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment