parent
5e1f106d1b
commit
d7a7f35bd7
@ -0,0 +1,13 @@ |
|||||||
|
package io.legado.app.ui.qrcode |
||||||
|
|
||||||
|
import com.google.zxing.Result |
||||||
|
import com.king.zxing.CaptureFragment |
||||||
|
|
||||||
|
class QrCodeFragment : CaptureFragment() { |
||||||
|
|
||||||
|
override fun onScanResultCallback(result: Result?): Boolean { |
||||||
|
(activity as? QrCodeActivity)?.onScanResultCallback(result) |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,467 @@ |
|||||||
|
package io.legado.app.utils |
||||||
|
|
||||||
|
import android.graphics.* |
||||||
|
import android.text.TextPaint |
||||||
|
import android.text.TextUtils |
||||||
|
import androidx.annotation.ColorInt |
||||||
|
import androidx.annotation.FloatRange |
||||||
|
import com.google.zxing.* |
||||||
|
import com.google.zxing.common.GlobalHistogramBinarizer |
||||||
|
import com.google.zxing.common.HybridBinarizer |
||||||
|
import com.google.zxing.qrcode.QRCodeWriter |
||||||
|
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel |
||||||
|
import com.king.zxing.DecodeFormatManager |
||||||
|
import com.king.zxing.util.LogUtils |
||||||
|
import java.util.* |
||||||
|
import kotlin.math.max |
||||||
|
|
||||||
|
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate", "unused") |
||||||
|
object QRCodeUtils { |
||||||
|
|
||||||
|
const val DEFAULT_REQ_WIDTH = 480 |
||||||
|
const val DEFAULT_REQ_HEIGHT = 640 |
||||||
|
|
||||||
|
/** |
||||||
|
* 生成二维码 |
||||||
|
* @param content 二维码的内容 |
||||||
|
* @param heightPix 二维码的高 |
||||||
|
* @param logo 二维码中间的logo |
||||||
|
* @param ratio logo所占比例 因为二维码的最大容错率为30%,所以建议ratio的范围小于0.3 |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
fun createQRCode( |
||||||
|
content: String, |
||||||
|
heightPix: Int = DEFAULT_REQ_HEIGHT, |
||||||
|
logo: Bitmap? = null, |
||||||
|
@FloatRange(from = 0.0, to = 1.0) ratio: Float = 0.2f, |
||||||
|
errorCorrectionLevel: ErrorCorrectionLevel = ErrorCorrectionLevel.H |
||||||
|
): Bitmap? { |
||||||
|
//配置参数 |
||||||
|
val hints: MutableMap<EncodeHintType, Any> = EnumMap(EncodeHintType::class.java) |
||||||
|
hints[EncodeHintType.CHARACTER_SET] = "utf-8" |
||||||
|
//容错级别 |
||||||
|
hints[EncodeHintType.ERROR_CORRECTION] = errorCorrectionLevel |
||||||
|
//设置空白边距的宽度 |
||||||
|
hints[EncodeHintType.MARGIN] = 1 //default is 4 |
||||||
|
return createQRCode(content, heightPix, logo, ratio, hints) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 生成二维码 |
||||||
|
* @param content 二维码的内容 |
||||||
|
* @param heightPix 二维码的高 |
||||||
|
* @param logo 二维码中间的logo |
||||||
|
* @param ratio logo所占比例 因为二维码的最大容错率为30%,所以建议ratio的范围小于0.3 |
||||||
|
* @param hints |
||||||
|
* @param codeColor 二维码的颜色 |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
fun createQRCode( |
||||||
|
content: String?, |
||||||
|
heightPix: Int, |
||||||
|
logo: Bitmap?, |
||||||
|
@FloatRange(from = 0.0, to = 1.0) ratio: Float = 0.2f, |
||||||
|
hints: Map<EncodeHintType, *>, |
||||||
|
codeColor: Int = Color.BLACK |
||||||
|
): Bitmap? { |
||||||
|
try { |
||||||
|
// 图像数据转换,使用了矩阵转换 |
||||||
|
val bitMatrix = |
||||||
|
QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, heightPix, heightPix, hints) |
||||||
|
val pixels = IntArray(heightPix * heightPix) |
||||||
|
// 下面这里按照二维码的算法,逐个生成二维码的图片, |
||||||
|
// 两个for循环是图片横列扫描的结果 |
||||||
|
for (y in 0 until heightPix) { |
||||||
|
for (x in 0 until heightPix) { |
||||||
|
if (bitMatrix[x, y]) { |
||||||
|
pixels[y * heightPix + x] = codeColor |
||||||
|
} else { |
||||||
|
pixels[y * heightPix + x] = Color.WHITE |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 生成二维码图片的格式 |
||||||
|
var bitmap = Bitmap.createBitmap(heightPix, heightPix, Bitmap.Config.ARGB_8888) |
||||||
|
bitmap!!.setPixels(pixels, 0, heightPix, 0, 0, heightPix, heightPix) |
||||||
|
if (logo != null) { |
||||||
|
bitmap = addLogo(bitmap, logo, ratio) |
||||||
|
} |
||||||
|
return bitmap |
||||||
|
} catch (e: WriterException) { |
||||||
|
LogUtils.w(e.message) |
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 在二维码中间添加Logo图案 |
||||||
|
* @param src |
||||||
|
* @param logo |
||||||
|
* @param ratio logo所占比例 因为二维码的最大容错率为30%,所以建议ratio的范围小于0.3 |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private fun addLogo( |
||||||
|
src: Bitmap?, |
||||||
|
logo: Bitmap?, |
||||||
|
@FloatRange(from = 0.0, to = 1.0) ratio: Float |
||||||
|
): Bitmap? { |
||||||
|
if (src == null) { |
||||||
|
return null |
||||||
|
} |
||||||
|
if (logo == null) { |
||||||
|
return src |
||||||
|
} |
||||||
|
|
||||||
|
//获取图片的宽高 |
||||||
|
val srcWidth = src.width |
||||||
|
val srcHeight = src.height |
||||||
|
val logoWidth = logo.width |
||||||
|
val logoHeight = logo.height |
||||||
|
if (srcWidth == 0 || srcHeight == 0) { |
||||||
|
return null |
||||||
|
} |
||||||
|
if (logoWidth == 0 || logoHeight == 0) { |
||||||
|
return src |
||||||
|
} |
||||||
|
|
||||||
|
//logo大小为二维码整体大小 |
||||||
|
val scaleFactor = srcWidth * ratio / logoWidth |
||||||
|
var bitmap = Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888) |
||||||
|
try { |
||||||
|
val canvas = Canvas(bitmap!!) |
||||||
|
canvas.drawBitmap(src, 0f, 0f, null) |
||||||
|
canvas.scale( |
||||||
|
scaleFactor, |
||||||
|
scaleFactor, |
||||||
|
(srcWidth / 2).toFloat(), |
||||||
|
(srcHeight / 2).toFloat() |
||||||
|
) |
||||||
|
canvas.drawBitmap( |
||||||
|
logo, |
||||||
|
((srcWidth - logoWidth) / 2).toFloat(), |
||||||
|
((srcHeight - logoHeight) / 2).toFloat(), |
||||||
|
null |
||||||
|
) |
||||||
|
canvas.save() |
||||||
|
canvas.restore() |
||||||
|
} catch (e: Exception) { |
||||||
|
bitmap = null |
||||||
|
LogUtils.w(e.message) |
||||||
|
} |
||||||
|
return bitmap |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 解析一维码/二维码图片 |
||||||
|
* @param bitmap 解析的图片 |
||||||
|
* @param hints 解析编码类型 |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
fun parseCode( |
||||||
|
bitmap: Bitmap, |
||||||
|
reqWidth: Int = DEFAULT_REQ_WIDTH, |
||||||
|
reqHeight: Int = DEFAULT_REQ_HEIGHT, |
||||||
|
hints: Map<DecodeHintType?, Any?> = DecodeFormatManager.ALL_HINTS |
||||||
|
): String? { |
||||||
|
val result = parseCodeResult(bitmap, reqWidth, reqHeight, hints) |
||||||
|
return result?.text |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 解析一维码/二维码图片 |
||||||
|
* @param bitmap 解析的图片 |
||||||
|
* @param hints 解析编码类型 |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
fun parseCodeResult( |
||||||
|
bitmap: Bitmap, |
||||||
|
reqWidth: Int = DEFAULT_REQ_WIDTH, |
||||||
|
reqHeight: Int = DEFAULT_REQ_HEIGHT, |
||||||
|
hints: Map<DecodeHintType?, Any?> = DecodeFormatManager.ALL_HINTS |
||||||
|
): Result? { |
||||||
|
if (bitmap.width > reqWidth || bitmap.height > reqHeight) { |
||||||
|
val bm = BitmapUtils.changeBitmapSize(bitmap, reqWidth, reqHeight) |
||||||
|
return parseCodeResult(getRGBLuminanceSource(bm), hints) |
||||||
|
} |
||||||
|
return parseCodeResult(getRGBLuminanceSource(bitmap), hints) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 解析一维码/二维码图片 |
||||||
|
* @param source |
||||||
|
* @param hints |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
fun parseCodeResult(source: LuminanceSource?, hints: Map<DecodeHintType?, Any?>?): Result? { |
||||||
|
var result: Result? = null |
||||||
|
val reader = MultiFormatReader() |
||||||
|
try { |
||||||
|
reader.setHints(hints) |
||||||
|
if (source != null) { |
||||||
|
result = decodeInternal(reader, source) |
||||||
|
if (result == null) { |
||||||
|
result = decodeInternal(reader, source.invert()) |
||||||
|
} |
||||||
|
if (result == null && source.isRotateSupported) { |
||||||
|
result = decodeInternal(reader, source.rotateCounterClockwise()) |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (e: java.lang.Exception) { |
||||||
|
LogUtils.w(e.message) |
||||||
|
} finally { |
||||||
|
reader.reset() |
||||||
|
} |
||||||
|
return result |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 解析二维码图片 |
||||||
|
* @param bitmapPath 需要解析的图片路径 |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
fun parseQRCode(bitmapPath: String?): String? { |
||||||
|
val result = parseQRCodeResult(bitmapPath) |
||||||
|
return result?.text |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 解析二维码图片 |
||||||
|
* @param bitmapPath 需要解析的图片路径 |
||||||
|
* @param reqWidth 请求目标宽度,如果实际图片宽度大于此值,会自动进行压缩处理,当 reqWidth 和 reqHeight都小于或等于0时,则不进行压缩处理 |
||||||
|
* @param reqHeight 请求目标高度,如果实际图片高度大于此值,会自动进行压缩处理,当 reqWidth 和 reqHeight都小于或等于0时,则不进行压缩处理 |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
fun parseQRCodeResult( |
||||||
|
bitmapPath: String?, |
||||||
|
reqWidth: Int = DEFAULT_REQ_WIDTH, |
||||||
|
reqHeight: Int = DEFAULT_REQ_HEIGHT |
||||||
|
): Result? { |
||||||
|
return parseCodeResult(bitmapPath, reqWidth, reqHeight, DecodeFormatManager.QR_CODE_HINTS) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 解析一维码/二维码图片 |
||||||
|
* @param bitmapPath 需要解析的图片路径 |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
fun parseCode( |
||||||
|
bitmapPath: String?, |
||||||
|
reqWidth: Int = DEFAULT_REQ_WIDTH, |
||||||
|
reqHeight: Int = DEFAULT_REQ_HEIGHT, |
||||||
|
hints: Map<DecodeHintType?, Any?> = DecodeFormatManager.ALL_HINTS |
||||||
|
): String? { |
||||||
|
return parseCodeResult(bitmapPath, reqWidth, reqHeight, hints)?.text |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 解析一维码/二维码图片 |
||||||
|
* @param bitmapPath 需要解析的图片路径 |
||||||
|
* @param reqWidth 请求目标宽度,如果实际图片宽度大于此值,会自动进行压缩处理,当 reqWidth 和 reqHeight都小于或等于0时,则不进行压缩处理 |
||||||
|
* @param reqHeight 请求目标高度,如果实际图片高度大于此值,会自动进行压缩处理,当 reqWidth 和 reqHeight都小于或等于0时,则不进行压缩处理 |
||||||
|
* @param hints 解析编码类型 |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
fun parseCodeResult( |
||||||
|
bitmapPath: String?, |
||||||
|
reqWidth: Int = DEFAULT_REQ_WIDTH, |
||||||
|
reqHeight: Int = DEFAULT_REQ_HEIGHT, |
||||||
|
hints: Map<DecodeHintType?, Any?> = DecodeFormatManager.ALL_HINTS |
||||||
|
): Result? { |
||||||
|
var result: Result? = null |
||||||
|
val reader = MultiFormatReader() |
||||||
|
try { |
||||||
|
reader.setHints(hints) |
||||||
|
val source = getRGBLuminanceSource(compressBitmap(bitmapPath, reqWidth, reqHeight)) |
||||||
|
result = decodeInternal(reader, source) |
||||||
|
if (result == null) { |
||||||
|
result = decodeInternal(reader, source.invert()) |
||||||
|
} |
||||||
|
if (result == null && source.isRotateSupported) { |
||||||
|
result = decodeInternal(reader, source.rotateCounterClockwise()) |
||||||
|
} |
||||||
|
} catch (e: Exception) { |
||||||
|
LogUtils.w(e.message) |
||||||
|
} finally { |
||||||
|
reader.reset() |
||||||
|
} |
||||||
|
return result |
||||||
|
} |
||||||
|
|
||||||
|
private fun decodeInternal(reader: MultiFormatReader, source: LuminanceSource): Result? { |
||||||
|
var result: Result? = null |
||||||
|
try { |
||||||
|
try { |
||||||
|
//采用HybridBinarizer解析 |
||||||
|
result = reader.decodeWithState(BinaryBitmap(HybridBinarizer(source))) |
||||||
|
} catch (e: Exception) { |
||||||
|
} |
||||||
|
if (result == null) { |
||||||
|
//如果没有解析成功,再采用GlobalHistogramBinarizer解析一次 |
||||||
|
result = reader.decodeWithState(BinaryBitmap(GlobalHistogramBinarizer(source))) |
||||||
|
} |
||||||
|
} catch (e: Exception) { |
||||||
|
} |
||||||
|
return result |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 压缩图片 |
||||||
|
* @param path |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private fun compressBitmap(path: String?, reqWidth: Int, reqHeight: Int): Bitmap { |
||||||
|
if (reqWidth > 0 && reqHeight > 0) { //都大于进行判断是否压缩 |
||||||
|
val newOpts = BitmapFactory.Options() |
||||||
|
// 开始读入图片,此时把options.inJustDecodeBounds 设回true了 |
||||||
|
newOpts.inJustDecodeBounds = true //获取原始图片大小 |
||||||
|
BitmapFactory.decodeFile(path, newOpts) // 此时返回bm为空 |
||||||
|
val width = newOpts.outWidth.toFloat() |
||||||
|
val height = newOpts.outHeight.toFloat() |
||||||
|
// 缩放比,由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可 |
||||||
|
var wSize = 1 // wSize=1表示不缩放 |
||||||
|
if (width > reqWidth) { // 如果宽度大的话根据宽度固定大小缩放 |
||||||
|
wSize = (width / reqWidth).toInt() |
||||||
|
} |
||||||
|
var hSize = 1 // wSize=1表示不缩放 |
||||||
|
if (height > reqHeight) { // 如果高度高的话根据宽度固定大小缩放 |
||||||
|
hSize = (height / reqHeight).toInt() |
||||||
|
} |
||||||
|
var size = max(wSize, hSize) |
||||||
|
if (size <= 0) size = 1 |
||||||
|
newOpts.inSampleSize = size // 设置缩放比例 |
||||||
|
// 重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了 |
||||||
|
newOpts.inJustDecodeBounds = false |
||||||
|
return BitmapFactory.decodeFile(path, newOpts) |
||||||
|
} |
||||||
|
return BitmapFactory.decodeFile(path) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 获取RGBLuminanceSource |
||||||
|
* @param bitmap |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private fun getRGBLuminanceSource(bitmap: Bitmap): RGBLuminanceSource { |
||||||
|
val width = bitmap.width |
||||||
|
val height = bitmap.height |
||||||
|
val pixels = IntArray(width * height) |
||||||
|
bitmap.getPixels(pixels, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height) |
||||||
|
return RGBLuminanceSource(width, height, pixels) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 生成条形码 |
||||||
|
* @param content |
||||||
|
* @param format |
||||||
|
* @param desiredWidth |
||||||
|
* @param desiredHeight |
||||||
|
* @param hints |
||||||
|
* @param isShowText |
||||||
|
* @param textSize |
||||||
|
* @param codeColor |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
fun createBarCode( |
||||||
|
content: String?, |
||||||
|
desiredWidth: Int, |
||||||
|
desiredHeight: Int, |
||||||
|
format: BarcodeFormat = BarcodeFormat.CODE_128, |
||||||
|
hints: Map<EncodeHintType?, *>? = null, |
||||||
|
isShowText: Boolean = true, |
||||||
|
textSize: Int = 40, |
||||||
|
@ColorInt codeColor: Int = Color.BLACK |
||||||
|
): Bitmap? { |
||||||
|
if (TextUtils.isEmpty(content)) { |
||||||
|
return null |
||||||
|
} |
||||||
|
val writer = MultiFormatWriter() |
||||||
|
try { |
||||||
|
val result = writer.encode( |
||||||
|
content, format, desiredWidth, |
||||||
|
desiredHeight, hints |
||||||
|
) |
||||||
|
val width = result.width |
||||||
|
val height = result.height |
||||||
|
val pixels = IntArray(width * height) |
||||||
|
// All are 0, or black, by default |
||||||
|
for (y in 0 until height) { |
||||||
|
val offset = y * width |
||||||
|
for (x in 0 until width) { |
||||||
|
pixels[offset + x] = if (result[x, y]) codeColor else Color.WHITE |
||||||
|
} |
||||||
|
} |
||||||
|
val bitmap = Bitmap.createBitmap( |
||||||
|
width, height, |
||||||
|
Bitmap.Config.ARGB_8888 |
||||||
|
) |
||||||
|
bitmap.setPixels(pixels, 0, width, 0, 0, width, height) |
||||||
|
return if (isShowText) { |
||||||
|
addCode(bitmap, content, textSize, codeColor, textSize / 2) |
||||||
|
} else bitmap |
||||||
|
} catch (e: WriterException) { |
||||||
|
LogUtils.w(e.message) |
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 条形码下面添加文本信息 |
||||||
|
* @param src |
||||||
|
* @param code |
||||||
|
* @param textSize |
||||||
|
* @param textColor |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private fun addCode( |
||||||
|
src: Bitmap?, |
||||||
|
code: String?, |
||||||
|
textSize: Int, |
||||||
|
@ColorInt textColor: Int, |
||||||
|
offset: Int |
||||||
|
): Bitmap? { |
||||||
|
if (src == null) { |
||||||
|
return null |
||||||
|
} |
||||||
|
if (TextUtils.isEmpty(code)) { |
||||||
|
return src |
||||||
|
} |
||||||
|
|
||||||
|
//获取图片的宽高 |
||||||
|
val srcWidth = src.width |
||||||
|
val srcHeight = src.height |
||||||
|
if (srcWidth <= 0 || srcHeight <= 0) { |
||||||
|
return null |
||||||
|
} |
||||||
|
var bitmap = Bitmap.createBitmap( |
||||||
|
srcWidth, |
||||||
|
srcHeight + textSize + offset * 2, |
||||||
|
Bitmap.Config.ARGB_8888 |
||||||
|
) |
||||||
|
try { |
||||||
|
val canvas = Canvas(bitmap!!) |
||||||
|
canvas.drawBitmap(src, 0f, 0f, null) |
||||||
|
val paint = TextPaint() |
||||||
|
paint.textSize = textSize.toFloat() |
||||||
|
paint.color = textColor |
||||||
|
paint.textAlign = Paint.Align.CENTER |
||||||
|
canvas.drawText( |
||||||
|
code!!, |
||||||
|
(srcWidth / 2).toFloat(), |
||||||
|
(srcHeight + textSize / 2 + offset).toFloat(), |
||||||
|
paint |
||||||
|
) |
||||||
|
canvas.save() |
||||||
|
canvas.restore() |
||||||
|
} catch (e: Exception) { |
||||||
|
bitmap = null |
||||||
|
LogUtils.w(e.message) |
||||||
|
} |
||||||
|
return bitmap |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue