图片转换工具开发

This commit is contained in:
2025-06-18 16:36:01 +08:00
parent 2795f725da
commit dbd26ccdc5
13 changed files with 754 additions and 173 deletions

View File

@@ -0,0 +1,72 @@
package com.njcn.common.utils;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
/**
* @author hongawen
* @version 1.0
* @data 2025/6/17 15:17
*/
public class BinaryDataConverter {
/**
* 使用Base64编码转换二进制数据为字符串
* 推荐使用,最安全可靠的方式
*/
public static String toBase64String(byte[] binaryData) {
return Base64.getEncoder().encodeToString(binaryData);
}
/**
* 使用Base64解码字符串为二进制数据
*/
public static byte[] fromBase64String(String base64String) {
return Base64.getDecoder().decode(base64String);
}
/**
* 使用UTF-8编码转换二进制数据为字符串
* 注意:仅适用于文本数据,不适用于二进制数据
*/
public static String toUtf8String(byte[] binaryData) {
return new String(binaryData, StandardCharsets.UTF_8);
}
/**
* 使用UTF-8编码转换字符串为二进制数据
*/
public static byte[] fromUtf8String(String text) {
return text.getBytes(StandardCharsets.UTF_8);
}
/**
* 使用十六进制字符串表示二进制数据
* 适用于需要可读性的场景
*/
public static String toHexString(byte[] binaryData) {
StringBuilder hexString = new StringBuilder();
for (byte b : binaryData) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
/**
* 从十六进制字符串转换为二进制数据
*/
public static byte[] fromHexString(String hexString) {
int len = hexString.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
+ Character.digit(hexString.charAt(i + 1), 16));
}
return data;
}
}

View File

@@ -0,0 +1,58 @@
package com.njcn.common.utils.images;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* @author hongawen
* @version 1.0
* @data 2025/6/18 11:17
*/
public class BinFileReader {
public static void main(String[] args) {
String filePath = "C:\\Users\\hongawen\\Desktop\\temp\\qrc.bin";
try (InputStream inputStream = new FileInputStream(filePath)) {
printHexDump(inputStream);
} catch (IOException e) {
System.err.println("读取文件时发生错误: " + e.getMessage());
}
}
public static void printHexDump(InputStream stream) throws IOException {
int bytesPerLine = 16;
byte[] buffer = new byte[bytesPerLine];
int bytesRead;
long offset = 0;
while ((bytesRead = stream.read(buffer)) != -1) {
// 打印偏移量 (地址)
System.out.printf("%08X: ", offset);
// 打印十六进制值
StringBuilder hexString = new StringBuilder();
StringBuilder asciiString = new StringBuilder();
for (int i = 0; i < bytesPerLine; i++) {
if (i < bytesRead) {
byte b = buffer[i];
hexString.append(String.format("%02X ", b));
// 将非打印字符替换为 '.'
asciiString.append(isPrintableChar(b) ? (char) b : '.');
} else {
// 用空格填充,如果最后一行不满
hexString.append(" ");
}
if (i == 7) { // 在中间加一个空格,方便阅读
hexString.append(" ");
}
}
System.out.printf("%-50s %s%n", hexString.toString(), asciiString.toString());
offset += bytesRead;
}
}
private static boolean isPrintableChar(byte b) {
return b >= 32 && b <= 126;
}
}

View File

@@ -1,6 +1,7 @@
package com.njcn.common.utils.images;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.ByteBuffer;
@@ -15,188 +16,81 @@ import java.nio.ByteOrder;
*/
public class ImageConverter {
/**
* 将BufferedImage转换为BMP格式的字节数组
* @param image BufferedImage对象
* @return BMP格式的字节数组
* @throws IOException 如果转换过程中发生IO错误
* 将一个 BufferedImage 对象转换为我们自定义的 BMPF .bin 格式。
*/
public static byte[] convertToBMP(BufferedImage image) throws IOException {
public static byte[] convertToBinFormat(BufferedImage image) throws IOException {
final int targetWidth = 148;
final int targetHeight = 148;
// 1. 创建一个新的、与目标格式(16-bpp)匹配的、固定尺寸的画布
BufferedImage newImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_USHORT_GRAY);
Graphics2D g2d = newImage.createGraphics();
// 2. 用白色(0xFFFF)填充背景
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, targetWidth, targetHeight);
// 3. 将原始图像居中绘制到新画布上
// Graphics2D 会自动处理颜色转换
int x = (targetWidth - image.getWidth()) / 2;
int y = (targetHeight - image.getHeight()) / 2;
g2d.drawImage(image, x, y, null);
g2d.dispose();
// 4. 后续操作基于新的、尺寸和位深度都正确的图像
final int bpp = 16;
// Stride: 每行像素数据的字节数。对于无填充的16bpp图像等于 宽度 * 2
final int stride = targetWidth * (bpp / 8);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (DataOutputStream out = new DataOutputStream(baos)) {
// --- 写入文件头 (严格44字节) ---
// 根据C代码 qrc_bitmap.pData = ...[44/4], 我们确认头部为44字节
ByteBuffer headerBuffer = ByteBuffer.allocate(44);
// 头部使用小端序
headerBuffer.order(ByteOrder.LITTLE_ENDIAN);
// 写入BMP文件头
writeBMPHeader(baos, image.getWidth(), image.getHeight());
// 0-3: 魔术字
headerBuffer.put(new byte[]{'b', 'm', 'p', 'f'});
// 4-5: 宽度
headerBuffer.putShort((short) targetWidth);
// 6-7: 高度
headerBuffer.putShort((short) targetHeight);
// 8-9: 行字节数
headerBuffer.putShort((short) stride);
// 10-11: 位深度
headerBuffer.putShort((short) bpp);
// 写入像素数据
for (int y = image.getHeight() - 1; y >= 0; y--) {
for (int x = 0; x < image.getWidth(); x++) {
int rgb = image.getRGB(x, y);
// 写入BGR格式BMP格式要求
// B
baos.write(rgb & 0xFF);
// G
baos.write((rgb >> 8) & 0xFF);
// R
baos.write((rgb >> 16) & 0xFF);
}
// 行对齐每行必须是4字节的倍数
int padding = (4 - (image.getWidth() * 3) % 4) % 4;
for (int i = 0; i < padding; i++) {
baos.write(0);
// 12-43: 剩余32字节严格参照样本文件(out.txt)填充
headerBuffer.put(new byte[]{
// 12 bytes of zeros
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 20 bytes of specific data from sample
(byte)0x44, (byte)0x3A, (byte)0x5C, (byte)0xD7, (byte)0xD4, (byte)0xB6, (byte)0xAF, (byte)0xBC,
(byte)0xEC, (byte)0xB2, (byte)0xE2, (byte)0xC6, (byte)0xBD, (byte)0xCC, (byte)0xA8, (byte)0x5C,
(byte)0x4C, (byte)0xAB, 0x00, 0x00
});
// 将44字节头部一次性写入
out.write(headerBuffer.array());
// --- 写入像素数据 ---
// 为确保数据布局的绝对正确,放弃直接访问数据缓冲区(getDataBuffer)
// 改为使用逐点扫描(getSample)的方式。这可以避免任何因Java内部内存对齐
// 或行填充(padding)导致的图像数据错位问题。
for (int r = 0; r < targetHeight; r++) {
for (int c = 0; c < targetWidth; c++) {
// getSample(x, y, band) 返回的是一个int, 但其值就是16位的ushort值
int pixelValue = newImage.getRaster().getSample(c, r, 0);
// 依旧使用大端序写入像素数据,这是上次测试得出的结论
out.writeShort((short) pixelValue);
}
}
}
return baos.toByteArray();
}
/**
* 将BMP格式的字节数组转换为BIN格式
* @param bmpData BMP格式的字节数组
* @return BIN格式的字节数组
*/
public static byte[] convertBMPToBIN(byte[] bmpData) {
// 跳过BMP文件头54字节
int offset = 54;
int width = ByteBuffer.wrap(bmpData, 18, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();
int height = ByteBuffer.wrap(bmpData, 22, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();
// 计算每行的字节数(包括对齐)
int rowSize = ((width * 3 + 3) / 4) * 4;
// 创建BIN数据数组
byte[] binData = new byte[width * height];
int binIndex = 0;
// 转换像素数据
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int bmpIndex = offset + (height - 1 - y) * rowSize + x * 3;
// 将BGR转换为灰度值
int b = bmpData[bmpIndex] & 0xFF;
int g = bmpData[bmpIndex + 1] & 0xFF;
int r = bmpData[bmpIndex + 2] & 0xFF;
// 使用加权平均计算灰度值
int gray = (int) (0.299 * r + 0.587 * g + 0.114 * b);
// 二值化阈值设为128
binData[binIndex++] = (byte) (gray > 128 ? 1 : 0);
}
}
return binData;
}
/**
* 将BufferedImage直接转换为BIN格式
* @param image BufferedImage对象
* @return BIN格式的字节数组
* @throws IOException 如果转换过程中发生IO错误
*/
public static byte[] convertToBIN(BufferedImage image) throws IOException {
byte[] bmpData = convertToBMP(image);
return convertBMPToBIN(bmpData);
}
/**
* 写入BMP文件头
* @param out 输出流
* @param width 图片宽度
* @param height 图片高度
* @throws IOException 如果写入过程中发生IO错误
*/
private static void writeBMPHeader(OutputStream out, int width, int height) throws IOException {
// 文件头14字节
out.write('B');
out.write('M');
writeInt(out, 54 + width * height * 3); // 文件大小
writeInt(out, 0); // 保留
writeInt(out, 54); // 数据偏移量
// 信息头40字节
writeInt(out, 40); // 信息头大小
writeInt(out, width); // 宽度
writeInt(out, height); // 高度
writeShort(out, 1); // 颜色平面数
writeShort(out, 24); // 每像素位数
writeInt(out, 0); // 压缩方式
writeInt(out, width * height * 3); // 图像数据大小
writeInt(out, 0); // 水平分辨率
writeInt(out, 0); // 垂直分辨率
writeInt(out, 0); // 使用的颜色数
writeInt(out, 0); // 重要颜色数
}
/**
* 写入一个整数(小端序)
* @param out 输出流
* @param value 要写入的整数
* @throws IOException 如果写入过程中发生IO错误
*/
private static void writeInt(OutputStream out, int value) throws IOException {
out.write(value & 0xFF);
out.write((value >> 8) & 0xFF);
out.write((value >> 16) & 0xFF);
out.write((value >> 24) & 0xFF);
}
/**
* 写入一个短整数(小端序)
* @param out 输出流
* @param value 要写入的短整数
* @throws IOException 如果写入过程中发生IO错误
*/
private static void writeShort(OutputStream out, int value) throws IOException {
out.write(value & 0xFF);
out.write((value >> 8) & 0xFF);
}
/**
* 将BufferedImage保存为BMP文件
* @param image BufferedImage对象
* @param filePath 保存路径
* @throws IOException 如果保存过程中发生IO错误
*/
public static void saveAsBMP(BufferedImage image, String filePath) throws IOException {
byte[] bmpData = convertToBMP(image);
try (FileOutputStream fos = new FileOutputStream(filePath)) {
fos.write(bmpData);
}
}
/**
* 将BufferedImage保存为BIN文件
* @param image BufferedImage对象
* @param filePath 保存路径
* @throws IOException 如果保存过程中发生IO错误
*/
public static void saveAsBIN(BufferedImage image, String filePath) throws IOException {
byte[] binData = convertToBIN(image);
try (FileOutputStream fos = new FileOutputStream(filePath)) {
fos.write(binData);
}
}
/**
* 将BMP数据保存为文件
* @param bmpData BMP格式的字节数组
* @param filePath 保存路径
* @throws IOException 如果保存过程中发生IO错误
*/
public static void saveBMPToFile(byte[] bmpData, String filePath) throws IOException {
try (FileOutputStream fos = new FileOutputStream(filePath)) {
fos.write(bmpData);
}
}
/**
* 将BIN数据保存为文件
* @param binData BIN格式的字节数组
* @param filePath 保存路径
* @throws IOException 如果保存过程中发生IO错误
*/
public static void saveBINToFile(byte[] binData, String filePath) throws IOException {
try (FileOutputStream fos = new FileOutputStream(filePath)) {
fos.write(binData);
}
}
}