用 C++ 读取 PPM 文件
在本文中,我们将了解 PPM 文件并使用 C++ 读取它们。
我们将首先讨论并了解 PPM 文件格式。 稍后,我们将学习用 C++ 读取 PPM 文件的分步过程。
PPM文件
每个图像文件都有一个附加到图像数据的标头/元数据。 标头包含有关图像和文件的不同信息。
在每个图像文件的开头,都存在文件格式的标识。 例如,BM被写在每个BMP文件的开头。
标头的其余部分包含宽度、高度、颜色数量、图像类型(二进制、灰度、16 色、256 色、24 位颜色)、压缩、偏移等信息。
可移植像素图(PPM)格式是一种格式相对简单的彩色图像。 PPM 有两种变体:一种是 P3,另一种是 P6。
P3是ASCII格式,而P6是二进制格式; ASCII 格式通常比二进制格式占用更多空间。 大多数图像都包含大量数据; 因此,空间至关重要。
例如,100x100 的小图像有 10000 个像素。 对于24位彩色图像,每个像素占用3个字节; 因此,需要 30000 字节来存储图像。
在本教程中,我们将讨论 P6 格式。 P3相对来说比较容易阅读和理解; 但是,学习 P6 格式后,您会变得更容易。
P6 是 24 位彩色图像。 P6的表头相对来说更简单; 您可以将其与 BMP 格式进行比较。
PPM 文件格式有 3 个标题行,占用 15 个字节的空间。 第一行具有 PPM 的标识符/签名(P3 或 P6)。
第二行有图像的宽度和空间(用空格分隔)信息,第三行有最大颜色值信息(例如15或255)。
P6
111 132
255
U`6Xe8Xk8Ul9Tg:Ve<Wd7Wd5Td5N_0MY/NZ3P^5Ub5Wc4T`2R`4T`4[d6Yd7NY1CM,@J-FQ/O\2Vg8Ra5FW.?O+@M,BQ/:F-0:**5)*1(6@-CQ5=H1;H-
如前所述,前三行包含标题信息。 第一行的P6标识图像的类型。
程序员可以检查标识符并根据格式读取图像。
第二行将宽度描述为 111,高度描述为 132。第三行将 255 描述为最大颜色值。
从第四行开始,我们以二进制格式存储图像数据(以字符的形式呈现在屏幕上,其中一些可以识别,但不是全部)。
用 C++ 读取 PPM 文件 要读取PPM文件,首先,我们必须读取其文件头以获取有关宽度和高度的信息。 这很重要,因为我们必须相应地声明动态内存并读取图像的数据。
P6是二进制格式; 因此,我们必须以二进制格式打开图像并进行相应的读取:
FILE *read;
read = fopen("west_1.ppm","rb");
在第一行中,声明 FILE 指针以存储第二行中的文件的处理程序。 注意 fopen 函数中的第二个参数是 rb,其中 r 表示读取,b 表示二进制(即文件的二进制读取模式)。
如果您对二进制存储不满意,请阅读这篇有关二进制文件的文章。 打开文件后,您必须读取文件头。
这是代码:
unsigned char header[15];
fread( header , 15, 1, read);
成功读取标头后,下一步就是提取宽度和高度信息以进一步进行。 由于这些信息是以字符形式存储的,我们可以通过以下代码将字符信息一一读取并转换为整数值(也可以使用atoi函数):
int x=0;
for (pos=3 ;header[pos]!='\n' && header[pos]!=' ';pos++)
x = x * 10 + (header[pos] - '0');
如前所述,标头的前两个字节包含 P6 值,第三个字节是下一行的分隔符。 因此,我们将从第 4 个元素开始,该元素从索引 3 开始。
我们逐个字符地读取以完成一个整数值(由空格字符或换行字符分隔)。 对于每个字符,我们必须减去 ASCII 值 0,因为数字的 ASCII 值从 48 或 32 十六进制(ASCII 值 0)开始。
例如,6的ASCII值换算为十进制为54; 因此,如果我们从 54 中减去 48,我们将得到 6。利用这个事实,我们可以从字符数组中获取整数值。
请参阅一个将字符数组 135 转换为等效整数的小示例。
integer x = 0 //initial integer value
x = x * 10 + 1 = 1
x = x * 10 + 3 = 13 //find out 10s by
x = x * 10 + 5 = 135 //find out 100s and x will have final integer value
获取宽度和高度信息后,我们将声明一个大小为宽 x 高 x 3 的动态数组,因为图像中有宽 x 高像素,每个图像有 3 个字节(红、绿、蓝,称为 RGB)。
unsigned char *image;
image = new unsigned char [width * height * 3];
我们使用 unsigned char
而不是 char,因为在简单 char 类型中,第一位用于符号(0 表示正数,1 表示负数)。
对于char来说,正值的最大范围是0到127,而我们要读取的信息是0到255。因此,我们需要一个unsigned char。
最后一步是读取图像,这非常简单。 只需将 fread 函数调用为:
fread( image , size, 1, file);//where size is width x height x 3
同样,第一个参数是一个用于存储图像数据的 char 数组,其大小已经很好地解释了。
可变图像具有所有像素 - 红色、绿色和蓝色值。 我们可以声明三个大小为宽 x 高的单独数组,或者我们可以通过记住第一个值是红色、第二个值是绿色、第三个值是蓝色来处理操作。
例如,如果我们想从图像中删除红色分量,我们必须在所有像素的第一个索引中分配0。
我们可以这样做:
for (i=0;i<size;i=i+3){
image[i] = 0;
请注意 for 循环中的增量步骤,其中我们将 i 增加了 3 而不是 1。这是因为第二个和第三个值是绿色和蓝色,我们不想更改绿色和蓝色值。
我们希望您对阅读 PPM 文件有完整的了解。 最后,让我们看一个完整的C++程序来读取、修改和写入PPM文件:
#include <iostream>
#include <cstdio>
using namespace std;
void readPPMHeader(FILE *file, unsigned char *header){
fread( header , 15, 1, file);
}
void readPPMImage(FILE *file, unsigned char *image, int size){
fread( image , size, 1, file);
}
void writePPM(FILE *file, unsigned char *header, unsigned char *image, int size){
fwrite( header , 15, 1, file);//writing header information
fwrite( image , size, 1, file);//writing image information
}
void removeRed(unsigned char *image, unsigned char *withoutredimage, int size){
int i;
for (i=0;i<size;i=i+3){
withoutredimage[i]=0;//red component is set to 0
withoutredimage[i+1]=image[i+1];
withoutredimage[i+2]=image[i+2];
}
}
void removeGreen(unsigned char *image, unsigned char *withoutgreenimage, int size){
int i;
for (i=0;i<size;i=i+3){
withoutgreenimage[i]=image[i];
withoutgreenimage[i+1]=0;//green component is set to 0
withoutgreenimage[i+2]=image[i+2];
}
}
void removeBlue(unsigned char *image, unsigned char *withoutblueimage, int size){
int i;
for (i=0;i<size;i=i+3){
withoutblueimage[i]=image[i];
withoutblueimage[i+1]=image[i+1];
withoutblueimage[i+2]=0;//blue component is set to 0
}
}
//To extract width & height from header
int getDimension(unsigned char *header, int &pos){
int dim=0;
for ( ;header[pos]!='\n' && header[pos]!=' ';pos++)
dim = dim * 10 + (header[pos] - '0');
return dim;
}
int main(){
FILE *read, *write1, *write2, *write3;
read = fopen("west_1.ppm","rb");
unsigned char header[15], *image;
readPPMHeader(read, header);
if (header[0]!='P' || header[1]!='6'){
cout << "Wrong file format\n";
return 0;
}
write1 = fopen("west_1_without_red.ppm","wb");
write2 = fopen("west_1_without_green.ppm","wb");
write3 = fopen("west_1_without_blue.ppm","wb");
int width, height, clrs, pos = 3;
width = getDimension(header, pos);
pos++;
height = getDimension(header, pos);
cout << "Width:" << width << "\tHeight:" << height << '\n';
image = new unsigned char [width * height * 3];
unsigned char *withoutredimage, *withoutgreenimage, *withoutblueimage;
withoutredimage = new unsigned char [width * height * 3];
withoutgreenimage = new unsigned char [width * height * 3];
withoutblueimage = new unsigned char [width * height * 3];
readPPMImage(read, image, width*height*3);
removeRed(image, withoutredimage, width*height*3);
writePPM(write1, header, withoutredimage, width * height * 3);
removeGreen(image, withoutgreenimage, width*height*3);
writePPM(write2, header, withoutgreenimage, width * height * 3);
removeBlue(image, withoutblueimage, width*height*3);
writePPM(write3, header, withoutblueimage, width * height * 3);
fclose(read);
fclose(write1);
fclose(write2);
fclose(write3);
return 0;
}
在这个程序中,我们读取了1个PPM文件,修改后写入了3个PPM文件。
相关文章
检查 Linux 中的 C++ 编译器版本
发布时间:2023/08/24 浏览次数:60 分类:C++
-
本文是关于检查 Linux 操作系统上安装的 C++ 编译器的版本。 此外,在撰写本文时,我们还将探讨 C++ 最新版本 C++ 11 的激活过程。检查 Linux 中的 C++ 编译器版本
C++ 中结构体和类的区别
发布时间:2023/08/23 浏览次数:52 分类:C++
-
本文解释了 C++ 中结构体和类之间的区别。 本文是针对最新版本的 C++ 编写的; 旧版本中的结构和类之间存在更多限制和差异。在大多数情况下,结构与类非常相似,但差异很少。 让我们一一
C++ 中的类模板继承
发布时间:2023/08/23 浏览次数:142 分类:C++
-
本文将讨论 C++ 中最流行和最常用的方法之一(即类模板)。C++ 中模板的添加带来了一种新的编码范式,称为通用编程。 现在,这是 C++ 程序员工具包的一个重要元素,是许多标准库的基础,也
C++ 中的Point 和 Line 类
发布时间:2023/08/23 浏览次数:158 分类:C++
-
C++ 中的 Point 和 Line 类是可以表示点和线的主要数据类型。 它提供了操作点、条形和向量的方法。C++ 中 Point 和 Line 类的基本用例 Point 和 Line 类是 C++ 语言的基本部分。
在 C++ 中获取类名
发布时间:2023/08/23 浏览次数:100 分类:C++
-
在本文中,我们将学习如何使用 C++ 编程语言获取类名。C++ 类概述 在 C++ 中,一切都与类和对象相关,每个类和对象都有其特征和过程。
在 C++ 类中初始化静态变量
发布时间:2023/08/23 浏览次数:52 分类:C++
-
我们将在这篇短文中学习如何在 C++ 中初始化静态变量。在 C++ 中初始化静态变量 C++类中静态变量的初始化就是给静态变量赋值的过程。
C++ 中的垃圾收集
发布时间:2023/08/23 浏览次数:148 分类:C++
-
在本文中,我们将了解 C++ 中的垃圾收集。垃圾收集作为一种内存管理技术 垃圾收集是编程语言中使用的内存管理技术之一。 它是一种自动内存管理技术,作为许多编程语言的功能添加。
在 C++ 中分配和释放内存
发布时间:2023/08/23 浏览次数:70 分类:C++
-
C++ 编程语言提供了几个分配和释放内存的函数。 这些函数包括 malloc、calloc、realloc、free、new 和 delete。让我们从 new 和 delete 运算符开始。使用 new 和 delete 运算符分配和释放内存
查找 C++ 中的内存泄漏
发布时间:2023/08/23 浏览次数:70 分类:C++
-
本文将使用 C++ 编程语言解释内存泄漏、其原因、如何识别它们以及如何防止它们。C++ 中的内存泄漏 如果程序员先前分配给一个目的的部分内存被用于另一个目的,则称内存“泄漏”。