BMP无损格式图像读写

wen java案例 1

本文目录导读:

BMP无损格式图像读写

  1. BMP文件格式概述
  2. Python实现
  3. C++实现
  4. 关键注意事项
  5. 实用工具函数

我来介绍BMP无损格式图像的读写方法,包括Python和C++两种实现。

BMP文件格式概述

BMP文件结构:

[BMP文件头] [DIB头] [调色板(可选)] [像素数据]

Python实现

使用PIL/Pillow(推荐)

from PIL import Image
import numpy as np
# 读取BMP
img = Image.open('image.bmp')
print(f"格式: {img.format}")
print(f"大小: {img.size}")
print(f"模式: {img.mode}")  # RGB, RGBA, L等
# 转换为numpy数组
pixels = np.array(img)
print(f"像素数组形状: {pixels.shape}")
# 写入BMP
img.save('output.bmp', 'BMP')
# 处理像素
def process_bmp(input_path, output_path):
    # 读取
    img = Image.open(input_path)
    pixels = np.array(img)
    # 示例:反转颜色
    processed = 255 - pixels
    # 保存
    result = Image.fromarray(processed.astype('uint8'))
    result.save(output_path, 'BMP')
# 使用
process_bmp('input.bmp', 'output.bmp')

不使用库的纯Python实现

import struct
class BMPReader:
    def __init__(self):
        self.file_header = {}
        self.info_header = {}
        self.pixels = None
        self.width = 0
        self.height = 0
        self.padding = 0
    def read(self, filename):
        with open(filename, 'rb') as f:
            # 读取文件头 (14字节)
            self.file_header['bfType'] = f.read(2)
            self.file_header['bfSize'] = struct.unpack('<I', f.read(4))[0]
            self.file_header['bfReserved1'] = struct.unpack('<H', f.read(2))[0]
            self.file_header['bfReserved2'] = struct.unpack('<H', f.read(2))[0]
            self.file_header['bfOffBits'] = struct.unpack('<I', f.read(4))[0]
            # 读取信息头
            self.info_header['biSize'] = struct.unpack('<I', f.read(4))[0]
            self.width = struct.unpack('<i', f.read(4))[0]
            self.height = struct.unpack('<i', f.read(4))[0]
            self.info_header['biPlanes'] = struct.unpack('<H', f.read(2))[0]
            self.info_header['biBitCount'] = struct.unpack('<H', f.read(2))[0]
            self.info_header['biCompression'] = struct.unpack('<I', f.read(4))[0]
            self.info_header['biSizeImage'] = struct.unpack('<I', f.read(4))[0]
            self.info_header['biXPelsPerMeter'] = struct.unpack('<i', f.read(4))[0]
            self.info_header['biYPelsPerMeter'] = struct.unpack('<i', f.read(4))[0]
            self.info_header['biClrUsed'] = struct.unpack('<I', f.read(4))[0]
            self.info_header['biClrImportant'] = struct.unpack('<I', f.read(4))[0]
            # 计算每行字节数和填充
            bit_count = self.info_header['biBitCount']
            row_size = (self.width * bit_count + 31) // 32 * 4
            self.padding = row_size - (self.width * bit_count // 8)
            # 移动到像素数据
            f.seek(self.file_header['bfOffBits'])
            # 读取像素数据 (假设24位BMP)
            if bit_count == 24:
                self.pixels = []
                for y in range(abs(self.height)):
                    row = []
                    for x in range(self.width):
                        # BMP存储顺序是BGR
                        b = struct.unpack('B', f.read(1))[0]
                        g = struct.unpack('B', f.read(1))[0]
                        r = struct.unpack('B', f.read(1))[0]
                        row.append((r, g, b))
                    # 跳过填充字节
                    if self.padding > 0:
                        f.read(self.padding)
                    self.pixels.append(row)
                # 如果高度为正,图像是倒置的
                if self.height > 0:
                    self.pixels.reverse()
    def write(self, filename):
        with open(filename, 'wb') as f:
            # 计算文件大小
            height = abs(self.height)
            row_size = (self.width * 24 + 31) // 32 * 4
            pixel_data_size = row_size * height
            file_size = 54 + pixel_data_size  # 14 + 40 = 54
            # 写入文件头
            f.write(b'BM')
            f.write(struct.pack('<I', file_size))
            f.write(struct.pack('<H', 0))  # 保留
            f.write(struct.pack('<H', 0))  # 保留
            f.write(struct.pack('<I', 54))  # 数据偏移
            # 写入信息头
            f.write(struct.pack('<I', 40))  # 信息头大小
            f.write(struct.pack('<i', self.width))
            f.write(struct.pack('<i', self.height))
            f.write(struct.pack('<H', 1))  # 颜色平面数
            f.write(struct.pack('<H', 24))  # 每像素位数
            f.write(struct.pack('<I', 0))  # 压缩方式
            f.write(struct.pack('<I', pixel_data_size))
            f.write(struct.pack('<i', 2835))  # 水平分辨率
            f.write(struct.pack('<i', 2835))  # 垂直分辨率
            f.write(struct.pack('<I', 0))  # 颜色数
            f.write(struct.pack('<I', 0))  # 重要颜色数
            # 写入像素数据
            pixels = self.pixels.copy()
            if self.height > 0:
                pixels.reverse()  # BMP存储是倒置的
            for row in pixels:
                for r, g, b in row:
                    f.write(struct.pack('B', b))
                    f.write(struct.pack('B', g))
                    f.write(struct.pack('B', r))
                # 填充字节
                for _ in range(self.padding):
                    f.write(struct.pack('B', 0))
# 使用示例
reader = BMPReader()
reader.read('input.bmp')
print(f"宽度: {reader.width}, 高度: {reader.height}")
# 修改像素(示例:添加红色滤镜)
for y in range(len(reader.pixels)):
    for x in range(len(reader.pixels[y])):
        r, g, b = reader.pixels[y][x]
        reader.pixels[y][x] = (min(r + 50, 255), g, b)
reader.write('output.bmp')

C++实现

使用C++标准库

#include <iostream>
#include <fstream>
#include <vector>
#include <cstdint>
#pragma pack(push, 1)
struct BMPFileHeader {
    uint16_t bfType = 0x4D42;      // 'BM'
    uint32_t bfSize = 0;           // 文件大小
    uint16_t bfReserved1 = 0;
    uint16_t bfReserved2 = 0;
    uint32_t bfOffBits = 54;       // 数据偏移
};
struct BMPInfoHeader {
    uint32_t biSize = 40;          // 信息头大小
    int32_t biWidth = 0;
    int32_t biHeight = 0;
    uint16_t biPlanes = 1;
    uint16_t biBitCount = 24;      // 每像素位数
    uint32_t biCompression = 0;
    uint32_t biSizeImage = 0;
    int32_t biXPelsPerMeter = 2835;
    int32_t biYPelsPerMeter = 2835;
    uint32_t biClrUsed = 0;
    uint32_t biClrImportant = 0;
};
#pragma pack(pop)
struct Pixel {
    uint8_t r, g, b;
};
class BMP {
public:
    BMPFileHeader fileHeader;
    BMPInfoHeader infoHeader;
    std::vector<std::vector<Pixel>> pixels;
    int width, height;
    int padding;
    BMP() : width(0), height(0), padding(0) {}
    bool read(const std::string& filename) {
        std::ifstream file(filename, std::ios::binary);
        if (!file) {
            std::cerr << "无法打开文件: " << filename << std::endl;
            return false;
        }
        // 读取文件头
        file.read(reinterpret_cast<char*>(&fileHeader), sizeof(BMPFileHeader));
        if (fileHeader.bfType != 0x4D42) {
            std::cerr << "不是有效的BMP文件" << std::endl;
            return false;
        }
        // 读取信息头
        file.read(reinterpret_cast<char*>(&infoHeader), sizeof(BMPInfoHeader));
        width = infoHeader.biWidth;
        height = abs(infoHeader.biHeight);
        // 计算填充
        int rowSize = ((width * infoHeader.biBitCount + 31) / 32) * 4;
        padding = rowSize - (width * 3);  // 24位图像
        // 移动到像素数据
        file.seekg(fileHeader.bfOffBits);
        // 读取像素数据
        pixels.resize(height);
        for (int y = 0; y < height; y++) {
            pixels[y].resize(width);
            for (int x = 0; x < width; x++) {
                uint8_t b, g, r;
                file.read(reinterpret_cast<char*>(&b), 1);
                file.read(reinterpret_cast<char*>(&g), 1);
                file.read(reinterpret_cast<char*>(&r), 1);
                pixels[y][x] = {r, g, b};
            }
            // 跳过填充字节
            if (padding > 0) {
                file.seekg(padding, std::ios::cur);
            }
        }
        // 如果高度为正,图像是倒置的
        if (infoHeader.biHeight > 0) {
            std::reverse(pixels.begin(), pixels.end());
        }
        file.close();
        return true;
    }
    bool write(const std::string& filename) {
        std::ofstream file(filename, std::ios::binary);
        if (!file) {
            std::cerr << "无法创建文件: " << filename << std::endl;
            return false;
        }
        // 计算文件大小
        int rowSize = ((width * infoHeader.biBitCount + 31) / 32) * 4;
        int pixelDataSize = rowSize * height;
        fileHeader.bfSize = 54 + pixelDataSize;
        infoHeader.biSizeImage = pixelDataSize;
        // 写入文件头
        file.write(reinterpret_cast<char*>(&fileHeader), sizeof(BMPFileHeader));
        // 写入信息头
        infoHeader.biHeight = -height;  // 顶向下存储
        file.write(reinterpret_cast<char*>(&infoHeader), sizeof(BMPInfoHeader));
        // 写入像素数据
        std::vector<Pixel> reversedPixels = pixels;
        std::reverse(reversedPixels.begin(), reversedPixels.end());
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                file.put(reversedPixels[y][x].b);
                file.put(reversedPixels[y][x].g);
                file.put(reversedPixels[y][x].r);
            }
            // 写入填充字节
            for (int p = 0; p < padding; p++) {
                file.put(0);
            }
        }
        file.close();
        return true;
    }
    void process() {
        // 示例操作:反转颜色
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                pixels[y][x].r = 255 - pixels[y][x].r;
                pixels[y][x].g = 255 - pixels[y][x].g;
                pixels[y][x].b = 255 - pixels[y][x].b;
            }
        }
    }
};
// 使用示例
int main() {
    BMP bmp;
    if (bmp.read("input.bmp")) {
        std::cout << "宽度: " << bmp.width << ", 高度: " << bmp.height << std::endl;
        bmp.process();  // 处理图像
        if (bmp.write("output.bmp")) {
            std::cout << "保存成功" << std::endl;
        }
    }
    return 0;
}

关键注意事项

BMP格式特点

  1. 字节顺序:BMP使用小端序(Little-Endian)
  2. 颜色顺序:存储顺序是BGR(蓝、绿、红)
  3. 行对齐:每行字节数必须是4的倍数,不足的用0填充
  4. 图像方向:BMP通常是从下到上存储

常见问题

# 1. 调色板处理(8位BMP)
def read_8bit_bmp(filename):
    # 8位BMP需要读取调色板
    pass
# 2. 透明通道处理
def handle_alpha(img):
    # RGBA模式
    if img.mode == 'RGBA':
        # 使用白色背景
        background = Image.new('RGB', img.size, (255, 255, 255))
        background.paste(img, mask=img.split()[3])
        return background
    return img
# 3. 大文件处理
def process_large_bmp(filename):
    # 使用迭代器逐行处理
    with Image.open(filename) as img:
        width, height = img.size
        for y in range(height):
            pixels = []  # 处理当前行
            for x in range(width):
                pixel = img.getpixel((x, y))
                # 处理像素
                pixels.append(process_pixel(pixel))

性能优化

import numpy as np
# 使用numpy加速
def fast_process_bmp(input_path, output_path):
    img = Image.open(input_path)
    pixels = np.array(img)
    # 矢量操作(比循环快很多)
    # 改为灰度
    gray = np.dot(pixels[..., :3], [0.299, 0.587, 0.114])
    # 保存结果
    result = Image.fromarray(gray.astype('uint8'), mode='L')
    result = result.convert('RGB')  # BMP通常使用RGB
    result.save(output_path, 'BMP')

实用工具函数

def get_bmp_info(filename):
    """获取BMP文件信息"""
    with Image.open(filename) as img:
        info = {
            'file': filename,
            'format': img.format,
            'mode': img.mode,
            'size': img.size,
            'info': img.info
        }
        # 添加文件大小信息
        import os
        info['file_size'] = os.path.getsize(filename)
        return info
def convert_bmp_to_rgb24(bmp_path, output_path):
    """转换为标准24位BMP"""
    img = Image.open(bmp_path)
    if img.mode != 'RGB':
        img = img.convert('RGB')
    img.save(output_path, 'BMP')
def create_test_bmp(width, height, color=(255, 0, 0)):
    """创建测试BMP图像"""
    img = Image.new('RGB', (width, height), color)
    img.save('test.bmp', 'BMP')
    print(f"创建测试图像: {width}x{height}")
# 批量处理
def batch_convert_to_bmp(input_dir, output_dir):
    """批量转换图像为BMP格式"""
    import os
    from pathlib import Path
    os.makedirs(output_dir, exist_ok=True)
    for file in Path(input_dir).glob('*'):
        if file.suffix.lower() in ['.jpg', '.png', '.gif']:
            try:
                img = Image.open(file)
                # 确保RGB模式
                if img.mode != 'RGB':
                    img = img.convert('RGB')
                output_path = Path(output_dir) / (file.stem + '.bmp')
                img.save(output_path, 'BMP')
                print(f"转换: {file.name} -> {output_path.name}")
            except Exception as e:
                print(f"转换失败 {file.name}: {e}")

代码提供了完整的BMP图像读写解决方案,适用于不同场景和需求。

抱歉,评论功能暂时关闭!