C++如何把位图保存到数组中
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++如何把位图保存到数组中相关的知识,希望对你有一定的参考价值。
不是MFC程序,要用标准C++
具体的要看你采用什么位图格式BMP是一种与硬件设备无关的图像文件格式,使用非常广。它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此,BMP文件所占用的空间很大。BMP文件的图像深度可选lbit、4bit、8bit及24bit。BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。 由于BMP文件格式是Windows环境中交换与图有关的数据的一种标准,因此在Windows环境中运行的图形图像软件都支持BMP图像格式。 文件结构: 典型的BMP图像文件由四部分组成:1:位图文件头数据结构,它包含BMP图像文件的类型、显示内容等信息;
2:位图信息数据结构,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息;
3:调色板,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24位的BMP)就不需要调色板;
4:位图数据,这部分的内容根据BMP位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜色索引值。
位图的类型:
位图一共有两种类型,即:设备相关位图(DDB)和设备无关位图(DIB)。DDB位图在早期的Windows系统(Windows 3.0以前)中是很普遍的,事实上它也是唯一的。然而,随着显示器制造技术的进步,以及显示设备的多样化,DDB位图的一些固有的问题开始浮现出来了。比如,它不能够存储(或者说获取)创建这张图片的原始设备的分辨率,这样,应用程序就不能快速的判断客户机的显示设备是否适合显示这张图片。为了解决这一难题,微软创建了DIB位图格式。
设备无关位图 (Device-Independent Bitmap)
DIB位图包含下列的颜色和尺寸信息:
* 原始设备(即创建图片的设备)的颜色格式。
* 原始设备的分辨率。
* 原始设备的调色板
* 一个位数组,由红、绿、蓝(RGB)三个值代表一个像素。
* 一个数组压缩标志,用于表明数据的压缩方案(如果需要的话)。
以上这些信息保存在BITMAPINFO结构中,该结构由BITMAPINFOHEADER结构和两个或更多个RGBQUAD结构所组成。BITMAPINFOHEADER结构所包含的成员表明了图像的尺寸、原始设备的颜色格式、以及数据压缩方案等信息。RGBQUAD结构标识了像素所用到的颜色数据。
DIB位图也有两种形式,即:底到上型DIB(bottom-up),和顶到下型DIB(top-down)。底到上型DIB的原点(origin)在图像的左下角,而顶到下型DIB的原点在图像的左上角。如果DIB的高度值(由BITMAPINFOHEADER结构中的biHeight成员标识)是一个正值,那么就表明这个DIB是一个底到上型DIB,如果高度值是一个负值,那么它就是一个顶到下型DIB。注意:顶到下型的DIB位图是不能被压缩的。
位图的颜色格式是通过颜色面板值(planes)和颜色位值(bitcount)计算得来的,颜色面板值永远是1,而颜色位值则可以是1、4、8、16、24、32其中的一个。如果它是1,则表示位图是一张单色位图(译者注:通常是黑白位图,只有黑和白两种颜色,当然它也可以是任意两种指定的颜色),如果它是4,则表示这是一张VGA位图,如果它是8、16、24、或是32,则表示该位图是其他设备所产生的位图。如果应用程序想获取当前显示设备(或打印机)的颜色位值(或称位深度),可调用API函数GetDeviceCaps(),并将第二个参数设为BITSPIXEL即可。
显示设备的分辨率是以每米多少个像素来表明的,应用程序可以通过以下三个步骤来获取显示设备或打印机的水平分辨率:
1. 调用GetDeviceCaps()函数,指定第二个参数为HORZRES。
2. 再次调用GetDeviceCaps()函数,指定第二个参数为HORZSIZE。
3. 用第一个返回值除以第二个返回值。即:GetDeviceCaps(hDC,HORZRES)/GetDeviceCaps(hDC,HORZSIZE);
应用程序也可以使用相同的三个步骤来获取设备的垂直分辨率,不同之处只是要将HORZRES替换为VERTRES,把HORZSIZE替换为VERTSIZE,即可。
调色板是被保存在一个RGBQUAD结构的数组中,该结构指出了每一种颜色的红、绿、蓝的分量值。位数组中的每一个索引都对应于一个调色板项(即一个RGBQUAD结构),应用程序将根据这种对应关系,将像素索引值转换为像素RGB值(真实的像素颜色)。应用程序也可以通过调用GetDeviceCaps()函数来获取当前显示设备的调色板尺寸(将该函数的第二个参数设为NUMCOLORS即可)。
Win32 API支持位数据的压缩(只对8位和4位的底到上型DIB位图)。压缩方法是采用运行长度编码方案(RLE),RLE使用两个字节来描述一个句法,第一个字节表示重复像素的个数,第二个字节表示重复像素的索引值。有关压缩位图的详细信息请参见对BITMAPINFOHEADER结构的解释。
应用程序可以从一个DDB位图创建出一个DIB位图,步骤是,先初始化一些必要的结构,然后再调用GetDIBits()函数。不过,有些显示设备有可能不支持这个函数,你可以通过调用GetDeviceCaps()函数来确定一下(GetDeviceCaps()函数在调用时指定RC_DI_BITMAP作为RASTERCAPS的标志)。
应用程序可以用DIB去设置显示设备上的像素(译者注:也就是显示DIB),方法是调用SetDIBitsToDevice()函数或调用StretchDIBits()函数。同样,有些显示设备也有可能不支持以上这两个函数,这时你可以指定RC_DIBTODEV作为RASTERCAPS标志,然后调用GetDeviceCaps()函数来判断该设备是否支持SetDIBitsToDevice()函数。也可以指定RC_STRETCHDIB作为RASTERCAPS标志来调用GetDeviceCaps()函数,来判断该设备是否支持StretchDIBits()函数。
如果应用程序只是要简单的显示一个已经存在的DIB位图,那么它只要调用SetDIBitsToDevice()函数就可以。比如一个电子表格软件,它可以打开一个图表文件,在窗口中简单的调用SetDIBitsToDevice()函数,将图形显示在窗口中。但如果应用程序要重复的绘制位图的话,则应该使用BitBlt()函数,因为BitBlt()函数的执行速度要比SetDIBitsToDevice()函数快很多。
设备相关位图 (Device-Dependent Bitmaps)
设备相关位图(DDB)之所以现在还被系统支持,只是为了兼容旧的Windows 3.0软件,如果程序员现在要开发一个与位图有关的程序,则应该尽量使用或生成DIB格式的位图。
DDB位图是被一个单个结构BITMAP所描述,这个结构的成员标明了该位图的宽度、高度、设备的颜色格式等信息。
DDB位图也有两种类型,即:可废弃的(discardable)DDB和不可废弃的(nondiscardable)DDB。可废弃的DDB位图就是一种当系统内存缺乏,并且该位图也没有被选入设备描述表(DC)的时候,系统就会把该DDB位图从内存中清除(即废弃)。不可废弃的DDB则是无论系统内存多少都不会被系统清除的DDB。API函数CreateDiscardableBitmap()函数可用于创建可废弃位图。而函数CreateBitmap()、CreateCompatibleBitmap()、和CreateBitmapIndirect()可用于创建不可废弃的位图。
应用程序可以通过一个DIB位图而创建一个DDB位图,只要先初始化一些必要的结构,然后再调用CreateDIBitmap()函数就可以。如果在调用该函数时指定了CBM_INIT标志,那么这一次调用就等价于先调用CreateCompatibleBitmap()创建当前设备格式的DDB位图,然后又调用SetDIBits()函数转换DIB格式到DDB格式。(可能有些设备并不支持SetDIBits()函数,你可以指定RC_DI_BITMAP作为RASTERCAPS的标志,然后调用GetDeviceCaps()函数来判断一下)。
对应的数据结构:
1:BMP文件组成
BMP文件由文件头、位图信息头、颜色信息和图形数据四部分组成。
2:BMP文件头(14字节)
BMP文件头数据结构含有BMP文件的类型、文件大小和位图起始位置等信息。
其结构定义如下:
typedef struct tagBITMAPFILEHEADER
WORD bfType; // 位图文件的类型,必须为BM(0-1字节)
DWORD bfSize; // 位图文件的大小,以字节为单位(2-5字节)
WORD bfReserved1; // 位图文件保留字,必须为0(6-7字节)
WORD bfReserved2; // 位图文件保留字,必须为0(8-9字节)
DWORD bfOffBits; // 位图数据的起始位置,以相对于位图(10-13字节)
// 文件头的偏移量表示,以字节为单位
BITMAPFILEHEADER;
3:位图信息头(40字节)
BMP位图信息头数据用于说明位图的尺寸等信息。
typedef struct tagBITMAPINFOHEADER
DWORD biSize; // 本结构所占用字节数(14-17字节)
LONG biWidth; // 位图的宽度,以像素为单位(18-21字节)
LONG biHeight; // 位图的高度,以像素为单位(22-25字节)
WORD biPlanes; // 目标设备的级别,必须为1(26-27字节)
WORD biBitCount;// 每个像素所需的位数,必须是1(双色),(28-29字节)
// 4(16色),8(256色)或24(真彩色)之一
DWORD biCompression; // 位图压缩类型,必须是 0(不压缩),(30-33字节)
// 1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一
DWORD biSizeImage; // 位图的大小,以字节为单位(34-37字节)
LONG biXPelsPerMeter; // 位图水平分辨率,每米像素数(38-41字节)
LONG biYPelsPerMeter; // 位图垂直分辨率,每米像素数(42-45字节)
DWORD biClrUsed;// 位图实际使用的颜色表中的颜色数(46-49字节)
DWORD biClrImportant;// 位图显示过程中重要的颜色数(50-53字节)
BITMAPINFOHEADER;
4:颜色表
颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个RGBQUAD类型的结构,定义一种颜色。RGBQUAD结构的定义如下:
typedef struct tagRGBQUAD
BYTE rgbBlue;// 蓝色的亮度(值范围为0-255)
BYTE rgbGreen; // 绿色的亮度(值范围为0-255)
BYTE rgbRed; // 红色的亮度(值范围为0-255)
BYTE rgbReserved;// 保留,必须为0
RGBQUAD;
颜色表中RGBQUAD结构数据的个数有biBitCount来确定:
当biBitCount=1,4,8时,分别有2,16,256个表项;
当biBitCount=24时,没有颜色表项。
位图信息头和颜色表组成位图信息,BITMAPINFO结构定义如下:
typedef struct tagBITMAPINFO
BITMAPINFOHEADER bmiHeader; // 位图信息头
RGBQUAD bmiColors[1]; // 颜色表
BITMAPINFO;
5:位图数据
位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右,扫描行之间是从下到上。位图的一个像素值所占的字节数:
当biBitCount=1时,8个像素占1个字节;
当biBitCount=4时,2个像素占1个字节;
当biBitCount=8时,1个像素占1个字节;
当biBitCount=24时,1个像素占3个字节;
Windows规定一个扫描行所占的字节数必须是
4的倍数(即以long为单位),不足的以0填充,
biSizeImage = ((((bi.biWidth * bi.biBitCount) + 31) & ~31) / 8) * bi.biHeight;
具体数据举例:
如某BMP文件开头:
4D42 4690 0000 0000 0000 4600 0000 2800 0000 8000 0000 9000 0000 0100*1000 0300 0000 0090 0000 A00F 0000 A00F 0000 0000 0000 0000 0000*00F8 0000 E007 0000 1F00 0000 0000 0000*02F1 84F1 04F1 84F1 84F1 06F2 84F1 06F2 04F2 86F2 06F2 86F2 86F2 .... ....
BMP文件可分为四个部分:位图文件头、位图信息头、彩色板、图像数据阵列,在上图中已用*分隔。
一、图像文件头
1)1:(这里的数字代表的是"字",即两个字节,下同)图像文件头。0x4D42=’BM’,表示是Windows支持的BMP格式。
2)2-3:整个文件大小。4690 0000,为00009046h=36934。
3)4-5:保留,必须设置为0。
4)6-7:从文件开始到位图数据之间的偏移量。4600 0000,为00000046h=70,上面的文件头就是35字=70字节。
二、位图信息头
5)8-9:位图图信息头长度。
6)10-11:位图宽度,以像素为单位。8000 0000,为00000080h=128。
7)12-13:位图高度,以像素为单位。9000 0000,为00000090h=144。
8)14:位图的位面数,该值总是1。0100,为0001h=1。
9)15:每个像素的位数。有1(单色),4(16色),8(256色),16(64K色,高彩色),24(16M色,真彩色),32(4096M色,增强型真彩色)。1000为0010h=16。
10)16-17:压缩说明:有0(不压缩),1(RLE 8,8位RLE压缩),2(RLE 4,4位RLE压缩,3(Bitfields,位域存放)。RLE简单地说是采用像素数+像素值的方式进行压缩。T408采用的是位域存放方式,用两个字节表示一个像素,位域分配为r5b6g5。图中0300 0000为00000003h=3。
11)18-19:用字节数表示的位图数据的大小,该数必须是4的倍数,数值上等于(≥位图宽度的最小的4的倍数)×位图高度×每个像素位数。0090 0000为00009000h=80×90×2h=36864。
12)20-21:用象素/米表示的水平分辨率。A00F 0000为0000 0FA0h=4000。
13)22-23:用象素/米表示的垂直分辨率。A00F 0000为0000 0FA0h=4000。
14)24-25:位图使用的颜色索引数。设为0的话,则说明使用所有调色板项。
15)26-27:对图象显示有重要影响的颜色索引的数目。如果是0,表示都重要。
三、彩色板
16)28-....(不确定):彩色板规范。对于调色板中的每个表项,用下述方法来描述RGB的值:
1字节用于蓝色分量
1字节用于绿色分量
1字节用于红色分量
1字节用于填充符(设置为0)
对于24-位真彩色图像就不使用彩色板,因为位图中的RGB值就代表了每个象素的颜色。
如,彩色板为00F8 0000 E007 0000 1F00 0000 0000 0000,其中:
00FB 0000为FB00h=1111100000000000(二进制),是蓝色分量的掩码。
E007 0000为 07E0h=0000011111100000(二进制),是绿色分量的掩码。
1F00 0000为001Fh=0000000000011111(二进制),是红色分量的掩码。
0000 0000总设置为0。
将掩码跟像素值进行“与”运算再进行移位操作就可以得到各色分量值。看看掩码,就可以明白事实上在每个像素值的两个字节16位中,按从高到低取5、6、5位分别就是r、g、b分量值。取出分量值后把r、g、b值分别乘以8、4、8就可以补齐第个分量为一个字节,再把这三个字节按rgb组合,放入存储器(同样要反序),就可以转换为24位标准BMP格式了。
四、图像数据阵列
17)27(无调色板)-...:每两个字节表示一个像素。阵列中的第一个字节表示位图左下角的象素,而最后一个字节表示位图右上角的象素。
五、存储算法
BMP文件通常是不压缩的,所以它们通常比同一幅图像的压缩图像文件格式要大很多。例如,一个800×600的24位几乎占据1.4MB空间。因此它们通常不适合在因特网或者其它低速或者有容量限制的媒介上进行传输。 根据颜色深度的不同,图像上的一个像素可以用一个或者多个字节表示,它由n/8所确定(n是位深度,1字节包含8个数据位)。图片浏览器等基于字节的ASCII值计算像素的颜色,然后从调色板中读出相应的值。更为详细的信息请参阅下面关于位图文件的部分。 n位2n种颜色的位图近似字节数可以用下面的公式计算: BMP文件大小约等于 54+4*2的n次方+(w*h*n)/8,其中高度和宽度都是像素数。 需要注意的是上面公式中的54是位图文件的文件头,是彩色调色板的大小。另外需要注意的是这是一个近似值,对于n位的位图图像来说,尽管可能有最多2n中颜色,一个特定的图像可能并不会使用这些所有的颜色。由于彩色调色板仅仅定义了图像所用的颜色,所以实际的彩色调色板将小于。 如果想知道这些值是如何得到的,请参考下面文件格式的部分。 由于存储算法本身决定的因素,根据几个图像参数的不同计算出的大小与实际的文件大小将会有一些细小的差别。 参考技术A 用winapi就可以啊,先CreateFile读文件,然后ReadFile读BITMAPFILEHEADER,在ReadFile读BITMAPINFOHEADER,如果图像<8位,那么要读下查找表,下来就ReadFile你要的数据了啊。
如何将数组保存到 C++ 中的文件中?
【中文标题】如何将数组保存到 C++ 中的文件中?【英文标题】:How to save an array into a file in C++? 【发布时间】:2017-12-27 18:16:57 【问题描述】:我创建了一个程序,将数组保存到文件中,然后读取文件以在屏幕上显示数组。这是代码
#include <iostream>
#include <fstream>
using namespace std;
int main(void)
int saveSize = 5;//size of the array
int save [saveSize + 1] = saveSize, 7, 1, 2 ,3, 4; //creating an array which first element is his own size
ofstream fileWrite; fileWrite.open ("File.dat", ios::out | ios::binary | ios::trunc);
fileWrite.write((char *) &save, sizeof(save)); //saves the array into the file
fileWrite.close();
int sizeLoad; //size of the array that will be loaded
ifstream fileRead; fileRead.open("File.dat", ios::in | ios::binary);
fileRead.read((char *) &sizeLoad, sizeof(int)); //it only reads the first element to know the size of the array
int load[sizeLoad+1]; //creating the array
fileRead.read((char *) &load, sizeof(int));
fileRead.close();
//showing the array
for(int i = 1; i <= sizeLoad; i++) cout << "Element " << i << ": " << load[i] << endl;
return 0;
问题是当我运行程序时,结果是这样的:
元素 1:2686224
元素 2:1878005308
元素 3:32
元素 4:2686232
元素 5:4199985
甚至没有接近。有人知道为什么会这样吗?
【问题讨论】:
int load[sizeLoad+1];
之类的东西不是有效的 C++。不要使用数组,使用 std::vector。
强烈推荐阅读:***.com/questions/46991224/…!
您可能应该只使用<<
和>>
来写入和读取文件,而不是将数组视为char*
- 它更简单且不易出错。
请注意,ofstream fileWrite; fileWrite.open (...);
可以更简单地写成ofstream fileWrite(...);
。这就是构造函数的用途。此外,ofstream
的析构函数会关闭文件,因此不需要调用 fileWrite.close()
。 fileRead
也一样。
【参考方案1】:
fileRead.read((char *) &sizeLoad, sizeof(int))
fileRead.read((char*)&load, sizeof(int));
你读到sizeLoad
是 5。
第二行你打算读取5个整数,因此应该改为
int load[sizeLoad];
fileRead.read((char*)&load, sizeLoad * sizeof(int));
您还需要更改循环,只需从0
转到sizeLoad
for(int i = 0; i < sizeLoad; i++)
cout << "Element " << i << ": " << load[i] << endl;
您也可以使用std::vector
,这样您就不必提前保存项目数。您可以一次读取一个整数并使用std::push_back
将元素添加到数组中。示例:
#include <iostream>
#include <fstream>
#include <vector>
int main()
std::vector<int> save = 7, 1, 2, 3, 4 ;
std::ofstream fileWrite("File.dat", std::ios::binary | std::ios::trunc);
fileWrite.write((char*)save.data(), save.size() * sizeof(int));
fileWrite.close();
std::ifstream fileRead("File.dat", std::ios::binary);
std::vector<int> load;
int temp;
while(fileRead.read((char*)&temp, sizeof(int)))
load.push_back(temp);
fileRead.close();
for(int i = 0; i < load.size(); i++)
std::cout << "Element " << i << ": " << load[i] << std::endl;
return 0;
【讨论】:
哇,这正是我想要的,而且更简单......谢谢! :D【参考方案2】:我更改了您的代码,并且可以正常工作。尝试关注;
int saveSize = 5; //size of the array
int save[saveSize + 1] = saveSize, 7, 1, 2 ,3, 4; //creating an array which first element is his own size
ofstream fileWrite("File.dat", ios::out | ios::binary | ios::trunc);
fileWrite.write(reinterpret_cast<char*>(&save), sizeof save ); //saves the array into the file
fileWrite.close();
int sizeLoad[saveSize+1]; //size of the array that will be loaded
ifstream fileRead("File.dat", ios::in | ios::binary);
fileRead.read(reinterpret_cast<char*>(&sizeLoad), sizeof sizeLoad ); //it only reads the first element to know the size of the array
fileRead.close();
//showing the array
for(int i = 0; i < (sizeof(sizeLoad)/sizeof(sizeLoad[0])); i++)
cout << "Element " << i + 1 << ": " << sizeLoad[i] << endl;
return 0;
编辑:我试图解释不清楚的部分。在 3.line 中创建了“.dat”文件,下一行将数组中的每个数字放入该文件。 ofstream.write 函数有两个参数,第一个参数应该是字符,所以我将数组转换为 char,第二个参数应该是数组的大小(您将写入多少个字符)。在您的情况下,您的数组包含 6 个数字,数组大小为 24(每个 int 值 4 个字节)。在将数组写入控制台时,您需要知道数组的大小,因此我只需将数组大小 (24) 划分为 1 个数组字符 (4)。抱歉我的解释不好,感谢您的谨慎。
【讨论】:
哦,谢谢!我不太了解您的几行代码,但我会尝试弄清楚它们是做什么的。 这不是一个完整的答案。你需要解释你改变了什么以及你为什么改变它。也没有正确复制询问者代码的意图。它可以编译并运行,但做的事情略有不同。【参考方案3】:你已经阅读了一个元素,
fileRead.read((char *) &load, sizeof(int)); // load[0] was read
没有打印出来
for(int i = 1; i <= sizeLoad; i++) cout << "Element " << i << ": " << load[i] << endl;
您应该使用
读取完整的数组int load[sizeLoad]; //creating the array
fileRead.read((char *) &load, sizeof(load));
并以这种方式打印所有元素
for(int i = 0; i < sizeLoad; i++) cout << "Element " << i << ": " << load[i] << endl;
【讨论】:
每三个带有标签 c++ 的代码在此页面上使用 VLA,但只有我被否决了。 “这似乎是正确的” - 不,它似乎不正确(它不是 C++) - 因此被否决了。 “在这个页面上,每三个带有标签 c++ 的代码都使用 VLA,但只有我被否决了。” - 废话。 @Duke 如果您想限制可移植性,您“被允许”使用它们,但这个问题被标记为 C++,而不是 GCC。以上是关于C++如何把位图保存到数组中的主要内容,如果未能解决你的问题,请参考以下文章
如何将从 C++ 发送的 cv::MAT 字节数组转换为 Java 中的位图?