Skip to content

Instantly share code, notes, and snippets.

@Radiokot
Last active September 29, 2023 10:19
Show Gist options
  • Save Radiokot/f83f6d09ae48bf253a433dcb5e02765d to your computer and use it in GitHub Desktop.
Save Radiokot/f83f6d09ae48bf253a433dcb5e02765d to your computer and use it in GitHub Desktop.
A scalable ZXing QR code drawable, which supports tint and transparency. Unlike a 515x515 bitmap commonly used in Android QR guides, the drawable is always sharp.
import android.content.res.ColorStateList
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
import android.graphics.Paint
import android.graphics.PixelFormat
import android.graphics.RectF
import android.graphics.drawable.Drawable
import androidx.annotation.ColorInt
import com.google.zxing.common.BitMatrix
class BitMatrixDrawable(
private val bitMatrix: BitMatrix,
@ColorInt
squareColor: Int = Color.BLACK,
) : Drawable() {
private val defaultSquareColor = squareColor
private val squarePaint = Paint().apply {
color = squareColor
// Stroke is to compensate for float rounding.
style = Paint.Style.FILL_AND_STROKE
strokeWidth = 1f
}
private var squareWidth: Float = 1f
private var squareHeight: Float = 1f
override fun setBounds(left: Int, top: Int, right: Int, bottom: Int) {
super.setBounds(left, top, right, bottom)
squareWidth = (right - left).toFloat() / bitMatrix.width
squareHeight = (bottom - top).toFloat() / bitMatrix.height
}
override fun draw(canvas: Canvas) {
(0 until bitMatrix.width).forEach { x ->
(0 until bitMatrix.height).forEach { y ->
if (bitMatrix[x, y]) {
val left = x * squareWidth
val top = y * squareHeight
canvas.drawRect(
RectF(
left,
top,
left + squareWidth,
top + squareHeight
),
squarePaint
)
}
}
}
}
override fun setAlpha(alpha: Int) {
squarePaint.alpha = alpha
}
override fun setColorFilter(colorFilter: ColorFilter?) {
squarePaint.colorFilter = colorFilter
}
override fun setTintList(tint: ColorStateList?) {
if (tint != null) {
squarePaint.color = tint.defaultColor
} else {
squarePaint.color = defaultSquareColor
}
invalidateSelf()
}
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
}
import android.graphics.Color
import androidx.annotation.ColorInt
import androidx.core.graphics.drawable.TintAwareDrawable
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
class QrDrawableFactory {
private val writer = QRCodeWriter()
suspend fun getDrawable(
content: String,
@ColorInt
squareColor: Int = Color.BLACK,
errorCorrectionLevel: ErrorCorrectionLevel = ErrorCorrectionLevel.M,
margin: Int = 0,
) = writer.encode(
content,
BarcodeFormat.QR_CODE,
// 1x1 matrix yields the minimum size matrix for the data size.
1, 1,
mapOf(
EncodeHintType.ERROR_CORRECTION to errorCorrectionLevel,
EncodeHintType.MARGIN to margin,
)
).let { bitMatrix ->
checkNotNull(bitMatrix)
BitMatrixDrawable(
bitMatrix = bitMatrix,
squareColor = squareColor,
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment