迹忆客 专注技术分享

当前位置:主页 > 学无止境 > 编程语言 > C++ >

用 C++ 读取 PPM 文件

作者:迹忆客 最近更新:2023/08/24 浏览次数:

在本文中,我们将了解 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++ 编译器版本

下一篇:没有了

转载请发邮件至 1244347461@qq.com 进行申请,经作者同意之后,转载请以链接形式注明出处

本文地址:

相关文章

检查 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++ 中的内存泄漏 如果程序员先前分配给一个目的的部分内存被用于另一个目的,则称内存“泄漏”。

扫一扫阅读全部技术教程

社交账号
  • https://www.github.com/onmpw
  • qq:1244347461

最新推荐

教程更新

热门标签

扫码一下
查看教程更方便