Java类无法正确生成RGB及Sepia BMP图像问题求助
修复BMP图像生成损坏问题
核心问题分析
- 单通道图像数据结构错误:24位BMP每个像素需要3字节(BGR顺序),原代码仅保留单个通道的字节,导致图像数据长度仅为原数据的1/3,完全不符合格式要求。正确做法是保留目标通道值,将另外两个通道设为0。
- 未处理行填充字节:BMP要求每行像素数据的字节数必须是4的倍数,不足时需补0,原代码完全忽略该规则,导致图像数据结构混乱。
- BMP头字段不完整/错误:未完整填充文件大小、宽高的4字节小端数据;未设置位深度(必须为24,对应24位真彩色);压缩方式等关键字段未初始化,导致图像无法被识别。
- Sepia颜色值溢出:计算后的颜色值可能超过255,未限制在0-255范围内,导致字节溢出为负数,颜色异常。
- 流读取不保证完整性:
read()方法无法确保一次性读取所有字节,需循环读取直到数据完整。
修复后的完整代码
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; public class BmpHandlerCore { public static void separateRGB(String fileName) { try (FileInputStream fileInputStream = new FileInputStream(fileName)) { byte[] header = new byte[54]; readFully(fileInputStream, header); int width = byteArrayToInt(header, 18); int height = byteArrayToInt(header, 22); int imageSize = byteArrayToInt(header, 34); byte[] imageData = new byte[imageSize]; readFully(fileInputStream, imageData); ArrayList<Byte> redChannel = new ArrayList<>(); ArrayList<Byte> greenChannel = new ArrayList<>(); ArrayList<Byte> blueChannel = new ArrayList<>(); int rowPadding = calculateRowPadding(width); for (int i = 0; i < imageSize; ) { for (int pixel = 0; pixel < width; pixel++) { byte b = imageData[i++]; byte g = imageData[i++]; byte r = imageData[i++]; // 红色通道:保留R,G/B设为0 redChannel.add((byte) 0); redChannel.add((byte) 0); redChannel.add(r); // 绿色通道:保留G,R/B设为0 greenChannel.add((byte) 0); greenChannel.add(g); greenChannel.add((byte) 0); // 蓝色通道:保留B,R/G设为0 blueChannel.add(b); blueChannel.add((byte) 0); blueChannel.add((byte) 0); } // 跳过原图像填充字节,同时为新图像添加对应填充字节 for (int p = 0; p < rowPadding; p++) { i++; redChannel.add((byte) 0); greenChannel.add((byte) 0); blueChannel.add((byte) 0); } } saveImage(redChannel, width, height, "Image-red.bmp"); saveImage(greenChannel, width, height, "Image-green.bmp"); saveImage(blueChannel, width, height, "Image-blue.bmp"); } catch (IOException e) { e.printStackTrace(); } } public static void generateSepiaTone(String fileName) { try (FileInputStream fileInputStream = new FileInputStream(fileName)) { byte[] header = new byte[54]; readFully(fileInputStream, header); int width = byteArrayToInt(header, 18); int height = byteArrayToInt(header, 22); int imageSize = byteArrayToInt(header, 34); byte[] imageData = new byte[imageSize]; readFully(fileInputStream, imageData); ArrayList<Byte> sepiaTone = new ArrayList<>(); int rowPadding = calculateRowPadding(width); for (int i = 0; i < imageSize; ) { for (int pixel = 0; pixel < width; pixel++) { int blue = imageData[i++] & 0xFF; int green = imageData[i++] & 0xFF; int red = imageData[i++] & 0xFF; // 计算Sepia值并限制在0-255范围内 int sepiaRed = clamp((int) (0.393 * red + 0.769 * green + 0.189 * blue)); int sepiaGreen = clamp((int) (0.349 * red + 0.686 * green + 0.168 * blue)); int sepiaBlue = clamp((int) (0.272 * red + 0.534 * green + 0.131 * blue)); sepiaTone.add((byte) sepiaBlue); sepiaTone.add((byte) sepiaGreen); sepiaTone.add((byte) sepiaRed); } // 处理填充字节 for (int p = 0; p < rowPadding; p++) { i++; sepiaTone.add((byte) 0); } } saveImage(sepiaTone, width, height, "Image-sepia.bmp"); } catch (IOException e) { e.printStackTrace(); } } public static void saveImage(ArrayList<Byte> imageData, int width, int height, String fileName) { try (FileOutputStream fileOutputStream = new FileOutputStream(fileName)) { int imageSize = imageData.size(); byte[] header = createBmpHeader(width, height, imageSize); fileOutputStream.write(header); for (byte b : imageData) { fileOutputStream.write(b); } } catch (IOException e) { e.printStackTrace(); } } private static byte[] createBmpHeader(int width, int height, int imageSize) { byte[] header = new byte[54]; // BMP标识 header[0] = 0x42; header[1] = 0x4D; // 文件大小:图像数据大小 + 头大小(54) int fileSize = imageSize + 54; header[2] = (byte) (fileSize & 0xFF); header[3] = (byte) ((fileSize >> 8) & 0xFF); header[4] = (byte) ((fileSize >> 16) & 0xFF); header[5] = (byte) ((fileSize >> 24) & 0xFF); // 保留字段设为0 header[6] = 0; header[7] = 0; header[8] = 0; header[9] = 0; // 数据偏移量:头大小54 header[10] = 0x36; header[11] = 0x00; header[12] = 0x00; header[13] = 0x00; // 信息头大小:40字节 header[14] = 0x28; header[15] = 0x00; header[16] = 0x00; header[17] = 0x00; // 宽度 header[18] = (byte) (width & 0xFF); header[19] = (byte) ((width >> 8) & 0xFF); header[20] = (byte) ((width >> 16) & 0xFF); header[21] = (byte) ((width >> 24) & 0xFF); // 高度 header[22] = (byte) (height & 0xFF); header[23] = (byte) ((height >> 8) & 0xFF); header[24] = (byte) ((height >> 16) & 0xFF); header[25] = (byte) ((height >> 24) & 0xFF); // 平面数,必须为1 header[26] = 0x01; header[27] = 0x00; // 位深度:24位真彩色 header[28] = 0x18; header[29] = 0x00; // 压缩方式:无压缩 header[30] = 0x00; header[31] = 0x00; header[32] = 0x00; header[33] = 0x00; // 图像数据大小 header[34] = (byte) (imageSize & 0xFF); header[35] = (byte) ((imageSize >> 8) & 0xFF); header[36] = (byte) ((imageSize >> 16) & 0xFF); header[37] = (byte) ((imageSize >> 24) & 0xFF); // 水平/垂直分辨率默认值 header[38] = 0x13; header[39] = 0x0B; header[40] = 0x00; header[41] = 0x00; header[42] = 0x13; header[43] = 0x0B; header[44] = 0x00; header[45] = 0x00; // 颜色表相关字段设为0 header[46] = 0x00; header[47] = 0x00; header[48] = 0x00; header[49] = 0x00; header[50] = 0x00; header[51] = 0x00; header[52] = 0x00; header[53] = 0x00; return header; } private static int byteArrayToInt(byte[] byteArray, int index) { return (byteArray[index] & 0xFF) | ((byteArray[index + 1] & 0xFF) << 8) | ((byteArray[index + 2] & 0xFF) << 16) | ((byteArray[index + 3] & 0xFF) << 24); } // 计算每行需要的填充字节数 private static int calculateRowPadding(int width) { int rowBytes = width * 3; return (4 - (rowBytes % 4)) % 4; } // 确保流读取完整数据 private static void readFully(FileInputStream in, byte[] buffer) throws IOException { int bytesRead = 0; while (bytesRead < buffer.length) { int read = in.read(buffer, bytesRead, buffer.length - bytesRead); if (read == -1) { throw new IOException("Unexpected end of file"); } bytesRead += read; } } // 将颜色值限制在0-255范围内 private static int clamp(int value) { return Math.max(0, Math.min(255, value)); } }
内容的提问来源于stack exchange,提问作者Seb Ar
相关产品推荐
相关产品推荐

