1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settingslib.qrcode
18 
19 import android.annotation.ColorInt
20 import android.graphics.Bitmap
21 import android.graphics.Color
22 import com.google.zxing.BarcodeFormat
23 import com.google.zxing.EncodeHintType
24 import com.google.zxing.MultiFormatWriter
25 import com.google.zxing.WriterException
26 import java.nio.charset.StandardCharsets
27 import java.util.EnumMap
28 
29 object QrCodeGenerator {
30     /**
31      * Generates a barcode image with [contents].
32      *
33      * @param contents The contents to encode in the barcode
34      * @param size     The preferred image size in pixels
35      * @param invert   Whether to invert the black/white pixels (e.g. for dark mode)
36      * @return Barcode bitmap
37      */
38     @JvmStatic
39     @Throws(WriterException::class, java.lang.IllegalArgumentException::class)
encodeQrCodenull40     fun encodeQrCode(contents: String, size: Int, invert: Boolean): Bitmap =
41         encodeQrCode(contents, size, DEFAULT_MARGIN, invert)
42 
43     private const val DEFAULT_MARGIN = -1
44 
45     /**
46      * Generates a barcode image with [contents].
47      *
48      * @param contents The contents to encode in the barcode
49      * @param size     The preferred image size in pixels
50      * @param margin   The margin around the actual barcode
51      * @param invert   Whether to invert the black/white pixels (e.g. for dark mode)
52      * @return Barcode bitmap
53      */
54     @JvmOverloads
55     @JvmStatic
56     @Throws(WriterException::class, IllegalArgumentException::class)
57     fun encodeQrCode(
58         contents: String,
59         size: Int,
60         margin: Int = DEFAULT_MARGIN,
61         invert: Boolean = false,
62     ): Bitmap {
63         val hints = EnumMap<EncodeHintType, Any>(EncodeHintType::class.java)
64         if (!isIso88591(contents)) {
65             hints[EncodeHintType.CHARACTER_SET] = StandardCharsets.UTF_8.name()
66         }
67         if (margin != DEFAULT_MARGIN) {
68             hints[EncodeHintType.MARGIN] = margin
69         }
70         val qrBits = MultiFormatWriter().encode(contents, BarcodeFormat.QR_CODE, size, size, hints)
71         @ColorInt val setColor = if (invert) Color.WHITE else Color.BLACK
72         @ColorInt val unsetColor = if (invert) Color.BLACK else Color.WHITE
73         @ColorInt val pixels = IntArray(size * size)
74         for (x in 0 until size) {
75             for (y in 0 until size) {
76                 pixels[x * size + y] = if (qrBits[x, y]) setColor else unsetColor
77             }
78         }
79         return Bitmap.createBitmap(size, size, Bitmap.Config.RGB_565).apply {
80             setPixels(pixels, 0, size, 0, 0, size, size)
81         }
82     }
83 
isIso88591null84     private fun isIso88591(contents: String): Boolean =
85         StandardCharsets.ISO_8859_1.newEncoder().canEncode(contents)
86 }
87