Blog Infos
Author
Published
Topics
Published
Topics

A few days ago, I came across this design on Pinterest, and it got me excited. There are many ways to build this, and the approach I took is a bit unusual, slightly complex, and not the most performant. But for a first version? It looks good.

Overview

 

We start by creating a Bitmap and drawing text onto it. Why a bitmap, you ask? Because a bitmap is essentially a 2D array of pixels, where each pixel holds RGBA values and that’s exactly what we need for this design: pixelated text.

Once we have the bitmap, we scale it down to match the number of dots we want to render using a fixed number of rows and columns. This gives us a smaller, low-res version of the original bitmap. More dots (i.e., higher pixel density) means a sharper, more accurate image.

From this scaled-down bitmap, we extract a dotMatrix a 2D array where each value represents the alpha (transparency) of that pixel. We then loop through this matrix, and for each pixel:

  • If it has visible content (alpha > 0), we display a colored dot.
  • If it’s fully transparent or black, we skip it or render it as empty.
Creating a Bitmap with Text

Since Jetpack Compose doesn’t support direct text rendering on bitmaps, we use the android.graphics.Canvas API to draw text manually. We create a mutable bitmap, attach it to a Canvas, and draw centered text using drawTextand we return this bitmap

@Composable
fun DottedText(
modifier: Modifier,
text: String,
textScale: Float
) {
BoxWithConstraints(modifier) {
//Create a Bitmap with text
val bitmap = remember(text, textScale) {
renderTextToBitmap(text, textScale, this.maxWidth.value, this.maxHeight.value)
}
//Create a Dot Matrix
val dotMatrix = remember(bitmap) { bitmapToDotMatrix(bitmap, 30, 30) }
//Render Dot Matrix
DotMatrixDisplay(
modifier = Modifier.fillMaxSize(),
dotMatrix = dotMatrix,
dotSize = 10.dp
)
}
}
fun renderTextToBitmap(
renderText: String,
renderTextScale: Float,
width: Float,
height: Float
): Bitmap {
val bitmap = createBitmap(width.toInt(), height.toInt(), Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
val textPaint = Paint().apply {
color = Color.WHITE
textSize = (Math.min(width, height) * renderTextScale)
isAntiAlias = true
textAlign = Paint.Align.CENTER
}
val x = width / 2f
val y = (height / 2f) - ((textPaint.descent() + textPaint.ascent()) / 2f)
canvas.drawText(renderText, x, y, textPaint)
return bitmap
}
Converting Bitmap to Dot Matrix

We scale the bitmap to a fixed size (rows × cols) to control the number of dots rendered on the screen. Think of it like converting a high-resolution image into a low-res version. We then iterate through each cell in the scaled-down bitmap and record the alpha value at each pixel to construct a 2D matrix. This matrix helps us decide which dots should be visible (non-zero alpha) and which should be skipped (zero alpha).

fun bitmapToDotMatrix(bitmap: Bitmap, rows: Int, cols: Int): List<List<Int>> {
//scale bitmap based on the number of rows and cols we want
val scaledBitmap = Bitmap.createScaledBitmap(bitmap, cols, rows, true)
val result = mutableListOf<List<Int>>()
for (y in 0 until rows) {
val row = mutableListOf<Int>()
for (x in 0 until cols) {
val pixel = scaledBitmap[x, y]
val red = Color.red(pixel)
val green = Color.green(pixel)
val blue = Color.blue(pixel)
val alpha = Color.alpha(pixel)
print(" $alpha ")
row.add(alpha)
}
println()
result.add(row)
}
return result
}
Displaying the Dot Matrix

We simply iterate through the matrix row by row, and for each cell. If the alpha value is greater than 0, we show a colored dot. Otherwise, we render it with the background color.

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

No results found.

Jobs

@Composable
fun DotMatrixDisplay(
modifier: Modifier = Modifier,
dotMatrix: List<List<Int>>,
dotSize: Dp
) {
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
dotMatrix.forEach { row ->
Row {
row.forEach { alpha ->
val targetColor = if (alpha > 0) {
MaterialTheme.colorScheme.primary
} else {
MaterialTheme.colorScheme.background
}
Box(
modifier = Modifier
.size(dotSize)
.padding(1.dp)
.background(
color = targetColor,
shape = CircleShape
)
)
}
}
}
}
}

That’s it! You can check out the GitHub repo below. I’ve also included another simple and intuitive approach to achieve the same effect, though it might need a few tweaks to match the exact output

This article was previously published on proandroiddev.com.

Menu