如何使用 C++ 以及 iostream 和 stdio 标头缩放 .bmp 图像?

Posted

技术标签:

【中文标题】如何使用 C++ 以及 iostream 和 stdio 标头缩放 .bmp 图像?【英文标题】:How to Scale a .bmp Image using C++ and just the iostream and stdio headers? 【发布时间】:2014-07-10 09:18:40 【问题描述】:

我目前正在做我的第一个 C++ 编程任务,我的目标是仅使用基本 IO 标头来缩放位图图像。

我使用http://en.wikipedia.org/wiki/BMP_file_format#DIB_header_.28bitmap_information_header.29 作为查找标头信息的参考指南。

我的问题是创建一种算法来根据添加单个像素来放大图像。 我制作了一系列带有注释的 for 循环(它们使用标题信息来确定它们需要运行多少),但我不知道接下来在操作实际像素方面要做什么,这就是我需要帮助

当我运行程序时,会创建一个新的 BMP 文件(称为 Image2),但它已损坏且大小为 1kb(很可能是因为程序未完成)。

这是代码,如果有人也可以就此批评我或让我知道任何不良做法以避免我目前正在做的事情,我将不胜感激。

 int main()
    
    
    FILE* pFile = NULL;
    FILE* pFile2 = NULL;
    
        
        //Open Image.bmp (In directory) as read binary
        pFile = fopen("Image.bmp", "rb");
        //Scan through the program and set to send.
        fseek (pFile, 0, SEEK_END);
        //Read file data
        int fileData = ftell (pFile);
        //set back to start of char pointer
        rewind (pFile);
    
    
        //Create a Char Filebuffer, this will be where data is viewed.
        char* FileBuffer = new char[fileData];
        //Read pFile into Filebuffer
        fread(FileBuffer, fileData, 1, pFile);
    
            //Read out BMP Header Data and Cast to Int
            int ImageSize = *(int*)&FileBuffer[2];
            int ImageOffset = *(int*)&FileBuffer[10]; //ImageOffset = Image Header Size
            int ImageHeight = *(int*)&FileBuffer[18];
            int ImageWidth = *(int*)&FileBuffer[20];
            int BitsPerPixel = *(int*)&FileBuffer[24];
    
        //Create a New Buffer that starts off at Pixel Data
        char* NewFileBuffer = FileBuffer + ImageOffset;
    
            
                std::cout << "Enter the amount of times you want to scale the image by." << std::endl;
                std::cin >> ScaleValue;
                std::cout << "\n";
    
                //Create New Image row/height/size variables
                int RowSizeOld = (BitsPerPixel * ImageWidth +31)/32 * 4;
                int RowSizeNew = (BitsPerPixel *(ImageWidth * ScaleValue) + 31) / 32 * 4;
                int NewImageSize = ImageOffset + (RowSizeNew * (ScaleValue * ImageHeight) );
                int NewImageHeight = ImageHeight * ScaleValue;
                int NewImageWidth = ImageWidth * ScaleValue;
    
    
    // These ints define the colour values for RGB and Padding
    int r = ImageOffset + 1; 
    int g = ImageOffset + 2; 
    int b= ImageOffset + 3; 
    int p = ImageOffset + 4; 
    
    
    
    //Rescale Image here, for (Newfile buffer [0])
        // This for loop figures out how many times the newHeight variable must iterate
        for (int newHeight = 0 ; newHeight < NewImageHeight ; newHeight++)
        
            //This line scales up the amount of rows in terms of scale value
            for(int doubleLine = 0 ; doubleLine < ScaleValue ; doubleLine++)
            
                //This for loop then figures out the width of the new image in pixels as an int value
                for (int newWidth = 0 ; newWidth < NewImageWidth ; newWidth++)
                 
                    //This loop figures out how many times you need to increase the pixel density (for each pixel)
                    for (int pixelMultiplier = 0 ; pixelMultiplier < ScaleValue ; pixelMultiplier++)
                    
                    
                  //Move pixel data around to scale image (This is where I'm having trouble)
        
                    
                
    
    
            
    
    
        
    
    
        //Create a new File pointer, add image name + bmp string
        FILE* pFile2;
        pFile2 = fopen("Image2.bmp", "wb");
        fwrite(NewFileBuffer, (NewImageSize + ImageOffset), sizeof(char) , pFile2);
    
    return 0;
    

这是我在 SE 上的第一篇文章,如果代码格式有点粗或问题难以理解,我深表歉意(我已通读论坛并修改了我的代码以使其更具可读性)

【问题讨论】:

您是否在调试器中检查了ImageSizeImageOffset 等的值,或者在阅读后添加了std::cout &lt;&lt; "ImageSize " &lt;&lt; ImageSize &lt;&lt; ... &lt;&lt; '\n'; 以确保它们正确无误?例如,您可能遇到字节顺序问题。您的代码也会在某些系统上崩溃,因为您正在取消引用可能不在对齐地址的int*s,尽管在 x86 上这没关系(但速度较慢)。使用fclose() 也是一个好主意。 另外,您正在覆盖已将原始图像读入的缓冲区:首先,它不足以缩放> = 1,其次您需要在缩放时继续查看它计算输出值。您也没有将更新的标头信息写入输出文件。 我使用了您的建议并检查了值,结果发现我一直在读取 BMP 标头中的错误值,感谢您的好消息!我还是个新手,我的讲师是取消引用 int* 值的人,但我会查找对齐值和字节顺序问题是什么。 【参考方案1】:

进行简单的最近重新采样可能是最简单的方法。为此,您只需计算新旧尺寸之间的比率,如下所示:

float xStep = (float)oldWidth / (float)newWidth;
float yStep = (float)oldHeight / (float)newHeight;

现在您知道,当您在新图像的 x 方向上步进 1 个像素时,您将在旧图像中步进“xStep”像素。然后,您将采样位置四舍五入到最近的整数位置,并从旧图像中采样并将其写入新图像!您现在有一个简单的重新采样图像。

为了获得更好的结果,您可以在重新采样时Bi-linear filter。这是一个非常简单的二维线性插值。

为了给您一个过滤浮点值的一维示例,您可以编写如下代码:

for( float x = 0, int xOut = 0; x += xStep, xOut++; xOut < newWidth; )

    const float sample0  = old1D[std::floor( x )];
    const float sample1  = old1D[std::ceil( x )];

    const float t        = x - std::floor( x );

    const float sample   = sample0 + ((sample1 - sample0) * t);

    new1D[xOut] = sample;

当然,我不会处理边缘情况,但处理这些情况并扩展到 2D 应该留作作业时的练习。

还值得注意的是,如果您将图像向下缩放 2 倍以上,这种过滤看起来会有点错误,因为您仍然完全跨过像素。这通常通过在每一步将图像预过滤成一组宽度和高度为一半的图像来解决。这被称为mip-mapping。

祝你好运!

【讨论】:

你能发布完整的解决方案吗?我遇到了同样的问题。【参考方案2】:

这个想法是增加现有像素而不是添加完全新的像素。 对于正缩放,您可以创建一个新的位图,它是旧位图的 x 倍。 然后用旧的像素和副本填充新的位图。

方案如下:

|o1|d1|o2|d2|... |d1|d1|d2|d2|...

其中 o1 是旧像素,d1 是它的副本。

因此,您必须检查所有像素并确定它们在新位图中填充的频率和位置。

【讨论】:

以上是关于如何使用 C++ 以及 iostream 和 stdio 标头缩放 .bmp 图像?的主要内容,如果未能解决你的问题,请参考以下文章

利用真值表法求取主析取范式以及主合取范式的实现(C++)

c++ 数组大小以及如何管理大数组?

如何在 C++ 中将字符串转换为 char 数组?

C++ 待办事项列表重复输入。如何避免这种情况?

C++中一个对象的继承和构造

C++标准I/O库:iostream, fstream, sstringstream