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 }