Java实现墨水屏点阵图

前言

点阵图是由称作像素(图片元素)的单个点组成的。这些点可以进行不同的排列和染色以构成图样。当放大位图时,可以看见赖以构成整个图像的无数单个方块。点阵图的格式包括PNG、TIFF、BMP、JPEG等,通常在放大时会显得参差不齐,但从远处观看时颜色和形状又显得是连续的

点阵图

点阵图分为行列式列行式取值,如图所示

大致的点阵图为

scss 复制代码
优(0) 秀(1)

{0x00,0x80,0x60,0xF8,0x07,0x10,0x10,0x10,0xFF,0x10,0xF0,0x11,0x16,0x10,0x10,0x00},
{0x01,0x00,0x00,0xFF,0x00,0x80,0x60,0x1C,0x03,0x00,0x3F,0x40,0x40,0x40,0x78,0x00},/*"优",0*/

{0x00,0x10,0x90,0x92,0x52,0x32,0x12,0xFE,0x12,0x31,0x51,0x91,0x90,0x10,0x00,0x00},
{0x01,0x81,0x80,0x40,0x31,0x0F,0x01,0x01,0x01,0x4D,0x8B,0x48,0x38,0x01,0x01,0x00},/*"秀",1*/

一个数字分为两个点阵,分别是16*16(宽度和高度),一个字节代表8个像素,如中的第一个字节0x01

补足8位是00000001,按照0显示暗,1显示亮

**备注:**根据字体大小和高度这些,生成的字节不一样

ini 复制代码
package cn.com.ut.semp.pic;

import cn.hutool.core.util.HexUtil;

import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;

/**
 * 墨水屏点阵计算工具
 * 支持文字转点阵、图形绘制及点阵数据处理
 */
public class EInkDotMatrix {
    // 点阵宽度(墨水屏横向像素数)
    private final int width;
    // 点阵高度(墨水屏纵向像素数)
    private final int height;
    // 点阵数据(true表示黑色,false表示白色)
    private boolean[][] matrix;

    /**
     * 初始化指定尺寸的点阵
     * @param width 宽度(像素)
     * @param height 高度(像素)
     */
    public EInkDotMatrix(int width, int height) {
        this.width = width;
        this.height = height;
        this.matrix = new boolean[height][width];
    }

    /**
     * 将文字转换为点阵
     * @param text 要转换的文字
     * @param font 字体
     * @param x 起始X坐标
     * @param y 起始Y坐标(基线位置)
     * @param align 对齐方式(0-左对齐,1-居中,2-右对齐)
     */
    public void drawText(String text, Font font, int x, int y, int align) {
        // 创建缓冲图像用于绘制文字
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY);
        Graphics2D g2d = image.createGraphics();
        
        // 设置背景为白色
        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, width, height);
        
        // 设置文字颜色为黑色
        g2d.setColor(Color.BLACK);
        g2d.setFont(font);
        
        // 计算文字宽度用于对齐
        FontRenderContext frc = g2d.getFontRenderContext();
        Rectangle2D bounds = font.getStringBounds(text, frc);
        int textWidth = (int) bounds.getWidth();
        int textHeight = (int) bounds.getHeight();
        
        // 根据对齐方式调整X坐标
        int drawX = x;
        switch (align) {
            case 1: // 居中对齐
                drawX -= textWidth / 2;
                break;
            case 2: // 右对齐
                drawX -= textWidth;
                break;
            default: // 左对齐
                break;
        }
        
        // 绘制文字(y是基线位置,需要调整以垂直居中)
        int drawY = y + (textHeight / 2) - 3; // 减去字体 descent
        g2d.drawString(text, drawX, drawY);
        
        // 提取像素数据到点阵
        for (int row = 0; row < height; row++) {
            for (int col = 0; col < width; col++) {
                // 二进制图像中,0表示黑色,1表示白色
                int pixel = image.getRGB(col, row) & 0xFF;
                matrix[row][col] = (pixel == 0);
            }
        }
        
        g2d.dispose();
    }

    /**
     * 绘制直线(Bresenham算法)
     * @param x1 起点X
     * @param y1 起点Y
     * @param x2 终点X
     * @param y2 终点Y
     */
    public void drawLine(int x1, int y1, int x2, int y2) {
        int dx = Math.abs(x2 - x1);
        int dy = Math.abs(y2 - y1);
        int sx = x1 < x2 ? 1 : -1;
        int sy = y1 < y2 ? 1 : -1;
        int err = dx - dy;

        while (true) {
            setPoint(x1, y1, true);
            if (x1 == x2 && y1 == y2) break;
            int e2 = 2 * err;
            if (e2 > -dy) {
                err -= dy;
                x1 += sx;
            }
            if (e2 < dx) {
                err += dx;
                y1 += sy;
            }
        }
    }

    /**
     * 绘制矩形
     * @param x 左上角X
     * @param y 左上角Y
     * @param w 宽度
     * @param h 高度
     */
    public void drawRectangle(int x, int y, int w, int h) {
        drawLine(x, y, x + w, y);          // 上边缘
        drawLine(x + w, y, x + w, y + h);  // 右边缘
        drawLine(x + w, y + h, x, y + h);  // 下边缘
        drawLine(x, y + h, x, y);          // 左边缘
    }

    /**
     * 设置单个点的状态
     * @param x X坐标
     * @param y Y坐标
     * @param isBlack 是否为黑色
     */
    public void setPoint(int x, int y, boolean isBlack) {
        if (x >= 0 && x < width && y >= 0 && y < height) {
            matrix[y][x] = isBlack;
        }
    }

    /**
     * 清除所有点(全部设为白色)
     */
    public void clear() {
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                matrix[i][j] = false;
            }
        }
    }

    /**
     * 将点阵数据转换为字节数组(适合硬件传输)
     * 每字节表示8个点,高位在前
     * @return 字节数组
     */
    public byte[] toByteArray() {
        int byteCount = (width * height + 7) / 8; // 向上取整
        byte[] result = new byte[byteCount];
        
        int index = 0;
        byte currentByte = 0;
        int bitPos = 7; // 从高位开始
        
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                if (matrix[y][x]) {
                    currentByte |= (1 << bitPos);
                }
                bitPos--;
                
                // 填满一个字节
                if (bitPos < 0) {
                    result[index++] = currentByte;
                    currentByte = 0;
                    bitPos = 7;
                }
            }
        }
        
        // 处理最后一个不完整的字节
        if (bitPos < 7) {
            result[index] = currentByte;
        }
        
        return result;
    }

    /**
     * 在控制台打印点阵(调试用)
     */
    public void printMatrix() {
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                System.out.print(matrix[y][x] ? "■ " : "□ ");
            }
            System.out.println();
        }
    }

    // Getters
    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

    public boolean[][] getMatrix() {
        return matrix;
    }
    
}

测试例子

ini 复制代码
package cn.com.ut.semp.pic;

import cn.hutool.core.util.HexUtil;

import java.io.IOException;

public class EInkExample {
    public static void main(String[] args) throws IOException {
        String str = "优秀";
        String[] data = str.split("\n");
        for (int a = 0; a < data.length; a++) {
            // 单行文本转换
            byte[][] dotMatrix = EInkDisplaySDK.textToDotMatrix(
                    data[a],
                    16,
                    EInkDisplaySDK.FontStyle.PLAIN,
                    128,
                    16
            );


            for (int i = 0; i < dotMatrix.length; i++) {
                for (int j = 0; j < dotMatrix[i].length; j++) {
                    System.out.print("0x" + HexUtil.toHex(dotMatrix[i][j] & 0xFF) + ",");
                }
                System.out.println();
            }
        }
    }
}

这个是列行式

ini 复制代码
package cn.com.ut.semp.hello;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

/**
 * 模拟PCtoLCD2002列行式取模功能
 */
public class PCToLCD2002Emulator {

    // 取模方向常量
    public static final int MODE_COLUMN_MAJOR = 0; // 列行式
    public static final int MODE_ROW_MAJOR = 1;    // 行列式

    // 字体设置
    private Font font;
    private boolean antiAliasing = false;
    private int mode = MODE_COLUMN_MAJOR;
    private boolean reverseByte = false; // 字节内位顺序是否反转
    private boolean reverseOrder = false; // 整体数据顺序是否反转

    public PCToLCD2002Emulator() {
        this("宋体", 16, Font.PLAIN);
    }

    public PCToLCD2002Emulator(String fontName, int fontSize, int fontStyle) {
        this.font = new Font(fontName, fontStyle, fontSize);
    }

    /**
     * 设置取模方向
     *
     * @param mode MODE_COLUMN_MAJOR 或 MODE_ROW_MAJOR
     */
    public void setMode(int mode) {
        this.mode = mode;
    }

    /**
     * 设置是否反转字节内位顺序
     */
    public void setReverseByte(boolean reverseByte) {
        this.reverseByte = reverseByte;
    }

    /**
     * 设置是否反转整体数据顺序
     */
    public void setReverseOrder(boolean reverseOrder) {
        this.reverseOrder = reverseOrder;
    }

    /**
     * 设置是否启用抗锯齿
     */
    public void setAntiAliasing(boolean antiAliasing) {
        this.antiAliasing = antiAliasing;
    }

    /**
     * 生成字模数据
     *
     * @param text 要取模的文本
     * @return 字模数据字节数组
     */
    public byte[] generateFontData(String text) {
        Dimension size = calculateTextSize(text);
        BufferedImage image = createTextImage(text, 16, 16);
        try {
            ImageIO.write(image, "png", new File("d:/hello/text.png"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return mode == MODE_COLUMN_MAJOR ?
                extractColumnMajorData(image, 16, 16) :
                extractRowMajorData(image, size.width, size.height);
    }

    /**
     * 生成字模数据并格式化为C语言数组格式
     */
    public String generateFontDataAsCArray(String text, String arrayName) {
        byte[] data = generateFontData(text);
        Dimension size = calculateTextSize(text);

        StringBuilder sb = new StringBuilder();
        sb.append("// ").append(text).append(" 字模数据\n");
        sb.append("// 宽度: ").append(size.width).append(" 像素, 高度: ").append(size.height).append(" 像素\n");
        sb.append("const unsigned char ").append(arrayName).append("[] = {");

        for (int i = 0; i < data.length; i++) {
            if (i % 16 == 0) {
                sb.append("\n    ");
            }
            sb.append(String.format("0x%02X", data[i] & 0xFF));
            if (i < data.length - 1) {
                sb.append(", ");
            }
        }

        sb.append("\n};\n");
        return sb.toString();
    }

    /**
     * 计算文本尺寸
     */
    private Dimension calculateTextSize(String text) {
        BufferedImage dummyImage = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_BINARY);
        Graphics2D g2d = dummyImage.createGraphics();
        setupRenderingHints(g2d);

        FontMetrics metrics = g2d.getFontMetrics(font);
        int width = metrics.stringWidth(text);
        int height = metrics.getHeight();

        g2d.dispose();

        return new Dimension(width, height);
    }

    /**
     * 创建文本图像
     */
    private BufferedImage createTextImage(String text, int width, int height) {
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY);
        Graphics2D g2d = image.createGraphics();
        setupRenderingHints(g2d);

        // 白色背景
        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, width, height);

        // 黑色文本
        g2d.setColor(Color.BLACK);
        g2d.setFont(font);

        FontMetrics metrics = g2d.getFontMetrics();
        int y = metrics.getAscent();
        g2d.drawString(text, 0, y);

        g2d.dispose();

        return image;
    }

    /**
     * 设置渲染参数
     */
    private void setupRenderingHints(Graphics2D g2d) {
        if (antiAliasing) {
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        } else {
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
        }
    }

    /**
     * 提取列主序点阵数据(PCtoLCD2002列行式)
     */
    private byte[] extractColumnMajorData(BufferedImage image, int width, int height) {
        // 计算需要的字节数 (width * ceil(height/8))
        int bytesPerColumn = (int) Math.ceil(height / 8.0);
        int totalBytes = width * bytesPerColumn;

        ByteBuffer buffer = ByteBuffer.allocate(totalBytes);

        // 是否反转列顺序
        int columnStart = reverseOrder ? width - 1 : 0;
        int columnStep = reverseOrder ? -1 : 1;

        for (int x = columnStart; reverseOrder ? x >= 0 : x < width; x += columnStep) {
            for (int byteIdx = 0; byteIdx < bytesPerColumn; byteIdx++) {
                byte columnByte = 0;
                for (int bit = 0; bit < 8; bit++) {
                    int pixelY = byteIdx * 8 + bit;
                    if (pixelY < height) {
                        int rgb = image.getRGB(x, pixelY);
                        // 黑色像素设置对应位
                        if (rgb == Color.BLACK.getRGB()) {
                            int actualBit = reverseByte ? (7 - bit) : bit;
                            columnByte |= (1 << actualBit);
                        }
                    }
                }
                buffer.put(columnByte);
            }
        }

        return buffer.array();
    }

    /**
     * 提取行主序点阵数据(PCtoLCD2002行列式)
     */
    private byte[] extractRowMajorData(BufferedImage image, int width, int height) {
        // 计算每行需要的字节数 (ceil(width/8))
        int bytesPerRow = (int) Math.ceil(width / 8.0);
        int totalBytes = bytesPerRow * height;

        ByteBuffer buffer = ByteBuffer.allocate(totalBytes);

        // 是否反转行顺序
        int rowStart = reverseOrder ? height - 1 : 0;
        int rowStep = reverseOrder ? -1 : 1;

        for (int y = rowStart; reverseOrder ? y >= 0 : y < height; y += rowStep) {
            for (int byteIdx = 0; byteIdx < bytesPerRow; byteIdx++) {
                byte rowByte = 0;
                for (int bit = 0; bit < 8; bit++) {
                    int pixelX = byteIdx * 8 + bit;
                    if (pixelX < width) {
                        int rgb = image.getRGB(pixelX, y);
                        // 黑色像素设置对应位
                        if (rgb == Color.BLACK.getRGB()) {
                            int actualBit = reverseByte ? (7 - bit) : bit;
                            rowByte |= (1 << actualBit);
                        }
                    }
                }
                buffer.put(rowByte);
            }
        }

        return buffer.array();
    }

    /**
     * 生成字模数据并格式化为十六进制字符串
     */
    public String generateHexString(String text) {
        byte[] data = generateFontData(text);
        StringBuilder sb = new StringBuilder();

        for (byte b : data) {
            sb.append(String.format("%02X ", b & 0xFF));
        }

        return sb.toString().trim();
    }

    /**
     * 生成字模数据并格式化为二进制字符串
     */
    public String generateBinaryString(String text) {
        byte[] data = generateFontData(text);
        StringBuilder sb = new StringBuilder();

        for (byte b : data) {
            String binary = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
            if (reverseByte) {
                binary = new StringBuilder(binary).reverse().toString();
            }
            sb.append(binary).append(" ");
        }

        return sb.toString().trim();
    }
}
java 复制代码
package cn.com.ut.semp.hello;

import cn.hutool.core.util.HexUtil;

import java.awt.*;
import java.util.Arrays;

public class PCToLCD2002Example {
    public static void main(String[] args) {
        // 创建取模器实例(模拟PCtoLCD2002)
        PCToLCD2002Emulator emulator = new PCToLCD2002Emulator("宋体", 16, Font.PLAIN);

        // 配置取模参数(与PCtoLCD2002设置一致)
        emulator.setMode(PCToLCD2002Emulator.MODE_COLUMN_MAJOR); // 列行式
        emulator.setReverseByte(false);  // 字节内位反转
        emulator.setReverseOrder(false); // 不反转整体顺序
        emulator.setAntiAliasing(false); // 禁用抗锯齿

        // 取模示例
        String text = "优";
//
//        // 生成字模数据
        byte[] fontData = emulator.generateFontData(text);
        System.out.println(Arrays.toString(fontData));
        for (int i = 0; i < fontData.length; i++) {
            System.out.print("0x" + HexUtil.toHex(fontData[i] & 0xFF) + ",");
            if ((i + 1) % 16 == 0) {
                System.out.println();
            }
        }
        System.out.println("字模数据字节数: " + fontData.length);
        

    }
}

总结

可以使用PCToLCD工具熟悉点阵图

相关推荐
_extraordinary_6 小时前
Java 多线程(一)
java·开发语言
网安Ruler6 小时前
第49天:Web开发-JavaEE应用&SpringBoot栈&模版注入&Thymeleaf&Freemarker&Velocity
java·spring boot·后端
cci7 小时前
使用nmcli连接网络
后端
奔跑吧邓邓子7 小时前
【Java实战㉟】Spring Boot与MyBatis:数据库交互的进阶之旅
java·spring boot·实战·mybatis·数据库交互
赛姐在努力.7 小时前
Spring DI详解--依赖注入的三种方式及优缺点分析
java·mysql·spring
雨中散步撒哈拉7 小时前
13、做中学 | 初一下期 Golang数组与切片
开发语言·后端·golang
0wioiw07 小时前
Go基础(③Cobra)
开发语言·后端·golang
IvanCodes8 小时前
六、Docker 核心技术:Dockerfile 指令详解
java·数据库·docker
_oP_i8 小时前
Java 服务接口中解决跨域(CORS,Cross-Origin Resource Sharing)问题
java·开发语言