taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

QrLuminanceTest.kt (3203B)


      1 /*
      2  * This file is part of GNU Taler
      3  * (C) 2026 Taler Systems S.A.
      4  *
      5  * GNU Taler is free software; you can redistribute it and/or modify it under the
      6  * terms of the GNU General Public License as published by the Free Software
      7  * Foundation; either version 3, or (at your option) any later version.
      8  *
      9  * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
     10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11  * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License along with
     14  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15  */
     16 
     17 package net.taler.merchantpos.config
     18 
     19 import com.google.zxing.BarcodeFormat
     20 import com.google.zxing.BinaryBitmap
     21 import com.google.zxing.MultiFormatReader
     22 import com.google.zxing.common.HybridBinarizer
     23 import com.google.zxing.qrcode.QRCodeWriter
     24 
     25 import org.junit.Assert.assertArrayEquals
     26 import org.junit.Assert.assertEquals
     27 import org.junit.Assert.assertThrows
     28 import org.junit.Test
     29 
     30 import java.nio.ByteBuffer
     31 
     32 class QrLuminanceTest
     33 {
     34     @Test
     35     fun `padded interleaved camera frames decode at every rotation without consuming the buffer`()
     36     {
     37         val payload = "taler-pos://example.invalid:8443/instances/cafe#test-only"
     38         val qr = QRCodeWriter().encode(payload, BarcodeFormat.QR_CODE, 191, 173)
     39         for (pixelStride in listOf(1, 2))
     40         {
     41             val rowStride = qr.width * pixelStride + 11
     42             val offset = 7
     43             val buffer = ByteBuffer.allocate(offset + rowStride * qr.height)
     44             for (y in 0 until qr.height)
     45             {
     46                 for (x in 0 until qr.width)
     47                 {
     48                     buffer.put(offset + y * rowStride + x * pixelStride, if (qr[x, y]) 0 else 255.toByte())
     49                 }
     50             }
     51             buffer.position(offset)
     52             for (rotation in listOf(0, 90, 180, 270))
     53             {
     54                 val source = qrLuminance(buffer, qr.width, qr.height, rowStride, pixelStride, rotation)
     55                 val decoded = MultiFormatReader().decode(BinaryBitmap(HybridBinarizer(source)))
     56                 assertEquals(payload, decoded.text)
     57                 assertEquals(offset, buffer.position())
     58             }
     59         }
     60     }
     61 
     62     @Test
     63     fun `rotation preserves exact rectangular pixel geometry`()
     64     {
     65         val buffer = ByteBuffer.wrap(byteArrayOf(1, 2, 3, 4, 5, 6))
     66         val expected =
     67             mapOf(
     68             0 to byteArrayOf(1, 2, 3, 4, 5, 6),
     69             90 to byteArrayOf(4, 1, 5, 2, 6, 3),
     70             180 to byteArrayOf(6, 5, 4, 3, 2, 1),
     71             270 to byteArrayOf(3, 6, 2, 5, 1, 4),
     72         )
     73         expected.forEach { (rotation, pixels) ->
     74             val source = qrLuminance(buffer, 3, 2, 3, 1, rotation)
     75             assertArrayEquals(pixels, source.matrix)
     76             assertEquals(if (rotation % 180 == 0) 3 else 2, source.width)
     77         }
     78     }
     79 
     80     @Test
     81     fun `truncated camera plane is rejected`()
     82     {
     83         assertThrows(IllegalArgumentException::class.java) {
     84             qrLuminance(ByteBuffer.allocate(5), 3, 2, 3, 1, 0)
     85         }
     86     }
     87 }