用汉字库16X16实现输入文字和图片生成,像素文字图片。
https://download.csdn.net/download/qq_33729058/89661391
hzk16.dat 下载地址
Java代码
package com.yk.javalearn.day1;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
/**
* 该类用于从 HZK16 字库文件中提取汉字的点阵数据。
* 通过 GB2312 编码找到汉字在字库中的位置,并提取该汉字的 16x16 点阵图。
*
* @Author: 61
*/
public class HZK16 {
/**
* 通过内容(汉字)获取其对应的点阵图,点阵图以 List<List<Integer>> 的形式返回。
* 每个汉字的点阵图大小为 16x16 像素。
*
* @param content 汉字内容
* @return 汉字的点阵图
*/
public static List<List<Integer>> getBitMapByContent(String content) {
try {
// 获取 HZK16 字库文件的数据
byte[] hzk16Data = loadHZK16Data();
// 将输入的汉字转换为 GB2312 编码格式
byte[] gb2312Bytes = content.getBytes(Charset.forName("GB2312"));
// 计算该汉字在字库中的索引
int charIndex = getCharIndex(gb2312Bytes);
// 根据索引从字库中获取对应汉字的点阵数据
byte[] charData = getCharData(hzk16Data, charIndex);
// 将点阵数据转化为 List<List<Integer>> 形式并返回
return convertArrayToList(extractBitmap(charData));
} catch (IOException e) {
e.printStackTrace();
}
// 如果发生异常,返回空的点阵列表
return new ArrayList<>();
}
/**
* 从类路径中读取 HZK16 字库文件,并返回其字节数组。
*
* @return HZK16 字库文件的字节数据
* @throws IOException 如果文件不存在或读取错误
*/
private static byte[] loadHZK16Data() throws IOException {
// 获取 HZK16 字库文件的输入流
try (InputStream inputStream = ClassLoader.getSystemResourceAsStream("hzk/HZK16.dat")) {
if (inputStream == null) {
// 如果文件未找到,抛出异常
throw new IOException("Resource not found: " + "hzk/HZK16.dat");
}
// 使用 ByteArrayOutputStream 来存储读取的数据
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] data = new byte[1024]; // 每次读取 1KB 的数据
int bytesRead;
// 循环读取数据,直到文件末尾
while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, bytesRead);
}
// 返回文件的字节数组
return buffer.toByteArray();
}
}
/**
* 根据 GB2312 编码计算出汉字在字库中的索引位置。
*
* @param gb2312Bytes GB2312 编码格式的字节数组
* @return 汉字在字库中的索引
*/
private static int getCharIndex(byte[] gb2312Bytes) {
if (gb2312Bytes.length != 2) {
// GB2312 编码的汉字应该由 2 个字节组成,如果不是则抛出异常
throw new IllegalArgumentException("GB2312 encoding should have exactly 2 bytes.");
}
// 获取高位字节和低位字节
int highByte = gb2312Bytes[0] & 0xFF; // 高位字节
int lowByte = gb2312Bytes[1] & 0xFF; // 低位字节
// 计算汉字在字库中的索引位置
int index = (highByte - 0xA1) * 94 + (lowByte - 0xA1); // 根据 GB2312 编码计算索引
return index;
}
// 每个汉字的点阵图大小为 16x16 像素,因此需要 32 个字节来存储。
private static final int CHAR_SIZE = 32; // 16x16 bits = 32 bytes
/**
* 根据索引从字库数据中提取特定汉字的点阵数据。
*
* @param data 字库的字节数据
* @param index 汉字在字库中的索引
* @return 汉字的点阵字节数组
*/
private static byte[] getCharData(byte[] data, int index) {
// 计算汉字在字库中的起始位置
int start = index * CHAR_SIZE;
int end = start + CHAR_SIZE;
// 创建字节数组来存储该汉字的点阵数据
byte[] charData = new byte[CHAR_SIZE];
// 从字库数据中复制对应的点阵数据到 charData 数组
System.arraycopy(data, start, charData, 0, CHAR_SIZE);
return charData;
}
/**
* 将汉字的点阵字节数据转换为二维数组,表示为 16x16 的点阵图。
*
* @param charData 汉字的点阵字节数组
* @return 16x16 的点阵图二维数组
*/
private static int[][] extractBitmap(byte[] charData) {
// 创建 16x16 的二维数组来表示点阵图
int[][] matrix = new int[16][16];
// 遍历 16x16 的每一个像素
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) {
// 计算当前像素所在的字节索引和位索引
int byteIndex = i * 2 + j / 8;
int bitIndex = j % 8;
// 检查该像素是否为 1(使用位操作来提取具体的位)
if ((charData[byteIndex] & (0x80 >> bitIndex)) != 0) {
matrix[i][j] = 1; // 点亮的像素用 1 表示
}
}
}
return matrix;
}
/**
* 将 16x16 的二维数组转换为 List<List<Integer>> 格式,以方便后续操作。
*
* @param array 16x16 的点阵图二维数组
* @return 转换后的 List<List<Integer>> 格式
*/
private static List<List<Integer>> convertArrayToList(int[][] array) {
// 创建 List<List<Integer>> 来存储结果
List<List<Integer>> list = new ArrayList<>();
// 遍历二维数组,并将每一行转换为 List<Integer>
for (int[] row : array) {
List<Integer> rowList = new ArrayList<>();
for (int element : row) {
rowList.add(element); // 将每一个像素添加到行列表中
}
// 将行列表添加到最终列表中
list.add(rowList);
}
return list;
}
/**
* 将 List<List<Integer>> 格式的点阵图转换为字符串,并在每一行后添加换行符。
*
* @param list 点阵图的 List<List<Integer>> 格式
* @return 点阵图的字符串表示
*/
public static String listToString(List<List<Integer>> list) {
// 使用 StringBuilder 来拼接字符串
StringBuilder sb = new StringBuilder();
// 遍历 List<List<Integer>> 的每一行
for (List<Integer> row : list) {
sb.append(row.toString()); // 将行转换为字符串并添加到 StringBuilder 中
sb.append(System.lineSeparator()); // 添加换行符
}
// 返回最终的字符串
return sb.toString();
}
}
代码详细说明:
getBitMapByContent
:获取指定汉字的点阵数据,并将其转换为 List<List> 形式返回。loadHZK16Data
:从类路径中加载 HZK16 字库文件,将其读取为字节数组。getCharIndex
:根据汉字的 GB2312 编码计算该汉字在字库中的索引。getCharData
:根据索引从字库中提取汉字的点阵数据。extractBitmap
:将汉字的点阵字节数据转化为 16x16 的二维数组,表示汉字的点阵图。convertArrayToList
:将二维数组转换为 List<List> 形式。listToString
:将 List<List> 转换为带有换行符的字符串形式,方便打印和显示。
html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Concatenation with AJAX</title>
<style>
/* 样式可以根据实际需求调整 */
img {
max-width: 100%;
max-height: 100px;
margin-top: 10px;
}
.container {
display: grid;
grid-template-columns: 1fr 1fr; /* 两列,每列占据相同的宽度 */
gap: 10px; /* 列之间的间距 */
}
.box {
padding: 20px;
box-sizing: border-box;
}
.box1 {
background-color: #4cd508;
}
.box2 {
background-color: #33a7cc;
}
</style>
</head>
<body>
<div class="container">
<!-- 文件上传输入框 -->
<div class="box box1">
<input id="fileInput" type="file" multiple/>
</div>
<!-- 中文字符输入框 -->
<div class="box box2">
<input id="textInput" type="text" value="我" maxlength="1" placeholder="输入一个中文字符"/>
<p id="errorMessage" style="color: red; display: none;">请输入一个中文字符</p>
<button id="concatenateBtn">拼接图片</button>
</div>
<!-- 图片预览和下载按钮 -->
<div>
<img id="concatenatedImage" alt="Concatenated Image" style="display: none;"/>
<a id="downloadBtn" style="display: none;">下载图片</a>
</div>
<div id="previewImages"></div> <!-- 图片预览区域 -->
</div>
<script>
const selectedFiles = []; // 存储用户选择的图片文件
let points = []; // 存储字库点阵图的坐标信息
document.getElementById('fileInput').addEventListener('change', handleFileChange); // 监听文件输入框的改变事件
document.getElementById('concatenateBtn').addEventListener('click', concatenateImages); // 监听拼接按钮点击事件
// 发送 AJAX 请求从服务器获取汉字点阵图数据
function postData(key) {
return new Promise((resolve, reject) => {
const url = `/api/lattice/bitmap?content=${encodeURIComponent(key)}`;
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
points = JSON.parse(xhr.responseText); // 获取汉字点阵图数据
resolve(points); // 返回点阵图数据
} else {
reject(xhr.statusText); // 请求失败时返回错误
}
}
};
xhr.send(); // 发送请求
});
}
// 处理文件输入的事件
function handleFileChange(event) {
selectedFiles.length = 0; // 清空之前选择的文件
const files = Array.from(event.target.files); // 将文件列表转换为数组
getPointMap().then(() => {
// 调整图片尺寸并显示预览
Promise.all(files.map(resizeImage)).then(resizedFiles => {
selectedFiles.push(...resizedFiles); // 将调整后的文件保存到 selectedFiles
previewImages(); // 显示图片预览
});
});
}
// 将图片调整为 100x100 像素大小
function resizeImage(file) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 100;
canvas.height = 100;
ctx.drawImage(img, 0, 0, 100, 100); // 绘制图片到 100x100 的 canvas 上
canvas.toBlob(blob => {
resolve(new File([blob], file.name, {type: file.type})); // 将 canvas 转换为 Blob 对象并返回新文件
}, file.type);
};
img.onerror = reject;
img.src = URL.createObjectURL(file); // 加载用户上传的图片
});
}
// 显示图片预览
function previewImages() {
const previewContainer = document.getElementById('previewImages');
previewContainer.innerHTML = ''; // 清空之前的预览
selectedFiles.forEach(file => {
const reader = new FileReader();
reader.onload = e => {
const img = document.createElement('img');
img.src = e.target.result; // 设置图片预览的源
previewContainer.appendChild(img); // 将图片添加到预览区域
};
reader.readAsDataURL(file); // 读取文件数据并生成预览
});
}
// 拼接图片并生成最终图像
function concatenateImages() {
if (selectedFiles.length === 0) {
alert('请先上传图片'); // 如果没有上传图片,显示提示
return;
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 1600; // 设置 canvas 的宽度为 1600 像素
canvas.height = 1600; // 设置 canvas 的高度为 1600 像素
// 加载所有选中的图片
const loadImages = selectedFiles.map(file => {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = URL.createObjectURL(file); // 为每个文件生成 URL
img.onload = () => {
resolve(img); // 当图片加载完成时,返回图片对象
};
img.onerror = reject;
});
});
Promise.all(loadImages).then(images => {
const imgLen = images.length - 1;
let countImg = 0; // 用于循环图片
return getPointMap().then(pMaps => {
// 根据字库中的点阵数据将图片绘制到 canvas 上
for (let i = 0; i < pMaps.length; i++) {
ctx.drawImage(images[countImg], pMaps[i][1], pMaps[i][0], 100, 100); // 按照点阵图的位置绘制图片
countImg = (countImg === imgLen) ? 0 : countImg + 1; // 轮流使用图片
}
// 将 canvas 生成的图片设置为 img 元素的 src
const concatenatedImageElement = document.getElementById('concatenatedImage');
const downloadBtn = document.getElementById('downloadBtn');
const imageUrl = canvas.toDataURL('image/png'); // 生成最终拼接图片的 URL
concatenatedImageElement.src = imageUrl;
concatenatedImageElement.style.display = 'block'; // 显示拼接后的图片
downloadBtn.href = imageUrl;
downloadBtn.download = 'concatenated-image.png'; // 设置下载按钮的文件名
downloadBtn.style.display = 'inline'; // 显示下载按钮
// 清空预览图片
document.getElementById('previewImages').innerHTML = '';
// 清空已选择的文件
selectedFiles.length = 0;
});
}).catch(error => {
console.error('Failed to load images:', error); // 处理加载图片时的错误
});
}
// 获取汉字点阵图的数据
function getPointMap() {
const key = document.getElementById('textInput').value; // 获取输入的汉字
return postData(key).then(() => {
const pLen = 100; // 每个点的大小为 100 像素
const result = [];
for (let i = 0; i < points.length; i++) {
for (let j = 0; j < points[i].length; j++) {
if (points[i][j] === 1) {
result.push([i * pLen, j * pLen]); // 根据点阵图生成拼接图片的位置
}
}
}
return result; // 返回图片绘制的坐标
});
}
// 监听文本输入框的输入事件,确保输入为一个中文字符
document.getElementById('textInput').addEventListener('input', function () {
const input = this.value;
const chineseCharRegex = /^[\u4e00-\u9fa5]$/; // 正则表达式匹配一个中文字符
if (!chineseCharRegex.test(input)) {
document.getElementById('errorMessage').style.display = 'block'; // 如果输入不是中文字符,显示错误提示
} else {
document.getElementById('errorMessage').style.display = 'none'; // 输入合法时隐藏错误提示
}
});
</script>
</body>
</html>
代码解释:
-
文件上传与图片处理:
- 用户可以通过文件输入框上传多个图片。
- 上传的图片会被缩放到 100x100 像素大小,并且会显示预览。
- 图片会按上传的顺序排列和显示在网页中。
-
中文字符输入:
- 用户可以在文本框中输入一个中文字符(例如汉字)。
- 输入的字符将被用于请求服务器生成一个字符的点阵图,这个点阵图会用于拼接图片的布局。
-
图片拼接:
- 当用户点击“拼接图片”按钮时,网页会根据生成的点阵图,将上传的图片按照点阵图中的位置拼接在一起。
- 拼接完成后,生成的图像会显示在页面中,并提供一个下载链接。
-
表单验证与错误处理:
- 如果用户输入的不是合法的中文字符,会显示错误提示信息。
- 如果用户没有上传图片,会显示相应的提示。
html 、字库文件位置。 spring实现一个接口入口。
演示如下:
这只是一个实现思路,可优化地方很多。如果大家喜欢可以给我提需要什么功能我可以增加,如果有需要的我可以共享这个代码库做成一件安装使用的。