You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 13:32:32