图片转换工具开发
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user