Skip to content

Customization

Alexander Zhirkevich edited this page Sep 5, 2022 · 1 revision

You can easily implement your own shapes and coloring for QR Code in 2 ways: using math formulas or by drawing on canvas. Second way is usually slower and uses a lot of memory but provides more freedom.

For example:

  1. Using math formulas:
object Circle : QrPixelShape {
    override fun invoke(
        i: Int, j: Int, elementSize: Int, neighbors: Neighbors
    ): Boolean {
        val center = elementSize/2.0
        return sqrt((center-i).pow(2) + (center-j).pow(2)) < center
    }
}

val options = createQrOptions(1024, .3f) {
    shapes {
        darkPixel = Circle
    }
}
//It is not scannable. Don't create such colorful QR codes
object Pride : QrColor {
    override fun invoke(
        i: Int, j: Int, width : Int, height : Int
    ): Int {
        return when(6f * j/height){
            in 0f..1f -> Color.RED
            in 1f..2f-> Color(0xffffa500)
            in 2f..3f-> Color.YELLOW
            in 3f..4f-> Color(0xff00A300)
            in 4f..5f-> Color.BLUE
            else -> Color(0xff800080)
        }
    }
}

val options = createQrOptions(1024) {
    colors {
        ball = Pride
    }
}
  1. By drawing on canvas:
val options : QrOptions = createQrOptions(1024) {
    shapes {
        darkPixel = drawShape { canvas, drawPaint, erasePaint ->
          val cx = canvas.width/2f
          val cy = canvas.height/2f
          val radius = minOf(cx,cy)
          canvas.drawCircle(cx, cy, radius, drawPaint)
          canvas.drawCircle(cx, cy, radius*2/2.5f, erasePaint)
          canvas.drawCircle(cx, cy, radius/1.75f, drawPaint)
        }
    }
}

drawShape is a generic function that can be used only inside a shapes or logo scope and only to create properties of QrElementsShapes or QrLogoShape. Usage with other type-parameters will cause an exception.

‼️ NOTE: Created shape should not be used with other QrOptions with larger size! This can cause shape quality issues.

You can also implement QrCanvasShape and cast it so necessary shape:

object Ring : QrCanvasShape {
   override fun draw(
       canvas: Canvas, drawPaint: Paint, erasePaint: Paint
   ) {
       // ...
   }
}

val ring : QrPixelShape = Ring
    .toShapeModifier(elementSize = 48)
    .asPixelShape()
// or automatically determine size with DSL
val ringPixelOptions : QrOptions = createQrOptions(1024){
    shapes {
        darkPixel = drawShape(Ring::draw)
    }
}
object CanvasColor : QrCanvasColor {
    override fun draw(canvas: Canvas) = with(canvas) {
        withRotation(135f, width/2f, height/2f) {
            drawRect(-width / 2f, -height / 2f,
                1.5f * width, height / 2f,
                Paint().apply { color = Color.BLACK }
            )
            drawRect(-width / 2f, height / 2f, 
                1.5f * width.toFloat(), 1.5f * height.toFloat(),
                Paint().apply { color = Color.DKGRAY }
            )
        }
    }
}
   

Using draw function inside colors scope you can colorize your code elements as you want. It will be converted to a QrColor.

This is QrOptions of the code above:

val options =  createQrOptions(1024, .2f) {
    colors {
        dark = QrColor.RadialGradient(
            startColor = Color.GRAY,
            endColor = Color.BLACK
        )
        ball = draw(CanvasColor::draw)
        frame = draw {
            withRotation(
                180f, width / 2f,
                height / 2f, CanvasColor::draw
            )
        }
        symmetry = true
    }
    shapes {
        darkPixel = QrPixelShape.RoundCorners()
        frame = QrFrameShape.RoundCorners(
            .25f, outer = false, inner = false
        )
    }
}

‼️ NOTE: Created color should not be used with other QrOptions with larger size!

Clone this wiki locally