使用Java实现简单的墨水屏点阵图效果
作者:考虑考虑
前言
点阵图是由称作像素(图片元素)的单个点组成的。这些点可以进行不同的排列和染色以构成图样。当放大位图时,可以看见赖以构成整个图像的无数单个方块。点阵图的格式包括PNG、TIFF、BMP、JPEG等,通常在放大时会显得参差不齐,但从远处观看时颜色和形状又显得是连续的
点阵图
点阵图分为行列式和列行式取值,如图所示
大致的点阵图为
优(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显示亮
**备注:**根据字体大小和高度这些,生成的字节不一样
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; } }
测试例子
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(); } } } }
这个是列行式
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(); } }
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
工具熟悉点阵图
到此这篇关于使用Java实现简单的墨水屏点阵图效果的文章就介绍到这了,更多相关Java墨水屏点阵图内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!