您的位置:首页 > 移动开发 > Android开发

安卓等间采样缩放算法的实现与解决ZXing生成DATA MATRIX二维码太小的问题

2017-08-11 20:28 399 查看
本文相关代码:https://gitee.com/mingyueyixi/ZxingLibraryTest

按我的上一篇文章所述,修改生成二维码的方法后,成功生成了DATA MATRIX格式的二维码,然而,这个二维码实在太小,以至于竟然看不见,堪比小芝麻。而Bitmap.createScaleBitmap()方法放大的图片又十分模糊,无法使用。

于是,寻找了等间采样算法来进行缩放。关于这个算法缩放的图片,其原理、优缺点自行研究,闲言少叙,以下是Android平台下实现等间采样缩放算法的代码:

BitmapFlex.java

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.util.Log;

/**
* @author Yue
* @date 2017/8/9 13:28
* 此类中的方法已通过测试。主要用于等间采样缩放。等间采样缩放在单一色彩的图中表现较好,不会产生模糊,
* 可用于放大 DATA_MATRIX 二维码。
*/
public class BitmapFlex {

/**
* 等间隔采样的图像缩放
* @param bitmap     要缩放的图像对象
* @param dstWidth   缩放后图像的宽
* @param dstHeight 缩放后图像的高
* @return 返回处理后的图像对象
*/
public static Bitmap flex(Bitmap bitmap, int dstWidth, int dstHeight) {
float wScale = (float) dstWidth / bitmap.getWidth();
float hScale = (float) dstHeight / bitmap.getHeight();
return flex(bitmap, wScale, hScale);
}

/**
* 等间隔采样的图像缩放
* @param bitmap 要缩放的bitap对象
* @param wScale 要缩放的横列(宽)比列
* @param hScale 要缩放的纵行(高)比列
* @return 返回处理后的图像对象
*/
public static Bitmap flex(Bitmap bitmap, float wScale, float hScale) {
if (wScale <= 0 || hScale <= 0){
return null;
}
float ii = 1 / wScale;    //采样的行间距
float jj = 1 / hScale; //采样的列间距

int width = bitmap.getWidth();
int height = bitmap.getHeight();
int dstWidth = (int) (wScale * width);
int dstHeight = (int) (hScale * height);

int[] pixels = new int[width * height];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);

int[] dstPixels = new int[dstWidth * dstHeight];

for (int j = 0; j < dstHeight; j++) {
for (int i = 0; i < dstWidth; i++) {
dstPixels[j * dstWidth + i] = pixels[(int) (jj * j) * width + (int) (ii * i)];
}
}
System.out.println((int) ((dstWidth - 1) * ii));
Log.d(">>>",""+"dstPixels:"+dstWidth+" x "+dstHeight);

Bitmap outBitmap = Bitmap.createBitmap(dstWidth, dstHeight, Config.ARGB_8888);
outBitmap.setPixels(dstPixels, 0, dstWidth, 0, 0, dstWidth, dstHeight);

return outBitmap;
}
}


接着,用它来缩放data matrix、pdf417格式的二维码。pdf417格式的二维码好生奇葩,好多时候是长方形的。

package banding.com.google.Zxing.utils;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.text.TextUtils;
import android.util.Log;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.Binarizer;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.EncodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.NotFoundException;
import com.google.zxing.RGBLuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.WriterException;
import com.google.zxing.aztec.encoder.Encoder;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

import java.util.HashMap;
import java.util.Map;

/**
* 条码码操作类。生成时需要特别注意,条码格式对字符编码有要求,有的不支持中文、字母等,需要依据格式额外处理。
* 注意,配置:
* <p> {@link BarcodeFormat#RSS_EXPANDED,BarcodeFormat#RSS_14,BarcodeFormat#UPC_EAN_EXTENSION}
*
* 以及code 93 由于某些原因生成失败,暂未排查完毕。
*
*/
public class BarcodeUtils {

/**
* 默认前景色黑色,背景色透明
* 生成一二维码
* @param content 二维码文字内容
* @param width   图片像素宽
* @param height  图片像素高
* @param format  二维码图片的编码方式
* @return 二维码bitmap
*/
public static Bitmap createBarcode(String content, int width, int height, BarcodeFormat format) {
return createBarcode(content, width, height, format, 0xff000000, 0x00000000);
}
/**
* 生成二维码(部分一维码不能使用此方法)
* @param content   二维码文字内容
* @param width     图片像素宽
* @param height    图片像素高
* @param format    二维码图片的编码方式
* @param foreColor 前景色
* @param backColor 背景色
* @return Bitmap对象
*/
public static Bitmap createBarcode(String content, int width, int height, BarcodeFormat format, int foreColor, int backColor) {
return createBarcode(content, "utf-8", width, height, format, foreColor, backColor);
}

/**
* 生成一二维码
* @param content     二维码文字内容
* @param charset 字符编码
* @param width       图片像素宽
* @param height      图片像素高
* @param format      二维码图片的编码方式
* @param foreColor   前景色
* @param backColor   背景色
* @return Bitmap对象
*/
public static Bitmap createBarcode(String content, String charset, int width, int height, BarcodeFormat format, int foreColor, int backColor) {
if (TextUtils.isEmpty(content)) {
return null;
}
if (TextUtils.isEmpty(charset)){
charset = "utf-8";
}
//配置参数
HashMap<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>();
hints.put(EncodeHintType.CHARACTER_SET, charset);//字符串编码

//错误纠正,Aztec格式,pdf417格式和其它条码配置不同,不能通用,否则会产生错误。
if (format == BarcodeFormat.AZTEC) {//错误校正词的最小百分比
hints.put(EncodeHintType.ERROR_CORRECTION, Encoder.DEFAULT_EC_PERCENT);//默认,可以不设
} else if (format == BarcodeFormat.PDF_417) {
hints.put(EncodeHintType.ERROR_CORRECTION, 2);//纠错级别,允许为0到8。默认2,可以不设
} else {
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);//
}
//设置空白边距的宽度,默认值为4
hints.put(EncodeHintType.MARGIN, 0);

return createBarcode(content, width, height, format, hints, foreColor, backColor);
}

/**
* 生成一二维码。
* @param content   文字内容
* @param format    条码编码方式
* @param hints     条码其他配置对象
* @param foreColor 条码前景色
* @param backColor 条码背景色
* @return bitmap 条码bitmap对象
*/
public static Bitmap createBarcode(String content, int width, int height, BarcodeFormat format, HashMap<EncodeHintType, Object> hints, int foreColor, int backColor) {
return createBarcode(content, width, height, format, hints, foreColor, backColor, true);
}

/**
* QR二维码生成
*
* @param content
* @param widthAndHeight
* @return 二维码bitmap
*/
public static Bitmap createQRCode(String content, int widthAndHeight) {
return createBarcode(content, widthAndHeight, widthAndHeight, BarcodeFormat.QR_CODE);
}

/**
* 128条形码
*
* @param content
* @param width
* @param height
* @return 一维码bitmap
*/
public static Bitmap create128Code(String content, int width, int height) {
return createBarcode(content, width, height, BarcodeFormat.CODE_128);
}

/**
* 条形码生成方法
*
* @param content     文字内容
* @param width       宽
* @param height      高
* @param format      条形码格式,pdf417,code128,QRcode等
* @param hints       条码的Map配置
* @param foreColor   前景色
* @param backColor   背景色
* @param fitSizeFlag 是否自动适应到预期的宽高(data matrix格式的二维码,生成时非常小,需要放大,其他格式也可能不到预期的大小)
*                    <p> true,使用等间采样算法,缩放bitmap到期望的尺寸
*                    <p> false则不处理
* @return 条码
*/
public static Bitmap createBarcode(String content, int width, int height, BarcodeFormat format,
Map<EncodeHintType, Object> hints, int foreColor, int backColor, boolean fitSizeFlag) {
// 图像数据转换,使用了矩阵转换
BitMatrix bitMatrix = null;
try {
MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
bitMatrix = multiFormatWriter.encode(content, format, width, height, hints);

} catch (WriterException e) {
e.printStackTrace();
}
if (bitMatrix == null) {
return null;
}
//注,以下代码修正DATA_MATRIX和PDF_417生成错误。
//网络上广泛使用预设的width和height生成像素矩阵,因这两种格式的二维码,其bitMatrix不一定等于预期的像素矩阵(width * height)
//会导致数组下标异常
int bitWidth = bitMatrix.getWidth();
int bitHeight = bitMatrix.getHeight();

int[] pixels = new int[bitWidth * bitHeight];

//遍历bitmatrix,为像素矩阵按一行行(横列)设置像素颜色。
for (int y = 0; y < bitHeight; y++) {
for (int x = 0; x < bitWidth; x++) {
if (bitMatrix.get(x, y)) {
pixels[y * bitWidth + x] = foreColor;
} else {
pixels[y * bitWidth + x] = backColor;
}
}
}

Bitmap bitmap = Bitmap.createBitmap(bitWidth, bitHeight, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, bitWidth, 0, 0, bitWidth, bitHeight);

Log.d(">>>", "预期宽高:" + width + " * " + height + "  矩阵宽高:" + bitWidth + " * " + bitHeight);
if (fitSizeFlag) {
//因为bitmap可能并不等于预先设置的width和height,需要进行等比缩放,尤其是BarcodeFormat.DATA_MATRIX格式,小的不可想象
float wMultiple = ((float) bitWidth) / (float) width;//生成的bitmap的宽除以预期的宽
float hMultiple = ((float) bitHeight) / (float) height;//生成的bitmap的高除以预期的高
Log.d(">>>", wMultiple + "--" + hMultiple);
if (wMultiple == 1f || hMultiple == 1f) {//说明生成的条形码符合预期,不需要缩放
Log.d(">>>", "...re1");
return bitmap;
}

if (wMultiple > hMultiple) {//说明宽超出范围更多,以宽的比例为标准进行缩放。
int dstWidth = width;// bitWidth / wMultiple
int dstHeight = (int) (bitHeight / wMultiple);
//          bitmap = Bitmap.createScaledBitmap(bitmap,dstWidth,dstHeight,true);//安卓的这个方法不行

bitmap = BitmapFlex.flex(bitmap, dstWidth, dstHeight);//等间采样算法进行缩放
} else {//说明相当或高超出范围更多,以高的比例为标准进行缩放。
int dstHeight = height;// bitHeight / hMultiple
int dstWidth = (int) (bitWidth / hMultiple);

bitmap = BitmapFlex.flex(bitmap, dstWidth, dstHeight);//等间采样算法进行缩放
}
}
return bitmap;
}

/**
* 一、二维码的解析
* @param bitmap 图片对象
* @param charaterset 编码。以此文字编码解析一二维码文字内容
* @return 解析结果
*/
public static Result parseCode(Bitmap bitmap, String charaterset) {
Map<DecodeHintType, String> hints = new HashMap<DecodeHintType, String>();
hints.put(DecodeHintType.CHARACTER_SET, charaterset);
return parseBarCode(bitmap,hints);
}

/**
* 一二维码的解析
* @param bitmap 图片对象
* @param hints 解码map配置
* @return 解析结果
*/
public static Result parseBarCode(Bitmap bitmap,Map<DecodeHintType,?> hints){
MultiFormatReader formatReader = new MultiFormatReader();

int bw = bitmap.getWidth();
int bh = bitmap.getHeight();
int[] pixels = new int[bw * bh];
bitmap.getPixels(pixels, 0, bw, 0, 0, bw, bh);
LuminanceSource source = new RGBLuminanceSource(bw, bh, pixels);
Binarizer binarizer = new HybridBinarizer(source);
BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);

Result result = null;
try {
result = formatReader.decode(binaryBitmap, hints);
} catch (NotFoundException e) {
e.printStackTrace();
}
return result;
}

/**
* 在bitma中间添加Logo图案(用于QR二维码等场合)
* @param src 原图
* @param logo logo图
*/
public static void drawLogo(Bitmap src, Bitmap logo) {
if (src == null) {
return;
}

if (logo == null) {
return;
}

//获取图片的宽高
int srcWidth = src.getWidth();
int srcHeight = src.getHeight();
int logoWidth = logo.getWidth();
int logoHeight = logo.getHeight();

if (srcWidth == 0 || srcHeight == 0) {
return;
}
if (logoWidth == 0 || logoHeight == 0) {
return;
}
//logo大小为二维码整体大小的1/5
float scaleFactor = srcWidth * 1.0f / 5 / logoWidth;
//      Bitmap bitmap = Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888);
try {
Canvas canvas = new Canvas(src);
canvas.drawBitmap(src, 0, 0, null);
canvas.scale(scaleFactor, scaleFactor, srcWidth / 2, srcHeight / 2);
canvas.drawBitmap(logo, (srcWidth - logoWidth) / 2, (srcHeight - logoHeight) / 2, null);

canvas.save(Canvas.ALL_SAVE_FLAG);
canvas.restore();
} catch (Exception e) {
e.getStackTrace();
}

}
}


注:

RSS_EXPANDED

RSS_14

BarcodeFormat

UPC_EAN_EXTENSION

UPCE_E

以及CODE_93

由于某些原因生成失败,暂未排查完毕。(其中有部分是扩展的条形码,可能需要自己写相关代码吧。)

上诉修改过后的代码,支持正常生成ZXing的所有二维码格式。

最后,利用这个工具类生成二维码。

图片像素很大,可以在新建标签中打开查看。

本文地址:



我的csdn博客地址:



额,混入了一个一维码,条形码虽然放大了那么多倍,但是估计也没人扫的出,太密集了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息