Recognize fonts or content spliced by squares in pictures based on java

1. Background

Recently, there is a need to extract and convert the words spliced by the small squares of the picture into a hexadecimal byte array, and then store them in the font library for use by single-chip microcomputer devices. Font in three formats (width x height): 12×16, 16×16 and 24×24. There are a lot of fonts and pictures in this font, so it is more eye-catching to identify and convert with the naked eye, so I decided to use the method of picture recognition to automatically recognize the picture and convert it into a byte array. But I couldn’t find a suitable solution on the Internet, so I finally realized it by myself.

For example, this picture:

2. Ideas

1. Binarize the picture, convert the pixels of the picture into a two-dimensional array composed of 0 and 1;
2. Convert the binarized array into a new binary array (array of squares) according to the size of the squares, where the black squares are 1 and the other squares are 0;
3. The block array array can be converted into the corresponding byte array according to the specified format.
Note: The new binary array is converted according to every 8 points in the vertical direction as a byte.

3. Results

Look at the result first, then there is code implementation
1. Hex byte array
power-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. The words printed on the console

................................
..........#..........
..........#..........
..........#..........
..##################.....
..#.......#.......#.....
..#.......#.......#.....
..#.......#.......#.....
..#.......#.......#.....
..#.......#.......#.....
..##################.....
..#.......#.......#.....
..#.......#.......#.....
..#.......#.......#.....
..#.......#.......#.....
..#.......#.......#.....
..##################.....
..........#..........
..........#..........#..
..........#..........#..
..........#..........#..
..........#..........#...
..........#########....
...................................

4. Specific code implementation

1. Font converter

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 {

    /**
     * Positive integer corresponding to 8 bits in an eight-bit binary integer
     */
    static int[] bitNum = {1,2,4,8,16,32,64,128};

    private String name;
    BufferedImage bufImage;
    /**
     * Picture size - wide
     */
    private int picWidth;
    /**
     * Picture size-high
     */
    private int picHeight;

    /**
     * Image pixel point two-dimensional array arrangement
     */
    private int[][] pixels;

    /**
     * Square two-dimensional array arrangement
     */
    public int[][] squareMap;

    /**
     * Horizontal number of blocks
     */
    private int squareWidthNum;
    /**
     * The vertical number of blocks
     */
    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();//The width of the picture
        picHeight = bufImage.getHeight();//The height of the picture
        pixels = new int[picHeight][picWidth];
        squareMap = new int[squareHeightNum][squareWidthNum];
        name = String.format("%s-%dx%d",fileName, squareWidthNum, squareHeightNum);
    }

    public String getName(){
        return name;
    }

    /**
     * Convert the light-colored squares in the figure to 0, and the black squares to 1, and record them in a two-dimensional array in order
     */
    public void convertSquare() {
        binaryImage();
        calculateSquareMap();
    }

    /**
     * Converted to a hexadecimal string of unsigned 8-bit integers
     * @return
     */
    public String toUInit8HexStr() {
        return bytesToHexStr(toByteArray());
    }

    /**
     * Convert to byte array
     * @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;
    }

    /**
     * Print out the converted result
     * @param blackPoint The character replaced by the black square
     * @param whitePoint The replacement character for other squares
     */
    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("\\
");
        }
        System.out.println(sb);
    }

    /**
     * Generate an array of squares
     */
    private void calculateSquareMap() {
        // block size
        int size = picWidth/squareWidthNum;
        // Extra horizontal pixels
        int yushuX = picWidth - squareWidthNum * size;
        // Extra vertical pixels
        int yushuY = picHeight - squareHeightNum * size;
        // Cumulative offset in horizontal and vertical directions
        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;

                /* If the proportion of the black area exceeds 75, it is a black square.
                 * Because there is a certain offset when taking the square pixel points, part of the taken out points is a white point, so the judgment is made according to the ratio.
                 */
                int ratio = countBlackRatio(startX, endX, startY, endY);
                if (ratio > 75) {
                    squareMap[y-1][x-1] = 1;
                } else {
                    squareMap[y-1][x-1] = 0;
                }
            }
        }
    }

    /**
     * Calculate the proportion of the black area in the square
     * @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("Calculation of black dot ratio is abnormal: x={}, y={}", x, y);
                    throw new RuntimeException(e);
                }
            }
        }
        return black*100/total;
    }

    /**
     * Binarized image pixels
     */
    public void binaryImage(){
        int minX = 0;//Image starting point X
        int minY = 0;//The starting point of the picture Y
        //Reduce the black area to 1, and the others to 0
        for (int i = minX; i < picWidth; i ++ ) {
            for (int j = minY; j < picHeight; j++ ) {
                Object data = bufImage.getRaster().getDataElements(i, j, null);//Get the pixel of this point and express it in object type
                int red = bufImage.getColorModel().getRed(data);
                int blue = bufImage.getColorModel().getBlue(data);
                int green = bufImage.getColorModel().getGreen(data);
                if(red==0 & amp; & amp;green==0 & amp; & amp;blue==0){
                    pixels[j + 1 ][i + 1 ] = 1;
                }
            }
        }
    }

    /**
     * Byte array to hexadecimal string
     * @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. Generate results

 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. Result test

/**
 * 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);
    }