前言
点阵图
是由称作像素(图片元素)的单个点组成的。这些点可以进行不同的排列和染色以构成图样。当放大位图时,可以看见赖以构成整个图像的无数单个方块。点阵图的格式包括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
工具熟悉点阵图