Skip to content

Instantly share code, notes, and snippets.

@dev-niiaddy
Last active April 4, 2024 16:02
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save dev-niiaddy/8f936062291e3d328c7d10bb644273d0 to your computer and use it in GitHub Desktop.
Save dev-niiaddy/8f936062291e3d328c7d10bb644273d0 to your computer and use it in GitHub Desktop.
A function to encode text as a QR code using BitmapPainter and zxing-core for use in jetpack compose with Image composable.
@Composable
fun rememberQrBitmapPainter(
content: String,
size: Dp = 150.dp,
padding: Dp = 0.dp
): BitmapPainter {
val density = LocalDensity.current
val sizePx = with(density) { size.roundToPx() }
val paddingPx = with(density) { padding.roundToPx() }
var bitmap by remember(content) {
mutableStateOf<Bitmap?>(null)
}
LaunchedEffect(bitmap) {
if (bitmap != null) return@LaunchedEffect
launch(Dispatchers.IO) {
val qrCodeWriter = QRCodeWriter()
val encodeHints = mutableMapOf<EncodeHintType, Any?>()
.apply {
this[EncodeHintType.MARGIN] = paddingPx
}
val bitmapMatrix = try {
qrCodeWriter.encode(
content, BarcodeFormat.QR_CODE,
sizePx, sizePx, encodeHints
)
} catch (ex: WriterException) {
null
}
val matrixWidth = bitmapMatrix?.width ?: sizePx
val matrixHeight = bitmapMatrix?.height ?: sizePx
val newBitmap = Bitmap.createBitmap(
bitmapMatrix?.width ?: sizePx,
bitmapMatrix?.height ?: sizePx,
Bitmap.Config.ARGB_8888,
)
for (x in 0 until matrixWidth) {
for (y in 0 until matrixHeight) {
val shouldColorPixel = bitmapMatrix?.get(x, y) ?: false
val pixelColor = if (shouldColorPixel) Color.BLACK else Color.WHITE
newBitmap.setPixel(x, y, pixelColor)
}
}
bitmap = newBitmap
}
}
return remember(bitmap) {
val currentBitmap = bitmap ?: Bitmap.createBitmap(
sizePx, sizePx,
Bitmap.Config.ARGB_8888,
).apply { eraseColor(Color.TRANSPARENT) }
BitmapPainter(currentBitmap.asImageBitmap())
}
}
@SherifMuSherif
Copy link

SherifMuSherif commented Jan 28, 2024

Hi, I wanted to start by saying thank you for sharing this QR code generation code. It's clear that a lot of effort and expertise went into it, and it's been very helpful. I've been using it in a LazyColumn and noticed some performance issues. After some investigation, I found that the bitmap generation could be optimized. Instead of setting pixels on the bitmap one at a time using setPixel, which can be quite slow, you could create an array of the pixel values and then use setPixels to set all the pixels at once. This change significantly improved the performance in my case. Here's the modified part of the code for your reference:

val pixels = IntArray(matrixWidth * matrixHeight)

for (x in 0 until matrixWidth) {
    for (y in 0 until matrixHeight) {
        val shouldColorPixel = bitmapMatrix?.get(x, y) ?: false
        val pixelColor = if (shouldColorPixel) Color.BLACK else Color.WHITE

        pixels[y * matrixWidth + x] = pixelColor
    }
}

newBitmap.setPixels(pixels, 0, matrixWidth, 0, 0, matrixWidth, matrixHeight)

I hope this suggestion might be useful to you or others who are using your code. Thanks again for your great work!

@ryanholden8
Copy link

Thanks for this gist! I forked to provide an Image Composable with circular indicator while the QR code renders.
It also includes the import statements and the performance enhancement from @SherifMuSherif.

https://gist.github.com/ryanholden8/6e921a4dc2a40bd40b3b5a15aaff4705

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment