几个星期以来,我一直在努力用 C 语言创建一个可以将 BMP 文件顺时针或逆时针旋转 90 度的函数。无论我做什么或如何读取/更改标题似乎都不起作用,我什至尝试让人工智能生成代码,但可惜似乎没有任何作用,我不知道我做错了什么或如果我的配置有问题或者什么。如果我遗漏了什么或者有更简单的方法,请告诉我

谢谢。

编辑:在这里删除了第二个问题,稍后会问,抱歉

(抱歉,是西​​班牙语)

typedef struct
{
    unsigned short  bm;       //tipo de fichero "BM"
    unsigned int    tam;      //tamanyo
    unsigned short  res1;     //reservado
    unsigned short  res2;     //reservado
    unsigned int    inicio;   //inicio datos de imagen
    unsigned int    head;     //tamanyo de la cabecera del bitmap
    unsigned int    x;        //ancho en pixeles
    unsigned int    y;        //alto en pixeles
    unsigned short  planes;   //numero de planos
    unsigned short  tam_pun;  //tamanyo de cada punto
    unsigned int    comp;     //compresion
    unsigned int    img;      //tamanyo imagen
    unsigned int    r_hor;    //resolucion horizontal
    unsigned int    r_ver;    //resolucion vertical
    unsigned int    t_color;  //tamanyo tabla de color
    unsigned int    impor;    //contador de colores importantes
}bmpHeader;

void rotar(FILE *f, int rotar) {
    bmpHeader header;
    unsigned char *data;
    unsigned char *data_rot;
    int i, j, k;

    // Leer la cabecera del BMP
    fread(&header, sizeof(bmpHeader), 1, f);

    // Asignar memoria para los datos de la imagen
    data = (unsigned char *)malloc(header.img);
    data_rot = (unsigned char *)malloc(header.img);

    // Leer los datos de la imagen
    fread(data, header.img, 1, f);

    // Rotar la imagen
    if (rotar == 0) {
        // Rotar 90 grados a la derecha
        for (i = 0; i < header.x; i++) {
            for (j = 0; j < header.y; j++) {
                k = (header.x - i - 1) * header.y + j;
                data_rot[k] = data[i * header.y + j];
            }
        }
    } else {
        // Rotar 90 grados a la izquierda
        for (i = 0; i < header.x; i++) {
            for (j = 0; j < header.y; j++) {
                k = i * header.y + (header.y - j - 1);
                data_rot[k] = data[i * header.y + j];
            }
        }
    }

    // Escribir la cabecera del BMP
    fwrite(&header, sizeof(bmpHeader), 1, f);

    // Escribir los datos de la imagen
    fwrite(data_rot, header.img, 1, f);

    // Liberar la memoria
    free(data);
    free(data_rot);
}

6

  • 请为您的第二个问题提出一个单独的问题,然后将其从该问题中删除。谢谢并欢迎来到SO。 — 请忽略@AndreasWenzel 的评论


    – 


  • 2
    @gregspears:我相信正确解释填充字节是解决主要问题的一个要求,所以我相信将两个问题合并为一个是有意义的,即使OP以这样一种方式提出问题,就好像两个问题没有直接相关一样。


    – 

  • 您似乎假设实际的像素数据直接出现在标题之后。情况可能是这样。但是,根据文件格式(在标头中指定),也可能预先存在调色板信息。biCompression结构中字段的值是多少?我相信这个字段应该与您结构中的字段相对应。comp


    – 

  • 您似乎还假设每个像素都由 8 位组成。据我所知,只有当您使用调色板而不是以 RGB 格式存储像素信息时,这才可能成立。这是我们需要知道biCompression我之前评论中提到的字段值的另一个原因。如果它的值为0,那么您使用的是未压缩的 RGB,这意味着每个像素有 3 个字节,而不是 1 个字节。


    – 


  • 结构是你bmpHeader自己创建的吗?如果是这样,那么我建议您使用(requires #include <stdint.h>) 代替unsigned shortuint32_tunsigned int因为 anint不保证在所有平台上具有 4 字节的大小,而 auint32_t保证在所有平台上具有相同的大小。


    – 



3 个回答
3

BMP 文件的一个问题是,由于文件开头的两字节“BM”魔法,标头中的所有 4 字节值都未对齐。因此,通常最好将其从“bmpHeader”结构中删除并单独读取。

bmpHeader header;
uint16_t magic;

if (fread(magic, 1, sizeof(magic), f) != sizeof(magic) || magic != 0x4d42 ||
    fread(&header, 1, sizeof(header), f) != sizeof(header)) {
    fprintf(stderr, "error reading bmp header");
    exit(1);
}

这样您就不必担心标题中的对齐或填充。将幻数检查为 uint16_t 而不是 2 个字符也会检查字节顺序,以确保它适合您的机器 – 如果它是 0x424d,您需要对所有内容进行字节交换。

您还应该始终检查调用的返回值fread

3

  • 另一种方法是告诉编译器结构内部没有填充,例如在 gcc/clang 或MSVC 上使用。


    – 


  • 2
    @AndreasWenzel:是的,但是没有可靠/可移植的方法来做到这一点——这取决于编译器和系统,它是否会工作或导致未对齐的陷阱。


    – 

  • 使用库,请参阅


    – 

我不愿意推荐任何特定的库来读取和写入 BMP 文件(但我会推荐)。你应该为自己省去一些悲伤并使用一个。谷歌找到了我,看起来不错。如果您想使用 C++,那么就是您想要做出的选择。当然,您可以疯狂地使用像这样的更大的库,但这对于您的需求来说完全是多余的。

无论如何,一旦你有了一个为你加载和保存图像数据的,你就可以不再担心诸如步幅之类的事情了。只需执行旋转即可。

如果您想坚持自己加载和保存

…那么您应该努力将图像数据加载到仅表示打包的 32 位 ARGB 像素数据的结构中。这再次使您在正确修改图像时不必处理像素格式和行步长等问题。

换句话说,将任务分成更简单的任务,不要让一项任务的复杂性蔓延到不相关的任务中。

1

  • 1
    如果你不想使用现有的库,你应该像库一样构造你的代码——有单独的例程来加载/存储位图和旋转位图,并且每个例程都有干净的接口,所以 main 变得很简单,只有 3 个调用+错误检查和报告。


    – 


旋转图像的尺寸可能与原始图像的尺寸不匹配。

header.img指定图像的大小(以字节为单位)。对于未压缩的 RGB 位图,可以将其设置为 0。

我已经调整了您的代码,使其适用于所有未压缩的格式。

请注意,bmpHeaderandRGBQUAD结构必须是字节对齐的:#pragma pack(1)or __attribute__((packed))(取决于编译器)。

#include <iostream>
#include <io.h>
#include <setjmp.h>

#define TRY do { jmp_buf ex_buf__; if (!setjmp(ex_buf__)) {
#define CATCH } else {
#define ETRY }} while(0)
#define THROW longjmp(ex_buf__, 1)

#pragma pack(push, 1)   // set alignment to 1 byte boundary

typedef struct /*__attribute__((packed))*/ {
    unsigned short  bm;       //tipo de fichero "BM"
    unsigned int    tam;      //tamanyo
    unsigned short  res1;     //reservado
    unsigned short  res2;     //reservado
    unsigned int    inicio;   //inicio datos de imagen
    unsigned int    head;     //tamanyo de la cabecera del bitmap
    unsigned int    x;        //ancho en pixeles
    unsigned int    y;        //alto en pixeles
    unsigned short  planes;   //numero de planos
    unsigned short  tam_pun;  //tamanyo de cada punto
    unsigned int    comp;     //compresion
    unsigned int    img;      //tamanyo imagen
    unsigned int    r_hor;    //resolucion horizontal
    unsigned int    r_ver;    //resolucion vertical
    unsigned int    t_color;  //tamanyo tabla de color
    unsigned int    impor;    //contador de colores importantes
} bmpHeader;

typedef struct /*__attribute__((packed))*/ {
    unsigned char blue;
    unsigned char green;
    unsigned char red;
    unsigned char reserved;
} RGBQUAD;

#pragma pack(pop)       // restore original alignment from stack

int get_stride(bmpHeader& header) {
    return (((header.x * header.tam_pun) + 31) & ~31) >> 3;
}

int sgn(int val) {
    return (0 < val) - (val < 0);
}

void rotar(FILE* f, int rotar) {    // fopen mode: rb+

    bmpHeader src_header;
    bmpHeader dst_header;
    RGBQUAD* palette = NULL;
    unsigned char* data = NULL;
    unsigned char* data_rot = NULL;
    unsigned char m;
    int i, j, k, b;

    TRY {
        // Get actual source file size
        fseek(f, 0, SEEK_END);
        int src_file_size = ftell(f);
        fseek(f, 0, SEEK_SET);

        // Leer la cabecera del BMP
        if (fread(&src_header, sizeof(bmpHeader), 1, f) != 1) THROW;
        if (src_header.bm != 0x4d42 ||
            src_header.x <= 0 ||
            src_header.y == 0 ||
            src_header.head != 40 ||
            src_header.t_color < 0) THROW;

        memcpy(&dst_header, &src_header, sizeof(bmpHeader));

        // positive height: the bitmap is a bottom - up
        // negative height: the bitmap is a top - down
        int src_height = abs((int)src_header.y);
        int dst_height = src_header.x;
        int dy = sgn((int)src_header.y);
        dst_header.x = src_height;
        dst_header.y = dst_height * dy;
        dst_header.r_hor = src_header.r_ver;
        dst_header.r_ver = src_header.r_hor;

        int src_stride = get_stride(src_header);
        int dst_stride = get_stride(dst_header);

        int src_map_size = src_stride * src_height;
        int dst_map_size = dst_stride * dst_height;

        int palette_size = src_header.inicio - sizeof(bmpHeader);
        if (palette_size < sizeof(RGBQUAD) * src_header.t_color) THROW;

        if (src_file_size < sizeof(bmpHeader) + palette_size + src_map_size) THROW;
        int dst_file_Size = sizeof(bmpHeader) + palette_size + dst_map_size;

        dst_header.tam = dst_file_Size;
        dst_header.img = dst_map_size;

        if (palette_size) {
            if (!(palette = (RGBQUAD*)malloc(palette_size))) THROW;
            if (fread(palette, palette_size, 1, f) != 1) THROW;
        }

        // Asignar memoria para los datos de la imagen
        if (!(data = (unsigned char*)malloc(src_map_size))) THROW;

        // Leer los datos de la imagen
        if (fread(data, src_map_size, 1, f) != 1) THROW;

        // Zero empty spaces
        if (!(data_rot = (unsigned char*)malloc(dst_map_size))) THROW;
        memset(data_rot, 0, dst_map_size);

        switch (src_header.tam_pun) {

        case 1:

            // Rotar la imagen
            if (rotar == dy < 0) {
                // Rotar 90 grados a la derecha (>)
                for (i = 0; i < src_header.x; i++) {
                    for (j = 0; j < src_height; j++) {
                        k = (dst_height - 1 - i) * dst_stride + (j >> 3);
                        if (data[(i >> 3) + j * src_stride] & (0x80 >> (i & 7)))
                            data_rot[k] |= (0x80 >> (j & 7));
                    }
                }
            }
            else {
                // Rotar 90 grados a la izquierda (<)
                for (i = 0; i < src_header.x; i++) {
                    for (j = 0; j < src_height; j++) {
                        k = i * dst_stride + ((dst_header.x - 1 - j) >> 3);
                        if (data[(i >> 3) + j * src_stride] & (0x80 >> (i & 7)))
                            data_rot[k] |= (0x80 >> ((dst_header.x - 1 - j) & 7));
                    }
                }
            }
            break;

        case 4:

            // Rotar la imagen
            if (rotar == dy < 0) {
                // Rotar 90 grados a la derecha (>)
                for (i = 0; i < src_header.x; i++) {
                    for (j = 0; j < src_height; j++) {
                        k = (dst_height - 1 - i) * dst_stride + (j >> 1);
                        m = i & 1
                            ? data[(i >> 1) + j * src_stride] & 0x0F
                            : data[(i >> 1) + j * src_stride] >> 4;
                        data_rot[k] |= (j & 1) ? m : m << 4;
                    }
                }
            }
            else {
                // Rotar 90 grados a la izquierda (<)
                for (i = 0; i < src_header.x; i++) {
                    for (j = 0; j < src_height; j++) {
                        k = i * dst_stride + ((dst_header.x - 1 - j) >> 1);
                        m = i & 1
                            ? data[(i >> 1) + j * src_stride] & 0x0F
                            : data[(i >> 1) + j * src_stride] >> 4;
                        data_rot[k] |= ((dst_header.x - 1 - j) & 1) ? m : m << 4;
                    }
                }
            }
            break;

        case  8:
        case 16:
        case 24:
        case 32:

            int bpp = src_header.tam_pun >> 3;  // Bytes per pixel        
            // Rotar la imagen
            if (rotar == dy < 0) {
                // Rotar 90 grados a la derecha (>)
                for (i = 0; i < src_header.x; i++) {
                    for (j = 0; j < src_height; j++) {
                        for (b = 0; b < bpp; b++) {
                            k = (dst_height - 1 - i) * dst_stride + j * bpp + b;
                            data_rot[k] = data[i * bpp + j * src_stride + b];
                        }
                    }
                }
            }
            else {
                // Rotar 90 grados a la izquierda (<)
                for (i = 0; i < src_header.x; i++) {
                    for (j = 0; j < src_height; j++) {
                        for (b = 0; b < bpp; b++) {
                            k = i * dst_stride + (dst_header.x - 1 - j) * bpp + b;
                            data_rot[k] = data[i * bpp + j * src_stride + b];
                        }
                    }
                }
            }
            break;
        }

        // Return to start position
        fseek(f, 0, SEEK_SET);

        // Escribir la cabecera del BMP
        if (fwrite(&dst_header, sizeof(bmpHeader), 1, f) != 1) THROW;

        // Palette
        if (palette_size) {
            if (fwrite(palette, palette_size, 1, f) != 1) THROW;
        }

        // Escribir los datos de la imagen
        if (fwrite(data_rot, dst_map_size, 1, f) != 1) THROW;

        // Flush buffer before truncate
        fflush(f);

        // Truncate if necessary
        _chsize(_fileno(f), dst_file_Size);
    }

    CATCH {    
    }

    ETRY;

    // Liberar la memoria
    if (palette) free(palette);
    if (data) free(data);
    if (data_rot) free(data_rot);
}

3

  • 1
    在编译器 gcc/clang 上,没有填充字节的选项是


    – 


  • 此代码不适用于具有负高度的位图(表示自上而下的位图)。请参阅我对该问题的评论以获取更多信息。


    – 


  • 安德烈亚斯·温泽尔,我把骨灰撒在头上,我忘记了负高度。代码已经修复了。


    –