From 47af32aec2a87cb51df423140219e6c4a0a35598 Mon Sep 17 00:00:00 2001 From: RTAkland Date: Wed, 24 Dec 2025 22:15:47 +0800 Subject: [PATCH] Upload --- build.gradle.kts | 2 + .../kotlin/cn/rtast/kqrcode/QRMatrix.kt | 29 +++++++++++ .../kotlin/cn/rtast/kqrcode/const.kt | 20 ++++++++ .../kotlin/cn/rtast/kqrcode/eclevel.kt | 12 +++++ .../kotlin/cn/rtast/kqrcode/encode.kt | 37 ++++++++++++++ .../kotlin/cn/rtast/kqrcode/galoisfield.kt | 51 +++++++++++++++++++ .../kotlin/cn/rtast/kqrcode/poly.kt | 25 +++++++++ .../kotlin/cn/rtast/kqrcode/reedsolomon.kt | 25 +++++++++ .../kotlin/cn/rtast/kqrcode/version.kt | 15 ++++++ src/commonTest/kotlin/test/TestAnchor.kt | 20 ++++++++ 10 files changed, 236 insertions(+) create mode 100644 src/commonMain/kotlin/cn/rtast/kqrcode/QRMatrix.kt create mode 100644 src/commonMain/kotlin/cn/rtast/kqrcode/const.kt create mode 100644 src/commonMain/kotlin/cn/rtast/kqrcode/eclevel.kt create mode 100644 src/commonMain/kotlin/cn/rtast/kqrcode/encode.kt create mode 100644 src/commonMain/kotlin/cn/rtast/kqrcode/galoisfield.kt create mode 100644 src/commonMain/kotlin/cn/rtast/kqrcode/poly.kt create mode 100644 src/commonMain/kotlin/cn/rtast/kqrcode/reedsolomon.kt create mode 100644 src/commonMain/kotlin/cn/rtast/kqrcode/version.kt create mode 100644 src/commonTest/kotlin/test/TestAnchor.kt diff --git a/build.gradle.kts b/build.gradle.kts index e803284..17e5cda 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,6 +23,8 @@ kotlin { } jvm { compilerOptions.jvmTarget = JvmTarget.JVM_1_8 } + explicitApi() + sourceSets { commonMain.dependencies { diff --git a/src/commonMain/kotlin/cn/rtast/kqrcode/QRMatrix.kt b/src/commonMain/kotlin/cn/rtast/kqrcode/QRMatrix.kt new file mode 100644 index 0000000..0b4aa36 --- /dev/null +++ b/src/commonMain/kotlin/cn/rtast/kqrcode/QRMatrix.kt @@ -0,0 +1,29 @@ +/* + * Copyright © 2025 RTAkland + * Author: RTAkland + * Date: 2025/12/24 + */ + + +package cn.rtast.kqrcode + +public class QRMatrix( + public val version: Int, +) { + private val size = 21 + (version - 1) * 4 + private val finderTL = 0 to 0 + private val finderTR = 0 to size - 7 + private val finderBL = size - 7 to 0 + + private fun drawTimingPatterns(matrix: Array) { + val size = matrix.size + for (col in 8 until size - 8) { + matrix[6][col] = if (col % 2 == 0) 1 else 0 + } + for (row in 8 until size - 8) { + matrix[row][6] = if (row % 2 == 0) 1 else 0 + } + } + public fun generate() { + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/cn/rtast/kqrcode/const.kt b/src/commonMain/kotlin/cn/rtast/kqrcode/const.kt new file mode 100644 index 0000000..dff8b4d --- /dev/null +++ b/src/commonMain/kotlin/cn/rtast/kqrcode/const.kt @@ -0,0 +1,20 @@ +/* + * Copyright © 2025 RTAkland + * Author: RTAkland + * Date: 2025/12/24 + */ + + +package cn.rtast.kqrcode + +internal val FINDER_MATRIX = arrayOf( + Array(9) { 0 }, + arrayOf(0, 1, 1, 1, 1, 1, 1, 1, 0), + arrayOf(0, 1, 0, 0, 0, 0, 0, 1, 0), + arrayOf(0, 1, 0, 1, 1, 1, 0, 1, 0), + arrayOf(0, 1, 0, 1, 1, 1, 0, 1, 0), + arrayOf(0, 1, 0, 1, 1, 1, 0, 1, 0), + arrayOf(0, 1, 0, 0, 0, 0, 0, 1, 0), + arrayOf(0, 1, 1, 1, 1, 1, 1, 1, 0), + Array(9) { 0 }, +) \ No newline at end of file diff --git a/src/commonMain/kotlin/cn/rtast/kqrcode/eclevel.kt b/src/commonMain/kotlin/cn/rtast/kqrcode/eclevel.kt new file mode 100644 index 0000000..ba31cb1 --- /dev/null +++ b/src/commonMain/kotlin/cn/rtast/kqrcode/eclevel.kt @@ -0,0 +1,12 @@ +/* + * Copyright © 2025 RTAkland + * Author: RTAkland + * Date: 2025/12/24 + */ + + +package cn.rtast.kqrcode + +public enum class ECLevel(public val ecPercent: Int) { + L(7), M(15), Q(25), H(30) +} \ No newline at end of file diff --git a/src/commonMain/kotlin/cn/rtast/kqrcode/encode.kt b/src/commonMain/kotlin/cn/rtast/kqrcode/encode.kt new file mode 100644 index 0000000..8304284 --- /dev/null +++ b/src/commonMain/kotlin/cn/rtast/kqrcode/encode.kt @@ -0,0 +1,37 @@ +/* + * Copyright © 2025 RTAkland + * Author: RTAkland + * Date: 2025/12/24 + */ + + +package cn.rtast.kqrcode + +internal object QRCodeEncoder { + + const val NumericModeIndicator = 0b0100 + + fun Int.toBits(bitCount: Int): List { + require(this >= 0) + val bits = ArrayList(bitCount) + for (i in bitCount - 1 downTo 0) bits.add((this shr i) and 1) + return bits + } + + fun encodeNumeric(s: String): IntArray { + val bits = mutableListOf() + var i = 0 + while (i < s.length) { + val chunk = minOf(3, s.length - i) + val num = s.substring(i, i + chunk).toInt() + val bitLen = when (chunk) { + 3 -> 10 + 2 -> 7 + else -> 4 + } + bits.addAll(num.toBits(bitLen)) + i += chunk + } + return bits.toIntArray() + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/cn/rtast/kqrcode/galoisfield.kt b/src/commonMain/kotlin/cn/rtast/kqrcode/galoisfield.kt new file mode 100644 index 0000000..77f08aa --- /dev/null +++ b/src/commonMain/kotlin/cn/rtast/kqrcode/galoisfield.kt @@ -0,0 +1,51 @@ +/* + * Copyright © 2025 RTAkland + * Author: RTAkland + * Date: 2025/12/24 + */ + + +package cn.rtast.kqrcode + + +/** + * https://en.wikipedia.org/wiki/Finite_field + */ + + +internal fun gfAdd(a: Int, b: Int) = a xor b + +/** + * eq 2 add + */ +internal fun gfMin(a: Int, b: Int) = gfAdd(a, b) + +internal fun gfMul(a: Int, b: Int): Int { + if (a == 0 || b == 0) return 0 + return EXP[LOG[a] + LOG[b]] +} + +internal fun gfDiv(a: Int, b: Int): Int { + require(b != 0) + if (a == 0) return 0 + return EXP[(LOG[a] + 255 - LOG[b]) % 255] +} + +internal val EXP = IntArray(512) +internal val LOG = IntArray(256) + +/** + * init galois field matrix + */ +internal fun initGF() { + var x = 1 + for (i in 0 until 255) { + EXP[i] = x + LOG[x] = i + x = x shl 1 + if (x and 0x100 != 0) x = x xor 0x11D + } + for (i in 255 until 512) { + EXP[i] = EXP[i - 255] + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/cn/rtast/kqrcode/poly.kt b/src/commonMain/kotlin/cn/rtast/kqrcode/poly.kt new file mode 100644 index 0000000..e173f1d --- /dev/null +++ b/src/commonMain/kotlin/cn/rtast/kqrcode/poly.kt @@ -0,0 +1,25 @@ +/* + * Copyright © 2025 RTAkland + * Author: RTAkland + * Date: 2025/12/24 + */ + + +package cn.rtast.kqrcode + +internal typealias Poly = IntArray + +internal fun polyMul(a: Poly, b: Poly): Poly { + val result = IntArray(a.size + b.size - 1) + a.indices.forEach { i -> b.forEach { j -> result[i + j] = result[i + j] xor gfMul(a[i], b[j]) } } + return result +} + +internal fun polyGenerator(ecLevel: ECLevel): Poly { + var r = intArrayOf(1) + for (i in 0 until ecLevel.ecPercent) { + val term = intArrayOf(1, EXP[i]) + r = polyMul(r, term) + } + return r +} \ No newline at end of file diff --git a/src/commonMain/kotlin/cn/rtast/kqrcode/reedsolomon.kt b/src/commonMain/kotlin/cn/rtast/kqrcode/reedsolomon.kt new file mode 100644 index 0000000..8e2c095 --- /dev/null +++ b/src/commonMain/kotlin/cn/rtast/kqrcode/reedsolomon.kt @@ -0,0 +1,25 @@ +/* + * Copyright © 2025 RTAkland + * Author: RTAkland + * Date: 2025/12/24 + */ + + +package cn.rtast.kqrcode + +/** + * https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction + */ +internal fun reedSolomonEncode(data: IntArray, ecLevel: ECLevel): IntArray { + val generator = polyGenerator(ecLevel) + val buffer = IntArray(data.size + ecLevel.ecPercent) + for (i in data.indices) buffer[i] = data[i] + for (i in data.indices) { + val factor = buffer[i] + if (factor != 0) for (j in generator.indices) buffer[i + j] = buffer[i + j] xor gfMul(generator[j], factor) + } + /** + * copy ec code part + */ + return buffer.copyOfRange(data.size, buffer.size) +} \ No newline at end of file diff --git a/src/commonMain/kotlin/cn/rtast/kqrcode/version.kt b/src/commonMain/kotlin/cn/rtast/kqrcode/version.kt new file mode 100644 index 0000000..22e3bf9 --- /dev/null +++ b/src/commonMain/kotlin/cn/rtast/kqrcode/version.kt @@ -0,0 +1,15 @@ +/* + * Copyright © 2025 RTAkland + * Author: RTAkland + * Date: 2025/12/24 + */ + + +package cn.rtast.kqrcode + +public value class QRCodeVersion(public val size: Int) + +public fun Int.asQRCodeVersion(): QRCodeVersion { + require(this in 1..40) { throw IllegalArgumentException("QRCode version must in range 1(included)..40(included)") } + return QRCodeVersion(21 + (this - 1) * 4) +} \ No newline at end of file diff --git a/src/commonTest/kotlin/test/TestAnchor.kt b/src/commonTest/kotlin/test/TestAnchor.kt new file mode 100644 index 0000000..70131fe --- /dev/null +++ b/src/commonTest/kotlin/test/TestAnchor.kt @@ -0,0 +1,20 @@ +/* + * Copyright © 2025 RTAkland + * Author: RTAkland + * Date: 2025/12/24 + */ + + +package test + +import cn.rtast.kqrcode.FINDER_MATRIX +import kotlin.test.Test + +class TestAnchor { + + @Test + fun `test anchor println`() { + println(FINDER_MATRIX.map { it.toList().apply { println(it.size) } }) + println(FINDER_MATRIX.size) + } +} \ No newline at end of file