二维码现在是大街小巷的标配设计,只要用手机扫一下就能快速的进入相应的页面,可以跳转到相应页面,或是查看名片、付款、收红包等等。本文依据二维码的生成原理,用艺术图标替代枯燥的黑白二维码,赋予二维码艺术性和鲜活的个性。
二维码 (2-dimensional bar code)是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的。二维码的优点:二维码存储的数据量更大;可以包含数字、字符,及中文文本等混合内容;有一定的容错性(在部分损坏以后可以正常读取);空间利用率高。
如下图所示:
1. 二维码 QR(Quick-Response) code
是被广泛使用的一种二维码,解码速度快。它可以存储多种类型。如下图是一个qrcode的基本结构
其中:位置探测图形、位置探测图形分隔符、定位图形:用于对二维码的定位,对每个QR码来说,位置都是固定存在的,只是大小规格会有所差异;校正图形:规格确定,校正图形的数量和位置也就确定了;格式信息:表示改二维码的纠错级别,分为L、M、Q、H;版本信息:即二维码的规格,QR码符号共有40种规格的矩阵(一般为黑白色),从21x21(版本1),到177x177(版本40),每一版本符号比前一版本 每边增加4个模块。数据和纠错码字:实际保存的二维码信息,和纠错码字(用于修正二维码损坏带来的错误)。
2. 艺术二维码
依据二维码的结构特性,加入丰富生动的图案,提升其美观性。其原理就是针对黑白二维码中的黑色码元,用色彩绚丽的图案进行替换。
1.生成二维码的原始数据矩阵。
有很多开源的库工具可以直接用来生成二维码的BitMatrix,比如应用较广泛的google提供的zxing库。
代码如下:
public ArtQRCode(String url) throws IllegalArgumentException {
BitMatrix bitMatrix = null;
if (!TextUtils.isEmpty(url)) {
//配置参数
Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>();
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
//容错级别
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
//设置空白边距的宽度
hints.put(EncodeHintType.MARGIN, 0); //default is 4
// 图像数据转换,使用了矩阵转换
try {
bitMatrix = new QRCodeWriter().encode(url, BarcodeFormat.QR_CODE, 0, 0, hints);
} catch (WriterException e) {
e.printStackTrace();
}
}
if (bitMatrix!=null) {
valid = formatBitMatrix(bitMatrix);
Log.i(TAG, "bitMatrix:"+bitMatrix);
} else {
throw new IllegalArgumentException("bitMatrix should not be null!");
}
}
2.格式化BitMatrix,找出其中所有码元点,和符合特定矩形形状的码元集合。
按照二维码的原则,在BitMatrix中,先找出左、右、下等3个回字形定位符;然后遍历在BitMatrix,找出所有的码元点,再从码元点中找出特定矩形框(也即实际需求对二维码中要替换成特定图标的矩形框,比如33,23,22,12等)。
/** 格式化BitMatrix,找出其中所有的码元点 */
private boolean formatBitMatrix(BitMatrix bitMatrix) {
boolean success = false;
int matrixLength = bitMatrix.getWidth();
int matrixHeight = bitMatrix.getHeight();
if (matrixLength == matrixHeight && matrixLength>(LOCATOR_WIDTH+1)*2) {
success = true;
codeNumPerLine = matrixLength;
leftLocator.set(0, 0, LOCATOR_WIDTH, LOCATOR_WIDTH);
rightLocator.set(matrixLength - LOCATOR_WIDTH, 0, matrixLength, LOCATOR_WIDTH);
bottomLocator.set(0, matrixLength - LOCATOR_WIDTH, LOCATOR_WIDTH, matrixLength);
//遍历,得出所有的码元
for (int x = 0; x < matrixLength; x++) {
StringBuilder sb = new StringBuilder("列"+x);
StringBuilder sb_p = new StringBuilder("point");
//先列遍历,再行遍历
for (int y = 0; y < matrixLength; y++) {
//查看此element是否为有信息的码元点
boolean info = bitMatrix.get(x, y);
Point point = new Point(x, y);
if (info) {
String pointStr = "["+y+","+x+"]";
if (!isElementInRect(leftLocator, point)
&& !isElementInRect(rightLocator, point)
&& !isElementInRect(bottomLocator, point)) {//不属于定位符的点
//以列优先的顺序存放,故需要把下标的行列反过来,便于后续的按列顺序查找
int columnRow = y + x*matrixLength;
//存储该码元在可用码元矩阵中的列行坐标
this.codeElements.put(columnRow, point);
sb.append(pointStr);
} else {
sb_p.append(pointStr);
}
}
}
Log.w(TAG, sb.toString());
sb_p.append(" in locator");
Log.w(TAG, sb_p.toString());
}
}
return success;
}
bitMatrix = new QRCodeWriter().encode(url, BarcodeFormat.QR_CODE, 0, 0, hints);
这句是用url链接来生成BitMatrix的,其中要传入需要生成矩阵的宽和高,经过对比,发现这里可以取巧,设置宽和高为0,这样生成的矩阵最小(保证码元点信息无遗漏),每个码元点的宽度为1个单位,后续可以省去再去查找定位符以及计算码元点宽度的步骤。
/** 查找出所有符合规则的形状 */
private void searchRect() {
//遍历形状集Shape,查找出所有的矩形框,将其中的码元点标记为已发现,并添加到outElements中
for (int i = 0; i < SHAPES.size(); i++) {
Shape shape = SHAPES.valueAt(i);
if (shape!=null && shape.width!=0 && shape.height!=0) {
adjustRect(shape);
}
}
//将已标记的码元点从codeElements中移除
int size = codeElements.size();
for (int index = size-1; index >= 0; index--) {
//每次只删一条,保证下标index始终处于size范围内
int columnRow = codeElements.keyAt(index);
Point point = outElements.get(columnRow);
if (point!=null) {
Log.i(TAG, "remove code at columnRow["+point.x+","+point.y+"]");
codeElements.remove(columnRow);
}
}
}
3.绘制图形。
依据前面两步对BitMatrix的操作,将找出的定位符用符合定位符特征的图片绘制到画布上,再将各个特定矩形框也以相应的宽高尺寸绘制到画布,然后将单个的码元点以简单的图标绘制上去,最后还可以利用二维码的容错机制,在画布的中央小块位置画上个性化的头像。
/** 绘制艺术二维码的图像 */
public Bitmap createArtBitmap(Context context, int width, Bitmap logoBmp) {
/**商 */
int codeWidth = width/codeNumPerLine;
/**余数 */
int remainder = width%codeNumPerLine;
int matrixWidth = width;
if (remainder>0) {//调整初始图片的宽度,凑整,为了每个码元点占用整数个像素点
codeWidth++;
matrixWidth = codeWidth*codeNumPerLine;
}
Bitmap bitmap = null;
if (valid) {
searchRect();
// 生成二维码图片的格式,使用ARGB_8888
bitmap = Bitmap.createBitmap(matrixWidth, matrixWidth, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
//先画原图
drawPicture(context, canvas, paint, codeWidth);
//再画logo
addLogo(canvas, bitmap, logoBmp);
}
if (remainder>0) {//再将图像压缩至目标宽高
bitmap = Bitmap.createScaledBitmap(bitmap, width, width, true);
}
return bitmap;
}
/** 在画布上绘出所有的矩形框和码元点 */
private void drawPicture(Context context, Canvas canvas, Paint paint, int codeUnit) {
// 按照二维码的算法,在canvas上逐个绘制二维码的码元点
Resources resource = context.getResources();
Bitmap locatorBmp = BitmapFactory.decodeResource(resource, R.drawable.qvip_pay_qrart_locator);
Bitmap locatorIcon = Bitmap.createScaledBitmap(locatorBmp, LOCATOR_WIDTH*codeUnit, LOCATOR_WIDTH*codeUnit, true);
//先画3个定位符
canvas.drawBitmap(locatorIcon, leftLocator.left*codeUnit, leftLocator.top*codeUnit, paint);
canvas.drawBitmap(locatorIcon, rightLocator.left*codeUnit, rightLocator.top*codeUnit, paint);
canvas.drawBitmap(locatorIcon, bottomLocator.left*codeUnit, bottomLocator.top*codeUnit, paint);
//再画所有找到的特殊矩形框
int size1 = allRects.size();
Log.i(TAG, "allRects size="+size1);
for (int index1 = 0; index1 < size1; index1++) {
int shapeId = allRects.keyAt(index1);//key为此集合中相同的shapeId
SparseArray<Point> rects = allRects.valueAt(index1);
Shape shape = SHAPES.get(shapeId);
Bitmap bmp = BitmapFactory.decodeResource(resource, shape.resId);
Bitmap shapeIcon = Bitmap.createScaledBitmap(bmp, shape.width*codeUnit, shape.height*codeUnit, true);
int size2 = rects!=null ? rects.size() : 0;
Log.i(TAG, "shape "+shapeId+" size="+size2);
//对同一种矩形框,查出所有实例并绘制
for (int index2 = 0; index2 < size2; index2++) {
Point point = rects.valueAt(index2);
if (point!=null) {
canvas.drawBitmap(shapeIcon, point.x*codeUnit, point.y*codeUnit, paint);
}
}
}
//再画剩下的单个的码元
int size = codeElements.size();
Bitmap singleBmp = BitmapFactory.decodeResource(resource, R.drawable.qvip_pay_qrart_3301);
Bitmap codeBmp = Bitmap.createScaledBitmap(singleBmp, codeUnit, codeUnit, true);
for (int singleIndex = 0; singleIndex < size; singleIndex++) {
Log.i(TAG, "single code size="+size);
Point singlePoint = codeElements.valueAt(singleIndex);
if (singlePoint!=null) {
canvas.drawBitmap(codeBmp, singlePoint.x*codeUnit, singlePoint.y*codeUnit, paint);
}
}
}
至此,二维码图片Bitmap就已经生成OK了。
利用二维码的结构特性,将其中连成片的特殊形状用个性化的图片代替,可以使二维码更加美观和生动。
在手Q中,用此方法,实际可用生成面对面红包、付款码等二维码图形。
附:试用手Q扫一扫,有惊喜哦!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。