【问题标题】:Bitmap color - C位图颜色 - C
【发布时间】:2016-03-31 15:19:46
【问题描述】:

我仍然无法访问位图图像中的颜色位。问题是,将位图的内容保存到缓冲区后,我不知道:

  1. 从哪里开始循环(如果我从 0 开始,我认为它会擦除标题)?

  2. 如何访问字节并进行更改(将 BMP 中的颜色转换为输出中所需的颜色)?

  3. 还有,如何将缓冲区插入到新的位图文件中?

我要修改的所有图像都具有可被 4 整除的行(当填充特定字节时我必须插入 0)和每像素 24 位。即使是很少的提示也将不胜感激。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "bmp_header.h"

int main(void)
{
    FILE *f;
    f = fopen("captcha.bmp","rb");
    if ((f = fopen("captcha.bmp", "rb")) == NULL)
    {
        printf("Error opening file %s.\n", "captcha.bmp");
        exit(1);
    }
    fread(&BMP_header,sizeof(BMP_header),1,f);
    fread(&BMP_info_header,sizeof(BMP_info_header),1,f);
    fseek(f,BMP_header.imageDataOffset,SEEK_SET);
    int rows = (BMP_info_header.bitPix * BMP_info_header.width + 31 ) /32 * 4 ;
    char *PixelArray =malloc( rows * abs(BMP_info_header.height)*sizeof(char));
    int i;
    for( i =sizeof(BMP_header)+sizeof(BMP_info_header); i<=(rows * abs(BMP_info_header.height))-2;i+=3)
    {
        PixelArray[i]=255; // just a random value to test if this makese any sense
        PixelArray[i+1]=255;
        PixelArray[i+2]=255;

    }

    return 0;
}

还有,这是bmp_header.h的内容:

#pragma pack(1)

struct bmp_fileheader
{
    unsigned char  fileMarker1; /* 'B' */
    unsigned char  fileMarker2; /* 'M' */
    unsigned int   bfSize; /* File's size */
    unsigned short unused1;
    unsigned short unused2;
    unsigned int   imageDataOffset; /* Offset to the start of image data */
}BMP_header,BMP_header_out;

struct bmp_infoheader
{
    unsigned int   biSize; /* Size of the info header - 40 bytes */
    signed int     width; /* Width of the image */
    signed int     height; /* Height of the image */
    unsigned short planes;
    unsigned short bitPix;
    unsigned int   biCompression;
    unsigned int   biSizeImage; /* Size of the image data */
    int            biXPelsPerMeter;
    int            biYPelsPerMeter;
    unsigned int   biClrUsed;
    unsigned int   biClrImportant;
}BMP_info_header,BMP_info_header_out;

#pragma pack()

【问题讨论】:

  • 尝试更改一个像素而不是所有像素。顺便说一句,圣诞快乐。
  • 你不必把它全部放在一个循环中。我想我不明白这个说法:int rows = (BMP_info_header.bitPix * BMP_info_header.width + 31 ) /32 * 4 ;。你能解释一下这里31、32和4的意义是什么吗?
  • 啊,好吧。那我会读那部分。我只是想删除循环,以便您可以编辑 1 个像素并查看它是否有效。编辑所有像素时,要找到问题要困难得多。我必须离开20分钟左右。我很快就会回来。
  • 好的,我在这里看到了几个问题。 PixelArray 的 malloc 中不需要 * sizeof(char)。 malloc 大小不基于字符的大小 - 仅基于像素图像数据的字节大小。不过,最大的错误是做i =sizeof(BMP_header)+sizeof(BMP_info_header);,然后使用 i (不是从 0 开始)来索引你的 malloced PixelArray (它确实从 0 开始)。还有一个错误是sizeof(BMP_header)+sizeof(BMP_info_header)是颜色表的偏移量,而不是像素数据的偏移量。
  • i&lt;=(rows * abs(BMP_info_header.height))-2 表示您不想编辑最后几行?另外,你确定你的像素只有 3 个字节吗? (与类似 4 的东西相反)。

标签: c bmp


【解决方案1】:

好吧,我写了一些代码,用旧位图文件的原始尺寸写出一个完全黑色的位图文件。

适用于所有类型的位图。例如,16 色位图在我的程序没有考虑的信息标题之后有一个调色板。每个像素的位数也需要被 8 整除。如果满足这些先决条件,因为我相信它们在 24 位位图中,那么这个程序应该可以工作。

这里的主要代码在getNewImageData。我们使用 Wikipedia 使用的相同公式计算图像的行大小 - 它计算所需的位,然后将其填充为四个字节的倍数,然后将位转换为字节。然后我们将所有像素阵列内存设置为零(主要是我偏执于将值留在填充字节中)。然后我们沿着每一行运行并编辑每个像素。最里面的for loop 对应每个像素。通过中间的一次迭代for loop 写入一个像素。

您显然可以修改此代码以将像素数据读入内存的分配部分,然后在将像素数据写回之前就地编辑像素数据。此示例代码不读取输入像素数据,只是写出与输入文件相同尺寸的黑色位图。

编辑:我想我应该提到你做错了什么。

  • 您调用了两次fopen,导致一个打开的文件指针挂在内存中的某处。
  • rows 可能应该重命名为 rowSize,因为它是以字节为单位的一行像素的大小
  • PixelArray 未被释放。
  • 您的for loop 从一个偏移量(两个标题的大小)开始,然后受像素数据大小的限制。 for loop 应该从 0 开始并达到像素数据的大小(除非您在执行此操作时正在读取像素数据……您可能不应该这样做)。
  • 您的for loop 尝试一次写入所有像素数据,但没有考虑到每行像素末尾都有 0 到 31 位填充的事实(我的程序假设填充只有 0、8、16 或 24 位)。

代码如下:

#include <stdio.h>
#include "bmp_header.h"
#include <stdlib.h>

int readBitmapHeaders(char* fileLocation, bmp_fileheader* fileheader, bmp_infoheader* infoheader)
{
    FILE* f;
    f = fopen(fileLocation, "rb");

    if (!f)
    {
        printf("Error opening file %s.\n", fileLocation);
        return 1;
    }

    fread(fileheader, sizeof(bmp_fileheader), 1, f);
    fread(infoheader, sizeof(bmp_infoheader), 1, f);

    fclose(f);
    return 0;
}

int writeBitmap(char* fileName, bmp_fileheader* fileheader, bmp_infoheader* infoheader, char* pixelArray, size_t pixelArraySize)
{
    FILE* out;
    out = fopen(fileName, "wb");
    if (!out)
    {
        printf("Error opening file %s.\n", fileName);
        return 1;
    }

    fwrite(fileheader, sizeof(bmp_fileheader), 1, out);
    fwrite(infoheader, sizeof(bmp_infoheader), 1, out);
    fwrite(pixelArray, pixelArraySize, 1, out);

    fclose(out);
    return 0;
}

char* getNewImageData(bmp_infoheader* infoheader, size_t* imageSize)
{
    //rowsize is padded to 4 bytes
    size_t rowSize = (infoheader->bitPix * infoheader->width + 31) / 32 * 4;

    size_t pixelArraySize = rowSize * abs(infoheader->height);

    char* pixelArray = (char*)malloc(pixelArraySize);
    if (!pixelArray)
    {
        return NULL;
    }

    memset(pixelArray, 0, pixelArraySize);

    size_t bytesPerPixel = infoheader->bitPix / 8;
    for (int i = 0; i < infoheader->height; i++)
    {
        for (int j = 0; j < infoheader->width; j++)
        {
            size_t offset = rowSize * i + bytesPerPixel * j;
            for (size_t k = 0; k < bytesPerPixel; k++)
            {
                pixelArray[offset + k] = 0;
            }
        }
    }

    if (imageSize)
    {
        *imageSize = pixelArraySize;
    }
    return pixelArray;
}

int main()
{
    char* fileLocation = "test.bmp";
    bmp_fileheader header;
    bmp_infoheader infoheader;

    int readResult = readBitmapHeaders(fileLocation, &header, &infoheader);
    if (readResult)
    {
        return readResult;
    }

    size_t pixelArraySize;
    char* pixelArray = getNewImageData(&infoheader, &pixelArraySize);
    if (!pixelArray)
    {
        printf("%s", "Failed to create the new image data. Exiting with fatal error.\n");
        return 1;
    }

    char* outFile = "out.bmp";
    int writeResult = writeBitmap(outFile, &header, &infoheader, pixelArray, pixelArraySize);

    free(pixelArray);
    return writeResult;
}

我稍微更改了位图头文件以对结构进行 typedef 并使生活更轻松(至少对我而言):

#pragma once
#pragma pack(1)

typedef struct _bmp_fileheader
{
    unsigned char  fileMarker1; /* 'B' */
    unsigned char  fileMarker2; /* 'M' */
    unsigned int   bfSize; /* File's size */
    unsigned short unused1;
    unsigned short unused2;
    unsigned int   imageDataOffset; /* Offset to the start of image data */
} bmp_fileheader;

typedef struct _bmp_infoheader
{
    unsigned int   biSize; /* Size of the info header - 40 bytes */
    signed int     width; /* Width of the image */
    signed int     height; /* Height of the image */
    unsigned short planes;
    unsigned short bitPix;
    unsigned int   biCompression;
    unsigned int   biSizeImage; /* Size of the image data */
    int            biXPelsPerMeter;
    int            biYPelsPerMeter;
    unsigned int   biClrUsed;
    unsigned int   biClrImportant;
} bmp_infoheader;

#pragma pack()

【讨论】:

  • 为什么要分配 offset 那个值? rowSize 与 height 和 width 有什么关系?
  • Width 用于计算行大小,可用于索引到一行。像素数据是连续内存中的一堆行。具体来说,共有高度行。计算会找到正确的行并在该行中偏移以供要编辑的特定像素。
  • 我通过填充必须添加到行的末尾..这就是我不明白为什么你把它放在第二个。我还有一个不清楚的地方:我在哪里可以编辑像素(将白色的像素修改为特定的颜色)?在同一秒内?
  • 第一个 for 在高度上循环。我们将一一浏览所有行。第二个 for 在行的宽度上循环。该宽度以像素为单位。这避免了填充。当我们在第三行时,执行rowSize * i 的偏移计算部分跳过第一行和第二行的填充。三行在内存中的格式为 ROW1,PAD1,ROW2,PAD2,ROW3,PAD3。第三个是设置一个像素的值。在您的情况下,k 将从 0 变为 2。当 k 为 0 时,您正在设置 R 值。当 k 为 1 时,您正在设置 G 值。等等……
  • 我现在明白了。非常感谢你的努力和时间米莉,你启发了我。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-19
  • 2016-10-09
  • 2011-06-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多