一、背景
最近有个需求,需要把图片小方块拼接的字提取转换成16进制的字节数组,然后存到字库中去,供单片机设备使用。字体三种格式(宽x高):12x16、16x16和24x24。这个字体图片有点多,所以用肉眼去识别转换比较伤眼,所以决定用通过图片识别的方式来对图片自动识别并转化成字节数组。但是在网上没有找到合适的方案,所以最终还是自己动手来实现了。
例如这个图片:
二、思路
1.二值化图片,将图片的像素点转化成0和1组成的二维数组;
2.将二值化后的数组根据按照方块的尺寸等比转换成新的二值数组(方块排列数组),其中黑色方块为1,其他方块为0;
3.方块排列数组按照指定的格式转换成对应的字节数组即可。
注意:新的二值数组是按照纵向每8个点为一个字节进行转换的。
三、结果
先看结果,后面有代码实现
1.十六进制字节数组
电-24x24={0x00,0x00,0xF0,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0xFE,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0xFF,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3F,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x40,0x20,0x1C,0x00,0x00}
2.控制台打印的字样
........................
..........#.............
..........#.............
..........#.............
..#################.....
..#.......#.......#.....
..#.......#.......#.....
..#.......#.......#.....
..#.......#.......#.....
..#.......#.......#.....
..#################.....
..#.......#.......#.....
..#.......#.......#.....
..#.......#.......#.....
..#.......#.......#.....
..#.......#.......#.....
..#################.....
..........#.............
..........#..........#..
..........#..........#..
..........#..........#..
..........#.........#...
...........#########....
........................
四、具体代码实现
1.字体转换器
import lombok.extern.slf4j.Slf4j;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
/**
* PictureFontConvertor
*
* @author hxj
* @date 2023-03-22 11:00
*/
@Slf4j
public class PictureFontConvertor {
/**
* 八位长度二进制整数中的8个位对应的正整数
*/
static int[] bitNum = {1,2,4,8,16,32,64,128};
private String name;
BufferedImage bufImage;
/**
* 图片尺寸-宽
*/
private int picWidth;
/**
* 图片尺寸-高
*/
private int picHeight;
/**
* 图片像素点二维数组排列
*/
private int[][] pixels;
/**
* 方块二维数组排列
*/
public int[][] squareMap;
/**
* 方块横向数量
*/
private int squareWidthNum;
/**
* 方块纵向数量
*/
private int squareHeightNum;
public PictureFontConvertor(String picPath, int squareWidthNum, int squareHeightNum) throws IOException {
File file =new File(picPath);
String fileName = file.getName().substring(0, file.getName().lastIndexOf("."));
bufImage = ImageIO.read(file);
this.squareWidthNum = squareWidthNum;
this.squareHeightNum = squareHeightNum;
picWidth = bufImage.getWidth();//图片的宽度
picHeight = bufImage.getHeight();//图片的高度
pixels = new int[picHeight][picWidth];
squareMap = new int[squareHeightNum][squareWidthNum];
name = String.format("%s-%dx%d",fileName, squareWidthNum, squareHeightNum);
}
public String getName(){
return name;
}
/**
* 将图中的浅色方块转换成0, 黑色方块转换成1,并按照顺序记录到二维数组中
*/
public void convertSquare() {
binaryImage();
calculateSquareMap();
}
/**
* 转换成无符号8位整数的16进制字符串
* @return
*/
public String toUInit8HexStr() {
return bytesToHexStr(toByteArray());
}
/**
* 转换成字节数组
* @return
*/
public byte[] toByteArray() {
int y = squareHeightNum/8;
int x = squareWidthNum;
byte[] items = new byte[y*x];
for (int h = 0; h < y; h++) {
for (int l = 0; l < x; l++) {
int n =0;
for (int i = 0; i < 8; i++) {
if (squareMap[h*8+i][l] == 1) {
n += bitNum[i];
}
}
items[(h*x)+l] = (byte) n;
}
}
return items;
}
/**
* 将转换后的结果打印输入
* @param blackPoint 黑色方块替换的字符
* @param whitePoint 其他方块的替换字符
*/
public void printSquareMap(String blackPoint, String whitePoint) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < squareMap.length; i++) {
int[] items = squareMap[i];
for (int item : items) {
if (item == 1) {
sb.append(blackPoint);
}else {
sb.append(whitePoint);
}
}
sb.append("\n");
}
System.out.println(sb);
}
/**
* 生成出方块的阵列
*/
private void calculateSquareMap() {
// 方块尺寸
int size = picWidth/squareWidthNum;
// 多余的横向像素点
int yushuX = picWidth - squareWidthNum * size;
// 多余的纵向像素点
int yushuY = picHeight - squareHeightNum * size;
// 横纵方向累计偏移
int xOffset = 0;
int yOffset = 0;
for (int y = 1; y <= squareHeightNum; y++) {
xOffset=0;
if (y <= yushuY) {
yOffset++;
}
int ybu = y<=yushuY ? 1:0;
int startY = yOffset == 0 ? (y-1)*size : (y-1)*size+yOffset-1;
int endY = startY+size+ybu;
for (int x = 1; x <= squareWidthNum; x++) {
if (x <= yushuX) {
xOffset++;
}
int xbu = x<=yushuX ? 1:0;
int startX = xOffset ==0? (x-1)*size : (x-1)*size+xOffset-1;
int endX = startX+size+xbu;
/* 黑色区域占比超过75即为黑色方块。
* 因为取方块像素点时,有一定的偏移,所以导致取出的点有一部分是白点,所以就按照比例来进行判断了。
*/
int ratio = countBlackRatio(startX, endX, startY, endY);
if (ratio > 75) {
squareMap[y-1][x-1] = 1;
}else {
squareMap[y-1][x-1] = 0;
}
}
}
}
/**
* 计算方块中的黑色区域占比
* @param startX
* @param endX
* @param startY
* @param endY
* @return
*/
private int countBlackRatio(int startX, int endX, int startY, int endY) {
int total=0;
int black=0;
for (int y = startY; y < endY; y++) {
for (int x = startX; x < endX; x++) {
total++;
try{
black += pixels[y][x];
}catch (Exception e) {
log.info("计算黑点占比异常:x={}, y={}", x, y);
throw new RuntimeException(e);
}
}
}
return black*100/total;
}
/**
* 二值化图像像素点
*/
public void binaryImage(){
int minX = 0;//图片起始点X
int minY = 0;//图片起始点Y
//将黑色区域化为1,其他为0
for (int i = minX; i < picWidth; i++) {
for (int j = minY; j < picHeight; j++) {
Object data = bufImage.getRaster().getDataElements(i, j, null);//获取该点像素,并以object类型表示
int red = bufImage.getColorModel().getRed(data);
int blue = bufImage.getColorModel().getBlue(data);
int green = bufImage.getColorModel().getGreen(data);
if(red==0&&green==0&&blue==0){
pixels[j+1 ][i+1 ] = 1;
}
}
}
}
/**
* 字节数组转16进制字符串
* @param bytes
* @return
*/
private static String bytesToHexStr(byte[] bytes) {
StringBuilder sb = new StringBuilder();
sb.append("{");
for (int i=0; i<bytes.length; i++) {
int low = bytes[i] & 0x0F;
int high = (bytes[i] >> 4) & 0x0F;
sb.append("0x");
sb.append(byteToChar(high));
sb.append(byteToChar(low));
if (i<bytes.length-1) {
sb.append(",");
}
}
sb.append("}");
return sb.toString();
}
public static char byteToChar(int b) {
char ch = (b < 0xA) ? (char) ('0' + b) : (char) ('A' + b - 10);
return ch;
}
}
3.生成结果
public static void main(String[] args) throws IOException {
String filePath = "./doc/.png";
PictureFontConvertor readPictureSquare = new PictureFontConvertor(filePath, 24,24);
readPictureSquare.convertSquare();
readPictureSquare.printSquareMap("#",".");
String str = readPictureSquare.toUInit8HexStr();
System.out.println(String.format("%s=%s",readPictureSquare.getName(), str));
}
2.结果测试
/**
* FontBytesCheck
*
* @author hxj
* @date 2023-03-21 13:40
*/
public class FontBytesCheck {
static int[] bs = {1,2,4,8,16,32,64,128};
public static void printFont(int[] bytes, int with, int height) {
int charHeight = height/8;
for (int i = 1; i <= charHeight; i++) {
for (int ci = 0; ci < 8; ci++) {
for (int j = 0; j < with; j++) {
if ((bytes[(i-1)*with+j] & bs[ci]) > 0 ) {
System.out.print("#");
}else {
System.out.print(" ");
}
if (j==with-1) {
System.out.println();
}
}
}
}
}
public static void main(String[] args) {
int[] bytes = {0x00,0x00,0xF0,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0xFE,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0xFF,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3F,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x40,0x20,0x1C,0x00,0x00};
printFont(bytes, 24, 24);
}